-
The modifications to the above to get it to work with MPR121 - put the following into onInit()
SCL=D15; SDA=D14; IRQ=D16; I2C1.setup({scl:SCL,sda:SDA}); sensor = require("MPR121").connect(I2C1, function() {}); // Function to be called when MPR121 notifies us of change setWatch(function(e) { var value = sensor.touched() & 0x0f; // Filter out other inputs if (value==0) { // All buttons up: Emit the key emitkey(state); state='_'; } // Buttons map to MP121 inputs 0,1,2,3 -> bits 1,2,4,8 if (value==1) keydown(0); if (value==2) keydown(1); if (value==4) keydown(2); if (value==8) keydown(3); }, IRQ, {edge: 'falling', repeat: true});
-
In a stunning example of high quality electronics construction(!) - no amateur construction of sticky tape, Pritt stick and tin foil here - I had a go this afternoon at making a gesture version, so you "write" letters by swiping the patterns above.
There are four pieces of tin foil glued to a piece of cardboard, an MPR121 breakout board and a MDBT42 module at the top. To ensure a quality construction the tin foil is covered with a thin piece of plastic from an old envelope.
It works surprisingly well, and I find the gestures are becoming part of muscle memory.
It would be cool to see if the nRF52 chip could implement four touch sensors directly -without the mpr121-, and then 4 pads could be attached to the cover of a puck. I know there is one cap sense input, but unsure if it can be easily extended to four, and how well they would work compared to the auto-calibration and filtering that the mpr121 chip does.
-
Hello,
I had a TTP229 capacitative keypad module lying around and thought it would be useful with Espruino. These can be picked up fairly cheaply online. Just be careful: There are actually two variants of this chip and it isn't always clear which is which!
The modules I see on ebay seem to have the TTP229-BSF version of the chip - this is what mine is and what this code works with. I think if the module has "SCL" and "SDO" pins on it things should be OK. The other version has an I2C interface and won't work with this code.
To explain the code: You can treat the chip as though it has an SPI interface, and read out 2 bytes of data from it. These contain the keypresses, but inverted - ie '1' means not set, and '0' means set.
I hope the code below makes sense and is useful to somebody!
// Espruino example for TTP229-BSF keypad // Connect keypad SCL and SDO lines to these pins: SCL = D14; SDO = D15; // Setup SPI interface but clock needs to be quite slow otherwise we get data errors SPI1.setup({miso:SDO, sck:SCL, mode:integer=3,baud:integer=10000}); // Periodically read the keys and display on the console setInterval(function() { // Read 2 bytes of data from the device data=SPI1.send([0,0]); // Invert the data so that a '1' means button pressed. This gives us // two bytes: // Top = buttons 1-8 : Bits are Button 1,2,3,4,5,6,7,8 // Bottom = buttons 9-16 : Bits are Button 9,10,11,12,13,14,15,16 top=data[0]^0xff; bottom=data[1]^0xff; // Now display as binary console.log("BTN1-8",top.toString(2),"BTN0-16",bottom.toString(2)); },500);
-
Hello,
Thanks for the block hint. I've actually got a 12 key version of that somewhere which I could try. The joystick switch also sounds interesting.
I ordered an MPR121 touch sensor board, which has just arrived. I'm currently glueing pieces of tin foil onto a piece of cardboard as an experiment. I'm firstly going to try a chord keyboard to compare, then will see if I can fashion some touch sensors into a square such that they detect swipe movements.
-
Hello,
Inspired by the "Pixl.js home computer" tutorial (https://www.espruino.com/Pixl.js+Home+Computer) I figured I'd make a tiny laptop.
I had a Microsoft chatpad keyboard in my box of electronic stuff. These can be obtained quite cheaply now and can be easily used with microcontrollers.
They output a 4800baud serial signal which can be connected straight to the serial port of a microcontroller.
The pinout for the one I have is:
red wire: 3.3v supply
black wire: data out (I connected to pin A0 but it doesn't really matter)
orange wire: gnd supply(Note that black isn't used for its traditional use of ground- a mistake I nearly made while wiring this up)
I figured that this could be quite neat when combined with a SIM800 mobile phone module - could build a programmable mobile phone to send and receive SMS, download text data etc etc.
The code is:
function onInit() { Serial1.setup(4800, {rx:A0, tx:A2}); Serial1.on('data', function (data) { Terminal.inject(data); }); }
OK. Time to get rid of this Macbook. It's now obsolete, having been replaced by the latest low powered Espurino open source DIY laptop. (I think....)
-
Hmm. The posting above went a bit wrong - I guess I'm posting Python code rather than Javascript.
Here is the garbled bit, not marked as code to hopefully keep the forum happy
def colin_hotp_token(secret, intervals_no):
# Key is a 10 byte array
key = base64.b32decode(secret, True)
# Python debug : print the key as bytes
#print "KEY",[ord(x) for x in key]#keybytes=[0, 68, 50, 20, 199, 66, 64, 17, 12, 133]
# msg is 8 bytes long - number with last byte = least significant
msg = struct.pack(">Q", intervals_no)
#msgbytes=[0, 0, 0, 0, 0, 0, 0, 1] -
Hello,
I've just gone down a rabbit hole trying to discover how best to do this, and have written some Python code that seems to work. I think it should be OK to translate this into Javascript.
I found a Javascript SHA1 implementation that works in the Espruino simulator at http://webtoolkit.info/javascript_sha1.html
I had some Python code to do TOTP tokens so had a go at rewriting it to use my own HMAC algorithm based on the Wikipedia page https://en.wikipedia.org/wiki/Hash-based_message_authentication_code
I think the Javascript code above returns the digest as hex - it will need to be changed to return an array of bytes but I haven't looked how to do that. But once that is done, I think my python call to sha1() can just use the Javascript code.
The "get_hotp_token" function was the original one - I forget where it came from. From what I can see my function and this function return the same results.
I hope it should be OK to transliterate my Python code into Javascript. The 'chr' and 'ord' function should be removed - this is as the code uses Python strings, but Javascript arrays won't have this problem. Likewise, the 'b'\x00' *' part is a Python thing that would need to be rewritten for Javascript.
I hope my code when combined with the Javascript SHA1 code gives some help anyway.
# SHA1 block size = 512 bits, or 64 bytes # https://en.wikipedia.org/wiki/Hash-based_message_authentication_code def colin_hmac(key,msg): # https://en.wikipedia.org/wiki/Hash-based_message_authentication_code # https://raw.githubusercontent.com/python/cpython/master/Lib/hmac.py # key is 10 bytes, msg is 8 bytes # Pad key to be 64 bytes long blocksize = 64 key = key + b'\x00' * (blocksize - len(key)) # Pad key with 0 byte values # Build up new strings with the key XORed with 0x36 and 0x5C k36="" k5c="" for c in key: k36 = k36+chr(ord(c) ^ 0x36) k5c = k5c+chr(ord(c) ^ 0x5c) hmac=sha1(k5c + sha1(k36 + msg).digest()) return hmac.digest() def colin_hotp_token(secret, intervals_no): # Key is a 10 byte array key = base64.b32decode(secret, True) # Python debug : print the key as bytes [#print](https://forum.espruino.com/search/?q=%23print) "KEY",[ord(x) for x in key] [#keybytes](https://forum.espruino.com/search/?q=%23keybytes)=[0, 68, 50, 20, 199, 66, 64, 17, 12, 133] # msg is 8 bytes long - number with last byte = least significant msg = struct.pack(">Q", intervals_no) [#msgbytes](https://forum.espruino.com/search/?q=%23msgbytes)=[0, 0, 0, 0, 0, 0, 0, 1] colin_hm=colin_hmac(key,msg) binary=offset=ord(colin_hm[19]) & 0x0f binary=((ord(colin_hm[offset]) & 0x7f) << 24) | ((ord(colin_hm[offset+1]) & 0xff) << 16) | ((ord(colin_hm[offset+2]) & 0xff) << 8 ) | (ord(colin_hm[offset+3]) & 0xff) # We want the last 6 digits return binary % 1000000 def get_hotp_token(secret, intervals_no): key = base64.b32decode(secret, True) msg = struct.pack(">Q", intervals_no) hm = hmac.new(key, msg, hashlib.sha1) h = hm.digest() # Return byte array o = ord(h[19]) & 0x0f h = (struct.unpack(">I", h[o:o+4])[0] & 0x7fffffff) % 1000000 return h
-
Hello,
Here is today's installment of "What Colin did in lockdown today".
I was teaching someone to program yesterday, and we made a bike computer using a Pixl.js to display speed and distance on the Pixl display using an sensor from an old broken wired Cateye bike computer we had. This sensor is a reed switch and a magnet.
I thought "I have a Puck.js with Bluetooth and magnetometer" so had a go at making a sensor for my phone using the Puck.
When running this code the Puck appears as a bluetooth cycling sensor ("CSC profile") to use the jargon and can be read by apps on a mobile phone. I tried "Jepster" on Android and the nRF toolbox app.
The theory is that the Puck could be mounted on the front fork of a bicycle, with a magnet on a spoke. When you bring the magnet nearby the LED on the Puck lights, and a reading is sent to the phone. When the magnet rotates away the LED goes off.
There is a commented out bit of code that uses setWatch so the same code could run with a bike computer sensor - this probably gives longer battery life than using the magnetometer.
I did discover if you alter the time between pulses the app seems quite happy to believe I can cycle at 80,000 km/h - take that, competitive Strava users :-)
I haven't actually tested this on a bike, but I think it works. Posting here in case it is of interest to anyone!
/* Espruino code for Bluetooth Low Energy CSC (Cycling and Cadence Sensor) profile https://www.bluetooth.com/wp-content/uploads/Sitecore-Media-Library/Gatt/Xml/Characteristics/org.bluetooth.characteristic.csc_measurement.xml Data is returned via the Measurement characteristic: flags = 0x01 = Wheel data; 0x02 = Crank data; OR together for both uint32 = count of wheel revolutions; uint16 = last wheel event time in units of 1/1024 seconds uint16 = count of crank revs uint16 = crank event time Usage: csc=create_csc_service() At each wheel revolution csc(time_in_seconds) ...each call to this will notify the phone etc */ function create_csc_service() { // Dataview and buffer to hold the measurement data var buffer = new ArrayBuffer(11); var dv = new DataView(buffer, 0); var revs=0; // Given # wheel revs, time in seconds, format the buffer // for the CSC measurement characteristic. function format_csc(revs,wtime) { var time_csc_units = wtime*1024; // Convert to units of 1024th of a second dv.setInt8(0, 0x01); // Only wheel data is available => 01 dv.setUint32(1, revs, little_endian=true); dv.setUint16(5, time_csc_units, little_endian=true); dv.setUint16(7, 0); // Crank revs not used dv.setUint16(9, 0); // Crank time not used return buffer; } // Update the characteristic. Call once per revolution passing // time in seconds. This is our "public" interface function revolution(time_secs) { revs+=1; format_csc(revs,time_secs); // Now notify... NRF.updateServices({ 0x1816 : { // CSC UUID 0x2A5B: { // Measurement UUID value : buffer, readable : true, notify : true, } } }); } // Start - advertise that we're a CSC NRF.setServices({ 0x180F : { // Battery service UUID 0x2A19: { // Battery value UUID value: [90], // Battery level percentage readable : true, }, }, 0x1816 : { // CSC UUID 0x2A5B: { // Measurement UUID value : format_csc(0,0), readable : true, notify : true, }, 0x2A5C: { // Feature UUID - need to fully understand this value: [ 0x01,0x01], readable: true, } } }, { advertise: [ '180F', '1816' ], uart: false}); return revolution; } // Version with ex bike computer magnet+reed sensor on GPIO pin D13 //pinMode(D13,"input_pullup"); //setWatch(function(e) { // csc(e.time); //},D13,{edge: 'falling', repeat:true, debounce:20}); // Version for Puck.js using its in-built magnetometer // Two values for hysteresis. Determine mag reading for magnet near and far. // Might need to change this depending on magnet or bicycle positioning MAGNET_NEAR_VALUE=150000; MAGNET_FAR_VALUE=130000; function on_mag(reading) { reading.x -= mag_zero.x; reading.y -= mag_zero.y; reading.z -= mag_zero.z; // Pythagoras theorem, but no need to // sqrt as we're only interested in // change var magnitude = reading.x*reading.x + reading.y*reading.y + reading.z*reading.z; // Magnet detected near? if (magnitude>MAGNET_NEAR_VALUE && !magnet_near) { magnet_near=true; LED.write(1); csc(getTime()); // Update the bluetooth service } // Magnet moved away? if (magnitude<MAGNET_FAR_VALUE && magnet_near) { magnet_near=false; LED.write(0); } } function onInit() { csc = create_csc_service(); mag_zero = Puck.mag(); // Get base case for magnetometer reading magnet_near = false; Puck.on('mag', on_mag); Puck.magOn(80); // 80Hz sample rate }
-
Thanks for the positive feedback!
Robin - I did actually display the letters on the screen to show which button did which. I wrote a prototype for this using a web browser a while ago - on the computer it did work OK, but on the Pixl I found it easier just to have a paper reference and think of each letter as a shape. Annoyingly I deleted the Pixl version which did draw to the screen!
If you're interested in trying then I think this code should work as a base, but I just wrote it and it might have bugs (e.g. I think it will show the ! unused marker). Changing the console.log to g.drawString in the appropriate place, and calling the showLabels(state) function after key up and key down should do the trick I think.
// Traverse the tree to show characters reachable from a certain state // as a string function reachablefrom(state) { if (state=='?') return ""; // End recursion return reachablefrom(states[state][0])+ reachablefrom(states[state][1])+ reachablefrom(states[state][2])+ reachablefrom(states[state][3]); } // Show characters available from each button function showLabels(state) { console.log("KEY1",reachablefrom(states[state][0])); console.log("KEY2",reachablefrom(states[state][1])); console.log("KEY3",reachablefrom(states[state][2])); console.log("KEY4",reachablefrom(states[state][3])); }
Gordon - I'm glad you're thinking the same as I am about the Puck making a tiny input device. Funnily enough that's why I bought it a while ago and am only just having time to play with it :-)
I'm actually wondering about some sort of finger movement detection sensor rather than buttons - It feels to me that 'writing out' the shapes by swiping a finger around might actually be quicker than pressing and holding buttons in sequence.
One option might be to use an optical mouse sensor - but they seem a bit harder to get hold of now (That's what I get for throwing out a hoard of old mice...note: never throw out old junk). The APDS-9960 sensor looks like it can do gestures from a distance, but I'm thinking more of touches - faster movements much closer to the device.
A capacitative sensor might work, but I'm not sure having 4 pads and just sensing a touch on each would work, as ideally it would be quite small and a fat finger might just smash all pads at once. I've got hold of an MPR121 board, which looks as though it can read the raw value of each sensor. Doing something clever with the raw values might allow movement detection - I will need to experiment.
Regarding the reconnection on the phone - I'll need to experiment more. I had a bit of a frustrating time when the Pixl stopped accepting any connections and I couldn't reconnect to debug it. I even put the Pixl in the microwave oven (with the oven off!) to see if it made a difference, as I got paranoid that somehow one of my neighbours was connecting to the UART service all the time! And shielded by the microwave there was no connection - so the problem wasn't the Pixl thinking it had a connection when it didn't. It turns out that my Mac was auto-connecting to it as soon as it advertised as an HID device and was staying connected even when the screen was closed (I thought if it was the Mac closing the screen would disconnect, so this sent me down a wild goose chase). Grr. So I will try the phone connection again with my Mac switched off - maybe it was working all along. I haven't yet investigated the bonding and PIN stuff however - I will do this also which might help.
-
Hello all,
I thought I'd share the code for the input method I've just thought of and been playing around with today. (Lockdown creativity!)
I've been wanting a method to enter text "on the go" on my phone, so thought that a Pixl.js was a good way to experiment. The attached code implements a Bluetooth keyboard that works with a Pixl.js with no other hardware.
The idea is to press buttons in sequence then release them. So BTN4 is 'T', BTN3 is 'A' etc. To get other letters you hold down a sequence - 'H' is hold BTN4, hold BTN3, release both. 'P' is hold down three buttons then release all.
This is a bit complex to remember, so I made a graphical pattern for each letter - see the attached picture. This works holding a Pixl "rotated" so that the 'Pixl.js..Espruino' writing is on the right hand side as you hold it, so that 'a' is the top right button, 'e' is the bottom left.
I don't think it's too bad to use; I was writing text reasonably quickly and found I was beginning to memorise the patterns. I've used letter frequencies so (in English) more common letters need fewer presses.
I'd like to expand this to have numbers and other characters, and possibly to work with a capacitative sensor so you can just write out the shapes with a finger. It would be cool to have this sensor woven into fabric, or via a tiny sensor with four quadrants you could wear as a thimble or something.
Future plans:
- Work out how to add more characters - e.g. numbers and capital letters. Theoretically the pattern length can be infinite, or maybe have a combination to switch 'mode'.
- Work out how to use 4 cap sense inputs on the Pixl and experiment with conductive thread to make pads on fabric. I've got a couple of I2C cap sensor chips lying around but I suspect the NRF chip can do this.
- Work out how to bond the Pixl to the phone so the phone automatically connects to the device - currently I have to manually connect on the phone.
What do you think?
Colin
The code is below (Indentation has gone a bit wrong but hopefully it's readable)
To explain the code:
- states maintains a mapping of buttons to letter. The starting state is '_'.
- Pressing button 1 moves to the 'E' state. If all buttons are released then 'E' is emitted. If more buttons are pressed then the state moves from 'E' to the next state according to the button.
- '?' is an error state - e.g. pressing button 1 twice without releasing it isn't possible.
'!' is an unused state so extra things can be added.
// Key state transitions var states = { // BTN. 1 2 3 4 //Single key '_': ['E',' ','A','T'], // Two keys ' ': ['L','?','I','M'], 'E': ['?','D','C','S'], 'A': ['U','O','?','R'], 'T': ['N','W','H','?'], // Three keys 'W': ['F','!','!','!'], 'D': ['!','!','!','G'], 'C': ['!','J','!','Y'], 'H': ['P','!','!','!'], 'S': ['!','B','X','!'], 'M': ['V','!','!','!'], 'I': ['K','!','!','!'], 'N': ['!','Q','!','!'], 'R': ['Z','!','!','!'], 'Z': ['!','CR','!','!'], // Error '?': ['?','?','?','?'] }; function keydown(key) { if (state=='?') Terminal.println("Error - bad state"); ++keydowncount; state=states[state][key]; // Transition to the new state } function keyup(key) { --keydowncount; if (keydowncount==0) { // All keys released: Emit the key emitkey(state); state='_'; // Reset state transition } } // Do whatever when the key is released function emitkey(letter) { Terminal.print(letter); // CR hack if (letter=='CR') Terminal.println(""); if (kb) { // CR hack if (letter=='CR') { kb.tap(kb.KEY.ENTER,0); } else kb.tap(kb.KEY[letter], 0); } } // Map a button to the position in the state array function wireKey(button,position) { setWatch(function() { keydown(position); }, button, { edge: 'rising', repeat: true}); setWatch(function() { keyup(0); }, button, { edge: 'falling', repeat: true}); } function onInit() { keydowncount = 0; state = '_'; // Starting state wireKey(BTN1,0); wireKey(BTN2,1); wireKey(BTN3,2); wireKey(BTN4,3); kb = undefined; kb = require("ble_hid_keyboard"); NRF.setServices(undefined, { hid : kb.report }); }
- Work out how to add more characters - e.g. numbers and capital letters. Theoretically the pattern length can be infinite, or maybe have a combination to switch 'mode'.
-
-
Gordon - thanks. Glad to confirm I wasn't missing the obvious. It's good to learn how to do these things.
This relates to the thread above, so I hope I can continue it here rather than start a new thread. I can't find the answer in the forum.
Getting an experiment using GATT between Puck and Pixl has been quite nice and easy - Got a Puck sending notifications to the Pixl every few minutes - managed to graph the temperature last night - until it stopped working. It looks as though the devices were a little bit too far apart so interference or whatever caused them to disconnect, as it's OK when they are closer. When too far apart the Pixl stops working with an error:
Uncaught InternalError: BLE task completed that wasn't scheduled (3/0)
I figured that I wasn't handling the disconnection that occurred, so I handled the 'gattserverdisconnected' event. This handler is doing something: my log function is called and prints reason '8', so is doing something.
However I still get the error "Uncaught InternalError: BLE task completed that wasn't scheduled (3/0)"
Is there something I should do to handle this and stop the Uncaught error? I tried adding a handler in NRF.on('disconnect')... but that doesn't seem to be called - I'm not sure why.
I'm also unsure of how best to handle this case. I basically always want the two devices to connect when they can, so am making the Pixl periodically try to reconnect.
For now, I've done this as follows:
- Added a 'connected' flag which is set false initially.
- Added a setInterval call to call the function that does the connection - to keep trying to connect until a successful connection is made.
- When a device is connected the flag is set to True.
- The gattserverdisconnected event sets 'connected' to false, so the timer function tries to periodically reconnect again.
- I guess I should set and clear the interval rather than using a flag, but - ease of experiment.
Is this the neatest way of handling this scenario? It looks as though the Uncaught Internal error is harmless, but it would be cleaner for me to do is correctly and remove the error.
Code below shown - this connects to the puck running the service above.
Thanks!
NRF.on('disconnect', function () { console.log("NRF disconnected"); }); setInterval(function() { if (!connected) { console.log("Trying to connect..."); connectRemoteTemperature(onTemperatureReceived); } }, 3000); // Do Bluetooth stuff to connect to the first device found with name beginning // 'Puck' and with our service characteristics function connectRemoteTemperature(newTextRxedFunction) { NRF.requestDevice({ filters: [{ namePrefix: 'Puck' }] }).then(function(device) { console.log("Found Puck",device); return device.gatt.connect(); }).then(function(g) { console.log("Connected"); connected = true; g.device.on('gattserverdisconnected', function(reason) { console.log("GATT server disconnected ",reason); connected = false; }); return g.getPrimaryService("047b0001-37e3-457d-3763-2a9b2d24cbb2"); }).then(function(service) { console.log("Got service"); return service.getCharacteristic("047b0002-37e3-457d-3763-2a9b2d24cbb2"); }).then(function(characteristic) { console.log("Got characteristic"); // Pulse the light to show we're connected digitalPulse(LED,1,[100,0,100,0,100]); // Called when the value updates. Decode the data from the event characteristic.on('characteristicvaluechanged', function(event) { var dataview = event.target.value; // Gives us a JS dataview var resultArray = dataview.buffer; // Gives us an array of chars var text = String.fromCharCode.apply(String, resultArray); // Gives us a string newTextRxedFunction(text); // Call the handler function to update the graph/whatever }); return characteristic.startNotifications(); }).catch(function(e) { console.log("ERROR",e); }); }
- Added a 'connected' flag which is set false initially.
-
Thanks all for the info. It sounds as if I'm not missing an obvious onRead() at least, which was the thing I was wondering.
on Connect is useful to know. I figured another way (albeit a bit hacky) could be to write to the characteristic from the phone, which triggers the read operation which in turns updates the value with a notify.
As I mentioned I don't actually have a use for this right now - but am learning so just wanted to check the best way of doing this. So thanks for the suggestions - most helpful.
-
Robin - thanks for your reply.
I don't think those links covered it, though thanks for pointing them out.
To try to explain a bit better what I was meaning:
The pixl.js wireless temperature example above has a line 'setInterval(updateBT, 6000);' so updateBT() reads the temperature every 6 seconds, and updates the advertising packet. This is all you can do with advertising, as advertising doesn't know if anything is actually listening - it just broadcasts - like a TV station that keeps broadcasting even if nobody is watching the channel - it doesn't know. But doing this uses battery - and we're wasting battery taking temperature readings that nobody might ever want.
If my understanding is correct when using GATT services and attributes the model is different - the phone must actively send a packet to the Puck to ask "Please tell me the value of this attribute of the temperature service". This normally returns the value field specified in nrf.setServices via a packet which is sent from the puck to the phone.
Say (for example) we were developing a thermometer and we wanted the Puck battery to last as long as we could. This thermometer might be in a remote location, and is hardly ever used.
Ideally, the puck would do nothing but periodically wake the radio to receive and see if there is a phone there talking to it. Normally there isn't, so it just sleeps all the time and uses little battery. It doesn't need read the temperature in all this time so it doesn't, and saves battery.
Now say the user goes to the remote place to use this rather contrived thermometer. They get out their phone and connect to the Puck. The phone does its Bluetooth thing and sends the "Send me the value for this service and this attribute" packet to the Puck.
The Puck hears the packet and only then does it actually need to read the temperature. "Finally! At last! Someone is interested in my temperature!" So it calls some sort of 'onRead' callback or something. This callback does the work of reading the temperature, and sends back the value to the phone via another packet.
The user is done so the Puck has a good long sleep again, not ever needing to read the temperature again until the user comes back, and just sipping power from the battery as a result.
I hope this slightly contrived example illustrates better the sort of thing I'm getting it. I'm not trying to do anything new against the code I posted - just curious as it seemed like a way which would optimise battery life but couldn't figure it out. I don't actually have a need for this, but always like to learn a better way.
My point of "what if my function took a long time to execute": I don't think setInterval() would work here. I know BLE has connection intervals and things so that packets must be sent at the correct time. I was thinking that maybe the reason you can't do a read from a user 'onRead' callback (as I'm asking for here) might be that it could mess with the timing - if my callback took a long time it might mess up the timing and cause the Bluetooth stack to crash, or the phone to be upset if it didn't get a quick reply.
I don't think it's setScan either - the Puck already wakes up to service the request so I don't think I need to mess around with that.
I feel I've written a bit of a tome here - but hopefully my convoluted story illustrates better what I was trying to get at!
-
Hello,
I've been playing around with BLE with Espruino Puck, and it's been quite pleasant so far. I've got a question and I can't find the answer- which means it's probably staring me in the face.
I'm experimenting/learning and used NRF.setServices to create a characteristic and I can read the value from it using Nordic nRF connect on my phone. I can also use NRF.updateServices to update the value, and if I use notify the value automatically updates on the phone screen. Yay: Bluetooth success!
My question: Suppose (hypothetically) I turned the puck into a BLE thermometer. This might be used really infrequently, so I don't want to use setInterval with updateServices as this would keep taking temperature readings which were never used, and use more power than necessary.
I only want to do the temperature reading (or whatever power hungry thing) when the data is requested by the phone.
I want something like an 'onRead' function for the characteristic, much as there is onWrite. So my code would be something like
onRead : function() { return doReallyPowerHungryThingThatReturnsAValue() }
I can't find out how to do such a thing. Is this possible or is it a limitation of BLE/The Nordic SDK? (I can imagine some sort of timing restriction in BLE or something - what if my function took a long time to execute.).
I'm grateful for any teachings!
Thanks,
Colin
Example code: NRF.setServices({ "047b0001-37e3-457d-3763-2a9b2d24cbb2" : { "047b0002-37e3-457d-3763-2a9b2d24cbb2": { maxLen : 10, value : "Unknown", readable : true, writable : true, notify : true, onWrite : function(evt) { var text = String.fromCharCode.apply(String, evt.data); console.log("Message received: ",text); }, } } }, { uart : false }); function updateTemperature() { NRF.updateServices({ "047b0001-37e3-457d-3763-2a9b2d24cbb2" : { "047b0002-37e3-457d-3763-2a9b2d24cbb2" : { value : E.getTemperature().toString(), notify : true, } } }); }
-
The new tracker arrived today! It is a Nordic one and looks identical to the one @Gordon posted.
It's really weird: Seeing them together shows that they are extremely similar, yet different.
I posted some pictures showing the differences:
https://twitter.com/shortcolin/status/692838015526260741
https://twitter.com/shortcolin/status/692837420060921856
https://twitter.com/shortcolin/status/692836483376414726The Nordic chip variant feels to be higher quality - the band is softer and more flexible and the screen is much sharper and it updates much more quickly.
There are smaller differences - one has an oval shaped button, and the other a rectangular one. The battery clips are an almost identical design but yet made of a slightly different quality of plastic.
The Nordic based one advertises itself as "YDY_P102" to Bluetooth and the other one seems to have decided not to advertise any more....
What to do with it? I figure it would work strapped to a bicycle wheel as a cycle computer, or could make some kind of shoe-mounted device to communicate online by morse code - pub-quiz cheat-tastic!
-
No need to apologise - and thanks for the link. This device uses the Nordic Bluetooth service UUID and shares an app even though it doesn't have a Nordic chip in. I find it fascinating that there appear to be a few companies making identical looking devices but with different insides, yet which share the same Android app.
-
Hello,
Aaargh! I ordered one and it is the wrong thing!
It looks exactly the same as the one you have - same bracelet, case and charger. However the insides are different. It looks as though it has an LCD screen with a backlight rather than OLED screen and the chip inside is marked CC2541. This could be a TI chip with an 8051 processor, assuming it isn't a clone!
It has "xianwei" and v3 written on the PCB, along with 2015.12.17 which I guess is a surprisingly recent manufacturing date. There is also a swoosh logo on the PCB which reminds me of a certain other sports manufacturer.....
It's really interesting to do a search on alibaba.com for "smart bracelet nrf51822". There appear to be loads of identical looking products from different manufacturers. Some claim to use nrf51822, others a dialog semiconductor part, and I guess this one has a TI part. I'm not sure what it says that there are so many different companies making identical looking but different products. Now how to find the correct one on eBay...
-
Hello,
Looks very interesting! After my hacking with the Adafruit board last week I finally managed to get around to the unrelated stuff I was supposed to be working on. One ebay order later and you have managed to distract me a little bit more :-)
What do you use with openocd to program it? I'm a bit tempted to order one of the STM32 discovery boards with STLink on it, which I think should work..it's easier to attach to that than the JLink Lite I was using previously.
-
Out of curiosity I flashed the Nordic sniffer software and watched the trace between my phone and Espruino using Wireshark. Outside of the datalink layer that is implemented by the project I linked to there doesn't seem to be a huge amount - the L2CAP "layer" just seems to be 2 bytes of packet saying "This is L2CAP" followed by the GAP "layer?" which is a single byte opcode of 'read','write' etc. I don't particularly fancy reimplementing it - just curious.
I notice the devices continually send a "I have nothing to say" packet between them every few milliseconds when connected and idle. For a low energy protocol this seems like a strange choice.
If I remember correctly, the Nordic SDK has an example of using the radio raw which I think can work with an nRF24. http://dmitry.gr/index.php?r=05.Projects&proj=11.%20Bluetooth%20LE%20fakery is interesting too - he sends out BTLE advertisements using an nRF24 - I guess similar code would work on nrf51x.
-
I think you're right in that it is certainly possible to pair with a device and then reconnect to the service without advertising and I notice some Bluetooth devices have a specific "pair" button to initiate this. The Nordic UART app only seems to be able to connect to devices which advertise, so maybe it is best to keep advertising in for now if that is the sort of app that people are likely to use - at least initially until something else is written. Maybe it is a "depends on the use case" decision.
I saw a project: https://github.com/pauloborges/blessed which looks interesting - an open source BTLE stack for nrf51822. It would be interesting to see if the softdevice could be replaced with something simpler to give extra flash and RAM, and could potentially allow the device to talk to nrf24l01 style radios as well (which I think it should be capable of doing). If I have got my head around the acronym-soup that is BTLE the GAP and GATT layers would need implementing to allow something like a UART to work. More studying required...
-
I thought previously about flow control being set in Linux - I tried disabling it with screen but no change. Minicom was actually worse - I found it appeared to hang quite often and I had to kill -9 it. I decided to stick with hardware flow control and screen (that I knew worked previously rather than trying to debug problems with the hardware dongle/Linux serial). I'm actually using OS X with Linux in VMWare, so I might try native OS X serial sometime.
The schematic for the Adafruit board shows it uses P0.9 and P0.11 for serial, which is already defined in the NRF51822DK.py file . As it has the 32k RAM variant of the chip I guess it should work with no changes.
I had a look into the advertising, and it definitely stops advertising (or at least Nordic UART 2.0 does not see it to allow reconnecting after disconnection). I noticed that running "NRF.wake()" from the terminal allowed it to be seen again and reconnect.
I made the following change:
diff --git a/libs/bluetooth/jswrap_bluetooth.c b/libs/bluetooth/jswrap_blue index 6ecd36c..b8aec46 100644 --- a/libs/bluetooth/jswrap_bluetooth.c +++ b/libs/bluetooth/jswrap_bluetooth.c @@ -261,6 +261,7 @@ static void on_ble_evt(ble_evt_t * p_ble_evt) { case BLE_GAP_EVT_DISCONNECTED: m_conn_handle = BLE_CONN_HANDLE_INVALID; jsiSetConsoleDevice( DEFAULT_CONSOLE_DEVICE ); + jswrap_nrf_bluetooth_startAdvertise(); break;
I'm not 100% sure if this is correct as I don't really understand the Nordic UART states enough. I had a look around, and the Nordic code at:
calls advertising_start() on handling BLE_GAP_EVT_DISCONNECTED...looks to be the same as my change.
-
I tried flashing the same hex file onto the Adafruit board. Terminal over Bluetooth now seems to work so I'll assume the fix you posted earlier has done the trick. The serial Rx and Tx lines (P0.9 and 0.11) are unconnected.
I have to reset it after every terminal connection as it stops advertising. I had earmarked other things to do today so had better get around to them, but I'll try to have a look at that later.
-
Hello,
Flow control (or at least the RTS (or CTS?) pin being asserted) seems to be needed before the JLink dongle serial interface will transmit anything. To begin with I only got data from the console but couldn't send it anything and couldn't find the correct incantation to turn off flow control.
It turns out that the Bluetooth flakiness was caused as both the "nRF toolbox" and "nRF UART v2.0" apps were running on my phone. It looks as though one of them was trying to reconnect in the background. I rebooted my phone to stop any services that were running, or in case the Android BTL stack was upset, and Bluetooth seems reliable now. I have noticed that after closing a connection it stops advertising and hence I cannot reconnect. Not sure if this is intentional or not.
I'll try the Adafruit board later to see if it now works without a serial connection.
In the meantime, for anyone interested, my quick hacks to get it working with the Nordic PCA10000 dongle are:
Install JLink_Linux_V492_i386 and gcc-arm-none-eabi-4_9-2015q1-20150306-linux.tar.bz2 and add to PATH
Change RAM size in boards.py to run with 16k RAM, as copied from MICROBIT.py:
diff --git a/boards/NRF51822DK.py b/boards/NRF51822DK.py index 0ebc85e..a7be363 100644 --- a/boards/NRF51822DK.py +++ b/boards/NRF51822DK.py @@ -23,7 +23,7 @@ info = { 'default_console_tx' : "D9", 'default_console_rx' : "D11", 'default_console_baudrate' : "9600", - 'variables' : 200, # How many variables are allocated for Espruino to use. RAM will be overflowed if this number is too high and code won't compile. + 'variables' : 145, # How many variables are allocated for Espruino to use. RAM will be overflowed if this number is too high and code won't compile. 'binary_name' : 'espruino_%v_nrf51822.bin', 'build' : { 'defines' : [ @@ -36,13 +36,13 @@ chip = { 'part' : "NRF51822", 'family' : "NRF51", 'package' : "QFN48", - 'ram' : 32, + 'ram' : 16, 'flash' : 256,
Hack serial port config to allow HW flow control:
diff --git a/targets/nrf5x/jshardware.c b/targets/nrf5x/jshardware.c index e30afc8..5ae69fa 100644 --- a/targets/nrf5x/jshardware.c +++ b/targets/nrf5x/jshardware.c @@ -400,9 +400,9 @@ void jshUSARTSetup(IOEventFlags device, JshUSARTInfo *inf) { const app_uart_comm_params_t comm_params = { pinInfo[inf->pinRX].pin, pinInfo[inf->pinTX].pin, - UART_PIN_DISCONNECTED, - UART_PIN_DISCONNECTED, - APP_UART_FLOW_CONTROL_DISABLED, + 8, //UART_PIN_DISCONNECTED, + 10, //UART_PIN_DISCONNECTED, + APP_UART_FLOW_CONTROL_ENABLED, //APP_UART_FLOW_CONTROL_DISABLED, inf->parity!=0, // TODO: ODD or EVEN parity? baud };
I created a script to allow easier flashing - save as 'flash.jlink':
device nrf51822 speed 1000 w4 4001e504 1 loadfile espruino_1v84.70_nrf51822.hex r g q
build and flash using:
NRF51822DK=1 RELEASE=1 make && JLinkExe flash.jlink
and access the serial console provided by the JLink using (on Linux):
screen /dev/ttyACM0 9600
Thanks for the capsense info. I saw it before, but couldn't remember where. (You might notice the two 470k resistors connected to the module. That was a previous attempt at sending morse code!)...