Preferences, SPIFFS, and JSON

While I enjoy the [relative] simplicity of the Arduino IDE, for the ESP32 I found it was worth devoting some time and brain-juice to reading the ESP-IDF source code. I know that many people see reading source code as something akin to reading the dictionary, but since that’s something I used to do as a kid, I’m cool with it…

Nevertheless, I still prefer to code in the Arduino IDE, even though it has a poor editor. Its one redeeming feature is the text-formatting feature, which everybody should be using… It makes for code that’s much more readable. I tend to pass all code I download and install for the Arduino IDE through the cleanup function, along with some additional formatting in my favorite text editor, BBEdit. All my libraries are on the other hand coded in BBEdit, and the example code tested in the IDE. So, between the IDE and BBEdit, I have a toolchain that works well enough for me.

But there’s much more to ESP32 than the Arduino ESP32 code. One of the first things I did was to download the ESP-IDF (which was recently upgraded to 3.0), and read bits and pieces when I have the time, and/or the need. I often have a specific functionality in mind, like when I wanted to set up the time via NTP. I went poking around and found some code, which I adapted, reworked, and extended, to make the M5-SNTP Sample Sketch. Works like a charm.

One thing I need[ed] is a way to save preferences on my ESP32 machines. I happen to have around 8 of them. Three M5Stack, and 5 TTGO/Heltec clones. What they have in common is that they’re ESP32-based. That’s about it. The M5Stack machines have an SD card, whereas the TTGO-like machines are quite bare. So I needed to see what other options I had. Easy peasy, there’s that thing, SPIFFS [which, if you ask me, sounds suspiciously like something related to marijuana…], I’ve been hearing about.

find . -type f|xargs -n 1 grep -c --with-filename \
-i spiffs|grep -v :0

Grepping the esp-idf folder gave me plenty of hits. Of note:

  • ./docs/en/api-reference/storage/index.rst:1
  • ./docs/en/api-reference/storage/spiffs.rst:9
  • ./docs/zh_CN/api-reference/storage/spiffs.rst:1
  • ./examples/protocols/aws_iot/subscribe_publish/main/Kconfig.projbuild:1
  • ./examples/protocols/aws_iot/thing_shadow/main/Kconfig.projbuild:1
  • ./examples/storage/spiffs/main/spiffs_example_main.c:20
  • ./examples/storage/spiffs/Makefile:1
  • ./examples/storage/spiffs/partitions_example.csv:1
  • ./examples/storage/spiffs/README.md:10

Docs, examples. Da heck else you need? So I had a look at the ./examples/storage/spiffs/ example, and it gives everything you need to read and write files to a SPIFFS volume. That’s half the job right there.

#include "esp_err.h"
#include "esp_log.h"
#include "esp_spiffs.h"

esp_vfs_spiffs_conf_t conf = {
  .base_path = "/spiffs",
  .partition_label = NULL,
  .max_files = 5,
  .format_if_mount_failed = true
};

// Use settings defined above to initialize and mount SPIFFS filesystem.
// Note: esp_vfs_spiffs_register is an all-in-one convenience function.
esp_err_t ret = esp_vfs_spiffs_register(&conf);

if (ret != ESP_OK) {
  if (ret == ESP_FAIL) {
    ESP_LOGE(TAG, "Failed to mount or format filesystem");
  } else if (ret == ESP_ERR_NOT_FOUND) {
    ESP_LOGE(TAG, "Failed to find SPIFFS partition");
  } else {
    ESP_LOGE(TAG, "Failed to initialize SPIFFS (%s)", esp_err_to_name(ret));
  }
  return;
}

[...]

FILE* f = fopen("/spiffs/hello.txt", "w");
if (f == NULL) {
  ESP_LOGE(TAG, "Failed to open file for writing");
  return;
}
fprintf(f, "Hello World!\n");
fclose(f);

[...]

f = fopen("/spiffs/foo.txt", "r");
if (f == NULL) {
  ESP_LOGE(TAG, "Failed to open file for reading");
  return;
}
char line[64];
fgets(line, sizeof(line), f);
fclose(f);
// strip newline
char* pos = strchr(line, '\n');
if (pos) {
  *pos = '\0';
}
ESP_LOGI(TAG, "Read from file: '%s'", line);

