Merge branch 'master' of https://github.com/T0TProduction/BangleApps into T0TProduction-master

master
Gordon Williams 2022-02-28 13:09:44 +00:00
commit a375853e46
35 changed files with 787 additions and 172 deletions

34
.github/workflows/nodejs.yml vendored Normal file
View File

@ -0,0 +1,34 @@
name: Node CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
steps:
- name: Checkout repository and submodules
uses: actions/checkout@v2
with:
submodules: recursive
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: install testing dependencies
run: npm i
- name: test all apps and widgets
run: npm run test
- name: install typescript dependencies
working-directory: ./typescript
run: npm ci
- name: build types
working-directory: ./typescript
run: npm run build:types
- name: build all TS apps and widgets
working-directory: ./typescript
run: npm run build

View File

@ -1 +0,0 @@
node_modules/

View File

@ -1,25 +0,0 @@
# BangleRun
An app for running sessions. Displays info and logs your run for later viewing.
## Compilation
The app is written in Typescript, and needs to be transpiled in order to be
run on the BangleJS. The easiest way to perform this step is by using the
ubiquitous [NPM package manager](https://www.npmjs.com/get-npm).
After having installed NPM for your platform, checkout the `BangleApps` repo,
open a terminal, and navigate into the `apps/banglerun` folder. Then issue:
```
npm i
```
to install the project's build tools, and:
```
npm run build
```
To build the app. The last command will generate the `app.js` file, containing
the transpiled code for the BangleJS.

View File

@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.stopActivity = exports.startActivity = exports.clearActivity = void 0;
const display_1 = require("./display");
const log_1 = require("./log");
const state_1 = require("./state");
function startActivity(state) {
if (state.status === state_1.ActivityStatus.Stopped) {
(0, log_1.initLog)(state);
}
if (state.status === state_1.ActivityStatus.Running) {
state.status = state_1.ActivityStatus.Paused;
}
else {
state.status = state_1.ActivityStatus.Running;
}
(0, display_1.draw)(state);
}
exports.startActivity = startActivity;
function stopActivity(state) {
if (state.status === state_1.ActivityStatus.Paused) {
clearActivity(state);
}
if (state.status === state_1.ActivityStatus.Running) {
state.status = state_1.ActivityStatus.Paused;
}
else {
state.status = state_1.ActivityStatus.Stopped;
}
(0, display_1.draw)(state);
}
exports.stopActivity = stopActivity;
function clearActivity(state) {
state.duration = 0;
state.distance = 0;
state.speed = 0;
state.steps = 0;
state.cadence = 0;
}
exports.clearActivity = clearActivity;

View File

@ -1 +1,15 @@
!function(){"use strict";var t;!function(t){t.Stopped="STOP",t.Paused="PAUSE",t.Running="RUN"}(t||(t={}));const n={STOP:63488,PAUSE:65504,RUN:2016};function e(t,n,e){g.setColor(0),g.fillRect(n-60,e,n+60,e+30),g.setColor(65535),g.drawString(t,n,e)}function i(i){var s;g.setFontVector(30),g.setFontAlign(0,-1,0),e((i.distance/1e3).toFixed(2),60,55),e(function(t){const n=Math.round(t),e=Math.floor(n/3600),i=Math.floor(n/60)%60,s=n%60;return(e?e+":":"")+("0"+i).substr(-2)+":"+("0"+s).substr(-2)}(i.duration),172,55),e(function(t){if(t<.1667)return"__'__\"";const n=Math.round(1e3/t),e=Math.floor(n/60),i=n%60;return("0"+e).substr(-2)+"'"+("0"+i).substr(-2)+'"'}(i.speed),60,115),e(i.hr.toFixed(0),172,115),e(i.steps.toFixed(0),60,175),e(i.cadence.toFixed(0),172,175),g.setFont("6x8",2),g.setColor(i.gpsValid?2016:63488),g.fillRect(0,216,80,240),g.setColor(0),g.drawString("GPS",40,220),g.setColor(65535),g.fillRect(80,216,160,240),g.setColor(0),g.drawString(("0"+(s=new Date).getHours()).substr(-2)+":"+("0"+s.getMinutes()).substr(-2),120,220),g.setColor(n[i.status]),g.fillRect(160,216,230,240),g.setColor(0),g.drawString(i.status,200,220),g.setFont("6x8").setFontAlign(0,0,1).setColor(-1),i.status===t.Paused?g.drawString("START",236,60,1).drawString(" CLEAR ",236,180,1):i.status===t.Running?g.drawString(" PAUSE ",236,60,1).drawString(" PAUSE ",236,180,1):g.drawString("START",236,60,1).drawString(" ",236,180,1)}function s(t){g.clear(),g.setColor(50712),g.setFont("6x8",2),g.setFontAlign(0,-1,0),g.drawString("DIST (KM)",60,32),g.drawString("TIME",180,32),g.drawString("PACE",60,92),g.drawString("HEART",180,92),g.drawString("STEPS",60,152),g.drawString("CADENCE",180,152),i(t),Bangle.drawWidgets()}function a(n){n.status===t.Stopped&&function(t){const n=(new Date).toISOString().replace(/[-:]/g,""),e=`banglerun_${n.substr(2,6)}_${n.substr(9,6)}`;t.file=require("Storage").open(e,"w"),t.fileWritten=!1}(n),n.status===t.Running?n.status=t.Paused:n.status=t.Running,i(n)}const r={fix:NaN,lat:NaN,lon:NaN,alt:NaN,vel:NaN,dop:NaN,gpsValid:!1,x:NaN,y:NaN,z:NaN,t:NaN,timeSinceLog:0,hr:60,hrError:100,file:null,fileWritten:!1,drawing:!1,status:t.Stopped,duration:0,distance:0,speed:0,steps:0,cadence:0};var o;o=r,Bangle.on("GPS",n=>function(n,e){n.lat=e.lat,n.lon=e.lon,n.alt=e.alt,n.vel=e.speed/3.6,n.fix=e.fix,n.dop=e.hdop,n.gpsValid=n.fix>0,function(n){const e=Date.now();let i=(e-n.t)/1e3;if(isFinite(i)||(i=0),n.t=e,n.timeSinceLog+=i,n.status===t.Running&&(n.duration+=i),!n.gpsValid)return;const s=6371008.8+n.alt,a=n.lat*Math.PI/180,r=n.lon*Math.PI/180,o=s*Math.cos(a)*Math.cos(r),g=s*Math.cos(a)*Math.sin(r),d=s*Math.sin(a);if(!n.x)return n.x=o,n.y=g,void(n.z=d);const u=o-n.x,l=g-n.y,c=d-n.z,f=Math.sqrt(u*u+l*l+c*c);n.x=o,n.y=g,n.z=d,n.status===t.Running&&(n.distance+=f,n.speed=n.distance/n.duration||0,n.cadence=60*n.steps/n.duration||0)}(n),i(n),n.gpsValid&&n.status===t.Running&&n.timeSinceLog>5&&(n.timeSinceLog=0,function(t){t.fileWritten||(t.file.write(["timestamp","latitude","longitude","altitude","duration","distance","heartrate","steps"].join(",")+"\n"),t.fileWritten=!0),t.file.write([Date.now().toFixed(0),t.lat.toFixed(6),t.lon.toFixed(6),t.alt.toFixed(2),t.duration.toFixed(0),t.distance.toFixed(2),t.hr.toFixed(0),t.steps.toFixed(0)].join(",")+"\n")}(n))}(o,n)),Bangle.setGPSPower(1),function(t){Bangle.on("HRM",n=>function(t,n){if(0===n.confidence)return;const e=n.bpm-t.hr,i=Math.abs(e)+101-n.confidence,s=t.hrError/(t.hrError+i)||0;t.hr+=e*s,t.hrError+=(i-t.hrError)*s}(t,n)),Bangle.setHRMPower(1)}(r),function(n){Bangle.on("step",()=>function(n){n.status===t.Running&&(n.steps+=1)}(n))}(r),function(t){Bangle.loadWidgets(),Bangle.on("lcdPower",n=>{t.drawing=n,n&&s(t)}),s(t)}(r),setWatch(()=>a(r),BTN1,{repeat:!0,edge:"falling"}),setWatch(()=>function(n){n.status===t.Paused&&function(t){t.duration=0,t.distance=0,t.speed=0,t.steps=0,t.cadence=0}(n),n.status===t.Running?n.status=t.Paused:n.status=t.Stopped,i(n)}(r),BTN3,{repeat:!0,edge:"falling"})}();
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const activity_1 = require("./activity");
const display_1 = require("./display");
const gps_1 = require("./gps");
const hrm_1 = require("./hrm");
const state_1 = require("./state");
const step_1 = require("./step");
const appState = (0, state_1.initState)();
(0, gps_1.initGps)(appState);
(0, hrm_1.initHrm)(appState);
(0, step_1.initStep)(appState);
(0, display_1.initDisplay)(appState);
setWatch(() => (0, activity_1.startActivity)(appState), BTN1, { repeat: true, edge: 'falling' });
setWatch(() => (0, activity_1.stopActivity)(appState), BTN3, { repeat: true, edge: 'falling' });

106
apps/banglerun/display.js Normal file
View File

@ -0,0 +1,106 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initDisplay = exports.formatTime = exports.formatPace = exports.formatDistance = exports.formatClock = exports.drawValue = exports.drawBackground = exports.drawAll = exports.draw = void 0;
const state_1 = require("./state");
const STATUS_COLORS = {
'STOP': 0xF800,
'PAUSE': 0xFFE0,
'RUN': 0x07E0,
};
function initDisplay(state) {
Bangle.loadWidgets();
Bangle.on('lcdPower', (on) => {
state.drawing = on;
if (on) {
drawAll(state);
}
});
drawAll(state);
}
exports.initDisplay = initDisplay;
function drawBackground() {
g.clear();
g.setColor(0xC618);
g.setFont('6x8', 2);
g.setFontAlign(0, -1, 0);
g.drawString('DIST (KM)', 60, 32);
g.drawString('TIME', 172, 32);
g.drawString('PACE', 60, 92);
g.drawString('HEART', 172, 92);
g.drawString('STEPS', 60, 152);
g.drawString('CADENCE', 172, 152);
}
exports.drawBackground = drawBackground;
function drawValue(value, x, y) {
g.setColor(0x0000);
g.fillRect(x - 60, y, x + 60, y + 30);
g.setColor(0xFFFF);
g.drawString(value, x, y);
}
exports.drawValue = drawValue;
function draw(state) {
g.setFontVector(30);
g.setFontAlign(0, -1, 0);
drawValue(formatDistance(state.distance), 60, 55);
drawValue(formatTime(state.duration), 172, 55);
drawValue(formatPace(state.speed), 60, 115);
drawValue(state.hr.toFixed(0), 172, 115);
drawValue(state.steps.toFixed(0), 60, 175);
drawValue(state.cadence.toFixed(0), 172, 175);
g.setFont('6x8', 2);
g.setColor(state.gpsValid ? 0x07E0 : 0xF800);
g.fillRect(0, 216, 80, 240);
g.setColor(0x0000);
g.drawString('GPS', 40, 220);
g.setColor(0xFFFF);
g.fillRect(80, 216, 160, 240);
g.setColor(0x0000);
g.drawString(formatClock(new Date()), 120, 220);
g.setColor(STATUS_COLORS[state.status]);
g.fillRect(160, 216, 230, 240);
g.setColor(0x0000);
g.drawString(state.status, 200, 220);
g.setFont("6x8").setFontAlign(0, 0, 1).setColor(-1);
if (state.status === state_1.ActivityStatus.Paused) {
g.drawString("START", 236, 60, 1).drawString(" CLEAR ", 236, 180, 1);
}
else if (state.status === state_1.ActivityStatus.Running) {
g.drawString(" PAUSE ", 236, 60, 1).drawString(" PAUSE ", 236, 180, 1);
}
else {
g.drawString("START", 236, 60, 1).drawString(" ", 236, 180, 1);
}
}
exports.draw = draw;
function drawAll(state) {
drawBackground();
draw(state);
Bangle.drawWidgets();
}
exports.drawAll = drawAll;
function formatClock(date) {
return ('0' + date.getHours()).substr(-2) + ':' + ('0' + date.getMinutes()).substr(-2);
}
exports.formatClock = formatClock;
function formatDistance(meters) {
return (meters / 1000).toFixed(2);
}
exports.formatDistance = formatDistance;
function formatPace(speed) {
if (speed < 0.1667) {
return `__'__"`;
}
const pace = Math.round(1000 / speed);
const min = Math.floor(pace / 60);
const sec = pace % 60;
return ('0' + min).substr(-2) + `'` + ('0' + sec).substr(-2) + `"`;
}
exports.formatPace = formatPace;
function formatTime(time) {
const seconds = Math.round(time);
const hrs = Math.floor(seconds / 3600);
const min = Math.floor(seconds / 60) % 60;
const sec = seconds % 60;
return (hrs ? hrs + ':' : '') + ('0' + min).substr(-2) + `:` + ('0' + sec).substr(-2);
}
exports.formatTime = formatTime;

71
apps/banglerun/gps.js Normal file
View File

@ -0,0 +1,71 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateGps = exports.readGps = exports.initGps = void 0;
const display_1 = require("./display");
const log_1 = require("./log");
const state_1 = require("./state");
const EARTH_RADIUS = 6371008.8;
function initGps(state) {
Bangle.on('GPS', (gps) => readGps(state, gps));
Bangle.setGPSPower(1);
}
exports.initGps = initGps;
function readGps(state, gps) {
state.lat = gps.lat;
state.lon = gps.lon;
state.alt = gps.alt;
state.vel = gps.speed / 3.6;
state.fix = gps.fix;
state.dop = gps.hdop;
state.gpsValid = state.fix > 0;
updateGps(state);
(0, display_1.draw)(state);
/* Only log GPS data every 5 secs if we
have a fix and we're running. */
if (state.gpsValid &&
state.status === state_1.ActivityStatus.Running &&
state.timeSinceLog > 5) {
state.timeSinceLog = 0;
(0, log_1.updateLog)(state);
}
}
exports.readGps = readGps;
function updateGps(state) {
const t = Date.now();
let dt = (t - state.t) / 1000;
if (!isFinite(dt))
dt = 0;
state.t = t;
state.timeSinceLog += dt;
if (state.status === state_1.ActivityStatus.Running) {
state.duration += dt;
}
if (!state.gpsValid) {
return;
}
const r = EARTH_RADIUS + state.alt;
const lat = state.lat * Math.PI / 180;
const lon = state.lon * Math.PI / 180;
const x = r * Math.cos(lat) * Math.cos(lon);
const y = r * Math.cos(lat) * Math.sin(lon);
const z = r * Math.sin(lat);
if (!state.x) {
state.x = x;
state.y = y;
state.z = z;
return;
}
const dx = x - state.x;
const dy = y - state.y;
const dz = z - state.z;
const dpMag = Math.sqrt(dx * dx + dy * dy + dz * dz);
state.x = x;
state.y = y;
state.z = z;
if (state.status === state_1.ActivityStatus.Running) {
state.distance += dpMag;
state.speed = (state.distance / state.duration) || 0;
state.cadence = (60 * state.steps / state.duration) || 0;
}
}
exports.updateGps = updateGps;

19
apps/banglerun/hrm.js Normal file
View File

@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateHrm = exports.initHrm = void 0;
function initHrm(state) {
Bangle.on('HRM', (hrm) => updateHrm(state, hrm));
Bangle.setHRMPower(1);
}
exports.initHrm = initHrm;
function updateHrm(state, hrm) {
if (hrm.confidence === 0) {
return;
}
const dHr = hrm.bpm - state.hr;
const hrError = Math.abs(dHr) + 101 - hrm.confidence;
const hrGain = (state.hrError / (state.hrError + hrError)) || 0;
state.hr += dHr * hrGain;
state.hrError += (hrError - state.hrError) * hrGain;
}
exports.updateHrm = updateHrm;

View File

@ -1,6 +0,0 @@
{
"spec_dir": "test",
"spec_files": [
"**/*.spec.ts"
]
}

37
apps/banglerun/log.js Normal file
View File

@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateLog = exports.initLog = void 0;
function initLog(state) {
const datetime = new Date().toISOString().replace(/[-:]/g, '');
const date = datetime.substr(2, 6);
const time = datetime.substr(9, 6);
const filename = `banglerun_${date}_${time}`;
return Object.assign(Object.assign({}, state), { file: require('Storage').open(filename, 'w'), fileWritten: false });
}
exports.initLog = initLog;
function updateLog(state) {
if (!state.fileWritten) {
state.file.write([
'timestamp',
'latitude',
'longitude',
'altitude',
'duration',
'distance',
'heartrate',
'steps',
].join(',') + '\n');
state.fileWritten = true;
}
state.file.write([
Date.now().toFixed(0),
state.lat.toFixed(6),
state.lon.toFixed(6),
state.alt.toFixed(2),
state.duration.toFixed(0),
state.distance.toFixed(2),
state.hr.toFixed(0),
state.steps.toFixed(0),
].join(',') + '\n');
}
exports.updateLog = updateLog;

47
apps/banglerun/log.ts Normal file
View File

@ -0,0 +1,47 @@
import { AppState, AppStateWithLog } from './state';
declare var require: any;
function initLog(state: AppState): AppStateWithLog {
const datetime = new Date().toISOString().replace(/[-:]/g, '');
const date = datetime.substr(2, 6);
const time = datetime.substr(9, 6);
const filename = `banglerun_${date}_${time}`;
return {
...state,
file: require('Storage').open(filename, 'w'),
fileWritten: false,
} as AppStateWithLog;
}
function updateLog(state: AppStateWithLog): void {
if (!state.fileWritten) {
state.file.write(
[
'timestamp',
'latitude',
'longitude',
'altitude',
'duration',
'distance',
'heartrate',
'steps',
].join(',') + '\n'
);
state.fileWritten = true;
}
state.file.write(
[
Date.now().toFixed(0),
state.lat.toFixed(6),
state.lon.toFixed(6),
state.alt.toFixed(2),
state.duration.toFixed(0),
state.distance.toFixed(2),
state.hr.toFixed(0),
state.steps.toFixed(0),
].join(',') + '\n'
);
}
export { initLog, updateLog };

View File

@ -1,27 +0,0 @@
{
"name": "banglerun",
"version": "0.5.0",
"description": "Bangle.js app for running sessions",
"main": "app.js",
"types": "app.d.ts",
"scripts": {
"build": "rollup -c",
"test": "ts-node -P tsconfig.spec.json node_modules/jasmine/bin/jasmine --config=jasmine.json"
},
"author": {
"name": "Stefano Baldan",
"email": "singintime@gmail.com"
},
"license": "ISC",
"devDependencies": {
"@rollup/plugin-typescript": "^4.1.1",
"@types/jasmine": "^3.5.10",
"jasmine": "^3.5.0",
"rollup": "^2.10.2",
"rollup-plugin-terser": "^5.3.0",
"terser": "^4.7.0",
"ts-node": "^8.10.2",
"tslib": "^2.0.0",
"typescript": "^3.9.2"
}
}

View File

@ -1,15 +0,0 @@
import typescript from '@rollup/plugin-typescript';
import { terser } from 'rollup-plugin-terser';
export default {
input: './src/app.ts',
output: {
dir: '.',
format: 'iife',
name: 'banglerun'
},
plugins: [
typescript(),
terser(),
]
};

View File

@ -1,40 +0,0 @@
import { AppState } from './state';
declare var require: any;
function initLog(state: AppState): void {
const datetime = new Date().toISOString().replace(/[-:]/g, '');
const date = datetime.substr(2, 6);
const time = datetime.substr(9, 6);
const filename = `banglerun_${date}_${time}`;
state.file = require('Storage').open(filename, 'w');
state.fileWritten = false;
}
function updateLog(state: AppState): void {
if (!state.fileWritten) {
state.file.write([
'timestamp',
'latitude',
'longitude',
'altitude',
'duration',
'distance',
'heartrate',
'steps',
].join(',') + '\n');
state.fileWritten = true;
}
state.file.write([
Date.now().toFixed(0),
state.lat.toFixed(6),
state.lon.toFixed(6),
state.alt.toFixed(2),
state.duration.toFixed(0),
state.distance.toFixed(2),
state.hr.toFixed(0),
state.steps.toFixed(0),
].join(',') + '\n');
}
export { initLog, updateLog };

37
apps/banglerun/state.js Normal file
View File

@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.initState = exports.ActivityStatus = void 0;
var ActivityStatus;
(function (ActivityStatus) {
ActivityStatus["Stopped"] = "STOP";
ActivityStatus["Paused"] = "PAUSE";
ActivityStatus["Running"] = "RUN";
})(ActivityStatus || (ActivityStatus = {}));
exports.ActivityStatus = ActivityStatus;
function initState() {
return {
fix: NaN,
lat: NaN,
lon: NaN,
alt: NaN,
vel: NaN,
dop: NaN,
gpsValid: false,
x: NaN,
y: NaN,
z: NaN,
t: NaN,
timeSinceLog: 0,
hr: 60,
hrError: 100,
fileWritten: false,
drawing: false,
status: ActivityStatus.Stopped,
duration: 0,
distance: 0,
speed: 0,
steps: 0,
cadence: 0,
};
}
exports.initState = initState;

View File

@ -4,7 +4,7 @@ enum ActivityStatus {
Running = 'RUN',
}
interface AppState {
interface BasicAppState {
// GPS NMEA data
fix: number;
lat: number;
@ -28,14 +28,12 @@ interface AppState {
hrError: number,
// Logger data
file: File;
fileWritten: boolean;
// Drawing data
drawing: boolean;
// Activity data
status: ActivityStatus;
duration: number;
distance: number;
speed: number;
@ -43,6 +41,17 @@ interface AppState {
cadence: number;
}
interface AppStateWithoutLog extends BasicAppState {
status: 'STOP';
}
interface AppStateWithLog extends BasicAppState {
file: File;
status: ActivityStatus;
}
type AppState = AppStateWithLog | AppStateWithoutLog;
interface File {
read: Function;
write: Function;
@ -68,7 +77,6 @@ function initState(): AppState {
hr: 60,
hrError: 100,
file: null,
fileWritten: false,
drawing: false,
@ -82,4 +90,4 @@ function initState(): AppState {
}
}
export { ActivityStatus, AppState, File, initState };
export { ActivityStatus, AppState, AppStateWithLog, File, initState };

14
apps/banglerun/step.js Normal file
View File

@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateStep = exports.initStep = void 0;
const state_1 = require("./state");
function initStep(state) {
Bangle.on('step', () => updateStep(state));
}
exports.initStep = initStep;
function updateStep(state) {
if (state.status === state_1.ActivityStatus.Running) {
state.steps += 1;
}
}
exports.updateStep = updateStep;

View File

@ -1,10 +0,0 @@
{
"compilerOptions": {
"module": "es2015",
"noImplicitAny": true,
"target": "es2015"
},
"include": [
"src"
]
}

View File

@ -1,10 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"target": "es2015"
},
"include": [
"test"
]
}

View File

@ -1 +1,2 @@
0.01: First release.
0.02: No functional changes, just moved codebase to Typescript.

View File

@ -2,7 +2,7 @@
"name": "Charging Status",
"shortName":"ChargingStatus",
"icon": "widget.png",
"version":"0.01",
"version":"0.02",
"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

@ -1,31 +1,33 @@
(() => {
const icon = require("heatshrink").decompress(atob("ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA"));
const iconWidth = 18;
function draw() {
g.reset();
if (Bangle.isCharging()) {
g.setColor("#FD0");
g.drawImage(icon, this.x + 1, this.y + 1, {
scale: 0.6875
});
}
}
WIDGETS.chargingStatus = {
area: 'tr',
width: Bangle.isCharging() ? iconWidth : 0,
draw: draw,
};
Bangle.on('charging', (charging) => {
if (charging) {
Bangle.buzz();
WIDGETS.chargingStatus.width = iconWidth;
} else {
WIDGETS.chargingStatus.width = 0;
}
Bangle.drawWidgets(); // re-layout widgets
g.flip();
});
})();
"use strict";
(() => {
const icon = require('heatshrink').decompress(atob('ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA'));
const iconWidth = 18;
function draw() {
g.reset();
if (Bangle.isCharging()) {
g.setColor('#FD0');
g.drawImage(icon, this.x + 1, this.y + 1, {
scale: 0.6875,
});
}
}
WIDGETS.chargingStatus = {
area: 'tr',
width: Bangle.isCharging() ? iconWidth : 0,
draw: draw,
};
Bangle.on('charging', (charging) => {
const widget = WIDGETS.chargingStatus;
if (widget) {
if (charging) {
Bangle.buzz();
widget.width = iconWidth;
}
else {
widget.width = 0;
}
Bangle.drawWidgets(); // re-layout widgets
g.flip();
}
});
})();

View File

@ -0,0 +1,38 @@
(() => {
const icon = require('heatshrink').decompress(
atob(
'ikggMAiEAgYIBmEAg4EB+EAh0AgPggEeCAIEBnwQBAgP+gEP//x///j//8f//k///H//4BYOP/4lBv4bDvwEB4EAvAEBwEAuA7DCAI7BgAQBhEAA'
)
);
const iconWidth = 18;
function draw(this: { x: number; y: number }) {
g.reset();
if (Bangle.isCharging()) {
g.setColor('#FD0');
g.drawImage(icon, this.x + 1, this.y + 1, {
scale: 0.6875,
});
}
}
WIDGETS.chargingStatus = {
area: 'tr',
width: Bangle.isCharging() ? iconWidth : 0,
draw: draw,
};
Bangle.on('charging', (charging) => {
const widget = WIDGETS.chargingStatus;
if (widget) {
if (charging) {
Bangle.buzz();
widget.width = iconWidth;
} else {
widget.width = 0;
}
Bangle.drawWidgets(); // re-layout widgets
g.flip();
}
});
})();

2
typescript/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
./node_modules
!package-lock.json

29
typescript/README.md Normal file
View File

@ -0,0 +1,29 @@
# BangleTS
A generic project setup for compiling apps from Typescript to Bangle.js ready, readable Javascript.
It includes types for _some_ of the modules and globals that are exposed for apps to use.
The goal is to have types for everything, but that will take some time. Feel free to help out by contributing!
## Using the types
All currently typed modules can be found in `/typescript/types.globals.d.ts`.
The typing is an ongoing process. If anything is still missing, you can add it! It will automatically be available in your TS files.
## Compilation
Install [npm](https://www.npmjs.com/get-npm) and node.js if you haven't already. We recommend using a version manager like nvm, which is also referenced in the linked documentation.
Make sure you are using node version 16 by running `nvm use 16` and npm version ^8 by running `npm -v`. If the latter version is incorrect, run `npm i -g npm@^8`.
After having installed npm for your platform, open a terminal, and navigate into the `/typescript` folder. Then run:
```
npm ci
```
to install the project's build tools, and:
```
npm run build
```
To build all Typescript apps and widgets. The last command will generate the `app.js` files containing the transpiled code for the BangleJS.

36
typescript/package-lock.json generated Normal file
View File

@ -0,0 +1,36 @@
{
"name": "Bangle.ts",
"version": "0.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "Bangle.ts",
"version": "0.0.1",
"devDependencies": {
"typescript": "4.5.2"
}
},
"node_modules/typescript": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz",
"integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
}
}
},
"dependencies": {
"typescript": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.2.tgz",
"integrity": "sha512-5BlMof9H1yGt0P8/WF+wPNw6GfctgGjXp5hkblpyT+8rkASSmkUKMXrxR0Xg8ThVCi/JnHQiKXeBaEwCeQwMFw==",
"dev": true
}
}
}

