Moved documentation from Altimeter-WLAN project to wiki authored by Tobias Stein's avatar Tobias Stein
tbd [[_TOC_]]
\ No newline at end of file
# Project Background
This project is meant to be an extension of the previous
[Altimeter project](./2023-Altimeter.md) which was built to measure the maximum
height reached by a model rocket. This version of the altimeter is built on
a different platform and utilizes different and more sensors to collect data
during the ascent of the rocket. The display from the original project is gone and
replaced with a webinterface which can be accessed over a WiFi connection.
# Usage
## WiFi Credential setup
One set of WiFi credentials can be embedded into the firmware during flashing.
On startup, the microcontroller will attempt to connect to the WiFi network
provided by those credentials and if that fails will open its own network.
The file containing the credentials should be named `wifi_credentials.h` and
be located in the `src` folder. It will automatically be excluded from
version control to avoid leaking of credentials.
The contents of the file should look like the following
```C++
#ifndef WIFI_CREDENTIALS_H
#define WIFI_CREDENTIALS_H
#define WIFI_SSID "YourWiFiSSID"
#define WIFI_PASSWORD "YourWiFiPassword"
#endif // WIFI_CREDENTIALS_H
```
## Starting/Stopping measurements
Before starting the sampling process, ensure that the measured reference values
are sensible by checking the webinterface, as the height calculation is relative
to those values. If they do not make sense, a recalibration can be triggered
from the webinterface.
The sampling process can be started by pressing the user button
connected to pin D8 on the controller or by using the start button in the
webinterface.
When the statemachine is in the sampling state, pressing the user button or
the start button in the webinterface again will restart the measurements and
discard existing samples.
The sampling process stops either when the current measured height falls a
configurable distance below the maximum height or when the stop button in the
webinterface is pressed. Afterwards the statemachine enters the idle stop state
and can be restarted by pressing the start buttons again. This will however
discard the previous measurement results.
## Downloading results
The measured sensor values and the reference measurements on ground level which
are established during startup can be downloaded in csv format using the
corresponding buttons in the webinterface.
Measurement results stay available, even after a reboot, until the measurement
process is started again.
## Visualization
A python script has been provided in the folder `plotter` which can use the
downloaded samples and reference values to plot a height over time graph.
# Technical information
## Hardware used
The microcontroller used is the Seeed Studio XIAO ESP32C3. The sensor board
is called GY-86 and is comprised of 3 different sensors which can all be
accessed over I2C.
| Name | Description | I2C address |
| -------- | ----------------------------------------- | ----------- |
| HMC5883L | 3-Axis digital compass | 0x3C |
| MPU-6050 | 3-axis gyroscope and 3-axis accelerometer | 0x68 |
| MS5611 | Altimeter | 0x77 |
## Libraries used
- [Adafruit HMC5883L Driver (3-Axis Magnetometer)](https://github.com/adafruit/Adafruit_HMC5883_Unified)
- [Adafruit MPU6050](https://github.com/adafruit/Adafruit_MPU6050)
- [MS5611](https://github.com/RobTillaart/MS5611)
- [ESPAsyncWebServer](https://github.com/esphome/ESPAsyncWebServer)
- [ArduinoJson](https://github.com/bblanchon/ArduinoJson)
## Wiring
**ESP32 to sensor:**
| XIAO ESO32C3 | GY-86 |
| ------------ | ------ |
| NC | VCC_IN |
| 3V3 | 3.3V |
| GND | GND |
| D5 | SCL |
| D4 | SDA |
| NC | FSYNC |
| NC | INTA |
| NC | DRDY |
An additional user button was added connecting pin D8 directly to the 3.3V pin.
This button is used as the main user button in the program and is also relevant
for correctly entering debug mode with this board.
## Debugging
<b style="color: red;"> The ESP32 can only be powered from the 5V pin or USB.
The GY-86 requires 3V3 volt though so don't use the same supply for both.
Also [do not connect the battery without a diode](https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/#power-pins)
while powering the ESP32 from the 5V pin.</b>
The ESP32C3 has a builtin debugger.
To allow gdb debug connection to succeed, D8 has to be pulled to 3.3V during boot.
Otherwise a `Failed to get flash maps` error will be triggered.
See also the [additional resources](#additional-resourcesguides) for information
on setting up the debugger and utilizing it.
## FreeRTOS tasks
### Main task (Statemachine task)
This task is responsible for running the altimeter statemachine.
It is implemented in the main function.
### Sensor task
The backbone of the system. This task is responsible for sampling the
sensors in a fixed interval and sending the data to the statemachine task
using a FreeRTOS queue. If the statemachine is not in the measuring state,
this task is suspended.
### Clock task
The simplest task is that of the clock task, which will in regular intervals
increment the system clock to keep track of time in a simple human readable
format.
### Webserver
The AsyncWebserver library implements its own tasks to asynchronously handle webserver requests.
## Statemachine
![](./2023-Altimeter-WLAN-resources/statemachine.svg)
The statemachine is implemented using transition tables to keep track of
which event triggers which transition and what action to take during
that transition.
There are 2 kinds of transition tables used for this.
- **Inner transition table:** Each state has exactly one inner transition table.
Each entry in this table defines the event triggering an outgoing transition,
the target state of the transition, an optional transition guard and an
optional transition action.
- **Outer transition table:** This transition table assigns the definitions of
inner transition tables to their corresponding statemachine states.
The `processEvent()` function checks a new event and finds whether the current
state has a corresponding transition which is not currently blocked by a
transition guard. If a valid transition is found, the optional transition action
is executed and the statemachine transitions into the new state.
### st_Initialization
The default state upon starting the controller. This state will take some reference measurements to determine the relative height of later measurements.
### st_Measuring
The main state of the statemachine. While in this state,
sensor data will be pulled in as provided by the sensor task and stored in
the global `dataStore` object.
Additionally the current height will be calculated and checked against the
maximum measured height to be able to trigger the `ev_Falling` event.
When transitioning to the stopped state, the measured sensor values
will be stored to a file in flash to save them in case of a reboot.
### st_StoppedMeasuring
Idle state. Allows restarting (and thereby overwriting!!!) the measurements.
## Component diagram
![](./2023-Altimeter-WLAN-resources/component_diagram.svg)
## Filesystem
Note: LittleFS requires absolute paths (filenames must start with a `/`).
It also cannot use files which are stored in folders other than the current one.
The filesystem is implemented using a self-written abstraction layer to
allow testing of certain components in a desktop environment.
On a desktop machine standard C++ library functions are used for file accesses.
On the microcontroller LittleFS is used instead.
LittleFS is used to persist a single measurement run in flash.
The saving is done automatically at the end of the measurement cycle.
## REST API
All API requests have to be directed to various subdirectories of the `/api` path depending
on their intended purpose.
### `/api/sensors`
**HTTP GET Requests:**
| Parameters | Expected response | Example request |
| ---------------------- | ----------------------------------------------------------------------------------------------------------------- | ------------------------------- |
| None (always returned) | Returns current sensor values. These are not read from the buffer but instead freshly requested from the sensors. | `<url>/api/sensors` |
| `references` | Returns the current references and offsets values used for calibrating the sensor measurements | `<url>/api/sensors?references` |
This endpoint allows chaining of multiple parameters listed in the table. For example, sending a request `<url>/api/sensors?sampleCount&references`
will return the data from both of those parameters and a current sensor value. Adding the `from=` and `to=` parameters to this request,
will return a chunk of measured sensor values instead of a current sensor value.
### `/api/log`
**HTTP GET Requests:**
| Parameters | Expected response | Example request |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------- |
| None | Returns last log message | `<url>/api/log` |
| `from=`, `to=` | Returns chunk of log messages. Can be negative or positive indexed, e.g. `from=-1` and `to=-3` will return the 3 newest messages while `from=0` and `to=2` will return the 3 oldest. Requesting more messages than exist will not cause an error and only return the maximum currently available. | `<url>/api/log?from=-1&to=-3` |
### `/api/system`
**HTTP GET Requests:**
| Parameters | Expected response | Example request |
| ------------- | -------------------------------------------------------------- | ------------------------------ |
| None | Returns general system information such as current system time | `<url>/api/system` |
| `clock-state` | Returns the state of the system clock | `<url>/api/system?clock-state` |
**HTTP POST:**
| Parameters | Result |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `h=`, `m=`, `s=` | Sets the hours, minutes and seconds of the controllers internal clock. Parameters must all be used and must be in valid range or HTTP 400 will be returned. |
| `restart=true` | Restarts the controller. |
| `rezero=true` | Recalibrates the reference temperature and pressure for the relative height calculation |
| `start=true` | Triggers start event for state machine. Response of controller depends on current state. Cannot be sent along with `stop=true`. |
| `stop=true` | Triggers stop event for state machine. Response of controller depends on current state. Cannot be sent along with `start=true`. |
### `/api/file`
**HTTP GET Requests:**
| Parameters | Expected response | Example request |
| ------------ | ------------------------------------------------------------------------------------------------------ | ------------------------------------ |
| `filename=` | Tries to open a file with the given filename and returns it on success. Filename must start with a `/` | `<url>/api/file?filename=/style.css` |
| `data` | Returns the file storing the persisted measurement data from the previous measurement run. | `<url>/api/file?data` |
| `references` | Returns the file storing the reference measurements | `<url>/api/file?references` |
## Unit tests
## Known Issues
- **Sensor task dropping samples while old values are written to filesystem:**<br>
The sensor task often drops individual values due to missed deadlines when the
statemachine task is busy writing old sensor values to the filesystem.
This is likely caused by the filesystem operations taking too long.
Theoretically the sensor task should not be impacted by this as it is
explicitly executed on another processor core and has a higher priority than
any other task.
- **wifiScan function prevents webinterface log messages:**<br>
The `wifiScan()` function (defined in `main.cpp`) can prevent the webinterface from displaying log messages when used.
This is caused by the function finding networks with non-ASCII symbols in their name, printing them to the log and the
JSON parser in the webinterface failing to interpret those characters.
- **WiFi SoftAP signal extremely weak:**<br>
The WiFi network hosted by the controller when no other network can be connected to
is extremely short range and often drops the connection to client devices.
- **HMC5883 sensor does not work at all:**<br>
When using the HMC5883 sensor (compass), only `[Wire.cpp:499] requestFrom(): i2cWriteReadNonStop returned Error -1`
is logged to the UART. This is likely a driver issue. For the time being requests to this sensor have been
completely disabled in the `getSensorValues()` function in `sensors.cpp`.
- **[ 20758][E][vfs_api.cpp:105] open(): /littlefs/persistData.csv does not exist, no permits for creation:**<br>
This error indicates the LittleFS library is trying to open a file that does
not exist. To check whether a file exists with LittleFS, the program has to
try and open it. If the open command fails, the file does not exist.
This error is caused by the webserver periodically checking whether a file
containing data to download is available, to indicate this state to the user.
Once a file has been created due to the program entering the sampling state,
it goes away.
# Future Improvements
## Hardware improvement ideas:
- Onboard SMD LED for simple status indication
- Onboard switch for correctly entering debug mode
## Software improvement ideas:
- Fix the [known issues](#known-issues)
- Expand plotting script to perform post processing of data before plotting,
such as filtering out measurement spikes.
- Expand plotting script to also plot the flight path. This requires measuring
the orientation of the controller during flight though for which the HMC5883
sensor issue has to be resolved.
- Improve webinterface design
# Additional resources/guides
## General information
- [Getting Started with Seeed Studio XIAO ESP32C3](https://wiki.seeedstudio.com/XIAO_ESP32C3_Getting_Started/): Getting started guide for MCU. Includes pinout and general information.
- [Seeed Studio XIAO ESP32C3](https://docs.platformio.org/en/latest/boards/espressif32/seeed_xiao_esp32c3.html): PlatformIO docs for using the XIAO ESP32C3.
- [First Steps with a GY-86 10DOF Sensor: MPU6050, HMC5883L and MS5611](https://www.youtube.com/watch?v=hvjNaIlHPV0): Getting started YouTube video.
## Debugging
- [Debug an ESP32-C3 via its JTAG interface](https://tutoduino.fr/en/tutorials/debug-esp32/debug-an-esp32-c3-via-its-jtag-interface/): Setup of JTAG debugging using on-board JTAG interface.
- [JTAG Debugging Tips and Quirks](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-guides/jtag-debugging/tips-and-quirks.html): Tips and quirks for jtag debugging on an ESP32.
- [Use the PlatformIO Debugger on the ESP32 Using an ESP-prog](https://www.hackster.io/brian-lough/use-the-platformio-debugger-on-the-esp32-using-an-esp-prog-f633b6): Guide to using the esp-prog debug probe in PlatformIO.
- [ESP32 Web Server using Server-Sent Events (Update Sensor Readings Automatically)](https://randomnerdtutorials.com/esp32-web-server-sent-events-sse/): Example on how to automatically update information on a webpage from the controller side.
\ No newline at end of file