// All done, unmount partition and disable SPIFFS
esp_vfs_spiffs_unregister(NULL);

So now, I needed a format that wouldn’t break every time I sneeze. JSON was the obvious choice, because there’s a very good library for it, ArduinoJson, and with that tool, and even an idiot will have a hard time breaking the format. And since I can be an idiot sometimes, I went for a safe solution…

When creating a preferences file, you can create a String from scratch (best way to break the format, for sure, ask me how I know), or you can create a root object and add to it. Mucho recommended…

StaticJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.createObject();
root["aes"] = a;
root["json"] = b;
root["sfbwagc"] = c;
root["band"] = d;
String output;
root.printTo(output);
// Like Javascript's .toString()
Serial.println("output.length() = " + String(output.length()));
// I know the length of my final string.
char charBuf[48];
// Don't do this at home, people.
// Putting the String to a char array for fprintf
output.toCharArray(charBuf, output.length() + 1);
charBuf[output.length()] = '\0';
Serial.println("Saving prefs as:");
Serial.println(charBuf);
fprintf(f, "%s", charBuf);
fclose(f);
// f here is a FILE *f object pointing to a file in my SPIFFS folder.
[...]

There you go. You have a properly formatted JSON string, saved to the SPIFFS. Reading is about the same. You get the string into a char array, and process it:

Serial.println("Prefs String:");
Serial.println(buff);
StaticJsonBuffer jsonBuffer;
JsonObject& root = jsonBuffer.parseObject(buff);
if (!root.success()) {
  Serial.println("parseObject(prefs) failed");
  return;
}
fpChoice = root["band"];
aespChoice = root["aes"];
jsonChoice = root["json"];
sfChoice = root["sfbwagc"];
BW = sfNumericChoices[sfChoice * 3];
SF = sfNumericChoices[sfChoice * 3 + 1];
AGC = sfNumericChoices[sfChoice * 3 + 2];
// Print values.
Serial.print(F("freq: "));
Serial.println(freqPrefsChoices[fpChoice]);
Serial.print(F("AES: "));
Serial.println(aesPrefsChoices[aespChoice].c_str());
Serial.print(F("SF: "));
Serial.print(sfChoices[sfChoice].c_str());
Serial.print(F(" index: "));
Serial.println(sfChoice);

Since there ARE idiots (/me looks at mirror), it won’t hurt to check whether root has the key you’re looking for, using containsKey, or testing for a NULL object. The containsKey function has actually another use: if the JSON object doesn’t contain a key you’re looking for, your JSON is incorrect, and you can stop what you’re doing. Works out pretty well for me.

Since the ArduinoJson library is very complex, you can have a preferences file that’s very intricate, with nested values. But for my use case, a safe storage solution for a few key/value pairs, it works very well out of the box.

Advertisements

M5Stack PLC [p]review

Unless you’re new to this blog, or have been asleep during class, you know I’m mostly playing with LoRa. One of my hobbies is doing LoRa walks, trying to pin the most remote points my LoRa gateway is reachable. I’ve had a little success improving things on the M5Stack, but so far I am not getting the range I’d like, especially compared with what I get on TTGO. So the next step – once I come back from my next month-long business trip – will be to break out the big guns. Or slightly bigger ones, anyway. I’m planning to buy an AS62-DTU30 from Ashining Tech.