13
typescript/package.json Normal file
View File

@ -0,0 +1,13 @@
{
"name": "Bangle.ts",
"description": "Bangle.js Typescript Project Setup and Types",
"author": "Sebastian Di Luzio <sebastian@diluz.io> (https://diluz.io)",
"version": "0.0.1",
"devDependencies": {
"typescript": "4.5.2"
},
"scripts": {
"build": "tsc",
"build:types": "tsc ./types/globals.d.ts"
}
}

17
typescript/tsconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"target": "es6",
"allowUnreachableCode": false,
"allowUnusedLabels": false,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noImplicitThis": true,
"noUncheckedIndexedAccess": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"strict": true
},
"include": ["../apps/**/*", "./**/*"],
}

184
typescript/types/globals.d.ts vendored Normal file
View File

@ -0,0 +1,184 @@
// TODO all of these globals (copied from eslintrc) need to be typed at some point
/* The typing status is listed on the left of the attribute, e.g.:
status "Attribute"
// Methods and Fields at https://banglejs.com/reference
"Array": "readonly",
"ArrayBuffer": "readonly",
"ArrayBufferView": "readonly",
started "Bangle": "readonly",
"BluetoothDevice": "readonly",
"BluetoothRemoteGATTCharacteristic": "readonly",
"BluetoothRemoteGATTServer": "readonly",
"BluetoothRemoteGATTService": "readonly",
"Boolean": "readonly",
"console": "readonly",
"DataView": "readonly",
"Date": "readonly",
"E": "readonly",
"Error": "readonly",
"Flash": "readonly",
"Float32Array": "readonly",
"Float64Array": "readonly",
"fs": "readonly",
"Function": "readonly",
started "Graphics": "readonly",
done "heatshrink": "readonly",
"I2C": "readonly",
"Int16Array": "readonly",
"Int32Array": "readonly",
"Int8Array": "readonly",
"InternalError": "readonly",
"JSON": "readonly",
"Math": "readonly",
"Modules": "readonly",
"NRF": "readonly",
"Number": "readonly",
"Object": "readonly",
"OneWire": "readonly",
"Pin": "readonly",
"process": "readonly",
"Promise": "readonly",
"ReferenceError": "readonly",
"RegExp": "readonly",
"Serial": "readonly",
"SPI": "readonly",
"Storage": "readonly",
"StorageFile": "readonly",
"String": "readonly",
"SyntaxError": "readonly",
"tensorflow": "readonly",
"TFMicroInterpreter": "readonly",
"TypeError": "readonly",
"Uint16Array": "readonly",
"Uint24Array": "readonly",
"Uint32Array": "readonly",
"Uint8Array": "readonly",
"Uint8ClampedArray": "readonly",
"Waveform": "readonly",
// Methods and Fields at https://banglejs.com/reference
"analogRead": "readonly",
"analogWrite": "readonly",
"arguments": "readonly",
"atob": "readonly",
"Bluetooth": "readonly",
"BTN": "readonly",
"BTN1": "readonly",
"BTN2": "readonly",
"BTN3": "readonly",
"BTN4": "readonly",
"BTN5": "readonly",
"btoa": "readonly",
"changeInterval": "readonly",
"clearInterval": "readonly",
"clearTimeout": "readonly",
"clearWatch": "readonly",
"decodeURIComponent": "readonly",
"digitalPulse": "readonly",
"digitalRead": "readonly",
"digitalWrite": "readonly",
"dump": "readonly",
"echo": "readonly",
"edit": "readonly",
"encodeURIComponent": "readonly",
"eval": "readonly",
"getPinMode": "readonly",
"getSerial": "readonly",
"getTime": "readonly",
"global": "readonly",
"HIGH": "readonly",
"I2C1": "readonly",
"Infinity": "readonly",
"isFinite": "readonly",
"isNaN": "readonly",
"LED": "readonly",
"LED1": "readonly",
"LED2": "readonly",
"load": "readonly",
"LoopbackA": "readonly",
"LoopbackB": "readonly",
"LOW": "readonly",
"NaN": "readonly",
"parseFloat": "readonly",
"parseInt": "readonly",
"peek16": "readonly",
"peek32": "readonly",
"peek8": "readonly",
"pinMode": "readonly",
"poke16": "readonly",
"poke32": "readonly",
"poke8": "readonly",
"print": "readonly",
started "require": "readonly",
"reset": "readonly",
"save": "readonly",
"Serial1": "readonly",
"setBusyIndicator": "readonly",
"setInterval": "readonly",
"setSleepIndicator": "readonly",
"setTime": "readonly",
"setTimeout": "readonly",
"setWatch": "readonly",
"shiftOut": "readonly",
"SPI1": "readonly",
"Terminal": "readonly",
"trace": "readonly",
"VIBRATE": "readonly",
// Aliases and not defined at https://banglejs.com/reference
done "g": "readonly",
done "WIDGETS": "readonly"
*/
// ambient JS definitions
declare const require: ((module: 'heatshrink') => {
decompress: (compressedString: string) => string;
}) & // TODO add more
((module: 'otherString') => {});
// ambient bangle.js definitions
declare const Bangle: {
// functions
buzz: () => void;
drawWidgets: () => void;
isCharging: () => boolean;
// events
on(event: 'charging', listener: (charging: boolean) => void): void;
// TODO add more
};
declare type Image = {
width: number;
height: number;
bpp?: number;
buffer: ArrayBuffer | string;
transparent?: number;
palette?: Uint16Array;
};
declare type GraphicsApi = {
reset: () => void;
flip: () => void;
setColor: (color: string) => void; // TODO we can most likely type color more usefully than this
drawImage: (
image: string | Image | ArrayBuffer,
xOffset: number,
yOffset: number,
options?: {
rotate?: number;
scale?: number;
}
) => void;
// TODO add more
};
declare const Graphics: GraphicsApi;
declare const g: GraphicsApi;
declare type Widget = {
area: 'tr' | 'tl';
width: number;
draw: (this: { x: number; y: number }) => void;
};
declare const WIDGETS: { [key: string]: Widget };