-
MQTT with message retention is a pretty easy way to do this. Messages sent from Espruino’s TinyMQTT module have retention set by default. This is a good choice if you can easily have IP at both ends and you can have a slightly beefier box as a server. A Pi2 is fine.
If you are relatively new to all this NodeRed is a good choice because it has a UI to manage the design of the MQTT flows of data between your sensing devices and display, and add logic plus you can easily debug the MQTT messages flying around. However NodeRed doing any amount of work needs a beefier server, a Pi3 would be recommended. -
-
-
I'm working on an mqtt to bluetooth project. At the moment I'm using your espruinohub, Gordon, and nodered but I plan to switch to trying out nodered's contrib BLE module
-
Trying to use E.setConsole() on the Pico v2.04
I get:
Uncaught Error: Function "setConsole" not found!
Obviously I can use USB.setConsole(true) instead, but what if I want to set the console to null, which is in the documentation as
E.setConsole(null).
?
(Incidentally, it took me a while to figure out that since my ESP8266 is on Serial1 my Pico's internet connection stopped working whenever I removed the USB connection, because (I assume) the console switched automatically to Serial1.)
-
I put some debugging code into a copy of TinyMQTT.js, and in each case with the problem MQTT messages the first byte of data (or sometimes the first two bytes of data) is/are missing.
Good receipt:
cmd 3 / 30:16:0:10:Coffeemaker/brewbrew
Bad receipt, it is received as two separate calls to the data handler in TinyMQTT (onDat):
cmd 3 / 30:0:0:0:
cmd 1 / 16:0:10:43:offeemaker/brewbrewWe can see that if these were received in a single call to onDat, the publish command would be good.
Looking at the code for TinyMQTT, I don't think the issue can be in there, it must be somewhere else - any thoughts on where to look next? Or any thoughts on a sensible workaround, maybe compelling the socket to wait a little longer for data somehow - but looks like that code is maybe built into the firmware in the net module.
In the meantime I have put in a horrible hack so that if it is a publish command (3) with a total length of less than 5 bytes - which should never happen in my setup - then it stores up to 4 bytes in state waiting for the next call, and prepends these to the second set of data it receives.
Here is the hacked replacement onDat function for TinyMQTT.js
function onDat(data) { var i; //var log="MQTT receiving "; if (_q.tmp != null) { //log += ' APPLYING STORED DATA '; data = _q.tmp + data; _q.tmp = null; } var length = data.length; var cmd = data.charCodeAt(0) >> 4; //log += " length " + data.length + " cmd " + cmd.toString() + " / "; if (cmd ===3 && length <5) { _q.tmp = data; //console.log(log + " STORING EXTRA DATA FOR NEXT TRANSMIT"); return; } //for(i=0;i<4;i++) { // log = log + data.charCodeAt(i).toString(16) + ":"; //} //log += data.substr(4,256); //console.log(log); //log = ""; if(cmd === 3) { var var_len = data.charCodeAt(2) << 8 | data.charCodeAt(3); var msg = { topic: data.substr(4, var_len), message: data.substr(4+var_len, (data.charCodeAt(1))-var_len) }; _q.emit("message", msg); } }
All kinds of things could go wrong with this approach, especially if new messages are received in quick succession so I don't really want to live with it long-term.
-
I'm using TinyMQTT in a home automation project on a Pico + ESP8266.
About 20% of the time when handling new messages to subscribed topics, my message events fire but do not contain the topic or message, meaning I am unable to handle the message properly.
Subscribing using mosquitto_sub to check what is going on shows that the messages are definitely there and look normal, so this is an issue on the Pico side.
As a workaround I am sending an error back in the case of a null topic so that the UI can ask the user to try again but this is not satisfactory.
The fact it happens only a proportion of the time for identical messages suggests to me that it might be some kind of latency issue reading the data.
Anyone seen anything like this before with TinyMQTT? I am loth to switch to the much heavier MQTT, although I haven't tried it to see if it behaves better yet.
function MQTTmessage(msg) { console.log("MQTT message " + msg.topic + ":" + msg.message); if (msg.topic.endsWith("/topic1")) { //doSomething(); } if (msg.topic.endsWith("/topic2")) { //doSomethingElse(); } if (msg.topic == "" || msg.topic == null) { //in this case msg.message is also empty throwError(null, "null message received - please try again", false); }
-
Just an aside on externs in Closure. In advanced compilation, it would be helpful to have an Espruino externs file which would prevent minification for example to methods of E or other Espruino bundled classes. I might have a go at this at some point. With more work, every module intended for Closure advanced optimisation could have an extern file to prevent references to it being minified when used with Closure. I am unlikely to find time for this.
-
@allObjects it's pretty straightforward. The basic rule is refer to anything you don't want minified as an indirect property reference, so me.property should be written as me['property']. After minification is applied, this goes back to me.property, so although the unoptimised code looks more prolix, the minified is fine. The main problem seems to me that someone later will "improve" the unminified code so that it stops working with advanced optimisation. I looked at using the exports and externs options in advanced optimisation https://developers.google.com/closure/compiler/docs/api-tutorial3?hl=en but they don't work well for the Espruino use case as is. Externs could work with some changes to WebIDE perhaps.
-
-
I'm writing versions of modules AT, ESP8266 amd MQTT which can be minified in closure's advanced mode (side-question: when I back-contribute these, looks like I should also contribute the minified versions as by default they will be minified using simple optimisations)
However, no matter what options I select in WebIDE minification, it won't use my local minified versions when I specify
require('ESP8266_MM').connect()
However, when I specify
require('ESP8266_MM.min').connect()
it DOES use the minified versions so they are there and working.
In both cases it works as expected, and there are no (visible) errors. The modules are stored locally in the modules directory of the project location
When I run a test using a very simple pair of examples, it does correctly use the minified version without being prompted.
-
In my ideal world we would move to promises rather than callbacks wherever possible and especially in network libraries. I expect that's a huge task though. I am planning though to rewrite for my own purposes the ESP8266 module with promises, probably using promiscuous library although it would be good to hear any opinions on good, small promise libraries. I haven't yet tested any with Espruino.
-
-
-
The MQTT module emits 'disconnected' when you call disconnect - https://github.com/espruino/EspruinoDocs/blob/master/devices/MQTT.js#L167
For me, this returns an error "event name too long".
A quick look at Espruino's source code [https://github.com/espruino/Espruino/blob/f18509acff2a9fdfb8d7414f46b4e546e3be01d3/src/jswrap_object.c#L484] suggests that an event name can be up to 12 characters long (16 is the length of the constant, minus 4 as in the line of code)
'Disconnected' is 12 characters long, so I feel like it should work, so I'm not quite sure what's going on here.
-
That's the disconnect code I'm using. I'm also using AT+GSLP=3600000 to put the ESP8266 into deep sleep, then using RST pin to wake it up again. I still have to actually measure current to see if this is working as I'm not entirely convinced.
I'm going to experiment with using CH_PD pin also once I get my non-Huzzah ESP8266s in from China.
-
@allObjects did you ever submit this as a module?
-
The BME280 module is a bit unnecessarily large, so I'm refactoring it, primarily to remove unnecessary variable declarations and debugging code, reduce the length of non-minifiable public method names and generally tidy up a bit.
Is there a protocol for this sort of thing, eg discussing here, or should I just change and create a pull request?
FWIW here's the code, anything else?
/* Copyright (c) 2015 Masafumi Okada. See the file LICENSE for copying permission. */ /* Read Temperature, Pressure, and Humidity from Bosch Sensortec's BME280 sensor module. */ var C = {ADDR: 0x76} function BME280(_i2c) { var t = this; t.i2c = _i2c; var osrs_t = 1; //Temperature oversampling x 1 var osrs_p = 1; //Pressure oversampling x 1 var osrs_h = 1; //Humidity oversampling x 1 var mode = 3; //Normal mode var t_sb = 5; //Tstandby 1000ms var filter = 0; //Filter off var spi3w_en = 0;//3-wire SPI Disable var ctrl_meas_reg = (osrs_t << 5) | (osrs_p << 2) | mode; var config_reg = (t_sb << 5) | (filter << 2) | spi3w_en; var ctrl_hum_reg = osrs_h; t.wReg(0xF2, ctrl_hum_reg); t.wReg(0xF4, ctrl_meas_reg); t.wReg(0xF5, config_reg); t.rCoeffs(); } /* Concatinate two Uint8Arrays */ BME280.prototype.concatU8 = function(a, b) { var c = new Uint8Array(a.length + b.length); c.set(a); c.set(b, a.length); return (c); }; /* Convert two UInt8 value into one "signed" number */ BME280.prototype.convS16 = function(ub1, ub2) { var value = (ub1 << 8) + ub2; if (value & 0x8000) { value = -((value - 1) ^ 0xffff); } return (value); }; /* Write single byte to register reg_address */ BME280.prototype.wReg = function(reg_address, data) { this.i2c.writeTo(C.ADDR, [reg_address, data]); }; /* Read single byte from register reg*/ BME280.prototype.read8 = function(reg) { this.i2c.writeTo(C.ADDR, reg); return this.i2c.readFrom(C.ADDR, 1)[0]; }; /* Read and store all coefficients stored in the sensor */ BME280.prototype.rCoeffs = function() { var t = this, i = t.i2c; i.writeTo(C.ADDR, 0x88); var data = i.readFrom(C.ADDR, 24); i.writeTo(C.ADDR, 0xA1); data = t.concatU8(data, i.readFrom(C.ADDR, 1)); i.writeTo(C.ADDR, 0xE1); data = t.concatU8(data, i.readFrom(C.ADDR, 7)); t.T1 = (data[1] << 8) | data[0]; t.T2 = t.convS16(data[3], data[2]); t.T3 = t.convS16(data[5], data[4]); t.P1 = (data[7] << 8) | data[6]; t.P2 = t.convS16(data[9], data[8]); t.P3 = t.convS16(data[11], data[10]); t.P4 = t.convS16(data[13], data[12]); t.P5 = t.convS16(data[15], data[14]); t.P6 = t.convS16(data[17], data[16]); t.P7 = t.convS16(data[19], data[18]); t.P8 = t.convS16(data[21], data[20]); t.P9 = t.convS16(data[23], data[22]); t.H1 = data[24]; t.H2 = t.convS16(data[26], data[25]); t.H3 = data[27]; t.H4 = (data[28] << 4) | (0x0F & data[29]); t.H5 = (data[30] << 4) | ((data[29] >> 4) & 0x0F); t.H6 = data[31]; }; /* Read Raw data from the sensor */ BME280.prototype.rdRaw = function() { var t = this; t.i2c.writeTo(C.ADDR, 0xF7); var data = t.i2c.readFrom(C.ADDR, 8); t.pres_raw = (data[0] << 12) | (data[1] << 4) | (data[2] >> 4); t.temp_raw = (data[3] << 12) | (data[4] << 4) | (data[5] >> 4); t.hum_raw = (data[6] << 8) | data[7]; }; /* Calibration of Temperature, algorithm is taken from the datasheet */ BME280.prototype.cal_T = function(adc_T) { var t = this, v1 = ((adc_T) / 16384.0 - (t.T1) / 1024.0) * (t.T2), v2 = (((adc_T) / 131072.0 - (t.T1) / 8192.0) * ((adc_T) / 131072.0 - (t.T1) / 8192.0)) * (t.T3); t.t_fine = (v1 + v2); return 100 * t.t_fine / 5120.0; }; /* Calibration of Pressure, algorithm is taken from the datasheet */ BME280.prototype.cal_P = function(adc_P) { var v2, p, t = this, v1 = (t.t_fine / 2.0) - 64000.0; v2 = v1 * v1 * (t.P6) / 32768.0; v2 = v2 + v1 * (t.P5) * 2.0; v2 = (v2 / 4.0) + ((t.P4) * 65536.0); v1 = ((t.P3) * v1 * v1 / 524288.0 + (t.P2) * v1) / 524288.0; v1 = (1.0 + v1 / 32768.0) * (t.P1); if (v1 === 0.0) { return 0; // avoid exception caused by division by zero } p = 1048576.0 - adc_P; p = (p - (v2 / 4096.0)) * 6250.0 / v1; v1 = (t.P9) * p * p / 2147483648.0; v2 = p * (t.P8) / 32768.0; p = p + (v1 + v2 + (t.P7)) / 16.0; return p; }; /* Calibration of Humidity, algorithm is taken from the datasheet */ BME280.prototype.cal_H = function(adc_H) { var t = this, v1 = (t.t_fine - (76800)); v1 = (((((adc_H << 14) - ((t.H4) << 20) - ((t.H5) * v1)) + (16384)) >> 15) * (((((((v1 * (t.H6)) >> 10) * (((v1 * (t.H3)) >> 11) + (32768))) >> 10) + (2097152)) * (t.H2) + 8192) >> 14)); v1 = (v1 - (((((v1 >> 15) * (v1 >> 15)) >> 7) * (t.H1)) >> 4)); v1 = (v1 < 0 ? 0 : v1); v1 = (v1 > 419430400 ? 419430400 : v1); return (v1 >> 12); }; exports.connect = function(_i2c) { return (new BME280(_i2c)); };
-
Relatedly, I still haven't tracked my memory leaks down, so I am using this pattern for those devices (sensors) which only poll occasionally and do not require realtime:
//simplified example with all the logic in init. E.on('init', function () { setDeepSleep(0); //only want to sleep after I finish my actions setTimeout(load, 600000); //restart every 10 minutes to poll again doPolls(function() { //callback function called when complete setDeepSleep(1); //now I can go to sleep until time to reboot }); });
Okay, I should find and fix my code leaks, but this is a common and/or reasonable pattern in embedded?
-
-
Is compression related to the odd experience I have of sending code once, executing save and being told too big for flash, then resending exactly the same code doing save and finding it is 5000 bytes slimmer and does fit on flash? Up till now I've assumed this is something wrong with the first time the IDE processes a local module, but it still seems very odd to me.
-
-
I wish I had read these experiments before my own work on moving from console-connected to independent operation! Thank you for writing them up.
Because I am going to be controlling power to all connected devices, I assume that all connected devices may have been independently powered down, and so (and because of not liking to be reliant on save timing) I prefer to reconnect devices again via a deviceInit function in onInit which is also called in the start section of the main app.m
-
By “it “ what do you mean?
There is a tutorial here which I started with. https://www.espruino.com/BLE+Node-RED
Here’s a simple breakdown of the general steps you need, I won’t have time (sorry!) to spell them all out in detail
1) you need an MQTT broker, sounds like you have that in aedes, but people often use Mosquitto
2) you need a BLE to MQTT bridge to get messages from Bluetooth to MQTT, for this you could consider Gordon’s EspruinoHub which also by the way has full instructions starting from scratch for running on a Pi.
3) you need a program running on the Bangle which is advertising data on BLE - I imagine it broadcasts battery by default so you could just start with that
4) you configure NodeRed to subscribe to MQTT messages coming from the Bangle and/or send messages to the Bangle take actions based on them. A good place to start is installing NodeRed’s dashboard UI and use the dashboard for testing.
If you are a decent programmer you can skip NodeRed and build your own program on the server to subscribe to the MQTT messages and respond directly.
It is more complicated to handle sending data to the Bangle, and advertising is by far the simplest mechanism.