-
That's what I also had in mind, but it was a bit too complicated for a quick first test.
I have experimented with radio wave shielding mesh for shielding off rooms and created small containers to "move" objects out of range just by dropping them in.But this requires mechanical work, I'd have to build a rig to get it going for minutes (hours?) that's why I came up with just fiddling with the transponder side and it worked. ;-)
-
Yes, I show
process.memory().free
on one of my status pages as you can see in the code.
That's how I found out about the freezing issue.
Memory was not actually near zero, it was a bit above 100 when strange things happened.
So I just limited the number of logged laps (I cannot log forever, actually I just consider pushing them to a SD card) and things got more stable. -
OK.
Some more tests show that it may have been a memory issue.
Limiting the stored lap data to 250 records resulted in the test running for about two hours now.However, somehow the Transponders stop advertising now somehow after a few hundered iterations being toggled on and off.
This is the modified code:/** ===================================================== * throw off unknown BLE clients * @constructor */ var Firewall = { whitelist: { '14:7d:da:3f:53:31 public': 'Macbook Work', '50:ed:3c:01:69:ab public': 'Macbook Air' }, init: function () { NRF.on("connect", (addr) => { if (!(addr in this.whitelist)) { NRF.disconnect(); } }); }, }; // Firewall var id = 0; var on = true; function toggle() { if(id == 0) { NRF.wake(); digitalPulse(LED2, 1, 200); id = setInterval( ()=>{ digitalPulse(LED3, 1, 30); on = ! on; advertise(); }, 5000 ); } else { digitalPulse(LED1, 1, 500); clearInterval(id); id = 0; NRF.sleep(); } } var longId = 0; function longPress(state) { if(state) { if(0 == longId) { longId = setTimeout( () => { toggle(); longId = 0; }, 2000 ); } } else { if(longId > 0) { clearTimeout(longId); longId = 0; } } } function advertise() { if(on) { NRF.setAdvertising( {}, { name: "Velo" + NRF.getAddress().substr(-5).replace(":", ""), showName: true, discoverable: true, connectable: true, scannable: true, interval: 20, manufacturer: 0x0590, manufacturerData: [Puck.getBatteryPercentage()] } ); } else { NRF.setAdvertising(); } } function onInit() { Firewall.init(); advertise(); setInterval(advertise, 60000); toggle(); setWatch( (btn) => longPress(btn.state), BTN, { repeat:true, edge:"both", debounce: 50 } ); }
The effect is this:
The blue flashing still occurs, but the change to advertise the manufacturer id doesn't seem to happen, because the Puck is not recognized by the tracker anymore.
Toggling on/off via button (resutls inNRF.sleep()
/NRF.wake()
does not help.
Doing a reboot (power cycle) makes it work again.It seems the repeatedly change via
NRF.setAdvertising()
breaks something over time.Very strange, but no issue for the later use case.
-
OK - testing again.
After about 10 minutes everything gets stuck and comes to a grinding halt.No display updates, no keypress recognitions.
The first time it happened external BLE console connect was possible (but took looong) and dozens of lines with
Execution interrupted Execution interrupted …
appeared.
The second time the module is not even connectable via bluetooth.
Some time after starting the test it dropped the still open BLE console from my computer but still worked.
Now after 10 minutes when it froze I cannot even find it via LightBlue anymore.May this be an internal bug in the Javascript engine?
How to proceed? -
@allObjects OK, after ironing out the first batch of issues this is a basically working (but still untested) new version.
Thedraw()
function was a bit tricky since I feared thatsetInterval()
could cause overlaps, so I run a number ofsetTimeout()s
in a row, calling the next one recursively until the queue is empty.
Hope that is what you suggested and have in mind.The next thing would be to patch my Puck.js fleet to turn their radio on and off at intervals to simulate track laps.
This way I can run extended testing without leaving the room. -
-
Hi @allObjects, I just refactored the whole code and changed every object - except
Velo
which is dynamically created and I don't know how that works in the way you suggested.However, I ran into an issue. Look at this simple example:
var Obj = { message: "foo", init: function() { setInterval(this.log, 1000); }, log: function() { console.log(this.message); } }; Obj.init(); undefined undefined undefined …
The interval output is just
undefined
and notfoo
as you would expect.
Somehow passing the callbackthis.log
tosetInterval()
loses the object context.Can you explain this or do you have any solution for this other than using
Obj.message
instead ofthis.message
inside thelog()
method?
PassingObj.log
as callback does not help by the way...Very strange.
-
Thanks @allObjects for the insights.
I will try to understand what you were saying and refactor my code a bit.
It seems there were a fews ways of doing OOP in Javascript for some historical reasons, but it is hard to find the "right" just for the Espruino world since there are not many examples here.
Most of the code snippets here are just simple and short and just do everything globally.
I felt like having some things encapsulated, so this it what I ended up with.Minification is a thing to try, but I was worried about possible error messages and not being able to track them back to their source.
Actually after one session when everything froze I had a syntax error in my
ErrorLog
object:>ErrorLog.log =[ SyntaxError: SyntaxError: Got ')' expected EOF ]
Any idea about that?
The code had no errors when it was uploaded. May it have changed itself?
Memory issues? -
Regarding the objects / singletons:
I am not 100 % fluent in JS, most of the things I have looked up somewhere.From what I understood the Objects/Classes are declared as functions and since I don't need dynamic instances I'd mostly stick to static Objects, so (I thought):
function Foo() {}
would be a static object and
Foo.bar = function() { Foo.some = 123; }
would declare a static method to it while
Foo.prototype.baz = function() { this.thing = 123; }
would be a dynamic method working on real instances of the object created with
var foo = new Foo();
Could some awkward style of my code cause some runtime trouble?
If your example regarding objects is more common / recommended, I can change my code.Also thanks for the pointers regarding the
draw()
method, it is really awkward that it just blocks up sometimes. I'll try to break it into smaller chunks and move things to the background.
Do you think the timeout value is relevant? Or is it just to have it running asynchronously likeObject.keys(list).forEach((i)=>{ setTimeout(()=>{ … }, 1); });
Can the timeout value actually be
0
as well?Regarding
Do you know the ' for a while' time and did you make the calculation of average time per rx received?
Sorry, I am not sure what you are asking here. Could you elaborate?
Regarding Velo: It is mainly about riding/racing Velomobiles (those fully faired three-wheeled recumbents), not bicycles... ;-)
-
Another issue/detail:
This is the status after tracking 4 transponders for a while:
>Velo.list ={ "f1:04:79:ec:15:2d random": Velo: { id: "A", rssi: -78, rssiMin: -98, rssiMax: -57, laps: 42, bat: 65, tStart: 1628779258.06911659240, tRssiMax: null, tRssiLast: 1628791487.25615882873, tLastLap: 1628780850.37148475646, tPreviousLap: 1628780823.29253578186, rxCount: NaN, state: 0 }, "dd:16:f9:6a:28:44 random": Velo: { id: "B", rssi: -65, rssiMin: -96, rssiMax: -57, laps: 44, bat: 78, tStart: 1628779258.02102088928, tRssiMax: null, tRssiLast: 1628791487.26290321350, tLastLap: 1628780850.51058387756, tPreviousLap: 1628780823.81463050842, rxCount: 11411, state: 0 }, "f9:2d:30:24:38:a4 random": Velo: { id: "C", rssi: -65, rssiMin: -97, rssiMax: -63, laps: 57, bat: 100, tStart: 1628779258.01436805725, tRssiMax: null, tRssiLast: 1628791487.24501991271, tLastLap: 1628780850.97222328186, tPreviousLap: 1628780823.97481727600, rxCount: 27610, state: 0 }, "e8:44:6b:5f:3e:f0 random": Velo: { id: "D", rssi: -86, rssiMin: -98, rssiMax: -69, laps: 42, bat: 82, tStart: 1628779296.86849403381, tRssiMax: null, tRssiLast: 1628791487.26839637756, tLastLap: 1628780850.93142127990, tPreviousLap: 1628780823.74621009826, rxCount: NaN, state: 0 }, "c2:fc:b0:90:61:c7 random": Velo: { id: "E", rssi: null, rssiMin: null, rssiMax: null, laps: 0, bat: null, tStart: null, tRssiMax: null, tRssiLast: null, tLastLap: null, tPreviousLap: null, rxCount: 0, state: 0 }, "f5:23:d4:cf:c7:8a random": Velo: { id: "F", rssi: null, rssiMin: null, rssiMax: null, laps: 0, bat: null, tStart: null, tRssiMax: null, tRssiLast: null, tLastLap: null, tPreviousLap: null, rxCount: 0, state: 0 }, "c7:da:df:68:c9:fc random": Velo: { id: "G", rssi: null, rssiMin: null, rssiMax: null, laps: 0, bat: null, tStart: null, tRssiMax: null, tRssiLast: null, tLastLap: null, tPreviousLap: null, rxCount: 0, state: 0 }, "d3:8f:95:28:f0:02 random": Velo: { id: "H", rssi: null, rssiMin: null, rssiMax: null, laps: 0, bat: null, tStart: null, tRssiMax: null, tRssiLast: null, tLastLap: null, tPreviousLap: null, rxCount: 0, state: 0 } }
How can
rxCount
be set toNaN
when this is the only way it is changed in the code:this.rxCount = 0; this.rxCount += 1;
-
As already mentioned here I try to develop a kind of BLE based race track lap counter .
The base station is a MDBT42s with external antenna, big LIPO batteries, a LCD, a few buttons, a piezo buzzer and a RGB LED.
The transponders are just Puck.js devices advertising at the highest rate.The Puck/transponder code is quite simple: flash a LED once in a while to show it's active, detect keypress > 2 s for switching on/off
The MDBT42s/tracker code has gotten quite complex:
- scan for transponders in range
- monitor RSSI values and detect out-of-range (timeouts)
- count lap when RSSI reaches a certain threshold
- display various status pages (Laps, RSSI, RX counter, Battery)
- log laps to array in RAM
Basically everything works, but after running a certain time/detecting some dozen laps everything freezes and things become quite unstable:
- keypresses are still handled (
setWatch()
handler causes a beep) - display update loop (
setInterval()
seems stuck, connecting to the console and just once calling the display handler function seems to cure thesetInterval()
loop again - connecting the terminal via BLE after this stuck situation lasts forever
process.memory().free
still shows ~390 when this happens, so I don't thinks this is a memory issue.I have uploaded the current code.
Maybe someone has some pointers or an idea what might go wrong here.The code is capsulated into classes with either dynamic or static methods, maybe some of this was not a good idea?
- scan for transponders in range
-
Sure. Thanks. I'm already refactoring some of the code.
So I can post the old code or spend some time to get the new sorted.One single puck advertising full blast will cause about 20-30 events / second on the receiver side.
Having all eight pucks together will process about 115-130 events / second.Currently it is hard to trigger the error message. It just happened at some time while testing the device "in the field".
The error was actually bothExecution Interrupted
(multiple times) and alsoExecution Interrupted during event processing
The BLE connection was quite slow and laggy at that point. -
Thanks, @Gordon.
Yes, it's the one with the aerial. For some reasons:- I'd like to experiment with reception ranges, maybe put the antenna "on the track" (there should be a gap in the track) or use a directional antenna.
- I wanted bigger batteries since I fear the Pixls coin cell will die at some time when scanning for hours, flashing lights and beeping
- I had a box for everything like buttons, batteries, power switch etc. anyway, so why sacrifice a Pixl?
The
setScan
handler may be an issue indeed. If I monitor ~ 8 devices for longer somehow the thing freezes and when I connect via BLE afterwards I'll see messages like
Interrupted during event processing
coming up. Is this related?The crux is:
- I'd need to process every single packed received since there may not be many when someone passes very quickly
- I'd do some calculation / thresholds / decision if this was a lap or not, so there are some lines of code involved here. Still looking for a reliable and also simple solution. Maybe you haven an idea.
First it worked on the assumption that contact is lost for some seconds on the far side of the lap so the next time someone passes it counts a new lap.
But if someone stays just at the edge of reception or is veeery slow, this can accumulate many false laps.
So now I monitor RSSI which will have to rise above a certain level first and then drop again below another level. Will have to test this again.Or maybe I'll make
setScan
just do basic things and fill a buffer or something and do the calculations in another concurrent task? - I'd like to experiment with reception ranges, maybe put the antenna "on the track" (there should be a gap in the track) or use a directional antenna.
-
Since I have collected a number of Puck.js devices and some MDBT42s there is the current need to improvise a cycle track lap counter for some 2..8 riders on a 250 m cycle track.
The project has progressed beyond POC now, what I do is:- let the pucks just advertise with frequency cranked up to the max and mounted to the bikes
- have the MDBT42 scanning (bigger battery here, so no worries) at the start/finish line
- whenever a puck comes in range, monitor RSSI and count a lap right after RSSI has maxed out (probably after RSSI either drops significantly or connection is lost completely). I assume the connection is lost at the far end of the track anyway (~ 100 m), otherwise I have to adjust the external antenna to a lower gain model
- have the MDBT42 track/queue each id + lap timestamp and advertise this as a service to be pulled and visualized by a laptop via Web Bluetooth (this is a todo).
Simple tests were very promising. I will have to check if passing at 50-60 km/h at 5-8 m distance carrying 8 Pucks all advertising will work flawlessly just to be sure...
Question here (I am not so much into the internals/Espruino architecture):
I connected a Pixl display to the MDBT42. Will additional load through interfacing/drawing or doing other calculations decrease the receive rates from received Pucks or will BTLE scanning always have priority (like interrupt handling)? - let the pucks just advertise with frequency cranked up to the max and mounted to the bikes
-
Short update:
I have the feeling the sensors are crap. Some of them seem to work for a few weeks or more, but others drain their battery within hours or sometimes minutes.
I have no idea why and even bought another two sets of sensor to rule out a bad batch.Maybe it's because I use them in a unusually high pressure range of 6 to 8 Bar.
Or maybe there is something in the protocol that I did not get and they rely on some handshake by the original app...
@user126998 can you confirm anything of my observations or are yours just working fine? -
Someone considering porting Espruino to Raspberry Pi Pico?
https://www.raspberrypi.org/products/raspberry-pi-pico/ -
Update:
After testing for a few weeks there were issues with the battery life. In the beginning sensors worked for days without noticeable battery wear.
Then some other sensors started failing after only 1-2 days, eating batteries like candy.
I bought another (identical) set and tested indoors with a modified aluminium water bottle and all was fine. Once the sensors were installed on the wheels some of them also started to drain battery.Taking the first set indoors again and counting messages showed that some sensors (the ones with the battery issues) sent far more messages than the others.
I don't know if this is a hardware issue or if there is more about the protocol than I first thought.But how can they run dry when there is not a smartphone around? This is probably the case in 90% of the time...
I just ordered a different/updated version (BLE 5.0) now and have a look at their protocol and battery lifetime. The outer caps just look similar. Stay tuned...
-
Ah yes, @Gordon thanks for mentioning. But I feared to run short of GPIO pins in this future project... ;-)
And for those who wonder (and asked) - there was an eBay link at the top of that thread already: https://www.ebay.co.uk/itm/122179960272 - looks like the baby version of the Pixl.js display.
The display also has a backlight. It seems to be a bit "whiter" than the one of Pixl.js.
And it runs on 3.3 V - the 5 V rating of the eBay listing probably only refers to the backlight (which runs off a GPIO pin as well in my case). -
-
That's great! Nice of them to stick the connectors in too - that would have been super helpful when I was prototyping it!
Look what I found on eBay. Those fit perfectly for the Pixl display.
And since I have 3 spare ones from last year in my box.And by the way:
How can I access the displays from the MDBT42Q breakout board?
Unfortunately two of the "original" Pins used for Pixl.js are missing on the breakout board.Let's say I can solder them on - can I just flash a Pixl.js image onto a MDBT42Q?
-
The sensor sits on the valve top. So it is mainly the outside temperature, not the gas temperature...
I think it is more about compensation. If you change from a cool underground car park to hot summer roads the temperature rises and so should the pressure.
If the pressure stays the same but the temperature rises this may be a leakage so the alarm goes off. Thats what seemed to be happening once during testing when I covered the sensor with my (warmer) fingers. -
-
Followup from this thread and now pimped up to be of actual use on the infamous Pixl.js.
Hardware
- cheap BLE 4 enabled set of 4 thimble-sized tire pressure sensors (search for TPMS - V11 on eBay or Aliexpress):
- Pixl.js module
Software
// define your tires here (2-4) const tires = { '207bdd': { name: 'FL', minBar: 7.0, minBat: 20, }, '30bc95': { name: 'FR', minBar: 7.0, minBat: 20, }, '10ba52': { name: 'R', minBar: 5.5, minBat: 20 } }; const ALARM_BAR = 0x01; const ALARM_BAT = 0x02; const ALARM_LEAK = 0x04; const ALARM_TIME = 0x08; var dirty = 1; const timeout = 120; var flashTimer; function setFlash(on) { if(on) { if(flashTimer == undefined) { flashTimer = setInterval(function(){ digitalPulse(LED1, 1, [100]); }, 1000); } } else { if(flashTimer) { clearInterval(flashTimer); flashTimer = undefined; } LED1.reset(); } } function analyze() { var flash = 0; for(let id in tires) { var alarm = 0; var d = tires[id]; if(d.t > 0) { // underpressure if(d.bar < d.minBar) { alarm |= ALARM_BAR; } // battery low if(d.bat < d.minBat) { alarm |= ALARM_BAT; } // signal loss if(d.t + timeout < getTime()) { alarm |= ALARM_TIME; } // leak if(d.leak) { alarm |= ALARM_LEAK; } } else { tires[id].t = getTime(); } if(alarm > 0) { flash = 1; } tires[id].alarm = alarm; } setFlash(flash); } function out(text, x, y, inverse) { if(inverse) { g.fillRect(x, y, x + g.stringWidth(text) + 2, y + g.getFontHeight() - 1); g.setColor(0, 0, 0); } g.drawString(text, x + 1, y); g.setColor(1, 1, 1); } function draw() { g.clear(); var y = 0; out('TPMS', 0, 0, 1); for(let id in tires) { y += g.getFontHeight(); var d = tires[id]; g.drawString(d.name, 0, y); if(d.bar != undefined) { out(d.bar.toFixed(1), 25, y, d.alarm & ALARM_BAR); out(d.temp.toFixed(1) + '°C', 55, y); out(d.bat + '%', 95, y, d.alarm & ALARM_BAT); } else { out('...', 50, y); } if(d.alarm & ALARM_TIME) { out('?', 120, y, 1); } else if(d.alarm & ALARM_LEAK) { out('!', 120, y, 1); } } g.flip(); } function decodeData(device) { var d = new DataView(device.manufacturerData); var id = d.getUint32(2).toString(16).slice(-6); if( tires[id] != undefined ) { tires[id].t = getTime(); tires[id].bar = d.getUint32(6, 1) / 100000; tires[id].temp = d.getUint32(10, 1) / 100; tires[id].bat = d.getUint8(14); tires[id].leak = d.getUint8(15); dirty = 1; } } function onInit() { require("FontDylex7x13").add(Graphics); g.setFontDylex7x13(); analyze(); draw(); NRF.setScan(function(device){ decodeData(device); }, { filters:[{ services: ['fbb0'] }] }); setInterval(function(){ if(dirty) { analyze(); draw(); dirty = 0; } }, 5000); } onInit();
Function
The sensors do not require a connection or traversing Services and Characteristics.
All the data is just put into 16 bytes ofmanufacturerData
found in each broadcast:- Bytes 0-5: pevice ID
- Bytes 6-9: pressure (Pascal, Bar * 100000)
- Bytes 10-13: temperature (°C * 100)
- Byte 14: Battery (%)
- Byte 15: Flat (flag - 0=OK, 1=detected)
The sensor seems to:
- Sleep when no pressure detected (no transmissions at all)
- Transmit as soon as a change appears (with a few seconds delay)
- Transmit at least every 60 seconds when pressure detected
The set contains a card showing the last 6 hex digits of each device.
You can copy these digits (lower case) to the object on top of the code.
Everything from 1-4 wheels is possible. I only use 3 wheels because I own a Velomobile, but everything from unicycle to car is possible.For each wheel you can define an individual
name
, a minimum pressureminBar
and a minimum battery percentageminBat
.Underpressure, low battery, loss of signal (stale data for more than
timeout
seconds) and leaks are detected.
There is a flag (I called itleak
that seems to show 1 when the pressure is starting to decrease at a certain rate and/or the temperature is rising but the pressure is not).When any issue is detected,
LED1
begins to flash and the value gets inverted.
This is how it looks: - cheap BLE 4 enabled set of 4 thimble-sized tire pressure sensors (search for TPMS - V11 on eBay or Aliexpress):
-
OK. Another update.
Some more investigation and some coding later I have a working solution now:const ttl = 30; var idlog = {}; function i2h(i) { return ('0'+i.toString(16)).slice(-2); } function a2h(a, i, l) { var res = ''; for(var j=i; j<i+l; j++) { res += i2h(a[j]); } return res; } function dec32(a, i) { var res = Uint32Array(1); res[0] = (a[i+3] << 24) | (a[i+2] << 16) | (a[i+1] << 8) | a[i]; return res[0]; } function decodeData(device) { var d = device.manufacturerData; var id = a2h(d, 0, 6); var data = { battery: d[14], flat: d[15], temp: dec32(d, 10) / 100, pressure: dec32(d, 6) / 100000 }; var t = getTime(); if( idlog[id] == undefined || idlog[id].last + ttl < t) { idlog[id] = { last: t, data: data }; console.log(id, data); } } function onInit() { NRF.setScan(function(device){ decodeData(device); }, { filters:[{ services: ['fbb0'] }] }); }
This is the output from a few sensors (*ba52 sits on a "real" tire, the others are just blown into to wake them up):
82eaca30bc95 { "battery": 85, "flat": 0, "temp": 23.11, "pressure": 0.07926 } 82eaca30bc95 { "battery": 85, "flat": 1, "temp": 23.55, "pressure": 0 } 80eaca10ba52 { "battery": 85, "flat": 0, "temp": 18.67, "pressure": 8.36479 } 80eaca10ba52 { "battery": 85, "flat": 0, "temp": 18.67, "pressure": 8.36479 } 80eaca10ba52 { "battery": 85, "flat": 0, "temp": 18.67, "pressure": 8.36479 } 82eaca30bc95 { "battery": 85, "flat": 0, "temp": 23.7, "pressure": 0.07018 } 81eaca207bdd { "battery": 75, "flat": 0, "temp": 23.73, "pressure": 0.053 } 80eaca10ba52 { "battery": 85, "flat": 0, "temp": 18.67, "pressure": 8.36479 } 82eaca30bc95 { "battery": 85, "flat": 1, "temp": 24.85, "pressure": 0 } 81eaca207bdd { "battery": 75, "flat": 1, "temp": 24.15, "pressure": 0 }
Now everything needs to be wrapped up, put into a working solution including low pressure / battery indication (sound, LED), missing signal detection, pairing etc...
Thanks for pointing that out.
But actually there were no issues with the buttons yet other than there was no response at all with the
setHandler()
part when the whole thing got stuck.Do you see any issue in my code?
The freezes happened even when not pressing any button for a longer period.