AS62-DTU30.jpg This gateway has a few interesting points. It has RS232 and RS485 ports, which means I can connect to my laptop via either a USB-RS232 cable, or a USB-RS485 cable. I can also connect it to an M5Stack via the RS485 port: with the new PLC module, I can use the RS485 connector: the PLC module has an SP3485E +3.3V Low Power Half-Duplex RS-485 Transceiver (connected to Serial2, GPIO16/17). Also, the PLC has an ACS712T Linear Current Sensor, connected to GPIO34. This should let me check on the current. Finally, the AS62-DTU30 is based on the AS62-T30 SX1278/SX1276 Wireless Module. This module looks almost like it could fit into the bare PLC module (I have one each: the full PLC with the ACS712T and SP3485E, and one without, which gives us plenty of space to hack something together.

AS62-T30.jpgthe AS62-T30

We’ll see if it fits. I’m probably going to buy both the gateway and a separate module to make testing easier. No point opening up that box every time I want to do a test on the PLC!

The PLC module has on another 6-pin port that isn’t connected to anything specific. It can be used to create custom ports, or add another RS485 port. Finally, the DIN power input has an LM2596S step-down voltage regulator, that brings voltage up to 40V to 5.0. There’s an adjustable version (you can see ADJ on the chip), but this version is 5.0V, which is what the M5 needs.

PLC Proto Layers.jpgOne thing to note is that because of the configuration of the module, it has to be the bottom layer, and the M5 Core’s bottom layer won’t be stackable. Considering that this module is targeting an industrial usage, I think that’s not a bad tradeoff: you won’t, hopefully, need a battery, and will have power on at all times.

So here goes for the preview. When I come back from my business trip, I’ll get the LoRa gateway hooked up, and I’ll be able to do a real review.

M5Stack Review, Part Deuce

This is a follow-up to my M5Stack Review. It’s been a couple of months, and plenty has happened. This time around, I will mostly review two things: the company and people behind the M5Stack, and new products. It’s not my place to reveal upcoming products, but I sure can review stuff that I have in hand…

The company & people

Let me talk about the folks behind the M5. It’s obvious from my previous review that I had gripes about the product and the company, interlaced with praises. So I vented, in an email. After a couple of tries, my email reached Jimmy Lai, the CEO of M5Stack. The couple of unsuccessful tries are entirely my fault. Jimmy puts his phone and email on the Taobao page, so I have no excuse except fat fingers for the failed attempts.

His response was immediate and extremely positive. We exchanged a bit by email, and agreed to meet at their office, once my schedule cleared up. It just so happened that our exchange happened just before I went on a six-country, month-long business trip (IoT is, for now at least, a hobby, to which I sure dedicate a whole lot of time, but a hobby nonetheless. The day job comes first, sometimes anyway).

My main interest in M5Stack, besides the form factor and flexibility, which in itself is a great selling point, is LoRa. I have a long-term project, which I’ve been working on for a few years now, and not only does the M5Stack fit the project requirements nicely, I need LoRa for this project. It solves [hopefully, at least on paper, for now] a huge chunk of operational problems in one fell swoop. The M5 LoRa module, while functional, has proven so far a little disappointing, and I’ve been working on fixing that – or at least improving things. And I did, although I’m not yet entirely happy.

20180321_102925.jpgI’ve also contributed quite a bit of code, from random tidbits like shutting up the speaker when it makes undesirable noises, or a larger palette of colours, to a series of UI widgets. And of course, I wrote a LOT of LoRa-related code. I managed to triple, so far, the range of the existing LoRa module, with a series of software tweaks, while changing the antenna to a spiral antenna (from the film-like antenna that’s provided, which proved weak, at best).

So we met last March, twice in a week. We both looked forward to this meeting. Our first meeting was a loose conversation of random subjects, from quality control to new products, and developments. The speed at which they come up with new products is amazing. The staff, about 8 people, were busily working on various parts, from assembling to coding. While discussing LoRa – of course – and GPS (one of my immediate needs to test my fixes is to make a Geolocation app for LoRa pings), Jimmy and I came up with an idea: why not have a GPS+LoRa module? After all, there’s space left on the GPS module’s proto board to throw a LoRa chip in there. My soldering skills being what they are, Jimmy had one of the staff do the assembly for me. And he very generously gave me this module, along with an M5 Panda, so that I could do comparative tests with my existing M5+original LoRa module.

20180408_173349.jpg
The custom-made GPS+LoRa module, with an M5 Panda

The LoRa module is not the current Ra-02 module, but an IntoRobot L8, SX1278-compatible. It promises up to 20 km range, given a specific set of settings. I had to hunt around quite a bit, and, thanks to GoJimmyPi, I was able to set the L8 with (hopefully) the proper set of settings. So far, I have seen improvements with:

  • Obstacle penetration: I’m getting PINGs on both the original machine and the Panda where I didn’t before. I live in an area of HK that has Godzilla-sized buildings, plenty of them.
  • Line of sight range. Even the new LoRa module (which has a weak antenna) keeps up more of less with the original module, equipped with a spiral antenna. I’m getting more or less 600 meters, very reliably. And about 300 meters with Godzilla in the way.

This is still far from the 2,000 meters I get on my TTGO, but I am not done yet! First, I need to work a little more on the software tweaks. And at some point, we’re going to test the same L8 module with a different antenna (Jimmy has a module that has an ipex connector, which should allow me to try with better antennas).

All in all, I’m quite happy now with the state of things. It’s not all rosy, but (A) I’m getting enthusiastic support from the company, and (B) there’s a growing community of people playing with the M5, and contributing. I believe we can make this line of products better.

I’ll have another post soon detailing my LoRa adventures. I need to clean up my notes.

M5Widgets: Widgets for the M5Stack

This is based on the [alas now defunct!] Phoenard project. The Phoenard had a touchscreen, whereas the M5Stack has a “dumb” screen. So instead of copy/pasting and adapting the code, I am selecting the juicy, non-touch bits, and adapting them to the M5, while simplifying a bit.

Installation

It is a little more complex than the regular library, as I am integrating this to the M5Stack library itself. The code repository is here. The first step is to drop all the .cpp and .h files (except Free_fonts.h, which is for the sample sketches) to the ~/Arduino/libraries/M5Stack/src/utility/ folder. Then, in M5Stack.h, towards the bottom of the file, after:

extern M5Stack M5;
#define m5 M5
#define lcd Lcd

add the following

// added by Kongduino
#include "utility/M5DataBuffer.h"
#include "utility/M5Widget.h"
#include "utility/M5Gauge.h"
#include "utility/M5ProgressBar.h"
#include "utility/M5BarGraph.h"
#include "utility/M5LineGraph.h"
#include "utility/M5Touch.h"
#include "utility/M5QRCode.h"

You have now access to the M5Widgets. Yay!

I am adding two [similar] examples: M5_Widgets.ino and M5_Widgets_BMP280.ino. The first one displays random data points, whereas the second one uses a BMP280 sensor to display temperature and pressure data in various widgets. They both use the Free_Fonts.h header file. Make copies, and put then in folders. You know the drill.

Remarks

Since this is a work in progress, I have stripped down the comments and license (I’m keeping the same MIT license). That doesn’t mean I am trying to negate Phoenard’s work, or take undue credit. I need the code to be as compact as possible, and don’t need reams of comments to wade through, in every file. In due time, I will make a proper documentation for all the widgets, and the main M5Widget class, from which all widgets are descended.

The original is much more complex, and evolved. There are quite a few abstraction classes (widget container, display, etc), that I decided to skip to make things simpler. I have added one widget of my own, the M5Gauge, and will add more, as the need arises. Also, I will try to rework some of the isTouched logic into an abstraction class for the three buttons the M5Stack has. This would enable things like a scrollable ItemList, where Button A would be up, Button C down, and Button B select.

M5Stack Review

Core.pngI’ve been looking forward to playing with an M5Stack for a few months – since it first came out, really (or at least since I became aware of this project). I have been interested in LoRa for a while, having a specific use for this technology in mind. At the same time, I need a self-contained solution: playing with “nude” platforms is fine when you’re not demoing a concept. However, when you meet potential investors and they see PCB and wires, or, worse, a Tupperware™ box, they tend to be a wee bit dubious.

So a clean, extendable [stackable, hence the name] design, with a nice case, a screen, and, added recently, a LoRa module and a 8,500 mAh battery? Gimme! By the time I had made up my mind, there were three core models out. The newer models have an MPU-9250 sensor, which is not something I need right now. There an extension that transforms the M5Stack into a Calculator, Gameboy, Micropython micro-computer called (quite an unfortunate name if you ask me) Faces. Looks like a cool product too. I have however little need for Micropython (I have one of the original PyBoard Micropython boards, which I supported on KickStarter if memory serves, but for what I need, Python isn’t good enough. I’ll stay a little closer to the iron…), I might, one day, buy one, and rip the Micropython firmware out, if I can figure out a way to use the keyboards (the Gameboy and the calculator keyboards are the most attractive for my use).

So finally I made up my mind and bought set of the original core, the LoRa module, and the battery module. They have two online stores [that I know of], one on AliExpress, and one on Taobao. Considering that:

  1. I not only live in HK, but I can see Shenzhen from my windows.
  2. I can have stuff shipped to a Shenzhen address.
  3. Taobao was 32% cheaper.
  4. I can read and write Chinese.

I placed my order with Taobao, for a whopping 336 CNY, 52 USD. The order was processed and sent within a couple of days, and I got it today morning. It came by SF Express, not just any 快递. And the goods were well packaged in a small envelope. Compare with a NodeMCU board:

20180113_155544_s.jpg

Oooooh, tiny.

Inside, each module is in its own sturdy plastic box. I’ll have some comments and suggestion about these, actually. Let’s have a look-see first:

20180113_155627_s.jpg

So far so good. The boxes are sturdy, and deserve to be kept for later use. Inside, we have the modules, a USB cable (which gave me a lot of headaches – it didn’t work, and had me believe the VCP drivers didn’t work, grrr), some cables for prototyping, some stickers (including stickers for the three buttons), and two leaflets: a one-page Quick Start guide, and a longer, folder guide.

20180113_155911.jpg

There are you issues with these leaflets. One, as I’m not a young man anymore, with tired eyes, they’re VERY hard to read. Ouchies. Two, some of the information is outdated. For instance, they give putStr() as a function to draw a string on the LCD. No workee. I went through the M5 library‘s header files (who doesn’t like to read header files, right?), and drawString() is what you need. Well, *I* need. The buttom functions are wrong too. The idea of a cheatsheet is great, and I’ll produce a (legible) one in a few days, once I’m done reading the header files. [EDIT: Done.]

But besides that, the leaflets are generally helpful. It helped also that I had been salivating about the M5 for a few weeks – I had read everything I could, I had installed the library. I was ready! 🙂

Suggestion #1

So what would be more helpful in terms of packaging is not a wider box, but a taller one. If the box for the Core was the same square as for the modules, but 3~5 times taller, we could put an assembled stack inside the box and close it. Whether for transport or for outdoors operation, it would be ideal.

Suggestion #2

Make a better leaflet. Or dispense with most of it. Put a QR Code (who types links, bro?) to your site, and please please please, steal my cheat sheet and put it up on your website (hint: start putting everything on github).

I have been working with Arduino and other IoT devices for a few years. I have all the drivers installed. On the M5 Forum and elsewhere, there have been complaints that upload doesn’t always work, especially on the older models. The current solutions are getting an older version of the SiLabs VCP drivers, or put a 2.2µf cap between ground and reset. The pins are accessible from the outside, so you have this cap sticking out of the case. A little unseemly, but that only when you need to upload. An ingenious Japanese user even soldered a cap inside the case, making it permanent and invisible.

When I plugged in the M5, nothing was detected. Hmmm. Tried with four Macs, two MacBook Air models, two MacBook Pro models, nada. Version 5, then downgrade to version 4, nada. Grrrr. I mentioned this, and the guys at M5Stack were responsive – but there was so much that they could do online of course… Usually the problem was with uploading, not the USB port per se. Then the M5 turned itself off: it wasn’t charging. Oops. Turns out, the Type-C cable, which was way too short, and would have gone to the trash anyway, was bad. One trip to Miniso later, and I was back on track.

20180113_161604_s.jpg

The Factory Test. Yes, I hit buttons 3 and 1 twice. CACA. I’m childish.

After that all went well. I have been working with LoRa, LCDs and other stuff long enough to make everything work in literally minutes. I did have to poke around the header files, as I said (and you have to use M5.Lcd, instead of the more generic SSD1306 code I have already installed, for my TTGO boards. With a different syntax. Grrr. But all in all, it took me an hour to (re)make a LoRa sender and receiver for testing. I started with a sender, attaching a BMP280 barometer sensor to the Grove port [I2C port]. The M5 started pumping out Temperature and Pressure data in a JSON packet, which my TTGO machines happily received and decoded. Bueno.

Suggestion #3

A better USB cable. and not 10 cm long, kkthxbai.

20180113_160242_s.jpgNow, since the M5 is self-contained, and I have a nice battery plugged in, that’s the machine I’m going to take around my neighbourhood, to test the range. So I put a simple sender sketch in one of my TTGO boards, sending every ten seconds a JSON packet with just a PING, UUID, and sendCount as data. Should be good enough for a test. Later on, when I start working on my mesh, I’ll buy more M5, which I’ll put in various places in my ‘hood that I know are our of range (at least for the weaker TTGO). I can only count on people’s honesty to leave them alone and not run away with them! 🙂

I wish I had included the GPS module in my order, as I could use it to plot geolocations while doing the testing. I have a Grove GPS module from SeeedStudio, which I can probably use in between. I don’t fancy the idea of having a cable and small board dangling out, but, oh well…

Uh oh, no. I forgot that on the ESP32 the SoftwareSerial library isn’t [officially] available, so using the I2C port for this won’t work. Which brings me to one of my gripes: there’s space on the frame for another Grover port (or two, if one wants to be greedy). Having at least a port for Serial2 would be nice… I know all the pins are exposed on the base, but there’s gotta be a reason they used a Grove port for I2C (hint: because it’s friggin’ convenient).

I connected my SeeedStudio Grove GPS module on the base pins. The M5 started making a whistling sound – and not the man-seeing-a-pretty-girl kind of whistling. More like, some things inside the machine are not enjoying being interconnected-kind of death threat whistling. After a while it subsided, and only came back intermittently. Grrr. The base pins connections seem to produce a lot of noise, physical and electrical: I was getting a lot of garbled text, and wiggling the cables fixes it, sometimes only temporarily. There seems to be room from improvement here (and more argument for another Grove connector).

I uploaded sample code from Mikal Hart’s TinyGPS library, modified to make good use of the M5’s screen, and we were in business.

So my next order will have to include a GPS module then. I really like the idea of being able to able to do logging on the SD Card of GPS coordinates when doing LoRa tests. THAT brings me to two problems, one of which I might be able to fix, the other one not easily.

(1) Unless there is something wrong with *my* LoRa module (as opposed with the M5Stack LoRa module in general), the range is super bleh. I suspect[ed] the internal antenna (and tests show that it might be part of it), but I am getting ranges of 200 meters max, as opposed to 1.2 ~ 1.5 km with my small TTGO machines. Not cool. The first thing I did was disconnect the provided antenna, and use one of the spare antennas I had for my TTGOs. That “improved” the range to about 200 meters. Not exactly what I wanted. So I’ll do further testing, and maybe if I have time, and they’re willing, I’ll go see the guys at @M5Stack to see if we can look into it…

20180119_225533.jpg

The LoRa module with its original antenna, disconnected, and the new one.

Suggestion #4

Let’s work on the LoRa module. I’m volunteering to go and see you guys, and help work on a solution. As it is now, your LoRa module (either the specific one I have, or in general) is useless.

Another problem is a software one, this time. There’s a convenient display-related command, M5.Lcd.drawJpgFile [which, I realize now, isn’t in the cheatsheet. Will fix that in a moment!]. The main issue is that the ESP32 stack uses TJpgDec, which is a nice tiny library. This library however isn’t too refined, and borks on some images. Consider this use-case, which I had in mind for my geoloc LoRa testing:

  1. Test LoRa reception.
  2. Acquire GPS coordinates.
  3. Save RSSI and GPS coordinates to SD Card.
  4. Call up Google Maps to get a static map as JPEG.
  5. Save file and display on-screen.

Which I tried. Nnngggggnnnn. It worked very well up to saving the data to the SD card. But displaying the map doesn’t work. Turns out the library borks on Google Maps JFIF images. I’ve tried to use the ArduinoJpeg library instead, but there are incompatibilities, and I need to have a look at the code first.

I might also have a look at GIF decoders — they are usually lightweight — as Google Static Maps can be requested in JPEG, PNG and GIF formats. Another project for later…

While writing this (long!) review, I wrote a bunch of sample sketches, to see what this machine can do. Apart from LoRa, which so far is a disappointment (or let’s call it a challenge…), everything else works well. One of the main things I noticed is that the M5Stack.h header makes my life (and my code) simpler. All this initialization crap you have to write in an Arduino sketch to setup SPI, Wire, SD, the TFT screen? No need. It’s done for you. Calling M5.begin(); is about everything you need.

There is, however, a catch. Custom libraries that require the SD library, for instance (or SD_Anywhere, which is usually my favored flavor for Arduino projects), will barf on your shoes. They call #include , or #include , and that creates incompatibilities. It solved it by adding in the libraries that call incompatible stuff this code:

#ifndef _M5STACK_H_
  #include <SD_Anywhere.h>
#else
  // If we're using an M5, don't include outside SD libraries.
  #include <M5Stack.h>
#endif

Likewise, libraries that load UTFT or other TFT-specific libraries have to be shorted, BZZERT, so that they don’t mess up with your code. Remember, the M5 uses an ILI9341, which is pretty standard, and has a long list of features. And having a specific driver for YOUR screen (it’s not like you’re going to remove the ILI9341 and graft a new one…) saves space and time.

There’s also a lot that is not entirely exposed [at least in a obvious manner] by the M5Stack library. You have to look one floor down, in the ESP32 toolchain. For instance, you can set up the ESP32’s clock via SNTP. Does one really have to code all this by hand? Nah… Espressif has sample code to use LwIP SNTP. I was able to cut down a little on their code and make it more, well, M5Stacky, in a sample app that is growing beyond the reasonable… 🙂

Conclusion

I need to play more with that thing (and possibly buy a few more), but so far here’s my assessment.

The good stuff

  • Most of what’s in the box “just works”. Old farts like me remember Apple & Microsoft raving about plug’n’play. Well then. Once the USB issue was solved, plug and play indeed.
  • Bright screen. Generally good-quality build of the Core.
  • Good central library that simplifies coding.
  • Stacker design is a good idea, and the 2×15 pin + breadboard design is clever.
  • Pins exposed on all four sides of the base is a good use of real estate. Remember, that thing is tiny.
  • The three buttons. Good enough for many basic UI uses.
  • The I2C Grove connector.

The less-good stuff

  • Hit and miss stuff like crappy USB cable, LoRa reception, outdated info on a microscopic leaflet with printed URLs.
  • Slightly flimsy build of the modules. I’m always fearful of breaking them when dismantling the stack.
  • Whistling sounds when using the pins on the base.
  • No Serial2 Grove connector.
  • AFAIK, no way of turning off the M5Stack when charging it.
  • Less than obvious “double-click” gesture to turn it off (instead of more obvious long-press).
  • The company (or probably the single dude behind it) is not responsive, either on the M5Stack community or Twitter. Most questions stay unanswered, and posts on Twitter get liked/retweeted, but that’s it. I dunno if it’s a language issue, or El Jeffe being overworked, or what, but this has to change.

I’m quite happy, but still grumpy. I look forward to improvements, and will try to support where and when I can.

Basic BLE Functionality on the M5Stack

I am nowhere near a BLE expert – I have difficulties wrapping my mind around that thing, and so far regular BLE has been good enough for me. But ESP32 have BLE, and I had a use case for Bluetooth. So might as well try…

20180116_073033.jpg

I have written, as a half joke, half proof of concept, a small app on the M5Stack that shows the temperature – it was so cold in the bus, with the A/C turned on in winter. As what I had on me was a BMP 280, it provides also atmospheric pressure, and altitude. But to calculate vaguely accurate altitude you need to have your locale’s sea level pressure. Which isn’t exactly convenient, when running this on a keyboard-less system. I had to change the seaLevel variable, recompile, and re-upload. No no no.

So, not entirely keyboard-less, as it has three buttons. The first solution was do provide 0.10 increment and decrement, with buttons A & C (left and right).

void buttons_test() {
  if (M5.BtnA.wasPressed()) {
    Serial.printf("-0.10 HPa");
    seaLevel -= 0.1;
    displayBMP();
  }
  if (M5.BtnB.wasPressed()) {
    Serial.printf("B");
  }
  if (M5.BtnC.wasPressed()) {
    Serial.printf("+0.10 HPa");
    seaLevel += 0.1;
    displayBMP();
  }
}

This works well enough. But it feels a little bit clunky. I could provide a small WiFi server, of course, to accepts POST or GET requests. But what about BLE?

The ESP32 examples have a small BLE_uart sketch that fits my purpose:

#define SERVICE_UUID           "6E400001-B5A3-F393-E0A9-E50E24DCCA9E" // UART service UUID
#define CHARACTERISTIC_UUID_RX "6E400002-B5A3-F393-E0A9-E50E24DCCA9E"
#define CHARACTERISTIC_UUID_TX "6E400003-B5A3-F393-E0A9-E50E24DCCA9E"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
    }
};

