Skip to content

Firmware

Johnathan Andersen edited this page Dec 6, 2018 · 9 revisions

Table of contents

Summary

The Wall-Ink firmware was written for the ESP8266 using the Arduino stack. It downloads an image, displays it to a screen, and sleeps for a while.

Building

The firmware was built using Arduino IDE 1.8.7. It used ESP8266 board version 2.4.2. It used commit af110914ef6618a974a062a860185aeeb0dc9e67 of the library found here: https://github.com/caedm/GxEPD. It used the Adafruit GFX library version 1.2.3. Later versions of these libraries may work, but these are included for reference in case they don't.

Setup Your Build Environment

First, you need to install the Arduino IDE. To do this, download the IDE from Arduino's website: https://www.arduino.cc/en/Main/Software

TIP: For Linux users, do not install the IDE with your package manager; you will almost certainly end up with an older version!

Download the GxEPD library from https://github.com/caedm/GxEPD as a .zip file

Open the Arduino IDE

Go to Sketch -> Include Library -> Add .ZIP Library and add the GxEPD library.

Go to Sketch -> Include Library -> Manage Libraries and add the Adafruit GFX Library

Go to File -> Preferences and paste http://arduino.esp8266.com/stable/package_esp8266com_index.json into the Additional Board Manager URLs field and hit 'okay'

Go to Tools -> Board: -> Boards Manager and search for ESP8266; install version 2.4.2 Go to Tools -> Board: and select Generic ESP8266 Module

Go to File -> Open and select the stream_screen.ino sketch in door-display/Arduino/stream_screen

Go to Sketch and select Verify/Compile

You have now built the firmware!

Flashing the firmware

To flash the firmware, follow the following steps:

  • Open the Arduino IDE
  • Connect your usb-serial adapter to your computer and to the Wall-Ink display
  • Go to Tools -> Port and select the correct port
  • Use the switch on the PCB to put the device in flash mode (if using a dev board, you will need to hold the flash button)
  • While the device is still in flash mode, hit the reset button
  • Click the Upload button in the top-left corner of the screen, which looks like an arrow

Summary of program execution

When the ESP8266 comes out of deep sleep, the following sequence of events happens:

Boot

The ESP8266 boots. This takes about 0.3 seconds.

Check admin mode switch

The ESP8266 checks to see if it's in admin mode. If it is, it starts broadcasting as a wifi access point.

Connect to WiFi

If the ESP8266 is using information stored from a previous connection, connecting to WiFi takes the ESP8266 1-5 seconds. Most of this time is spent waiting for a DHCP lease. Otherwise, it takes about an extra 10 seconds for the ESP8266 to scan the different access points to determine which has the best connection.

Get beginning of image file

