├── README.md
├── app
├── index.js
├── ping.js
└── settings.js
├── common
├── const.js
├── sconst.js
├── sutils.js
└── utils.js
├── companion
├── index.js
├── weather.js
└── weatherEnv.js
├── info.php
├── package.json
├── resources
├── common.defs
├── device.css
├── device~336x336.css
├── device~348x250.css
├── images
│ ├── activeZoneMinutes.png
│ ├── battery.png
│ ├── btoff.png
│ ├── bton.png
│ ├── calories.png
│ ├── distance.png
│ ├── down-arrow.png
│ ├── elevationGain.png
│ ├── hr.png
│ ├── steps.png
│ ├── up-arrow.png
│ └── weather
│ │ ├── weather_01.png
│ │ ├── weather_02.png
│ │ ├── weather_03.png
│ │ ├── weather_04.png
│ │ ├── weather_05.png
│ │ ├── weather_06.png
│ │ ├── weather_07.png
│ │ ├── weather_08.png
│ │ ├── weather_09.png
│ │ ├── weather_10.png
│ │ ├── weather_11.png
│ │ └── weather_error.png
├── index.gui
├── index.view
├── styles.css
├── widget.defs
└── widgets.gui
├── screenshots
├── ionic
│ ├── Programmer's-Watch-screenshot (1).png
│ └── Programmer's-Watch-screenshot (2).png
├── sense
│ ├── Programmer's-Watch-screenshot (1).png
│ └── Programmer's-Watch-screenshot (2).png
└── versa
│ ├── Programmer's-Watch-screenshot (1).png
│ └── Programmer's-Watch-screenshot (2).png
└── settings
├── const.js
└── index.jsx
/README.md:
--------------------------------------------------------------------------------
1 | # programmersWatchFitbit
2 |
3 | Inspired by a clockface with the same name from Facer, made by user Lobby: https://www.facer.io/watchface/cUe0O8YjmA
4 | I added more features:
5 | - Wind speed
6 | - BT connection info
7 | - Heart rate icon
8 | - Click on the bottom of the screen will show the binary code of the selected decimal value below
9 | - INFO SCRIPT capability: The clockface can send a request to your script with some data in json format with raw POST, and it will display your script result under the DATE line. Info Script help: https://czandor.hu/fitbit/prog/info-help.php
10 |
11 | Implemented features:
12 | - Weather
13 | - Health information
14 |
15 |
16 | For weather, fill your OWM API key in /companion/weatherEnv.js file
17 |
18 | Screenshots:
19 |
20 | .png?raw=true)
21 | .png?raw=true)
22 |
23 | .png?raw=true)
24 | .png?raw=true)
25 |
26 | .png?raw=true)
27 | .png?raw=true)
28 |
29 | You can download the compiled version from Fitbit Gallery:
30 | https://gallery.fitbit.com/details/b36d8ea6-c803-45fe-ad34-91dbe8d08e89
31 |
32 | This clockface is completely free. Please support my work if you can.
33 | ☕ Buy me a coffee: https://czandor.hu/paypal/prog
34 |
35 |
--------------------------------------------------------------------------------
/app/index.js:
--------------------------------------------------------------------------------
1 | import clock from "clock";
2 | import document from "document";
3 | import { preferences } from "user-settings";
4 | import * as util from "../common/utils";
5 | import * as C from "../common/const";
6 | import { units} from "user-settings";
7 | import { user } from "user-profile";
8 | import { today as todayActivity } from "user-activity";
9 | import { primaryGoal } from "user-activity";
10 | import { goals } from "user-activity";
11 |
12 | import * as messaging from "messaging";
13 |
14 | import { me } from "appbit";
15 | import { display } from "display";
16 |
17 | import { vibration } from "haptics";
18 | import { HeartRateSensor } from "heart-rate";
19 | import { BodyPresenceSensor } from "body-presence";
20 | import { battery } from "power";
21 | import { charger } from "power";
22 | import { me as device } from "device";
23 | if (!device.screen) device.screen = { width: 348, height: 250 };
24 | device.ionic=(device.screen.width==348);
25 | device.versa=(device.screen.width==300);
26 | device.sense=(device.screen.width==336);
27 | device.noBarometer=(device.modelName=='Versa Lite');
28 |
29 | import * as ping from "./ping";
30 | import * as settings from "./settings";
31 | // Update the clock every minute
32 | clock.granularity = "minutes";
33 |
34 | settings.load();
35 |
36 | // Get a handle on the element
37 |
38 |
39 |
40 | let bodyPresent=false;
41 | let hrbpm=0;
42 | let hrm=null;
43 | let body=null;
44 |
45 | let bottomNumberType=0;
46 | let btActive=-1;
47 |
48 | setInterval(function(){ping.ping(0);},C.PING_PERIOD*C.MILLISECONDS_PER_MINUTE);
49 | let bottomNumberResetTimer=null;
50 | document.getElementById("buttonLow").onclick=function(){
51 | if(settings.getSettings(C.SettingsTapVibe)) util.vibe(C.VIBRATE_PATTERNS[settings.getSettings(C.SettingsTapVibe)],vibration);
52 | bottomNumberType++;
53 | if(bottomNumberType > C.BottomNumberTypes) bottomNumberType=C.BottomNumberNothing;
54 | //if(bottomNumberType>=C.BottomNumberCals && bottomNumberType <= C.BottomNumberHR) needHealthRefresh=true;
55 | //if(bottomNumberType=C.BottomNumberSec) needWeatherRefresh=true;
56 | //updateHealthBlock();
57 | /*if(bottomNumberResetTimer) clearTimeout(bottomNumberResetTimer);
58 | bottomNumberResetTimer=setTimeout(function(){
59 | resetBottomNumber();
60 | },2000);*/
61 | tick();
62 | }
63 | if (me.permissions.granted("access_heart_rate")) {
64 | if (HeartRateSensor) {
65 | hrm = new HeartRateSensor({ frequency: 1 });
66 | hrm.addEventListener("reading", () => {
67 | //if(LOG) console.log("Current heart rate: " + hrm.heartRate);
68 | hrbpm = hrm.heartRate;
69 | updateHR();
70 | //drawDelayed();
71 | });
72 | hrm.start();
73 | }
74 | if (BodyPresenceSensor) {
75 | body = new BodyPresenceSensor();
76 | body.addEventListener("reading", () => {
77 | bodyPresent=body.present;
78 | updateHR();
79 | if (!bodyPresent) {
80 | hrm.stop();
81 | } else {
82 | hrm.start();
83 | }
84 | //drawDelayed();
85 | });
86 | body.start();
87 | }
88 |
89 |
90 | //if(LOG) console.log("Started heart rate: " + JSON.stringify(hrm));
91 | }
92 | else{
93 | //if(LOG) console.log("Heart rate not started, no permission");
94 | }
95 | me.onunload=function(evt){settings.save();};
96 | display.onchange = function() {
97 | //let today = new Date();
98 | if (!display.on) { //képernyő KIKAPCSOLVA
99 | onDisplayOff();
100 | }
101 | else{ //képernyő BEKAPCSOLVA
102 | onDisplayOn();
103 | }
104 | }
105 |
106 | function onDisplayOn(){
107 | //console.log("képernyő bekapcs, actualPet: "+actualPet);
108 | //if(util.getRandomInt(0,50) == 0) bgdeath.animate("enable");
109 | //refreshScreen(new Date());
110 | if(hrm) hrm.start();
111 | if(body) body.start();
112 | }
113 | function onDisplayOff(){
114 | //console.log("képernyő kikapcs");
115 | if(hrm) hrm.stop();
116 | if(body) body.stop();
117 | stopSeconds();
118 | resetBottomNumber();
119 | }
120 | function resetBottomNumber(){
121 | if(bottomNumberResetTimer){
122 | clearTimeout(bottomNumberResetTimer);
123 | bottomNumberResetTimer=null;
124 | }
125 | bottomNumberType=C.BottomNumberNothing;
126 | tick();
127 | }
128 | let secondsTimer=null;
129 | function startSeconds(){
130 | stopSeconds();
131 | //return;
132 | let today=new Date();
133 | setTimeout(function(){
134 | updateSeconds();
135 | secondsTimer = setInterval(function(){
136 | updateSeconds();
137 | },1000);
138 | },1000-today.getMilliseconds());
139 | }
140 | function stopSeconds(){
141 | if(secondsTimer) clearInterval(secondsTimer);
142 | secondsTimer=null;
143 | }
144 | function tick(){
145 | let today = new Date();
146 |
147 |
148 | //console.log('tick '+today.getTime()+' '+today.getMilliseconds());
149 | //if(!secondsTimer) updateSeconds(today);
150 | //else stopSeconds();
151 | stopSeconds();
152 | updateSeconds();
153 | startSeconds();
154 |
155 | let hours = today.getHours();
156 | let mins = today.getMinutes();
157 |
158 | //return;
159 | //updateSeconds(today);
160 |
161 |
162 | if (settings.is12h(preferences)) {
163 | // 12h format
164 | document.getElementById("ampm").text=(hours<12)?'AM':'PM';
165 | hours = hours % 12 || 12;
166 | document.getElementById("hourStyle").text='12 hours';
167 | } else {
168 | // 24h format
169 | //hours = util.zeroPad(hours);
170 | document.getElementById("hourStyle").text='24 hours';
171 | document.getElementById("ampm").text=='';
172 | }
173 | let hoursText=util.monoDigits(hours);
174 | let minsText = util.monoDigits(util.zeroPad(mins));
175 | document.getElementById("time").text=`${hoursText}:${minsText}`;
176 | let today2=new Date(today.getTime()+settings.holder.timeZone2Offset*1000*60);
177 | document.getElementById("hourHand").groupTransform.rotate.angle = (360 / 12) * today2.getHours() + (360 / 12 / 60) * today2.getMinutes();
178 | document.getElementById("minuteHand").groupTransform.rotate.angle = (360 / 60) * today2.getMinutes();// + (360 / 60 / 60) * seconds;
179 |
180 | //return;
181 | //return;
182 | //document.getElementById("secondsAnim").to=(360 / 60) * seconds+(2);
183 | //document.getElementById("secondsAnim2").to=(360 / 60) * seconds;
184 | //document.getElementById("secondsHand").animate("enable");
185 | //return;
186 | //date.text=`${C.WEEKDAYS_LONG_DEFAULT[today.getDay()]}, ${C.MONTHNAMES_DEFAULT[today.getMonth()]}
187 |
188 |
189 | drawDelayed();
190 | }
191 | // Update the element every tick with the current time
192 | clock.ontick = (evt) => {
193 | tick();
194 | }
195 | function drawDelayed(delay){
196 | if(!delay) {
197 | setTimeout(function(){drawDelayed(true);return;},150);
198 | return;
199 | }
200 | //return;
201 | let today = new Date();
202 | let day = today.getDate();
203 | let month = today.getMonth()+1;
204 | let year = today.getYear()+1900;
205 |
206 | document.getElementById("date1").text='DATE '+C.WEEKDAYS_DEFAULT[today.getDay()]+' '+util.zeroPad(day)+' '+util.zeroPad(month)+''+C.MONTHNAMES_DEFAULT[month-1];
207 | document.getElementById("date2").text=util.zeroPad(year-2000);
208 |
209 | if(bottomNumberType==C.BottomNumberNothing) {
210 | setBottomNumbers(0);
211 | if(device.versa){
212 | document.getElementById("secondsBlock").style.opacity=1;
213 | document.getElementById("ampmBlock").style.opacity=0;
214 | }
215 | }
216 | else if(device.versa){
217 | document.getElementById("secondsBlock").style.opacity=0;
218 | document.getElementById("ampmBlock").style.opacity=1;
219 | }
220 | //return;
221 | refreshScreen();
222 | ping.ping(0,tick);
223 |
224 | if(settings.isDisconnected(messaging)){
225 | if(btActive!=0){
226 | btActive=0;
227 | document.getElementById("btImage").href='images/btoff.png';
228 | document.getElementById("btImage").style.opacity=0.7;
229 | }
230 | }
231 | else{
232 | if(btActive!=1){
233 | btActive=1;
234 | document.getElementById("btImage").href='images/bton.png';
235 | document.getElementById("btImage").style.opacity=1;
236 | }
237 | }
238 |
239 | }
240 |
241 | function updateSeconds(){
242 | //return;
243 | let today=new Date();
244 | let seconds = today.getSeconds();
245 | document.getElementById("seconds").text = util.monoDigits(util.zeroPad(seconds));
246 | //return;
247 | document.getElementById("secondsBar1").width=Math.ceil((45/60)*seconds);
248 | document.getElementById("secondsBar2").width=Math.floor((45/60)*seconds);
249 | document.getElementById("secondsHand").groupTransform.rotate.angle = (360 / 60) * seconds;
250 | updateUnixTime(today,0);
251 |
252 | }
253 | function refreshScreen(){
254 | let today=new Date();
255 | //return;
256 | document.getElementById("timeZone").text=settings.getTimezoneName(1);
257 | document.getElementById("timeZone2").text=settings.getTimezoneName(2);
258 | document.getElementById("additionalTimes1").text=settings.getSunrise(preferences);
259 | document.getElementById("additionalTimes2").text=settings.getSunset(preferences)+' UTC '+util.zeroPad(today.getUTCHours())+':'+util.zeroPad(today.getUTCMinutes());
260 | //return;
261 | updateHealthBlock();
262 | updateGeekField();
263 | updateWeather();
264 | //return true;
265 | }
266 | function updateWeather(){
267 | //return;
268 | document.getElementById("weatherIcon").href=settings.getWeatherIcon();
269 | document.getElementById("weather1").text=settings.getWeatherLine(1,units,device);
270 | document.getElementById("weather2").text=settings.getWeatherLine(2,units,device);
271 | document.getElementById("weather3").text=settings.getWeatherLine(3,units,device);
272 | document.getElementById("weather4").text=settings.getWeatherLine(4,units,device);
273 | }
274 | function updateUnixTime(today,counter){
275 | //let msec=today.getTime()+counter;
276 | let msec=Math.floor(today.getTime()/1000);
277 | document.getElementById("unixTime").text="UNIX TIMESTAMP "+((bottomNumberType==C.BottomNumberSec)?'*':'')+(msec);
278 | if(bottomNumberType==C.BottomNumberSec) setBottomNumbers(msec);
279 | //if(counter < 1000) setTimeout(function(){updateUnixTime(today,(counter+1));},1);
280 | }
281 | let bottomNumber=-1;
282 | function setBottomNumbers(num){
283 | if(bottomNumber == num) return;
284 | bottomNumber = num;
285 | /*let binDarab=15;
286 | //let base = (number).toString(2);
287 | for(let i= 0 ; i <= (binDarab*2) ; i++){
288 | if(i <= binDarab) document.getElementById("bottomText"+Math.pow(2,i)).style.textDecoration=((number >> i) & 1)?'underline':'none';
289 | else document.getElementById("bottomText"+Math.pow(2,i-(binDarab+1))).style.fill=((number >> i) & 1)?'white':'#3de4ea';
290 | }
291 | */
292 | let binDarab=32;
293 | let calcNum=0;
294 | for(let i= 0 ; i < (binDarab) ; i++){
295 | //console.log(calcNum +'-'+ num);
296 | //document.getElementById("bottomText"+Math.pow(2,i)).style.textDecoration=((num >> i) & 1)?'underline':'none';
297 | //document.getElementById("bottomText"+Math.pow(2,i)).style.fill=((num >> i) & 1)?'white':'#3de4ea';
298 | if(i < 14) document.getElementById("bottomText"+(i+1)).style.textDecoration=((num >> i) & 1)?'underline':'none';
299 | //else document.getElementById("bottomText"+(i+1)).text=((num >> i) & 1)?'-':'_';
300 | //document.getElementById("bottomText"+(i+1)).style.fill=((num >> i) & 1)?'white':'#3de4ea';
301 | else if (calcNum <= num) document.getElementById("bottomText"+(i+1)).style.opacity=((num >> i) & 1)?1:0.4;
302 | else document.getElementById("bottomText"+(i+1)).style.opacity=0;
303 | calcNum+=Math.pow(2,i);
304 |
305 | }
306 | //document.getElementById("bottomTextEnd").text='+'+Math.floor(num/2048)+'*2048';
307 | }
308 | let prevHR="";
309 | function updateHR(){
310 | let hrText=((bottomNumberType==C.BottomNumberHR)?'*':'')+((hrbpm==0 || bodyPresent==false)?"--":hrbpm);
311 | if(prevHR == hrText) return;
312 | prevHR == hrText;
313 |
314 | let hrOpacity=0.5;
315 | let hrZone=user.heartRateZone(hrbpm);
316 | if(bodyPresent) switch(hrZone){
317 | case "fat-burn":
318 | case "custom":
319 | hrOpacity=0.8;
320 | break;
321 | case "cardio":
322 | case "above-custom":
323 | hrOpacity=0.9;
324 | break;
325 | case "peak":
326 | hrOpacity=1;
327 | break;
328 | case "out-of-range":
329 | case "below-custom":
330 | default:
331 | hrOpacity=0.7;
332 | break;
333 | }
334 | document.getElementById("hrImage").style.opacity=hrOpacity;
335 | document.getElementById("hrText").text=hrText;
336 | }
337 | function updateHealthBlock(){
338 |
339 | updateHR();
340 |
341 | let steps=(todayActivity.adjusted.steps || todayActivity.local.steps || 0);
342 | let distance=(todayActivity.adjusted.distance || todayActivity.local.distance || 0);
343 | let minutes=((todayActivity.adjusted.activeZoneMinutes || todayActivity.local.activeZoneMinutes || {total:0}).total);//(todayActivity.adjusted.activeMinutes || todayActivity.local.activeMinutes || 0);
344 | let calories=(todayActivity.adjusted.calories || todayActivity.local.calories || 0);
345 | let elevation=(todayActivity.adjusted.elevationGain || todayActivity.local.elevationGain || 0);
346 | let batteryLevel=Math.floor(battery.chargeLevel);
347 | let spacer=" ";
348 | if(device.ionic) spacer=" ";
349 | let distIsMetric=settings.isDistanceMetric(units);
350 | document.getElementById("health1").text=((device.ionic)?"BATT "+((bottomNumberType==C.BottomNumberBatt)?"*":"")+batteryLevel+spacer:'')+"KCALS "+((bottomNumberType==C.BottomNumberCals)?"*":"")+calories+spacer+"STEPS "+((bottomNumberType==C.BottomNumberSteps)?"*":"")+steps;
351 | document.getElementById("health2").text=((device.ionic)?((distIsMetric)?"METERS":"MILES")+" "+((bottomNumberType==C.BottomNumberDist)?((distIsMetric)?"*":"**"):"")+(distance*((distIsMetric)?1:0.000621371192)).toFixed((distIsMetric)?0:2)+spacer:"")+"A.Z.MINS "+((bottomNumberType==C.BottomNumberMins)?"*":"")+minutes+spacer+"FLOORS "+((bottomNumberType==C.BottomNumberFloors)?"*":"")+((device.noBarometer)?'N/A':elevation);
352 | if(!device.ionic) document.getElementById("health3").text="BATT "+((bottomNumberType==C.BottomNumberBatt)?"*":"")+batteryLevel+spacer+((distIsMetric)?"METERS":"MILES")+" "+((bottomNumberType==C.BottomNumberDist)?((distIsMetric)?"*":"**"):"")+(distance*((distIsMetric)?1:0.000621371192)).toFixed((distIsMetric)?0:2);
353 |
354 | //document.getElementById("hrText").style.textDecoration=(bottomNumberType==C.BottomNumberHR)?'underline':'none';
355 | let goalImage=settings.getBarImage(primaryGoal);
356 |
357 | let goalMax=1;
358 | let goal=1;
359 | let bottomNumber=-1;
360 | switch(goalImage){
361 | case "steps":
362 | goalMax=(goals.steps || 10000);
363 | goal=steps;
364 | break;
365 | case "distance":
366 | goalMax=(goals.distance || 8000);
367 | goal=distance;
368 | break;
369 | case "calories":
370 | goalMax=(goals.calories || 2400);
371 | goal=calories;
372 | break;
373 | case "elevationGain":
374 | goalMax=(goals.elevationGain || 5);
375 | goal=elevation;
376 | break;
377 | case "activeZoneMinutes":
378 | goalMax=(goals.activeZoneMinutes || {total:22}).total;
379 | goal=minutes;
380 | break;
381 | case "battery":
382 | goalMax=100;
383 | goal=batteryLevel;
384 | break;
385 | }
386 | switch (bottomNumberType){
387 | case C.BottomNumberSteps:
388 | bottomNumber=steps;
389 | break;
390 | case C.BottomNumberDist:
391 | bottomNumber=(distance*((distIsMetric)?1:1.0936)); //yards for us
392 | break;
393 | case C.BottomNumberCals:
394 | bottomNumber=calories;
395 | break;
396 | case C.BottomNumberFloors:
397 | bottomNumber=elevation;
398 | break;
399 | case C.BottomNumberMins:
400 | bottomNumber=minutes;
401 | break;
402 | case C.BottomNumberBatt:
403 | bottomNumber=batteryLevel;
404 | break;
405 | case C.BottomNumberSteps:
406 | bottomNumber=steps;
407 | break;
408 | case C.BottomNumberHR:
409 | bottomNumber=hrbpm;
410 | break;
411 | }
412 | //goalMax=1000;
413 | //goal=2000;
414 | if(bottomNumber >= 0) setBottomNumbers(bottomNumber);
415 | if(goalImage != ""){
416 | let barActiveMax=360;
417 | let barActiveValue=(barActiveMax/goalMax)*goal;
418 | document.getElementById("healthArc").sweepAngle=(Math.max(Math.min(barActiveValue,barActiveMax),0));
419 | document.getElementById("healthImage").href='images/'+goalImage+'.png';
420 | }
421 | else{
422 | document.getElementById("healthArc").sweepAngle=0;
423 | document.getElementById("healthImage").href='';
424 | }
425 |
426 | }
427 | function updateGeekField(){
428 | let needGeekAlert=false;
429 | if(settings.isInfoEnabled()){
430 | let infos=settings.holder.infoText.replace('\r','').split('\n');
431 | document.getElementById("geekNumbers1").text=(0 in infos)?infos[0]:'';
432 | document.getElementById("geekNumbers2").text=(1 in infos)?infos[1]:'';
433 | document.getElementById("geekNumbers3").text=(2 in infos)?infos[2]:'';
434 | needGeekAlert=settings.isInfoError();
435 | }
436 | else {
437 | document.getElementById("geekNumbers1").text='RND '+util.getRandomInt(0, 999);
438 | document.getElementById("geekNumbers2").text='PI '+Math.PI.toFixed(5);
439 | document.getElementById("geekNumbers3").text='e '+Math.E.toFixed(5);
440 | }
441 | document.getElementById("geekNumbers0").style.opacity=(needGeekAlert)?1:0;
442 | document.getElementById("geekNumbers4").style.opacity=(needGeekAlert)?1:0;
443 | }
444 |
--------------------------------------------------------------------------------
/app/ping.js:
--------------------------------------------------------------------------------
1 | import * as messaging from "messaging";
2 | import * as C from "../common/const";
3 | import * as util from "../common/utils";
4 | import { battery } from "power";
5 | import { display } from "display";
6 | import { me } from "appbit";
7 | import {APP_LOG as LOG} from "../common/const";
8 | import * as settings from "./settings";
9 | import { vibration } from "haptics";
10 |
11 | let lastPingStart=0;
12 | let lastAppStarted=(new Date()).getTime();
13 | const appRestartTimeout=30*C.MILLISECONDS_PER_MINUTE; //ha nincs kapcsolat
14 | let callback=null;
15 |
16 | export function ping(source,func){
17 | let today=new Date();
18 | if(func) callback=func;
19 | //console.log('ping');
20 | if(!source) source=0;
21 | if(source == 0 && lastPingStart < 3) {
22 | if(LOG) console.log("Initing Ping... "+lastPingStart);
23 | lastPingStart++;
24 | return;
25 | }
26 | if(source >=1) lastPingStart=1000; //ezzel kikényszerítem a pinget akármi is van (csak akkor ha már rég volt success)
27 | if(source >=2) settings.holder.lastPong=1000; //ezzel a successt is kinyírom
28 | if((settings.holder.lastPong + C.PING_PERIOD*C.MILLISECONDS_PER_MINUTE) < today.getTime() ){
29 | if((lastPingStart + C.PING_TIMEOUT) < today.getTime() ){
30 | lastPingStart=today.getTime();
31 | if (settings.isPeerOpen(messaging)) {
32 | if(LOG) console.log("Pinging companion to starts periodic task, ping success at: "+settings.holder.lastPong+", " + ((today.getTime()-settings.holder.lastPong)/1000)+" seconds ago");
33 | messaging.peerSocket.send([C.MessageTypePing,battery.chargeLevel+1]);//,todayActivity.adjusted || todayActivity.local,dayHistory.query()]);
34 | onPing(C.PING_STATUS_STARTED);
35 | setTimeout(function(){onPing(C.PING_STATUS_TIMEOUT);},C.PING_TIMEOUT);
36 | }
37 | else {
38 | if(LOG) console.log("Pinging not starts, peersocket not open");
39 | onPing(C.PING_STATUS_NOCONNECTION);
40 | }
41 | }
42 | else {
43 | if(LOG) console.log("Pinging not start, already running at: "+lastPingStart+", " + ((today.getTime()-lastPingStart)/1000)+" seconds ago");
44 | onPing(C.PING_STATUS_ALREADY);
45 | }
46 | }
47 | else {
48 | if(LOG) console.log("Pinging not start, ping success at: "+settings.holder.lastPong+", " + ((today.getTime()-settings.holder.lastPong)/1000)+" seconds ago");
49 | onPing(C.PING_STATUS_NONEED);
50 | }
51 | }
52 | function onPing(status){
53 | let today=new Date();
54 | if(status == C.PING_STATUS_TIMEOUT && (settings.holder.lastPong + C.PING_TIMEOUT) >= today.getTime()) return;
55 | if(status == C.PING_STATUS_ALREADY) return;
56 | if(status == C.PING_STATUS_STARTED) return;
57 | if(status == C.PING_STATUS_SUCCESS) {
58 | settings.holder.lastPong=today.getTime();
59 | //settings.save();
60 | }
61 |
62 | let disconnected=settings.isDisconnected(messaging);
63 | //console.log(settings.lastDisconnected , disconnected);
64 | //if(PINGFAILEDRESTARTCOUNT > 0){
65 | // if(disconnected) pingFailedCount++;
66 | // else pingFailedCount=0;
67 | // if(pingFailedCount > PINGFAILEDRESTARTCOUNT && !display.on) me.exit();
68 | //}
69 | let forceRestart=(disconnected && display.on && (lastAppStarted < (today.getTime()-appRestartTimeout)));
70 | if(forceRestart) try{
71 | console.log("!!!!!! long time disconnected !!!!! trying to exit in 5sec...");
72 | setTimeout(function(){
73 | if(!settings.isDisconnected(messaging)) console.log("!!!!!! cancelling restarting, now everything good again...");
74 | else me.exit();
75 | },5000);
76 | }
77 | catch(e){
78 | console.log("!!!!!! exit not success long time !!!!! argh....");
79 | }
80 | /*
81 | if(settings.lastDisconnected != disconnected){
82 | //if(settings.settings[C.SETTINGS_DISCONNECT]){
83 | console.log("!!!!! lastDisconnected: "+settings.lastDisconnected+", disconnected: "+disconnected+", lastAppStarted: "+((today.getTime()-lastAppStarted)/1000)+" seconds ago");
84 | drawIcons();
85 | if(!disconnected){ // most kapcsolódott
86 | console.log("!!!!!! now connected !!!!! suuupeeerr");
87 | settings.lastDisconnected = false;
88 | if(settings.settings[C.SETTINGS_DISCONNECT] && settings.settings[C.SETTINGS_RECONNECTVIBRATE] && C.VIBRATE_PATTERNS[settings.settings[C.SETTINGS_RECONNECTVIBRATE]]){
89 | if (vibe(C.VIBRATE_PATTERNS[settings.settings[C.SETTINGS_RECONNECTVIBRATE]])) display.poke();
90 | }
91 | }
92 | else{ //most szakadt
93 | // újraindítjuk, hátha azzal megjavul, és akkor nem kell lefuttatni a szakadáskori procedúrát
94 | //saveSettings();
95 | let normalDisconnectedProcedure=false;
96 | if (lastAppStarted < (today.getTime()-appRestartDelay)) { //már régebb óta fut az app , vagyis nem most indult el
97 | try{
98 | console.log("!!!!!! now disconnected !!!!! trying to exit...");
99 | me.exit();
100 | }
101 | catch(e){
102 | // nem sikerült az újraindítás normál szakadáskori procedúra futtatása...
103 | console.log("!!!!!! exit not success !!!!! argh....");
104 | normalDisconnectedProcedure=true;
105 | }
106 | }
107 | else normalDisconnectedProcedure=true;
108 |
109 | if(normalDisconnectedProcedure){ //ez a normál szakadáskori procedúra, csak az első pingpróbálkozáskor futtatjuk, mivel mindig újraindítjuk szakadáskor. Kivéve mikor nem sikerül, akkor a catch bekamuzza hogy most van először a ping
110 | settings.lastDisconnected = true;
111 | console.log("!!!!!! still no connection !!!!! argh....");
112 | if(settings.settings[C.SETTINGS_DISCONNECT] && settings.settings[C.SETTINGS_DISCONNECTVIBRATE] && C.VIBRATE_PATTERNS[settings.settings[C.SETTINGS_DISCONNECTVIBRATE]]){
113 | if(vibe(C.VIBRATE_PATTERNS[settings.settings[C.SETTINGS_DISCONNECTVIBRATE]])) display.poke();
114 | }
115 | }
116 | }
117 | //}
118 | //saveSettings();
119 | }
120 | */
121 |
122 | }
123 |
124 | messaging.peerSocket.onclose = function(evt) {
125 | if(LOG){
126 | if(evt.code == evt.CONNECTION_LOST) console.log('!!!!!!!!!!!!!!!!!!!!!messaging.peerSocket.onclose: CONNECTION_LOST '+evt.reason+' wasClean: '+evt.wasClean);
127 | if(evt.code == evt.PEER_INITIATED) console.log('!!!!!!!!!!!!!!!!!!!!!messaging.peerSocket.onclose: PEER_INITIATED '+evt.reason+' wasClean: '+evt.wasClean);
128 | if(evt.code == evt.SOCKET_ERROR) console.log('!!!!!!!!!!!!!!!!!!!!!messaging.peerSocket.onclose: SOCKET_ERROR '+evt.reason+' wasClean: '+evt.wasClean);
129 | }
130 | //ping();
131 | }
132 | messaging.peerSocket.onopen = function(evt) {
133 | ping(1);
134 | }
135 | // Listen for the onmessage event
136 | messaging.peerSocket.onmessage = function(evt) {
137 | if(LOG) console.log("Got message from COMPANION: "+evt.data[0]+", "+JSON.stringify(evt.data[1]));
138 | let today=new Date();
139 | onPing(C.PING_STATUS_SUCCESS);
140 | if(evt.data[1] !== null && evt.data[1][0] !== null) switch(evt.data[0]){
141 | case C.MESSAGE_TYPE_WEATHER:
142 | settings.holder.weatherText=evt.data[1][0];
143 | if(evt.data[1][1] !== null) settings.holder.weatherTemp=evt.data[1][1];
144 | if(evt.data[1][2] !== null) settings.holder.weatherHum=evt.data[1][2];
145 | if(evt.data[1][3] !== null) settings.holder.windSpeed=evt.data[1][3];
146 | if(evt.data[1][4] !== null) settings.holder.windDir=evt.data[1][4];
147 | if(evt.data[1][5] !== null) settings.holder.weatherCity=evt.data[1][5];
148 | if(evt.data[1][6] !== null) settings.holder.weatherIcon=evt.data[1][6];
149 | if(evt.data[1][7] !== null) settings.holder.forecastMin=evt.data[1][7];
150 | if(evt.data[1][8] !== null) settings.holder.forecastMax=evt.data[1][8];
151 | if(evt.data[1][9] !== null) settings.holder.forecastIcon=evt.data[1][9];
152 | if(evt.data[1][10] !== null) settings.holder.forecastText=evt.data[1][10];
153 | if(evt.data[1][11] !== null) settings.holder.sunrise=evt.data[1][11];
154 | if(evt.data[1][12] !== null) settings.holder.sunset=evt.data[1][12];
155 | settings.holder.lastWeather=today.getTime();
156 | break;
157 | case C.MessageTypeInfo:
158 | settings.holder.infoText=evt.data[1][0];
159 | if(evt.data[1][1]) util.vibe(C.VIBRATE_PATTERNS[evt.data[1][1]],vibration);
160 | settings.holder.lastInfo=today.getTime();
161 | break;
162 | case C.MessageTypePong:
163 | settings.holder.timeZone=evt.data[1][0];
164 | settings.holder.timeZone2=evt.data[1][1];
165 | settings.holder.timeZone2Offset=evt.data[1][2]*1;
166 | break;
167 | case C.MessageTypeSettings:
168 | //if(evt.data[1] !== null) {
169 | /*if(evt.data[1][C.SETTINGS_VIBRATE_TAP]
170 | && settings.settings[C.SETTINGS_VIBRATE_TAP] != evt.data[1][C.SETTINGS_VIBRATE_TAP]) {
171 | vibe(C.VIBRATE_PATTERNS[evt.data[1][C.SETTINGS_VIBRATE_TAP]],1);
172 | }*/
173 | settings.holder.settings=evt.data[1];
174 | //}
175 | display.poke();
176 | if(settings.getSettings(C.SettingsTapVibe)) util.vibe(C.VIBRATE_PATTERNS[settings.getSettings(C.SettingsTapVibe)],vibration);
177 | break;
178 | /*case C.MESSAGE_TYPE_COLORS:
179 | for (let i=0 ; i < evt.data[1].length ; i++) settings.colors[i]=evt.data[1][i];
180 | display.poke();
181 | break;
182 | */
183 | }
184 | onPing(C.PING_STATUS_SUCCESS);
185 | settings.save();
186 | if(callback) callback();
187 | //ui.updateUI("loaded", evt.data);
188 | }
--------------------------------------------------------------------------------
/app/settings.js:
--------------------------------------------------------------------------------
1 | import * as fs from "fs";
2 | import {APP_LOG as LOG} from "../common/const";
3 | import * as C from "../common/const";
4 | import * as util from '../common/utils';
5 |
6 | const SETTINGS_TYPE = "cbor";
7 | const SETTINGS_FILE = "settings.cbor";
8 |
9 |
10 | //to save
11 | export let holder = null;
12 |
13 | //temporary variables
14 | let loaded=false;
15 | let saved=false;
16 |
17 | export function getSettings(id){
18 | if(!loaded) load();
19 | if(typeof id !== 'undefined') return holder.settings[id];
20 | return holder.settings;
21 | }
22 | export function setSettings(s){
23 | //if(holder.settings[C.SettingsBgColorId] != s[C.SettingsBgColorId]) holder.bgColor=s[C.SettingsBgColorId];
24 | holder.settings=s;
25 | saved=false;
26 | }
27 |
28 | export function load() {
29 | if(LOG) console.log("Loading settings");
30 | let loadedFromFile=true;
31 | let h=null;
32 | try {
33 | h=fs.readFileSync(SETTINGS_FILE, SETTINGS_TYPE);
34 | } catch (ex) {
35 | // Defaults
36 | if(LOG) console.log("no settings file, defaults loaded");
37 | h= {}
38 | loadedFromFile=false;
39 | }
40 | //if(!('lastPingSuccess' in s) ) s.lastPingSuccess=0;
41 | if(!('settings' in h) ) h.settings=C.SettingsDefaults;
42 | for(let a = 0 ; a < C.SettingsDefaults.length ; a++) if(typeof h.settings[a] === 'undefined') h.settings[a]=C.SettingsDefaults[a];
43 | if(!('weatherCity' in h) ) h.weatherCity='City';
44 | if(!('weatherTemp' in h) ) h.weatherTemp=-99;
45 | if(!('weatherText' in h) ) h.weatherText='Weather text';
46 | if(!('weatherHum' in h) ) h.weatherHum=0;
47 | if(!('weatherIcon' in h) ) h.weatherIcon=0;
48 | if(!('forecastMin' in h) ) h.forecastMin=-99;
49 | if(!('forecastMax' in h) ) h.forecastMax=99;
50 | if(!('forecastText' in h) ) h.forecastText='Tomorrow';
51 | if(!('forecastText' in h) ) h.forecastIcon=0;
52 | if(!('windChill' in h) ) h.windChill=0;
53 | if(!('windDir' in h) ) h.windDir=350;
54 | if(!('windSpeed' in h) ) h.windSpeed=0;
55 | if(!('sunrise' in h) ) h.sunrise='--:--';
56 | if(!('sunset' in h) ) h.sunset='--:--';
57 | if(!('timeZone' in h) ) h.timeZone='---';
58 | if(!('timeZone2' in h) ) h.timeZone2='---';
59 | if(!('timeZone2Offset' in h) ) h.timeZone2Offset=0;
60 | if(!('lastWeather' in h) ) h.lastWeather=0;
61 | if(!('lastPong' in h) ) h.lastPong=0;
62 | if(!('lastInfo' in h) ) h.lastInfo=0;
63 | if(!('infoText' in h) ) h.infoText='<\nINFO SCRIPT TESTING\n/>';
64 |
65 | holder=h;
66 | loaded=true;
67 | return loadedFromFile;
68 | }
69 |
70 | export function save(){
71 | if(LOG) console.log("Saving settings");
72 | try {
73 | fs.unlinkSync(SETTINGS_FILE);
74 | } catch (ex) {
75 | }
76 | fs.writeFileSync(SETTINGS_FILE, holder, SETTINGS_TYPE);
77 | if(LOG) console.log("settings file saved");
78 | saved=true;
79 | }
80 | export function clear(){
81 | if(LOG) console.log("Clearing settings");
82 | fs.unlinkSync(SETTINGS_FILE);
83 | load();
84 | }
85 | export function getTimezoneName(i){
86 | if(i==2) return holder.timeZone2;
87 | return holder.timeZone;
88 | }
89 | export function getWeatherIcon(){
90 | if(isWeatherError()) return 'images/weather/weather_error.png';
91 | if(holder.weatherIcon > 0 && holder.weatherIcon < 12) return 'images/weather/weather_'+util.zeroPad(holder.weatherIcon)+'.png';
92 | return '';
93 | }
94 | export function getWeatherLine(line,units,device){
95 | switch(line){
96 | case 1:
97 | return holder.weatherCity+((device.ionic)?' HUM '+holder.weatherHum+'%':'');
98 | case 2:
99 | return toTemp(holder.weatherTemp,units)+getTempUnit(units)+' '+holder.weatherText+((device.ionic)?' WIND '+toSpeed(holder.windSpeed,units)+speedUnit(units)+' '+getDirection(holder.windDir):'');
100 | case 3:
101 | if(device.ionic) return '';
102 | return 'WIND '+toSpeed(holder.windSpeed,units)+speedUnit(units)+' '+getDirection(holder.windDir)+' HUM '+holder.weatherHum+'%';
103 | case 4:
104 | if(device.ionic) return '';
105 | //holder.forecastText='Scuttered Rain';
106 | return ((holder.forecastText.length<20)?'FORECAST':'FCST')+' '+toTemp(holder.forecastMin,units)+'/'+toTemp(holder.forecastMax,units)+getTempUnit(units)+' '+holder.forecastText;
107 | }
108 | return '';
109 | }
110 | function toSpeed(speed,units){
111 | if(isSpeedMetric(units)) speed = speed * 1.609344;
112 | return Math.round(speed);
113 | }
114 | function speedUnit(units){
115 | return (isSpeedMetric(units))?'kmh':'mph';
116 | }
117 | function getDirection(deg){
118 | let dirs=['N','NE','E','SE','S','SW','W','NW','N'];
119 | let osztas=360/(dirs.length-1);
120 | let index=Math.round(deg/osztas);
121 | return dirs[index];
122 | }
123 | export function getSunrise(preferences){
124 | if(is12h(preferences)) return to12h(holder.sunrise);
125 | return holder.sunrise;
126 | }
127 | export function getSunset(preferences){
128 | if(is12h(preferences)) return to12h(holder.sunset);
129 | return holder.sunset;
130 | }
131 | function toTemp(temp,units){
132 | if(isTempMetric(units)) temp = (temp - 32) * 5/9;
133 | return Math.round(temp);
134 | }
135 | function getTempUnit(units){
136 | return isTempMetric(units)?'C':'F';
137 | }
138 | export function showAmPm(){
139 | return getSettings(C.SettingsShowAmPm)==1;
140 | }
141 | function to12h(timestring){
142 | if(timestring.indexOf(':')!==-1){
143 | let t=timestring.split(':');
144 | t[0]=t[0]*1;
145 | let ampmtext="";
146 | if(showAmPm()) ampmtext=(t[0]<12)?'am':'pm';
147 | t[0] = t[0] % 12 || 12;
148 | timestring=''+t[0]+':'+t[1]+ampmtext;
149 | }
150 | return timestring;
151 | }
152 | export function is12h(preferences){
153 | let h24=getSettings(C.SettingsUnits24h);
154 | if(h24==C.UnitsDefault) return (preferences.clockDisplay === "12h");
155 | return (h24==C.UnitsUS);
156 | }
157 | export function isSpeedMetric(units){
158 | //return false;
159 | let metric=getSettings(C.SettingsUnitsSpeed);
160 | if(metric==C.UnitsDefault) return (units.speed=="metric");
161 | return (metric==C.UnitsMetric);
162 | }
163 | export function isTempMetric(units){
164 | //return false;
165 | let metric=getSettings(C.SettingsUnitsTemp);
166 | if(metric==C.UnitsDefault) return (units.temperature=="C");
167 | return (metric==C.UnitsMetric);
168 | }
169 | export function isDistanceMetric(units){
170 | //return false;
171 | let metric=getSettings(C.SettingsUnitsDistance);
172 | if(metric==C.UnitsDefault) return (units.distance=="metric");
173 | return (metric==C.UnitsMetric);
174 | }
175 | export function getBarImage(primaryGoal){
176 | //return "battery";
177 | let barType=getSettings(C.SettingsBarType);
178 | if(barType==C.SETTINGS_SYSTEMDEFAULT) return (primaryGoal || 'steps');
179 | switch(barType){
180 | case C.HEALTH_steps:
181 | return "steps";
182 | case C.HEALTH_distance:
183 | return "distance";
184 | case C.HEALTH_calories:
185 | return "calories";
186 | case C.HEALTH_elevationGain:
187 | return "elevationGain";
188 | case C.HEALTH_activeZoneMinutes:
189 | return "activeZoneMinutes";
190 | case C.HEALTH_battery:
191 | return "battery";
192 | }
193 | return "";
194 | }
195 | export function isPeerOpen(messaging){
196 | //if(!messageInited) return settings.lastDisconnected;
197 | return (messaging.peerSocket.readyState === messaging.peerSocket.OPEN);
198 | }
199 | export function isDisconnected(messaging){
200 | if(!isPeerOpen(messaging)) return true;
201 | let today=new Date();
202 | return ((holder.lastPong + C.PING_PERIOD*C.MILLISECONDS_PER_MINUTE*2) < today.getTime() ) ;
203 | }
204 | export function isInfoEnabled(){
205 | //return true;
206 | return (holder.settings[C.SettingsInfoEnabled] == 1);
207 | }
208 |
209 | export function isInfoError(){
210 | //if(!needDownload(false,true)) return false;
211 | //if(LOG) console.log("needInfo true");
212 | //if(LOG) console.log("isInfoError, INFO_PERIOD:"+INFO_PERIOD+", lastInfo: "+settings.lastInfo);
213 | if(!isInfoEnabled()) return false;
214 | let INFO_PERIOD=holder.settings[C.SettingsInfoPeriod];
215 | let today=new Date();
216 | return ((holder.lastInfo + INFO_PERIOD*C.MILLISECONDS_PER_MINUTE*2) < today.getTime() ) ;
217 | }
218 | export function isWeatherError(){
219 | //if(!needDownload(true,false)) return false;
220 | //if(LOG) console.log("needWeather true");
221 | let WEATHER_PERIOD=holder.settings[C.SettingsWeatherPeriod];
222 | //if(LOG) console.log("isWeatherError, WEATHER_PERIOD:"+WEATHER_PERIOD+", lastWeather: "+settings.lastWeather);
223 | let today=new Date();
224 | return ((holder.lastWeather + WEATHER_PERIOD*C.MILLISECONDS_PER_MINUTE*2) < today.getTime() ) ;
225 | }
226 |
--------------------------------------------------------------------------------
/common/const.js:
--------------------------------------------------------------------------------
1 |
2 | export const APP_LOG = 0;
3 | export const COMPANION_LOG = 0;
4 | export const SETTINGS_LOG = 0;
5 |
6 | export const AppVersion=210625;
7 | export const AppName="prog";
8 | export const AppName2="beta";
9 |
10 |
11 | export const MONTHNAMES_DEFAULT= ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
12 | export const WEEKDAYS_DEFAULT = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
13 | export const MONTHNAMES_LONG_DEFAULT = ['January','February','March','April','May','June','July','August','September','October','November','December'];
14 | export const WEEKDAYS_LONG_DEFAULT = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
15 |
16 | export const MILLISECONDS_PER_MINUTE=60000;
17 |
18 | export const WEATHER_PERIOD_DEFAULT=30;
19 | export const WEATHER_FORECAST_PERIOD = 239; //minutes //4 óra
20 |
21 | export const INFO_PERIOD_DEFAULT=15;
22 | export const PING_PERIOD=5;
23 | export const PING_TIMEOUT = 5000; //msecs
24 |
25 | export const PING_STATUS_SUCCESS =0;
26 | export const PING_STATUS_STARTED =1;
27 | export const PING_STATUS_NOCONNECTION=2;
28 | export const PING_STATUS_TIMEOUT =3;
29 | export const PING_STATUS_NONEED =4;
30 | export const PING_STATUS_ALREADY =5;
31 |
32 | export const MessageTypePing = 0;
33 | export const MessageTypePong = 1;
34 | export const MessageTypeSettings = 2;
35 | export const MESSAGE_TYPE_WEATHER = 3;
36 | export const MessageTypeInfo = 4;
37 |
38 | export const SETTINGS_RANDOM = -1;
39 | export const SETTINGS_SYSTEMDEFAULT = -2;
40 | export const SETTINGS_NOTHING = -3;
41 |
42 | export const SettingsInfoPeriod = 0;
43 | export const SettingsUnitsDistance = 1;
44 | export const SettingsUnits24h = 2;
45 | export const SettingsShowAmPm = 3;
46 | export const SettingsUnitsTemp = 4;
47 | export const SettingsBarType = 5;
48 | export const SettingsUnitsSpeed = 6;
49 | export const SettingsWeatherPeriod = 7;
50 | export const SettingsInfoEnabled = 8;
51 | export const SettingsTapVibe = 9;
52 |
53 | export const UnitsDefault =0;
54 | export const UnitsMetric =1;
55 | export const UnitsUS =2;
56 |
57 | export const SettingsDefaults=[
58 | INFO_PERIOD_DEFAULT, //0
59 | UnitsDefault, //1
60 | UnitsDefault, //2
61 | 1, //3
62 | UnitsDefault, //4
63 | SETTINGS_SYSTEMDEFAULT, //5
64 | UnitsDefault, //6
65 | WEATHER_PERIOD_DEFAULT, //7
66 | 0, //8
67 | 1 //9
68 | ];
69 |
70 |
71 | export const HEALTH_steps =0;
72 | export const HEALTH_distance =1;
73 | export const HEALTH_calories =2;
74 | export const HEALTH_elevationGain =3;
75 | export const HEALTH_activeZoneMinutes =4;
76 | export const HEALTH_battery =5;
77 |
78 | export const BottomNumberTypes=8;
79 |
80 | export const BottomNumberNothing=0;
81 | export const BottomNumberSec=1;
82 | export const BottomNumberCals=2;
83 | export const BottomNumberSteps=3;
84 | export const BottomNumberMins=4;
85 | export const BottomNumberFloors=5;
86 | export const BottomNumberBatt=6;
87 | export const BottomNumberDist=7;
88 | export const BottomNumberHR=8;
89 |
90 | export const VIBRATE_PATTERNS=["","bump","nudge","nudge-max","ping","confirmation","confirmation-max","special","special-max"];
91 |
92 |
--------------------------------------------------------------------------------
/common/sconst.js:
--------------------------------------------------------------------------------
1 | export * from "../common/const";
2 | import * as C from "../common/const";
3 |
--------------------------------------------------------------------------------
/common/sutils.js:
--------------------------------------------------------------------------------
1 | import * as C from "../settings/const";
2 | export * from "../common/utils";
3 | import * as util from "./utils";
4 |
5 |
6 | export function settingsToSelect(settingsStorage){
7 | let s=null;
8 | let LOG=C.COMPANION_LOG;
9 | if(LOG) console.log("sutils ---- starting settings convert "+ settingsStorage.getItem('settings'));
10 | //if(settingsStorage.getItem('settings')) s=JSON.parse(settingsStorage.getItem('settings'));
11 | //if(s) {for(let a = 0 ; a < C.SettingsDefaults.length ; a++) if(typeof s[a] === 'undefined') s[a]=C.SettingsDefaults[a];}
12 | //else s=C.SettingsDefaults;
13 |
14 | s=getSettingsArray(settingsStorage);
15 | //console.log(s[C.SettingsBgOption]);
16 |
17 | //settingsStorage.setItem("bgOption", createSelectValue(C.bgOptions,[s[C.SettingsBgOption]]));
18 |
19 | settingsStorage.setItem("barType", createSelectValue(C.barOptions,[s[C.SettingsBarType]]));
20 | settingsStorage.setItem("units24h", createSelectValue(C.units24hOptions,[s[C.SettingsUnits24h]]));
21 | settingsStorage.setItem("unitsDistance", createSelectValue(C.unitsDistanceOptions,[s[C.SettingsUnitsDistance]]));
22 | settingsStorage.setItem("unitsSpeed", createSelectValue(C.unitsSpeedOptions,[s[C.SettingsUnitsSpeed]]));
23 | settingsStorage.setItem("unitsTemp", createSelectValue(C.unitsTempOptions,[s[C.SettingsUnitsTemp]]));
24 | settingsStorage.setItem("tapVibrate", createSelectValue(C.vibrateOptions,[s[C.SettingsTapVibe]]));
25 |
26 | settingsStorage.setItem("weatherPeriod", createSelectValue(C.weatherPeriodOptions,[s[C.SettingsWeatherPeriod]]));
27 | settingsStorage.setItem("infoPeriod", createSelectValue(C.infoPeriodOptions,[s[C.SettingsInfoPeriod]]));
28 |
29 |
30 | settingsStorage.setItem("infoScriptEnabled", JSON.stringify(s[C.SettingsInfoEnabled]==1));
31 | settingsStorage.setItem("showAmPm", JSON.stringify(s[C.SettingsShowAmPm]==1));
32 |
33 | if(LOG) console.log("sutils ---- settings converted to select tags");
34 | }
35 |
36 |
37 | let selectToSettingsTimer=null;
38 | export function selectToSettings(settingsStorage,delay){
39 | if(!delay){
40 | if(selectToSettingsTimer) clearTimeout(selectToSettingsTimer);
41 | selectToSettingsTimer=setTimeout(function(){selectToSettings(settingsStorage,1)},1000);
42 | return;
43 | }
44 | selectToSettingsTimer=null;
45 | let s=getSettingsArray(settingsStorage);//JSON.parse(JSON.stringify(C.SettingsDefaults));
46 | let json=null;
47 | let LOG=C.SETTINGS_LOG;
48 | if(LOG) console.log("sutils ---- starting convert select to settings");
49 |
50 | if(settingsStorage.getItem("barType") && (json=JSON.parse(settingsStorage.getItem("barType"))) ) s[C.SettingsBarType]=json.values[0].value.id*1;
51 | if(settingsStorage.getItem("units24h") && (json=JSON.parse(settingsStorage.getItem("units24h"))) ) s[C.SettingsUnits24h]=json.values[0].value.id*1;
52 | if(settingsStorage.getItem("unitsDistance") && (json=JSON.parse(settingsStorage.getItem("unitsDistance"))) ) s[C.SettingsUnitsDistance]=json.values[0].value.id*1;
53 | if(settingsStorage.getItem("unitsSpeed") && (json=JSON.parse(settingsStorage.getItem("unitsSpeed"))) ) s[C.SettingsUnitsSpeed]=json.values[0].value.id*1;
54 | if(settingsStorage.getItem("unitsTemp") && (json=JSON.parse(settingsStorage.getItem("unitsTemp"))) ) s[C.SettingsUnitsTemp]=json.values[0].value.id*1;
55 | if(settingsStorage.getItem("tapVibrate") && (json=JSON.parse(settingsStorage.getItem("tapVibrate"))) ) s[C.SettingsTapVibe]=json.values[0].value.id*1;
56 |
57 | if(settingsStorage.getItem("weatherPeriod") && (json=JSON.parse(settingsStorage.getItem("weatherPeriod"))) ) s[C.SettingsWeatherPeriod]=json.values[0].value.id*1;
58 | if(settingsStorage.getItem("infoPeriod") && (json=JSON.parse(settingsStorage.getItem("infoPeriod"))) ) s[C.SettingsInfoPeriod]=json.values[0].value.id*1;
59 |
60 |
61 | if(settingsStorage.getItem("infoScriptEnabled")) s[C.SettingsInfoEnabled]=(settingsStorage.getItem("infoScriptEnabled")=='true')?1:0;
62 | if(settingsStorage.getItem("showAmPm")) s[C.SettingsShowAmPm]=(settingsStorage.getItem("showAmPm")=='true')?1:0;
63 |
64 | if(LOG) console.log("sutils ---- settings Select tags converted to settings settings: "+JSON.stringify(s));
65 | settingsStorage.setItem('settings',JSON.stringify(s));
66 | return s;
67 | }
68 |
69 |
70 | export function cleanSettings(json){
71 | if(!json) json=JSON.parse(JSON.stringify(C.SettingsDefaults));
72 | for(let i=0 ; i < C.SettingsDefaults.length ; i++){
73 | if(!(i in json)) json[i]=C.SettingsDefaults[i];
74 | }
75 | return json;
76 | }
77 | export function getSettingsArray(settingsStorage){
78 | let json=null;
79 | if(settingsStorage.getItem('settings')) json=JSON.parse(settingsStorage.getItem('settings'));
80 | return cleanSettings(json);
81 | }
82 |
83 | function createSelectValue(where,values){
84 | let json={"selected":[],"values":[]};
85 | //console.log(where,values);
86 | for(let i=0 ; i < values.length ; i++){
87 | let id=searchIdInList(where,values[i]);
88 | //console.log(id);
89 | json.selected[i]=id;
90 | json.values[i]=where[id];
91 | }
92 | return JSON.stringify(json);
93 | }
94 | function searchIdInList(where,value){
95 | //console.log(where,value);
96 | for(let a = 0 ; a < where.length ; a++){
97 | if((""+where[a].value.id) == (""+value)) return a;
98 | }
99 | return 0;
100 | }
101 |
102 | /*
103 | export function colorOptions(){
104 | let colors=[];
105 | for(let i=0 ; i < C.BGColors.length ; i++){
106 | colors[i]={color: '#'+C.BGColors[i]};
107 | }
108 | return colors;
109 | }
110 | */
111 | export function timeZoneOptions(){
112 |
113 | let zones=[];
114 | let date = new Date;
115 | /*let i=0;
116 | zones.push({name:"Default ("+getTimezoneName()+")", value:''+(i)});
117 | C.aryIannaTimeZones.forEach((timeZone) => {
118 | i++;
119 | let strTime = date.toLocaleString("en-US", {
120 | timeZone: `${timeZone}`
121 | });
122 | zones.push({name:timeZone+' ('+getTimezoneName(timeZone)+')', value:''+(i)});
123 | //console.log(timeZone, strTime);
124 | });*/
125 |
126 | zones.push({name:"Default ("+getTimezoneName()+")", value:'0'});
127 |
128 | C.minimalTimezoneSet.forEach((timeZone) => {
129 | zones.push({name:timeZone.tzCode+' '+timeZone.label+'', value:''+(1+C.aryIannaTimeZones.indexOf(timeZone.tzCode))});
130 | });
131 |
132 | return zones;
133 | }
134 | export function getTimezoneName(timeZone) {
135 | const today = new Date();
136 | //date.toLocaleString('de-DE', {hour: '2-digit', hour12: false, timeZone: 'Asia/Shanghai' })
137 | let short = today.toLocaleDateString("en-US");
138 | let full = today.toLocaleDateString("en-US", {timeZoneName: 'short' });
139 | if(timeZone){
140 | short = today.toLocaleDateString("en-US",{timeZone: timeZone});
141 | full = today.toLocaleDateString("en-US", {timeZone: timeZone, timeZoneName: 'short' });
142 | }
143 | // Trying to remove date from the string in a locale-agnostic way
144 | const shortIndex = full.indexOf(short);
145 | if (shortIndex >= 0) {
146 | const trimmed = full.substring(0, shortIndex) + full.substring(shortIndex + short.length);
147 |
148 | // by this time `trimmed` should be the timezone's name with some punctuation -
149 | // trim it from both sides
150 | return trimmed.replace(/^[\s,.\-:;]+|[\s,.\-:;]+$/g, '');
151 |
152 | } else {
153 | // in some magic case when short representation of date is not present in the long one, just return the long one as a fallback, since it should contain the timezone's name
154 | return full;
155 | }
156 | }
157 | export function getTimezoneUtcOffset(timeZone) {
158 | //let today = new Date((new Date()).toLocaleString("en-US",{timeZone: 'UTC'}));
159 | let today = new Date();
160 | //date.toLocaleString('de-DE', {hour: '2-digit', hour12: false, timeZone: 'Asia/Shanghai' })
161 | let short = today.toLocaleString("en-US");
162 | if(timeZone){
163 | short = today.toLocaleString("en-US",{timeZone: timeZone});
164 | }
165 | console.log(short);
166 | let today2 = new Date(short);
167 | return Math.round((today2.getTime()-today.getTime())/(1000*60));
168 | }
169 | export function timeOptions(settingsStorage){
170 | let h12=false;
171 | let json=null;
172 | if(settingsStorage && settingsStorage.getItem("units24h") && (json=JSON.parse(settingsStorage.getItem("units24h"))) && (json.values[0].value*1) == C.UnitsUS) h12=true;
173 | //if(settingsStorage && settingsStorage.getItem("showAmPm") && settingsStorage.getItem("showAmPm") == 'true') h12=true;
174 |
175 | let times=[];
176 | for(let hour=0 ; hour < 24 ; hour++) for(let minute=0 ; minute < 60 ; minute+=5){
177 | let ampm="";
178 | let hour2=hour;
179 | if(h12){
180 | ampm=(hour < 12)?" AM":" PM";
181 | hour2 = hour2 % 12 || 12;
182 | }
183 | times.push({name:((ampm)?hour2:util.zeroPad(hour2))+':'+util.zeroPad(minute)+ampm, value:''+(hour*100+minute)});
184 | }
185 | return times;
186 | }
187 | export function weatherEnabled(settingsStorage){
188 | return true;
189 | /*let s=(settingsStorage.getItem('statusIds'))?JSON.parse(settingsStorage.getItem('statusIds')):null;
190 | if(!s) s=JSON.parse(JSON.stringify(C.STATUSIDS_DEFAULT));
191 | for(let i=0 ; i<4 ; i++){
192 | if(i == 0 && statusWeather(s[i])) return true;
193 | if(i != 0 && statusWeather(s[i][0])) return true;
194 | if(i != 0) for(let j = 0 ; j < s[i][1].length ; j++){
195 | if(statusWeather(s[i][1][j])) return true;
196 | }
197 | }
198 | if(getTextProvider(settingsStorage) == "weather") return true;
199 | return false;*/
200 | }
201 | export function getLanguageCode(settingsStorage){
202 | //let id=getLanguageId(settingsStorage);
203 | //if(lang.languages[id]) return lang.languages[id][0];
204 | return "en";
205 | }
--------------------------------------------------------------------------------
/common/utils.js:
--------------------------------------------------------------------------------
1 | // Add zero in front of numbers < 10
2 | export function zeroPad(i) {
3 | if (i < 10) {
4 | i = "0" + i;
5 | }
6 | return i;
7 | }
8 | export function spacePad(i) {
9 | if (i < 10) {
10 | i = " " + i;
11 | }
12 | return i;
13 | }
14 | export function monoDigits(digits) {
15 | var ret = "";
16 | var str = digits.toString();
17 | for (var index = 0; index < str.length; index++) {
18 | var num = str.charAt(index);
19 | if(''+num === ''+(num*1)) ret = ret.concat(hex2a("0x1" + num));
20 | else ret = ret.concat(num);
21 | }
22 | return ret;
23 | }
24 | export function hex2a(hex) {
25 | var str = '';
26 | for (var index = 0; index < hex.length; index += 2) {
27 | var val = parseInt(hex.substr(index, 2), 16);
28 | if (val) str += String.fromCharCode(val);
29 | }
30 | return str.toString();
31 | }
32 |
33 | export function getRandomInt(min, max) {
34 | min = Math.ceil(min);
35 | max = Math.floor(max);
36 | return Math.floor(Math.random() * (max - min + 1)) + min;
37 | }
38 | export function vibe(pattern,vibration){
39 | vibration.stop();
40 | if(pattern == "special" || pattern == "special-max"){
41 | pattern=pattern.replace("special","confirmation");
42 | vibe(pattern,vibration);
43 | setTimeout(function(){vibe(pattern,vibration)},400);
44 | setTimeout(function(){vibe(pattern,vibration)},500);
45 | //setTimeout(function(){vibe(pattern,force)},660);
46 | }
47 | else vibration.start(pattern);
48 | return true;
49 | }
50 | export function WEATHER_PERIOD(forError){
51 | if(!forError) forError=false;
52 | let today=new Date();
53 | if(forError) today.setTime( today.getTime() - 1000 * (60 * 20) )
54 | /*
55 | régi: 48 darab //félóránként
56 | új (adaptív):
57 | 0-6: 1 //4 óránként
58 | 6-8: 6 //20 percenként
59 | 8-11: 3 //1 óránként
60 | 11-16: 3 //2 óránként
61 | 16-18: 4 //30 percenként
62 | 18-22: 4 // 1 óránként
63 | 22-24: 1 // 2 óránként
64 | üsszesen: 22
65 | új (adaptív):
66 | 0-6: 1 //4 óránként
67 | 6-9: 9 //20 percenként
68 | 9-16: 7 //1 óránként
69 | 16-18: 4 //30 percenként
70 | 18-24: 6 // 1 óránként
71 | üsszesen: 27
72 | */
73 | let hour=today.getHours();
74 | /*if (hour < 6) return 239;
75 | if (hour >= 6 && hour < 8) return 19;
76 | if (hour >= 8 && hour < 11) return 59;
77 | if (hour >= 11 && hour < 16) return 119;
78 | if (hour >= 16 && hour < 18) return 29;
79 | if (hour >= 18 && hour < 22) return 59;
80 | if (hour >= 22) return 119;*/
81 |
82 | if (hour < 6) return 239;
83 | if (hour >= 6 && hour < 9) return 19;
84 | if (hour >= 9 && hour < 16) return 59;
85 | if (hour >= 16 && hour < 18) return 29;
86 | if (hour >= 18) return 59;
87 |
88 |
89 | return 79;
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/companion/index.js:
--------------------------------------------------------------------------------
1 | import {COMPANION_LOG as LOG} from "../settings/const"
2 |
3 | import * as C from "../settings/const"
4 | import * as sutil from "../common/sutils";
5 | import { me } from "companion";
6 |
7 | import * as messaging from "messaging";
8 | import { localStorage } from "local-storage";
9 | import { settingsStorage } from "settings";
10 | import * as weather from "./weather";
11 |
12 |
13 | import { device } from "peer";
14 | if (!device.screen) device.screen = { width: 300, height: 300 };
15 | if(!device.modelId) device.modelId = "0";
16 | if(!device.modelName) device.modelName = "unknown";
17 | device.ionic=(device.screen.width==348);
18 | device.versa=(device.screen.width==300);
19 | device.sense=(device.screen.width==336);
20 | settingsStorage.setItem("screenWidth", device.screen.width);
21 | settingsStorage.setItem("screenHeight", device.screen.height);
22 | if(!localStorage.getItem('uniqueid')) localStorage.setItem('uniqueid',C.AppName+'_'+C.AppName2+'_'+((device.ionic)?'i':((device.sense)?'s':'v'))+me.host.os.name.substring(0,1)+'_'+Math.random().toString(36).substring(2)+(new Date()).getTime().toString(36));
23 |
24 | let uniqueid = localStorage.getItem('uniqueid');
25 | let infoIsRunning=false;
26 |
27 | import { me as companion } from "companion";
28 | me.onunload=function(){debug('Companion unloaded')};
29 | import { geolocation } from "geolocation";
30 | const WAKEUP_PERIOD = 0; //minutes (0=disabled) nem kell, mivel mindent az óra indít
31 | if(WAKEUP_PERIOD >=5) me.wakeInterval = WAKEUP_PERIOD * C.MILLISECONDS_PER_MINUTE;
32 | if(LOG) console.log("companion launched, starting sendAnswer in 7 secs...");
33 | setTimeout(sendAnswer,7000);
34 |
35 | function initStorage(clear){
36 | if(LOG) console.log('initStorage started...');
37 | if(!localStorage.getItem('storageInited',0)) clear=true;
38 |
39 | if(!clear) clear=false;
40 | else if(LOG) console.log('Clearing local and settings storages');
41 | if(clear) settingsStorage.clear();
42 | sutil.settingsToSelect(settingsStorage);
43 |
44 | if(!settingsStorage.getItem('weatherprovider') || settingsStorage.getItem('weatherprovider').includes('yahoo')) settingsStorage.setItem('weatherprovider','{"selected":[0],"values":[{"name":"OpenWeatherMap","value":"owm"}]}');
45 |
46 | if(!settingsStorage.getItem('screenWidth') && 'screen' in device && 'width' in device.screen) {
47 | if(LOG) console.log("device.screen.width Available, setting up in settingsStorage screenWidth and screenHeight...");
48 | settingsStorage.setItem("screenWidth", device.screen.width);
49 | settingsStorage.setItem("screenHeight", device.screen.height);
50 | }
51 | if(!settingsStorage.getItem('infoUrlInput')) {
52 | settingsStorage.setItem("infoUrl", C.DefaultInfoURL);
53 | settingsStorage.setItem("infoUrlInput", '{"name":"'+C.DefaultInfoURL+'"}');
54 | settingsStorage.setItem("infoNeedPosition", 'true');
55 | }
56 |
57 |
58 | if(clear) localStorage.clear();
59 |
60 | if(clear && LOG) console.log('Success clearing local and settings storages');
61 |
62 | localStorage.setItem('storageInited',1);
63 |
64 |
65 | if(!localStorage.getItem('lastInfo')) localStorage.setItem('lastInfo',0);
66 | if(!clear) {
67 | //info és weather period beállításához kell
68 | processSettingsMessage('settings',JSON.stringify(sutil.selectToSettings(settingsStorage,1)));
69 | }
70 | setTimeout(function(){
71 | settingsStorage.setItem('isLCD',(device.modelName&&(device.modelName=='Ionic'||device.modelName=='Versa'||device.modelName=='Versa Lite'))?'true':'false');
72 | settingsStorage.setItem('isSense',(device.modelName&&(device.modelName=='Sense'||device.modelName=='Versa 3'))?'true':'false');
73 | settingsStorage.setItem('access_internet',(companion.permissions.granted("access_internet"))?'true':'false');
74 | },1000);
75 | }
76 |
77 | if(LOG==2) initStorage(true);
78 | else initStorage();
79 |
80 |
81 | settingsStorage.onchange = function(evt) {
82 | if(LOG) console.log("Got message from settings, key: "+evt.key+", processing...");
83 | //if(evt.key == 'settingsRunning'){
84 | // settingsStorage.setItem('connectionInfo',JSON.stringify((messaging.peerSocket.readyState === messaging.peerSocket.OPEN)));
85 | //}
86 | //else
87 | if(!processSettingsMessage(evt.key,evt.newValue,true)){
88 | settingsStorage.setItem(evt.key,evt.oldValue);
89 | initStorage();
90 | settingsStorage.setItem('connectionInfo','false');
91 | };
92 | }
93 |
94 | if (me.launchReasons.settingsChanged) {
95 | if(LOG) console.log("launchReasons.settingsChanged, sendAllSettings in 3 secs...");
96 | setTimeout(function(){sendAllSettings();},3000);
97 | }
98 | if (me.launchReasons.peerAppLaunched) {
99 | if(LOG) console.log("launchReasons.peerAppLaunched, sendAllSettings in 3 secs...");
100 | setTimeout(function(){sendAllSettings();},3000);
101 | }
102 | function sendAllSettings(){
103 | for (let i = 0; i < settingsStorage.length; i++){
104 | //setTimeout(function(){processSettingsMessage(settingsStorage.key(i),(settingsStorage.getItem(settingsStorage.key(i))));},i*50);
105 | if(processSettingsMessage(settingsStorage.key(i),(settingsStorage.getItem(settingsStorage.key(i))),false)) return;
106 | }
107 | }
108 | function processSettingsMessage(key,newValue,defaultReturnValue){
109 | if(LOG){
110 | // Which setting changed
111 | //console.log("message ---- Processing settings message, key: " + key+");
112 | // What was the old value
113 | //console.log("old value: " + evt.oldValue)
114 | // What is the new value
115 | console.log("message ---- new value: " + newValue);
116 | }
117 | let messageType=null;
118 | let message=null;
119 | let json=null;
120 | switch(key){
121 | case "infoUrlTryTime":
122 | let settings=sutil.getSettingsArray(settingsStorage);
123 | let need=(settings[C.SettingsInfoEnabled]==1);
124 | if(need) downloadInfo(true);
125 | break;
126 | case "weatherDownloadNow":
127 | if(newValue == "true"){
128 | setTimeout(function(){weather.getWeather(true)},3500);
129 | debug("Weather starting...");
130 | }
131 | break;
132 | case "settings":
133 | messageType=C.MessageTypeSettings;
134 | if(json=JSON.parse(newValue)) {
135 | message=JSON.stringify(json);
136 | }
137 | debug("Submit settings changes...");
138 | break;
139 | case "timeZone":
140 | getTimeZone();
141 | break;
142 | /*case "settingsRunning":
143 | messageType=C.MessageTypePing;
144 | if(json=JSON.parse(newValue)) {
145 | message=JSON.stringify([json]);
146 | }
147 | break;*/
148 | }
149 |
150 | if(messageType===null || message===null) {
151 | if(LOG) console.log("message ---- No message to send to watch, messageType: "+messageType+", message: "+message);
152 | return defaultReturnValue;
153 | }
154 | return sendMessage(messageType,message,0,defaultReturnValue);
155 | }
156 |
157 | function sendMessage(type,data,counter,defaultReturnValue){
158 | if(!counter) counter=0;
159 | if(localStorage.getItem('sentMessage-'+type) != data){
160 | if(LOG) console.log("message ---- Sending message to watch: type: "+type+", data: "+(data));
161 | if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
162 | if(LOG) console.log("message ---- peerSocket is OK, sending...");
163 | localStorage.setItem('sentMessage-'+type,(data));
164 | messaging.peerSocket.send([type,JSON.parse(data)]);
165 | return true;
166 | }
167 | else{
168 | if(LOG) console.log("message ---- NOT sending message to watch messaging.peerSocket.readyState="+messaging.peerSocket.readyState);
169 | /*if(counter < 2){
170 | if(LOG) console.log("Delaying message, count #"+counter);
171 | counter++;
172 | setTimeout(function(){sendMessage(type,data,counter);},3000);
173 | }
174 | else{
175 | if(LOG) console.log("Message not sent, giving up...");
176 | }*/
177 | return false;
178 | }
179 | }
180 | else{
181 | if(LOG) console.log("message ---- Not sending message to watch, same message already sent");
182 | }
183 | return defaultReturnValue;
184 | }
185 |
186 | // Listen for the onmessage event
187 | messaging.peerSocket.onmessage = function(evt) {
188 | // Output the message to the console
189 | if(LOG) console.log("Got message from device: "+JSON.stringify(evt.data));
190 | setTimeout(function(){gotMessage(evt);},500);
191 | }
192 | let lastMessageGot=[1];
193 | function gotMessage(evt){
194 | let today=new Date();
195 |
196 | if(!(0 in evt.data)) return;
197 | let type=evt.data[0];
198 | settingsStorage.setItem('connectionInfo','true');
199 | initStorage();
200 | if(!lastMessageGot[type]) return;
201 | lastMessageGot[type]++;
202 |
203 | if(LOG) console.log("gotMessage... type: "+type+", lastMessageGot="+lastMessageGot[type]);
204 | if((lastMessageGot[type] + C.PING_TIMEOUT) >= today.getTime() ) {
205 | console.log("gotMessage at "+lastMessageGot[type]+" ("+((today.getTime()-lastMessageGot[type])/1000)+" secs ago) , not sending answer");
206 | return;
207 | }
208 | lastMessageGot[type]=today.getTime();
209 |
210 | if(type == C.MessageTypePing) {
211 | sendAnswer();
212 | if(evt.data[1]) localStorage.setItem('lastBattery',evt.data[1]*1-1);
213 | //console.log(JSON.stringify(evt.data));
214 | }
215 | }
216 | function sendAnswer(){
217 | getTimeZone();
218 | setTimeout(function(){getInfo();},1000);
219 | setTimeout(function(){weather.getWeather();},2000);
220 | //setTimeout(function(){download();},4000);
221 | }
222 | function getTimeZone(){
223 | if(LOG) console.log("Sending Timezone");
224 | //console.log(tzAbbr()) ;
225 | //let tz=Intl.DateTimeFormat().resolvedOptions().timeZone;
226 | //var moment = require('companion/moment-timezone.js');
227 | let today = new Date();
228 | let json=null;
229 | let analogTimezone="";
230 | if(settingsStorage.getItem('timeZone') && (json=JSON.parse(settingsStorage.getItem('timeZone')))){
231 | if(LOG) console.log("timeZone, json: "+JSON.stringify(json));
232 | let id=-1;
233 | if(json.values && json.values[0]) id = json.values[0].value*1-1;
234 | if(id in C.aryIannaTimeZones) analogTimezone=C.aryIannaTimeZones[id];
235 | }
236 |
237 | sendMessage(C.MessageTypePong,JSON.stringify([sutil.getTimezoneName(),sutil.getTimezoneName(analogTimezone),sutil.getTimezoneUtcOffset(analogTimezone),today.getTime()]),0,true);
238 | }
239 |
240 |
241 |
242 |
243 |
244 | function sendInfoToWatch(info,vibe){
245 | localStorage.setItem('lastInfoText',info);
246 | if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
247 | let today=new Date();
248 | localStorage.setItem('lastInfo',today.getTime());
249 | sendMessage(C.MessageTypeInfo,JSON.stringify([info,vibe,localStorage.getItem('lastInfo')*1]));
250 | debug("Sending info to watch");
251 | }
252 | else {
253 | if(LOG) console.log("NOT sending message to watch: "+info+", messaging.peerSocket.readyState="+messaging.peerSocket.readyState);
254 | debug("No connection to watch, try restart settings/clockface");
255 | }
256 | }
257 | function downloadInfo(test,position){
258 | if(!test) test=false;
259 | if(!settingsStorage.getItem('infoUrlTry') && settingsStorage.getItem('infoUrl')) test=false;
260 | let url=test?settingsStorage.getItem('infoUrlTry'):settingsStorage.getItem('infoUrl');
261 | //console.log(settingsStorage.getItem('infoUrlTryTime'));
262 | let needPosition=(settingsStorage.getItem('infoNeedPosition') && settingsStorage.getItem('infoNeedPosition')=="true");
263 | if(typeof position === 'undefined' && needPosition){
264 | var options = {
265 | enableHighAccuracy: false,
266 | timeout: 15000,
267 | maximumAge: Infinity
268 | };
269 | geolocation.getCurrentPosition(function(position) {
270 | if(LOG) console.log("Got position: "+position.coords.latitude + ", " + position.coords.longitude);
271 | debug("Success getCurrentPosition");
272 | settingsStorage.setItem('infoResult',"Success getCurrentPosition, downloading "+url+"");
273 | localStorage.setItem('lastPosition',JSON.stringify(locationdatafix(position)));
274 | downloadInfo(test,position);
275 | },function(err) {
276 | if(LOG) console.log(`getCurrentPosition ERROR(${err.code}): ${err.message}`);
277 | if(localStorage.getItem('lastPosition')) {
278 | debug("ERROR getCurrentPosition, using last location");
279 | settingsStorage.setItem('infoResult',"Error getCurrentPosition, last location is available, downloading "+url+" with last location...");
280 | downloadInfo(test,JSON.parse(localStorage.getItem('lastPosition')));
281 | }
282 | else {
283 | debug("ERROR getCurrentPosition, NO last location");
284 | settingsStorage.setItem('infoResult',"Error getCurrentPosition, NO last location available, downloading "+url+" without location...");
285 | downloadInfo(test,null);
286 | }
287 | },options);
288 | return;
289 | }
290 | else settingsStorage.setItem('infoResult',"Downloading info script without location, downloading "+url);
291 | if(!position) position=null;
292 | else position=locationdatafix(position);
293 | let dataValue=(settingsStorage.getItem('infoData'))?JSON.parse(settingsStorage.getItem('infoData')):{name:''};
294 | let settings=sutil.getSettingsArray(settingsStorage);
295 | let data={pos: position,
296 | uniqueid: uniqueid,
297 | modelId: device.modelId*1,
298 | modelName: device.modelName,
299 | screenWidth: device.screen.width*1,
300 | screenHeight: device.screen.height*1,
301 | battery: (localStorage.getItem('lastBattery'))?localStorage.getItem('lastBattery')*1:0,
302 | data: ('name' in dataValue)?dataValue.name:'',
303 | test: test
304 | };
305 | //console.log(data);
306 | let urlOption={
307 | method: 'POST', // *GET, POST, PUT, DELETE, etc.
308 | mode: 'cors', // no-cors, *cors, same-origin
309 | cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
310 | credentials: 'same-origin', // include, *same-origin, omit
311 | headers: {
312 | 'Content-Type': 'application/json'
313 | // 'Content-Type': 'application/x-www-form-urlencoded',
314 | },
315 | redirect: 'follow', // manual, *follow, error
316 | referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
317 | body: JSON.stringify(data) // body data type must match "Content-Type" header
318 | }
319 |
320 | fetch(url,urlOption).then(function(response) {
321 | return response.text();
322 | }).then(function(text) {
323 | if(LOG) console.log("Got result from info script, try to decode: "+text.substring(0,1000)+"");
324 | settingsStorage.setItem('infoResult',"Got result from info script, try to decode: "+text.substring(0,500)+((text.length>500)?"...":""));
325 | //outbox.enqueue("wallpaper.jpg", buffer);
326 | processInfoResult(text,test);
327 | }).catch(function (error) {
328 | if(LOG) console.log("Got ERROR response info script: " + error);
329 | setTimeout(function(){settingsStorage.setItem('infoResult',"Got ERROR response info script: " + error);},3000);
330 | });
331 | }
332 | function processInfoResult(text,test){
333 | //settingsStorage.setItem('infoResult','SUCCESS, result: '+text);
334 | let json=JSON.parse(text);
335 | let info='Decode error';
336 | let vibe=0;
337 | let status='';
338 | if(json && ('status' in json)) status=json.status;
339 | if(json && ('info' in json)) info=(json.info)?json.info.replace("\r",""):"";
340 | if(json && ('vibe' in json)) vibe=json.vibe*1;
341 | if(!C.VIBRATE_PATTERNS[vibe]) vibe=0;
342 | if(status == 'error') info='Script Error';
343 | else if(json && test) settingsStorage.setItem('infoUrl',settingsStorage.getItem('infoUrlTry'));
344 | sendInfoToWatch(info,vibe);
345 | }
346 |
347 |
348 | function getInfo(force){
349 | if(LOG) console.log('getInfo...');
350 | //force=true;
351 | if(!force) force=false;
352 | else localStorage.setItem('lastInfo',0) ;
353 | if(needInfoDownload(force)) {
354 | if(messaging.peerSocket.readyState === messaging.peerSocket.OPEN){
355 | //localStorage.setItem('lastDownloadStart',today.getTime());
356 | if(infoIsRunning) {
357 | if(LOG) console.log("Info download not start, already running");
358 | return;
359 | }
360 | infoIsRunning=true;
361 | setTimeout(function(){infoIsRunning=false;},5000);
362 | downloadInfo();
363 | }
364 | else{
365 | if(LOG) console.log("Info download not start, messaging.peerSocket.readyState = "+messaging.peerSocket.readyState);
366 | debug("No connection to watch, try restart settings/app");
367 | }
368 | }
369 | }
370 | function needInfoDownload(force){
371 | let today=new Date();
372 | let settings=sutil.getSettingsArray(settingsStorage);
373 | let need=(settings[C.SettingsInfoEnabled]==1);
374 | if(!need){
375 | if(LOG) console.log("Info not enabled, no need to download");
376 | }
377 | else{
378 | //let settings=(settingsStorage.getItem('settings'))?JSON.parse(settingsStorage.getItem('settings')):null;
379 | //if(!settings) settings=C.SETTINGS_DEFAULTS;
380 |
381 | let INFO_PERIOD=settings[C.SettingsInfoPeriod];
382 | need = (force || ((localStorage.getItem('lastInfo')*1 + INFO_PERIOD*C.MILLISECONDS_PER_MINUTE) < today.getTime()));
383 | if(!need) if(LOG) console.log("Info download not start, downloaded at: "+localStorage.getItem('lastInfo')+", " + ((today.getTime()-localStorage.getItem('lastInfo'))/C.MILLISECONDS_PER_MINUTE)+" minutes ago");
384 | }
385 | return need;
386 | }
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 |
395 |
396 |
397 |
398 | function locationdatafix(position){
399 | let positionObject = {};
400 |
401 | if ('coords' in position) {
402 | positionObject.coords = {};
403 |
404 | if ('latitude' in position.coords) {
405 | positionObject.coords.latitude = position.coords.latitude;
406 | }
407 | if ('longitude' in position.coords) {
408 | positionObject.coords.longitude = position.coords.longitude;
409 | }
410 | if ('accuracy' in position.coords) {
411 | positionObject.coords.accuracy = position.coords.accuracy;
412 | }
413 | if ('altitude' in position.coords) {
414 | positionObject.coords.altitude = position.coords.altitude;
415 | }
416 | if ('altitudeAccuracy' in position.coords) {
417 | positionObject.coords.altitudeAccuracy = position.coords.altitudeAccuracy;
418 | }
419 | if ('heading' in position.coords) {
420 | positionObject.coords.heading = position.coords.heading;
421 | }
422 | if ('speed' in position.coords) {
423 | positionObject.coords.speed = position.coords.speed;
424 | }
425 | }
426 |
427 | if ('timestamp' in position) {
428 | positionObject.timestamp = position.timestamp;
429 | }
430 | return positionObject;
431 | }
432 |
433 |
434 | function debug(text){
435 | settingsStorage.setItem('debugText',text);
436 | console.log("debugText: "+text);
437 | }
438 |
439 | weather.init(debug,device,sendMessage,uniqueid);
440 |
--------------------------------------------------------------------------------
/companion/weather.js:
--------------------------------------------------------------------------------
1 | import {COMPANION_LOG as LOG} from "../common/const";
2 | import * as weatherEnv from "./weatherEnv";
3 |
4 |
5 | import { localStorage } from "local-storage";
6 | import { settingsStorage } from "settings";
7 | import * as messaging from "messaging";
8 | import { geolocation } from "geolocation";
9 |
10 |
11 | import * as C from "../common/sconst"
12 | //import * as util from "../common/utils"
13 | import * as util from "../common/sutils";
14 |
15 | let debug=null;
16 | let device=null;
17 | let sendMessage=null;
18 | let uniqueid='';
19 |
20 |
21 | let weatherIsRunning=false;
22 |
23 | export function init(debugFunction,deviceObject,sendMessageFunction,uniqueID){
24 | debug=debugFunction;
25 | device=deviceObject;
26 | sendMessage=sendMessageFunction;
27 | uniqueid=uniqueID;
28 | if(!localStorage.getItem('lastWeather')) localStorage.setItem('lastWeather',0);
29 | if(!localStorage.getItem('lastForecast')) localStorage.setItem('lastForecast',0);
30 | if(!localStorage.getItem('lastOWMForecast')) localStorage.setItem('lastOWMForecast',"");
31 | }
32 |
33 | export function getWeatherProvider(){
34 | let json=null;
35 | if(LOG) console.log("getWeatherProvider, weatherprovider: "+settingsStorage.getItem('weatherprovider'));
36 | if(settingsStorage.getItem('weatherprovider') && (json=JSON.parse(settingsStorage.getItem('weatherprovider')))){
37 | if(LOG) console.log("getWeatherProvider, json: "+JSON.stringify(json));
38 | if(json.values && json.values[0]) return json.values[0].value;
39 | }
40 | return "owm";
41 | }
42 |
43 | function needWeatherDownload(force){
44 | let today=new Date();
45 | let need=util.weatherEnabled(settingsStorage);
46 | if(!need){
47 | if(LOG) console.log("Weather not enabled in statusIds, no need to download");
48 | }
49 | else{
50 | let WEATHER_PERIOD=util.WEATHER_PERIOD();
51 | need = (force || ((localStorage.getItem('lastWeather')*1 + WEATHER_PERIOD*C.MILLISECONDS_PER_MINUTE) < today.getTime()));
52 | if(!need) if(LOG) console.log("Weather not start, downloaded at: "+localStorage.getItem('lastWeather')+", " + ((today.getTime()-localStorage.getItem('lastWeather'))/C.MILLISECONDS_PER_MINUTE)+" minutes ago (WEATHER_PERIOD:"+WEATHER_PERIOD+")");
53 | }
54 | return need;
55 | }
56 | export function getWeather(force){
57 | if(LOG) console.log('getWeather...');
58 | setTimeout(function(){
59 | settingsStorage.setItem("weatherDownloadNow","false");
60 | },5000);
61 | if(!force) force=false;
62 | else localStorage.setItem('lastOWMForecast','') ;
63 | if(needWeatherDownload(force)) {
64 | if(messaging.peerSocket.readyState === messaging.peerSocket.OPEN){
65 | //localStorage.setItem('lastDownloadStart',today.getTime());
66 | if(weatherIsRunning) {
67 | if(LOG) console.log("Weather not start, already running");
68 | return;
69 | }
70 | debug("Weather provider: "+getWeatherProvider());
71 | weatherIsRunning=true;
72 | setTimeout(function(){weatherIsRunning=false;},5000);
73 | if (getWeatherProvider() == "owm") getOWM();
74 | else getMeta();
75 | }
76 | else{
77 | if(LOG) console.log("Weather not start, messaging.peerSocket.readyState = "+messaging.peerSocket.readyState);
78 | debug("No connection to watch, try restart settings/app");
79 | }
80 | }
81 | }
82 |
83 |
84 | //////////////////////////////// OWM ////////////////////////////////
85 |
86 |
87 | function decodeAPIresult(result){
88 | let jsonAPI=null;
89 | try {
90 | jsonAPI = JSON.parse(result);
91 | if(LOG) console.log("Successfully parsed API json");
92 | } catch(e) {
93 | if(LOG) console.log("Error while decoding API json: "+result);
94 | }
95 | if(jsonAPI && ('key' in jsonAPI) && ('use_allowed' in jsonAPI) && ('status' in jsonAPI) && jsonAPI.status=='ok'){
96 | if(jsonAPI.use_allowed > 0){
97 | if(jsonAPI.key !=''){
98 | return jsonAPI.key;
99 | }
100 | else{
101 | if(LOG) console.log("No key received: "+result);
102 | debug("No key received. try later.");
103 | }
104 | }
105 | else{
106 | if(LOG) console.log("API usage not allowed: "+result);
107 | debug("API usage not allowed yet. try later.");
108 | }
109 | }
110 | else{
111 | if(LOG) console.log("Error API json: "+result);
112 | debug("Error response from API server");
113 | }
114 | return '';
115 | }
116 | function getOWM(){
117 | if(LOG) console.log("Start weather downloading OWM");
118 | debug("Start weather downloading OWM");
119 | var options = {
120 | enableHighAccuracy: false,
121 | timeout: 15000,
122 | maximumAge: Infinity
123 | };
124 | geolocation.getCurrentPosition(function(position) {
125 | if(LOG) console.log("Got position: "+position.coords.latitude + ", " + position.coords.longitude);
126 | debug("Success getCurrentPosition");
127 | localStorage.setItem('lastPosition',JSON.stringify(locationdatafix(position)));
128 | getOWMWeather(position);
129 | },function(err) {
130 | if(LOG) console.log(`getCurrentPosition ERROR(${err.code}): ${err.message}`);
131 | debug("Error getCurrentPosition");
132 | if(localStorage.getItem('lastPosition')) getOWMWeather(JSON.parse(localStorage.getItem('lastPosition')));
133 | },options);
134 | }
135 | function getOWMWeather(position,apiKey){
136 | let url='';
137 | if(!apiKey){
138 | if(weatheEnv.OWMApiKey){
139 | apiKey=weatherEnv.OWMApiKey;
140 | getOWMWeather(position,apiKey);
141 | }
142 | else{
143 | url=weatherEnv.OWMApiKeyProviderUrl+'?uniqueid='+uniqueid+'&v='+C.AppVersion+'&m='+device.modelId+"-"+device.modelName+'&use='+((needOWMForecast())?2:1);
144 | if(LOG) console.log("Getting api key for OWM "+url);
145 | debug("Getting api key for OWM");
146 | fetch(url).then(function(response) {
147 | return response.text();
148 | }).then(function(text) {
149 | apiKey=decodeAPIresult(text);
150 | //outbox.enqueue("wallpaper.jpg", buffer);
151 | if(apiKey) getOWMWeather(position,apiKey);
152 | }).catch(function (error) {
153 | if(LOG) console.log("Got ERROR response api key server: " + error);
154 | debug("Error response from API server");
155 | });
156 | }
157 | return;
158 | }
159 |
160 | url = 'https://api.openweathermap.org/data/2.5/weather?lang='+util.getLanguageCode(settingsStorage)+'&lat=' + position.coords.latitude + '&lon=' + position.coords.longitude + '&units=imperial&appid='+apiKey;
161 | //url = 'https://api.openweathermap.org/data/2.5/weather?lang='+util.getLanguageCode(settingsStorage)+'&lat=' + position.coords.latitude + '&lon=' + position.coords.longitude + '&units=imperial&appid=czandorapikey';
162 | //url='https://czandor.hu/pebble/weather_owm.php?uniqueid='+uniqueid+'&v='+C.AppVersion+'&m='+device.modelId+"-"+device.modelName+'&url='+encodeURIComponent(url);
163 |
164 | if(LOG) console.log("OWM, url: "+url);
165 |
166 | fetch(url).then(function(response) {
167 | return response.text();
168 | }).then(function(text) {
169 | if(LOG) console.log("Got weather from OWM, try to download forecast");
170 | debug("Weather downloaded, getting forecast...");
171 | //outbox.enqueue("wallpaper.jpg", buffer);
172 | getOWMForecast(text,position,apiKey);
173 | }).catch(function (error) {
174 | if(LOG) console.log("Got ERROR response from OWM: " + error);
175 | debug("Error response from server");
176 | });
177 | }
178 | function needOWMForecast(){
179 | let today=new Date();
180 | return (!localStorage.getItem('lastOWMForecast') || (localStorage.getItem('lastForecast')*1 + C.WEATHER_FORECAST_PERIOD*C.MILLISECONDS_PER_MINUTE) < today.getTime());
181 | }
182 | function getOWMForecast(currentWeather,position,apiKey){
183 | if(LOG) console.log("Start forecast downloading OWM");
184 | let today=new Date();
185 | if(needOWMForecast()) {
186 | //let position=JSON.parse(localStorage.getItem('lastPosition'));
187 | //geolocation.getCurrentPosition(function(position) {
188 | //if(LOG) console.log("Got position: "+position.coords.latitude + ", " + position.coords.longitude);
189 | let url='';
190 | url='https://api.openweathermap.org/data/2.5/forecast?lang='+util.getLanguageCode(settingsStorage)+'&lat=' + position.coords.latitude + '&lon=' + position.coords.longitude + '&cnt=8&units=imperial&appid='+apiKey;
191 | //url='https://api.openweathermap.org/data/2.5/forecast?lang='+util.getLanguageCode(settingsStorage)+'&lat=' + position.coords.latitude + '&lon=' + position.coords.longitude + '&cnt=8&units=imperial&appid=czandorapikey';
192 | //url='https://czandor.hu/pebble/weather_owm.php?url='+encodeURIComponent(url);
193 |
194 | if(LOG) console.log("OWM forecast, url: "+url);
195 |
196 | fetch(url).then(function(response) {
197 | return response.text();
198 | }).then(function(text) {
199 | if(LOG) console.log("Got forecast from OWM, try to decode("+currentWeather+"\n"+text+")");
200 | debug("Forecast downloaded");
201 | //outbox.enqueue("wallpaper.jpg", buffer);
202 | localStorage.setItem('lastOWMForecast',text);
203 | localStorage.setItem('lastForecast',today.getTime());
204 | decodeOWM(currentWeather,text,apiKey);
205 | }).catch(function (error) {
206 | if(LOG) console.log("Got ERROR response from OWM forecast: " + error);
207 | debug("Forecast error from server");
208 | });
209 |
210 | //});
211 | }
212 | else{
213 | if(LOG) console.log("NOT start forecast downloading OWM, already have and fresh");
214 | debug("Forecast already have and fresh");
215 | decodeOWM(currentWeather,localStorage.getItem('lastOWMForecast'),apiKey);
216 | }
217 | }
218 |
219 | function decodeOWM(currentWeather,forecastWeather,apiKey){
220 | let jsonC=null;
221 | let jsonF=null;
222 | try {
223 | jsonC = JSON.parse(currentWeather);
224 | if(LOG) console.log("Successfully parsed OWM current json");
225 | } catch(e) {
226 | if(LOG) console.log("Error while decoding OWM current json");
227 | }
228 | try {
229 | jsonF = JSON.parse(forecastWeather);
230 | if(LOG) console.log("Successfully parsed OWM forecast json");
231 | } catch(e) {
232 | if(LOG) console.log("Error while decoding OWM forecast json");
233 | }
234 | if(jsonC.cod && jsonC.cod == "200" && jsonF.cod && jsonF.cod == "200") {
235 | let json=jsonC;
236 | let temperature = Math.round(json.main.temp);
237 | let hum = Math.round(json.main.humidity);
238 | let wind=-1;
239 | let windDir=-1;
240 | if(json.wind) {
241 | wind = Math.round(json.wind.speed);
242 | windDir = Math.round(json.wind.deg);
243 | }
244 | let city = "";
245 | if(json.name) city=json.name;
246 | else if(json.main.name) city=json.main.name;
247 |
248 | // Conditions
249 | let conditionCode = json.weather[0].icon;//json.weather[0].id;
250 | let condition = json.weather[0].description;
251 | if(condition) condition=condition.charAt(0).toUpperCase() + condition.slice(1);
252 |
253 | // night state
254 | let isNight = (json.weather[0].icon.slice(-1) == 'n') ? 1 : 0;
255 |
256 | let iconToLoad = 0;//getIconForConditionCodeOWM(conditionCode, isNight);
257 | if(conditionCode in owmWeatherCodes){
258 | if(owmWeatherCodes[conditionCode] in weatherCodesToId) iconToLoad=weatherCodesToId[owmWeatherCodes[conditionCode]];
259 | }
260 |
261 | let sunrise=(new Date(json.sys.sunrise*1000)).getHours()+':'+(new Date(json.sys.sunrise*1000)).getMinutes();
262 | let sunset=(new Date(json.sys.sunset*1000)).getHours()+':'+(new Date(json.sys.sunset*1000)).getMinutes();
263 |
264 | if(LOG) console.log('Current weather downloaded successfully temp:' + temperature + ', Wind:'+wind + ', Humidity:'+hum + ', City:'+city+', isNight:' + isNight + ', conditionCode:'+conditionCode+', Icon:' + iconToLoad);
265 | json=jsonF;
266 | let forecast = extractFakeDailyForecastOWM(json);
267 |
268 |
269 | // Conditions
270 | conditionCode = forecast.condition;
271 | let fCondition= forecast.description;
272 | if(fCondition) fCondition=fCondition.charAt(0).toUpperCase() + fCondition.slice(1);
273 |
274 | let fIconToLoad = 0;//getIconForConditionCodeOWM(conditionCode, false);
275 | let lowTemp=Math.round(forecast.lowTemp);
276 | let highTemp=Math.round(forecast.highTemp);
277 |
278 | if(LOG) console.log('Forecast downloaded successfully ' + highTemp + '/' + lowTemp + ', conditionCode:'+conditionCode+', Icon:' + iconToLoad+', fCondition:' + fCondition);
279 |
280 |
281 | sendWeatherToWatch(condition,temperature,hum,wind,windDir,city,iconToLoad,lowTemp,highTemp,fIconToLoad,fCondition,sunrise,sunset);
282 |
283 | //sendWeatherToWatch(condition,temperature,hum,wind,windDir,city,iconToLoad,lowTemp,highTemp,fIconToLoad,fCondition);
284 |
285 | }
286 | else{
287 | if(jsonC.cod && jsonC.cod == "401"){
288 | if(LOG) console.log("OWM invalid api key "+apiKey);
289 | debug("Invalid OWM API key");
290 | }
291 | else{
292 | if(LOG) console.log("OWM unknown error: "+currentWeather);
293 | debug("Unknown error while decoding OWM result");
294 | }
295 | let url='https://czandor.hu/pebble/getapikey_owm.php?uniqueid='+uniqueid+'&v='+C.AppVersion+'&m='+device.modelId+"-"+device.modelName+'&use=0&error='+encodeURIComponent('apiKey:'+apiKey+', result:'+currentWeather);
296 | fetch(url).then(function(response) {
297 | return response.text();
298 | }).then(function(text) {
299 | }).catch(function (error) {
300 | });
301 |
302 | }
303 | }
304 | function extractFakeDailyForecastOWM(json) {
305 | let todaysForecast = {};
306 |
307 | // find the max and min of those temperatures
308 | todaysForecast.highTemp = -Number.MAX_SAFE_INTEGER;
309 | todaysForecast.lowTemp = Number.MAX_SAFE_INTEGER;
310 |
311 | for(let i = 0; i < json.list.length; i++) {
312 | if(todaysForecast.highTemp < json.list[i].main.temp_max) {
313 | todaysForecast.highTemp = json.list[i].main.temp_max;
314 | }
315 |
316 | if(todaysForecast.lowTemp > json.list[i].main.temp_min) {
317 | todaysForecast.lowTemp = json.list[i].main.temp_min;
318 | }
319 | }
320 |
321 | // we can't really "average" conditions, so we'll just cheat and use...one of them :-O
322 | todaysForecast.condition = json.list[3].weather[0].id;
323 | todaysForecast.description = json.list[3].weather[0].description;
324 | return todaysForecast;
325 | }
326 |
327 |
328 | ////////////////////////////////////////////
329 |
330 |
331 | //////////////////////////////////////////// META (by czandor) ////////////////////////
332 |
333 | function getMeta(){
334 | if(LOG) console.log("Start weather downloading Meta");
335 | debug("Start weather downloading Meta");
336 | var options = {
337 | enableHighAccuracy: false,
338 | timeout: 15000,
339 | maximumAge: Infinity
340 | };
341 | geolocation.getCurrentPosition(function(position) {
342 | if(LOG) console.log("Got position: "+position.coords.latitude + ", " + position.coords.longitude);
343 | debug("Success getCurrentPosition");
344 | localStorage.setItem('lastPosition',JSON.stringify(locationdatafix(position)));
345 | getMetaWeather(position);
346 |
347 | },function(err) {
348 | if(LOG) console.log(`getCurrentPosition ERROR(${err.code}): ${err.message}`);
349 | debug("Error getCurrentPosition");
350 | if(localStorage.getItem('lastPosition')) getMetaWeather(JSON.parse(localStorage.getItem('lastPosition')));
351 | },options);
352 | }
353 | function getMetaWeather(position){
354 | /*let url = 'https://query.yahooapis.com/v1/public/yql?q=' +
355 | encodeURIComponent('select * from weather.forecast where woeid in (select woeid from geo.places(1) where text="('+position.coords.latitude+','+position.coords.longitude+')") and u="'+
356 | 'f'+'"') + '&format=json';*/
357 | //let url='https://weather-ydn-yql.media.yahoo.com/forecastrss?lat='+position.coords.latitude+'&lon='+position.coords.longitude+'&u=f'+'&format=json';
358 | let url=weatherEnv.MetaUrl+'?uniqueid='+uniqueid+'&v='+C.AppVersion+'&m='+device.modelId+"-"+device.modelName+'&lat='+position.coords.latitude+'&lon='+position.coords.longitude+'&source=fitbit&device='+((device.ionic)?'ionic':'versa')+'&app='+C.AppName+'&IMAGEPROVIDER_DEFAULT='+C.AppName2;
359 |
360 |
361 | if(LOG) {
362 | console.log("Meta, url: "+url);
363 | }
364 | fetch(url).then(function(response) {
365 | return response.text();
366 | }).then(function(text) {
367 | if(LOG) console.log("Got weather from Meta, try to decode("+text+")");
368 | debug("Got weather and forecast");
369 | //outbox.enqueue("wallpaper.jpg", buffer);
370 | decodeMeta(text);
371 | }).catch(function (error) {
372 | if(LOG) console.log("Got ERROR response from Meta: " + error);
373 | debug("Error response from server");
374 | });
375 |
376 | }
377 | function decodeMeta(text){
378 | let json=null;
379 | try {
380 | json = JSON.parse(text);
381 | if(LOG) console.log("Successfully parsed Meta json");
382 | } catch(e) {
383 | if(LOG) console.log("Error while decoding Meta json");
384 | }
385 | if(json && json.location && json.current_observation){
386 |
387 |
388 |
389 |
390 |
391 |
392 |
393 |
394 | let temperature = Math.round(json.current_observation.condition.temperature);
395 | let hum = Math.round(json.current_observation.atmosphere.humidity);
396 | let wind=-1;
397 | let windDir=-1;
398 | if(json.current_observation.wind) {
399 | wind = Math.round(json.current_observation.wind.speed);
400 | windDir = Math.round(json.current_observation.wind.direction);
401 | }
402 |
403 | let city = json.location.city;
404 |
405 | // Conditions
406 | let conditionCode = json.current_observation.condition.code*1;
407 | let condition = json.current_observation.condition.text;
408 |
409 | let iconToLoad = 0;//getIconForConditionCodeYahoo(conditionCode);
410 | if(conditionCode in yahooWeatherCodes){
411 | if(yahooWeatherCodes[conditionCode] in weatherCodesToId) iconToLoad=weatherCodesToId[yahooWeatherCodes[conditionCode]];
412 | }
413 |
414 | if(LOG) console.log('Current weather downloaded successfully condition: '+condition+', temp:' + temperature + ', Wind:'+wind + ', Humidity:'+hum + ', City:'+city + ', conditionCode:'+conditionCode+', Icon:' + iconToLoad);
415 |
416 | //getForecastWeather();
417 | let todaysForecast = json.forecasts[0];
418 | let tomorrowForecast = json.forecasts[1];
419 | if(!tomorrowForecast) tomorrowForecast=todaysForecast;
420 |
421 | // Conditions
422 | //var d = new Date();
423 | let fCondition=todaysForecast.text;
424 | conditionCode = todaysForecast.code;
425 | let d=new Date();
426 | if(d.getHours() > 20) {
427 | conditionCode = tomorrowForecast.code;
428 | fCondition = tomorrowForecast.text;
429 | }
430 | let fIconToLoad = 0;//getIconForConditionCodeYahoo(conditionCode);
431 | let lowTemp;
432 | let lowTemp2;
433 | let highTemp;
434 | let highTemp2;
435 | lowTemp=Math.round(parseInt(todaysForecast.low, 10));
436 | highTemp=Math.round(parseInt(todaysForecast.high, 10));
437 | lowTemp2=Math.round(parseInt(tomorrowForecast.low, 10));
438 | highTemp2=Math.round(parseInt(tomorrowForecast.high, 10));
439 | if(lowTemp > lowTemp2) lowTemp=lowTemp2;
440 | if(highTemp < highTemp2) highTemp=highTemp2;
441 |
442 |
443 | if(LOG) console.log('Forecast downloaded successfully ' + highTemp + '/' + lowTemp + ', conditionCode:'+conditionCode+', Icon:' + iconToLoad+', fCondition:'+fCondition );
444 |
445 | //let text=""+temperature+"° "+condition;//+localStorage.getItem('tempUnits');
446 |
447 | const convertTime12to24 = (time12h) => {
448 | const [time, modifier] = time12h.toUpperCase().split(' ');
449 |
450 | let [hours, minutes] = time.split(':');
451 |
452 | if (hours === '12') {
453 | hours = '00';
454 | }
455 |
456 | if (modifier === 'PM') {
457 | hours = parseInt(hours, 10) + 12;
458 | }
459 |
460 | return `${hours}:${minutes}`;
461 | }
462 | let sunrise='--:--';
463 | let sunset='--:--';
464 | if(json.current_observation.astronomy) {
465 | sunrise = convertTime12to24(json.current_observation.astronomy.sunrise);
466 | sunset = convertTime12to24(json.current_observation.astronomy.sunset);
467 | }
468 | if(LOG) console.log('Sun downloaded successfully ' + sunrise + '/' + sunset + '' );
469 | sendWeatherToWatch(condition,temperature,hum,wind,windDir,city,iconToLoad,lowTemp,highTemp,fIconToLoad,fCondition,sunrise,sunset);
470 |
471 |
472 | }
473 | else{
474 | debug("Something wrong with Meta json");
475 | console.log(text);
476 | }
477 | }
478 |
479 | /////////////////////////////////////////////////////////////////////////////////////
480 |
481 | export function locationdatafix(position){
482 | let positionObject = {};
483 |
484 | if ('coords' in position) {
485 | positionObject.coords = {};
486 |
487 | if ('latitude' in position.coords) {
488 | positionObject.coords.latitude = position.coords.latitude;
489 | }
490 | if ('longitude' in position.coords) {
491 | positionObject.coords.longitude = position.coords.longitude;
492 | }
493 | if ('accuracy' in position.coords) {
494 | positionObject.coords.accuracy = position.coords.accuracy;
495 | }
496 | if ('altitude' in position.coords) {
497 | positionObject.coords.altitude = position.coords.altitude;
498 | }
499 | if ('altitudeAccuracy' in position.coords) {
500 | positionObject.coords.altitudeAccuracy = position.coords.altitudeAccuracy;
501 | }
502 | if ('heading' in position.coords) {
503 | positionObject.coords.heading = position.coords.heading;
504 | }
505 | if ('speed' in position.coords) {
506 | positionObject.coords.speed = position.coords.speed;
507 | }
508 | }
509 |
510 | if ('timestamp' in position) {
511 | positionObject.timestamp = position.timestamp;
512 | }
513 | return positionObject;
514 | }
515 |
516 | function sendWeatherToWatch(condition,temperature,hum,wind,windDir,city,iconToLoad,lowTemp,highTemp,fIconToLoad,fCondition,sunrise,sunset){
517 | if (messaging.peerSocket.readyState === messaging.peerSocket.OPEN) {
518 | let today=new Date();
519 | localStorage.setItem('lastWeather',today.getTime());
520 | sendMessage(C.MESSAGE_TYPE_WEATHER,JSON.stringify([condition,temperature,hum,wind,windDir,city,iconToLoad,lowTemp,highTemp,fIconToLoad,fCondition,sunrise,sunset,localStorage.getItem('lastWeather')*1]));
521 | debug("Sending weather to watch");
522 | }
523 | else {
524 | if(LOG) console.log("NOT sending message to watch: "+text+", messaging.peerSocket.readyState="+messaging.peerSocket.readyState);
525 | debug("No connection to watch, try restart settings/clockface");
526 | }
527 | }
528 |
529 | const owmWeatherCodes={
530 | "01d":"Sunny",
531 | "01n":"ClearNight",
532 | "02d":"PartlyCloudyDay",
533 | "02n":"PartlyCloudyNight",
534 | "03n":"PartlyCloudy",
535 | "03d":"PartlyCloudy",
536 | "04d":"MostlyCloudyDay",
537 | "04n":"PartlyCloudyNight",
538 | "09d":"ShowersDay",
539 | "09n":"ShowersNight",
540 | "10d":"ShowersDay",
541 | "10n":"ShowersNight",
542 | "11d":"Thundershowers",
543 | "11n":"Thundershowers",
544 | "13d":"Snow",
545 | "13n":"Snow",
546 | "50d":"Foggy",
547 | "50n":"Foggy"
548 | };
549 |
550 | const yahooWeatherCodes=[
551 | "Tornado",
552 | "TropicalStorm",
553 | "Hurricane",
554 | "SevereThunderstorms",
555 | "Thunderstorms",
556 | "MixedRainSnow",
557 | "MixedRainSleet",
558 | "MixedSnowSleet",
559 | "FreezingDrizzle",
560 | "Drizzle",
561 | "FreezingRain",
562 | "ShowersNight",
563 | "ShowersDay",
564 | "SnowFlurries",
565 | "LightSnowShowers",
566 | "BlowingSnow",
567 | "Snow",
568 | "Hail",
569 | "Sleet",
570 | "Dust",
571 | "Foggy",
572 | "Haze",
573 | "Smoky",
574 | "Blustery",
575 | "Windy",
576 | "Cold",
577 | "Cloudy",
578 | "MostlyCloudyNight",
579 | "MostlyCloudyDay",
580 | "PartlyCloudyNight",
581 | "PartlyCloudyDay",
582 | "ClearNight",
583 | "Sunny",
584 | "FairNight",
585 | "FairDay",
586 | "MixedRainAndHail",
587 | "Hot",
588 | "IsolatedThunderstorms",
589 | "ScatteredThunderstormsNight",
590 | "ScatteredThunderstormsDay",
591 | "ScatteredShowers",
592 | "HeavySnowNight",
593 | "ScatteredSnowShowers",
594 | "HeavySnowDay",
595 | "PartlyCloudy",
596 | "Thundershowers",
597 | "SnowShowers",
598 | "IsolatedThundershowers"
599 | ];
600 | const weatherCodesToId={
601 | 'Tornado':4,
602 | 'TropicalStorm':4,
603 | 'Hurricane':4,
604 | 'SevereThunderstorms':4,
605 | 'Thunderstorms':4,
606 | 'MixedRainSnow':8,
607 | 'MixedRainSleet':8,
608 | 'MixedSnowSleet':9,
609 | 'FreezingDrizzle':9,
610 | 'Drizzle':9,
611 | 'FreezingRain':9,
612 | 'ShowersNight':2,
613 | 'ShowersDay':7,
614 | 'SnowFlurries':9,
615 | 'LightSnowShowers':8,
616 | 'BlowingSnow':9,
617 | 'Snow':9,
618 | 'Hail':8,
619 | 'Sleet':8,
620 | 'Dust':3,
621 | 'Foggy':3,
622 | 'Haze':3,
623 | 'Smoky':3,
624 | 'Blustery':3,
625 | 'Windy':11,
626 | 'Cold':9,
627 | 'Cloudy':3,
628 | 'MostlyCloudyNight':6,
629 | 'MostlyCloudyDay':5,
630 | 'PartlyCloudyNight':6,
631 | 'PartlyCloudyDay':5,
632 | 'ClearNight':1,
633 | 'Sunny':10,
634 | 'FairNight':1,
635 | 'FairDay':10,
636 | 'MixedRainAndHail':8,
637 | 'Hot':10,
638 | 'IsolatedThunderstorms':4,
639 | 'ScatteredThunderstormsNight':2,
640 | 'ScatteredThunderstormsDay':7,
641 | 'ScatteredShowers':8,
642 | 'HeavySnowNight':9,
643 | 'ScatteredSnowShowers':9,
644 | 'HeavySnowDay':9,
645 | 'PartlyCloudy':3,
646 | 'Thundershowers':4,
647 | 'SnowShowers':9,
648 | 'IsolatedThundershowers':4
649 | };
650 |
--------------------------------------------------------------------------------
/companion/weatherEnv.js:
--------------------------------------------------------------------------------
1 |
2 | export const OWMApiKey=''; //fill with your OWM API key, or...
3 | export const OWMApiKeyProviderUrl=''; //Or you can make your own server which sends you your actual OWM API key
4 | export const MetaUrl=''; // My (czandor) own weather service. Not published. Very bad... :( not used
5 |
--------------------------------------------------------------------------------
/info.php:
--------------------------------------------------------------------------------
1 | uniqueid)?$obj->uniqueid:"uniqueid";
8 | $modelId=isset($obj->modelId)?$obj->modelId*1:0;
9 | $modelName=isset($obj->modelName)?$obj->modelName:"";
10 | $screenWidth=isset($obj->screenWidth)?$obj->screenWidth*1:300;
11 | $screenHeight=isset($obj->screenHeight)?$obj->screenHeight:300;
12 | $data=isset($obj->data)?$obj->data:"";
13 | $battery=isset($obj->battery)?$obj->battery*1:0;
14 |
15 | // test data from the phone
16 | $test=isset($obj->test)?$obj->test:false;
17 | $response->status='ok';
18 | $info="";
19 | if($test) $info.="test\n";
20 | else{
21 | $rf=file_get_contents("https://api.coindesk.com/v1/bpi/currentprice.json");
22 | $obj = json_decode($rf);
23 | if($obj && $obj->bpi) $info.="BTC ".round($obj->bpi->USD->rate_float)." USD\n";
24 | }
25 | $info.="\n".gmdate("Y-m-d H:i:s");
26 | $response->info=$info;
27 | $response->vibe=0;
28 | }
29 | else $response->status='error';
30 |
31 | header("Content-Type: application/json;charset=utf-8");
32 | echo json_encode($response);
33 |
34 |
35 | ?>
36 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "devDependencies": {
3 | "@fitbit/sdk": "~6.0.0"
4 | },
5 | "fitbit": {
6 | "appUUID": "b36d8ea6-c803-45fe-ad34-91dbe8d08e89",
7 | "appType": "clockface",
8 | "appDisplayName": "Programmer's Watch",
9 | "iconFile": "resources/icon.png",
10 | "wipeColor": "",
11 | "requestedPermissions": [
12 | "access_activity",
13 | "access_user_profile",
14 | "access_heart_rate",
15 | "access_location",
16 | "access_internet",
17 | "run_background",
18 | "access_sleep"
19 | ],
20 | "buildTargets": [
21 | "atlas",
22 | "vulcan"
23 | ],
24 | "i18n": {
25 | "en-US": {
26 | "name": "Programmer's Watch"
27 | }
28 | },
29 | "defaultLanguage": "en-US"
30 | }
31 | }
--------------------------------------------------------------------------------
/resources/common.defs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
150 |
151 |
--------------------------------------------------------------------------------
/resources/device.css:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/resources/device~336x336.css:
--------------------------------------------------------------------------------
1 | #unixTime{
2 | x:50%;
3 | y:10+10;
4 | text-anchor: middle;
5 | text-length: 32;
6 | }
7 | #upperBlock{
8 | x:5;
9 | y:30+2;
10 | }
11 | #weather1{
12 | x: 10+20;
13 | }
14 | #weather2{
15 | x: 10+10;
16 | }
17 | #weather3{
18 | x: 10+5;
19 | }
20 | #weather4{
21 | }
22 |
23 | .analogBlock{
24 | x: 100%-100-10;
25 | y: 10+17;
26 | }
27 |
28 | .timeBlock{
29 | y:50%-40-5;
30 | }
31 | #time {
32 | letter-spacing: 6;
33 | }
34 | #seconds{
35 | x: 100%-10-55;
36 | }
37 | .secondsBar{
38 | x: 100%-63-55;
39 | }
40 | #ampmBlock{
41 | opacity:1;
42 | }
43 |
44 | .lowerBlock{
45 | y: 100%-68-20;
46 | }
47 |
48 | #hrImage{
49 | x:10+10;
50 | }
51 | #hrText{
52 | x: 26+10;
53 | }
54 | #btImage{
55 | x:90+10;
56 | }
57 | .circleBig{
58 | cx: 67+10;
59 | }
60 | .circleSmall{
61 | cx: 67+10;
62 | }
63 | #healthArc{
64 | x:51+10;
65 | }
66 | #healthImage{
67 | x:59+10;
68 | }
69 | #health1{
70 | x:100%-10-5;
71 | }
72 | #health2{
73 | x:100%-10-10;
74 | }
75 | #health3{
76 | x:100%-10-15;
77 | }
78 |
79 |
80 | .bottomText{
81 | x:$+1;
82 | y:100%-10-14;
83 | }
84 | #bottomText1{
85 | x:10+25;
86 | }
87 | .bottomTextD{
88 | y: 100%+2-4-10;
89 | x:$+8+2-5;
90 | }
91 | #bottomText15{
92 | x:10+60;
93 | }
94 |
95 |
96 |
--------------------------------------------------------------------------------
/resources/device~348x250.css:
--------------------------------------------------------------------------------
1 | #weatherIcon{
2 | x: 50%+25;
3 | y: -5;
4 | }
5 | #weather1{
6 | y:6;
7 | }
8 |
9 | .timeBlock{
10 | y:50%-40-10;
11 | }
12 | .topLine{
13 | width: 100%-100;
14 | }
15 |
16 | #time {
17 | letter-spacing: 5;
18 | }
19 |
20 | #seconds{
21 | x: 100%-10-70;
22 | }
23 | .secondsBar{
24 | x: 100%-63-70;
25 | }
26 | #ampmBlock{
27 | opacity:1;
28 | }
29 |
30 | #timeZone2{
31 | y:10+10+20;
32 | }
33 |
34 |
35 | #hrImage{
36 | y: 5+8;
37 | }
38 | #hrText{
39 | x: 26;
40 | y: 24+8;
41 | }
42 | #btImage{
43 | x:90-10;
44 | y: 6+8;
45 | }
46 |
47 | .circleBig{
48 | cx: 67-5;
49 | cy: 20+8;
50 | }
51 | .circleSmall{
52 | cx: 67-5;
53 | cy: 20+8;
54 | }
55 | #healthArc{
56 | x:51-5;
57 | y:4+8;
58 | }
59 | #healthImage{
60 | x:59-5;
61 | y:12+8;
62 | }
63 |
64 | #health1{
65 | y:10+17;
66 | }
67 |
68 |
69 | .bottomText{
70 | x:$+6;
71 | }
72 | .bottomTextD{
73 | x:$+8+5;
74 | }
75 |
76 |
--------------------------------------------------------------------------------
/resources/images/activeZoneMinutes.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/activeZoneMinutes.png
--------------------------------------------------------------------------------
/resources/images/battery.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/battery.png
--------------------------------------------------------------------------------
/resources/images/btoff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/btoff.png
--------------------------------------------------------------------------------
/resources/images/bton.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/bton.png
--------------------------------------------------------------------------------
/resources/images/calories.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/calories.png
--------------------------------------------------------------------------------
/resources/images/distance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/distance.png
--------------------------------------------------------------------------------
/resources/images/down-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/down-arrow.png
--------------------------------------------------------------------------------
/resources/images/elevationGain.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/elevationGain.png
--------------------------------------------------------------------------------
/resources/images/hr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/hr.png
--------------------------------------------------------------------------------
/resources/images/steps.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/steps.png
--------------------------------------------------------------------------------
/resources/images/up-arrow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/up-arrow.png
--------------------------------------------------------------------------------
/resources/images/weather/weather_01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/weather/weather_01.png
--------------------------------------------------------------------------------
/resources/images/weather/weather_02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/weather/weather_02.png
--------------------------------------------------------------------------------
/resources/images/weather/weather_03.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/weather/weather_03.png
--------------------------------------------------------------------------------
/resources/images/weather/weather_04.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/weather/weather_04.png
--------------------------------------------------------------------------------
/resources/images/weather/weather_05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/weather/weather_05.png
--------------------------------------------------------------------------------
/resources/images/weather/weather_06.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/weather/weather_06.png
--------------------------------------------------------------------------------
/resources/images/weather/weather_07.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/weather/weather_07.png
--------------------------------------------------------------------------------
/resources/images/weather/weather_08.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/weather/weather_08.png
--------------------------------------------------------------------------------
/resources/images/weather/weather_09.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/weather/weather_09.png
--------------------------------------------------------------------------------
/resources/images/weather/weather_10.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/weather/weather_10.png
--------------------------------------------------------------------------------
/resources/images/weather/weather_11.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/weather/weather_11.png
--------------------------------------------------------------------------------
/resources/images/weather/weather_error.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/resources/images/weather/weather_error.png
--------------------------------------------------------------------------------
/resources/index.gui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/resources/index.view:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/resources/styles.css:
--------------------------------------------------------------------------------
1 | .main {
2 | viewport-fill: #000000;
3 | fill: #3de4ea;
4 | font-family: System-Regular;
5 | font-size:14;
6 | }
7 |
8 | #unixTime{
9 | x:10;
10 | y:10+10;
11 | text-anchor: start;
12 | text-length: 32;
13 | }
14 | #upperBlock{
15 | x:0;
16 | y:30;
17 | width: 100%;
18 | height:100%;
19 | text-anchor: start;
20 | }
21 | #weatherIcon{
22 | x: 50%;
23 | y: 0;
24 | width: 30;
25 | height:30;
26 | }
27 | .upperTexts{
28 | text-anchor: start;
29 | text-length: 64;
30 | x:10;
31 | y: $+0;
32 | }
33 | #weather1{
34 | y:10;
35 | }
36 | #weather2{
37 | fill: white;
38 | }
39 | .analogBlock{
40 | width:90;
41 | height:90;
42 | x: 100%-100;
43 | y: 10;
44 | }
45 | .num{
46 | text-anchor:middle;
47 | text-length: 2;
48 | x:0;
49 | y:0;
50 | }
51 | .num12{
52 | x:50%;
53 | y:9;
54 | }
55 | .num1{
56 | x:50%+20;
57 | y:9+5;
58 | }
59 | .num2{
60 | x:100%-10;
61 | y:50%-16;
62 | }
63 | .num3{
64 | x:100%-5;
65 | y:50%+4;
66 | }
67 | .num4{
68 | x:100%-10;
69 | y:50%+24;
70 | }
71 | .num5{
72 | x:50%+20;
73 | y:100%-6;
74 | }
75 | .num6{
76 | x:50%;
77 | y:100%-1;
78 | }
79 | .num7{
80 | x:50%-20;
81 | y:100%-6;
82 | }
83 | .num8{
84 | x:10;
85 | y:50%+24;
86 | }
87 | .num9{
88 | x:5;
89 | y:50%+4;
90 | }
91 | .num10{
92 | x:10;
93 | y:50%-16;
94 | }
95 | .num11{
96 | x:50%-20;
97 | y:9+5;
98 | }
99 |
100 | .timeBlock{
101 | width: 100%;
102 | height:100;
103 | x:0;
104 | y:50%-40;
105 | }
106 | .up-arrow{
107 | x:10;
108 | y:-17;
109 | width:8;
110 | height:10;
111 | }
112 | .down-arrow{
113 | x:70+5;
114 | y:-17;
115 | width:8;
116 | height:10;
117 | }
118 | .additionalTimes{
119 | y:-7;
120 | text-anchor: start;
121 | text-length: 32;
122 | }
123 | #additionalTimes1{
124 | x:20;
125 | }
126 | #additionalTimes2{
127 | x:80+5;
128 | }
129 | .line{
130 | x:0;
131 | width:100%;
132 | height:1;
133 | }
134 | .topLine{
135 | y: 0;
136 | fill: #3de4ea;
137 | }
138 | .bottomLine{
139 | y: 99;
140 | fill: #3de4ea;
141 | }
142 | .geekNumbers{
143 | text-length: 64;
144 | y: 100+15;
145 | fill:white;
146 | }
147 | #geekNumbers0{
148 | x:5;
149 | text-anchor: middle;
150 | fill: red;
151 | opacity:0;
152 | }
153 | #geekNumbers1{
154 | x:10;
155 | text-anchor: start;
156 | }
157 | #geekNumbers2{
158 | x:50%;
159 | text-anchor: middle;
160 | }
161 |
162 | #geekNumbers3{
163 | x:100%-10;
164 | text-anchor: end;
165 | }
166 | #geekNumbers4{
167 | x:100%-5;
168 | text-anchor: middle;
169 | fill: red;
170 | opacity:0;
171 | }
172 |
173 | #time {
174 | font-size: 70;
175 | font-family: System-Regular;
176 | text-length: 16;
177 | text-anchor: start;
178 | letter-spacing: 10;
179 | x: 15;
180 | y: 60;
181 | fill: #ffffff;
182 | }
183 | #secondsBlock{
184 | opacity:1;
185 | }
186 | #seconds{
187 | font-size: 43;
188 | font-family: System-Regular;
189 | text-length: 8;
190 | text-anchor: end;
191 | letter-spacing: 5;
192 | x: 100%-10;
193 | y: 42;
194 | fill: #ffffff;
195 | }
196 | .secondsBar{
197 | x: 100%-63;
198 | y: 51;
199 | width: 45;
200 | height: 9;
201 | fill: #838383;
202 | }
203 | #secondsBar1{
204 | fill:#bbbbbb;
205 | width:0;
206 | }
207 | #secondsBar2{
208 | fill:#ffffff;
209 | width:0;
210 | }
211 | #ampmBlock{
212 | opacity:0;
213 | }
214 | #ampmBlock text{
215 |
216 | }
217 | #timeZone2{
218 | y:10+10;
219 | text-length: 16;
220 | text-anchor: start;
221 | x: 100%-63;
222 | }
223 | #timeZone{
224 | y:$+0;
225 | text-length: 16;
226 | text-anchor: start;
227 | x: 100%-63;
228 | }
229 | #hourStyle{
230 | y: $+0;
231 | text-length: 16;
232 | text-anchor: start;
233 | x: 100%-63;
234 | }
235 | #ampm{
236 | y: $+0;
237 | text-length: 16;
238 | text-anchor: start;
239 | x: 100%-63;
240 | }
241 | .date{
242 | font-size: 20;
243 | y: 88;
244 | text-length: 32;
245 | }
246 | #date1{
247 | text-anchor: start;
248 | x:10;
249 | }
250 | #date2{
251 | text-anchor: end;
252 | x:100%-10;
253 | }
254 |
255 |
256 |
257 |
258 | .lowerBlock{
259 | x: 0;
260 | y: 100%-68;
261 | }
262 | #hrImage{
263 | width: 32;
264 | height:32;
265 | x:10;
266 | y: 5;
267 | }
268 | #hrText{
269 | text-anchor: middle;
270 | text-length: 4;
271 | fill: black;
272 | x: 26;
273 | y: 24;
274 | font-family: System-Bold;
275 | }
276 | #btImage{
277 | width: 28;
278 | height:28;
279 | x:90;
280 | y: 6;
281 | }
282 |
283 | .circleBig{
284 | cx: 67;
285 | cy: 20;
286 | r: 16;
287 | fill: #0a484d;
288 | }
289 | .circleSmall{
290 | cx: 67;
291 | cy: 20;
292 | r: 11;
293 | fill: black;
294 | }
295 | #healthArc{
296 | x:51;
297 | y:4;
298 | width:32;
299 | height:32;
300 | arc-width: 5;
301 | start-angle: 0;
302 | sweep-angle:90;
303 | }
304 | #healthImage{
305 | x:59;
306 | y:12;
307 | width:16;
308 | height:16;
309 | opacity:0.8;
310 | }
311 | .lowerTexts{
312 | text-length: 64;
313 | text-anchor: end;
314 | x:100%-10;
315 | y: $+0;
316 | }
317 | #health1{
318 | y:10;
319 | }
320 |
321 |
322 |
323 | .bottomText{
324 | font-family: Seville-Condensed;
325 | text-length: 4;
326 | text-anchor: start;
327 | x:$+2;
328 | y:100%-10;
329 | }
330 | #bottomText1{
331 | x:10;
332 | }
333 | .bottomTextD{
334 | y: 100%+2-4;
335 | x:$+8+2;
336 | font-size:30;
337 | }
338 | #bottomText15{
339 | x:10;
340 | }
341 |
342 |
343 |
344 | #buttonLow{
345 | x:0;
346 | y:50%;
347 | pointer-events:visible;
348 | width: 100%;
349 | height:50%;
350 | opacity:0;
351 | }
352 |
--------------------------------------------------------------------------------
/resources/widget.defs:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/resources/widgets.gui:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/screenshots/ionic/Programmer's-Watch-screenshot (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/screenshots/ionic/Programmer's-Watch-screenshot (1).png
--------------------------------------------------------------------------------
/screenshots/ionic/Programmer's-Watch-screenshot (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/screenshots/ionic/Programmer's-Watch-screenshot (2).png
--------------------------------------------------------------------------------
/screenshots/sense/Programmer's-Watch-screenshot (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/screenshots/sense/Programmer's-Watch-screenshot (1).png
--------------------------------------------------------------------------------
/screenshots/sense/Programmer's-Watch-screenshot (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/screenshots/sense/Programmer's-Watch-screenshot (2).png
--------------------------------------------------------------------------------
/screenshots/versa/Programmer's-Watch-screenshot (1).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/screenshots/versa/Programmer's-Watch-screenshot (1).png
--------------------------------------------------------------------------------
/screenshots/versa/Programmer's-Watch-screenshot (2).png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/czandor/programmersWatchFitbit/c4a02c5ee4fdcf59f1f0c25eab99b8eb19860bb1/screenshots/versa/Programmer's-Watch-screenshot (2).png
--------------------------------------------------------------------------------
/settings/const.js:
--------------------------------------------------------------------------------
1 | export * from "../common/const";
2 | import * as C from "../common/const";
3 |
4 | export const DefaultInfoURL="https://czandor.hu/fitbit/prog/info.php";
5 | export const vibrateOptions=[
6 | {name:"None", value:{id:"0"}},
7 | {name:"Bump", value:{id:"1"}},
8 | {name:"Nudge", value:{id:"2"}},
9 | {name:"Nudge Max", value:{id:"3"}},
10 | {name:"Ping", value:{id:"4"}},
11 | {name:"Confirmation", value:{id:"5"}},
12 | {name:"Confirmation Max", value:{id:"6"}},
13 | {name:"Special", value:{id:"7"}},
14 | {name:"Special Max", value:{id:"8"}}
15 | ];
16 | export const unitsSpeedOptions=[
17 | {name:"Default setting", value:{id:""+C.UnitsDefault,info:'From Fitbit Setup',icon:'https://czandor.hu/fitbit/amongus/fitbit.png'}},
18 | {name:"Metric (kmh)", value:{id:""+C.UnitsMetric,info:'Always Metric',icon:'https://czandor.hu/fitbit/amongus/metric.png'}},
19 | {name:"US (mph)", value:{id:""+C.UnitsUS,info:'Always Imperial',icon:'https://czandor.hu/fitbit/amongus/imperial.png'}}
20 | ];
21 |
22 | export const unitsDistanceOptions=[
23 | {name:"Default setting", value:{id:""+C.UnitsDefault,info:'From Fitbit Setup',icon:'https://czandor.hu/fitbit/amongus/fitbit.png'}},
24 | {name:"Metric (meter, km)", value:{id:""+C.UnitsMetric,info:'Always Metric',icon:'https://czandor.hu/fitbit/amongus/metric.png'}},
25 | {name:"US (miles)", value:{id:""+C.UnitsUS,info:'Always Imperial',icon:'https://czandor.hu/fitbit/amongus/imperial.png'}}
26 | ];
27 |
28 | export const unitsTempOptions=[
29 | {name:"Default setting", value:{id:""+C.UnitsDefault,info:'From Fitbit Setup',icon:'https://czandor.hu/fitbit/amongus/fitbit.png'}},
30 | {name:"Metric (C°)", value:{id:""+C.UnitsMetric,info:'Always Metric',icon:'https://czandor.hu/fitbit/amongus/metric.png'}},
31 | {name:"US (F°)", value:{id:""+C.UnitsUS,info:'Always Imperial',icon:'https://czandor.hu/fitbit/amongus/imperial.png'}}
32 | ];
33 |
34 | export const units24hOptions=[
35 | {name:"Default setting", value:{id:""+C.UnitsDefault,info:'From Fitbit Setup',icon:'https://czandor.hu/fitbit/amongus/fitbit.png'}},
36 | {name:"24h", value:{id:""+C.UnitsMetric,info:'Always 24-hour clock',icon:'https://czandor.hu/fitbit/amongus/h24.png'}},
37 | {name:"12h", value:{id:""+C.UnitsUS,info:'Always 12-hour clock',icon:'https://czandor.hu/fitbit/amongus/h12.png'}}
38 | ];
39 |
40 | export const barOptions=[
41 | {name:"Default setting", value:{id:""+C.SETTINGS_SYSTEMDEFAULT,info:'From Fitbit Setup',icon:'https://czandor.hu/fitbit/amongus/fitbit.png'}},
42 | {name:"Battery", value:{id:""+C.HEALTH_battery,info:'Battery',icon:'https://czandor.hu/fitbit/amongus/empty.png'}},
43 | {name:"Steps", value:{id:""+C.HEALTH_steps,info:'Number of steps taken',icon:'https://czandor.hu/fitbit/amongus/steps.png'}},
44 | {name:"Distance", value:{id:""+C.HEALTH_distance,info:'Distance traveled',icon:'https://czandor.hu/fitbit/amongus/distance.png'}},
45 | {name:"Calories", value:{id:""+C.HEALTH_calories,info:'Number of calories burned',icon:'https://czandor.hu/fitbit/amongus/calories.png'}},
46 | {name:"Elevation Gain", value:{id:""+C.HEALTH_elevationGain,info:'Elevation gain',icon:'https://czandor.hu/fitbit/amongus/elevationGain.png'}},
47 | {name:"Active Zone Minutes", value:{id:""+C.HEALTH_activeZoneMinutes,info:'The Zone Minutes spent in each of the heart rate zones that relate to physical activity',icon:'https://czandor.hu/fitbit/amongus/activeZoneMinutes.png'}}
48 | ]
49 | export const weatherPeriodOptions=[
50 | {name:"6 hours", value:{id:"360"}},
51 | {name:"3 hours", value:{id:"180"}},
52 | {name:"2 hours", value:{id:"120"}},
53 | {name:"90 minutes", value:{id:"90"}},
54 | {name:"60 minutes", value:{id:"60"}},
55 | {name:"50 minutes", value:{id:"50"}},
56 | {name:"40 minutes", value:{id:"40"}},
57 | {name:"30 minutes", value:{id:"30"}},
58 | {name:"20 minutes", value:{id:"20"}}/*,
59 | {name:"10 minutes", value:"10"}/*,
60 | {name:"Below 10%", value:"10"},
61 | {name:"Never", value:"0"}*/
62 | ];
63 | export const infoPeriodOptions=[
64 | {name:"6 hours", value:{id:"360"}},
65 | {name:"3 hours", value:{id:"180"}},
66 | {name:"2 hours", value:{id:"120"}},
67 | {name:"90 minutes", value:{id:"90"}},
68 | {name:"60 minutes", value:{id:"60"}},
69 | {name:"50 minutes", value:{id:"50"}},
70 | {name:"40 minutes", value:{id:"40"}},
71 | {name:"30 minutes", value:{id:"30"}},
72 | {name:"20 minutes", value:{id:"20"}},
73 | {name:"15 minutes", value:{id:"15"}}/*,
74 | {name:"Below 10%", value:"10"},
75 | {name:"Never", value:"0"}*/
76 | ];
77 |
78 | export const aryIannaTimeZones = [
79 | 'Europe/Andorra',
80 | 'Asia/Dubai',
81 | 'Asia/Kabul',
82 | 'Europe/Tirane',
83 | 'Asia/Yerevan',
84 | 'Antarctica/Casey',
85 | 'Antarctica/Davis',
86 | 'Antarctica/DumontDUrville', // https://bugs.chromium.org/p/chromium/issues/detail?id=928068
87 | 'Antarctica/Mawson',
88 | 'Antarctica/Palmer',
89 | 'Antarctica/Rothera',
90 | 'Antarctica/Syowa',
91 | 'Antarctica/Troll',
92 | 'Antarctica/Vostok',
93 | 'America/Argentina/Buenos_Aires',
94 | 'America/Argentina/Cordoba',
95 | 'America/Argentina/Salta',
96 | 'America/Argentina/Jujuy',
97 | 'America/Argentina/Tucuman',
98 | 'America/Argentina/Catamarca',
99 | 'America/Argentina/La_Rioja',
100 | 'America/Argentina/San_Juan',
101 | 'America/Argentina/Mendoza',
102 | 'America/Argentina/San_Luis',
103 | 'America/Argentina/Rio_Gallegos',
104 | 'America/Argentina/Ushuaia',
105 | 'Pacific/Pago_Pago',
106 | 'Europe/Vienna',
107 | 'Australia/Lord_Howe',
108 | 'Antarctica/Macquarie',
109 | 'Australia/Hobart',
110 | 'Australia/Currie',
111 | 'Australia/Melbourne',
112 | 'Australia/Sydney',
113 | 'Australia/Broken_Hill',
114 | 'Australia/Brisbane',
115 | 'Australia/Lindeman',
116 | 'Australia/Adelaide',
117 | 'Australia/Darwin',
118 | 'Australia/Perth',
119 | 'Australia/Eucla',
120 | 'Asia/Baku',
121 | 'America/Barbados',
122 | 'Asia/Dhaka',
123 | 'Europe/Brussels',
124 | 'Europe/Sofia',
125 | 'Atlantic/Bermuda',
126 | 'Asia/Brunei',
127 | 'America/La_Paz',
128 | 'America/Noronha',
129 | 'America/Belem',
130 | 'America/Fortaleza',
131 | 'America/Recife',
132 | 'America/Araguaina',
133 | 'America/Maceio',
134 | 'America/Bahia',
135 | 'America/Sao_Paulo',
136 | 'America/Campo_Grande',
137 | 'America/Cuiaba',
138 | 'America/Santarem',
139 | 'America/Porto_Velho',
140 | 'America/Boa_Vista',
141 | 'America/Manaus',
142 | 'America/Eirunepe',
143 | 'America/Rio_Branco',
144 | 'America/Nassau',
145 | 'Asia/Thimphu',
146 | 'Europe/Minsk',
147 | 'America/Belize',
148 | 'America/St_Johns',
149 | 'America/Halifax',
150 | 'America/Glace_Bay',
151 | 'America/Moncton',
152 | 'America/Goose_Bay',
153 | 'America/Blanc-Sablon',
154 | 'America/Toronto',
155 | 'America/Nipigon',
156 | 'America/Thunder_Bay',
157 | 'America/Iqaluit',
158 | 'America/Pangnirtung',
159 | 'America/Atikokan',
160 | 'America/Winnipeg',
161 | 'America/Rainy_River',
162 | 'America/Resolute',
163 | 'America/Rankin_Inlet',
164 | 'America/Regina',
165 | 'America/Swift_Current',
166 | 'America/Edmonton',
167 | 'America/Cambridge_Bay',
168 | 'America/Yellowknife',
169 | 'America/Inuvik',
170 | 'America/Creston',
171 | 'America/Dawson_Creek',
172 | 'America/Fort_Nelson',
173 | 'America/Vancouver',
174 | 'America/Whitehorse',
175 | 'America/Dawson',
176 | 'Indian/Cocos',
177 | 'Europe/Zurich',
178 | 'Africa/Abidjan',
179 | 'Pacific/Rarotonga',
180 | 'America/Santiago',
181 | 'America/Punta_Arenas',
182 | 'Pacific/Easter',
183 | 'Asia/Shanghai',
184 | 'Asia/Urumqi',
185 | 'America/Bogota',
186 | 'America/Costa_Rica',
187 | 'America/Havana',
188 | 'Atlantic/Cape_Verde',
189 | 'America/Curacao',
190 | 'Indian/Christmas',
191 | 'Asia/Nicosia',
192 | 'Asia/Famagusta',
193 | 'Europe/Prague',
194 | 'Europe/Berlin',
195 | 'Europe/Copenhagen',
196 | 'America/Santo_Domingo',
197 | 'Africa/Algiers',
198 | 'America/Guayaquil',
199 | 'Pacific/Galapagos',
200 | 'Europe/Tallinn',
201 | 'Africa/Cairo',
202 | 'Africa/El_Aaiun',
203 | 'Europe/Madrid',
204 | 'Africa/Ceuta',
205 | 'Atlantic/Canary',
206 | 'Europe/Helsinki',
207 | 'Pacific/Fiji',
208 | 'Atlantic/Stanley',
209 | 'Pacific/Chuuk',
210 | 'Pacific/Pohnpei',
211 | 'Pacific/Kosrae',
212 | 'Atlantic/Faroe',
213 | 'Europe/Paris',
214 | 'Europe/London',
215 | 'Asia/Tbilisi',
216 | 'America/Cayenne',
217 | 'Africa/Accra',
218 | 'Europe/Gibraltar',
219 | 'America/Godthab',
220 | 'America/Danmarkshavn',
221 | 'America/Scoresbysund',
222 | 'America/Thule',
223 | 'Europe/Athens',
224 | 'Atlantic/South_Georgia',
225 | 'America/Guatemala',
226 | 'Pacific/Guam',
227 | 'Africa/Bissau',
228 | 'America/Guyana',
229 | 'Asia/Hong_Kong',
230 | 'America/Tegucigalpa',
231 | 'America/Port-au-Prince',
232 | 'Europe/Budapest',
233 | 'Asia/Jakarta',
234 | 'Asia/Pontianak',
235 | 'Asia/Makassar',
236 | 'Asia/Jayapura',
237 | 'Europe/Dublin',
238 | 'Asia/Jerusalem',
239 | 'Asia/Kolkata',
240 | 'Indian/Chagos',
241 | 'Asia/Baghdad',
242 | 'Asia/Tehran',
243 | 'Atlantic/Reykjavik',
244 | 'Europe/Rome',
245 | 'America/Jamaica',
246 | 'Asia/Amman',
247 | 'Asia/Tokyo',
248 | 'Africa/Nairobi',
249 | 'Asia/Bishkek',
250 | 'Pacific/Tarawa',
251 | 'Pacific/Enderbury',
252 | 'Pacific/Kiritimati',
253 | 'Asia/Pyongyang',
254 | 'Asia/Seoul',
255 | 'Asia/Almaty',
256 | 'Asia/Qyzylorda',
257 | 'Asia/Qostanay', // https://bugs.chromium.org/p/chromium/issues/detail?id=928068
258 | 'Asia/Aqtobe',
259 | 'Asia/Aqtau',
260 | 'Asia/Atyrau',
261 | 'Asia/Oral',
262 | 'Asia/Beirut',
263 | 'Asia/Colombo',
264 | 'Africa/Monrovia',
265 | 'Europe/Vilnius',
266 | 'Europe/Luxembourg',
267 | 'Europe/Riga',
268 | 'Africa/Tripoli',
269 | 'Africa/Casablanca',
270 | 'Europe/Monaco',
271 | 'Europe/Chisinau',
272 | 'Pacific/Majuro',
273 | 'Pacific/Kwajalein',
274 | 'Asia/Yangon',
275 | 'Asia/Ulaanbaatar',
276 | 'Asia/Hovd',
277 | 'Asia/Choibalsan',
278 | 'Asia/Macau',
279 | 'America/Martinique',
280 | 'Europe/Malta',
281 | 'Indian/Mauritius',
282 | 'Indian/Maldives',
283 | 'America/Mexico_City',
284 | 'America/Cancun',
285 | 'America/Merida',
286 | 'America/Monterrey',
287 | 'America/Matamoros',
288 | 'America/Mazatlan',
289 | 'America/Chihuahua',
290 | 'America/Ojinaga',
291 | 'America/Hermosillo',
292 | 'America/Tijuana',
293 | 'America/Bahia_Banderas',
294 | 'Asia/Kuala_Lumpur',
295 | 'Asia/Kuching',
296 | 'Africa/Maputo',
297 | 'Africa/Windhoek',
298 | 'Pacific/Noumea',
299 | 'Pacific/Norfolk',
300 | 'Africa/Lagos',
301 | 'America/Managua',
302 | 'Europe/Amsterdam',
303 | 'Europe/Oslo',
304 | 'Asia/Kathmandu',
305 | 'Pacific/Nauru',
306 | 'Pacific/Niue',
307 | 'Pacific/Auckland',
308 | 'Pacific/Chatham',
309 | 'America/Panama',
310 | 'America/Lima',
311 | 'Pacific/Tahiti',
312 | 'Pacific/Marquesas',
313 | 'Pacific/Gambier',
314 | 'Pacific/Port_Moresby',
315 | 'Pacific/Bougainville',
316 | 'Asia/Manila',
317 | 'Asia/Karachi',
318 | 'Europe/Warsaw',
319 | 'America/Miquelon',
320 | 'Pacific/Pitcairn',
321 | 'America/Puerto_Rico',
322 | 'Asia/Gaza',
323 | 'Asia/Hebron',
324 | 'Europe/Lisbon',
325 | 'Atlantic/Madeira',
326 | 'Atlantic/Azores',
327 | 'Pacific/Palau',
328 | 'America/Asuncion',
329 | 'Asia/Qatar',
330 | 'Indian/Reunion',
331 | 'Europe/Bucharest',
332 | 'Europe/Belgrade',
333 | 'Europe/Kaliningrad',
334 | 'Europe/Moscow',
335 | 'Europe/Simferopol',
336 | 'Europe/Kirov',
337 | 'Europe/Astrakhan',
338 | 'Europe/Volgograd',
339 | 'Europe/Saratov',
340 | 'Europe/Ulyanovsk',
341 | 'Europe/Samara',
342 | 'Asia/Yekaterinburg',
343 | 'Asia/Omsk',
344 | 'Asia/Novosibirsk',
345 | 'Asia/Barnaul',
346 | 'Asia/Tomsk',
347 | 'Asia/Novokuznetsk',
348 | 'Asia/Krasnoyarsk',
349 | 'Asia/Irkutsk',
350 | 'Asia/Chita',
351 | 'Asia/Yakutsk',
352 | 'Asia/Khandyga',
353 | 'Asia/Vladivostok',
354 | 'Asia/Ust-Nera',
355 | 'Asia/Magadan',
356 | 'Asia/Sakhalin',
357 | 'Asia/Srednekolymsk',
358 | 'Asia/Kamchatka',
359 | 'Asia/Anadyr',
360 | 'Asia/Riyadh',
361 | 'Pacific/Guadalcanal',
362 | 'Indian/Mahe',
363 | 'Africa/Khartoum',
364 | 'Europe/Stockholm',
365 | 'Asia/Singapore',
366 | 'America/Paramaribo',
367 | 'Africa/Juba',
368 | 'Africa/Sao_Tome',
369 | 'America/El_Salvador',
370 | 'Asia/Damascus',
371 | 'America/Grand_Turk',
372 | 'Africa/Ndjamena',
373 | 'Indian/Kerguelen',
374 | 'Asia/Bangkok',
375 | 'Asia/Dushanbe',
376 | 'Pacific/Fakaofo',
377 | 'Asia/Dili',
378 | 'Asia/Ashgabat',
379 | 'Africa/Tunis',
380 | 'Pacific/Tongatapu',
381 | 'Europe/Istanbul',
382 | 'America/Port_of_Spain',
383 | 'Pacific/Funafuti',
384 | 'Asia/Taipei',
385 | 'Europe/Kiev',
386 | 'Europe/Uzhgorod',
387 | 'Europe/Zaporozhye',
388 | 'Pacific/Wake',
389 | 'America/New_York',
390 | 'America/Detroit',
391 | 'America/Kentucky/Louisville',
392 | 'America/Kentucky/Monticello',
393 | 'America/Indiana/Indianapolis',
394 | 'America/Indiana/Vincennes',
395 | 'America/Indiana/Winamac',
396 | 'America/Indiana/Marengo',
397 | 'America/Indiana/Petersburg',
398 | 'America/Indiana/Vevay',
399 | 'America/Chicago',
400 | 'America/Indiana/Tell_City',
401 | 'America/Indiana/Knox',
402 | 'America/Menominee',
403 | 'America/North_Dakota/Center',
404 | 'America/North_Dakota/New_Salem',
405 | 'America/North_Dakota/Beulah',
406 | 'America/Denver',
407 | 'America/Boise',
408 | 'America/Phoenix',
409 | 'America/Los_Angeles',
410 | 'America/Anchorage',
411 | 'America/Juneau',
412 | 'America/Sitka',
413 | 'America/Metlakatla',
414 | 'America/Yakutat',
415 | 'America/Nome',
416 | 'America/Adak',
417 | 'Pacific/Honolulu',
418 | 'America/Montevideo',
419 | 'Asia/Samarkand',
420 | 'Asia/Tashkent',
421 | 'America/Caracas',
422 | 'Asia/Ho_Chi_Minh',
423 | 'Pacific/Efate',
424 | 'Pacific/Wallis',
425 | 'Pacific/Apia',
426 | 'Africa/Johannesburg'
427 | ];
428 | /*
429 | export const msTimeZones=[
430 | [0, "Dateline Standard Time", "(GMT-12:00) International Date Line West"],
431 | [1, "Samoa Standard Time", "(GMT-11:00) Midway Island, Samoa"],
432 | [2, "Hawaiian Standard Time", "(GMT-10:00) Hawaii"],
433 | [3, "Alaskan Standard Time", "(GMT-09:00) Alaska"],
434 | [4, "Pacific Standard Time", "(GMT-08:00) Pacific Time (US and Canada); Tijuana"],
435 | [10, "Mountain Standard Time", "(GMT-07:00) Mountain Time (US and Canada)"],
436 | [13, "Mexico Standard Time 2", "(GMT-07:00) Chihuahua, La Paz, Mazatlan"],
437 | [15, "U.S. Mountain Standard Time", "(GMT-07:00) Arizona"],
438 | [20, "Central Standard Time", "(GMT-06:00) Central Time (US and Canada"],
439 | [25, "Canada Central Standard Time", "(GMT-06:00) Saskatchewan"],
440 | [30, "Mexico Standard Time", "(GMT-06:00) Guadalajara, Mexico City, Monterrey"],
441 | [33, "Central America Standard Time", "(GMT-06:00) Central America"],
442 | [35, "Eastern Standard Time", "(GMT-05:00) Eastern Time (US and Canada)"],
443 | [40, "U.S. Eastern Standard Time", "(GMT-05:00) Indiana (East)"],
444 | [45, "S.A. Pacific Standard Time", "(GMT-05:00) Bogota, Lima, Quito"],
445 | [50, "Atlantic Standard Time", "(GMT-04:00) Atlantic Time (Canada)"],
446 | [55, "S.A. Western Standard Time", "(GMT-04:00) Caracas, La Paz"],
447 | [56, "Pacific S.A. Standard Time", "(GMT-04:00) Santiago"],
448 | [60, "Newfoundland and Labrador Standard Time", "(GMT-03:30) Newfoundland and Labrador"],
449 | [65, "E. South America Standard Time", "(GMT-03:00) Brasilia"],
450 | [70, "S.A. Eastern Standard Time", "(GMT-03:00) Buenos Aires, Georgetown"],
451 | [73, "Greenland Standard Time", "(GMT-03:00) Greenland"],
452 | [75, "Mid-Atlantic Standard Time", "(GMT-02:00) Mid-Atlantic"],
453 | [80, "Azores Standard Time", "(GMT-01:00) Azores"],
454 | [83, "Cape Verde Standard Time", "(GMT-01:00) Cape Verde Islands"],
455 | [85, "GMT Standard Time", "(GMT) Greenwich Mean Time: Dublin, Edinburgh, Lisbon, London"],
456 | [90, "Greenwich Standard Time", "(GMT) Casablanca, Monrovia"],
457 | [95, "Central Europe Standard Time", "(GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague"],
458 | [100, "Central European Standard Time", "(GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb"],
459 | [105, "Romance Standard Time", "(GMT+01:00) Brussels, Copenhagen, Madrid, Paris"],
460 | [110, "W. Europe Standard Time", "(GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna"],
461 | [113, "W. Central Africa Standard Time", "(GMT+01:00) West Central Africa"],
462 | [115, "E. Europe Standard Time", "(GMT+02:00) Bucharest"],
463 | [120, "Egypt Standard Time", "(GMT+02:00) Cairo"],
464 | [125, "FLE Standard Time", "(GMT+02:00) Helsinki, Kiev, Riga, Sofia, Tallinn, Vilnius"],
465 | [130, "GTB Standard Time", "(GMT+02:00) Athens, Istanbul, Minsk"],
466 | [135, "Israel Standard Time", "(GMT+02:00) Jerusalem"],
467 | [140, "South Africa Standard Time", "(GMT+02:00) Harare, Pretoria"],
468 | [145, "Russian Standard Time", "(GMT+03:00) Moscow, St. Petersburg, Volgograd"],
469 | [150, "Arab Standard Time", "(GMT+03:00) Kuwait, Riyadh"],
470 | [155, "E. Africa Standard Time", "(GMT+03:00) Nairobi"],
471 | [158, "Arabic Standard Time", "(GMT+03:00) Baghdad"],
472 | [160, "Iran Standard Time", "(GMT+03:30) Tehran"],
473 | [165, "Arabian Standard Time", "(GMT+04:00) Abu Dhabi, Muscat"],
474 | [170, "Caucasus Standard Time", "(GMT+04:00) Baku, Tbilisi, Yerevan"],
475 | [175, "Transitional Islamic State of Afghanistan Standard Time", "(GMT+04:30) Kabul"],
476 | [180, "Ekaterinburg Standard Time", "(GMT+05:00) Ekaterinburg"],
477 | [185, "West Asia Standard Time", "(GMT+05:00) Islamabad, Karachi, Tashkent"],
478 | [190, "India Standard Time", "(GMT+05:30) Chennai, Kolkata, Mumbai, New Delhi"],
479 | [193, "Nepal Standard Time", "(GMT+05:45) Kathmandu"],
480 | [195, "Central Asia Standard Time", "(GMT+06:00) Astana, Dhaka"],
481 | [200, "Sri Lanka Standard Time", "(GMT+06:00) Sri Jayawardenepura"],
482 | [201, "N. Central Asia Standard Time", "(GMT+06:00) Almaty, Novosibirsk"],
483 | [203, "Myanmar Standard Time", "(GMT+06:30) Yangon Rangoon"],
484 | [205, "S.E. Asia Standard Time", "(GMT+07:00) Bangkok, Hanoi, Jakarta"],
485 | [207, "North Asia Standard Time", "(GMT+07:00) Krasnoyarsk"],
486 | [210, "China Standard Time", "(GMT+08:00) Beijing, Chongqing, Hong Kong SAR, Urumqi"],
487 | [215, "Singapore Standard Time", "(GMT+08:00) Kuala Lumpur, Singapore"],
488 | [220, "Taipei Standard Time", "(GMT+08:00) Taipei"],
489 | [225, "W. Australia Standard Time", "(GMT+08:00) Perth"],
490 | [227, "North Asia East Standard Time", "(GMT+08:00) Irkutsk, Ulaanbaatar"],
491 | [230, "Korea Standard Time", "(GMT+09:00) Seoul"],
492 | [235, "Tokyo Standard Time", "(GMT+09:00) Osaka, Sapporo, Tokyo"],
493 | [240, "Yakutsk Standard Time", "(GMT+09:00) Yakutsk"],
494 | [245, "A.U.S. Central Standard Time", "(GMT+09:30) Darwin"],
495 | [250, "Cen. Australia Standard Time", "(GMT+09:30) Adelaide"],
496 | [255, "A.U.S. Eastern Standard Time", "(GMT+10:00) Canberra, Melbourne, Sydney"],
497 | [260, "E. Australia Standard Time", "(GMT+10:00) Brisbane"],
498 | [265, "Tasmania Standard Time", "(GMT+10:00) Hobart"],
499 | [270, "Vladivostok Standard Time", "(GMT+10:00) Vladivostok"],
500 | [275, "West Pacific Standard Time", "(GMT+10:00) Guam, Port Moresby"],
501 | [280, "Central Pacific Standard Time", "(GMT+11:00) Magadan, Solomon Islands, New Caledonia"],
502 | [285, "Fiji Islands Standard Time", "(GMT+12:00) Fiji Islands, Kamchatka, Marshall Islands"],
503 | [290, "New Zealand Standard Time", "(GMT+12:00) Auckland, Wellington"],
504 | [300, "Tonga Standard Time", "(GMT+13:00) Nuku'alofa"]
505 | ];
506 | */
507 | export const minimalTimezoneSet = [
508 | { offset: '-11:00', label: '(GMT-11:00) Pago Pago', tzCode: 'Pacific/Pago_Pago' },
509 | { offset: '-10:00', label: '(GMT-10:00) Hawaii Time', tzCode: 'Pacific/Honolulu' },
510 | { offset: '-10:00', label: '(GMT-10:00) Tahiti', tzCode: 'Pacific/Tahiti' },
511 | { offset: '-09:00', label: '(GMT-09:00) Alaska Time', tzCode: 'America/Anchorage' },
512 | { offset: '-08:00', label: '(GMT-08:00) Pacific Time', tzCode: 'America/Los_Angeles' },
513 | { offset: '-07:00', label: '(GMT-07:00) Mountain Time', tzCode: 'America/Denver' },
514 | { offset: '-06:00', label: '(GMT-06:00) Central Time', tzCode: 'America/Chicago' },
515 | { offset: '-05:00', label: '(GMT-05:00) Eastern Time', tzCode: 'America/New_York' },
516 | { offset: '-04:00', label: '(GMT-04:00) Atlantic Time - Halifax', tzCode: 'America/Halifax' },
517 | { offset: '-03:00', label: '(GMT-03:00) Buenos Aires', tzCode: 'America/Argentina/Buenos_Aires' },
518 | { offset: '-02:00', label: '(GMT-02:00) Sao Paulo', tzCode: 'America/Sao_Paulo' },
519 | { offset: '-01:00', label: '(GMT-01:00) Azores', tzCode: 'Atlantic/Azores' },
520 | { offset: '+00:00', label: '(GMT+00:00) London', tzCode: 'Europe/London' },
521 | { offset: '+01:00', label: '(GMT+01:00) Berlin', tzCode: 'Europe/Berlin' },
522 | { offset: '+02:00', label: '(GMT+02:00) Helsinki', tzCode: 'Europe/Helsinki' },
523 | { offset: '+03:00', label: '(GMT+03:00) Istanbul', tzCode: 'Europe/Istanbul' },
524 | { offset: '+04:00', label: '(GMT+04:00) Dubai', tzCode: 'Asia/Dubai' },
525 | { offset: '+04:30', label: '(GMT+04:30) Kabul', tzCode: 'Asia/Kabul' },
526 | { offset: '+05:00', label: '(GMT+05:00) Maldives', tzCode: 'Indian/Maldives' },
527 | { offset: '+05:30', label: '(GMT+05:30) India Standard Time', tzCode: 'Asia/Kolkata' },
528 | { offset: '+05:45', label: '(GMT+05:45) Kathmandu', tzCode: 'Asia/Kathmandu' },
529 | { offset: '+06:00', label: '(GMT+06:00) Dhaka', tzCode: 'Asia/Dhaka' },
530 | { offset: '+06:30', label: '(GMT+06:30) Cocos', tzCode: 'Indian/Cocos' },
531 | { offset: '+07:00', label: '(GMT+07:00) Bangkok', tzCode: 'Asia/Bangkok' },
532 | { offset: '+08:00', label: '(GMT+08:00) Hong Kong', tzCode: 'Asia/Hong_Kong' },
533 | { offset: '+08:30', label: '(GMT+08:30) Pyongyang', tzCode: 'Asia/Pyongyang' },
534 | { offset: '+09:00', label: '(GMT+09:00) Tokyo', tzCode: 'Asia/Tokyo' },
535 | { offset: '+09:30', label: '(GMT+09:30) Central Time - Darwin', tzCode: 'Australia/Darwin' },
536 | { offset: '+10:00', label: '(GMT+10:00) Eastern Time - Brisbane', tzCode: 'Australia/Brisbane' },
537 | { offset: '+10:30', label: '(GMT+10:30) Central Time - Adelaide', tzCode: 'Australia/Adelaide' },
538 | { offset: '+11:00', label: '(GMT+11:00) Eastern Time - Melbourne, Sydney', tzCode: 'Australia/Sydney' },
539 | { offset: '+12:00', label: '(GMT+12:00) Nauru', tzCode: 'Pacific/Nauru' },
540 | { offset: '+13:00', label: '(GMT+13:00) Auckland', tzCode: 'Pacific/Auckland' },
541 | { offset: '+14:00', label: '(GMT+14:00) Kiritimati', tzCode: 'Pacific/Kiritimati' }
542 | ];
543 |
--------------------------------------------------------------------------------
/settings/index.jsx:
--------------------------------------------------------------------------------
1 | import {SETTINGS_LOG as LOG} from "./const"
2 | import * as C from "./const";
3 | import * as sutil from "../common/sutils";
4 |
5 |
6 | let settingsStorage=null;
7 | let started=false;
8 |
9 | function infoResult(result){
10 | if(typeof result !== 'undefined'){
11 | settingsStorage.setItem('infoResult',result);
12 | }
13 | //if(!settingsStorage.getItem('infoResult')) settingsStorage.setItem('infoResult','');
14 | return settingsStorage.getItem('infoResult') || '';
15 | }
16 | function checkInfoUrl(value){
17 | let json=null;
18 | if(typeof value === 'undefined' && settingsStorage.getItem('infoUrlInput') && (json=JSON.parse(settingsStorage.getItem('infoUrlInput'))) && ('name' in json) ) value=json;
19 | //console.log(json.name);
20 | if(!value) return;
21 | let url = value.name;
22 | settingsStorage.setItem('infoUrlTry',url);
23 | settingsStorage.setItem('infoUrlTryTime',""+(new Date().getTime()));
24 | //console.log("info script, url: "+settingsStorage.getItem('infoUrlTry'));
25 | if(url) infoResult('Testing url: '+url);
26 | else infoResult('Downloading...');
27 | }
28 |
29 | function selectUpdate(runNow){
30 | if(!runNow){
31 | setTimeout(function(){
32 | selectUpdate(settingsStorage,true);
33 | }, 500);
34 | return;
35 | }
36 | sutil.selectToSettings(settingsStorage);
37 | }
38 |
39 | function connectionOK(){
40 | if(!settingsStorage.getItem('connectionInfo')) return true;
41 | return JSON.parse(settingsStorage.getItem('connectionInfo'));
42 | /*let today = new Date();
43 | let seconds=Math.floor(today.getTime()/1000);
44 | return JSON.parse(settingsStorage.getItem('connectionInfo') > (seconds - 10));*/
45 | }
46 |
47 | function getWeatherProviderRow(){
48 | let icon="https://www.metaweather.com/static/img/weather/lc.svg";
49 |
50 | if(settingsStorage.getItem('weatherprovider') && settingsStorage.getItem('weatherprovider').includes('owm')) icon="https://openweathermap.org/themes/openweathermap/assets/vendor/owm/img/icons/logo_60x60.png";
51 | return
56 |
57 | }
58 |
59 | function mySettings(props) {
60 | settingsStorage=props.settingsStorage;
61 | if(!started){
62 | started=true;
63 | //let today = new Date();
64 | //let seconds=Math.floor(today.getTime()/1000);
65 | //settingsStorage.setItem('connectionInfo',JSON.stringify(seconds-8));
66 | settingsStorage.setItem('connectionInfo','true');
67 | settingsStorage.setItem("weatherDownloadNow","false");
68 | }
69 | return (
70 |
71 | {!(JSON.parse(settingsStorage.getItem('access_internet') || 'false')) && 🌐 - Internet Permission is required to fully use these settings. You can enable it in the 'Permissions' settings of the clock face}
72 |
73 | { !connectionOK() && ❌ - NO CONNECTION TO WATCH!}>
75 | There is no connection between your watch and your phone.
76 | You need a connection to use the settings.
77 | }
79 | onClick={() => settingsStorage.setItem('connectionInfo','true')}
80 | />
81 | Possible solutions:
82 | - Turn off and on the Bluetooth on your phone.
83 | {!(JSON.parse(settingsStorage.getItem('isSense') || 'false')) && - Launch the 'Today' screen (swipe up) on your watch, and after 3 seconds return to time with the back button.}
84 | {(JSON.parse(settingsStorage.getItem('isSense') || 'false')) && - Launch the 'Today' program (swipe left) on your watch, and after 3 seconds return to time with the back button.}
85 | - Go back to the Fitbit home screen on your phone, make sure your watch syncs properly, and then come back here.
86 | - Ultimately, restart your watch and / or your phone. You can turn off your watch by going to Settings->About->Shut down.
87 | }
88 | { true && 🆓 - SUPPORT MY WORK.}>
90 | This clockface is completely free. Please support my work if you can.
91 | ☕ Buy me a coffee
92 | }
93 |
94 |
95 | {connectionOK() && ☁️ - WEATHER}>
97 |
98 | }
139 |
140 |
141 |
142 | { connectionOK() && 🕒 - TIME SETTINGS}>
144 |
150 | selectUpdate()}
156 | />
157 | {true && selectUpdate()} />}
158 | }
159 | { connectionOK() && ⚙️ - OTHER SETTINGS}>
161 | selectUpdate()}
167 | />
168 | selectUpdate()}
174 | />
175 | selectUpdate()}
181 | />
182 | selectUpdate()}
188 | />
189 | selectUpdate()}
195 | />
196 | }
197 |
198 | {connectionOK() && 🤓 - INFO SCRIPT}>
200 | You can make your own info script, when you have some code skill.
201 | The watchface can send a request to your script with some data in json format with raw POST, and it will display your script result under the second line.
202 | More info & examples
203 |
204 | selectUpdate()} />
205 | {((settingsStorage.getItem('infoScriptEnabled') || 'false') == 'true') &&
206 | checkInfoUrl(value)}
214 | />
215 | }
252 | }
253 |
257 | { JSON.parse(props.settingsStorage.getItem('debugEnabled') || 'false') && {props.settingsStorage.getItem('debugText')}}
258 |
259 | );
260 | }
261 |
262 | registerSettingsPage(mySettings);
263 |
--------------------------------------------------------------------------------