-
Notifications
You must be signed in to change notification settings - Fork 0
Firmware Overview
Firmware for both the Network Modules and Transmitter Modules is written in the ESP-IDF development framework. Firmware for all modules is written in a single ESP-IDF Project, with an ESP-IDF config setting which selects which specific module to build for (Network vs. Transmitter).
The project is structured as a collection of ESP-IDF Components invoked via a basic driver program which starts FreeRTOS tasks to communicate with the server and manage the feedback submodule(s). For an overview of the firmware, it is sufficient to review the Driver Code section.
main.c contains app_main(), the application entry point for programs compiled with ESP-IDF. This function:
- invokes the
NVSUtil("Non-Volatile Storage Utility") component to retrieve data stored on the ESP's NVS. Currently, the NVS stores the network SSID, network password, server IP, server port, and module ID number. - invokes the
MZSTModuleImplcomponent viainitMzstModule(), which does any hardware setup specific to the module type being targeted (pin setup, interrupt setup, etc.) - connects to the WiFi network via
startWifi(), defined inWiFi.c(not currently its own component). - starts a Task invoking the
AdminListenercomponent, which listens for and processes messages from an admin connector (adminListenerLoop()). - connects to the MZST server by invoking the
Servercomponent viaserverConnect(), then starts a task runningserverMessageLoopto handle all further server communications. - once more invokes the
MZSTModuleImplcomponent to run afeedbackLooptask, which manages the feedback submodule(s).
The components are designed to operate independently: any cross-component communication is handled using either dependency injection, or global variables defined at the program's top level. For example, the MZSTModuleImpl component defines a processCommandCommon(char*) function, which takes a command and does some module-specific action. The Server component runs serverMessageLoop(*fn(char*)) in a task, which must listen to and handle messages from the server. So, serverMessageLoop(*fn(char*)) takes a function pointer as an argument, designating what function should be used to actually process the messages. The driver program invokes serverMessageLoop(processCommandCommon), which keeps the two components decoupled while still allowing them to invoke each other.
The AdminListener component defines an adminListenerLoop(*fn(char*)) function intended to operate in its own FreeRTOS task. This function installs a listener socket which accepts incoming connections on port ADMIN_LISTEN_PORT (7777), receives a single batch of up to ADMIN_RECEIVE_BUF_SIZE (1024) bytes, and processes the command using the passed fn(char*) function (nominally processCommandCommon).
The MZSTModuleImpl component contains code to control the hardware itself. A CommonModule.c file contains functionality that is common to all module types (NetworkModule and TransmitterModule); separate NetworkModule.c and TransmitterModule.c define module-type-specific functionality.
The common code defines the following functions:
-
int64_t getCurrentTimeAbsUs(), returns the current timestamp in microseconds. -
void processCommandCommon(char* cmd), API for other parts of the project to interact with the hardware. Typically, the server will send a command, which is received by theServercomponent; the command is then passed to this function. The function contains handlers for different commands. If no match is found for a given command, the command will be passed down to the module-specificprocessCommandSpecific(char*)function.
Implementation files must define the following functions (stubs are acceptable):
-
void initMzstModule(), performs any hardware-specific setup such as pin setup, interrupt setup, LED setup, etc. -
void processCommandSpecific(char* cmd), fallthrough for commands which could not be handled byprocessCommandCommon(). -
void setColor(int r, int g, int b), sets the LEDs of the module. -
void feedbackLoop(), a loop function which runs in its own task and handles any logic required by feedback submodules (such as unsetting LEDs after a certain amount of time).
The component's CMakeLists shows that the specific implementation file to be built is specified by a custom ESP-IDF config setting defined in the component's KConfig file. Running idf.py menuconfig allows you to specify which module type to build the project for, under "MZST Module Type".
Also defines a void rfTransmissionReceived(void* args) callback function, which may be invoked through processCommandSpecific(). ... also touchpadIsr(), setState()...
Also defines a void transmit() callback function, which may be invoked through a physical debug button or via processCommandSpecific(). ...
The NVSUtil component is effectively a light wrapper around the built-in ESP-IDF nvs_flash component. It defines the following functions used by the driver code:
-
nvsInit(), must be called before any other NVSUtil functions. -
nvsGetInt(char* key, uint32_t& out, retrieve a uint32_t at key and store it in out. -
nvsGetStr(char* key, char&& out), retrieve a string at key, allocate space for it in out, and store it in out. Appropriate call looks likechar* s; nvsGetStr("example_key", &s). This function allocates space, so if the result must be discarded, it should be freed. -
nvsSetInt(char* key, uint32_t val), store a uint32_t at key. Must be followed (eventually) by a call tonvsCommit(). -
nvsSetSet(char* key, char* val), store a (null-terminated) string at key. Must be followed (eventually) by a call tonvsCommit(). -
nvsCommit(), commits any pendingnvsSet...()calls to NVS. -
dumpNVS(), prints a debug-friendly output of all keys currently stored on NVS. Not implemented yet -
nvsWipe(), wipes all data from NVS. Not implemented yet
The Server component contains all functionality and protocols required for communicating with the Server. It defines the following functions used by the driver code:
-
serverConnect(char* ip, uint16_t port, uint16_t id), establishes a socket connection to the server, sets up internal buffers and semaphores used for queuing outbound messages, and sets aconnectedflag to true, allowing access to theserverSend()function. -
serverMessageLoop(void (*processCommandFunction)(char* cmd)), a loop function which runs in its own task and 1. pulls messages from the outbound-message-queue and delivers them, and 2. checks the socket for any incoming data (commands) and adds it to an internal buffer, dispatching the data toprocessCommandFunctiononce a newline is encountered. -
serverSend(uint16_t mtype, uint16_t id, uint32_t data), which posts a message to the outbound-message-queue (thread-safe, using a semaphor/mutex-controlled ring buffer), where it will eventually be delivered by theserverMessageLoop()task.
Internally, the Server component also defines a __serverReconnect() function. If the serverMessageLoop() task detects a lost connection, __serverReconnect() cleans up the internal buffers, semaphors, and outbound-message-queue, then attempts to reconnect to the server.
...