-
-
-
When you switch apps on the Bangle all the volatile state of the currently running app and of the current set of active widgets is lost. This is because the Bangle loader completely replaces the currently running app. This contributes greatly to the robustness of the Bangle since an error in an app is contained. However, it does mean that any widget state such as a Bluetooth connection is lost - see here.
I frequently switch between different clock apps to increase visibility, for more information etc and it occurred to me that a full app context switch is not necessary. Consequently, I have developed multiclock an app which supports multiple clock faces. You can try it from my development repository app loader at jeffmer.github.io.
It currently has three clock faces. Each clock face is described by a javascript file with the following format:
(() => { function getFace(){ function onSecond(){ //draw digits, hands etc } function drawAll(){ //draw background + initial state of digits, hands etc } return {init:drawAll, tick:onSecond}; } return getFace; })();
For those familiar with the structure of widgets, this is similar, however, there is an additional level of function nesting. This means that although faces are loaded when the clock app starts running they are not instantiated until their
getFace
function is called, i.e. memory is not allocated to structures such as image buffer arrays declared ingetFace
until the face is selected. Consequently, adding a face does not require a lot of extra memory.The app at start up loads all files
*.face.js
. The simplest way of adding a new face is thus to load it into Storage using the WebIDE.I have not submitted a PR for the official repository as I am not sure that this is a problem that only I have due to the ANCS widget. However, it does allow very fast clock face switches.
-
Just submitted a PR for a tilt compensated compass. Seems pretty stable with respect to roll and pitch if you are conscientious about calibration. If anyone is eager to try it before it appears on the official app loader, it is available now at https://jeffmer.github.io/JeffsBangleAppsDev/.
-
Thanks - that's perfect - I sometimes have difficulty getting used to the event-based programming paradigm. This version is much more responsive with respect to widgets and buttons as you can see from the screenshot below - the ANCS widget now gets time to connect properly. The app now computes a generation in 16 slots with each slot being 60ms which includes idle time in each slot. You can see the overall time is approx 960ms.
-
Here is a screenshot of a 16x16 version of Conway's Game of Life that I have just got to work with reasonable performance:
It uses two
Uint8Arrays
to store the state of the current and next generations. The real performance issue is computing the number of active cells in the 8 surrounding neighbours of a cell. I have used sentinel rows and columns in the arrays to avoid boundary checks and a flattened loop to compute the count. This together with using the Google optimiser in the WebIDE which inlines functions and turns conditional statements into expression etc together with keeping the critical routine in memory and tokenisation gives the sub 1 second generation time. You can try it at https://jeffmer.github.io/JeffsBangleAppsDev/.
I will submit it to the official app loader when I have tidied it up.I have used a graphics buffer and drawImage to display the cells as I wanted to be able to run widgets and to have the nicely delineated cells.
An interval timer is used to start a new generation computation every second. As you can see, the computation and display takes on average around 900ms leaving 100ms for widget update etc. This is usually OK, however, I would like to distribute this idle time more evenly during the generation computation to allow more responsive widgets. I could introduce a delay/reschedule call after computing very row, however, I am not sure how to do this - any suggestions?
-
-
I have quite a few widgets running such as battery, bluetooth, gpsrec, ANCS etc and although they are incredibly useful, they do clutter up the screen and take away from the design simplicity of some of the clock faces I use. In an attempt to have the best of both worlds, I have added the following code to the clock apps I use. Touching the left side of the screen hides all widgets and touching the right side re-displays them.
var widgetVis = { saved:null, hide:()=>{ if (!Bangle.isLCDOn() || this.saved) return; this.saved = []; for (var wd of WIDGETS) { this.saved.push(wd.draw); wd.draw=()=>{}; } g.setColor(0,0,0); g.fillRect(0,0,239,23); }, reveal:()=>{ if (!Bangle.isLCDOn() || !this.saved) return; for (var wd of WIDGETS) wd.draw = this.saved.shift(); Bangle.drawWidgets(); this.saved=null; }, setup:()=>{ setWatch(this.hide, BTN4, {repeat:true,edge:"rising"}); setWatch(this.reveal, BTN5, {repeat:true,edge:"rising"}); } }; widgetVis.setup();
It is not enough to simply blank out the widget area and then avoid calling
drawWidget()
since many widgets redraw based on timer events etc. The methods above replace widget draw functions with null functions and then restore the old draw functions when widgets are not hidden.If others feel this is a useful facility, it might be sensible to have a standard way to hiding widgets while leaving them running. There may well be a better way of doing this than the one I have outlined.
-
-
Yes, Gordon’s
createArrayBuffer
anddrawImage
with palettes are exactly the same mechanism. Actually you can usually get away with one or two buffers and move them around to write at different times. Mygpsnav
app uses only one buffer which is used every 200ms to draw the compass display and once a second for numerical data display.Combining javascript and compiled C in your driver is really neat, I was going to ask to see it!
-
The rotating cube looks quite fast - fast enough to use as you note. With hardware supported SPI, I have previously managed to get quite good performance for a relatively simple Arduino based ILI9341 driver - see.
-
-
Apologies for the length of this post, however, I would really like to find out if the widget works for other people and I would appreciate feedback on some of the issues mentioned in the following.
iPhone ANCS widget available for testing
This widget allows you to answer or cancel iPhone incoming calls and also displays messages. It connects to the Apple Notification Center Service which is already on all iPhones, so you do not need to install any additional iPhone apps to use this widget.
Warning - this is very much a beta release and although so far it appears quite stable, you may get the odd crash when moving between apps which will require a reboot - pressing BTN1 & BTN2 for 6 seconds. See issues at the end of this file.
Installation
First,you must update to the latest Bangle firmware - v2.05v27 or later.
This beta version can be installed from this repository's app loader - https://jeffmer.github.io/JeffsBangleAppsDev/. The widget will only run with a compatible clock app - see below. There are three listed in the App loader so install one or all three as they are linked and pressing BTN1 or BTN3 if they are not all installed will cause a black screen of death. Once installed, the widget will only appear in a compatible app when enabled in the Bangle Settings app -
ANCS Widget
will appear inAPP/Widget settings
.iPhone Pairing
Once enabled, the widget icon should be displayed coloured grey (its green in the photo). Go to the phone's Bluetooth settings menu and your Bangle should appear under Other devices. If this is the first time you have connected with the Bangle from your iPhone, it may be named Accessory. Click on the name and the iPhone should connect and start pairing. The widget icon will turn red and the iPhone will ask you to enter a pairing code - the traditional 123456. After that, the iPhone may also ask to allow the device access to ANCS. Once pairing is complete, the widget icon should go blue and eventually green. The range of colours is:
- Grey - not connected - advertising
- Red - connected - not paired.
- Blue - paired and connected - getting services
- Yellow - got Services - discarding old notifications.
- Green - waiting for new notifications - calls and messages only in this implementation.
After pairing the first time, the Bangle should connect automatically when the widget is running. Sometimes you may need to click on the Bangle name in
Settings:Bluetooth:My devices
on the iPhone or disable and then enable Bluetooth to start connection. You may also need to get the iPhone to forget the bond if you want to connect the App Loader or Web IDE.Messages & Calls
Messages are displayed as shown above until BTN2 is pressed to dismiss it. I strongly advise disabling the BTN2 LCD wake function in the Settings App as otherwise when the screen times out and you press BTN2 to wake the LCD, the screen will turn on and the Message Alert will be dismissed!. Calls can be answered or dropped.
.
Issues
With GadgetBridge, the Android phone has a Central-Client role with the Bangle as Peripheral-Server. With the ANCS widget there is the fairly unusual situation in which the Bangle is Peripheral-Client to the iPhone's Central-Server role. Firstly, since Espruino does not deal explicitly with Bangle as Peripheral-Client, we have adopted the bodge outlined in an earlier posting and immediately connect back to the iPhone after the iPhone initiates the connection due to its Central role. This usually works, however, sometimes and especially when phone and Bangle are quite far apart, the Bangle connects in Peripheral-Server mode and the widget cannot get the Peripheral-Client connection it needs. The symptom is that the Bluetooth widget goes blue indicating connection from Central and the ANCS widget icon is grey indicating it has no connection. This should be fixed when Gordon deals with Issue 1800. Currently - the solution is to bring the Bangle near the phone and switch apps to force a reconnection attempt.
When the Bangle switches apps, all state - including widget state - is lost unless explicitly stored. The consequence of this is that when the Bangle switches apps, the connection to iPhone has to be re-established to restore the remote GATT server and characteristics state. This is quite slow. To minimise reconnection, the widget needs to grab the screen from the running app to signal messages and calls. To allow this to work, the app needs to implement the
SCREENACCESS
interface. In essence, the widget only connects when running with compatible apps that implement this interface. An example of an implemented interface is:var SCREENACCESS = { withApp:true, request:function(){ this.withApp=false; stopdraw(); //clears redraw timers etc clearWatch(); //clears button handlers }, release:function(){ this.withApp=true; startdraw(); //redraw app screen, restart timers etc setButtons(); //install button event handlers } } Bangle.on('lcdPower',function(on) { if (!SCREENACCESS.withApp) return; if (on) { startdraw(); } else { stopdraw(); } });
I would be very interested in a less instrusive way of doing this, however, the solutions for full screen alerts that I have seen involve switching apps.
- Grey - not connected - advertising
-
-
-
-
Looking at the following piece of code from
targets/nrf5x/bluetooth.c, line 518
- see below -which handles the IOEvent, it looks to me like the data is associated with the characteristic not the event, so that if the second IOEvent is processed before a callback occurs, the second set of data will be installed in the characteristic value attribute and the callbacks for both events will thus see the same data. This implementation - if my understanding is correct - will always mean that the client sees the latest value of the server data, however, it means intermediate values are lost. It is reasonable if the client simply needs to monitor the value of a changing server variable, however, it leads to the current problem with ANCS. Not sure the best way to fix this, however, rather than create another queue, it might be better to defer processing of the second Bluetooth IOEvent until the first callback has occurred.case BLEP_NOTIFICATION: { JsVar *handles = jsvObjectGetChild(execInfo.hiddenRoot, "bleHdl", 0); if (handles) { JsVar *characteristic = jsvGetArrayItem(handles, data/*the handle*/); if (characteristic) { // Set characteristic.value, and return {target:characteristic} jsvObjectSetChildAndUnLock(characteristic, "value", jsvNewDataViewWithData(bufferLen, (unsigned char*)buffer)); JsVar *evt = jsvNewObject(); if (evt) { jsvObjectSetChild(evt, "target", characteristic); jsiQueueObjectCallbacks(characteristic, JS_EVENT_PREFIX"characteristicvaluechanged", &evt, 1); jshHadEvent(); jsvUnLock(evt); } } jsvUnLock2(characteristic, handles); }
-
-
It is easy to reproduce if you have an iPhone! Seriously though, when first connected, ANCS sends a stream of the outstanding notifications and they have sequence numbers so it’s easy to see that messages are overwritten. Android also can send up to 6 packets per interval so you it should be possible to reproduce using Android. Not sure if nRF connect has the facility to quickly repeat notifications.
I plan to experiment by creating cloned versions of the two queueing routines involved that do not use interruptOff and interruptOn and use these in the notification handler. I will let you know the result. The situation with notifications is fairly unique I think as usually interruptOn is only called after an event is queued and you are more or less finished with the IRQ. For reads of course, these are request response and the multiple message per interval probably does not happen.
Is there an easy way to look at the contents of the IO event queue without using a Jlink debugger etc?
-
Thanks for the pointers. I had a look through the code and it occurred to me that one might see exactly the behaviour I describe if the second IRQ signalling notification interrupted the first. I read in the Nordic documentation that softdevice event handlers usually run at priority 2 and I see that the Espruino IO queueing routines disable and enable interrupts to preserve exclusive access to shared queue structures, however, it was not clear to me as to whether the enable routine set a fixed priority or returned it to the priority that the IRQ handler was running at before the queueing operation was called.
Do you think this might be a possibility or I have I got this completely wrong?
-
I have made quite a lot of progress with this and then hit another roadblock:
Progress: The Bangle - or actually Puck.js which I am using for testing - can connect and bond reliably to the iPhone such that the iPhone automatically reconnects when it's Bluetooth is turned off and then on again. The Puck can create the required remote service and characteristic objects and send commands to start notifications etc. I can decode the data I get back from the iPhone on messages, calls etc.
Problem: Data is transferred by the iPhone ANCS service using notifications. Each notification message can transfer 20bytes of user data. The problem arises when the data to be transferred is greater than 20 bytes. In this situation the iPhone sends multiple notification messages. The Puck gets the correct number of
characteristicvaluechanged
events, however usually, for two events received consecutively,the data associated with the first event will be the data from the last notification message. In other words, each of the two events has the same data values.@Gordon: - I have tried increasing the minimum interval which can be set during connection, however, the iPhone can send up to four packets per interval and it is obviously doing this when the data to be transferred is greater than the MTU of 20 bytes. The soft device is clearly getting these messages as I get the right number of events, however somewhere either in the soft device or the transfer to Espruino, the data is getting lost - any suggestions as to where in the code this might be happening?
-
I had something similar happen a few weeks ago with the screen blank and the watch completely unresponsive. I guessed that it might be in some sort of uninterruptible loop as the watch seemed to be discharging quickly. The screen was blank and no bluetooth connection so I estimated this by measuring the charge current after leaving the watch for a bit. I fixed it by opening up the watch and shorting out the battery to force a reset. After doing this, I reconnected the charging cable to reset the battery protection circuit. A safer way would be simply to wait for a long time and then reconnect the charger:).
-
I implemented the hack suggested by Gordon in the firmware for a Puck.js and it does work, however, after some experimentation, I have realised that while interesting - it is not necessary!
Essentially, after getting the address of the iPhone using
NRF.on("connect",function(addr){})
, you can get a connectedBluetoothRemoteGATTServer
object usingNRF.connect(addr)
. This causes a disconnect event with reason 8, however, the method returns a connected object that can then be used for bonding. I was able to discover this as I connected the REPL to the physical Puck UART and could thus interrogate objects while bluetooth was connected to the iPhone.So, I have now been able to get the iPhone notifications on the Bangle without any firmware changes. The only issue with this method is that the
startBonding()
method promise is fulfilled before bonding is actually complete. This can be solved by waiting a bit and checking the security status too ensure the connection is encrypted. -
Thanks for the quick response.
Yes, I am reasonably certain about this. I have had a look at the Arduino ESP32 implementation of ANCS access and in fact, I have traced its connection protocol with NRf Sniffer and Wireshark and it is pretty clear that the iPhone has both the Master/Central role and that it also provides the service.
Someone tried to do this for a Puck two years ago - http://forum.espruino.com/conversations/314651/ - and I am sure this and the fact that you have to advertise the solicited Service UUID is why they failed.
Resolution of this along the lines you suggest in the issue would be very elegant indeed. I have managed to build the firmware from source, so I may try to hack something in the interim.
I just encountered the same problem:
I cannot see find an option to disable spiram during the build except in the
sdkconfig
file which is generated and says DO NOT EDIT?