Skip to content

Commit

Permalink
Merge pull request #9 from etienne1911/websockets
Browse files Browse the repository at this point in the history
Websockets
  • Loading branch information
etienne-85 authored Mar 27, 2022
2 parents 9e9fb33 + 6b66c96 commit 0b10221
Show file tree
Hide file tree
Showing 12 changed files with 344 additions and 119 deletions.
1 change: 1 addition & 0 deletions .config/platformio.ini
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ lib_deps =
me-no-dev/AsyncTCP@^1.1.1
https://github.com/me-no-dev/ESPAsyncWebServer.git ;use this lib instead of me-no-dev/ESP Async WebServer@^1.2.3
ayushsharma82/AsyncElegantOTA@^2.2.6
bblanchon/ArduinoJson@^6.19.3
; esp32m/ESP32 logger@^1.0.2
; FS
; Wifi
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,9 @@ jobs:
needs: workspace_setup
runs-on: ubuntu-latest
env:
LIB_NAME: esp32-toolkit-lib
PIO_ENV_CONF: esp32-latest
FIRMWARE_SRC: firmware-main.cpp # firmware source file to build
steps:
- name: Retrieve workspace artifact
uses: actions/download-artifact@v2
Expand All @@ -80,6 +82,8 @@ jobs:
pip install --upgrade platformio
- name: Build project
run: |
# link to selected firmware source file
rm src/main.cpp && ln -s ../lib/$LIB_NAME/examples/$FIRMWARE_SRC src/main.cpp
pio run -e $PIO_ENV_CONF
- name: Firmware artifact
uses: actions/upload-artifact@v2
Expand Down
20 changes: 20 additions & 0 deletions CamControls.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <FirmwareModule.h>
#include <WebSocketService.h>

