From 9a4762dcca344ede61a638992e3c260c5b6a95f2 Mon Sep 17 00:00:00 2001 From: smirko-dev Date: Fri, 19 Nov 2021 12:09:30 +0100 Subject: [PATCH] First impl draft; Updated icons --- app/appointment.js | 19 ++-- app/index.js | 90 ++++++++++------- app/weather.js | 54 +++++++++++ common/constants.js | 6 +- common/utils.js | 20 ++-- companion/index.js | 112 ++++++++++++++++++++-- package.sdk4.json | 3 +- package.sdk5.json | 3 +- resources/index.gui | 4 +- resources/index.view | 4 +- resources/weather-cloudy.png | 4 +- resources/weather-fog.png | 4 +- resources/weather-hail.png | 4 +- resources/weather-hazy.png | 4 +- resources/weather-lightning-rainy.png | 4 +- resources/weather-lightning.png | 4 +- resources/weather-night-partly-cloudy.png | 4 +- resources/weather-night.png | 4 +- resources/weather-partly-cloudy.png | 4 +- resources/weather-partly-lightning.png | 4 +- resources/weather-partly-rainy.png | 4 +- resources/weather-partly-snowy-rainy.png | 4 +- resources/weather-partly-snowy.png | 4 +- resources/weather-pouring.png | 3 + resources/weather-rainy.png | 4 +- resources/weather-snowflake-alert.png | 3 + resources/weather-snowflake.png | 3 + resources/weather-snowy-heavy.png | 3 + resources/weather-snowy-rainy.png | 4 +- resources/weather-snowy.png | 4 +- resources/weather-storm.png | 4 +- resources/weather-sunny-alert.png | 3 + resources/weather-sunny.png | 4 +- resources/weather-windy.png | 4 +- settings/i18n/de-DE.po | 3 + settings/i18n/en-US.po | 3 + settings/i18n/ru-RU.po | 3 + settings/index.jsx | 18 ++-- 38 files changed, 312 insertions(+), 121 deletions(-) create mode 100644 app/weather.js create mode 100644 resources/weather-pouring.png create mode 100644 resources/weather-snowflake-alert.png create mode 100644 resources/weather-snowflake.png create mode 100644 resources/weather-snowy-heavy.png create mode 100644 resources/weather-sunny-alert.png diff --git a/app/appointment.js b/app/appointment.js index af91e67..f3f0767 100644 --- a/app/appointment.js +++ b/app/appointment.js @@ -1,14 +1,14 @@ import { inbox } from "file-transfer"; import { readFileSync } from "fs"; -import { dataFile, dataType } from "../common/constants"; +import { calendarFile, calendarType } from "../common/constants"; import { toEpochSec } from "../common/utils"; let data; -let handleCalendarUpdatedCallback; +let handleCallback; export function initialize(callback) { - handleCalendarUpdatedCallback = callback; + handleCallback = callback; data = loadData(); inbox.addEventListener("newfile", fileHandler); fileHandler(); @@ -43,14 +43,17 @@ function fileHandler() { let fileName; do { fileName = inbox.nextFile(); - data = loadData(); - updatedData(); + if (fileName === calendarFile) { + console.log('Load ' + fileName); + data = loadData(); + updatedData(); + } } while (fileName); } function loadData() { try { - return readFileSync(`/private/data/${dataFile}`, dataType); + return readFileSync(`/private/data/${calendarFile}`, calendarType); } catch (ex) { console.error(`Appointment: loadData() failed. ${ex}`); return; @@ -66,7 +69,7 @@ function existsData() { } function updatedData() { - if (typeof handleCalendarUpdatedCallback === "function" && existsData()) { - handleCalendarUpdatedCallback(); + if (typeof handleCallback === "function" && existsData()) { + handleCallback(); } } diff --git a/app/index.js b/app/index.js index 27644cb..de6b5fa 100644 --- a/app/index.js +++ b/app/index.js @@ -4,8 +4,10 @@ import { battery } from "power"; import { display } from "display"; import { today } from 'user-activity'; import { me as device } from "device"; +import { units } from "user-settings"; import * as fs from "fs"; import * as appointment from "./appointment"; +import * as weather from "./weather"; import * as clock from "./clock"; import * as messaging from "messaging"; import { fromEpochSec, timeString } from "../common/utils"; @@ -19,10 +21,8 @@ const appointmentsLabel = document.getElementById("appointmentsLabel"); const batteryImage = document.getElementById("batteryImage"); const batteryLabel = document.getElementById("batteryLabel"); -const activityIcon = document.getElementById("activityIcon"); -const activityLabel = document.getElementById("activityLabel"); - -//TODO: let activityIntervalID = 0; +const infoIcon = document.getElementById("infoIcon"); +const infoLabel = document.getElementById("infoLabel"); const INVISIBLE = 0.0; const VISIBLE = 0.8; @@ -40,17 +40,22 @@ me.onunload = saveSettings; // Load settings at startup let settings = loadSettings(); -applySettings(settings.activity, settings.color); +applySettings(settings.info, settings.color); // Apply and store settings -function applySettings(activity, color) { - if (typeof activity !== 'undefined') { - activityIcon.image = `${activity}.png`; - settings.activity = activity; +function applySettings(info, color) { + if (typeof info !== 'undefined') { + if (info === 'weather') { + renderWeather(weather.current()); + } + else { + infoIcon.image = `${info}.png`; + settings.info = info; + } } if (typeof color !== 'undefined') { hourLabel.style.fill = color; - activityIcon.style.fill = color; + infoIcon.style.fill = color; settings.color = color; } } @@ -63,7 +68,7 @@ function loadSettings() { catch (ex) { // Default values return { - activity: "steps", + info: "steps", color: "#2490DD" }; } @@ -76,11 +81,11 @@ function saveSettings() { // Update settings messaging.peerSocket.onmessage = (evt) => { - if (evt.data.key === "activity") { + if (evt.data.key === "info") { applySettings(evt.data.value, settings.color); } else if (evt.data.key === "color") { - applySettings(settings.activity, evt.data.value); + applySettings(settings.info, evt.data.value); } renderAppointment(); } @@ -103,6 +108,11 @@ appointment.initialize(() => { renderAppointment(); }); +weather.initialize(data => { + // Update weather with new data + renderWeather(data); +}); + display.addEventListener("change", () => { if (display.on) { // Update appointment and battery on display on @@ -110,15 +120,15 @@ display.addEventListener("change", () => { renderBattery(); } else { - // Stop updating activity info - hideActivity(); + // Stop updating info + hideInfo(); } }); // Hide event when touched appointmentsLabel.addEventListener("mousedown", () => { - showActivity(); - updateActivity(); + showInfo(); + updateInfo(); }) function renderAppointment() { @@ -127,40 +137,48 @@ function renderAppointment() { if (event) { const date = fromEpochSec(event.startDate); appointmentsLabel.text = timeString(date) + " " + event.title; - hideActivity(); + hideInfo(); } else { - showActivity(); - updateActivity(); + showInfo(); + updateInfo(); } } -function hideActivity() { - activityIcon.style.opacity = INVISIBLE; - activityLabel.style.opacity = INVISIBLE; +function renderWeather(data) { + data = units.temperature === "F" ? toFahrenheit(data) : data; + infoLabel.text = `${data.temperature}\u00B0 ${data.unit}`; + infoIcon.image = `${data.icon}`; +} + +function hideInfo() { + infoIcon.style.opacity = INVISIBLE; + infoLabel.style.opacity = INVISIBLE; appointmentsLabel.style.opacity = VISIBLE; - //TODO: clearInterval(activityIntervalID); } -function showActivity() { - activityIcon.style.opacity = VISIBLE; - activityLabel.style.opacity = VISIBLE; +function showInfo() { + infoIcon.style.opacity = VISIBLE; + infoLabel.style.opacity = VISIBLE; appointmentsLabel.style.opacity = INVISIBLE; - //TODO: activityIntervalID = setInterval(updateActivity, 1500); } -function updateActivity() { - if (settings.activity === 'distance') { - activityLabel.text = today.adjusted.distance; +function updateInfo() { + if (settings.info === 'distance') { + infoLabel.text = today.adjusted.distance; + } + else if (settings.info === 'floors') { + infoLabel.text = today.adjusted.elevationGain; } - else if (settings.activity === 'floors') { - activityLabel.text = today.adjusted.elevationGain; + else if (settings.info === 'calories') { + infoLabel.text = today.adjusted.calories; } - else if (settings.activity === 'calories') { - activityLabel.text = today.adjusted.calories; + else if (settings.info === 'weather') { + // TODO weather + console.log('updateInfo -> weather not implemented yet'); } else { - activityLabel.text = today.adjusted.steps; + infoLabel.text = today.adjusted.steps; } } diff --git a/app/weather.js b/app/weather.js new file mode 100644 index 0000000..e7352d4 --- /dev/null +++ b/app/weather.js @@ -0,0 +1,54 @@ +import { inbox } from "file-transfer"; +import { readFileSync } from "fs"; + +import { weatherFile, weatherType } from "../common/constants"; + +let data; +let handleCallback; + +export function current() { + return data; +} + +export function initialize(callback) { + handleCallback = callback; + data = loadData(); + inbox.addEventListener("newfile", fileHandler); + fileHandler(); + updatedData(); +} + +function fileHandler() { + let fileName; + do { + fileName = inbox.nextFile(); + if (fileName === weatherFile) { + console.log('Load ' + fileName); + data = loadData(); + updatedData(); + } + } while (fileName); +} + +function loadData() { + try { + return readFileSync(`/private/data/${weatherFile}`, weatherType); + } catch (ex) { + console.error(`loadData() failed. ${ex}`); + return; + } +} + +function existsData() { + if (data === undefined) { + console.warn("No data found."); + return false; + } + return true; +} + +function updatedData() { + if (typeof handleCallback === "function" && existsData()) { + handleCallback(data); + } +} diff --git a/common/constants.js b/common/constants.js index bef9528..9d07bb3 100644 --- a/common/constants.js +++ b/common/constants.js @@ -1,5 +1,7 @@ -export const dataType = "cbor"; -export const dataFile = "appointments.cbor"; +export const calendarType = "cbor"; +export const calendarFile = "appointments.cbor"; export const settingsType = "cbor"; export const settingsFile = "settings.cbor"; +export const weatherType = "cbor"; +export const weatherFile = "weather.cbor"; export const millisecondsPerMinute = 1000 * 60; diff --git a/common/utils.js b/common/utils.js index a6bd3cb..a9dc262 100644 --- a/common/utils.js +++ b/common/utils.js @@ -9,6 +9,14 @@ export function zeroPad(i) { return i; } +// Convert celcius to fahrenheit +export function toFahrenheit(data) { + if (data.unit.toLowerCase() === "celsius") { + data.temperature = Math.round((data.temperature * 1.8) + 32.0); + data.unit = "Fahrenheit"; + } + return data; +} // Return day as a string export function dayString(day) { if (day == 1) { @@ -20,18 +28,6 @@ export function dayString(day) { else if (day == 3) { return "3rd" } - if (day == 21) { - return "21st" - } - else if (day == 22) { - return "22nd" - } - else if (day == 23) { - return "23rd" - } - if (day == 31) { - return "31st" - } return day.toString() + "th"; } diff --git a/companion/index.js b/companion/index.js index 3167783..4fc0814 100644 --- a/companion/index.js +++ b/companion/index.js @@ -3,16 +3,18 @@ import * as cbor from "cbor"; import { me as companion } from "companion"; import { outbox } from "file-transfer"; -import { toEpochSec } from "../common/utils"; -import { dataFile, millisecondsPerMinute } from "../common/constants"; +import { toEpochSec, findWeatherConditionIcon } from "../common/utils"; +import { calendarFile, weatherFile, millisecondsPerMinute } from "../common/constants"; import { settingsStorage } from "settings"; import * as messaging from "messaging"; +import { weather, WeatherCondition } from "weather"; + // Update user settings settingsStorage.onchange = function(evt) { if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) { - if (evt.key === "activity") { + if (evt.key === "info") { let data = JSON.parse(evt.newValue); messaging.peerSocket.send({ key: evt.key, @@ -28,10 +30,39 @@ settingsStorage.onchange = function(evt) { } } +// Set update interval and callback companion.wakeInterval = 15 * millisecondsPerMinute; -companion.addEventListener("wakeinterval", refreshEvents); +companion.addEventListener("wakeinterval", refreshData); + +function refreshData() { + refreshEvents(); + refreshWeather(); +} -refreshEvents(); +refreshData(); + +// Refresh weather data +function refreshWeather() { + weather + .getWeatherData() + .then((data) => { + if (data.locations.length > 0) { + sendWeatherFile({ + temperature: Math.floor(data.locations[0].currentWeather.temperature), + condition: data.locations[0].currentWeather.weatherCondition, + icon: findWeatherConditionIcon(data.locations[0].currentWeather.weatherCondition), + location: data.locations[0].name, + unit: data.temperatureUnit + }); + } + else { + console.warn("No data for this location.") + } + }) + .catch((ex) => { + console.error(ex); + }); +} // Refresh calendar data function refreshEvents() { @@ -63,7 +94,7 @@ function refreshEvents() { }); }); if (dataEvents && dataEvents.length > 0) { - sendData(dataEvents); + sendCalendarFile(dataEvents); } }) .catch(error => { @@ -74,8 +105,73 @@ function refreshEvents() { } // Send data -function sendData(data) { - outbox.enqueue(dataFile, cbor.encode(data)).catch(error => { +function sendCalendarFile(data) { + outbox.enqueue(calendarFile, cbor.encode(data)).catch(error => { console.warn(`Failed to enqueue data. Error: ${error}`); }); } + +// Send data +function sendWeatherFile(data) { + outbox.enqueue(weatherFile, cbor.encode(data)).catch(error => { + console.warn(`Failed to enqueue data. Error: ${error}`); + }); +} + +// Find the icon for a weather conditionCode +function findWeatherConditionIcon(condition) { + switch (condition) { + case WeatherCondition.ClearNight: + case WeatherCondition.MostlyClearNight: + return 'weather-night.png'; + case WeatherCondition.Cloudy: + return 'weather-cloudy.png'; + case WeatherCondition.Cold: + return 'weather-snowflake-alert.png'; + case WeatherCondition.Flurries: + return ''; + case WeatherCondition.Fog: + return 'weather-fog.png'; + case WeatherCondition.FreezingRain: + return 'weather-snowy-rainy.png'; + case WeatherCondition.HazyMoonlight: + case WeatherCondition.HazySunshineDay: + return 'weather-hazy.png'; + case WeatherCondition.Hot: + return 'weather-sunny-alert.png'; + case WeatherCondition.Ice: + return 'weather-snowflake.png'; + case WeatherCondition.IntermittentCloudsDay: + case WeatherCondition.MostlyCloudyDay: + return 'weather-partly-cloudy.png'; + case WeatherCondition.IntermittentCloudsNight: + case WeatherCondition.MostlyCloudyNight: + return 'weather-night-partly-cloudy.png'; + case WeatherCondition.MostlyCloudyWithFlurriesDay: + case WeatherCondition.MostlyCloudyWithFlurriesNight: + case WeatherCondition.MostlyCloudyWithShowersDay: + case WeatherCondition.MostlyCloudyWithShowersNight: + case WeatherCondition.MostlyCloudyWithSnowDay: + case WeatherCondition.MostlyCloudyWithSnowNight: + case WeatherCondition.MostlyCloudyWithThunderstormsDay: + case WeatherCondition.MostlyCloudyWithThunderstormsNight: + case WeatherCondition.MostlySunnyDay: + case WeatherCondition.Overcast: + case WeatherCondition.PartlyCloudyNight: + case WeatherCondition.PartlyCloudyWithShowersNight: + case WeatherCondition.PartlyCloudyWithThunderstormsNight: + case WeatherCondition.PartlySunnyDay: + case WeatherCondition.PartlySunnyWithFlurriesDay: + case WeatherCondition.PartlySunnyWithShowersDay: + case WeatherCondition.PartlySunnyWithThunderstormsDay: + case WeatherCondition.Rain: + case WeatherCondition.RainAndSnow: + case WeatherCondition.Showers: + case WeatherCondition.Sleet: + case WeatherCondition.Snow: + case WeatherCondition.SunnyDay: + case WeatherCondition.Thunderstorms: + case WeatherCondition.Windy: + return ''; + } +} diff --git a/package.sdk4.json b/package.sdk4.json index 900f7ff..2f3e9a9 100644 --- a/package.sdk4.json +++ b/package.sdk4.json @@ -22,7 +22,8 @@ "requestedPermissions": [ "access_calendar", "run_background", - "access_activity" + "access_activity", + "access_location" ], "buildTargets": [ "higgs", diff --git a/package.sdk5.json b/package.sdk5.json index bff0e8b..db75052 100644 --- a/package.sdk5.json +++ b/package.sdk5.json @@ -22,7 +22,8 @@ "requestedPermissions": [ "access_calendar", "run_background", - "access_activity" + "access_activity", + "access_location" ], "buildTargets": [ "atlas", diff --git a/resources/index.gui b/resources/index.gui index 0ebc4ae..bb5705a 100644 --- a/resources/index.gui +++ b/resources/index.gui @@ -5,6 +5,6 @@