-
• #2
Wow, that's pretty extreme! I'd say try using a Pico if your USB-TTL converter doesn't work - the flashing code for http://www.espruino.com/ESP8266 actually sets a Pico up as a pass-through.
With Puck-Puck there's a bit of a catch-22. The serial port takes power, so won't initialise itself if the Puck is powered on without serial connected (the RX pin pulled high). Obviously if you connect 2 Pucks together neither will initialise, so you'd have to manually enable serial on one first with
Serial1.setup
.I actually fixed a whole bunch of HID-related bugs just recently, so first it might be worth trying the attached zip file.
I'm still trying to get it working stably, but the attached build just happens to work fine by the look of it. There are 2 issues that might cause problems that the recent build fixes:
- If you try and send a key while disconnected it generates an error, but that stops you sending any more HID commands
- If you disconnect then reconnect, the connecting device expects Puck.js to remember some info about it, but Puck.js didn't - and that can cause some problems.
1 Attachment
- If you try and send a key while disconnected it generates an error, but that stops you sending any more HID commands
-
• #3
Ah, okay... I'm DFU'ing that build now, thanks.
I was thinking exactly of that Pico pass-through code before I wondered if I could use a second puck. I see what you mean about the Catch-22.
Anyway, I'll try this out. FYI, this is how it's being used:
-
• #4
Cool - what's the idea? Is that some kind of rubbery gauntlet thing?
-
• #5
It's actually rigid: Thermomorph / Polymorph, ie. polyester plastic that melts in hot water.
It's so that when I'm relaxing and recovering from back pain, lying down, I can just turn page on iBooks on iPad by sending a "right" keypress. That way I don't have to hold the iPad or reach up to turn page, thus saving my shoulder and upper back muscles from unnecessary tension.
Incidentally, I haven't managed to get the new firmware talking yet. In fact, it's having trouble connecting to the Web IDE. I think my Mac needs a reboot. Still trying.
-
• #6
Nope.. this build seems to just do a full reset (RGB LEDs all to full for about a second, then off, then a short red blink) when I try to connect to it via Web IDE, or pair it with the iPad.
I'm going to DFU it again to make sure it's not my code, but I doubt it.
-
• #7
That's really neat! I'd love to see Puck.js used for some more stuff like this!
I guess you could detect long presses (or maybe even use the magnetometer) and mimic other keypresses with it.
That's odd - maybe it's an issue with saved code (I had to move the saved code location and it might be messing up the pairing).
Can you try this on a working firmware, and then uploading the new one?
var f = require("Flash"); f.erasePage(0x75000); f.erasePage(0x76000); f.erasePage(0x77000);
-
• #8
Okay, that did the trick: I DFU'ed to 1.91, did the lines above, and then DFU'ed to 1.91.715. It now pairs more successfully.
However, it hasn't been entirely successful. It took about three goes of code tweaking, re-upload, resets, unpair/pair before the iPad would actually do the "page right" function. It's working okay now, even after an
NRF.disconnect()
. I'll keep an eye on it and see if it stops working again.FYI: at least with this firmware,
onInit()
doesn't seem to work. Once reconnected,dump()
yields garbage. -
• #9
Thanks for checking - I'll make sure I do something about wiping the old saved code in the final version.
Odd about the dump() problems though. I'll take another look - it's all really flaky at the moment for some reason - I've had a lot of problems with the Nordic peer management stuff :(
-
• #10
(Incidentally, I've just written up the project)
-
• #11
After exhausting a CR2032 in about a month, I've been tweaking my page-turner trying to make it more efficient.
I'm wondering about the best approach for saving energy between clicks as well as between sessions. I've got a timeout of ~10 seconds that is reset every time the button is clicked; when the timeout does activate, it performs an
NRF.sleep()
. Then, when the button is clicked, it doesNRF.setServices(...hid...)
andNRF.setAdvertising(...)
.I've tried doing
NRF.wake()
in the button's watch function, as well asNRF.disconnect()
during the sleep routine in an attempt to cut off the connection so the iPad doesn't keep thinking it's attached to a keyboard (and so hide the on-screen keyboard until I switch off Bluetooth)However, I get some weird bugs. With
NRF.disconnect()
reconnecting is unreliable, and sometimes all of the LED light flashes are slowed down, which makes me think there's something else going on.When
NRF.wake()
is included, I get:NRF ERROR 0x8 at ?:0 REBOOTING.
I assume
setDeepSleep()
is not relevant on the Puck, but what would you see to be the right sequence of events for this application?It's to be woken by a button click, and doesn't really require discovery when the device is "cold", so sleeping all of the BLE stuff seems reasonable; I'm not sure if an entire shutdown of the softdevice is in order. Disconnecting any HID connections when the device isn't being used is also desirable for the on-screen keyboard reason given above.
My code's attached, with the
NRF.wake()
andNRF.disconnect()
calls commented out for the time being.Any thoughts?
var hid = require("ble_hid_keyboard"); var long_click = 2000; // 2 seconds var double_click = 250; // 0.25 second // Detect short press ( < 2 seconds) var is_short = false; var short_timeout = false; // Detect fast-click count ( < 0.25 seconds) var clickcount = 0; var clearclick_timeout; // Sleep when not in use. Needs to be long enough that it doesn't // interfere with attempts to connect to it via the IDE. Saying that, // a triple click will postpone the sleep. var sleep_timeout; var sleep_delay = 10000; // 30 seconds var sleep_long_delay = 600000; // 10 minutes function setupSleep(is_long) { if (undefined !== sleep_timeout) clearTimeout(sleep_timeout); // Schedule the sleep function to run in the future. A button click // will reset this timer. sleep_timeout = setTimeout(sleep, is_long ? sleep_long_delay : sleep_delay); } function sleep() { // Flicker the LED to indicate sleep. This is really for debugging digitalWrite([LED1,LED2,LED3], 0x101); setTimeout(function () { // Clear the LED digitalWrite([LED1,LED2,LED3], 0); // Clear the timeout sleep_timeout = undefined; //// Disconnect any running HID sessions -- problematic; doesn't reconnect well. // NRF.disconnect(); // And shut down the chip NRF.sleep(); }, 250); } // Wake is called for two reasons: woken by a button press; and called by // init() for a full reset. function wake() { //// This is VERY problematic: big-time crash in 1v92 // NRF.wake(); // Set up HID (keyboard) services NRF.setServices(undefined, { hid : hid.report }); // Name it NRF.setAdvertising({},{name:"Page-Turn-O-Matic 4000"}); } // Upon reset of the device function init() { // Clear any current button event handlers clearWatch(); // Just in case, we want to make sure this is off. Puck.magOff(); // Set up BLE wake(); // Set up button event handlers for both rising and falling. These will // also take it out of sleepage. setWatch(btnPressed, BTN, { repeat:true, edge:'rising', debounce : 50 }); setWatch(btnReleased, BTN, { repeat:true, edge:'falling', debounce : 50 }); } // The primary action, on a single click function primary() { hid.tap(hid.KEY.RIGHT, 0); } // The secondary action, on a double click function secondary() { hid.tap(hid.KEY.LEFT, 0); } // Triple-click function tertiary() { // Postpone the sleep cycle for a long time (ten minutes) for debug purposes setupSleep(true); } // The reset function, on a long click (>2s) function long() { NRF.disconnect(); init(); } function btnPressed() { // When the button's pressed, we clear the LEDs. Any // LED activity is on _release_ digitalWrite([LED1,LED2,LED3], 0); // Count clicks in chain clickcount ++; // Reset execution of short-click chain if (undefined !== clearclick_timeout) { clearTimeout(clearclick_timeout); clearclick_timeout = undefined; } // Assume it's a short press is_short = true; // Set a timeout for two seconds to recognise a long press short_timeout = setTimeout(function () { // It's been two seconds, so... // Long press detected is_short = false; short_timeout = null; // Full blast RGB digitalWrite([LED1,LED2,LED3], 0x111); // and don't do anything until release... }, long_click); } // Once a chain of repeated rapid clicks is over (ie. // the 0.25 second threshold has passed)... function chainEnded() { var o = clickcount; clickcount = 0; clearclick_timeout = undefined; // Reset the sleep watchdog wake(); setupSleep(); switch (o) { case 1: // Simple click; GREEN digitalWrite([LED1,LED2,LED3], 0b010); primary(); break; case 2: // Double-click; RED digitalWrite([LED1,LED2,LED3], 0b100); secondary(); break; // Triple-click, etc. can be added as additional `case`s. case 3: // Triple-click; YELLOW? digitalWrite([LED1,LED2,LED3], 0b110); tertiary(); break; default: // Too many clicks. Ignore. break; } } function btnReleased() { // `short_timeout` is there to _deny_ a short // click. If it times out, then it means the button // has been held longer than a short click. // // So, for a short click, if the timeout is still going // then clear it: `short` should remain whatever it is, // including `true`. if (short_timeout) { clearTimeout(short_timeout); short_timeout = null; } if (is_short) { // Set a timeout to process short clicks clearclick_timeout = setTimeout(chainEnded, 250); } else { // Long press: reset digitalWrite([LED1,LED2,LED3], 0x001); clearclick_timeout = undefined; long(); } // And clear any LEDs after a reasonable period. setTimeout(function () { digitalWrite([LED1,LED2,LED3], 0); }, double_click); } // Set the initialisation function E.on('init', init);
-
• #12
Have you tried
NRF.setLowPowerConnection(true)
?Realistically it'll be the actual HID connection to the computer that sucks all the power, rather than the advertising. setLowPowerConnection modifies the connection interval so next time you connect it's only polling twice a second rather than ~50 times a second.
Worrying about the
NRF.wake
causing a reset - I wonder - is it possible it's being called when you already have a BLE connection? It basically enables advertising, but you can't advertise when connected so I wonder if I've actually accounted for that.I'd give it a shot with all the wake/sleep/disconnect stuff removed, and just with setLowPowerConnection - I reckon that should make a massive difference.
-
• #13
Thanks, I'll try that. I do want to get
disconnect
working in some way, as I want it to actively disconnect from the iPad so the iPad doesn't keep thinking it's got an external keyboard; unless I can figure out how to get it to realise that it's not really a keyboard, which I imagine will be nasty -- it's nasty to do in Bluetooth Classic, IIRC.So, do you think keeping track of the HID connection (the
BluetoothRemoteGATTServer
via the Promise, presumably?) and issuing aserver.disconnect()
is the way to go?(EDIT: Oh, hang on... that won't work... these connections are initiated by the iPad; there's no
connect
call going on, and we're the server, so there's noBluetoothRemoteGATTServer
object. Hmm. If acting as a server rather than a client, is there a way to selectively disconnect a connection, or is it just a case ofNRF.disconnect()
and let the UART service and anything else be damned? ) -
• #14
Yes - I think you'll need to just do
NRF.disconnect()
- and I guess if you want to avoid it reconnecting you'll need to use sleep and wake.If you can reproducably get the 3 lights on with a small big of code it'd be great (even better if it was without HID) - then I should be able to track it down a lot better. I wonder whether it might be related to Bonding again...
-
• #15
I was going to say I've managed to isolate it down to a few lines of code, but ended up with this situation:
=undefined _____ _ | __|___ ___ ___ _ _|_|___ ___ | __|_ -| . | _| | | | | . | |_____|___| _|_| |___|_|_|_|___| |_| http://espruino.com 1v92 Copyright 2016 G.Williams >dump() pinMode(D29, "input_pulldown", true); =undefined >NRF.setAdvertising({},{name:"Test"}); NRF ERROR 0x8 at ?:0 REBOOTING. Disconnected
Hmm. I think there's something rather wrong. I've DFU'ed it back to 1v92, and tried various resets and flash resets. This is since I started doing
NRF.setLowPowerConnection(true)
, although I've since doneNRF.setLowPowerConnection(false)
a few million times.UPDATE (#3): I've managed to get it (and the other Puck I soft-bricked) back using the five-green-flashes method,
E.setBootCode()
, although that didn't seem to help originally.This is all on 1v92 stock on one puck and 1v92.3045 (a fresh build) on the other.
I think the
NRF ERROR 0x8
is happening when it keeps reconnecting and then advertising changes while the connection is active, or something like that. I'm not sure if that makes sense.I've had big problems with
setLowPowerConnection
, as it messes with the timing, giving me runs of repeated taps.From my limited understanding of BLE, I can see that the idea of getting the client (the iPad) to stop connecting to the Puck is problematic. The only way I can see this working is if I can shut down BLE, or HID advertisement, and then restart it when a button is pressed to wake it up.
setLowPowerConnection
alone might save power, but it won't handle the disconnection I need for keyboard access.I'm still rather unclear on what can and can't be done while connections are open, and so forth. Some commands will queue for the next reset, but things like
NRF.setAdvertising
,NRF.sleep
,NRF.wake
might not have the right guards in there, and I think they may be causing the crashes and reboots.I'm still trying to isolate some code to demonstrate it.
-
• #16
It's really strange about your error 8 - I guess it must have been something in the saved code.
I think I need to come up with a better way of handling the 'save on send' thing - it seems a lot of people are hitting problems when turning it on, uploading, then trying to do things after it's turned off if the existing code is still saved.
It definitely looks like there's some issue with
wake
,sleep
andsetAdvertising
as you say.sleep
andwake
should do what you want (stopping advertising) but I bet there are some checks missing in there (maybe callingsleep
orwake
while connected?). They tend not to be used that much so I haven't focussed on them as much as I should - I'll see if I can find a way to reproduce it here as well. -
• #17
Ok, I managed to reproduce it by calling
NRF.sleep
during an active connection, and I've now fixed that - so if you use one of the absolute latest travis builds then it should work for you.When built, they should be available here: http://www.espruino.com/binaries/travis/9c179b2421900319c045c7eb39362caf3568b921
-
• #18
Thanks… that does seem better as far as the
ERROR 0x8
fatal crashes are concerned. I'm still having trouble with a lot ofBLE error 13313
s, though... looks like my services are getting munged, or something like that.I'm not really too sure about the order of execution here, and what's necessary in what order; such as whether
NRF.setAdvertising()
needs to be called after anNRF.wake()
, whetherNRF.setServices()
needs to be called after aNRF.setAdvertising()
. A simple test rig seems to work fine, but as soon as I start introducing a) HID and b) NRF.sleep/wake, it all starts going horribly wrong.I expect this would be easier if I wasn't trying to do HID and UART through Web Bluetooth (which is hinky at the best of times) but finding a USB-to-Serial or rigging a Pico to do the same job and then trying to connect to the Puck non-destructively is a big PITA. I'm also suspicious of connecting to Espruinos via serial because whenever I've tried the different redirection of the console seems to change behaviour subtly.
Anyhoo. I'll soldier on. Thanks
-
• #19
I've never seen an error 13313 before! If you can get something simple that reproduces it then I'll see what I can do to fix it though.
For the order of setAdvertising/setServices and sleep/wake - it shouldn't matter. If they're set up when in
sleep
, it should just apply when youwake
. Internally, if the device is 'awake',setAdvertising
actually callssleep
, sets up advertising, then callswake
- so it should be fine either way. -
• #20
The
Error 13313
s seem to come whenhid.tap()
calls are made on a HID connection while also doingNRF.disconnect()
,NRF.sleep()
,NRF.wake()
in various combinations. I'm not entirely sure yet. I think it might be a case of the service characteristics getting lost after anNRF.wake()
, but redoing them fails too. I'm seeing these 13313 errors in the Espruino IDE when connected with Web Bluetooth, so there is a connection open with the computer, albeit UART.BLE_ERROR_GATTS_SYS_ATTR_MISSING
composes to13313
. Pertinent? https://devzone.nordicsemi.com/question/6085/strange-error-code-13313-0x3401-returned-by-sd_ble_gatts_hvx/?answer=6106#post-id-6106The context: the script sets timeouts to make it execute
NRF.disconnect(); ... NRF.sleep()
after a set time (5 mins) of inactivity; each button press willclearTimeout()
and start a new 5 minute timeout. Then, when the button is pressed, anNRF.wake()
call is made if necessary, beforehid.tap()
is called subsequently on release.When this behaviour works (it has sporadically worked in random hack versions along the way) it works really well - the Puck disconnects and shuts down when idle, and then wakes up and posts a keypress when it's activated with a click (forward) or a double-click (back)
And, if the on-screen keyboard is wanted, you can prematurely disconnect and sleep the Puck with a triple-click. That also acts as a manual "Off" when you remember to do so rather than idling it for five minutes.
A long click does a disconnect without a sleep, allowing Web Bluetooth to see the Puck for long enough to connect to it for maintenance.
Anyway, I wouldn't analyse that too much: part of the problem of getting you a neat little test case is the unpredictability, and the limitation of debugging while also having connects and disconnects, sleep and wake happen at the same time... I'm mainly working on cryptic LED flashes. I am still trying to isolate it though. My weekend's packed, unfortunately.
-
• #21
As far as I understand it, HID works a bit like this:
- Device wakes, starts advertising
- PC/Mac sees it some time after
- PC/Mac connects
- It requests a list of services
- It does interesting things with those services and remembers stuff about them
- It requests to be notified to some of them
That all takes a bunch of time though, so even if you get the
connected
event I guess the HID might not have been initialised enough that a call to the NRF HID function would actually work.What happens if you leave a reasonable delay (5 seconds?) between connecting and trying to send any HID packets?
Also, when you're getting that error, is your Mac actually showing the device as a HID device? Or does it show it as a normal serial device?
Are you calling
setServices
orsetAdvertising
more than once at all? I guess that could mess things up too - if the services change then the CCCD numbers might change, and that could confuse the computer. - Device wakes, starts advertising
-
• #22
I haven't tried having a delay per se., but when it's connected to the IDE and producing 13313 errors, it'll do it whenever the button is pressed, repeatedly for as long as you like.
I have been running
setServices
andsetAdvertising
whenever it comes out of sleep withNRF.wake()
too... I wasn't sure if doingNRF.sleep()
wiped the existing records. Regardless, I was trying to dosetAdvertising()
to update the battery level, so it would need to be called repeatedly.To be honest, the lack of an
isConnected()
and anisSleeping()
method is a bit inconvenient. Getting the status of the softdevice would be useful, rather than trying to track it manually. -
• #23
Hmm. Something's getting stuck, I think.
With this stripped-down code on 1v92.91; after a power cycle and an
E.setBootCode("");
, an upload and a refresh, I get Error 13313 when clicking. However, if IRemove
(ie. unpair and forget) the Puck with Opt-click on the menu bar, it works okay.var hid = require("ble_hid_keyboard"); var is_connected = false; var ad_values = {}; var ad_options = { name: "Page-Turn-o-Matic 4000" }; function on_connect () { digitalPulse(LED3, true, 100); is_connected = true; } function on_disconnect() { digitalPulse(LED1, true, 100); is_connected = false; } function btn_released() { if (is_connected) { digitalPulse(LED2, true, 100); hid.tap(hid.KEY.RIGHT, 0); } else digitalPulse(LED1, true, 500); } function init () { NRF.on('connect', on_connect); NRF.on('disconnect', on_disconnect); clearWatch(); setTimeout(function () { setWatch(btn_released, BTN, { repeat:true, edge:'falling', debounce : 50 }); NRF.disconnect(); // ad_values[0x180F] = Math.round(Puck.getBatteryPercentage()); NRF.setAdvertising(ad_values, ad_options); NRF.setServices(undefined, { hid : hid.report }); }, 3000); } E.on('init', init);
I'll start putting stuff back in, bit-by-bit, and unpair if/when it goes bad. It's times like these I really wish Puck had a USB port.
-
• #24
:) Yeah, it's not that hard to add a USB-serial converter to it, but doing the wireless programming can be a bit of a pain.
I'd call
setServices
andsetAdvertising
immediately ononInit
if I were you - and while calling setAdvertising again shouldn't be an issue I'd avoid calling setServices - that'll trigger a softdevice restart which could confuse the Mac.It sounds a bit like what's happening is the Mac is 'forgetting' that it's a HID device though, so when it connects it doesn't request the HID stuff, and then you get the error when trying to send a keypress. It's possible that now it's working, without the repeated
setServices
, it'll keep working.However if you connect again at any point when it's reset, the Mac might see there's no HID and then stop treating it as a HID device - so you'll have to go through that whole remove/pair thing again :(
-
• #25
The delay during
init
is really so it can do a disconnect -- and hence do theset*
calls without queueing -- without interfering with the upload loop. I've found that ifonInit
immediately disconnects, it can confuse the IDE.I've run out of batteries for the pucks right now -- I've been away from home this weekend -- but I think the code works fine until
sleep
andwake
get involved. After that, it's all a bit unpredictable. Now I've got a barebones implementation, I'll try to add them back in.
Hey,
I'm using the HID Keyboard example - https://www.espruino.com/Puck.js+Keyboard plus simple LED on/off with button press/release - to try to send a single keypress to an iPad.
While it sometimes works, once it's cut off it does nothing: the LED still lights when pressed so the code's still working, but there's no effect on the iPad. The Puck is still paired and connected with the iPad.
Now, I've noticed https://github.com/espruino/Espruino/issues/1101 and some other ideas, but what's really tricky is the limitation that the Puck must be disconnected from the IDE to pair it with the iPad: only one connection at a time. It means I have no error messages visible to debug the problem.
My NoName™ FT232 USB-to-serial board is not working for some reason - probably lack of flow control - and the wiring is unwieldy, so I was thinking the other Puck I got from Kickstarter might be an easy way to do it. Failing that, I might rope a spare Pico in to act as a dumb serial converter.
So, I guess my question is, what's a straightforward way to cross-connect the serials of one Puck with another Puck, so the IDE (or at least the command-line errors) can be viewed while still connected as a HID device? Or, any other approaches to try?
Thanks,
Tom