class MyCallbacks: public BLECharacteristicCallbacks {
    void onWrite(BLECharacteristic *pCharacteristic) {
      std::string rxValue = pCharacteristic->getValue();
      double fValue;
      if (rxValue.length() > 0) {
        Serial.println("*********");
        Serial.print("Received Value: ");
        for (int i = 0; i < rxValue.length(); i++)           Serial.print(rxValue[i]);         Serial.println();         Serial.println("*********");         fValue = ::atof(rxValue.c_str());         if (fValue > 0.0) {
          seaLevel = fValue;
          Serial.println("Changed seaLevel to " + String(seaLevel));
          displayBMP();
        }
      }
    }
};

[...]

void setup() {
  Serial.begin(115200);

  [...]

  // Create the BLE Device
  BLEDevice::init("BMP Sea Level Service");
  // Create the BLE Server
  BLEServer *pServer = BLEDevice::createServer();
  pServer->setCallbacks(new MyServerCallbacks());
  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);
  // Create a BLE Characteristic
  pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_TX, BLECharacteristic::PROPERTY_NOTIFY);
  pCharacteristic->addDescriptor(new BLE2902());
  BLECharacteristic *pCharacteristic = pService->createCharacteristic(CHARACTERISTIC_UUID_RX, BLECharacteristic::PROPERTY_WRITE);
  pCharacteristic->setCallbacks(new MyCallbacks());
  // Start the service
  pService->start();
  // Start advertising
  pServer->getAdvertising()->start();
  Serial.println("Waiting a client connection to notify...");

