mirror of https://github.com/espruino/BangleApps
Merge branch 'espruino:master' into master
commit
352e663c68
|
@ -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
|
||||
0.29: Support for http request xpath return format
|
||||
0.30: Send firmware and hardware versions on connection
|
||||
Allow alarm enable/disable
|
|
@ -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", () => {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: Initial release.
|
||||
0.02: Handle the case where other apps have set bleAdvert to an array
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New app!
|
||||
0.02: Advertise accelerometer data and sensor location
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
// <see encode function>
|
||||
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<T> = {
|
|||
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<boolean> = {
|
|||
mag: false,
|
||||
};
|
||||
|
||||
const idToName: BtAdvMap<string, true> = {
|
||||
acc: "Acceleration",
|
||||
const idToName: BtAdvMap<string> = {
|
||||
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<CompassData> = (data: CompassData) => {
|
|||
};
|
||||
encodeMag.maxLen = 6;
|
||||
|
||||
const encodeAcc: LenFunc<AccelData> = (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<number> = 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,
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
0.01: New App!
|
||||
0.02: Handle the case where other apps have set bleAdvert to an array
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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"}],
|
||||
|
|
|
@ -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();
|
||||
})
|
||||
|
|
|
@ -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
|
||||
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
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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"}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
})
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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"}],
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
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
|
|
@ -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
|
||||
|
|
|
@ -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"}],
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
||||
|
|
2
core
2
core
|
@ -1 +1 @@
|
|||
Subproject commit b8813ab92ceb70fb8ec6a7de6baaec88f6b5026f
|
||||
Subproject commit 5b8b5fdfd68164358ecbfbdd0f882404f5e3b0c4
|
Loading…
Reference in New Issue