1
0
Fork 0

Merge branch 'upstream/master' into feat/bleadvert-module

Conflicts:
	.eslintignore
master
Rob Pilling 2024-05-17 21:58:04 +01:00
commit 2d113883eb
55 changed files with 1337 additions and 472 deletions

View File

@ -5,15 +5,22 @@ apps/gipy/pkg/gps.js
# Needs to be ignored because it includes broken JS
apps/health/chart.min.js
# TypeScript
apps/btadv/
apps/clkinfostopw/
apps/ctrlpad/
apps/drained/
apps/folderlaunch/
apps/popconlaunch/
apps/rep/
apps/widChargingStatus/
apps/widbtstates/
apps/widhid/
# Generated from TS files that have already been linted
apps/btadv/app.js
apps/clkinfostopw/clkinfo.js
apps/ctrlpad/main.js
apps/drained/app.js
apps/drained/boot.js
apps/drained/settings.js
apps/folderlaunch/app.js
apps/folderlaunch/configLoad.js
apps/folderlaunch/settings.js
apps/folderlaunch/types.d.js
apps/popconlaunch/boot.js
apps/popconlaunch/settings.js
apps/rep/app.js
apps/rep/settings.js
apps/widChargingStatus/widget.js
apps/widbtstates/widget.js
apps/widhid/wid.js
modules/ble_advert.js

View File

