diff --git a/firmware/src/MessageCodes.h b/firmware/src/MessageCodes.h new file mode 100644 index 0000000..1cb4b9e --- /dev/null +++ b/firmware/src/MessageCodes.h @@ -0,0 +1,24 @@ +#pragma once + + +enum class MessageCode : uint8_t +{ + // from swim tracker device to frontend + ERROR = 1, + + INITIAL_INFO = 2, + SESSION_STARTED = 3, + SESSION_STOPPED = 4, + SESSION_NEW_DATA = 5, + ANSWER_USER_LIST = 6, + ANSWER_SESSION_LIST = 7, + + // from frontend to device + START_SESSION = 128, + STOP_SESSION = 129, + TARE = 130, + QUERY_USER_LIST = 131, + QUERY_SESSION_LIST = 132, + WIFI_STATE_SET = 133, + WIFI_STATE_GET = 134, +}; diff --git a/firmware/src/SessionAPI.h b/firmware/src/SessionAPI.h new file mode 100644 index 0000000..332e239 --- /dev/null +++ b/firmware/src/SessionAPI.h @@ -0,0 +1,169 @@ + +#include "Dtypes.h" +#include "MessageCodes.h" +#include "SwimTrackerConfig.h" + +#include +#include + +template +class SessionManager; + +template +class SessionAPI +{ +public: + SessionAPI(SessionManager &sessionManager) + : sessionManager_(sessionManager) + { + } + + void onClientConnect(websockets::WebsocketsClient &client); + bool handleMessage(websockets::WebsocketsClient &client, MessageCode code, const char *payload, size_t size); + + template + void iteration(TServer &server); + +private: + template + void sendSessionStartMessages(TServer &server); + template + void sendSessionStopMessages(TServer &server); + template + void sendNewDataMessages(TServer &server); + + SessionManager &sessionManager_; + size_t numSentMeasurements_[MAX_WEBSOCKET_CONNECTIONS]; + bool running_; +}; + +// sending message about current session +template +void SessionAPI::onClientConnect(websockets::WebsocketsClient &client) +{ + // TODO write msgpack instead for consistency? + + using MeasurementT = typename T::MeasurementType; + + // Message format: + // - uint8_t messageType + // - uint8_t running + // - uint32_t sessionId + // - MeasurementT [] measurements (if running) + + auto &session = sessionManager_.session(); + const auto numMeasurements = session.numMeasurements(); + const auto sessionId = session.getStartTime(); + + const size_t msgSize = sizeof(uint8_t) + sizeof(uint8_t) + sizeof(sessionId) + sizeof(MeasurementT) * numMeasurements; + char *msg = (char *)heap_caps_malloc(msgSize, MALLOC_CAP_SPIRAM); + + char *writeHead = msg; + + *writeHead = MessageCode::INITIAL_INFO; + writeHead += sizeof(uint8_t); + + *writeHead = sessionManager_.isMeasuring(); + writeHead += sizeof(uint8_t); + + *((uint32_t *)writeHead) = sessionManager_.isMeasuring() ? sessionId : 0; + writeHead += sizeof(uint32_t); + + assert(writeHead - msg == msgSize - sizeof(MeasurementT) * numMeasurements); + + memcpy(writeHead, session.getDataPointer(), sizeof(MeasurementT) * numMeasurements); + client.sendBinary(msg, msgSize); + + free(msg); +} + +template +bool SessionAPI::handleMessage(websockets::WebsocketsClient &client, MessageCode code, + const char *payload, size_t size) +{ + switch (code) + { + case MessageCode::START_SESSION: + this->sessionManager_.startMeasurements(); + return true; + case MessageCode::STOP_SESSION: + this->sessionManager_.stopMeasurements(); + return true; + case MessageCode::TARE: + this->sessionManager_.tare(); + return true; + } + return false; +} + +template +template +void SessionAPI::iteration(TServer &server) +{ + if (!running_ && sessionManager_.isMeasuring()) + { + sendSessionStartMessages(server); + for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i) + numSentMeasurements_[i] = 0; + } + else if (running_ && !sessionManager_.isMeasuring()) + { + sendSessionStopMessages(server); + for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i) + numSentMeasurements_[i] = 0; + } + sendNewDataMessages(server); +} + +template +template +void SessionAPI::sendSessionStartMessages(TServer &server) +{ + StaticJsonDocument<128> data; + data["sessionId"] = sessionManager_.session().getStartTime(); + server.sendToAll<32>(MessageCode::SESSION_STARTED, data); +} + +template +template +void SessionAPI::sendSessionStopMessages(TServer &server) +{ + MessageCode code = MessageCode::SESSION_STOPPED; + server.sendToAll<32>(code); +} + +template +template +void SessionAPI::sendNewDataMessages(TServer &server) +{ + constexpr size_t MAX_MEASUREMENTS_PER_MSG = 16; + constexpr size_t WAIT_UNTIL_AT_LEAST_NUM_MEASUREMENTS = 1; + + // new data messages are the only messages not sent in msgpack format + // since they are sent multiple times a second + using MeasurementT = typename T::MeasurementType; + auto &session = sessionManager_.session(); + + char buffer[1 + MAX_MEASUREMENTS_PER_MSG]; + buffer[0] = MessageCode::SESSION_NEW_DATA; + constexpr int headerSize = 1; + + for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i) + { + auto &c = server.client(i); + if (c.available()) + { + MeasurementT *dataToSend = session.getDataPointer() + numSentMeasurements_[i]; + int32_t numMeasurementsToSend = int32_t(session.numMeasurements()) - int32_t(numSentMeasurements_[i]); + if (numMeasurementsToSend >= WAIT_UNTIL_AT_LEAST_NUM_MEASUREMENTS) + { + if (numMeasurementsToSend > MAX_MEASUREMENTS_PER_MSG) + numMeasurementsToSend = MAX_MEASUREMENTS_PER_MSG; + + memcpy(buffer + headerSize, dataToSend, sizeof(MeasurementT) * numMeasurementsToSend); + c.sendBinary(buffer, headerSize + sizeof(MeasurementT) * numMeasurementsToSend); + numSentMeasurements_[i] += numMeasurementsToSend; + } + } + } +} diff --git a/firmware/src/WifiAPI.cpp b/firmware/src/WifiAPI.cpp new file mode 100644 index 0000000..eecdad2 --- /dev/null +++ b/firmware/src/WifiAPI.cpp @@ -0,0 +1,46 @@ +#include "WifiAPI.h" +#include "WifiManager.h" + +#include "WebsocketServer.h" + +bool WifiAPI::handleMessage(websockets::WebsocketsClient &client, MessageCode code, const char *payload, size_t size) +{ + if (code == MessageCode::WIFI_STATE_GET) + { + StaticJsonDocument<128> data; + data["state"] = wifiManager_.stateStr(); + sendToClient<64>(client, MessageCode::WIFI_STATE_GET, data); + return true; + } + else if (code == MessageCode::WIFI_STATE_SET) + { + StaticJsonDocument<1024> json; + + DeserializationError err = deserializeJson(json, payload, size); + if (err) + { + sendErrorToClient(client, "Deserialization Error"); + return true; + } + if (json.containsKey("reset_to_provisioning") && json["reset_to_provisioning"].as()) + { + wifiManager_.resetToApProvisioning(); + restartScheduled_ = true; + return true; + } + else if (json.containsKey("ap_password")) + { + wifiManager_.setApCredentials(json["ap_password"].as()); + restartScheduled_ = true; + return true; + } + else if (json.containsKey("sta_ssid") && json.containsKey("sta_password")) + { + wifiManager_.setStaCredentials(json["sta_ssid"].as(), // + json["sta_password"].as()); + restartScheduled_ = true; + return true; + } + } + return false; +} diff --git a/firmware/src/WifiAPI.h b/firmware/src/WifiAPI.h new file mode 100644 index 0000000..a7a983a --- /dev/null +++ b/firmware/src/WifiAPI.h @@ -0,0 +1,34 @@ +#include "Dtypes.h" +#include "MessageCodes.h" +#include "SwimTrackerConfig.h" + +#include +#include + +class WifiManager; + +class WifiAPI +{ +public: + WifiAPI(WifiManager &wifiManager) + : wifiManager_(wifiManager), restartScheduled_(false) + { + } + + void onClientConnect(websockets::WebsocketsClient &client) {} + bool handleMessage(websockets::WebsocketsClient &client, MessageCode code, const char *payload, size_t size); + + template + void iteration(TServer &server) + { + if (restartScheduled_) + { + Serial.print("Restart triggered by WifiAPI"); + ESP.restart(); + } + } + +private: + WifiManager &wifiManager_; + bool restartScheduled_; +}; diff --git a/hardware/case/swim_tracker_case_2.FCStd b/hardware/case/swim_tracker_case_2.FCStd new file mode 100644 index 0000000..e60a996 Binary files /dev/null and b/hardware/case/swim_tracker_case_2.FCStd differ