diff --git a/apps/android/ChangeLog b/apps/android/ChangeLog index d966c1440..4587c0911 100644 --- a/apps/android/ChangeLog +++ b/apps/android/ChangeLog @@ -27,4 +27,6 @@ 0.26: Change handling of GPS status to depend on GPS events instead of connection events 0.27: Issue newline before GB commands (solves issue with console.log and ignored commands) 0.28: Navigation messages no longer launch the Maps view unless they're new -0.29: Support for http request xpath return format \ No newline at end of file +0.29: Support for http request xpath return format +0.30: Send firmware and hardware versions on connection + Allow alarm enable/disable \ No newline at end of file diff --git a/apps/android/boot.js b/apps/android/boot.js index 7988c378f..018ea7561 100644 --- a/apps/android/boot.js +++ b/apps/android/boot.js @@ -86,7 +86,7 @@ var a = require("sched").newDefaultAlarm(); a.id = "gb"+j; a.appid = "gbalarms"; - a.on = true; + a.on = event.d[j].on !== undefined ? event.d[j].on : true; a.t = event.d[j].h * 3600000 + event.d[j].m * 60000; a.dow = ((dow&63)<<1) | (dow>>6); // Gadgetbridge sends DOW in a different format a.last = last; @@ -253,6 +253,7 @@ Bangle.on("charging", sendBattery); NRF.on("connect", () => setTimeout(function() { sendBattery(); + gbSend({t: "ver", fw: process.env.VERSION, hw: process.env.HWVERSION}); GB({t:"force_calendar_sync_start"}); // send a list of our calendar entries to start off the sync process }, 2000)); NRF.on("disconnect", () => { diff --git a/apps/android/metadata.json b/apps/android/metadata.json index 8489570f7..e875c2072 100644 --- a/apps/android/metadata.json +++ b/apps/android/metadata.json @@ -2,7 +2,7 @@ "id": "android", "name": "Android Integration", "shortName": "Android", - "version": "0.29", + "version": "0.30", "description": "Display notifications/music/etc sent from the Gadgetbridge app on Android. This replaces the old 'Gadgetbridge' Bangle.js widget.", "icon": "app.png", "tags": "tool,system,messages,notifications,gadgetbridge", diff --git a/apps/bootgattbat/ChangeLog b/apps/bootgattbat/ChangeLog index 2a37193a3..df07f6ad0 100644 --- a/apps/bootgattbat/ChangeLog +++ b/apps/bootgattbat/ChangeLog @@ -1 +1,2 @@ 0.01: Initial release. +0.02: Handle the case where other apps have set bleAdvert to an array diff --git a/apps/bootgattbat/boot.js b/apps/bootgattbat/boot.js index d67b766b5..34d9f8d93 100644 --- a/apps/bootgattbat/boot.js +++ b/apps/bootgattbat/boot.js @@ -1,6 +1,22 @@ (() => { function advertiseBattery() { - Bangle.bleAdvert[0x180F] = [E.getBattery()]; + if(Array.isArray(Bangle.bleAdvert)){ + // ensure we're in the cycle + var found = false; + for(var ad in Bangle.bleAdvert){ + if(ad[0x180F]){ + ad[0x180F] = [E.getBattery()]; + found = true; + break; + } + } + if(!found) + Bangle.bleAdvert.push({ 0x180F: [E.getBattery()] }); + }else{ + // simple object + Bangle.bleAdvert[0x180F] = [E.getBattery()]; + } + NRF.setAdvertising(Bangle.bleAdvert); } diff --git a/apps/bootgattbat/metadata.json b/apps/bootgattbat/metadata.json index 95a521f47..f67b4507d 100644 --- a/apps/bootgattbat/metadata.json +++ b/apps/bootgattbat/metadata.json @@ -2,7 +2,7 @@ "id": "bootgattbat", "name": "BLE GATT Battery Service", "shortName": "BLE Battery Service", - "version": "0.01", + "version": "0.02", "description": "Adds the GATT Battery Service to advertise the percentage of battery currently remaining over Bluetooth.\n", "icon": "bluetooth.png", "type": "bootloader", diff --git a/apps/btadv/ChangeLog b/apps/btadv/ChangeLog index 1a3bc1757..07e67157c 100644 --- a/apps/btadv/ChangeLog +++ b/apps/btadv/ChangeLog @@ -1 +1,2 @@ 0.01: New app! +0.02: Advertise accelerometer data and sensor location diff --git a/apps/btadv/app.js b/apps/btadv/app.js index 670691fb9..b72a8127a 100644 --- a/apps/btadv/app.js +++ b/apps/btadv/app.js @@ -1,10 +1,16 @@ +var _a; { var __assign = Object.assign; var Layout_1 = require("Layout"); Bangle.loadWidgets(); Bangle.drawWidgets(); var HRM_MIN_CONFIDENCE_1 = 75; - var services_1 = ["0x180d", "0x181a", "0x1819"]; + var services_1 = [ + "0x180d", + "0x181a", + "0x1819", + "E95D0753251D470AA062FA1922DFA9A8", + ]; var acc_1; var bar_1; var gps_1; @@ -21,7 +27,6 @@ mag: false, }; var idToName = { - acc: "Acceleration", bar: "Barometer", gps: "GPS", hrm: "HRM", @@ -69,7 +74,6 @@ { type: "h", c: [ - __assign(__assign({ type: "btn", label: idToName.acc, id: "acc", cb: function () { } }, btnStyle), { col: colour_1.on, btnBorder: colour_1.on }), __assign({ type: "btn", label: "Back", cb: function () { setBtnsShown_1(false); } }, btnStyle), @@ -222,6 +226,13 @@ return [x[0], x[1], y[0], y[1], z[0], z[1]]; }; encodeMag_1.maxLen = 6; + var encodeAcc_1 = function (data) { + var x = toByteArray_1(data.x * 1000, 2, true); + var y = toByteArray_1(data.y * 1000, 2, true); + var z = toByteArray_1(data.z * 1000, 2, true); + return [x[0], x[1], y[0], y[1], z[0], z[1]]; + }; + encodeAcc_1.maxLen = 6; var toByteArray_1 = function (value, numberOfBytes, isSigned) { var byteArray = new Array(numberOfBytes); if (isSigned && (value < 0)) { @@ -251,6 +262,7 @@ case "0x180d": return !!hrm_1; case "0x181a": return !!(bar_1 || mag_1); case "0x1819": return !!(gps_1 && gps_1.lat && gps_1.lon || mag_1); + case "E95D0753251D470AA062FA1922DFA9A8": return !!acc_1; } }; var serviceToAdvert_1 = function (serv, initial) { @@ -264,11 +276,20 @@ readable: true, notify: true, }; + var os = { + maxLen: 1, + readable: true, + notify: true, + }; if (hrm_1) { o.value = encodeHrm_1(hrm_1); + os.value = [2]; hrm_1 = undefined; } - return _a = {}, _a["0x2a37"] = o, _a; + return _a = {}, + _a["0x2a37"] = o, + _a["0x2a38"] = os, + _a; } return {}; case "0x1819": @@ -331,6 +352,21 @@ } return o; } + case "E95D0753251D470AA062FA1922DFA9A8": { + var o = {}; + if (acc_1 || initial) { + o["E95DCA4B251D470AA062FA1922DFA9A8"] = { + maxLen: encodeAcc_1.maxLen, + readable: true, + notify: true, + }; + if (acc_1) { + o["E95DCA4B251D470AA062FA1922DFA9A8"].value = encodeAcc_1(acc_1); + acc_1 = undefined; + } + } + return o; + } } }; var getBleAdvert_1 = function (map, all) { @@ -402,12 +438,23 @@ enableSensors_1(); { var ad = getBleAdvert_1(function (serv) { return serviceToAdvert_1(serv, true); }, true); - var adServices = Object - .keys(ad) - .map(function (k) { return k.replace("0x", ""); }); NRF.setServices(ad, { - advertise: adServices, uart: false, }); + var bangle2 = Bangle; + var cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : []; + for (var id in ad) { + var serv = ad[id]; + var value = void 0; + for (var ch in serv) { + value = serv[ch].value; + break; + } + cycle.push((_a = {}, _a[id] = value || [], _a)); + } + bangle2.bleAdvert = cycle; + NRF.setAdvertising(cycle, { + interval: 100, + }); } } diff --git a/apps/btadv/app.ts b/apps/btadv/app.ts index 5e4930865..1d9501175 100644 --- a/apps/btadv/app.ts +++ b/apps/btadv/app.ts @@ -33,16 +33,27 @@ const enum BleServ { // contains: LocationAndSpeed LocationAndNavigation = "0x1819", - // Acc // none known for this + // org.microbit.service.accelerometer + // contains: Acc + Acc = "E95D0753251D470AA062FA1922DFA9A8", } -const services = [BleServ.HRM, BleServ.EnvSensing, BleServ.LocationAndNavigation]; +const services = [ + BleServ.HRM, + BleServ.EnvSensing, + BleServ.LocationAndNavigation, + BleServ.Acc, +]; const enum BleChar { // org.bluetooth.characteristic.heart_rate_measurement // HRM = "0x2a37", + // org.bluetooth.characteristic.body_sensor_location + // u8 + SensorLocation = "0x2a38", + // org.bluetooth.characteristic.elevation // s24, meters 0.01 Elevation = "0x2a6c", @@ -65,6 +76,11 @@ const enum BleChar { // org.bluetooth.characteristic.magnetic_flux_density_3d // s16: x, y, z, tesla (10^-7) MagneticFlux3D = "0x2aa1", + + // org.microbit.characteristic.accelerometer_data + // s16 x3, -1024 .. 1024 + // docs: https://lancaster-university.github.io/microbit-docs/ble/accelerometer-service/ + Acc = "E95DCA4B251D470AA062FA1922DFA9A8", } type BleCharAdvert = { @@ -84,6 +100,16 @@ type LenFunc = { maxLen: number, } +const enum SensorLocations { + Other = 0, + Chest = 1, + Wrist = 2, + Finger = 3, + Hand = 4, + EarLobe = 5, + Foot = 6, +} + let acc: undefined | AccelData; let bar: undefined | PressureData; let gps: undefined | GPSFix; @@ -104,8 +130,7 @@ const settings: BtAdvMap = { mag: false, }; -const idToName: BtAdvMap = { - acc: "Acceleration", +const idToName: BtAdvMap = { bar: "Barometer", gps: "GPS", hrm: "HRM", @@ -197,15 +222,6 @@ const btnLayout = new Layout( { type: "h", c: [ - { - type: "btn", - label: idToName.acc, - id: "acc", - cb: () => {}, - ...btnStyle, - col: colour.on, - btnBorder: colour.on, - }, { type: "btn", label: "Back", @@ -464,6 +480,15 @@ const encodeMag: LenFunc = (data: CompassData) => { }; encodeMag.maxLen = 6; +const encodeAcc: LenFunc = (data: AccelData) => { + const x = toByteArray(data.x * 1000, 2, true); + const y = toByteArray(data.y * 1000, 2, true); + const z = toByteArray(data.z * 1000, 2, true); + + return [ x[0]!, x[1]!, y[0]!, y[1]!, z[0]!, z[1]! ]; +}; +encodeAcc.maxLen = 6; + const toByteArray = (value: number, numberOfBytes: number, isSigned: boolean) => { const byteArray: Array = new Array(numberOfBytes); @@ -503,6 +528,7 @@ const haveServiceData = (serv: BleServ): boolean => { case BleServ.HRM: return !!hrm; case BleServ.EnvSensing: return !!(bar || mag); case BleServ.LocationAndNavigation: return !!(gps && gps.lat && gps.lon || mag); + case BleServ.Acc: return !!acc; } }; @@ -515,12 +541,22 @@ const serviceToAdvert = (serv: BleServ, initial = false): BleServAdvert => { readable: true, notify: true, }; + const os: BleCharAdvert = { + maxLen: 1, + readable: true, + notify: true, + }; + if (hrm) { o.value = encodeHrm(hrm); + os.value = [SensorLocations.Wrist]; hrm = undefined; } - return { [BleChar.HRM]: o }; + return { + [BleChar.HRM]: o, + [BleChar.SensorLocation]: os, + }; } return {}; @@ -591,6 +627,25 @@ const serviceToAdvert = (serv: BleServ, initial = false): BleServAdvert => { return o; } + + case BleServ.Acc: { + const o: BleServAdvert = {}; + + if (acc || initial) { + o[BleChar.Acc] = { + maxLen: encodeAcc.maxLen, + readable: true, + notify: true, + }; + + if (acc) { + o[BleChar.Acc]!.value = encodeAcc(acc); + acc = undefined; + } + } + + return o; + } } }; @@ -702,16 +757,39 @@ enableSensors(); // must have fixed services from the start: const ad = getBleAdvert(serv => serviceToAdvert(serv, true), /*all*/true); - const adServices = Object - .keys(ad) - .map((k: string) => k.replace("0x", "")); - NRF.setServices( ad, { - advertise: adServices, uart: false, }, ); + + type BleAdvert = { [key: string]: number[] }; + const bangle2 = Bangle as { + bleAdvert?: BleAdvert | BleAdvert[]; + }; + const cycle = Array.isArray(bangle2.bleAdvert) ? bangle2.bleAdvert : []; + + for(const id in ad){ + const serv = ad[id as BleServ]; + let value; + + // pick the first characteristic to advertise + for(const ch in serv){ + value = serv[ch as BleChar]!.value; + break; + } + + cycle.push({ [id]: value || [] }); + } + + bangle2.bleAdvert = cycle; + + NRF.setAdvertising( + cycle, + { + interval: 100, + } + ); } } diff --git a/apps/btadv/metadata.json b/apps/btadv/metadata.json index 7028b2a95..efe024a2f 100644 --- a/apps/btadv/metadata.json +++ b/apps/btadv/metadata.json @@ -2,7 +2,7 @@ "id": "btadv", "name": "btadv", "shortName": "btadv", - "version": "0.01", + "version": "0.02", "description": "Advertise & export live heart rate, accel, pressure, GPS & mag data over bluetooth", "icon": "icon.png", "tags": "health,tool,sensors,bluetooth", diff --git a/apps/bthometemp/ChangeLog b/apps/bthometemp/ChangeLog index 5560f00bc..480780ec5 100644 --- a/apps/bthometemp/ChangeLog +++ b/apps/bthometemp/ChangeLog @@ -1 +1,2 @@ 0.01: New App! +0.02: Handle the case where other apps have set bleAdvert to an array diff --git a/apps/bthometemp/app.js b/apps/bthometemp/app.js index 7b55777d1..cf74c7937 100644 --- a/apps/bthometemp/app.js +++ b/apps/bthometemp/app.js @@ -23,7 +23,7 @@ function onTemperature(p) { var temp100 = Math.round(avrTemp*100); var pressure100 = Math.round(avrPressure*100); - Bangle.bleAdvert[0xFCD2] = [ 0x40, /* BTHome Device Information + var advert = [ 0x40, /* BTHome Device Information bit 0: "Encryption flag" bit 1-4: "Reserved for future use" bit 5-7: "BTHome Version" */ @@ -37,6 +37,21 @@ function onTemperature(p) { 0x04, // Pressure, 16 bit pressure100&255,(pressure100>>8)&255,pressure100>>16 ]; + + if(Array.isArray(Bangle.bleAdvert)){ + var found = false; + for(var ad in Bangle.bleAdvert){ + if(ad[0xFCD2]){ + ad[0xFCD2] = advert; + found = true; + break; + } + } + if(!found) + Bangle.bleAdvert.push({ 0xFCD2: advert }); + }else{ + Bangle.bleAdvert[0xFCD2] = advert; + } NRF.setAdvertising(Bangle.bleAdvert); } diff --git a/apps/bthometemp/metadata.json b/apps/bthometemp/metadata.json index 4bfd08c31..8ffb22c83 100644 --- a/apps/bthometemp/metadata.json +++ b/apps/bthometemp/metadata.json @@ -1,7 +1,7 @@ { "id": "bthometemp", "name": "BTHome Temperature and Pressure", "shortName":"BTHome T", - "version":"0.01", + "version":"0.02", "description": "Displays temperature and pressure, and advertises them over bluetooth using BTHome.io standard", "icon": "app.png", "tags": "bthome,bluetooth,temperature", diff --git a/apps/calendar/ChangeLog b/apps/calendar/ChangeLog index c7902e263..12776867f 100644 --- a/apps/calendar/ChangeLog +++ b/apps/calendar/ChangeLog @@ -14,3 +14,4 @@ 0.13: Switch to swipe left/right for month and up/down for year selection Display events for current month on touch 0.14: Add support for holidays +0.15: Edit holidays on device in settings diff --git a/apps/calendar/calendar.js b/apps/calendar/calendar.js index d7c43eb1f..0ae852d83 100644 --- a/apps/calendar/calendar.js +++ b/apps/calendar/calendar.js @@ -75,11 +75,32 @@ function getDowLbls(locale) { } function sameDay(d1, d2) { + "jit"; return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate(); } +function drawEvent(ev, curDay, x1, y1, x2, y2) { + "ram"; + switch(ev.type) { + case "e": // alarm/event + const hour = 0|ev.date.getHours() + 0|ev.date.getMinutes()/60.0; + const slice = hour/24*(eventsPerDay-1); // slice 0 for 0:00 up to eventsPerDay for 23:59 + const height = (y2-2) - (y1+2); // height of a cell + const sliceHeight = height/eventsPerDay; + const ystart = (y1+2) + slice*sliceHeight; + g.setColor(bgEvent).fillRect(x1+1, ystart, x2-2, ystart+sliceHeight); + break; + case "h": // holiday + g.setColor(bgColorWeekend).fillRect(x1+1, y1+1, x2-1, y2-1); + break; + case "o": // other + g.setColor(bgOtherEvent).fillRect(x1+1, y1+1, x2-1, y2-1); + break; + } +} + function drawCalendar(date) { g.setBgColor(bgColor); g.clearRect(0, 0, maxX, maxY); @@ -118,7 +139,6 @@ function drawCalendar(date) { true ); - g.setFont("6x8", fontSize); let dowLbls = getDowLbls(require('locale').name); dowLbls.forEach((lbl, i) => { g.drawString(lbl, i * colW + colW / 2, headerH + rowH / 2); @@ -172,6 +192,7 @@ function drawCalendar(date) { const eventsThisMonth = events.filter(ev => ev.date > weekBeforeMonth && ev.date < week2AfterMonth); eventsThisMonth.sort((a,b) => a.date - b.date); let i = 0; + g.setFont("8x12", fontSize); for (y = 0; y < rowN - 1; y++) { for (x = 0; x < colN; x++) { i++; @@ -188,22 +209,7 @@ function drawCalendar(date) { // Display events for this day eventsThisMonth.forEach((ev, idx) => { if (sameDay(ev.date, curDay)) { - switch(ev.type) { - case "e": // alarm/event - const hour = ev.date.getHours() + ev.date.getMinutes()/60.0; - const slice = hour/24*(eventsPerDay-1); // slice 0 for 0:00 up to eventsPerDay for 23:59 - const height = (y2-2) - (y1+2); // height of a cell - const sliceHeight = height/eventsPerDay; - const ystart = (y1+2) + slice*sliceHeight; - g.setColor(bgEvent).fillRect(x1+1, ystart, x2-2, ystart+sliceHeight); - break; - case "h": // holiday - g.setColor(bgColorWeekend).fillRect(x1+1, y1+1, x2-1, y2-1); - break; - case "o": // other - g.setColor(bgOtherEvent).fillRect(x1+1, y1+1, x2-1, y2-1); - break; - } + drawEvent(ev, curDay, x1, y1, x2, y2); eventsThisMonth.splice(idx, 1); // this event is no longer needed } @@ -221,17 +227,15 @@ function drawCalendar(date) { ); } - require("Font8x12").add(Graphics); - g.setFont("8x12", fontSize); g.setColor(day < 50 ? fgOtherMonth : fgSameMonth); g.drawString( (day > 50 ? day - 50 : day).toString(), x * colW + colW / 2, headerH + rowH + y * rowH + rowH / 2 ); - } - } -} + } // end for (x = 0; x < colN; x++) + } // end for (y = 0; y < rowN - 1; y++) +} // end function drawCalendar function setUI() { Bangle.setUI({ @@ -279,6 +283,7 @@ function setUI() { }); } +require("Font8x12").add(Graphics); drawCalendar(date); setUI(); // No space for widgets! diff --git a/apps/calendar/metadata.json b/apps/calendar/metadata.json index 44a68d879..bd35c8879 100644 --- a/apps/calendar/metadata.json +++ b/apps/calendar/metadata.json @@ -1,7 +1,7 @@ { "id": "calendar", "name": "Calendar", - "version": "0.14", + "version": "0.15", "description": "Simple calendar", "icon": "calendar.png", "screenshots": [{"url":"screenshot_calendar.png"}], diff --git a/apps/calendar/settings.js b/apps/calendar/settings.js index 54ed50a64..40eca9f68 100644 --- a/apps/calendar/settings.js +++ b/apps/calendar/settings.js @@ -1,5 +1,6 @@ (function (back) { var FILE = "calendar.json"; + const HOLIDAY_FILE = "calendar.days.json"; var settings = require('Storage').readJSON(FILE, true) || {}; if (settings.ndColors === undefined) if (process.env.HWVERSION == 2) { @@ -7,21 +8,147 @@ } else { settings.ndColors = false; } + const holidays = require("Storage").readJSON(HOLIDAY_FILE,1).sort((a,b) => new Date(a.date) - new Date(b.date)) || []; function writeSettings() { require('Storage').writeJSON(FILE, settings); } - E.showMenu({ - "": { "title": "Calendar" }, - "< Back": () => back(), - 'B2 Colors': { - value: settings.ndColors, - onchange: v => { - settings.ndColors = v; - writeSettings(); - } - }, - }); -}) + function writeHolidays() { + holidays.sort((a,b) => new Date(a.date) - new Date(b.date)); + require('Storage').writeJSON(HOLIDAY_FILE, holidays); + } + function formatDate(d) { + return d.getFullYear() + "-" + (d.getMonth() + 1).toString().padStart(2, '0') + "-" + d.getDate().toString().padStart(2, '0'); + } + + const editdate = (i) => { + const holiday = holidays[i]; + const date = new Date(holiday.date); + const dateStr = require("locale").date(date, 1); + const menu = { + "": { "title" : holiday.name}, + "< Back": () => { + writeHolidays(); + editdates(); + }, + /*LANG*/"Day": { + value: date ? date.getDate() : null, + min: 1, + max: 31, + wrap: true, + onchange: v => { + date.setDate(v); + holiday.date = formatDate(date); + } + }, + /*LANG*/"Month": { + value: date ? date.getMonth() + 1 : null, + format: v => require("date_utils").month(v), + onchange: v => { + date.setMonth((v+11)%12); + holiday.date = formatDate(date); + } + }, + /*LANG*/"Year": { + value: date ? date.getFullYear() : null, + min: 1900, + max: 2100, + onchange: v => { + date.setFullYear(v); + holiday.date = formatDate(date); + } + }, + /*LANG*/"Name": () => { + require("textinput").input({text:holiday.name}).then(result => { + holiday.name = result; + editdate(i); + }); + }, + /*LANG*/"Type": { + value: function() { + switch(holiday.type) { + case 'h': return 0; + case 'o': return 1; + } + return 0; + }(), + min: 0, max: 1, + format: v => [/*LANG*/"Holiday", /*LANG*/"Other"][v], + onchange: v => { + holiday.type = function() { + switch(v) { + case 0: return 'h'; + case 1: return 'o'; + } + }(); + } + }, + /*LANG*/"Repeat": { + value: !!holiday.repeat, + format: v => v ? /*LANG*/"Yearly" : /*LANG*/"Never", + onchange: v => { + holiday.repeat = v ? 'y' : undefined; + } + }, + /*LANG*/"Delete": () => E.showPrompt(/*LANG*/"Delete" + " " + menu[""].title + "?").then(function(v) { + if (v) { + holidays.splice(i, 1); + writeHolidays(); + editdates(); + } else { + editday(i); + } + } + ), + }; + try { + require("textinput"); + } catch(e) { + // textinput not installed + delete menu[/*LANG*/"Name"]; + } + + E.showMenu(menu); + }; + + const editdates = () => { + const menu = holidays.map((holiday,i) => { + const date = new Date(holiday.date); + const dateStr = require("locale").date(date, 1); + return { + title: dateStr + ' ' + holiday.name, + onchange: v => setTimeout(() => editdate(i), 10), + }; + }); + + menu[''] = { 'title': 'Holidays' }; + menu['< Back'] = ()=>settingsmenu(); + E.showMenu(menu); + }; + + const settingsmenu = () => { + E.showMenu({ + "": { "title": "Calendar" }, + "< Back": () => back(), + 'B2 Colors': { + value: settings.ndColors, + onchange: v => { + settings.ndColors = v; + writeSettings(); + } + }, + /*LANG*/"Edit Holidays": () => editdates(), + /*LANG*/"Add Holiday": () => { + holidays.push({ + "date":formatDate(new Date()), + "name":/*LANG*/"New", + "type":'h', + }); + editdate(holidays.length-1); + }, + }); + }; + settingsmenu(); +}) diff --git a/apps/gpstrek/ChangeLog b/apps/gpstrek/ChangeLog index 0d7c06ab4..921000e82 100644 --- a/apps/gpstrek/ChangeLog +++ b/apps/gpstrek/ChangeLog @@ -15,4 +15,7 @@ Save state if route or waypoint has been chosen 0.09: Workaround a minifier issue allowing to install gpstrek with minification enabled 0.10: Adds map view of loaded route - Automatically search for new waypoint if moving away from current target \ No newline at end of file + Automatically search for new waypoint if moving away from current target +0.11: Adds configuration + Draws direction arrows on route + Turn of compass when GPS fix is available \ No newline at end of file diff --git a/apps/gpstrek/app.js b/apps/gpstrek/app.js index 95db86aaf..eb21498c0 100644 --- a/apps/gpstrek/app.js +++ b/apps/gpstrek/app.js @@ -7,25 +7,12 @@ const MODE_SLICES = 2; const STORAGE = require("Storage"); const BAT_FULL = require("Storage").readJSON("setting.json").batFullVoltage || 0.3144; -const SETTINGS = { - mapCompass: true, - mapScale:0.2, //initial value - mapRefresh:1000, //minimum time in ms between refreshs of the map - mapChunkSize: 5, //render this many waypoints at a time - overviewScroll: 30, //scroll this amount on swipe in pixels - overviewScale: 0.02, //initial value - refresh:500, //general refresh interval in ms - refreshLocked:3000, //general refresh interval when Bangle is locked - cacheMinFreeMem:2000, - cacheMaxEntries:0, - minCourseChange: 5, //course change needed in degrees before redrawing the map - minPosChange: 5, //position change needed in pixels before redrawing the map - waypointChangeDist: 50, //distance in m to next waypoint before advancing automatically - queueWaitingTime: 5, // waiting time during processing of task queue items when running with timeouts - autosearch: true, - maxDistForAutosearch: 300, - autosearchLimit: 3 -}; + + +const SETTINGS = Object.assign( + require('Storage').readJSON("gpstrek.default.json", true) || {}, + require('Storage').readJSON("gpstrek.json", true) || {} +); let init = function(){ global.screen = 1; @@ -38,7 +25,6 @@ let init = function(){ Bangle.loadWidgets(); WIDGETS.gpstrek.start(false); - if (!WIDGETS.gpstrek.getState().numberOfSlices) WIDGETS.gpstrek.getState().numberOfSlices = 2; if (!WIDGETS.gpstrek.getState().mode) WIDGETS.gpstrek.getState().mode = MODE_MENU; }; @@ -184,11 +170,6 @@ let getDoubleLineSlice = function(title1,title2,provider1,provider2){ }; }; -const dot = Graphics.createImage(` -XX -XX -`); - const arrow = Graphics.createImage(` X XXX @@ -198,6 +179,14 @@ const arrow = Graphics.createImage(` XXX XXX `); +const thinarrow = Graphics.createImage(` + X + XXX + XX XX + XX XX +XX XX +`); + const cross = Graphics.createImage(` XX XX XX XX @@ -459,7 +448,7 @@ let getMapSlice = function(){ if (!isMapOverview){ drawCurrentPos(); } - if (!isMapOverview && renderInTimeouts){ + if (SETTINGS.mapCompass && !isMapOverview && renderInTimeouts){ drawMapCompass(); } if (renderInTimeouts) drawInterface(); @@ -472,7 +461,8 @@ let getMapSlice = function(){ i:startingIndex, poly:[], maxWaypoints: maxWaypoints, - breakLoop: false + breakLoop: false, + dist: 0 }; let drawChunk = function(data){ @@ -483,6 +473,7 @@ let getMapSlice = function(){ let last; let toDraw; let named = []; + let dir = []; for (let j = 0; j < SETTINGS.mapChunkSize; j++){ data.i = data.i + (reverse?-1:1); let p = get(route, data.i); @@ -497,7 +488,17 @@ let getMapSlice = function(){ break; } toDraw = Bangle.project(p); - if (p.name) named.push({i:data.poly.length,n:p.name}); + + if (SETTINGS.mapDirection){ + let lastWp = get(route, data.i - (reverse?-1:1)); + if (lastWp) data.dist+=distance(lastWp,p); + if (!isMapOverview && data.dist > 20/mapScale){ + dir.push({i:data.poly.length,b:require("graphics_utils").degreesToRadians(bearing(lastWp,p)-(reverse?0:180))}); + data.dist=0; + } + } + if (p.name) + named.push({i:data.poly.length,n:p.name}); data.poly.push(startingPoint.x-toDraw.x); data.poly.push((startingPoint.y-toDraw.y)*-1); } @@ -518,7 +519,11 @@ let getMapSlice = function(){ } graphics.drawString(c.n, data.poly[c.i] + 10, data.poly[c.i+1]); } - + + for (let c of dir){ + graphics.drawImage(thinarrow, data.poly[c.i], data.poly[c.i+1], {rotate: c.b}); + } + if (finish) graphics.drawImage(finishIcon, data.poly[data.poly.length - 2] -5, data.poly[data.poly.length - 1] - 4); else if (last) { @@ -1254,11 +1259,6 @@ let showMenu = function(){ "Background" : showBackgroundMenu, "Calibration": showCalibrationMenu, "Reset" : ()=>{ E.showPrompt("Do Reset?").then((v)=>{ if (v) {WIDGETS.gpstrek.resetState(); removeMenu();} else {E.showMenu(mainmenu);}}).catch(()=>{E.showMenu(mainmenu);});}, - "Info rows" : { - value : WIDGETS.gpstrek.getState().numberOfSlices, - min:1,max:6,step:1, - onchange : v => { WIDGETS.gpstrek.getState().numberOfSlices = v; } - }, }; E.showMenu(mainmenu); @@ -1374,7 +1374,7 @@ const finishData = { }; let getSliceHeight = function(number){ - return Math.floor(Bangle.appRect.h/WIDGETS.gpstrek.getState().numberOfSlices); + return Math.floor(Bangle.appRect.h/SETTINGS.numberOfSlices); }; let compassSlice = getCompassSlice(); @@ -1455,7 +1455,6 @@ let updateRouting = function() { lastSearch = Date.now(); autosearchCounter++; } - let counter = 0; while (hasNext(s.route) && distance(s.currentPos,get(s.route)) < SETTINGS.waypointChangeDist) { next(s.route); minimumDistance = Number.MAX_VALUE; @@ -1479,7 +1478,7 @@ let updateSlices = function(){ slices.push(healthSlice); slices.push(systemSlice); slices.push(system2Slice); - maxSlicePages = Math.ceil(slices.length/s.numberOfSlices); + maxSlicePages = Math.ceil(slices.length/SETTINGS.numberOfSlices); }; let page_slices = 0; @@ -1515,9 +1514,9 @@ let drawSlices = function(){ if (force){ clear(); } - let firstSlice = page_slices*s.numberOfSlices; + let firstSlice = page_slices*SETTINGS.numberOfSlices; let sliceHeight = getSliceHeight(); - let slicesToDraw = slices.slice(firstSlice,firstSlice + s.numberOfSlices); + let slicesToDraw = slices.slice(firstSlice,firstSlice + SETTINGS.numberOfSlices); for (let slice of slicesToDraw) { g.reset(); if (!slice.refresh || slice.refresh() || force) diff --git a/apps/gpstrek/default.json b/apps/gpstrek/default.json new file mode 100644 index 000000000..aa8d5ecb1 --- /dev/null +++ b/apps/gpstrek/default.json @@ -0,0 +1,21 @@ +{ + "mapCompass": true, + "mapScale":0.5, + "mapRefresh":1000, + "mapChunkSize": 15, + "mapDirection": true, + "overviewScroll": 30, + "overviewScale": 0.02, + "refresh":500, + "refreshLocked":3000, + "cacheMinFreeMem":2000, + "cacheMaxEntries":0, + "minCourseChange": 5, + "minPosChange": 5, + "waypointChangeDist": 50, + "queueWaitingTime": 5, + "autosearch": true, + "maxDistForAutosearch": 300, + "autosearchLimit": 3, + "numberOfSlices": 3 +} diff --git a/apps/gpstrek/metadata.json b/apps/gpstrek/metadata.json index 0ec3a8bfe..ec953e6e7 100644 --- a/apps/gpstrek/metadata.json +++ b/apps/gpstrek/metadata.json @@ -1,7 +1,7 @@ { "id": "gpstrek", "name": "GPS Trekking", - "version": "0.10", + "version": "0.11", "description": "Helper for tracking the status/progress during hiking. Do NOT depend on this for navigation!", "icon": "icon.png", "screenshots": [{"url":"screenInit.png"},{"url":"screenMenu.png"},{"url":"screenMap.png"},{"url":"screenLost.png"},{"url":"screenOverview.png"},{"url":"screenOverviewScroll.png"},{"url":"screenSlices.png"},{"url":"screenSlices2.png"},{"url":"screenSlices3.png"}], @@ -12,8 +12,13 @@ "interface" : "interface.html", "storage": [ {"name":"gpstrek.app.js","url":"app.js"}, + {"name":"gpstrek.settings.js","url":"settings.js"}, + {"name":"gpstrek.default.json","url":"default.json"}, {"name":"gpstrek.wid.js","url":"widget.js"}, {"name":"gpstrek.img","url":"app-icon.js","evaluate":true} ], - "data": [{"name":"gpstrek.state.json"}] + "data": [ + {"name":"gpstrek.state.json"}, + {"name":"gpstrek.json"} + ] } diff --git a/apps/gpstrek/settings.js b/apps/gpstrek/settings.js new file mode 100644 index 000000000..1510bcba4 --- /dev/null +++ b/apps/gpstrek/settings.js @@ -0,0 +1,162 @@ +(function(back) { + const FILE="gpstrek.json"; + let settings; + + function writeSettings(key, value) { + var s = require('Storage').readJSON(FILE, true) || {}; + s[key] = value; + require('Storage').writeJSON(FILE, s); + readSettings(); + } + + function readSettings(){ + settings = Object.assign( + require('Storage').readJSON("gpstrek.default.json", true) || {}, + require('Storage').readJSON(FILE, true) || {} + ); + } + + + function showMapMenu(){ + var menu = { + '': { 'title': 'Map', back: showMainMenu }, + 'Show compass on map': { + value: !!settings.mapCompass, + onchange: v => { + writeSettings("mapCompass",v); + }, + }, + 'Initial map scale': { + value: settings.mapScale, + min: 0.01,max: 2, step:0.01, + onchange: v => { + writeSettings("mapScale",v); + }, + }, + 'Rendered waypoints': { + value: settings.mapChunkSize, + min: 5,max: 60, step:5, + onchange: v => { + writeSettings("mapChunkSize",v); + } + }, + 'Overview scroll': { + value: settings.overviewScroll, + min: 10,max: 100, step:10, + format: v => v + "px", + onchange: v => { + writeSettings("overviewScroll",v); + } + }, + 'Initial overview scale': { + value: settings.overviewScale, + min: 0.005,max: 0.1, step:0.005, + onchange: v => { + writeSettings("overviewScale",v); + } + }, + 'Show direction': { + value: !!settings.mapDirection, + onchange: v => { + writeSettings("mapDirection",v); + } + } + }; + E.showMenu(menu); + } + + function showRoutingMenu(){ + var menu = { + '': { 'title': 'Routing', back: showMainMenu }, + 'Auto search closest waypoint': { + value: !!settings.autosearch, + onchange: v => { + writeSettings("autosearch",v); + }, + }, + 'Auto search limit': { + value: settings.autosearchLimit, + onchange: v => { + writeSettings("autosearchLimit",v); + }, + }, + 'Waypoint change distance': { + value: settings.waypointChangeDist, + format: v => v + "m", + min: 5,max: 200, step:5, + onchange: v => { + writeSettings("waypointChangeDist",v); + }, + } + }; + E.showMenu(menu); + } + + function showRefreshMenu(){ + var menu = { + '': { 'title': 'Refresh', back: showMainMenu }, + 'Unlocked refresh': { + value: settings.refresh, + format: v => v + "ms", + min: 250,max: 5000, step:250, + onchange: v => { + writeSettings("refresh",v); + } + }, + 'Locked refresh': { + value: settings.refreshLocked, + min: 1000,max: 60000, step:1000, + format: v => v + "ms", + onchange: v => { + writeSettings("refreshLocked",v); + } + }, + 'Minimum refresh': { + value: settings.mapRefresh, + format: v => v + "ms", + min: 250,max: 5000, step:250, + onchange: v => { + writeSettings("mapRefresh",v); + } + }, + 'Minimum course change': { + value: settings.minCourseChange, + min: 0,max: 180, step:1, + format: v => v + "°", + onchange: v => { + writeSettings("minCourseChange",v); + } + }, + 'Minimum position change': { + value: settings.minPosChange, + min: 0,max: 50, step:1, + format: v => v + "px", + onchange: v => { + writeSettings("minPosChange",v); + } + } + }; + E.showMenu(menu); + } + + + function showMainMenu(){ + var mainmenu = { + '': { 'title': 'GPS Trekking', back: back }, + 'Map': showMapMenu, + 'Routing': showRoutingMenu, + 'Refresh': showRefreshMenu, + "Info rows" : { + value : settings.numberOfSlices, + min:1,max:6,step:1, + onchange : v => { + writeSettings("numberOfSlices",v); + } + }, + }; + E.showMenu(mainmenu); + } + + readSettings(); + showMainMenu(); +}) \ No newline at end of file diff --git a/apps/gpstrek/widget.js b/apps/gpstrek/widget.js index 6887486bc..44acdc722 100644 --- a/apps/gpstrek/widget.js +++ b/apps/gpstrek/widget.js @@ -45,7 +45,15 @@ function onPulse(e){ } function onGPS(fix) { - if(fix.fix) state.currentPos = fix; + if(fix.fix) { + state.currentPos = fix; + if (Bangle.isCompassOn()){ + Bangle.setCompassPower(0, "gpstrek"); + state.compassSamples = new Array(SAMPLES).fill(0) + } + } else { + Bangle.setCompassPower(1, "gpstrek"); + } } let radians = function(a) { diff --git a/apps/lcdclock/ChangeLog b/apps/lcdclock/ChangeLog index 56ea03c2c..07bb3cdfd 100644 --- a/apps/lcdclock/ChangeLog +++ b/apps/lcdclock/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Use clock_info module as an app -0.03: clock_info now uses app name to maintain settings specifically for this clock face \ No newline at end of file +0.03: clock_info now uses app name to maintain settings specifically for this clock face +0.04: clock_info is loaded before widgets to match other clocks diff --git a/apps/lcdclock/app.js b/apps/lcdclock/app.js index 3808f46fe..c7789a85f 100644 --- a/apps/lcdclock/app.js +++ b/apps/lcdclock/app.js @@ -30,24 +30,6 @@ let draw = function() { }, 60000 - (Date.now() % 60000)); }; -// Show launcher when middle button pressed -Bangle.setUI({ - mode : "clock", - remove : function() { - // Called to unload all of the clock app - if (drawTimeout) clearTimeout(drawTimeout); - drawTimeout = undefined; - delete Graphics.prototype.setFont7Seg; - // remove info menu - clockInfoMenu.remove(); - delete clockInfoMenu; - clockInfoMenu2.remove(); - delete clockInfoMenu2; - // reset theme - g.setTheme(oldTheme); - }}); -// Load widgets -Bangle.loadWidgets(); var R = Bangle.appRect; R.x+=1; R.y+=1; @@ -57,12 +39,6 @@ R.w-=2; R.h-=2; var midX = R.x+R.w/2; var barY = 80; -// Clear the screen once, at startup -let oldTheme = g.theme; -g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(1); -g.fillRect({x:R.x, y:R.y, w:R.w, h:R.h, r:8}).clearRect(R.x,barY,R.w,barY+1).clearRect(midX,R.y,midX+1,barY); -draw(); -setTimeout(Bangle.drawWidgets,0); let clockInfoDraw = (itm, info, options) => { let texty = options.y+41; @@ -81,4 +57,29 @@ let clockInfoDraw = (itm, info, options) => { let clockInfoItems = require("clock_info").load(); let clockInfoMenu = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:R.x, y:R.y, w:midX-2, h:barY-R.y-2, draw : clockInfoDraw}); let clockInfoMenu2 = require("clock_info").addInteractive(clockInfoItems, { app:"lcdclock", x:midX+2, y:R.y, w:midX-3, h:barY-R.y-2, draw : clockInfoDraw}); + +// Show launcher when middle button pressed +Bangle.setUI({ + mode : "clock", + remove : function() { + // Called to unload all of the clock app + if (drawTimeout) clearTimeout(drawTimeout); + drawTimeout = undefined; + delete Graphics.prototype.setFont7Seg; + // remove info menu + clockInfoMenu.remove(); + delete clockInfoMenu; + clockInfoMenu2.remove(); + delete clockInfoMenu2; + // reset theme + g.setTheme(oldTheme); + }}); +// Load widgets +Bangle.loadWidgets(); +// Clear the screen once, at startup +let oldTheme = g.theme; +g.setTheme({bg:"#000",fg:"#fff",dark:true}).clear(1); +g.fillRect({x:R.x, y:R.y, w:R.w, h:R.h, r:8}).clearRect(R.x,barY,R.w,barY+1).clearRect(midX,R.y,midX+1,barY); +draw(); +setTimeout(Bangle.drawWidgets,0); } diff --git a/apps/lcdclock/metadata.json b/apps/lcdclock/metadata.json index b144c125e..4a98e8124 100644 --- a/apps/lcdclock/metadata.json +++ b/apps/lcdclock/metadata.json @@ -1,6 +1,6 @@ { "id": "lcdclock", "name": "LCD Clock", - "version":"0.03", + "version":"0.04", "description": "A Casio-style clock, with ClockInfo areas at the top and bottom. Tap them and swipe up/down to toggle between different information", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/openstmap/ChangeLog b/apps/openstmap/ChangeLog index 7d51a1d0c..32951bda7 100644 --- a/apps/openstmap/ChangeLog +++ b/apps/openstmap/ChangeLog @@ -29,3 +29,4 @@ 0.22: Replace position marker with direction arrow 0.23: Bugfix: Enable Compass if needed 0.24: Allow zooming by clicking the screen +0.25: Enable scaled image filtering on 2v19+ firmware \ No newline at end of file diff --git a/apps/openstmap/README.md b/apps/openstmap/README.md index e0fc30abd..d492bae05 100644 --- a/apps/openstmap/README.md +++ b/apps/openstmap/README.md @@ -22,6 +22,11 @@ quality, but uploads faster and takes less space). Bangle.js 2 is limited to 3bp can change settings, move the map around, and click `Get Map` again. * When you're ready, click `Upload` +**Note:** By default on Bangle.js, pre-dithered 3 bpp bitmaps will be uploaded +(which match the screen bit depth). However you can untick the `3 bit` checkbox +to use 8 bit maps, which take up 2.6x more space but look much better when +zoomed in/out. + ## Bangle.js App The Bangle.js app allows you to view a map. It also turns the GPS on diff --git a/apps/openstmap/interface.html b/apps/openstmap/interface.html index 0d9ef3152..9e22c57e4 100644 --- a/apps/openstmap/interface.html +++ b/apps/openstmap/interface.html @@ -124,10 +124,11 @@ TODO: // ---------------------------------------- Run at startup function onInit(device) { if (device && device.info && device.info.g) { - // On 3 bit devices, don't even offer the option. 3 bit is the only way + // On 3 bit devices, 3 bit is the best way + // still allow 8 bit as it makes zoom out much nicer if (device.info.g.bpp==3) { document.getElementById("3bit").checked = true; - document.getElementById("3bitdiv").style = "display:none"; + //document.getElementById("3bitdiv").style = "display:none"; } } @@ -258,15 +259,16 @@ TODO: mode:"3bit", diffusion:"bayer2" }; - /* If in 3 bit mode, go through all the data beforehand and - turn the saturation up to maximum, so when thresholded it - works a lot better */ - var imageData = ctx.getImageData(0,0,width,height); - var dstData = ctx.createImageData(width, height); - var filterOptions = {}; - imageFilterFor3BPP(imageData, dstData, filterOptions); - ctx.putImageData(dstData,0,0); } + /* Go through all the data beforehand and + turn the saturation up to maximum, so if thresholded to 3 bits it + works a lot better */ + var imageData = ctx.getImageData(0,0,width,height); + var dstData = ctx.createImageData(width, height); + var filterOptions = {}; + imageFilterFor3BPP(imageData, dstData, filterOptions); + ctx.putImageData(dstData,0,0); + console.log("Compression options", options); var w = Math.round(width / TILESIZE); var h = Math.round(height / TILESIZE); diff --git a/apps/openstmap/metadata.json b/apps/openstmap/metadata.json index 988a1414d..05dcf2709 100644 --- a/apps/openstmap/metadata.json +++ b/apps/openstmap/metadata.json @@ -2,7 +2,7 @@ "id": "openstmap", "name": "OpenStreetMap", "shortName": "OpenStMap", - "version": "0.24", + "version": "0.25", "description": "Loads map tiles from OpenStreetMap onto your Bangle.js and displays a map of where you are. Once installed this also adds map functionality to `GPS Recorder` and `Recorder` apps", "readme": "README.md", "icon": "app.png", diff --git a/apps/openstmap/openstmap.js b/apps/openstmap/openstmap.js index 0a8f35f66..0a36b829e 100644 --- a/apps/openstmap/openstmap.js +++ b/apps/openstmap/openstmap.js @@ -52,6 +52,7 @@ exports.draw = function() { if (d!=1) { // if the two are different, add scaling s *= d; o.scale = d; + o.filter = true; // on 2v19+ enables supersampling } //console.log(ix,iy); var tx = 0|(ix/s); diff --git a/apps/pebblepp/ChangeLog b/apps/pebblepp/ChangeLog index f3886a42e..83be498bd 100644 --- a/apps/pebblepp/ChangeLog +++ b/apps/pebblepp/ChangeLog @@ -1,4 +1,5 @@ 0.01: First release 0.02: clock_info now uses app name to maintain settings specifically for this clock face ensure clockinfo text is usppercase (font doesn't render lowercase) -0.03: Use smaller font if clock_info test doesn't fit in area \ No newline at end of file +0.03: Use smaller font if clock_info test doesn't fit in area +0.04: Ensure we only scale down clockinfo text if it really won't fit \ No newline at end of file diff --git a/apps/pebblepp/app.js b/apps/pebblepp/app.js index 5121f450e..330d79618 100644 --- a/apps/pebblepp/app.js +++ b/apps/pebblepp/app.js @@ -97,10 +97,10 @@ let clockInfoDraw = (itm, info, options) => { } g.setFontLECO1976Regular22().setFontAlign(0, 0); var txt = info.text.toString().toUpperCase(); - if (g.stringWidth(txt) > options.w-4) // if too big, smaller font + if (g.stringWidth(txt) > options.w) // if too big, smaller font g.setFontLECO1976Regular14(); - if (g.stringWidth(txt) > options.w-4) {// if still too big, split to 2 lines - var l = g.wrapString(txt, options.w-2); + if (g.stringWidth(txt) > options.w) {// if still too big, split to 2 lines + var l = g.wrapString(txt, options.w); txt = l.slice(0,2).join("\n") + (l.length>2)?"...":""; } g.drawString(txt, midx,options.y+options.h-12); // draw the text diff --git a/apps/pebblepp/metadata.json b/apps/pebblepp/metadata.json index 881b558db..e2be44ea0 100644 --- a/apps/pebblepp/metadata.json +++ b/apps/pebblepp/metadata.json @@ -2,7 +2,7 @@ "id": "pebblepp", "name": "Pebble++ Clock", "shortName": "Pebble++", - "version": "0.03", + "version": "0.04", "description": "A pebble style clock (based on the 'Pebble Clock' app) but with two configurable ClockInfo items at the top", "icon": "app.png", "screenshots": [{"url":"screenshot.png"}], diff --git a/apps/teatimer/ChangeLog b/apps/teatimer/ChangeLog index db8dd270b..31b495ecf 100644 --- a/apps/teatimer/ChangeLog +++ b/apps/teatimer/ChangeLog @@ -1,3 +1,4 @@ 0.01: New App! 0.02: Fix issue setting colors after showMessage 0.03: Fix BG/FG Color if e.g. theme background is black +0.04: Get time zone from settings for showing the clock diff --git a/apps/teatimer/app.js b/apps/teatimer/app.js index c394b5e00..fbaed4d62 100644 --- a/apps/teatimer/app.js +++ b/apps/teatimer/app.js @@ -13,7 +13,8 @@ const states = { stop: 32 // timer stopped }; var state = states.start; -E.setTimeZone(1); +let setting = require("Storage").readJSON("setting.json",1); +E.setTimeZone(setting.timezone); // Title showing current time function appTitle() { diff --git a/apps/teatimer/metadata.json b/apps/teatimer/metadata.json index b5cdce92e..a298a0e2b 100644 --- a/apps/teatimer/metadata.json +++ b/apps/teatimer/metadata.json @@ -1,7 +1,7 @@ { "id": "teatimer", "name": "Tea Timer", - "version": "0.03", + "version": "0.04", "description": "A simple timer. You can easyly set up the time.", "icon": "teatimer.png", "type": "app", diff --git a/bin/firmwaremaker_c.js b/bin/firmwaremaker_c.js index 4bd7fda2a..bd1e1f564 100755 --- a/bin/firmwaremaker_c.js +++ b/bin/firmwaremaker_c.js @@ -103,7 +103,15 @@ Promise.all(APPS.map(appid => { var app = apploader.apps.find(a => a.id==appid); if (!app) throw new Error(`App ${appid} not found`); return apploader.getAppFiles(app).then(files => { - appfiles = appfiles.concat(files); + files.forEach(f => { + var existing = appfiles.find(a=> a.name==f.name); + if (existing) { + if (existing.content !== f.content) + throw new Error(`Duplicate file ${f.name} is different`) + } else { + appfiles.push(f); + } + }); }); })).then(() => { // work out what goes in storage diff --git a/core b/core index b8813ab92..5b8b5fdfd 160000 --- a/core +++ b/core @@ -1 +1 @@ -Subproject commit b8813ab92ceb70fb8ec6a7de6baaec88f6b5026f +Subproject commit 5b8b5fdfd68164358ecbfbdd0f882404f5e3b0c4