Semi-persistent storage for widget state

Posted on
  • Hi,
    I am fairly new to javascript and I just got my bangle.
    However I already made my first widget, which does exactly what I want and is useful. The device really made this easy. Thanks.

    I have the following problem, however:
    I would like to preserve a state (boolean variable) in the widget, so that when I long press BTN3,
    the variable is not reset.
    I tried many things, including adding the variable to the global namespace from the widget. however each time I long press BTN3 all variables are lost.
    Is this expected ?
    Q: Is there any other way to preserve a widget's state between long presses of BTN3,
    apart from writing to storage ?

    I am planning to switch this state many times, and would like to avoid writing to flash.

  • Hi!

    each time I long press BTN3 all variables are lost. Is this expected ?

    Yes - because the device is a bit limited we decided that in order to make it as robust as possible we'd just reload everything each time, but that does mean state is lost.

    Q: Is there any other way to preserve a widget's state between long presses of BTN3,
    apart from writing to storage ?

    No, you'd have to use Storage, but if your value is changing state often and you don't want to write it all the time you can use the kill event to save the state of the value only when the Bangle is about to shut down: http://www.espruino.com/Reference#l_E_ki­ll

    var state = require("Storage").readJSON("myapp.json"­,1);
    if (state===undefined) state = { something: true };
    E.on('kill', function() {
      require("Storage").writeJSON("myapp.json­", state);
    });
    

    The only times it won't save state are when it shuts down unexpectedly (battery dies, 2 button reset, or actual firmware crash).

  • Thanks for the info. I might do the saving on kill event.

    Is there any other way ? Like just writing something to some memory location (I need to store a bool), some function call, anything ?
    I wanted to check if I could just write something at some memory location at random.

    The experiment:

    1.st: I find some memory address that I could potentially use by:

    var testArray = new Int8Array(10);
    E.getAddressOf(testArray,true);
    

    Return is 536883628;

    Then I write some number to this address:

    var num = 1 + 4 + 16 + 64;
    testArray[0] = num;
    testArray[1] = num;
    

    And check the output

    peek8(536883628,1);
    

    The return is 85, as expected.

    Now I reload the watch (long press on btn3).
    And try again.
    This time I get a different number (120 instead of 85).
    So this tells me the memory was changed and
    I can't use this trick to store some number between reboots.
    Is there another way ? Like some other memory location,
    some function to set some parameter ?

    Do I understand this correctly: long-pressing button 3 (or using load) will completely reset the js interpreter, and read all .boot files and so on ?
    Would it be possible to use a saved state of the interpreter (saved with http://www.espruino.com/Reference#l__glo­bal_save) which gets loaded at the beginning of a .boot script and contains a Int8Array ?

    Or is there really no way to achieve this ?

  • (Unintentionally partially duplicate post - so I edited to cover seperate concept(s) in separate posts)

    Conceptually it is possible to write something to the RAM and have it surviving a button 3 long press. I don't know the (RAM) memory map in detail, but altering where the stack begins could be an option (I'm leaning out of the window here by a lot...).

  • Initially I had some thoughts to go for use of a custom memory or an abuse of some config of any of the sensors. Unfortunately, none of the devices offers general purpose custom memory, and abusing a config value - for example of the GPS - is living on shaky grounds. Even with later option, data survives a power cycle only when written to permanent storage, which is also flash memory. (By the way, the GPS device uses the same concept of writing the RAM config to FLASH on demand / defined events. GPS power is controlled by the software and since an active GPS runs the battery down in less than 10 hours, it is not really an option. GPS has option of separate power for retaining current config and data in RAM, it is not wired that way.)

    Last alternative would be modifying the hardware... but I can tell you that regular soldering on no-lead devices with 0.2mm 'pins' of 0.4mm pitch is a challenge. I just recently tried to hookup a TDFN 14 3x3x0.75mm package. After many attempts I got it, but then 'hit' the 0.2mm wires when mounting it on a carrier and gave up. Even if you get connectivity - for 4 to 5 wires (or 6 wires with power control), you still need to find space for a serially controlled, non-volatile memory other than flash. There are such memories: see my post(s) about 256-Kbit (32 K × 8) Serial (SPI) F-RAM - Ferroelecric RAM - SPI challenges. That was 6+ years ago... and I'm sure there are now more suitable chips - and technology - out there, that also have better life cycle: MRAM.

    Last is to change the hardware completely - but I guess this is really not an option here: ti - Texas Instruments - has micro controllers that use FRAM as (some of their) RAM: no need to save and restore: 'it' - data - is just always there. See https://www.ti.com/microcontrollers/msp4­30-ultra-low-power-mcus/products.html with a particular chip that has only FRAM as RAM: https://www.ti.com/product/MSP430FR6007 - and a typical application: metering of services that cannot loose data under any circumstances... - Memory alone: https://www.cypress.com/products/excelon­-fram - and particular product with package small enough to fit: https://www.newark.com/cypress-semicondu­ctor/cy15b104qi-20lpxc/fram-4mbit-0-to-7­0deg-c/dp/39AH1712?mckv=smaXxg4SK_dc|pcr­id||plid||kword|cy15b104qi-20lpxc|match|­e|slid||product||pgrid|1224856154107520|­ptaid|kwd-76553668726599:loc-190|&s_kwci­d=AL!8472!3!!e!!o!!cy15b104qi-20lpxc&msc­lkid=9b857f5944fc1e7d3e2820dc434f3791&CM­P=KNC-BUSA-GEN-SKU-Semiconductors-ICs-07­

    NB: Have no financial interests in any of related organizations or businesses.

  • Using the memory location you get that way is always inside the memory area reserved for JS, so it'll always get cleared on restart. Potentially you could write to somewhere right at the top of stack - I think process.memory() might give you some addresses you could use.

    Is there another way ? Like some other memory location

    You could potentially find some piece of on-chip hardware that's not used (let's say QDEC the quadrature decoder): https://infocenter.nordicsemi.com/index.­jsp?topic=%2Fcom.nordic.infocenter.nrf52­832.ps.v1.1%2Fnvmc.html

    And then write to one of the registers that won't do anything unless it's enabled like SAMPLEPER - so address 0x40012508 - and then you'd be ok to peek/poke.

    Do I understand this correctly: long-pressing button 3 (or using load) will completely reset the js interpreter, and read all .boot files and so on ?

    Correct, yes.

    Would it be possible to use a saved state of the interpreter (saved with http://www.espruino.com/Reference#l__glo­bal_save) which gets loaded at the beginning of a .boot script and contains a Int8Array ?

    Yes, you could, but that's still saved to Storage... So it'll be a lot slower and more inefficient than just writing a single bool to Storage explicitly.

    Or is there really no way to achieve this ?

    There's no way to preserve data in RAM inside the JS interpreter between apps, no. I guess potentially something could be added, but it seems like with the kill event you've really got what you want - especially as it's a bool, if you write the same value Storage is smart enough to know and not change the file, so it's really not going to be wearing out your flash.

  • I am now storing the widget state in the SAMPLER register, with these two functions (and not setting it to false on boot):

    function getAlertsStoredState() {
        var addr = 0x40012508;
        var val = peek8(addr);
        val = val & 1;
        return val == 1;
      }
      
      function setAlertsStoredState( state ) {
        var addr = 0x40012508;
        var val = peek8(addr) & 254;
        if( state == true )
        {
          val = val + 1;
        }
        poke8(addr,val);
      }
    

    So far it looks like it works the way I intended.
    I would be interested to know if there is a way to make some code run only on first boot, but not on bnt3 long presses, but otherwise it works as expected.

    Thanks for all the advice and replies.

  • Great solution.

    Do you know what happens when power goes out?

  • When I reboot (long press BNT1 + BTN2) the state is lost.
    So I guess that during the hardware setup stage, the state of all registers is initialized to some defaults. As a consequence, my flag is set to 0.

  • I would be interested to know if there is a way to make some code run only on first boot, but not on bnt3 long presses

    No, I'm afraid not. However given that register is 0 on boot, you could just use one bit of it to store 'I've already run', to figure out if you're running after a BTN3 reboot or not?

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Semi-persistent storage for widget state

Posted by Avatar for Matjaz @Matjaz

Actions