@ -195,6 +195,28 @@ module.exports = {
"no-control-regex" : "off"
},
overrides: [
{
files: ["*.ts"],
extends: [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
rules: {
"no-delete-var": "off",
"no-empty": ["error", { "allowEmptyCatch": true }],
"no-prototype-builtins": "off",
"prefer-const": "off",
"prefer-rest-params": "off",
"no-control-regex" : "off",
"@typescript-eslint/no-delete-var": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/no-this-alias": "off",
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" } ],
"@typescript-eslint/no-var-requires": "off",
}
},
...Object.entries(lintExemptions).map(([filePath, {rules}]) => ({
files: [filePath],
rules: Object.fromEntries(rules.map(rule => [rule, "off"])),

3
apps/blecsc/ChangeLog Normal file
View File

@ -0,0 +1,3 @@
0.01: Initial version
0.02: Minor code improvements
0.03: Moved from cycling app, fixed connection issues and cadence

32
apps/blecsc/README.md Normal file
View File

@ -0,0 +1,32 @@
# BLE Cycling Speed Sencor (CSC)
Displays data from a BLE Cycling Speed and Cadence sensor.
Other than in the original version of the app, total distance is not stored on the Bangle, but instead is calculated from the CWR (cumulative wheel revolutions) reported by the sensor. This metric is, according to the BLE spec, an absolute value that persists throughout the lifetime of the sensor and never rolls over.
## Settings
Accessible from `Settings -> Apps -> BLE CSC`
Here you can set the wheel diameter
## Development
```
var csc = require("blecsc").getInstance();
csc.on("status", txt => {
print("##", txt);
E.showMessage(txt);
});
csc.on("data", e => print(e));
csc.start();
```
The `data` event contains:
* cwr/ccr => wheel/crank cumulative revs
* lwet/lcet => wheel/crank last event time in 1/1024s
* wrps/crps => calculated wheel/crank revs per second
* wdt/cdt => time period in seconds between events
* wr => wheel revs
* kph => kilometers per hour

216
apps/blecsc/blecsc.js Normal file
View File

@ -0,0 +1,216 @@
/**
* This library communicates with a Bluetooth CSC peripherial using the Espruino NRF library.
*
* ## Usage:
* 1. Register event handlers using the \`on(eventName, handlerFunction)\` method
* You can subscribe to the \`wheelEvent\` and \`crankEvent\` events or you can
* have raw characteristic values passed through using the \`value\` event.
* 2. Search and connect to a BLE CSC peripherial by calling the \`connect()\` method
* 3. To tear down the connection, call the \`disconnect()\` method
*
* ## Events
* - \`status\` - string containing connection status
* - \`data\` - the peripheral sends a notification containing wheel/crank event data
* - \`disconnect\` - the peripheral ends the connection or the connection is lost
*
* cwr/ccr => wheel/crank cumulative revs
* lwet/lcet => wheel/crank last event time in 1/1024s
* wrps/crps => calculated wheel/crank revs per second
* wdt/cdt => time period in seconds between events
* wr => wheel revs
* kph => kilometers per hour
*/
class BLECSC {
constructor() {
this.reconnect = false; // set when start called
this.device = undefined; // set when device found
this.gatt = undefined; // set when connected
// .on("status", => string
// .on("data"
// .on("disconnect"
this.resetStats();
// Set default values and merge with stored values
this.settings = Object.assign({
circum: 2068 // circumference in mm
}, (require('Storage').readJSON('blecsc.json', true) || {}));
}
resetStats() {
this.cwr = undefined;
this.ccr = undefined;
this.lwet = undefined;
this.lcet = undefined;
this.lastCwr = undefined;
this.lastCcr = undefined;
this.lastLwet = undefined;
this.lastLcet = undefined;
this.kph = undefined;
this.wrps = 0; // wheel revs per second
this.crps = 0; // crank revs per second
//this.batteryLevel = undefined;
}
getDeviceAddress() {
if (!this.device || !this.device.id)
return '00:00:00:00:00:00';
return this.device.id.split(" ")[0];
}
status(txt) {
this.emit("status", txt);
}
/**
* Find and connect to a device which exposes the CSC service.
*
* @return {Promise}
*/
connect() {
this.status("Scanning");
// Find a device, then get the CSC Service and subscribe to
// notifications on the CSC Measurement characteristic.
// NRF.setLowPowerConnection(true);
var reconnect = this.reconnect; // auto-reconnect
return NRF.requestDevice({
timeout: 5000,
filters: [{
services: ["1816"]
}],
}).then(device => {
this.status("Connecting");
this.device = device;
this.device.on('gattserverdisconnected', event => {
this.device = undefined;
this.gatt = undefined;
this.resetStats();
this.status("Disconnected");
this.emit("disconnect", event);
if (reconnect) {// auto-reconnect
reconnect = false;
setTimeout(() => {
if (this.reconnect) this.connect().then(() => {}, () => {});
}, 500);
}
});
return new Promise(resolve => setTimeout(resolve, 150)); // On CooSpo we get a 'Connection Timeout' if we try and connect too soon
}).then(() => {
return this.device.gatt.connect();
}).then(gatt => {
this.status("Connected");
this.gatt = gatt;
return gatt.getPrimaryService("1816");
}).then(service => {
return service.getCharacteristic("2a5b"); // UUID of the CSC measurement characteristic
}).then(characteristic => {
// register for changes on 2a5b
characteristic.on('characteristicvaluechanged', event => {
const flags = event.target.value.getUint8(0);
var offs = 0;
var data = {};
if (flags & 1) { // FLAGS_WREV_BM
this.lastCwr = this.cwr;
this.lastLwet = this.lwet;
this.cwr = event.target.value.getUint32(1, true);
this.lwet = event.target.value.getUint16(5, true);
if (this.lastCwr === undefined) this.lastCwr = this.cwr;
if (this.lastLwet === undefined) this.lastLwet = this.lwet;
if (this.lwet < this.lastLwet) this.lastLwet -= 65536;
let secs = (this.lwet - this.lastLwet) / 1024;
this.wrps = (this.cwr - this.lastCwr) / (secs?secs:1);
this.kph = this.wrps * this.settings.circum / 3600;
Object.assign(data, { // Notify the 'wheelEvent' handler
cwr: this.cwr, // cumulative wheel revolutions
lwet: this.lwet, // last wheel event time
wrps: this.wrps, // wheel revs per second
wr: this.cwr - this.lastCwr, // wheel revs
wdt : secs, // time period
kph : this.kph
});
offs += 6;
}
if (flags & 2) { // FLAGS_CREV_BM
this.lastCcr = this.ccr;
this.lastLcet = this.lcet;
this.ccr = event.target.value.getUint16(offs + 1, true);
this.lcet = event.target.value.getUint16(offs + 3, true);
if (this.lastCcr === undefined) this.lastCcr = this.ccr;
if (this.lastLcet === undefined) this.lastLcet = this.lcet;
if (this.lcet < this.lastLcet) this.lastLcet -= 65536;
let secs = (this.lcet - this.lastLcet) / 1024;
this.crps = (this.ccr - this.lastCcr) / (secs?secs:1);
Object.assign(data, { // Notify the 'crankEvent' handler
ccr: this.ccr, // cumulative crank revolutions
lcet: this.lcet, // last crank event time
crps: this.crps, // crank revs per second
cdt : secs, // time period
});
}
this.emit("data",data);
});
return characteristic.startNotifications();
/* }).then(() => {
return this.gatt.getPrimaryService("180f");
}).then(service => {
return service.getCharacteristic("2a19");
}).then(characteristic => {
characteristic.on('characteristicvaluechanged', (event)=>{
this.batteryLevel = event.target.value.getUint8(0);
});
return characteristic.startNotifications();*/
}).then(() => {
this.status("Ready");
}, err => {
this.status("Error: " + err);
if (reconnect) { // auto-reconnect
reconnect = false;
setTimeout(() => {
if (this.reconnect) this.connect().then(() => {}, () => {});
}, 500);
}
throw err;
});
}
/**
* Disconnect the device.
*/
disconnect() {
if (!this.gatt) return;
this.gatt.disconnect();
this.gatt = undefined;
}
/* Start trying to connect - will keep searching and attempting to connect*/
start() {
this.reconnect = true;
if (!this.device)
this.connect().then(() => {}, () => {});
}
/* Stop trying to connect, and disconnect */
stop() {
this.reconnect = false;
this.disconnect();
}
}
// Get an instance of BLECSC or create one if it doesn't exist
BLECSC.getInstance = function() {
if (!BLECSC.instance) {
BLECSC.instance = new BLECSC();
}
return BLECSC.instance;
};
exports = BLECSC;
/*
var csc = require("blecsc").getInstance();
csc.on("status", txt => {
print("##", txt);
E.showMessage(txt);
});
csc.on("data", e => print(e));
csc.start();
*/

74
apps/blecsc/clkinfo.js Normal file
View File

@ -0,0 +1,74 @@
(function() {
var csc = require("blecsc").getInstance();
//csc.on("status", txt => { print("CSC",txt); });
csc.on("data", e => {
ci.items.forEach(it => { if (it._visible) it.emit('redraw'); });
});
csc.on("disconnect", e => {
// redraw all with no info
ci.items.forEach(it => { if (it._visible) it.emit('redraw'); });
});
var uses = 0;
var ci = {
name: "CSC",
items: [
{ name : "Speed",
get : () => {
return {
text : (csc.kph === undefined) ? "--" : require("locale").speed(csc.kph),
img : atob("GBiBAAAAAAAAAAAAAAABwAABwAeBgAMBgAH/gAH/wAPDwA/DcD9m/Ge35sW9o8//M8/7E8CBA2GBhn8A/h4AeAAAAAAAAAAAAAAAAA==")
};
},
show : function() {
uses++;
if (uses==1) csc.start();
this._visible = true;
},
hide : function() {
this._visible = false;
uses--;
if (uses==0) csc.stop();
}
},
{ name : "Distance",
get : () => {
return {
text : (csc.kph === undefined) ? "--" : require("locale").distance(csc.cwr * csc.settings.circum / 1000),
img : atob("GBiBAAAAAB8AADuAAGDAAGTAAGRAAEBAAGBAAGDAADCAADGAIB8B+A/BjAfjBgAyJgAyIgAyAj/jBnADBmABjGAA2HAA8D//4AAAAA==")
};
},
show : function() {
uses++;
if (uses==1) csc.start();
this._visible = true;
},
hide : function() {
this._visible = false;
uses--;
if (uses==0) csc.stop();
}
},
{ name : "Cadence",
get : () => {
return {
text : (csc.crps === undefined) ? "--" : Math.round(csc.crps*60),
img : atob("GBiBAAAAAAAAAAB+EAH/sAeB8A4A8AwB8BgAABgAADAAADAAADAAADAADDAADDAAABgAABgAGAwAEA4AAAeAwAH8gAB8AAAAAAAAAA==")
};
},
show : function() {
uses++;
if (uses==1) csc.start();
this._visible = true;
},
hide : function() {
this._visible = false;
uses--;
if (uses==0) csc.stop();
}
}
]
};
return ci;
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

22
apps/blecsc/metadata.json Normal file
View File

@ -0,0 +1,22 @@
{
"id": "blecsc",
"name": "BLE Cycling Speed Sensor Library",
"shortName": "BLE CSC",
"version": "0.03",
"description": "Module to get live values from a BLE Cycle Speed (CSC) sensor. Includes recorder and clockinfo plugins",
"icon": "icons8-cycling-48.png",
"tags": "outdoors,exercise,ble,bluetooth,clkinfo",
"type":"module",
"provides_modules" : ["blecsc"],
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"blecsc","url":"blecsc.js"},
{"name":"blecsc.settings.js","url":"settings.js"},
{"name":"blecsc.recorder.js","url":"recorder.js"},
{"name":"blecsc.clkinfo.js","url":"clkinfo.js"}
],
"data": [
{"name":"blecsc.json"}
]
}

28
apps/blecsc/recorder.js Normal file
View File

@ -0,0 +1,28 @@
(function(recorders) {
recorders.blecsc = function() {
var csc = require("blecsc").getInstance();
var speed, cadence;
csc.on("data", e => {
speed = e.kph; // speed in KPH
cadence = (e.crps===undefined)?"":Math.round(e.crps*60); // crank rotations per minute
});
return {
name : "CSC",
fields : ["Speed (kph)","Cadence (rpm)"],
getValues : () => {
var r = [speed,cadence];
speed = "";
cadence = "";
return r;
},
start : () => {
csc.start();
},
stop : () => {
csc.stop();
},
draw : (x,y) => g.setColor(csc.device?"#0f0":"#8f8").drawImage(atob("Dw+BAAAAAAABgOIA5gHcBxw9fpfTPqYRC8HgAAAAAAAA"),x,y)
};
}
})

85
apps/blecsc/settings.js Normal file
View File

@ -0,0 +1,85 @@
(function(back) {
const storage = require('Storage')
const SETTINGS_FILE = 'blecsc.json'
// Set default values and merge with stored values
let settings = Object.assign({
circum: 2068 // circumference in mm
}, (storage.readJSON(SETTINGS_FILE, true) || {}));
function saveSettings() {
storage.writeJSON(SETTINGS_FILE, settings);
}
function circumMenu() {
var v = 0|settings.circum;
var cm = 0|(v/10);
var mm = v-(cm*10);
E.showMenu({
'': { title: /*LANG*/"Circumference", back: mainMenu },
'cm': {
value: cm,
min: 80, max: 240, step: 1,
onchange: (v) => {
cm = v;
settings.circum = (cm*10)+mm;
saveSettings();
},
},
'+ mm': {
value: mm,
min: 0, max: 9, step: 1,
onchange: (v) => {
mm = v;
settings.circum = (cm*10)+mm;
saveSettings();
},
},
/*LANG*/'Std Wheels': function() {
// https://support.wahoofitness.com/hc/en-us/articles/115000738484-Tire-Size-Wheel-Circumference-Chart
E.showMenu({
'': { title: /*LANG*/'Std Wheels', back: circumMenu },
'650x38 wheel' : function() {
settings.circum = 1995;
saveSettings();
mainMenu();
},
'700x32c wheel' : function() {
settings.circum = 2152;
saveSettings();
mainMenu();
},
'24"x1.75 wheel' : function() {
settings.circum = 1890;
saveSettings();
mainMenu();
},
'26"x1.5 wheel' : function() {
settings.circum = 2010;
saveSettings();
mainMenu();
},
'27.5"x1.5 wheel' : function() {
settings.circum = 2079;
saveSettings();
mainMenu();
}
});
}
});
}
function mainMenu() {
E.showMenu({
'': { 'title': 'BLE CSC' },
'< Back': back,
/*LANG*/'Circumference': {
value: settings.circum+"mm",
onchange: circumMenu
},
});
}
mainMenu();
})

View File

@ -1,5 +1,6 @@
{
// @ts-ignore helper
// @ts-expect-error helper
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const __assign = Object.assign;
const Layout = require("Layout");

View File

@ -91,7 +91,7 @@
};
var info = {
name: "Gps",
name: "GPS",
items: [
{
name: "gridref",

View File

@ -0,0 +1 @@
0.01: New Clock Info!

View File

@ -0,0 +1,27 @@
(function() {
var speed;
function gpsHandler(e) {
speed = e.speed;
ci.items[0].emit('redraw');
}
var ci = {
name: "GPS",
items: [
{ name : "Speed",
get : function() { return { text : isFinite(speed) ? require("locale").speed(speed) : "--",
v : 0, min : isFinite(speed) ? speed : 0, max : 150,
img : atob("GBiBAAAAAAAAAAAAAAAAAAD/AAHDgAMYwAbDYAwAMAoA0BgDmBgfGB4ceBgYGBgAGBoAWAwAMAwAMAf/4AP/wAAAAAAAAAAAAAAAAA==") }},
show : function() {
Bangle.setGPSPower(1, "clkinfogpsspeed");
Bangle.on("GPS", gpsHandler);
},
hide : function() {
Bangle.removeListener("GPS", gpsHandler);
Bangle.setGPSPower(0, "clkinfogpsspeed");
}
// run : function() {} optional (called when tapped)
}
]
};
return ci;
}) // must not have a semi-colon!

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,13 @@
{ "id": "clkinfogpsspeed",
"name": "GPS Speed Clockinfo",
"shortName":"GPS Speed",
"version":"0.01",
"description": "A Clockinfo that displays your current speed according to the GPS",
"icon": "icon.png",
"type": "clkinfo",
"tags": "clkinfo",
"supports" : ["BANGLEJS2"],
"storage": [
{"name":"clkinfogpsspeed.clkinfo.js","url":"clkinfo.js"}
]
}

View File

@ -1,2 +1,3 @@
0.01: 1st ver,RGB565 and RGB888 colors in a common UI/UX
0.02: Minor code improvements
0.03: Minor code improvements

View File

@ -35,22 +35,22 @@ var v_model=process.env.BOARD;
var v_color_text='#FB0E01';
var v_color_statictxt='#e56e06'; //orange RGB format rrggbb
//RGB565 requires only 16 (5+6+5) bits/2 bytes
var a_colors_str= Array('White RGB565 0x','Orange','DarkGreen','Yellow',
var a_colors_str= ['White RGB565 0x','Orange','DarkGreen','Yellow',
'Maroon','Blue','green','Purple',
'cyan','olive','DarkCyan','DarkGrey',
'Navy','Red','Magenta','GreenYellow',
'Blush RGB888','pure red','Orange','Grey green',
'D. grey','Almond','Amber','Bone',
'Canary','Aero blue','Camel','Baby pink',
'Y.Corn','Cultured','Eigengrau','Citrine');
var a_colors= Array(0xFFFF,0xFD20,0x03E0,0xFFE0,
'Y.Corn','Cultured','Eigengrau','Citrine'];
var a_colors= [0xFFFF,0xFD20,0x03E0,0xFFE0,
0x7800,0x001F,0x07E0,0x780F,
0x07FF,0x7BE0,0x03EF,0x7BEF,
0x000F,0xF800,0xF81F,0xAFE5,
'#DE5D83','#FB0E01','#E56E06','#7E795C',
'#404040','#EFDECD','#FFBF00','#E3DAC9',
'#FFFF99','#C0E8D5','#C19A6B','#F4C2C2',
'#FBEC5D','#F5F5F5','#16161D','#E4D00A');
'#FBEC5D','#F5F5F5','#16161D','#E4D00A'];
var v_color_lines=0xFFFF; //White hex format

View File

@ -2,7 +2,7 @@
"id": "color_catalog",
"name": "Colors Catalog",
"shortName": "Colors Catalog",
"version": "0.02",
"version": "0.03",
"description": "Displays RGB565 and RGB888 colors, its name and code in screen.",
"icon": "app.png",
"tags": "Color,input,buttons,touch,UI",

View File

@ -8,3 +8,5 @@
0.07: Make Bangle.js 2 compatible
0.08: Convert Yes/No On/Off in settings to checkboxes
0.09: Automatically reconnect on error
0.10: Fix cscsensor when using coospoo sensor that supports crank *and* wheel
0.11: Update to use blecsc library

View File

@ -1,8 +1,3 @@
var device;
var gatt;
var service;
var characteristic;
const SETTINGS_FILE = 'cscsensor.json';
const storage = require('Storage');
const W = g.getWidth();
@ -17,12 +12,10 @@ class CSCSensor {
constructor() {
this.movingTime = 0;
this.lastTime = 0;
this.lastBangleTime = Date.now();
this.lastRevs = -1;
this.settings = storage.readJSON(SETTINGS_FILE, 1) || {};
this.settings.totaldist = this.settings.totaldist || 0;
this.totaldist = this.settings.totaldist;
this.wheelCirc = (this.settings.wheelcirc || 2230)/25.4;
this.speedFailed = 0;
this.speed = 0;
this.maxSpeed = 0;
@ -34,8 +27,6 @@ class CSCSensor {
this.distFactor = this.qMetric ? 1.609344 : 1;
this.screenInit = true;
this.batteryLevel = -1;
this.lastCrankTime = 0;
this.lastCrankRevs = 0;
this.showCadence = false;
this.cadence = 0;
}
@ -63,10 +54,6 @@ class CSCSensor {
}
}
updateBatteryLevel(event) {
if (event.target.uuid == "0x2a19") this.setBatteryLevel(event.target.value.getUint8(0));
}
drawBatteryIcon() {
g.setColor(1, 1, 1).drawRect(10*W/240, yStart+0.029167*H, 20*W/240, yStart+0.1125*H)
.fillRect(14*W/240, yStart+0.020833*H, 16*W/240, yStart+0.029167*H)
@ -81,7 +68,7 @@ class CSCSensor {
}
updateScreenRevs() {
var dist = this.distFactor*(this.lastRevs-this.lastRevsStart)*this.wheelCirc/63360.0;
var dist = this.distFactor*(this.lastRevs-this.lastRevsStart)*csc.settings.circum/63360.0;
var ddist = Math.round(100*dist)/100;
var tdist = Math.round(this.distFactor*this.totaldist*10)/10;
var dspeed = Math.round(10*this.distFactor*this.speed)/10;
@ -157,114 +144,38 @@ class CSCSensor {
}
}
updateSensor(event) {
var qChanged = false;
if (event.target.uuid == "0x2a5b") {
if (event.target.value.getUint8(0, true) & 0x2) {
// crank revolution - if enabled
const crankRevs = event.target.value.getUint16(1, true);
const crankTime = event.target.value.getUint16(3, true);
if (crankTime > this.lastCrankTime) {
this.cadence = (crankRevs-this.lastCrankRevs)/(crankTime-this.lastCrankTime)*(60*1024);
qChanged = true;
}
this.lastCrankRevs = crankRevs;
this.lastCrankTime = crankTime;
} else {
// wheel revolution
var wheelRevs = event.target.value.getUint32(1, true);
var dRevs = (this.lastRevs>0 ? wheelRevs-this.lastRevs : 0);
if (dRevs>0) {
qChanged = true;
this.totaldist += dRevs*this.wheelCirc/63360.0;
if ((this.totaldist-this.settings.totaldist)>0.1) {
this.settings.totaldist = this.totaldist;
storage.writeJSON(SETTINGS_FILE, this.settings);
}
}
this.lastRevs = wheelRevs;
if (this.lastRevsStart<0) this.lastRevsStart = wheelRevs;
var wheelTime = event.target.value.getUint16(5, true);
var dT = (wheelTime-this.lastTime)/1024;
var dBT = (Date.now()-this.lastBangleTime)/1000;
this.lastBangleTime = Date.now();
if (dT<0) dT+=64;
if (Math.abs(dT-dBT)>3) dT = dBT;
this.lastTime = wheelTime;
this.speed = this.lastSpeed;
if (dRevs>0 && dT>0) {
this.speed = (dRevs*this.wheelCirc/63360.0)*3600/dT;
this.speedFailed = 0;
this.movingTime += dT;
} else if (!this.showCadence) {
this.speedFailed++;
qChanged = false;
if (this.speedFailed>3) {
this.speed = 0;
qChanged = (this.lastSpeed>0);
}
}
this.lastSpeed = this.speed;
if (this.speed>this.maxSpeed && (this.movingTime>3 || this.speed<20) && this.speed<50) this.maxSpeed = this.speed;
}
}
if (qChanged) this.updateScreen();
}
}
var mySensor = new CSCSensor();
function getSensorBatteryLevel(gatt) {
gatt.getPrimaryService("180f").then(function(s) {
return s.getCharacteristic("2a19");
}).then(function(c) {
c.on('characteristicvaluechanged', (event)=>mySensor.updateBatteryLevel(event));
return c.startNotifications();
});
}
var csc = require("blecsc").getInstance();
csc.on("data", e => {
mySensor.totaldist += e.wr * csc.settings.circum/*mm*/ / 1000000; // finally in km
mySensor.lastRevs = e.cwr;
if (mySensor.lastRevsStart<0) mySensor.lastRevsStart = e.cwr;
mySensor.speed = e.kph;
mySensor.movingTime += e.wdt;
if (mySensor.speed>mySensor.maxSpeed && (mySensor.movingTime>3 || mySensor.speed<20) && mySensor.speed<50)
mySensor.maxSpeed = mySensor.speed;
mySensor.cadence = e.crps;
mySensor.updateScreen();
mySensor.updateScreen();
});
function connection_setup() {
mySensor.screenInit = true;
E.showMessage("Scanning for CSC sensor...");
NRF.requestDevice({ filters: [{services:["1816"]}], maxInterval: 100}).then(function(d) {
device = d;
E.showMessage("Found device");
return device.gatt.connect();
}).then(function(ga) {
gatt = ga;
E.showMessage("Connected");
return gatt.getPrimaryService("1816");
}).then(function(s) {
service = s;
return service.getCharacteristic("2a5b");
}).then(function(c) {
characteristic = c;
characteristic.on('characteristicvaluechanged', (event)=>mySensor.updateSensor(event));
return characteristic.startNotifications();
}).then(function() {
console.log("Done!");
g.reset().clearRect(Bangle.appRect).flip();
getSensorBatteryLevel(gatt);
mySensor.updateScreen();
}).catch(function(e) {
E.showMessage(e.toString(), "ERROR");
console.log(e);
setTimeout(connection_setup, 1000);
});
}
connection_setup();
csc.on("status", txt => {
//print("->", txt);
E.showMessage(txt);
});
E.on('kill',()=>{
if (gatt!=undefined) gatt.disconnect();
csc.stop();
mySensor.settings.totaldist = mySensor.totaldist;
storage.writeJSON(SETTINGS_FILE, mySensor.settings);
});
NRF.on('disconnect', connection_setup); // restart if disconnected
Bangle.setUI("updown", d=>{
if (d<0) { mySensor.reset(); g.clearRect(0, yStart, W, H); mySensor.updateScreen(); }
else if (d>0) { if (Date.now()-mySensor.lastBangleTime>10000) connection_setup(); }
else { mySensor.toggleDisplayCadence(); g.clearRect(0, yStart, W, H); mySensor.updateScreen(); }
else if (!d) { mySensor.toggleDisplayCadence(); g.clearRect(0, yStart, W, H); mySensor.updateScreen(); }
});
Bangle.loadWidgets();
Bangle.drawWidgets();
csc.start(); // start a connection

View File

@ -2,10 +2,11 @@
"id": "cscsensor",
"name": "Cycling speed sensor",
"shortName": "CSCSensor",
"version": "0.09",
"version": "0.11",
"description": "Read BLE enabled cycling speed and cadence sensor and display readings on watch",
"icon": "icons8-cycling-48.png",
"tags": "outdoors,exercise,ble,bluetooth,bike,cycle,bicycle",
"dependencies" : { "blecsc":"module" },
"supports": ["BANGLEJS", "BANGLEJS2"],
"readme": "README.md",
"storage": [

View File

@ -1 +1,2 @@
0.01: New app - forked from widhid
0.02: Minor code improvements

View File

@ -127,7 +127,7 @@
text: "DnD",
cb: function (tap) {
var on;
if (on = !!origBuzz) {
if ((on = !!origBuzz)) {
if (tap) {
Bangle.buzz = origBuzz;
origBuzz = undefined;

View File

@ -194,7 +194,7 @@
text: "DnD",
cb: tap => {
let on;
if(on = !!origBuzz){
if((on = !!origBuzz)){
if(tap){
Bangle.buzz = origBuzz;
origBuzz = undefined;

View File

@ -2,7 +2,7 @@
"id": "ctrlpad",
"name": "Control Panel",
"shortName": "ctrlpad",
"version": "0.01",
"version": "0.02",
"description": "Fast access (via a downward swipe) to common functions, such as bluetooth/HRM power and Do Not Disturb",
"icon": "icon.png",
"readme": "README.md",

View File

@ -1,2 +1,3 @@
0.01: Initial version
0.02: Minor code improvements
0.03: Move blecsc library into its own app so it can be shared (and fix some issues)

View File

@ -1,4 +1,5 @@
# Cycling
> Displays data from a BLE Cycling Speed and Cadence sensor.
*This is a fork of the CSCSensor app using the layout library and separate module for CSC functionality. It also drops persistence of total distance on the Bangle, as this information is also persisted on the sensor itself. Further, it allows configuration of display units (metric/imperial) independent of chosen locale. Finally, multiple sensors can be used and wheel circumference can be configured for each sensor individually.*
@ -27,8 +28,5 @@ Inside the Cycling app, use button / tap screen to:
## TODO
* Sensor battery status
* Implement crank events / show cadence
* Bangle.js 1 compatibility
* Allow setting CWR on the sensor (this is a feature intended by the BLE CSC spec, in case the sensor is replaced or transferred to a different bike)
## Development
There is a "mock" version of the `blecsc` module, which can be used to test features in the emulator. Check `blecsc-emu.js` for usage.

View File

@ -1,111 +0,0 @@
// UUID of the Bluetooth CSC Service
//const SERVICE_UUID = "1816";
// UUID of the CSC measurement characteristic
const MEASUREMENT_UUID = "2a5b";
// Wheel revolution present bit mask
const FLAGS_WREV_BM = 0x01;
// Crank revolution present bit mask
const FLAGS_CREV_BM = 0x02;
/**
* Fake BLECSC implementation for the emulator, where it's hard to test
* with actual hardware. Generates "random" wheel events (no crank).
*
* To upload as a module, paste the entire file in the console using this
* command: require("Storage").write("blecsc-emu",`<FILE CONTENT HERE>`);
*/
class BLECSCEmulator {
constructor() {
this.timeout = undefined;
this.interval = 500;
this.ccr = 0;
this.lwt = 0;
this.handlers = {
// value
// disconnect
// wheelEvent
// crankEvent
};
}
getDeviceAddress() {
return 'fa:ke:00:de:vi:ce';
}
/**
* Callback for the GATT characteristicvaluechanged event.
* Consumers must not call this method!
*/
onValue(event) {
// Not interested in non-CSC characteristics
if (event.target.uuid != "0x" + MEASUREMENT_UUID) return;
// Notify the generic 'value' handler
if (this.handlers.value) this.handlers.value(event);
const flags = event.target.value.getUint8(0, true);
// Notify the 'wheelEvent' handler
if ((flags & FLAGS_WREV_BM) && this.handlers.wheelEvent) this.handlers.wheelEvent({
cwr: event.target.value.getUint32(1, true), // cumulative wheel revolutions
lwet: event.target.value.getUint16(5, true), // last wheel event time
});
// Notify the 'crankEvent' handler
if ((flags & FLAGS_CREV_BM) && this.handlers.crankEvent) this.handlers.crankEvent({
ccr: event.target.value.getUint16(7, true), // cumulative crank revolutions
lcet: event.target.value.getUint16(9, true), // last crank event time
});
}
/**
* Register an event handler.
*
* @param {string} event value|disconnect
* @param {function} handler handler function that receives the event as its first argument
*/
on(event, handler) {
this.handlers[event] = handler;
}
fakeEvent() {
this.interval = Math.max(50, Math.min(1000, this.interval + Math.random()*40-20));
this.lwt = (this.lwt + this.interval) % 0x10000;
this.ccr++;
var buffer = new ArrayBuffer(8);
var view = new DataView(buffer);
view.setUint8(0, 0x01); // Wheel revolution data present bit
view.setUint32(1, this.ccr, true); // Cumulative crank revolutions
view.setUint16(5, this.lwt, true); // Last wheel event time
this.onValue({
target: {
uuid: "0x2a5b",
value: view,
},
});
this.timeout = setTimeout(this.fakeEvent.bind(this), this.interval);
}
/**
* Find and connect to a device which exposes the CSC service.
*
* @return {Promise}
*/
connect() {
this.timeout = setTimeout(this.fakeEvent.bind(this), this.interval);
return Promise.resolve(true);
}
/**
* Disconnect the device.
*/
disconnect() {
if (!this.timeout) return;
clearTimeout(this.timeout);
}
}
exports = BLECSCEmulator;

View File

@ -1,150 +0,0 @@
const SERVICE_UUID = "1816";
// UUID of the CSC measurement characteristic
const MEASUREMENT_UUID = "2a5b";
// Wheel revolution present bit mask
const FLAGS_WREV_BM = 0x01;
// Crank revolution present bit mask
const FLAGS_CREV_BM = 0x02;
/**
* This class communicates with a Bluetooth CSC peripherial using the Espruino NRF library.
*
* ## Usage:
* 1. Register event handlers using the \`on(eventName, handlerFunction)\` method
* You can subscribe to the \`wheelEvent\` and \`crankEvent\` events or you can
* have raw characteristic values passed through using the \`value\` event.
* 2. Search and connect to a BLE CSC peripherial by calling the \`connect()\` method
* 3. To tear down the connection, call the \`disconnect()\` method
*
* ## Events
* - \`wheelEvent\` - the peripharial sends a notification containing wheel event data
* - \`crankEvent\` - the peripharial sends a notification containing crank event data
* - \`value\` - the peripharial sends any CSC characteristic notification (including wheel & crank event)
* - \`disconnect\` - the peripherial ends the connection or the connection is lost
*
* Each event can only have one handler. Any call to \`on()\` will
* replace a previously registered handler for the same event.
*/
class BLECSC {
constructor() {
this.device = undefined;
this.ccInterval = undefined;
this.gatt = undefined;
this.handlers = {
// wheelEvent
// crankEvent
// value
// disconnect
};
}
getDeviceAddress() {
if (!this.device || !this.device.id)
return '00:00:00:00:00:00';
return this.device.id.split(" ")[0];
}
checkConnection() {
if (!this.device)
console.log("no device");
// else
// console.log("rssi: " + this.device.rssi);
}
/**
* Callback for the GATT characteristicvaluechanged event.
* Consumers must not call this method!
*/
onValue(event) {
// Not interested in non-CSC characteristics
if (event.target.uuid != "0x" + MEASUREMENT_UUID) return;
// Notify the generic 'value' handler
if (this.handlers.value) this.handlers.value(event);
const flags = event.target.value.getUint8(0, true);
// Notify the 'wheelEvent' handler
if ((flags & FLAGS_WREV_BM) && this.handlers.wheelEvent) this.handlers.wheelEvent({
cwr: event.target.value.getUint32(1, true), // cumulative wheel revolutions
lwet: event.target.value.getUint16(5, true), // last wheel event time
});
// Notify the 'crankEvent' handler
if ((flags & FLAGS_CREV_BM) && this.handlers.crankEvent) this.handlers.crankEvent({
ccr: event.target.value.getUint16(7, true), // cumulative crank revolutions
lcet: event.target.value.getUint16(9, true), // last crank event time
});
}
/**
* Callback for the NRF disconnect event.
* Consumers must not call this method!
*/
onDisconnect(event) {
console.log("disconnected");
if (this.ccInterval)
clearInterval(this.ccInterval);
if (!this.handlers.disconnect) return;
this.handlers.disconnect(event);
}
/**
* Register an event handler.
*
* @param {string} event wheelEvent|crankEvent|value|disconnect
* @param {function} handler function that will receive the event as its first argument
*/
on(event, handler) {
this.handlers[event] = handler;
}
/**
* Find and connect to a device which exposes the CSC service.
*
* @return {Promise}
*/
connect() {
// Register handler for the disconnect event to be passed throug
NRF.on('disconnect', this.onDisconnect.bind(this));
// Find a device, then get the CSC Service and subscribe to
// notifications on the CSC Measurement characteristic.
// NRF.setLowPowerConnection(true);
return NRF.requestDevice({
timeout: 5000,
filters: [{ services: [SERVICE_UUID] }],
}).then(device => {
this.device = device;
this.device.on('gattserverdisconnected', this.onDisconnect.bind(this));
this.ccInterval = setInterval(this.checkConnection.bind(this), 2000);
return device.gatt.connect();
}).then(gatt => {
this.gatt = gatt;
return gatt.getPrimaryService(SERVICE_UUID);
}).then(service => {
return service.getCharacteristic(MEASUREMENT_UUID);
}).then(characteristic => {
characteristic.on('characteristicvaluechanged', this.onValue.bind(this));
return characteristic.startNotifications();
});
}
/**
* Disconnect the device.
*/
disconnect() {
if (this.ccInterval)
clearInterval(this.ccInterval);
if (!this.gatt) return;
try {
this.gatt.disconnect();
} catch {
//
}
}
}
exports = BLECSC;

View File

@ -23,7 +23,6 @@ class CSCSensor {
// CSC runtime variables
this.movingTime = 0; // unit: s
this.lastBangleTime = Date.now(); // unit: ms
this.lwet = 0; // last wheel event time (unit: s/1024)
this.cwr = -1; // cumulative wheel revolutions
this.cwrTrip = 0; // wheel revolutions since trip start
this.speed = 0; // unit: m/s
@ -84,7 +83,7 @@ class CSCSensor {
console.log("Trying to connect to BLE CSC");
// Hook up events
this.blecsc.on('wheelEvent', this.onWheelEvent.bind(this));
this.blecsc.on('data', this.onWheelEvent.bind(this));
this.blecsc.on('disconnect', this.onDisconnect.bind(this));
// Scan for BLE device and connect
@ -171,20 +170,11 @@ class CSCSensor {
// Increment the trip revolutions counter
this.cwrTrip += dRevs;
// Calculate time delta since last wheel event
var dT = (event.lwet - this.lwet)/1024;
var now = Date.now();
var dBT = (now-this.lastBangleTime)/1000;
this.lastBangleTime = now;
if (dT<0) dT+=64; // wheel event time wraps every 64s
if (Math.abs(dT-dBT)>3) dT = dBT; // not sure about the reason for this
this.lwet = event.lwet;
// Recalculate current speed
if (dRevs>0 && dT>0) {
this.speed = dRevs * this.wheelCirc / dT;
if (dRevs>0 ) {
this.speed = event.wrps * this.wheelCirc;
this.speedFailed = 0;
this.movingTime += dT;
this.movingTime += event.wdt;
} else {
this.speedFailed++;
if (this.speedFailed>3) {
@ -429,15 +419,7 @@ class CSCDisplay {
}
}
var BLECSC;
if (process.env.BOARD === "EMSCRIPTEN" || process.env.BOARD === "EMSCRIPTEN2") {
// Emulator
BLECSC = require("blecsc-emu");
} else {
// Actual hardware
BLECSC = require("blecsc");
}
var blecsc = new BLECSC();
var blecsc = require("blecsc").getInstance();
var display = new CSCDisplay();
var sensor = new CSCSensor(blecsc, display);

View File

@ -2,16 +2,16 @@
"id": "cycling",
"name": "Bangle Cycling",
"shortName": "Cycling",
"version": "0.02",
"version": "0.03",
"description": "Display live values from a BLE CSC sensor",
"icon": "icons8-cycling-48.png",
"tags": "outdoors,exercise,ble,bluetooth",
"dependencies" : { "blecsc":"module" },
"supports": ["BANGLEJS2"],
"readme": "README.md",
"storage": [
{"name":"cycling.app.js","url":"cycling.app.js"},
{"name":"cycling.settings.js","url":"settings.js"},
{"name":"blecsc","url":"blecsc.js"},
{"name":"cycling.img","url":"cycling.icon.js","evaluate": true}
],
"data": [

View File

@ -1,7 +1,7 @@
const app = "drained";
// from boot.js
declare var drainedInterval: IntervalId | undefined;
declare let drainedInterval: IntervalId | undefined;
if(typeof drainedInterval !== "undefined")
drainedInterval = clearInterval(drainedInterval) as undefined;

View File

@ -1,3 +1,4 @@
0.01: New app!
0.02: Handle files potentially not existing
0.03: Add setting to disable vibration
0.03: Add setting to disable vibration
0.04: Minor code improvements

View File

@ -78,19 +78,21 @@
var text = void 0;
var fontSize = void 0;
switch (entry.type) {
case 'app':
case 'app': {
var app_1 = storage_1.readJSON(entry.id + '.info', false);
icon = storage_1.read(app_1.icon);
text = app_1.name;
empty = false;
fontSize = config_1.display.font;
break;
case 'folder':
}
case 'folder': {
icon = FOLDER_ICON_1;
text = entry.id;
empty = false;
fontSize = config_1.display.font ? config_1.display.font : 12;
break;
}
default:
continue;
}
@ -132,12 +134,13 @@
y = config_1.display.rows - 1;
var entry = grid_1[x][y];
switch (entry.type) {
case "app":
case "app": {
buzz_1();
var infoFile = storage_1.readJSON(entry.id + '.info', false);
load(infoFile.src);
break;
case "folder":
}
case "folder": {
buzz_1();
resetTimeout_1();
page_1 = 0;
@ -145,9 +148,11 @@
folder_1 = getFolder_1(folderPath_1);
render_1();
break;
default:
}
default: {
resetTimeout_1();
break;
}
}
};
var page_1 = 0;

View File

@ -114,19 +114,21 @@
// Get the icon and text, skip if the space is empty. Always draw text for folders even if disabled
switch (entry.type) {
case 'app':
case 'app': {
let app = storage.readJSON(entry.id + '.info', false) as AppInfo;
icon = storage.read(app.icon!)!;
text = app.name;
empty = false;
fontSize = config.display.font;
break;
case 'folder':
}
case 'folder': {
icon = FOLDER_ICON;
text = entry.id;
empty = false;
fontSize = config.display.font ? config.display.font : 12;
break;
}
default:
continue;
}
@ -184,12 +186,13 @@
// Handle the grid cell
let entry: GridEntry = grid[x]![y]!;
switch (entry.type) {
case "app":
case "app": {
buzz();
let infoFile = storage.readJSON(entry.id + '.info', false) as AppInfo;
load(infoFile.src);
break;
case "folder":
}
case "folder": {
buzz();
resetTimeout();
page = 0;
@ -197,9 +200,11 @@
folder = getFolder(folderPath);
render();
break;
default:
}
default: {
resetTimeout();
break;
}
}
}

View File

@ -80,7 +80,7 @@ function cleanAndSave(config: Config): Config {
let infoFileSorter = (a: string, b: string): number => {
let aJson = storage.readJSON(a, false) as AppInfo;
let bJson = storage.readJSON(b, false) as AppInfo;
var n = (0 | aJson.sortorder!) - (0 | bJson.sortorder!);
const n = (0 | aJson.sortorder!) - (0 | bJson.sortorder!);
if (n) return n; // do sortorder first
if (aJson.name < bJson.name) return -1;
if (aJson.name > bJson.name) return 1;

View File

@ -1,7 +1,7 @@
{
"id": "folderlaunch",
"name": "Folder launcher",
"version": "0.03",
"version": "0.04",
"description": "Launcher that allows you to put your apps into folders",
"icon": "icon.png",
"type": "launch",

View File

@ -11,7 +11,7 @@ type Config = {
showClocks: boolean, // Whether clocks are shown
showLaunchers: boolean, // Whether launchers are shown
disableVibration: boolean, // Whether vibration is disabled
hidden: Array<String>, // IDs of apps to explicitly hide
hidden: Array<string>, // IDs of apps to explicitly hide
display: {
rows: number, // Display an X by X grid of apps
icon: boolean, // Whether to show icons

View File

@ -2,3 +2,4 @@
0.02: Supports bottom widgets and UI based in UI4swatch!
0.03: Added compatibility with BJS2, improvements
0.04: Minor code improvements
0.05: Minor code improvements

View File

@ -8,7 +8,7 @@
var v_color_statictxt='#b30000';
//var v_color_b_area='#111111';
//orange RGB format rrggbb //white,Orange,DarkGreen,Yellow,Maroon,Blue,green,Purple,cyan,olive,DarkCyan,pink
var a_colors= Array(0xFFFF,0xFD20,0x03E0,0xFFE0,0x7800,0x001F,0x07E0,0x780F,0x07FF,0x7BE0,0x03EF,0xF81F);
var a_colors= [0xFFFF,0xFD20,0x03E0,0xFFE0,0x7800,0x001F,0x07E0,0x780F,0x07FF,0x7BE0,0x03EF,0xF81F];
var x_max_screen=g.getWidth();
//var y_max_screen=g.getHeight();
@ -47,8 +47,8 @@ if (v_model=='BANGLEJS'||v_model=='EMSCRIPTEN') {
var v_arraypos=0;
var v_acolorpos=0; //for fg
var v_aBGcolorPos=5; //for bg
var a_string1 = Array('hola', 'hello', 'saluton', 'ola','ciao', 'salut','czesc','konnichiwa');
var a_string2 = Array('mundo!', 'world!', 'mondo!','mundo!','mondo!','monde!','swiat!','sekai!');
var a_string1 = ['hola', 'hello', 'saluton', 'ola','ciao', 'salut','czesc','konnichiwa'];
var a_string2 = ['mundo!', 'world!', 'mondo!','mundo!','mondo!','monde!','swiat!','sekai!'];
}

View File

@ -2,7 +2,7 @@
"id": "helloworld",
"name": "hello, world!",
"shortName": "hello world",
"version": "0.04",
"version": "0.05",
"description": "A cross cultural hello world!/hola mundo! app with colors and languages",
"icon": "app.png",
"tags": "input,interface,buttons,touch",

View File

@ -216,12 +216,6 @@ module.exports = {
"no-unused-vars"
]
},
"promenu/bootb2.js": {
"hash": "4f7a0cb285c35a61e22325dafdd548845393df8d952d8934b04576efb4b19561",
"rules": [
"no-unused-vars"
]
},
"poweroff/settings.js": {
"hash": "c197afe72c612a4b3825a3a12a628d0f4ed83823da3f28885bbf473037a02506",
"rules": [
@ -1071,12 +1065,6 @@ module.exports = {
"no-undef"
]
},
"fuzzyw/fuzzyw.app.js": {
"hash": "dbef30fe5639a240ada0968491c73242092bd2db7f3b060ef9c2f66a6cbbb19d",
"rules": [
"no-undef"
]
},
"fontclock/fontclock.js": {
"hash": "478c96b1319d548a79727c5af3191b77ac9753c6314789570e900e512a99a152",
"rules": [
@ -1125,12 +1113,6 @@ module.exports = {
"no-undef"
]
},
"drained/boot.js": {
"hash": "914ea2c47aa6b9502b9dd354d490b2a8b838d4c6fafaa20e206f1f2396d4db40",
"rules": [
"no-undef"
]
},
"dinoClock/app.js": {
"hash": "e97566aa4f586ef654e3fc1ec286376352762ed8e0ea2e685a7b3ae687f51552",
"rules": [

View File

@ -10,3 +10,4 @@
0.09: Minor code improvements
0.10: Handle missing alarm data, e.g. when our reset is fired from
non-multitimer alarms
0.11: Preserve setUI removal callbacks, e.g. those of showMenu

View File

@ -691,10 +691,12 @@ function setUI() {
// E.showMenu/E.showScroller/E.showAlert call setUI, so we register onDrag() separately
// and tack on uiRemove after the fact to avoid interfering
Bangle.on("drag", onDrag);
const origRemove = Bangle.uiRemove;
Bangle.uiRemove = () => {
Bangle.removeListener("drag", onDrag);
Object.values(timerInt1).forEach(clearTimeout);
Object.values(timerInt2).forEach(clearTimeout);
if (origRemove) origRemove();
};
}

View File

@ -1,7 +1,7 @@
{
"id": "multitimer",
"name": "Multi Timer",
"version": "0.10",
"version": "0.11",
"description": "Set timers and chronographs (stopwatches) and watch them count down in real time. Pause, create, edit, and delete timers and chronos, and add custom labels/messages. Also sets alarms.",
"icon": "app.png",
"screenshots": [

View File

@ -1,3 +1,4 @@
0.01: First release.
0.02: No functional changes, just moved codebase to Typescript.
0.03: Also buzz on disconnect from charging
0.04: Minor code improvements

View File

@ -2,7 +2,7 @@
"name": "Charging Status",
"shortName":"ChargingStatus",
"icon": "widget.png",
"version":"0.03",
"version": "0.04",
"type": "widget",
"description": "A simple widget that shows a yellow lightning icon to indicate whenever the watch is charging. This way one can see the charging status at a glance, no matter which battery widget is being used.",
"tags": "widget",

View File

@ -10,13 +10,13 @@
});
}
}
WIDGETS.chargingStatus = {
WIDGETS["chargingStatus"] = {
area: 'tr',
width: Bangle.isCharging() ? iconWidth : 0,
draw: draw,
};
Bangle.on('charging', function (charging) {
var widget = WIDGETS.chargingStatus;
var widget = WIDGETS["chargingStatus"];
if (widget) {
if (charging) {
Bangle.buzz();

View File

@ -16,16 +16,14 @@
}
}
// @ts-ignore
WIDGETS.chargingStatus = {
WIDGETS["chargingStatus"] = {
area: 'tr',
width: Bangle.isCharging() ? iconWidth : 0,
draw: draw,
};
Bangle.on('charging', (charging) => {
// @ts-ignore
const widget = WIDGETS.chargingStatus;
const widget = WIDGETS["chargingStatus"];
if (widget) {
if (charging) {
Bangle.buzz();

View File

@ -4,7 +4,7 @@
console.log("widhid: can't enable, HID setting isn't \"kbmedia\"");
return;
}
// @ts-ignore
// @ts-expect-error espruino-specific delete
delete settings;
let anchor = {x:0,y:0};
@ -128,7 +128,7 @@
if(connected)
Bangle.on("swipe", onSwipe);
// @ts-ignore
// @ts-expect-error espruino-specific delete
delete connected;
NRF.on("connect", () => {

View File

@ -12,7 +12,7 @@
* node bin/bulk-update-apps.mjs GITHASH CHANGELOGMESSAGE
*
* Example command:
* node bin/exempt-lint.mjs 29ced17i7 'Minor code improvements'
* node bin/bulk-update-apps.mjs 29ced17i7 'Minor code improvements'
*
* You can also run it in output mode like this:
* node bin/bulk-update-apps.mjs GITHASH --output

View File

@ -28,7 +28,7 @@ if (!lintRule) {
const filePathInput = process.argv[3];
const filePathMatch = filePathInput?.match(
/^(?:.*?\/apps\/|apps\/|\/)?(?<path>.*\.js)$/iu,
/^(?:.*?\/apps\/|apps\/|\/)?(?<path>.*\.[jt]s)$/iu,
);
const filePath = filePathMatch?.groups?.path;
if (!filePath) {

700
package-lock.json generated
View File

@ -12,6 +12,8 @@
"acorn": "^7.2.0"
},
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"eslint": "^8.57.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.29.1",
@ -152,12 +154,283 @@
"node": ">= 8"
}
},
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"node_modules/@types/semver": {
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz",
"integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==",
"dev": true,
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.8.0",
"@typescript-eslint/type-utils": "7.8.0",
"@typescript-eslint/utils": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^7.0.0",
"eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz",
"integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==",
"dev": true,
"dependencies": {
"@typescript-eslint/scope-manager": "7.8.0",
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/typescript-estree": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0",
"debug": "^4.3.4"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz",
"integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz",
"integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==",
"dev": true,
"dependencies": {
"@typescript-eslint/typescript-estree": "7.8.0",
"@typescript-eslint/utils": "7.8.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.56.0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz",
"integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==",
"dev": true,
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz",
"integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/utils": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz",
"integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.15",
"@types/semver": "^7.5.8",
"@typescript-eslint/scope-manager": "7.8.0",
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/typescript-estree": "7.8.0",
"semver": "^7.6.0"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"eslint": "^8.56.0"
}
},
"node_modules/@typescript-eslint/utils/node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz",
"integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==",
"dev": true,
"dependencies": {
"@typescript-eslint/types": "7.8.0",
"eslint-visitor-keys": "^3.4.3"
},
"engines": {
"node": "^18.18.0 || >=20.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/typescript-eslint"
}
},
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@ -284,6 +557,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/array.prototype.filter": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz",
@ -652,6 +934,18 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
"dev": true,
"dependencies": {
"path-type": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -1081,6 +1375,34 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"node_modules/fast-glob": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.4"
},
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/fast-glob/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@ -1344,6 +1666,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/globby": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true,
"dependencies": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.2.9",
"ignore": "^5.2.0",
"merge2": "^1.4.1",
"slash": "^3.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@ -1924,6 +2266,40 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"engines": {
"node": ">= 8"
}
},
"node_modules/micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"dev": true,
"dependencies": {
"braces": "^3.0.2",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@ -2289,6 +2665,15 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"node_modules/path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@ -2680,6 +3065,15 @@
"semver": "bin/semver.js"
}
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@ -2830,6 +3224,18 @@
"nodetouch": "bin/nodetouch.js"
}
},
"node_modules/ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
"engines": {
"node": ">=16"
},
"peerDependencies": {
"typescript": ">=4.2.0"
}
},
"node_modules/tsconfig-paths": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@ -2939,6 +3345,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/typescript": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"dev": true,
"peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@ -3061,6 +3481,12 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@ -3168,12 +3594,176 @@
"fastq": "^1.6.0"
}
},
"@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
"integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
"dev": true
},
"@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
"dev": true
},
"@types/semver": {
"version": "7.5.8",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
"integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
"dev": true
},
"@typescript-eslint/eslint-plugin": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.8.0.tgz",
"integrity": "sha512-gFTT+ezJmkwutUPmB0skOj3GZJtlEGnlssems4AjkVweUPGj7jRwwqg0Hhg7++kPGJqKtTYx+R05Ftww372aIg==",
"dev": true,
"requires": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "7.8.0",
"@typescript-eslint/type-utils": "7.8.0",
"@typescript-eslint/utils": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0",
"debug": "^4.3.4",
"graphemer": "^1.4.0",
"ignore": "^5.3.1",
"natural-compare": "^1.4.0",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"dependencies": {
"semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"@typescript-eslint/parser": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.8.0.tgz",
"integrity": "sha512-KgKQly1pv0l4ltcftP59uQZCi4HUYswCLbTqVZEJu7uLX8CTLyswqMLqLN+2QFz4jCptqWVV4SB7vdxcH2+0kQ==",
"dev": true,
"requires": {
"@typescript-eslint/scope-manager": "7.8.0",
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/typescript-estree": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0",
"debug": "^4.3.4"
}
},
"@typescript-eslint/scope-manager": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.8.0.tgz",
"integrity": "sha512-viEmZ1LmwsGcnr85gIq+FCYI7nO90DVbE37/ll51hjv9aG+YZMb4WDE2fyWpUR4O/UrhGRpYXK/XajcGTk2B8g==",
"dev": true,
"requires": {
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0"
}
},
"@typescript-eslint/type-utils": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.8.0.tgz",
"integrity": "sha512-H70R3AefQDQpz9mGv13Uhi121FNMh+WEaRqcXTX09YEDky21km4dV1ZXJIp8QjXc4ZaVkXVdohvWDzbnbHDS+A==",
"dev": true,
"requires": {
"@typescript-eslint/typescript-estree": "7.8.0",
"@typescript-eslint/utils": "7.8.0",
"debug": "^4.3.4",
"ts-api-utils": "^1.3.0"
}
},
"@typescript-eslint/types": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.8.0.tgz",
"integrity": "sha512-wf0peJ+ZGlcH+2ZS23aJbOv+ztjeeP8uQ9GgwMJGVLx/Nj9CJt17GWgWWoSmoRVKAX2X+7fzEnAjxdvK2gqCLw==",
"dev": true
},
"@typescript-eslint/typescript-estree": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.8.0.tgz",
"integrity": "sha512-5pfUCOwK5yjPaJQNy44prjCwtr981dO8Qo9J9PwYXZ0MosgAbfEMB008dJ5sNo3+/BN6ytBPuSvXUg9SAqB0dg==",
"dev": true,
"requires": {
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/visitor-keys": "7.8.0",
"debug": "^4.3.4",
"globby": "^11.1.0",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"ts-api-utils": "^1.3.0"
},
"dependencies": {
"brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0"
}
},
"minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dev": true,
"requires": {
"brace-expansion": "^2.0.1"
}
},
"semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"@typescript-eslint/utils": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.8.0.tgz",
"integrity": "sha512-L0yFqOCflVqXxiZyXrDr80lnahQfSOfc9ELAAZ75sqicqp2i36kEZZGuUymHNFoYOqxRT05up760b4iGsl02nQ==",
"dev": true,
"requires": {
"@eslint-community/eslint-utils": "^4.4.0",
"@types/json-schema": "^7.0.15",
"@types/semver": "^7.5.8",
"@typescript-eslint/scope-manager": "7.8.0",
"@typescript-eslint/types": "7.8.0",
"@typescript-eslint/typescript-estree": "7.8.0",
"semver": "^7.6.0"
},
"dependencies": {
"semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"dev": true,
"requires": {
"lru-cache": "^6.0.0"
}
}
}
},
"@typescript-eslint/visitor-keys": {
"version": "7.8.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.8.0.tgz",
"integrity": "sha512-q4/gibTNBQNA0lGyYQCmWRS5D15n8rXh4QjK3KV+MBPlTYHpfBUT3D3PaPR/HeNiI9W6R7FvlkcGhNyAoP+caA==",
"dev": true,
"requires": {
"@typescript-eslint/types": "7.8.0",
"eslint-visitor-keys": "^3.4.3"
}
},
"@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@ -3264,6 +3854,12 @@
"is-string": "^1.0.7"
}
},
"array-union": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
"integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
"dev": true
},
"array.prototype.filter": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz",
@ -3533,6 +4129,15 @@
"object-keys": "^1.1.1"
}
},
"dir-glob": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
"integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
"dev": true,
"requires": {
"path-type": "^4.0.0"
}
},
"doctrine": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -3871,6 +4476,30 @@
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"dev": true
},
"fast-glob": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
"dev": true,
"requires": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.4"
},
"dependencies": {
"glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"requires": {
"is-glob": "^4.0.1"
}
}
}
},
"fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
@ -4053,6 +4682,20 @@
"define-properties": "^1.1.3"
}
},
"globby": {
"version": "11.1.0",
"resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
"integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
"dev": true,
"requires": {
"array-union": "^2.1.0",
"dir-glob": "^3.0.1",
"fast-glob": "^3.2.9",
"ignore": "^5.2.0",
"merge2": "^1.4.1",
"slash": "^3.0.0"
}
},
"gopd": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz",
@ -4462,6 +5105,31 @@
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
"dev": true
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dev": true,
"requires": {
"yallist": "^4.0.0"
}
},
"merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true
},
"micromatch": {
"version": "4.0.5",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
"integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
"dev": true,
"requires": {
"braces": "^3.0.2",
"picomatch": "^2.3.1"
}
},
"mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@ -4729,6 +5397,12 @@
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"dev": true
},
"path-type": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
"integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
"dev": true
},
"picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
@ -4991,6 +5665,12 @@
}
}
},
"slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"dev": true
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@ -5102,6 +5782,13 @@
"nopt": "~1.0.10"
}
},
"ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
"integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
"dev": true,
"requires": {}
},
"tsconfig-paths": {
"version": "3.15.0",
"resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
@ -5181,6 +5868,13 @@
"possible-typed-array-names": "^1.0.0"
}
},
"typescript": {
"version": "5.4.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
"integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
"dev": true,
"peer": true
},
"unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@ -5279,6 +5973,12 @@
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"dev": true
},
"yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@ -6,6 +6,8 @@
"license": "MIT",
"repository": "https://github.com/espruino/BangleApps",
"devDependencies": {
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"eslint": "^8.57.0",
"eslint-config-airbnb-base": "^15.0.0",
"eslint-plugin-import": "^2.29.1",
@ -13,8 +15,8 @@
"npm-watch": "^0.11.0"
},
"scripts": {
"lint-apps": "node bin/sync-lint-exemptions.mjs && eslint ./apps --ext .js",
"lint-modules": "eslint ./modules --ext .js",
"lint-apps": "node bin/sync-lint-exemptions.mjs && eslint ./apps",
"lint-modules": "eslint ./modules",
"test": "node bin/sanitycheck.js && npm run lint-apps && npm run lint-modules",
"update-local-apps": "./bin/create_apps_json.sh apps.local.json",
"local": "npm-watch & npx http-server -a localhost -c-1",