-
https://gitlab.com/notEvil/cyclometer
Its not done, but stable enough to be used :)
There are a few parts that might be of particular interest:
-
Espruino should continue working just fine after an out of memory
error - and actually the Linux build does have a 'memtest' where it'll
just run the tests time after time, slowly decreasing memory so that
it can see if anything breaks badly when out of memory happens at
different times.This is very reassuring :) Thanks for the guidance!
-
Sorry for the confusion, i meant "out of memory", not out of array bounds. I hope the following is better.
In
_jswrap_interface_setTimeoutOrInterval
,timerPtr
is zero if there isn't enough memory. This might not have immediate consequences if all future ops align well (likejsvObjectSetChildAndUnLock
). OritemIndex
may be zero despite a new timer. The consequences in this particular case may be negligible, so it may be just a bad example.Regarding
jsvObjectSetChildAndUnLock
: ifjsvObjectSetChildVar
returns zero due to "out of memory", it won't unlockchild
(https://github.com/espruino/Espruino/blob/7277a8dc9377e09f9a6837ff754e3dd8dd761563/src/jsvar.c#L3005). Compare withjsvArrayPushAndUnLock
wherevalue
is unlocked regardless (https://github.com/espruino/Espruino/blob/7277a8dc9377e09f9a6837ff754e3dd8dd761563/src/jsvar.c#L3298).The main question is: care about "out of memory" (e.g.
if (var) { ...; jsvUnLock(var); }
all the way) because there is a sane future for the system after the incident, or don't and assume nothing bad will happen. Both viable imo. -
-
Hi,
I was wondering what the recommended approach to memory overflow is. Most of
jsvar.c
check for overflow and behave accordingly, while other code assume no overflow, e.g._jswrap_interface_setTimeoutOrInterval
. Then there isjsvObjectSetChildAndUnLock
which doesn't unlock the child in case of overflow, while others likejsvArrayPushAndUnLock
do.I tried to use the following defines for perfect overflow coverage in my library
﹟define INIT(n) JsVar* _lock_array[n]; int _lock_index = 0
﹟define END(i) _end(i, _lock_array, _lock_index)
﹟define NEW(a) ({ JsVar* var = a; if (!var) return END(0); _lock_array[_lock_index++] = var; var; })
﹟define TMP(a) ({ JsVar* var = a; if (!var) return END(0); var; })
﹟define GRD(a) if (!(a)) return END(0);
JsVar* _end(int end_index, JsVar** array, int index) {
while (index != end_index) jsvUnLock(array[--index]);
return 0;
}
but I'm uncertain if its worth the trouble. What if I won't check for overflow, like
_jswrap_interface_setTimeoutOrInterval
, would it lead to unexpected behavior in case of overflow instead of noisy "segfault"? -
I found the issue, and its not Espruino/Bangle. The callback isn't called twice (should have count it earlier) but the returned message on Nordic UART is processed twice on the remote side (Arch Linux, Intel 7260, Bluez 5.66). But only if I didn't remove the device using
bluetoothctl
after a reset.Sorry for the distraction!
For future reference: when the custom characteristic is added, and my Bluez already knows about the Nordic UART from previous discover, it would immediately disconnect after connect. So I'm forced to remove and rediscover. If, however, it knows about the custom characteristic and the custom characteristic is added during a reset, it wouldn't disconnect after connect but process incoming messages twice. Or at least thats what I see and I'm able to reproduce. Remove and rediscover solves the issue for me.
-
Hi,
there is
NRF.setServices
and I have no trouble setting up a custom service and characteristics when executing code in the IDE, and use it thereafter. Then I tried to put this in a.boot.js
file and an issue pops up: the writable characteristic callsonWrite
twice each time a message arrives.So I thought its because
setServices
is called multiple times (e.g. after long-press BTN3), but even the following doesn't solve it:setTimeout(() => {
var cc = require('concom');
cc.t = (0|cc.t) + 1;
function has_service(sid, cid) {
var svc = {};
svc[cid] = {};
var svcs = {};
svcs[sid] = svc;
try {
NRF.updateServices(svcs);
} catch (e) {
return false;
}
return true;
}
if (has_service(0xf011, 0xf012)) return;
NRF.setServices({
0xf011: {
0xf012: {
maxLen: 53,
writable: true,
onWrite: (e) => cc.read(new Uint8Array(e.data))
},
0xf013: {
maxLen: 53,
notify: true
}
}
});
}, 3 * 1000);
When I run its parts individually, everything works (e.g. first
has_service
returns false, thensetServices
, thenhas_service
returns true) and I get oneonWrite
per message. When I do a hard reset with this in the boot file and write to the characteristic,onWrite
gets called twice.It would be great if anyone could shed some light on this :)
edit:
.t
is 1 -
-
Thanks, good to know about the reset. Was using a custom build to try things, so reflashing wasn't wasted effort at least :)
Regarding initialized or not, I tried a few variations and it seems that
volatile
is the culprit. Without it, the counter runs to at least 2000 whether or not it was initialized during declaration. Just initializing didn't fix it. The reason why I used volatile was because I read it here http://www.espruino.com/InlineC.I'll keep the optimization in mind :)
-
Hi,
I tried
E.compiledC
on my Bangle 1 2v16 and see weird things happening. After "Resetting without loading any code", the followingvar c = E.compiledC(`
// void init()
// int f()
volatile int a;
void init() {
a = 0;
}
int f() {
a++;
return a;
}
`);
c.init();
setInterval(() => print(c.f()), 100);
gives me
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
1644167202
1644167203
1644167204
1644167205
1644167206
39
40
41
42
43
44
45
46
47
48
268435505
50
51
52
53
54
55
1811939384
57
58
1761607739
1761607740
1761607741
1761607742
1761607743
1761607744
1761607745
1761607746
Prior to that I ran code which wrote to RAM locations where it shouldn't have. So I rebooted and reflashed the firmware hoping it would reset the device and resolve the issues I see. Is there anything I can do? Its a bit scary to run code that might modify any RAM location at any point.
Thanks in advance!
-
-
edit: OMG, there is actually a critical bug in the code (
buf[bufi] = 0;
whenbufi == buf.length
). If this turns out to be the source, please apologize the distraction!Unfortunately I can't reliably reproduce the issue. It happened thrice in a row (2 outdoors, 1 indoors), then once at BT disconnect (Android
blessed.BluetoothPeripheral.closeConnection
; saw the bootloader screen) followed by 2 times without issues. My guess now is that the BT device in my phone is buggy and somehow triggers an extra hard reset on connection loss/disconnect.I will therefore rewrite the app to recover in case of a hard reset and look for any pattern in the data.
Code:
var ACC = 1;
var MAG = 2;
var GPS = 3;
var TIM = 4;
var bufs = [];
var buf = new ArrayBuffer(1000);
var bufi = 0;
function get_dv(len) {
if (buf.length < (bufi + len)) {
buf[bufi] = 0;
bufs.push(buf);
buf = new ArrayBuffer(1000);
bufi = 0;
}
var dv = new DataView(buf, bufi);
bufi += len;
return dv;
}
function send_buf() {
if (bufs.length) {
Bluetooth.write(btoa(bufs[0]));
}
Bluetooth.write('\n');
}
function remove_buf() {
bufs.shift();
}
function on_accel(a) {
var v = get_dv(7);
v.setUint8(0, ACC);
v.setInt16(1, a.x * 8192);
v.setInt16(3, a.y * 8192);
v.setInt16(5, a.z * 8192);
}
function on_mag(m) {
var v = get_dv(7);
v.setUint8(0, MAG);
v.setInt16(1, m.x);
v.setInt16(3, m.y);
v.setInt16(5, m.z);
}
function on_gps(g) {
if (g.fix == 0) return;
var v = get_dv(26);
v.setUint8(0, GPS);
v.setInt32(1, g.lat * 11930464);
v.setInt32(5, g.lon * 11930464);
v.setUint16(9, clamp(g.alt * 16, 0, 65535));
v.setFloat64(11, g.time.getTime());
v.setUint8(19, g.fix);
v.setUint8(20, clamp(g.hdop * 12, 0, 255));
v.setUint8(21, g.satellites);
v.setUint16(22, g.course * 182);
v.setUint16(24, clamp(g.speed * 327, 0, 65535));
}
var clamp = (a, mi, ma) => a < mi ? mi : (ma < a ? ma : a);
function log(s) {
Terminal.println(s);
}
g.clear();
Bangle.loadWidgets();
Bangle.drawWidgets();
log('# Cyclometer');
log('press top button to start');
Bangle.setGPSPower(true, 'cyc');
var satc = 0;
function ready(g) {
if (g.fix) {
Bangle.removeListener('GPS', ready);
Bangle.buzz(500);
log('GPS ready');
} else if (g.satellites != satc) {
satc = g.satellites;
log('GPS satellites: ' + satc);
}
}
Bangle.on('GPS', ready);
setWatch(() => {
log('starting ...');
Bangle.setLCDTimeout(0);
Bangle.setOptions({btnLoadTimeout: 0});
Bangle.setCompassPower(true, 'cyc');
Bangle.on('accel', on_accel);
Bangle.on('mag', on_mag);
Bangle.on('GPS', on_gps);
var tim_id = setInterval(() => {
var v = get_dv(9);
v.setUint8(0, TIM);
v.setFloat64(1, getTime());
}, 60 * 1000);
var log_id = setInterval(() => {
var m = process.memory();
var p = Math.floor((m.usage / m.total) * 100);
log('queue: ' + bufs.length + ' memory: ' + p + '%');
if (67 <= p) {
Bangle.buzz(500);
setTimeout(() => Bangle.buzz(500), 1000);
}
}, 5 * 1000);
log('started');
log('hold top button for 3 seconds to stop');
var cnt;
var cnt_id;
var btn_id = setWatch((e) => {
if (e.state) {
log('stop in 3');
cnt = 3;
cnt_id = setTimeout(stop, 1000);
Bangle.buzz(300);
} else if (cnt_id) {
clearTimeout(cnt_id);
cnt_id = undefined;
}
}, BTN1, {repeat: true, edge: 'both'});
function stop() {
cnt--;
if (cnt == 0) {
log('stopping ...');
Bangle.setLCDTimeout(10);
Bangle.setOptions({btnLoadTimeout: 1500});
Bangle.setGPSPower(false, 'cyc');
Bangle.setCompassPower(false, 'cyc');
var _ = Bangle.removeListener.bind(Bangle);
_('accel', on_accel);
_('mag', on_mag);
_('GPS', on_gps);
clearInterval(tim_id);
clearInterval(log_id);
clearWatch(btn_id);
log('stopped');
} else {
log('stop in ' + cnt);
cnt_id = setTimeout(stop, 1000);
Bangle.buzz(300);
}
}
}, BTN1);
-
Hi,
I wrote an app which communicates with an Android app and transfers data continuously. Everything works fine until the watch seems to (soft) reset for seamingly no reason. What I did so far:
- use 2v16 release build
- Android app drives communication (no
Bluetooth.write
unless requested) - continuously log memory consumption using
Terminal.println
and buzz when exceeding ~2/3 - disabled home button (set option
btnLoadTimeout
to 0) and implemented a 3s count down with buzz when holding a button to reenable the home button
Because it takes a couple of minutes to appear and the timespan varies a lot (observed 7m and 12m from sent data), I didn't "see" what happens on screen so far.
I would appreciate any information on what I should "log", on JS or C level, to get a grip on this.
Thanks in advance!
- use 2v16 release build
-
-
Thank you for the details, I feared as much. The use case is:
Every 30s check if the watch is in power save mode. Remember this to keep track of recent activity and act accordingly.
Related issues:
https://github.com/espruino/BangleApps/issues/1923
https://github.com/espruino/Espruino/issues/2308For this particular use case (any activity in the last 10m), resetting the history is not a problem because BTN3 reset is activity and clearing the history does lead to the same behavior (assuming activity by default). However, this is usually an exception rather than the norm.
Regarding
E.on('kill', ...
: power off proofness in this case is not desirable ;)
Thanks again for the confirmation and detailed response! -
Hi,
in most apps, long pressing BTN3 gets you back to default UI. It performs a lot more than just unloading the current app and loading the clock app. It even executes
*.boot.js
files. How would you write a "background" app which actually initializes just once and provides some state for the duration of power on? -
Hi,
I wonder why the amount of usable memory is relatively small. Bangle.js 1 has 2584 blocks with 13 bytes each, in total 33.592 kB. This means the runtime requires ~30kB. From https://www.espruino.com/Performance:
Espruino is designed to run on devices with very small amounts of RAM available (under 8kB) ...
Obviously there is lots of code for Bangle.js in
libs/banglejs/
and I guess elsewhere too, but it would be interesting to know what is taking up that space. -
Hi,
I was using https://banglejs.com/apps/?id=health for a day until I realized that it doesn't stop HRM when I stop wearing it at night.
see edit below
RegardingBangle.getHealthStatus().movement > 146
: from the docs,
movement is the sum of diffs, so you can't just use a constant
threshold. And as Gordon pointed out, it only resets every 10min.Anyways, Bangle.js already detects movement for powerSave option and for me this would be sufficient. The following proof-of-concept works well enough for me:
function is_worn() {
return new Promise(function(resolve, reject) {
var i = 0;
function on_accel(a) { i++; }
Bangle.on('accel', on_accel);
setTimeout(function() {
Bangle.removeListener('accel', on_accel);
resolve(1 < i);
}, 3*80);
});
}
I didn't find a way to get the current power state/poll interval/"hasn't moved". Is there?
edit: I was wrong, acceleration diffs are unsigned, sry
-
Hi,
recently I learned to use Holoviews and wanted to make it work with Bangle.js. And here it is:
https://gitlab.com/-/snippets/2476631
The dependencies are at the very top, the main ones are: jupyterlab, bleak, holoviews and streamz
Fastest way to setup is pipenv:pip install pipenv; pipenv run pip install jupyterlab bleak holoviews streamz; pipenv run python -m jupyter lab
Feel free to use it however you want.
Merry Christmas
edit: should have put this in Tutorials sub forum, sry
:)
There is example data now, so you don't have to go through the lengthy process of building and running it to see what the end result is:
https://gitlab.com/notEvil/cyclometer/-/tree/master/python
If you have any troubles or questions, please let me know by creating an issue there