class CamControls : FirmwareModule, WebSocketService
{
public:
CamControls() : FirmwareModule("CamControls"), WebSocketService("camcontrols"){};

void process()
{
Serial.println("[CamControls] process message");
// Most of the time, you can rely on the implicit casts.
// In other case, you can do doc["time"].as<long>();
// const char *service = jsonMsg["service"];
int count = jsonMsg["counter"];

// Print values.
Serial.println(count);
}
};
2 changes: 1 addition & 1 deletion FirmwareModule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ void FirmwareModule::setupAll()
int i = 0;
for (auto module : *FirmwareModule::instances)
{
logStr = "[Firmware] module " + std::to_string(i + 1) + " of " + std::to_string(total) + ": ";
logStr = "[Module " + std::to_string(i + 1) + "/" + std::to_string(total) + "] ";
logStr += "*** " + module->moduleName + " ***";
Serial.println(logStr.c_str());
module->setup();
Expand Down
2 changes: 1 addition & 1 deletion FirmwareModule.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class FirmwareModule
std::string moduleName;

public:
FirmwareModule(std::string moduleName = "Default");
FirmwareModule(std::string moduleName = "defaultmodule");

virtual void onCycle();

Expand Down
54 changes: 40 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,49 @@
# esp32-toolkit-lib
ESP32 firmware building lib
ESP32 firmware build lib

## Purpose
Aim is to provide core features to quickly develop ESP32 firmware for various diy projects, as well as a modular architecture to extend firmware's features .

Aim is to provide useful features commonly used in ESP32 firmware development and a flexible architecture to develop custom firmware modules.
# Quick start

Several other benefits among which:
- no boilerplate code
- entirely automated build process
- ready to use workspace dev environment
- easy to maintain and evolve
- Grab a workspace artifact which includes source and project configuration
- Customize wifi settings in `network-module.h`.
- Build one of the default firmware available in exemples by linking main.cpp to the chosen firmware source file.
- Upload to the board using serial interface.

## Key features:

## Included frameworks, librairies
The lib makes use of the following libs:
- latest [arduino-esp32](https://github.com/espressif/arduino-esp32) framework ([docs](https://docs.espressif.com/projects/arduino-esp32/en/latest/)): mandatory to use littleFS (not available to date in PIO)
- `littleFS` filesystem with strong advantage (over SPIFFS) to support directories.
- `ArduinoJson`: provide support for JSON

# Architecture

The firmware architecture is made of different modules and services.
A module inherits the `FirmwareModule` class and can overrides `setup`, `loop` methods to provide its own implementation.
In a similar way, a service provides a custom implementation of another service such as `WebSocketService`.


## Core modules
The firmware can embedd several core modules which implements main device features:

- network: provides wifi connectivity in both AP (Access Point) and STA (Client) modes
- filesystem: LittleFS
- webserver
- WebSockets Service
- OTA service

## Custom modules
On top of these core modules, firmware can be easily extended through custom modules to provide additional features (such as specific sensor support, ...).
To get included in the main update loop, a custom module inherits the `FirmwareModule` class.
As C++ allows multiple inheritance, it can also inherit a service to make use of it.

For instance a module requiring acceess to websockets, will inherit both `FirmwareModule` and `WebSocketService` which will forward
received message to process them.

## Predefined firmwares
A set of predefined firmwares is ready to be used:
# Purpose of this project

- mini version
- base version: mini version + network + filesys + web server + web sockets + OTA
- full version: base version + json +
- as less as boilerplate code possible
- fully automated build process
- ready to code workspace (available as build artifacts)
- easy to maintain and customize
20 changes: 20 additions & 0 deletions RovControl.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include <FirmwareModule.h>
#include <WebSocketService.h>

class RovControl : FirmwareModule, WebSocketService
{
public:
RovControl() : FirmwareModule("RovControl"), WebSocketService("rovcontrol"){};

void process()
{
Serial.println("[RoverControl] process message");
// Most of the time, you can rely on the implicit casts.
// In other case, you can do doc["time"].as<long>();
int time = jsonMsg["counter"];

// Print values.
// Serial.println(service);
Serial.println(time);
}
};
81 changes: 0 additions & 81 deletions WebSocket.h

This file was deleted.

123 changes: 123 additions & 0 deletions WebSocketService.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#include <WebSocketService.h>
#include <string>
AsyncWebSocket WebSocketService::aws("/ws");
std::vector<WebSocketService *> *WebSocketService::instances = new std::vector<WebSocketService *>();

// Allocate the JSON document
//
// Inside the brackets, 200 is the capacity of the memory pool in bytes.
// Don't forget to change this value to match your JSON document.
// Use arduinojson.org/v6/assistant to compute the capacity.
// StaticJsonDocument<200> doc;

// StaticJsonDocument<N> allocates memory on the stack, it can be
// replaced by DynamicJsonDocument which allocates in the heap.
//
// DynamicJsonDocument doc(200);
StaticJsonDocument<200> WebSocketService::jsonMsg;

// JSON stub string.
//
// Using a char[], as shown here, enables the "zero-copy" mode. This mode uses
// the minimal amount of memory because the JsonDocument stores pointers to
// the input buffer.
// If you use another type of input, ArduinoJson must copy the strings from
// the input to the JsonDocument, so you need to increase the capacity of the
// JsonDocument.
char jsonDataStub[] = "{\"sensor\":\"gps\",\"time\":1351824120,\"data\":[48.756080,2.302038]}";

WebSocketService::WebSocketService(std::string name) : name(name)
{
// register service instance
WebSocketService::instances->push_back(this);
}

void WebSocketService::dispatch(void *arg, uint8_t *data, size_t len)
{
AwsFrameInfo *info = (AwsFrameInfo *)arg;
if (info->final && info->index == 0 && info->len == len && info->opcode == WS_TEXT)
{
data[len] = 0;
// convert raw data input
std::string clientMsg(data, data + len);
std::string serverMsg("");

// convert to a json object
DeserializationError error = deserializeJson(jsonMsg, clientMsg);

// Test if parsing succeeds.
if (!error)
{
// JsonObject jsonObj = jsonMsg.as<JsonObject>();

// for (JsonPair kv : jsonObj)
// {
// std::string serviceName(kv.key().c_str());
// JsonObject serviceData = kv.value().as<JsonObject>();

// }
// dispatch message to corresponding service
std::string serviceName = jsonMsg["service"];
Serial.println(serviceName.c_str());
for (auto service : *WebSocketService::instances)
{
Serial.println(service->name.c_str());
// match service name
if (service->name.compare(serviceName) == 0)
{
std::string log = "[WebSocket] message routed to service " + serviceName;
Serial.println(log.c_str());
service->process();
}
}

serverMsg.append(clientMsg);
}
else
{
Serial.print(F("deserializeJson() failed: "));
Serial.println(error.f_str());
}

// notify server response to all clients
String dataOut(serverMsg.c_str());
aws.textAll(dataOut);
}
}

void WebSocketService::eventHandler(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
void *arg, uint8_t *data, size_t len)
{
switch (type)
{
case WS_EVT_CONNECT:
Serial.printf("[WebSocket] client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
break;
case WS_EVT_DISCONNECT:
Serial.printf("[WebSocket] client #%u disconnected\n", client->id());
break;
case WS_EVT_DATA:
Serial.println("[WebSocket] Data received");
dispatch(arg, data, len);
break;
case WS_EVT_PONG:
case WS_EVT_ERROR:
break;
}
}

void WebSocketService::process()
{
// Most of the time, you can rely on the implicit casts.
// In other case, you can do doc["time"].as<long>();
const char *sensor = jsonMsg["sensor"];
long time = jsonMsg["time"];
double latitude = jsonMsg["data"][0];
double longitude = jsonMsg["data"][1];

// Print values.
Serial.println(sensor);
Serial.println(time);
Serial.println(latitude, 6);
Serial.println(longitude, 6);
}
30 changes: 30 additions & 0 deletions WebSocketService.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
#pragma once

#include <ESPAsyncWebServer.h>
#include <ArduinoJson.h>
#include <string>

class WebSocketService
{
public:
static AsyncWebSocket aws;
static std::vector<WebSocketService *> *instances; // the components that will process json message
static StaticJsonDocument<200> jsonMsg;
std::string name = "";

// WebSocketImpl(string awsUrl) : FirmwareModule("WebSocket")
// {
// Serial.printf("[WebSocket] creation");
// aws = new AsyncWebSocket(awsUrl.c_str());
// }
WebSocketService(std::string name = "defaultservice");

static void eventHandler(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type,
void *arg, uint8_t *data, size_t len);

static void dispatch(void *arg, uint8_t *data, size_t len);

static void parseJson(std::string jsonStr);

virtual void process();
};
Loading

0 comments on commit 0b10221

Please sign in to comment.