If the file is a dynamically generated file, this takes 1-3 seconds. This is because the server needs to go through the process of contacting the relevant databases and generating it; more information on that process can be found at the [https://github.com/caedm/wall-ink-server Wall-Ink-Server github repo]. If the file is a static file, it usually takes 0.1-0.5 seconds.

Process beginning of the file

The beginning of the image file contains certain useful data: the current time, the next time the ESP8266 should update, and a hash of the image. The ESP8266 first checks the image hash against the last one it stored. It then checks the current time against the time it expects it to be, and adjusts its sleep time accordingly. It then stores these values. If the image hashes matched, it immediately goes to sleep. Otherwise, it downloads the rest of the file. This step takes <0.1 seconds.

Initialize the display and download remainder of the file

The ESP8266 initializes the display. It then downloads the rest of the file in chunks, and decompresses each chunk into an image buffer before moving on to the next one. This process usually takes about .4 seconds. Note that it only draws the black pixels; this is because the display buffer is pre-initialized to white. This optimization saves about .6 seconds.

Verify the image

After the entire image has been downloaded, the EPS8266 hashes it. It also hashes the image key. It then hashes the two hashes together and compares the result against the hash that came with the image. If they don't match, it is assumed that the image was served by an impostor server and the device sleeps. This step takes <0.1 seconds.

Update the screen

The ESP8266 updates the screen. On a 7" Waveshare display this takes 5 seconds. The time for the 4" Waveshare display is unknown.

Define values

FIRMWARE_VERSION

The version of the firmware that gets reported to the server

DEVICE_TYPE

Possible values are integers from 0-4. Describes the screen layout.

ADMIN_MODE_ENABLED

Whether admin mode is enabled, 0 or 1.

MAX_SLEEP

The longest the ESP8266 will try to deep sleep for at a time, in seconds. It might not be safe to increase this.

MIN_SLEEP

The shortest the ESP8266 will try to deep sleep for at a time, in seconds.

INITIAL_CRASH_SLEEP_SECONDS

The number of seconds the ESP8266 waits after the first crash before trying again. This number actually gets multiplied by 4, so a value of 15 really means 60.

INITIAL_DRIFT_SECONDS

The number of seconds the ESP8266 adds to its sleep time by default to adjust for time drift in the RTC. This doesn't matter too much, because the ESP8266 will adapt this value as it runs. Note that this time drift compensation won't work well for sleep times of more than a few hours.

ADMIN_PASSWORD

This is the password you'll need if you want to connect to the device when it is in admin mode

DEFAULT_IMAGE_KEY

This is the image key your device will start with. It can be changed later in admin mode. Note that if you change it here, you'll also want to change it in compessImage.cpp on the server.

RTC memory

The RTC memory is memory that persists even when the ESP8266 is in deep sleep mode. There is only a half-kilobyte of RTC memory, but it is still very useful. This is because unlike flash memory, RTC memory can withstand a virtually unlimited number of reads and writes. We store the following variables in RTC memory:

crc32

This is a hash which is used to verify that the RTC memory hasn't been corrupted during a deep-sleep cycle, or if the ESP8266 is booting for the first time.

currentTime

This is the current time, in Unix time.

nextTime

This is the next time the ESP8266 plans on waking, in Unix time

elapsedTime

This is the estimated amount of time the ESP8266 thinks has elapsed since it started sleeping

driftSeconds

This is the number of seconds added to the sleep time to account for time drift in the RTC.

crashSleepSeconds

This is the number of seconds the ESP8266 will sleep for after a crash. It is multplied by 4 before each sleep cycle, but will never be allowed to exceed 1 day.

channel

This is the wifi channel the ESP8266 last connected to.

bssid

This is the MAC address of the last wireless access point the ESP8266 connected to.

padding

The RTC memory can only store memory in 4-byte chunks, so a 1-byte padding variable was added. Just to be clear, nothing is stored here and this variable is not used. Don't remove it though, or the firmware will break.

ssid

This is the SSID of the last wireless access point the ESP8266 connected to.

password

This is the password of the last wireless access point the ESP8266 connected to.

imageHash

This is a hash of the last image to have been downloaded and displayed to the screen. This is referred to as "chunk 4" in the wink file header.

errorCode

This is the last unreported error to have occurred. It will upload this next time it contacts the server, and then reset the value to 0.

EEPROM

The EEPROM is persists even when power is disconnected or the device is flashed, but has a limited number of reads & writes. This makes it optimal for saving things such as settings. Options set while in admin mode get stored here.

wifiProfile1Active

A boolean which stores information about whether the 2nd wifi profile is active. This currently does nothing.

baseURL

Contains the location of the image file being downloaded. Parameters get stuck on the end of this URL in the setURL function.

ssid0

Contains the first wifi profile's ssid

ssid1

Contains the second wifi profile's ssid

password0

Contains the first wifi profile's password

password1

Contains the second wifi profile's password

imageKey

Contains the key used to validate images received from the server

debug

A boolean which stores whether debug mode is active

hash

A sha1 hash of the rest of the data stored in EEPROM, used to detect memory corruption

Admin mode

When GPIO4 is pulled low (when the switch on the PCB is set to Admin), the ESP8266 will boot into admin mode. This means that it will set itself up as a wifi access point with an ssid of BYU DD followed by its MAC address. It will then start a webserver on 192.168.4.1 Visitors to the web page hosted there will be able to change the settings stored in the EEPROM.

For more information, see the Admin-mode wiki page.

WIFI connectivity

At the top of the sketch, you may define up to 2 default SSIDs and passwords (these can be changed in admin mode. The Wall-Ink device will, upon booting for the first time, scan for nearby wifi hotspots. It will connect to whichever has the better signal, and will continue trying to connect to the same access point after each reboot.

GET request

After the Wall-Ink display connects to wifi, it makes an http GET request that looks something like this: yourbaseurlhere?mac_address=ECFABC0DBAED&firmware=2.07c&error=0&voltage=3.05

Error reporting

When there is an error, it is saved to RTC memory and reported next time the device checks in. The error codes are then logged to www/log.txt The key is at https://github.com/caedm/wall-ink/blob/master/stream_screen/error_codes.txt

Serial debugging

When the device is in admin mode, there is an option to enable debug mode. When this option is enabled, you can use a USB to Serial adapter to connect to the device at 115200 baud to get some debug information from it.

Battery saving measures

Several Battery saving measures have been taken. Here are a few:

Saving WiFi info

When the ESP8266 connects to WiFi, information about that connection is saved in RTC memory. This makes the next connection significantly faster. We would be able to save even more time by saving the IP address, thus skipping DHCP. Unfortunately there doesn't seem to be an easy way to get the DHCP lease time, making this dangerous. It is thus not implemented, but if you can find a way to do that it would speed up the process of connecting by 3-5 seconds, making an enormous difference in the battery life of these devices.

Not updating identical images

If the ESP8266 downloads an image with a hash which matches the hash stored in rtcMemory.imageHash, it will immediate go to sleep instead of downloading the remainder of the file and flashing the screen

Serving static images

It is about 1-1.5 seconds faster to serve static images to the ESP8266 than it is to dynamically create the image when the ESP8266 demands it. There is currently support for static images to be manually added on the server, but if there were a way to generate the images just in advance of the ESP8266's checkins that would be beneficial.

Only writing the black pixels to image buffer

When the display is initialized, the display buffer is initialized to be white. There is therefore no reason to write the white pixels to that buffer. This optimization saved a little more than 0.6 seconds on the 7" screen.

Not using WiFiMulti unless we don't have saved connection data

Using WiFiMulti takes way longer than simply using WiFi.begin. Can't remember how big the difference is, but I think it's more than 1 second.

Why things are the way they are

Why isn't this mess divided into more functions?

For some stupid reason, the delay() and yield() commands crash the ESP8266 unless they are called directly from within loop() or setup() The ESP8266 will crash even if these commands are called from other functions which are called from within loop() or setup() If they are not called, the watchdog timer reboots the ESP8266. This has encouraged me to write code in a worse way. Sorry about that.

Why are you streaming an image instead of downloading the whole thing?

The ESP8266 just doesn't have enough RAM for that.

Why is the ESP8266 set to sometimes ignore its saved WiFi information, and to instead do a full AP scan?

Without this, the ESP8266 would be stuck forever to whichever AP it first attaches to, unless that AP disappears entirely. This way the device can be moved down the hall a little ways and the ESP8266 will, within a few dozen power cycles, pick the new closest AP.

Why not use WiFi.persistent?

It saves the connection info to flash for every reboot. This kills the flash.

Clone this wiki locally