It does come at a price: the BLE stack is huge, and compiling the script takes a while, and the end-product ends up taking 75% of the Flash memory:

Screen Shot 2018-01-17 at 11.36.34 AM.png

But it works! If I look up the sea-level pressure on Thailand Meteo Dept’s web site, I can open a BLE scanner, connect to the M5, input the new sea-level pressure, and update.

I feel so much nerdy now 🙂

I’ve done this before in another project: the ultimate nerd way would be to look up automatically the relevant Meteo page, extract the sea-level pressure, and update it. So technically, I could have the M5Stack update itself, if it has a network. But, for the moment, this will do!

Full code on github.

Vroom Vroom

In Nitrous for the Arduino, I explained how to speed up code execution on Arduino machines, by replacing -Os directives with -Ofast. The procedure is slightly tedious, and every time I install a new version of the IDE, I have to set up things again. No more…

Screen Shot 2018-01-12 at 1.09.40 PM.png

In the background a sketch, compiled with the regular -Os directives. Code is 6,558 bytes. In the foreground, a small app I just wrote, offering me to change that. Let’s change that to -Ofast.

Screen Shot 2018-01-12 at 1.10.12 PM.png

Gasp! 14,400 bytes. That thing more than doubled in size. But is it effective? On a SeeedStudio Lotus, the sample test sketch of ricmoo’s QRCode library executes in 870 ms and 696 ms respectively. A 20% speed increase. Not bad…

Test.jpg

The tool is a bit crude (hey, took me 30 minutes to write it!) and only recognizes the arduino/avr platform. What I need to do now is to make it a weeeeeee bit more versatile, looking for all platform.txt files. And maybe add more options. Oh, and, by the way, if you’re wondering whether this plain-text QR code works:

Hello World QR.jpg

🙂