├── 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 | ![Ionic](https://github.com/czandor/programmersWatchFitbit/blob/main/screenshots/ionic/Programmer's-Watch-screenshot%20(1).png?raw=true) 21 | ![Ionic](https://github.com/czandor/programmersWatchFitbit/blob/main/screenshots/ionic/Programmer's-Watch-screenshot%20(2).png?raw=true) 22 | 23 | ![Versa](https://github.com/czandor/programmersWatchFitbit/blob/main/screenshots/versa/Programmer's-Watch-screenshot%20(1).png?raw=true) 24 | ![Versa](https://github.com/czandor/programmersWatchFitbit/blob/main/screenshots/versa/Programmer's-Watch-screenshot%20(2).png?raw=true) 25 | 26 | ![Sense](https://github.com/czandor/programmersWatchFitbit/blob/main/screenshots/sense/Programmer's-Watch-screenshot%20(1).png?raw=true) 27 | ![Sense](https://github.com/czandor/programmersWatchFitbit/blob/main/screenshots/sense/Programmer's-Watch-screenshot%20(2).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 | 4 | 5 | UNIX TIME 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 48 | 1 49 | 2 50 | 3 51 | 4 52 | 5 53 | 6 54 | 7 55 | 8 56 | 9 57 | 10 58 | 11 59 | 12 60 | 61 | 62 | 63 | 64 | 65 | 66 | ; 67 | 68 | ; 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | ! 87 | 88 | 89 | 90 | ! 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 123 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 1 111 | 2 112 | 4 113 | 8 114 | 16 115 | 32 116 | 64 117 | 128 118 | 256 119 | 512 120 | 1024 121 | 2048 122 | 4096 123 | 8192 124 | 125 | . 126 | . 127 | . 128 | . 129 | . 130 | . 131 | . 132 | . 133 | . 134 | . 135 | . 136 | . 137 | . 138 | . 139 | . 140 | . 141 | . 142 | . 143 | 144 | 145 | 146 | 147 | 148 | 149 | 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 |
} 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 | selectUpdate()} 129 | />} 130 | Weather update is adaptive to save resources. Less frequent at night, more frequent in the morning and evening 131 | 132 | {((settingsStorage.getItem('weatherDownloadNow') || 'false') == 'false') &&
} 139 | 140 | 141 | 142 | { connectionOK() &&
🕒 - TIME SETTINGS}> 144 | selectUpdate()} 156 | /> 157 | {true && selectUpdate()} />} 158 |
} 159 | { connectionOK() &&
⚙️ - OTHER SETTINGS}> 161 | selectUpdate()} 174 | /> 175 | selectUpdate()} 188 | /> 189 | selectUpdate()} 225 | /> 226 | 230 | 238 | {infoResult() != '' && Info script result:} 239 | {infoResult() != '' && {infoResult()}} 240 | Currently used URL: 241 | {settingsStorage.getItem('infoUrl')} 242 |
} 252 | } 253 | 257 | { JSON.parse(props.settingsStorage.getItem('debugEnabled') || 'false') && {props.settingsStorage.getItem('debugText')}} 258 |
259 | ); 260 | } 261 | 262 | registerSettingsPage(mySettings); 263 | --------------------------------------------------------------------------------