Merge branch 'espruino:master' into master

pull/2957/head
inhof009 2023-08-08 15:48:29 -04:00 committed by GitHub
commit 352e663c68
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
39 changed files with 674 additions and 159 deletions

View File

@ -28,3 +28,5 @@
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.30: Send firmware and hardware versions on connection
Allow alarm enable/disable

View File

@ -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", () => {

View File

@ -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",

View File

@ -1 +1,2 @@
0.01: Initial release.
0.02: Handle the case where other apps have set bleAdvert to an array

View File

@ -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);
}

View File

@ -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",

View File

@ -1 +1,2 @@
0.01: New app!
0.02: Advertise accelerometer data and sensor location

View File

@ -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,
});
}
}

View File

@ -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,
}
);
}
}

View File

@ -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",

View File

@ -1 +1,2 @@
0.01: New App!
0.02: Handle the case where other apps have set bleAdvert to an array

View File

@ -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);
}

View File

@ -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",

View File

@ -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

View File

@ -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!

View File

@ -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"}],

View File

@ -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();
})

View File

@ -16,3 +16,6 @@
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
0.11: Adds configuration
Draws direction arrows on route
Turn of compass when GPS fix is available

View File

@ -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);
}
@ -519,6 +520,10 @@ 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)

21
apps/gpstrek/default.json Normal file
View File

@ -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
}

View File

@ -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"}
]
}

162
apps/gpstrek/settings.js Normal file
View File

@ -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();
})

View File

@ -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) {

View File

@ -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.04: clock_info is loaded before widgets to match other clocks

View File

@ -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);
}

View File

@ -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"}],

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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",

View File

@ -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);

View File

@ -2,3 +2,4 @@
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.04: Ensure we only scale down clockinfo text if it really won't fit

View File

@ -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

View File

@ -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"}],

View File

@ -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

View File

@ -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() {

View File

@ -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",

View File

@ -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

@ -1 +1 @@
Subproject commit b8813ab92ceb70fb8ec6a7de6baaec88f6b5026f
Subproject commit 5b8b5fdfd68164358ecbfbdd0f882404f5e3b0c4