Trouble with interface.html

Posted on
  • Hi all, I'm working on my first app and am having a really frustrating time getting interface.html working the way I want. The only docs I'm able to find for this are in the BangleApps README, and it just doesn't seem adequate at all.

    The problem: I want a “download” button in the app loader that when pressed displays a page with a very simple HTML representation of the data logged in my app. I already have a small function in my app's app.js that returns the desired HTML code as a string, and I just want to call that function and inject it directly into the page as shown in the README example. But for some reason, I can't get it to work at all.

    Here is what my interface.html currently looks like:

    <html>
      <head>
        <link rel="stylesheet" href="../../css/spectre.min.css">
      </head>
      <body>
        <script src="../../core/lib/interface.js"></scri­pt>
        <div id="content">Loading...</div>
        <script>
          function onInit() {
            Puck.eval("load('timestamplog.app.js')",­ output => {
              Puck.eval("printHTMLStampLog(stampLog)",­ output2 => {
                document.getElementById("content").inner­HTML = output2;
              });
            });
          }
        </script>
      </body>
    </html>
    

    The idea is to load my app first (so that I have access to its routines—there's probably a cleaner way to do this, but my focus is on making it work first, then worry about refactoring) and then call the function that runs on the Bangle itself and outputs the HTML I want injected into the page. The app gets loaded, but that's as far as it gets. The second call to Puck.eval just returns nothing, every time. And I can see from the Espruino console that my printHTMLStampLog function does get called and outputs the desired data, but it just doesn't get returned.

    What's going on here? After a full day and a half of investigation, I suspect a race condition, because if I fumble around in the browser console and run the second .eval() call by itself after my app is already loaded, it kind of works (except it's somehow not rendering the HTML tags, but we'll deal with that later). I was under the impression that this code would load the app and only call the second part once the first part finishes. Instead, it seems to run the second part too early, and it usually returns nothing, or occasionally, some other nonsense from the Espruino console that has nothing to do with what my app's code is actually writing.

    I've looked at a lot of source code for other apps, but it's not getting me anywhere. No one seems to try to use their app's assets directly, and instead they just dump all the code for reading/writing and encoding/decoding data in interface.html. That's not what I want to do because that means needlessly copying and pasting perfectly good code I already wrote into multiple files, and that's a code-maintenance nightmare. I do not wish to take that route. I'd like to leverage the existing on-device app code for this functionality as much as possible.

    Can anyone help me get this working properly?

  • Did you try with a \n in both Puck.eval()? Do you have your code somewhere so we can test this?

  • I think what you're after for docs is: https://www.espruino.com/Bangle.js+Stora­ge

    The \n shouldn't be needed in eval, but the first line:

    Puck.eval("load('timestamplog.app.js')"
    

    is probably causing you issues - eval tries to run the command and get the response, but in this case the command is effectively resetting the Bangle and loading your app. So load(..) returns undefined to your interface file, but now the Bangle is starting to load your app, during which time you call printHTMLStampLog.

    So I'd definitely put a 500ms delay after Puck.eval("load... with `setTimeout.

    But then what you're doing should work, but I'm concerned based on the name printHTMLStampLog that you're printing the information to the console, not returning it from the function?

    If so, Puck.eval is going to return what your function returns, not what it prints.

    You could potentially use Puck.write("\x10printHTMLStampLog(stampL­og)\n", output2=> ...) (which does need a \n after the command) which I think will write the command and then wait ~200ms or so for a response and return that (it may have a trailing > on the text you receive though).

    Personally, I probably wouldn't advise trying to push HTML from the Bangle up to the interface.html though- I'd just do Puck.eval(stampLog, ...) to get the stamplog into the webpage and then run printHTMLStampLog in the web browser.

    Ideally, you could even just do Util.readStorageJSON("timestamplog.json"­, ourput2=>...) in the web browser and could then avoid even having to load your timestamp app.

    Although it depends on how you wrote the data - if you're using a StorageFile you'll need Util.readStorageFile

  • Thanks for the support. That's pretty much what I figured was going on. I was just hoping there was a more reliable way than using a fixed delay before running the second command, as that seems a bit like a fragile approach since it relies on trying to guess how long app loading time will take.

    But technically, I don't really need to load my whole app and its UI. What I'd ideally like is to be able to import a few functions from the app and execute those, perhaps using require() or something. I think I could put some shared functions pertaining to data handling into an appname.lib.js file and export them that way? It seems that I've seen some apps such as Sleeplog do that. It seems that would work more cleanly.

    As far as returning vs. printing, I in fact tried both approaches in printHTMLStampLog, because I had no idea what I was doing. It just didn't feel like the docs I read were all that clear exactly how it worked. I understand the Storage.* stuff, but I could not find references to Puck.eval or Puck.write in the search box on the Espruino docs, and I couldn't find an explanation on when to use '\n' and '\x10' and things like that or what the Bluetooth.* functions were. I had seen those in various source code but couldn't find the explanations.

    My code involved in this is currently very simple. I could put most of it in interface.html, but it's just that testing that currently involves pushing it up to Github Pages and waiting up to ten minutes just to test the code, whereas I can test code on the Bangle device immediately. I'd have to look into getting a local web server going to work around that, which is another time and effort investment I will have to consider.

    Update:
    I've changed my interface.html code to this:

          function onInit() {
            Puck.eval("load('timestamplog.app.js')",­ output => {
              setTimeout(
                Puck.eval("stampLog", output2 => {
                  document.getElementById("content").inner­HTML = output2;
                }),
                2000);
            });
          }
    

    as a test (stampLog is an object containing simple datatypes), but I still don't get any output. The browser console reports:

    BLE> Unable to decode "\r\u001b[Jstamplog: skipping save to storage because no changes made\r", got SyntaxError: Unexpected token '', "
    stamplo"... is not valid JSON
    

    “skipping save to storage” is a normal debug message that my app logs to the console, so it's as if it's trying to parse the wrong part of the console (the output of stampLog is way further down than that).

  • Never mind, I think I totally botched the syntax. It should be:

          function onInit() {
            Puck.eval("load('timestamplog.app.js')",­ output => {
              setTimeout(() => {
                Puck.eval("stampLog", output2 => {
                  document.getElementById("content").inner­HTML = output2;
                });
              }, 3000);
            });
          }
    

    My mind gets along much better with Python than this mess. :D

  • “skipping save to storage” is a normal debug message that my app logs to the console,

    This is why I'd really suggest you do what I said and just use Util.readStorageX to read the file direct - that's definitely the fastest, safest option IMO.

    Glad you got it working though!

    If you want to make changes quicker, you can always try hosting the app loader locally if you can get an HTTP server on your PC - changes are then super quick to make.

  • First of all, I totally misunderstood your link to the Storage documentation. I took it as the generic documentation for the Storage module and just now realized it was a special page that contained interface.html-specific information. Oops.

    Still, it doesn't seem all that straightforward what functions are available for Util, Puck, etc., that is, I can't find a reference. I can do Util.readStorage, for instance, but apparently not Util.readStorageJSON. Speaking of that, JSON.parse in the browser doesn't seem to like strings from storage files written on the Bangle.js with storage.writeJSON. I had to resort to parsing it with plain old eval instead.

    I got the local web server working on Linux, though. That was really easy. Just go into the root of my BangleApps project directory and run python3 -m http.server 8080 --bind 127.0.0.1. Then go to localhost:8080 in my browser, and that was pretty much it. :) I'm posting this here for future reference in case someone finds it helpful.

    I think I finally got my desired functionality working using Util.readStorage, though, with a bit of rewriting. There wasn't much code involved in simply printing out the contents from the app, so it's not so bad, I guess.

  • Just in case someone finds the the command to long: Try just 'npm start'

  • JSON.parse in the browser doesn't seem to like strings from storage files

    That's why we have readStorageJSON - it's definitely there.

    It's easier to look in the interface.js file itself where there are a bunch of comments at the top explaining what's available and what it does: https://github.com/espruino/EspruinoAppL­oaderCore/blob/1cdcb3405f78ef35f231b9c3d­f501721bda75525/lib/interface.js

  • Ah, okay. But I swear that when I try to use Util.readStorageJSON from interface.html it definitely complains that the function doesn't exist, and I have to use readStorage instead. And in the browser console, autocomplete even shows Util.readStorage but not Util.readStorageJSON.

  • Could it be because you forked your app loader from the main one some time ago, and you haven't updated it with all the latest changes? readStorageJSON went it maybe 6 months or a year ago?

  • Ah, I see what happened. I thought I had updated my fork, but I was working on a different branch that got old. Now I see that readStorageJSON works. Thanks!

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

Trouble with interface.html

Posted by Avatar for tev @tev

Actions