Wifi scanning
This commit is contained in:
parent
26d3469c83
commit
fdfd26307e
|
@ -78,12 +78,13 @@ void WifiManager::resetToApProvisioning()
|
||||||
prefs_.remove("staPassword");
|
prefs_.remove("staPassword");
|
||||||
}
|
}
|
||||||
|
|
||||||
void WifiManager::wifiWatchdog()
|
void WifiManager::iteration()
|
||||||
{
|
{
|
||||||
if (state_ == STA && WiFi.status() != WL_CONNECTED) {
|
if (state_ == STA && WiFi.status() != WL_CONNECTED) {
|
||||||
startWifi();
|
startWifi();
|
||||||
Serial.println("Connection lost - Restarting WIFI");
|
Serial.println("Connection lost - Restarting WIFI");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
*
|
*
|
||||||
* When operating in access point mode, the device IP is 192.168.42.1
|
* When operating in access point mode, the device IP is 192.168.42.1
|
||||||
*
|
*
|
||||||
* call wifiWatchdog regularly to reconnect in station mode if connection was lost
|
* call iteration() regularly, it has a wifiWatchdog to reconnect in station mode if connection was lost
|
||||||
*/
|
*/
|
||||||
class WifiManager
|
class WifiManager
|
||||||
{
|
{
|
||||||
|
@ -34,7 +34,7 @@ public:
|
||||||
void setApCredentials(const char *password);
|
void setApCredentials(const char *password);
|
||||||
void resetToApProvisioning();
|
void resetToApProvisioning();
|
||||||
|
|
||||||
void wifiWatchdog();
|
void iteration();
|
||||||
|
|
||||||
bool inProvisioningMode() const { return state_ == INVALID || state_ == AP_PROVISIONING; }
|
bool inProvisioningMode() const { return state_ == INVALID || state_ == AP_PROVISIONING; }
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ enum class MessageCode : uint8_t
|
||||||
SESSION_NEW_DATA = 5,
|
SESSION_NEW_DATA = 5,
|
||||||
ANSWER_USER_LIST = 6,
|
ANSWER_USER_LIST = 6,
|
||||||
ANSWER_SESSION_LIST = 7,
|
ANSWER_SESSION_LIST = 7,
|
||||||
|
WIFI_STATE_RESPONSE = 8,
|
||||||
|
WIFI_SCAN_RESPONSE = 9,
|
||||||
|
|
||||||
// from frontend to device
|
// from frontend to device
|
||||||
START_SESSION = 128,
|
START_SESSION = 128,
|
||||||
|
@ -21,4 +23,5 @@ enum class MessageCode : uint8_t
|
||||||
QUERY_SESSION_LIST = 132,
|
QUERY_SESSION_LIST = 132,
|
||||||
WIFI_STATE_SET = 133,
|
WIFI_STATE_SET = 133,
|
||||||
WIFI_STATE_GET = 134,
|
WIFI_STATE_GET = 134,
|
||||||
|
WIFI_TRIGGER_SCAN = 135,
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,13 +2,11 @@
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
||||||
|
|
||||||
// Uncomment for Version 2.0 where load cell is connected differently
|
// Uncomment for Version 2.0 where load cell is connected differently
|
||||||
//#define _HW_V_20
|
#define _HW_V_20
|
||||||
#define NEW_HEAVY_LOAD_CELL
|
#define NEW_HEAVY_LOAD_CELL
|
||||||
|
|
||||||
|
constexpr const char *CONFIG_HOSTNAME = "swimtracker";
|
||||||
const char *CONFIG_HOSTNAME = "swimtracker";
|
|
||||||
|
|
||||||
// ------------------------------------- Hardware & Measurement Settings ------------------------------------------------------------
|
// ------------------------------------- Hardware & Measurement Settings ------------------------------------------------------------
|
||||||
|
|
||||||
|
@ -16,32 +14,31 @@ const uint8_t CONFIG_MEASUREMENT_AVG_COUNT = 1; // number of measurements in
|
||||||
const uint8_t CONFIG_TARE_AVG_COUNT = 20; // number of measurements in tare-phase (to find 0 )
|
const uint8_t CONFIG_TARE_AVG_COUNT = 20; // number of measurements in tare-phase (to find 0 )
|
||||||
const int CONFIG_MEASURE_DELAY = 100; // interval in ms between measurements
|
const int CONFIG_MEASURE_DELAY = 100; // interval in ms between measurements
|
||||||
const uint32_t CONFIG_SESSION_MAX_LENGTH_HOURS = 3; // maximum length of one session
|
const uint32_t CONFIG_SESSION_MAX_LENGTH_HOURS = 3; // maximum length of one session
|
||||||
const char *CONFIG_DATA_PATH = "/dat"; // folder in SPIFFS file system to store measurement data
|
constexpr const char *CONFIG_DATA_PATH = "/dat"; // folder in SPIFFS file system to store measurement data
|
||||||
using MeasurementT = uint16_t; // data type for one measurement
|
using MeasurementT = uint16_t; // data type for one measurement
|
||||||
#ifdef NEW_HEAVY_LOAD_CELL
|
#ifdef NEW_HEAVY_LOAD_CELL
|
||||||
const int CONFIG_VALUE_RIGHT_SHIFT = 3; // uint32 measurements are divided by this power, before stored in uint16_t
|
const int CONFIG_VALUE_RIGHT_SHIFT = 3; // uint32 measurements are divided by this power, before stored in uint16_t
|
||||||
#else
|
#else
|
||||||
const int CONFIG_VALUE_RIGHT_SHIFT = 7;
|
const int CONFIG_VALUE_RIGHT_SHIFT = 7;
|
||||||
#endif
|
#endif
|
||||||
const MeasurementT CONFIG_KG_FACTOR_INV = 701; // after shifting - how many "measurement units" are one kg
|
const MeasurementT CONFIG_KG_FACTOR_INV = 701; // after shifting - how many "measurement units" are one kg
|
||||||
|
|
||||||
|
static constexpr int MAX_WEBSOCKET_CONNECTIONS = 3; // maximal number of websocket connections maintained at the same time
|
||||||
|
|
||||||
const char * UPDATE_URL = "https://swimtracker-update.bauer.tech/firmware.bin";
|
constexpr const char *UPDATE_URL = "https://swimtracker-update.bauer.tech/firmware.bin";
|
||||||
|
|
||||||
|
|
||||||
// auto start/stop
|
// auto start/stop
|
||||||
MeasurementT CONFIG_AUTO_START_MIN_THRESHOLD = CONFIG_KG_FACTOR_INV * 1;
|
constexpr MeasurementT CONFIG_AUTO_START_MIN_THRESHOLD = CONFIG_KG_FACTOR_INV * 1;
|
||||||
MeasurementT CONFIG_AUTO_START_MAX_THRESHOLD = CONFIG_KG_FACTOR_INV * 3;
|
constexpr MeasurementT CONFIG_AUTO_START_MAX_THRESHOLD = CONFIG_KG_FACTOR_INV * 3;
|
||||||
uint32_t CONFIG_AUTO_START_MAX_MEASUREMENTS_BETWEEN_PEAKS = (1000 / CONFIG_MEASURE_DELAY) * 6;
|
constexpr uint32_t CONFIG_AUTO_START_MAX_MEASUREMENTS_BETWEEN_PEAKS = (1000 / CONFIG_MEASURE_DELAY) * 6;
|
||||||
|
|
||||||
MeasurementT CONFIG_AUTO_STOP_THRESHOLD = CONFIG_KG_FACTOR_INV * 1;
|
constexpr MeasurementT CONFIG_AUTO_STOP_THRESHOLD = CONFIG_KG_FACTOR_INV * 1;
|
||||||
//uint32_t CONFIG_AUTO_STOP_NUM_MEASUREMENTS = (1000 / CONFIG_MEASURE_DELAY) * 60 * 15;
|
//uint32_t CONFIG_AUTO_STOP_NUM_MEASUREMENTS = (1000 / CONFIG_MEASURE_DELAY) * 60 * 15;
|
||||||
uint32_t CONFIG_AUTO_STOP_NUM_MEASUREMENTS = (1000 / CONFIG_MEASURE_DELAY) * 30;
|
constexpr uint32_t CONFIG_AUTO_STOP_NUM_MEASUREMENTS = (1000 / CONFIG_MEASURE_DELAY) * 30;
|
||||||
|
|
||||||
|
|
||||||
// ------------------------------------- Derived Settings -----------------------------------------------------------------------------
|
// ------------------------------------- Derived Settings -----------------------------------------------------------------------------
|
||||||
|
|
||||||
const uint32_t CONFIG_SESSION_MAX_SIZE = CONFIG_SESSION_MAX_LENGTH_HOURS * 3600 * (1000 / CONFIG_MEASURE_DELAY) * sizeof(uint16_t);
|
constexpr uint32_t CONFIG_SESSION_MAX_SIZE = CONFIG_SESSION_MAX_LENGTH_HOURS * 3600 * (1000 / CONFIG_MEASURE_DELAY) * sizeof(uint16_t);
|
||||||
static_assert(CONFIG_SESSION_MAX_SIZE < 1024 * 1024, "Measurement data takes more than 1MiB space");
|
static_assert(CONFIG_SESSION_MAX_SIZE < 1024 * 1024, "Measurement data takes more than 1MiB space");
|
||||||
|
|
||||||
#ifdef _HW_V_20
|
#ifdef _HW_V_20
|
||||||
|
|
|
@ -2,21 +2,23 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Dtypes.h"
|
#include "Dtypes.h"
|
||||||
#include "UserDB.h"
|
#include "UserDB.h"
|
||||||
|
#include "MessageCodes.h"
|
||||||
|
|
||||||
#include <ArduinoWebsockets.h>
|
#include <ArduinoWebsockets.h>
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class SessionManager;
|
class SessionManager;
|
||||||
|
|
||||||
static constexpr int MAX_CONNECTIONS = 3;
|
|
||||||
static constexpr int NUM_DATA_CHUNK_SIZE = 1;
|
static constexpr int NUM_DATA_CHUNK_SIZE = 1;
|
||||||
|
|
||||||
template <typename SessionT>
|
template <typename ApiManagerTuple>
|
||||||
class WebsocketServer
|
class WebsocketServer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
WebsocketServer(SessionManager<SessionT> &sessionManager, UserStorage &userStorage, int port)
|
WebsocketServer(int port, ApiManagerTuple &tuple)
|
||||||
: sessionManager_(sessionManager), userStorage_(userStorage), nextFreeClient_(0), port_(port),
|
: port_(port), nextFreeClient_(0), apiManagers_(tuple)
|
||||||
running_(false)
|
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,328 +27,145 @@ public:
|
||||||
server_.listen(port_);
|
server_.listen(port_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void iteration();
|
void iteration()
|
||||||
|
|
||||||
private:
|
|
||||||
void reportSessionUpdate();
|
|
||||||
|
|
||||||
void sendMessageOnConnection(websockets::WebsocketsClient &client);
|
|
||||||
void sendSessionStartMessages();
|
|
||||||
void sendSessionStopMessages();
|
|
||||||
void sendNewDataMessages();
|
|
||||||
|
|
||||||
void sendUserList(websockets::WebsocketsClient &client);
|
|
||||||
void sendSessionList(websockets::WebsocketsClient &client, const String &userId);
|
|
||||||
|
|
||||||
SessionManager<SessionT> &sessionManager_;
|
|
||||||
UserStorage &userStorage_;
|
|
||||||
|
|
||||||
int nextFreeClient_;
|
|
||||||
int port_;
|
|
||||||
|
|
||||||
size_t sentMessageCount_;
|
|
||||||
websockets::WebsocketsServer server_;
|
|
||||||
websockets::WebsocketsClient clients_[MAX_CONNECTIONS];
|
|
||||||
|
|
||||||
// previous session state
|
|
||||||
size_t numSentMeasurements_[MAX_CONNECTIONS];
|
|
||||||
bool running_;
|
|
||||||
};
|
|
||||||
|
|
||||||
using websockets::WebsocketsClient;
|
|
||||||
|
|
||||||
// ------------------------------------- Message types & classes ---------------------------
|
|
||||||
|
|
||||||
enum MessageType
|
|
||||||
{
|
|
||||||
// from swim tracker device to frontend
|
|
||||||
INITIAL_INFO = 1,
|
|
||||||
SESSION_STARTED = 2,
|
|
||||||
SESSION_STOPPED = 3,
|
|
||||||
SESSION_NEW_DATA = 4,
|
|
||||||
ANSWER_USER_LIST = 5,
|
|
||||||
ANSWER_SESSION_LIST = 6,
|
|
||||||
|
|
||||||
// from frontend to device
|
|
||||||
START_SESSION = 7,
|
|
||||||
STOP_SESSION = 8,
|
|
||||||
TARE = 9,
|
|
||||||
QUERY_USER_LIST = 10,
|
|
||||||
QUERY_SESSION_LIST = 11,
|
|
||||||
};
|
|
||||||
|
|
||||||
#pragma pack(push, 1)
|
|
||||||
class SessionStartedMessage
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SessionStartedMessage(uint32_t id) : messageType_(SESSION_STARTED), sessionId_(id) {}
|
|
||||||
void send(WebsocketsClient &c) const
|
|
||||||
{
|
{
|
||||||
c.sendBinary((const char *)(this), sizeof(*this));
|
using namespace websockets;
|
||||||
}
|
const auto onMessage = [this](WebsocketsClient &client, WebsocketsMessage message)
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t messageType_;
|
|
||||||
uint32_t sessionId_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SessionStoppedMessage
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
SessionStoppedMessage() : messageType_(SESSION_STOPPED) {}
|
|
||||||
void send(WebsocketsClient &c) const
|
|
||||||
{
|
|
||||||
c.sendBinary((const char *)(this), sizeof(*this));
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
uint8_t messageType_;
|
|
||||||
};
|
|
||||||
|
|
||||||
template <typename MeasurementT>
|
|
||||||
class SessionNewDataMessage
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
// typically a message contains NUM_DATA_CHUNK_SIZE measurements
|
|
||||||
// if some measurements are skipped, because loop() takes too long
|
|
||||||
// there might actually be more measurements, to be safe there is an
|
|
||||||
// additional factor here
|
|
||||||
static constexpr size_t MAX_MEASUREMENTS = 4 * NUM_DATA_CHUNK_SIZE;
|
|
||||||
|
|
||||||
SessionNewDataMessage(MeasurementT *ptr, size_t numMeasurements)
|
|
||||||
: messageType_(SESSION_NEW_DATA), numMeasurements_(min(numMeasurements, MAX_MEASUREMENTS))
|
|
||||||
{
|
|
||||||
memcpy(measurements_, ptr, sizeof(MeasurementT) * numMeasurements_);
|
|
||||||
}
|
|
||||||
|
|
||||||
void send(WebsocketsClient &c) const
|
|
||||||
{
|
|
||||||
c.sendBinary((const char *)(this), numBytes());
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t numMeasurements() const
|
|
||||||
{
|
|
||||||
return numMeasurements_;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
size_t numBytes() const { return sizeof(uint8_t) + numMeasurements() * sizeof(MeasurementT); }
|
|
||||||
|
|
||||||
// data to be sent
|
|
||||||
uint8_t messageType_;
|
|
||||||
MeasurementT measurements_[MAX_MEASUREMENTS];
|
|
||||||
|
|
||||||
// book-keeping
|
|
||||||
size_t numMeasurements_;
|
|
||||||
};
|
|
||||||
|
|
||||||
#pragma pack(pop)
|
|
||||||
|
|
||||||
// ------------------------------------- WebsocketServer members ---------------------------
|
|
||||||
|
|
||||||
template <typename SessionT>
|
|
||||||
void WebsocketServer<SessionT>::iteration()
|
|
||||||
{
|
|
||||||
using namespace websockets;
|
|
||||||
|
|
||||||
auto onMessage = [this](WebsocketsClient &client, WebsocketsMessage message) {
|
|
||||||
if (message.isPing())
|
|
||||||
client.pong();
|
|
||||||
else if (message.isBinary())
|
|
||||||
{
|
{
|
||||||
const char *data = message.c_str();
|
if (message.isPing())
|
||||||
const size_t length = message.length();
|
client.pong();
|
||||||
if (length < 1)
|
else if (message.isBinary())
|
||||||
{
|
{
|
||||||
client.close(CloseReason_UnsupportedData);
|
const char *data = message.c_str();
|
||||||
return;
|
const size_t length = message.length();
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t opCode = uint8_t(data[0]);
|
MessageCode msgCode = MessageCode((uint8_t)(data[0]));
|
||||||
switch (opCode)
|
this->handlMessageImpl(client, msgCode, data + 1, length - 1);
|
||||||
{
|
|
||||||
case START_SESSION:
|
|
||||||
this->sessionManager_.startMeasurements();
|
|
||||||
break;
|
|
||||||
case STOP_SESSION:
|
|
||||||
this->sessionManager_.stopMeasurements();
|
|
||||||
break;
|
|
||||||
case TARE:
|
|
||||||
this->sessionManager_.tare();
|
|
||||||
break;
|
|
||||||
case QUERY_USER_LIST:
|
|
||||||
this->sendUserList(client);
|
|
||||||
break;
|
|
||||||
case QUERY_SESSION_LIST:
|
|
||||||
{
|
|
||||||
StaticJsonDocument<USER_STRING_ID_MAX_LEN + 16> doc;
|
|
||||||
deserializeMsgPack(doc, data, length);
|
|
||||||
String userId = doc.as<String>();
|
|
||||||
if (userId.length() > 0)
|
|
||||||
this->sendSessionList(client, userId);
|
|
||||||
}
|
}
|
||||||
break;
|
else
|
||||||
default:
|
|
||||||
client.close(CloseReason_UnsupportedData);
|
client.close(CloseReason_UnsupportedData);
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
|
if (server_.poll())
|
||||||
|
{
|
||||||
|
Serial.println("new websocket connection");
|
||||||
|
clients_[nextFreeClient_] = server_.accept();
|
||||||
|
clients_[nextFreeClient_].onMessage(onMessage);
|
||||||
|
this->onClientConnectImpl(clients_[nextFreeClient_]);
|
||||||
|
nextFreeClient_ = (nextFreeClient_ + 1) % MAX_WEBSOCKET_CONNECTIONS;
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
if (server_.poll())
|
for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i)
|
||||||
{
|
clients_[i].poll();
|
||||||
clients_[nextFreeClient_] = server_.accept();
|
|
||||||
clients_[nextFreeClient_].onMessage(onMessage);
|
this->iterationImpl<>();
|
||||||
Serial.println("new websocket connection");
|
|
||||||
sendMessageOnConnection(clients_[nextFreeClient_]);
|
|
||||||
numSentMeasurements_[nextFreeClient_] = sessionManager_.session().numMeasurements();
|
|
||||||
nextFreeClient_ = (nextFreeClient_ + 1) % MAX_CONNECTIONS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int i = 0; i < MAX_CONNECTIONS; ++i)
|
template <size_t bufferSize>
|
||||||
clients_[i].poll();
|
void sendToAll(MessageCode msgCode, const JsonDocument &content)
|
||||||
|
|
||||||
reportSessionUpdate();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename SessionT>
|
|
||||||
void WebsocketServer<SessionT>::reportSessionUpdate()
|
|
||||||
{
|
|
||||||
if (!running_ && sessionManager_.isMeasuring())
|
|
||||||
{
|
{
|
||||||
sendSessionStartMessages();
|
char buffer[bufferSize];
|
||||||
for (int i = 0; i < MAX_CONNECTIONS; ++i)
|
buffer[0] = (char)(msgCode);
|
||||||
numSentMeasurements_[i] = 0;
|
size_t bytesWritten = serializeMsgPack(content, buffer + sizeof(msgCode), bufferSize - sizeof(msgCode));
|
||||||
|
for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i)
|
||||||
|
if (clients_[i].available())
|
||||||
|
clients_[i].sendBinary(buffer, bytesWritten);
|
||||||
}
|
}
|
||||||
else if (running_ && !sessionManager_.isMeasuring())
|
|
||||||
|
void sendToAll(MessageCode msgCode, const JsonDocument &content)
|
||||||
{
|
{
|
||||||
sendSessionStopMessages();
|
size_t expectedSize = measureMsgPack(content);
|
||||||
for (int i = 0; i < MAX_CONNECTIONS; ++i)
|
char *buffer = (char *)malloc(expectedSize + sizeof(msgCode));
|
||||||
numSentMeasurements_[i] = 0;
|
buffer[0] = (char)(msgCode);
|
||||||
}
|
size_t bytesWritten = serializeMsgPack(content, buffer + sizeof(msgCode), expectedSize);
|
||||||
sendNewDataMessages();
|
for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i)
|
||||||
}
|
if (clients_[i].available())
|
||||||
|
clients_[i].sendBinary(buffer, bytesWritten + sizeof(msgCode));
|
||||||
|
|
||||||
template <typename SessionT>
|
|
||||||
void WebsocketServer<SessionT>::sendUserList(websockets::WebsocketsClient &client)
|
|
||||||
{
|
|
||||||
const auto numUsers = userStorage_.numUsers();
|
|
||||||
constexpr size_t constantSlack = 64;
|
|
||||||
DynamicJsonDocument result(JSON_ARRAY_SIZE(numUsers) + numUsers * (USER_STRING_ID_MAX_LEN + 2) + constantSlack);
|
|
||||||
JsonArray arr = result.to<JsonArray>();
|
|
||||||
for (auto userIt = userStorage_.beginWithoutUnassigned(); userIt != userStorage_.end(); ++userIt)
|
|
||||||
arr.add(userIt->stringId());
|
|
||||||
|
|
||||||
char buffer[MAX_USERS * (USER_STRING_ID_MAX_LEN + 1) + constantSlack];
|
|
||||||
size_t bytesWritten = serializeMsgPack(result, buffer, sizeof(buffer));
|
|
||||||
client.sendBinary(buffer, bytesWritten);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename SessionT>
|
|
||||||
void WebsocketServer<SessionT>::sendSessionList(websockets::WebsocketsClient &client, const String &userId)
|
|
||||||
{
|
|
||||||
|
|
||||||
User *user = userStorage_.getUserInfo(userId);
|
|
||||||
if (user != nullptr)
|
|
||||||
{
|
|
||||||
DynamicJsonDocument result(JSON_ARRAY_SIZE(user->numSessions()) + user->numSessions() * (sizeof(SessionIdType) + 8));
|
|
||||||
JsonArray arr = result.to<JsonArray>();
|
|
||||||
for (SessionIdType *sIt = user->sessionBegin(); sIt != user->sessionEnd(); ++sIt)
|
|
||||||
arr.add(*sIt);
|
|
||||||
|
|
||||||
size_t bytesToWrite = measureMsgPack(result);
|
|
||||||
char *buffer = (char *)malloc(bytesToWrite);
|
|
||||||
size_t bytesWritten = serializeMsgPack(result, buffer, bytesToWrite);
|
|
||||||
assert(bytesWritten <= bytesToWrite);
|
|
||||||
client.sendBinary(buffer, bytesWritten);
|
|
||||||
free(buffer);
|
free(buffer);
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
void sendToAll(MessageCode msgCode)
|
||||||
{
|
{
|
||||||
DynamicJsonDocument result(JSON_ARRAY_SIZE(1) + 8);
|
for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i)
|
||||||
result.to<JsonArray>();
|
if (clients_[i].available())
|
||||||
char buffer[32];
|
clients_[i].sendBinary((const char*)&msgCode, sizeof(MessageCode));
|
||||||
size_t bytesWritten = serializeMsgPack(result, buffer, sizeof(buffer));
|
|
||||||
client.sendBinary(buffer, bytesWritten);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
template <typename SessionT>
|
websockets::WebsocketsClient &client(size_t i) { return clients_[i]; }
|
||||||
void WebsocketServer<SessionT>::sendSessionStartMessages()
|
|
||||||
{
|
|
||||||
SessionStartedMessage msg(sessionManager_.session().getStartTime());
|
|
||||||
for (auto &c : clients_)
|
|
||||||
if (c.available())
|
|
||||||
msg.send(c);
|
|
||||||
running_ = sessionManager_.isMeasuring();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename SessionT>
|
private:
|
||||||
void WebsocketServer<SessionT>::sendSessionStopMessages()
|
// -- Tuple calls
|
||||||
{
|
template <size_t managerIdx = std::tuple_size<ApiManagerTuple>::value - 1, typename std::enable_if<managerIdx != 0, bool>::type = true>
|
||||||
SessionStoppedMessage msg;
|
void iterationImpl()
|
||||||
for (auto &c : clients_)
|
|
||||||
if (c.available())
|
|
||||||
msg.send(c);
|
|
||||||
running_ = sessionManager_.isMeasuring();
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename SessionT>
|
|
||||||
void WebsocketServer<SessionT>::sendNewDataMessages()
|
|
||||||
{
|
|
||||||
using MeasurementT = typename SessionT::MeasurementType;
|
|
||||||
auto &session = sessionManager_.session();
|
|
||||||
|
|
||||||
for (int i = 0; i < MAX_CONNECTIONS; ++i)
|
|
||||||
{
|
{
|
||||||
auto &c = clients_[i];
|
std::get<managerIdx>(apiManagers_).iteration(*this);
|
||||||
if (c.available())
|
iterationImpl<managerIdx - 1>();
|
||||||
{
|
|
||||||
MeasurementT *dataToSend = session.getDataPointer() + numSentMeasurements_[i];
|
|
||||||
int32_t numMeasurementsToSend = int32_t(session.numMeasurements()) - int32_t(numSentMeasurements_[i]);
|
|
||||||
if (numMeasurementsToSend > 0)
|
|
||||||
{
|
|
||||||
SessionNewDataMessage<MeasurementT> msg(dataToSend, numMeasurementsToSend);
|
|
||||||
msg.send(c);
|
|
||||||
numSentMeasurements_[i] += msg.numMeasurements();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
template <size_t managerIdx, typename std::enable_if<managerIdx == 0, bool>::type = true>
|
||||||
|
void iterationImpl() {}
|
||||||
|
|
||||||
template <typename SessionT>
|
template <size_t managerIdx = std::tuple_size<ApiManagerTuple>::value - 1, typename std::enable_if<managerIdx != 0, bool>::type = true>
|
||||||
void WebsocketServer<SessionT>::sendMessageOnConnection(WebsocketsClient &client)
|
bool handlMessageImpl(websockets::WebsocketsClient &client, MessageCode code, const char *payload, size_t size)
|
||||||
|
{
|
||||||
|
bool handled = std::get<managerIdx>(apiManagers_).handleMessage(client, code, payload, size);
|
||||||
|
if (handled)
|
||||||
|
return true;
|
||||||
|
else
|
||||||
|
return handlMessageImpl<managerIdx - 1>(client, code, payload, size);
|
||||||
|
}
|
||||||
|
template <size_t managerIdx, typename std::enable_if<managerIdx == 0, bool>::type = true>
|
||||||
|
bool handlMessageImpl(websockets::WebsocketsClient &, MessageCode, const char *, size_t) { return false; }
|
||||||
|
|
||||||
|
template <size_t managerIdx = std::tuple_size<ApiManagerTuple>::value - 1, typename std::enable_if<managerIdx != 0, bool>::type = true>
|
||||||
|
void onClientConnectImpl(websockets::WebsocketsClient &client)
|
||||||
|
{
|
||||||
|
std::get<managerIdx>(apiManagers_).onClientConnect(client);
|
||||||
|
onClientConnectImpl<managerIdx - 1>(client);
|
||||||
|
}
|
||||||
|
template <size_t managerIdx, typename std::enable_if<managerIdx == 0, bool>::type = true>
|
||||||
|
void onClientConnectImpl(websockets::WebsocketsClient &client) {}
|
||||||
|
|
||||||
|
// -- Members
|
||||||
|
|
||||||
|
int port_;
|
||||||
|
int nextFreeClient_;
|
||||||
|
|
||||||
|
ApiManagerTuple apiManagers_;
|
||||||
|
|
||||||
|
websockets::WebsocketsServer server_;
|
||||||
|
websockets::WebsocketsClient clients_[MAX_WEBSOCKET_CONNECTIONS];
|
||||||
|
};
|
||||||
|
|
||||||
|
template<typename... ApiManagers>
|
||||||
|
inline WebsocketServer<std::tuple<ApiManagers...>> makeWebsocketServer(int port, ApiManagers... managers)
|
||||||
{
|
{
|
||||||
using MeasurementT = typename SessionT::MeasurementType;
|
auto tuple = std::make_tuple(managers...);
|
||||||
|
return WebsocketServer<decltype(tuple)>(port, tuple);
|
||||||
// Message format:
|
}
|
||||||
// - uint8_t messageType
|
|
||||||
// - uint8_t running
|
template <size_t bufferSize>
|
||||||
// - uint32_t sessionId
|
inline void sendToClient(websockets::WebsocketsClient &client, MessageCode msgCode, const JsonDocument &content)
|
||||||
// - MeasurementT [] measurements (if running)
|
{
|
||||||
|
char buffer[bufferSize];
|
||||||
auto &session = sessionManager_.session();
|
buffer[0] = (char)(msgCode);
|
||||||
const auto numMeasurements = session.numMeasurements();
|
size_t bytesWritten = serializeMsgPack(content, buffer + sizeof(msgCode), bufferSize - sizeof(msgCode));
|
||||||
const auto sessionId = session.getStartTime();
|
client.sendBinary(buffer, bytesWritten + 1);
|
||||||
|
}
|
||||||
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);
|
inline void sendToClient(websockets::WebsocketsClient &client, MessageCode msgCode, const JsonDocument &content)
|
||||||
|
{
|
||||||
char *writeHead = msg;
|
size_t expectedSize = measureMsgPack(content);
|
||||||
|
char *buffer = (char *)malloc(expectedSize + sizeof(msgCode));
|
||||||
*writeHead = INITIAL_INFO;
|
buffer[0] = static_cast<char>(msgCode);
|
||||||
writeHead += sizeof(uint8_t);
|
size_t bytesWritten = serializeMsgPack(content, buffer + sizeof(msgCode), expectedSize);
|
||||||
|
client.sendBinary(buffer, bytesWritten + sizeof(msgCode));
|
||||||
*writeHead = sessionManager_.isMeasuring();
|
free(buffer);
|
||||||
writeHead += sizeof(uint8_t);
|
}
|
||||||
|
|
||||||
*((uint32_t *)writeHead) = sessionManager_.isMeasuring() ? sessionId : 0;
|
inline void sendErrorToClient(websockets::WebsocketsClient &client, const char *msg)
|
||||||
writeHead += sizeof(uint32_t);
|
{
|
||||||
|
DynamicJsonDocument doc(strlen(msg) + 64);
|
||||||
assert(writeHead - msg == msgSize - sizeof(MeasurementT) * numMeasurements);
|
doc["msg"] = msg;
|
||||||
|
sendToClient(client, MessageCode::ERROR, doc);
|
||||||
memcpy(writeHead, session.getDataPointer(), sizeof(MeasurementT) * numMeasurements);
|
|
||||||
client.sendBinary(msg, msgSize);
|
|
||||||
|
|
||||||
free(msg);
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,436 @@
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include "Dtypes.h"
|
||||||
|
#include "UserDB.h"
|
||||||
|
#include "MessageCodes.h"
|
||||||
|
|
||||||
|
#include <ArduinoWebsockets.h>
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class SessionManager;
|
||||||
|
|
||||||
|
static constexpr int NUM_DATA_CHUNK_SIZE = 1;
|
||||||
|
|
||||||
|
template <typename ApiManagerTuple>
|
||||||
|
class WebsocketInterface
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WebsocketInterface(int port) : port_(port), nextFreeClient_(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin()
|
||||||
|
{
|
||||||
|
server_.listen(port_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void iteration();
|
||||||
|
|
||||||
|
template <size_t bufferSize>
|
||||||
|
void sendToAll(MessageCode msgCode, const JsonDocument &content)
|
||||||
|
{
|
||||||
|
char buffer[bufferSize];
|
||||||
|
buffer[0] = msgCode;
|
||||||
|
size_t bytesWritten = serializeMsgPack(content, buffer + sizeof(msgCode), bufferSize - sizeof(msgCode));
|
||||||
|
for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i)
|
||||||
|
if (clients_[i].available())
|
||||||
|
clients_[i].sendBinary(buffer, bytesWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendToAll(MessageCode msgCode, const JsonDocument &content)
|
||||||
|
{
|
||||||
|
size_t expectedSize = measureMsgPack(content);
|
||||||
|
char *buffer = (char *)malloc(expectedSize + sizeof(msgCode));
|
||||||
|
buffer[0] = msgCode;
|
||||||
|
size_t bytesWritten = serializeMsgPack(content, buffer + sizeof(msgCode), expectedSize);
|
||||||
|
for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i)
|
||||||
|
if (clients_[i].available())
|
||||||
|
clients_[i].sendBinary(buffer, bytesWritten + sizeof(msgCode));
|
||||||
|
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
void sendToAll(MessageCode msgCode)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i)
|
||||||
|
if (clients_[i].available())
|
||||||
|
clients_[i].sendBinary(&msgCode, sizeof(MessageCode));
|
||||||
|
}
|
||||||
|
|
||||||
|
websockets::WebsocketsClient &client(size_t i) { return clients_[i]; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
int port_;
|
||||||
|
int nextFreeClient_;
|
||||||
|
|
||||||
|
websockets::WebsocketsServer server_;
|
||||||
|
websockets::WebsocketsClient clients_[MAX_WEBSOCKET_CONNECTIONS];
|
||||||
|
};
|
||||||
|
|
||||||
|
template <size_t bufferSize>
|
||||||
|
inline void sendToClient(websockets::WebsocketsClient &client, MessageCode msgCode, const JsonDocument &content)
|
||||||
|
{
|
||||||
|
char buffer[bufferSize];
|
||||||
|
buffer[0] = msgCode;
|
||||||
|
size_t bytesWritten = serializeMsgPack(content, buffer + sizeof(msgCode), bufferSize - sizeof(msgCode));
|
||||||
|
client.sendBinary(buffer, bytesWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void sendToClient(websockets::WebsocketsClient &client, MessageCode msgCode, const JsonDocument &content)
|
||||||
|
{
|
||||||
|
size_t expectedSize = measureMsgPack(content);
|
||||||
|
char *buffer = (char *)malloc(expectedSize + sizeof(msgCode));
|
||||||
|
buffer[0] = static_cast<char>(msgCode);
|
||||||
|
size_t bytesWritten = serializeMsgPack(content, buffer + sizeof(msgCode), expectedSize);
|
||||||
|
client.sendBinary(buffer, bytesWritten + sizeof(msgCode));
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
inline void sendErrorToClient(websockets::WebsocketsClient &client, const char *msg)
|
||||||
|
{
|
||||||
|
DynamicJsonDocument doc(strlen(msg) + 64);
|
||||||
|
doc["msg"] = msg;
|
||||||
|
sendToClient(client, MessageCode::ERROR, doc);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SessionT>
|
||||||
|
class WebsocketServer
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
WebsocketServer(SessionManager<SessionT> &sessionManager, UserStorage &userStorage, int port)
|
||||||
|
: sessionManager_(sessionManager), userStorage_(userStorage), nextFreeClient_(0), port_(port),
|
||||||
|
running_(false)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void begin()
|
||||||
|
{
|
||||||
|
server_.listen(port_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void iteration();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void reportSessionUpdate();
|
||||||
|
|
||||||
|
void sendMessageOnConnection(websockets::WebsocketsClient &client);
|
||||||
|
void sendSessionStartMessages();
|
||||||
|
void sendSessionStopMessages();
|
||||||
|
void sendNewDataMessages();
|
||||||
|
|
||||||
|
void sendUserList(websockets::WebsocketsClient &client);
|
||||||
|
void sendSessionList(websockets::WebsocketsClient &client, const String &userId);
|
||||||
|
|
||||||
|
SessionManager<SessionT> &sessionManager_;
|
||||||
|
UserStorage &userStorage_;
|
||||||
|
|
||||||
|
int nextFreeClient_;
|
||||||
|
int port_;
|
||||||
|
|
||||||
|
size_t sentMessageCount_;
|
||||||
|
websockets::WebsocketsServer server_;
|
||||||
|
websockets::WebsocketsClient clients_[MAX_WEBSOCKET_CONNECTIONS];
|
||||||
|
|
||||||
|
// previous session state
|
||||||
|
size_t numSentMeasurements_[MAX_WEBSOCKET_CONNECTIONS];
|
||||||
|
bool running_;
|
||||||
|
};
|
||||||
|
|
||||||
|
using websockets::WebsocketsClient;
|
||||||
|
|
||||||
|
// ------------------------------------- Message types & classes ---------------------------
|
||||||
|
|
||||||
|
enum MessageType
|
||||||
|
{
|
||||||
|
// from swim tracker device to frontend
|
||||||
|
INITIAL_INFO = 1,
|
||||||
|
SESSION_STARTED = 2,
|
||||||
|
SESSION_STOPPED = 3,
|
||||||
|
SESSION_NEW_DATA = 4,
|
||||||
|
ANSWER_USER_LIST = 5,
|
||||||
|
ANSWER_SESSION_LIST = 6,
|
||||||
|
|
||||||
|
// from frontend to device
|
||||||
|
START_SESSION = 7,
|
||||||
|
STOP_SESSION = 8,
|
||||||
|
TARE = 9,
|
||||||
|
QUERY_USER_LIST = 10,
|
||||||
|
QUERY_SESSION_LIST = 11,
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
class SessionStartedMessage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SessionStartedMessage(uint32_t id) : messageType_(SESSION_STARTED), sessionId_(id) {}
|
||||||
|
void send(WebsocketsClient &c) const
|
||||||
|
{
|
||||||
|
c.sendBinary((const char *)(this), sizeof(*this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t messageType_;
|
||||||
|
uint32_t sessionId_;
|
||||||
|
};
|
||||||
|
|
||||||
|
class SessionStoppedMessage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SessionStoppedMessage() : messageType_(SESSION_STOPPED) {}
|
||||||
|
void send(WebsocketsClient &c) const
|
||||||
|
{
|
||||||
|
c.sendBinary((const char *)(this), sizeof(*this));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint8_t messageType_;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename MeasurementT>
|
||||||
|
class SessionNewDataMessage
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
// typically a message contains NUM_DATA_CHUNK_SIZE measurements
|
||||||
|
// if some measurements are skipped, because loop() takes too long
|
||||||
|
// there might actually be more measurements, to be safe there is an
|
||||||
|
// additional factor here
|
||||||
|
static constexpr size_t MAX_MEASUREMENTS = 4 * NUM_DATA_CHUNK_SIZE;
|
||||||
|
|
||||||
|
SessionNewDataMessage(MeasurementT *ptr, size_t numMeasurements)
|
||||||
|
: messageType_(SESSION_NEW_DATA), numMeasurements_(min(numMeasurements, MAX_MEASUREMENTS))
|
||||||
|
{
|
||||||
|
memcpy(measurements_, ptr, sizeof(MeasurementT) * numMeasurements_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void send(WebsocketsClient &c) const
|
||||||
|
{
|
||||||
|
c.sendBinary((const char *)(this), numBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t numMeasurements() const
|
||||||
|
{
|
||||||
|
return numMeasurements_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t numBytes() const { return sizeof(uint8_t) + numMeasurements() * sizeof(MeasurementT); }
|
||||||
|
|
||||||
|
// data to be sent
|
||||||
|
uint8_t messageType_;
|
||||||
|
MeasurementT measurements_[MAX_MEASUREMENTS];
|
||||||
|
|
||||||
|
// book-keeping
|
||||||
|
size_t numMeasurements_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
// ------------------------------------- WebsocketServer members ---------------------------
|
||||||
|
|
||||||
|
template <typename SessionT>
|
||||||
|
void WebsocketServer<SessionT>::iteration()
|
||||||
|
{
|
||||||
|
using namespace websockets;
|
||||||
|
|
||||||
|
auto onMessage = [this](WebsocketsClient &client, WebsocketsMessage message)
|
||||||
|
{
|
||||||
|
if (message.isPing())
|
||||||
|
client.pong();
|
||||||
|
else if (message.isBinary())
|
||||||
|
{
|
||||||
|
const char *data = message.c_str();
|
||||||
|
const size_t length = message.length();
|
||||||
|
if (length < 1)
|
||||||
|
{
|
||||||
|
client.close(CloseReason_UnsupportedData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t opCode = uint8_t(data[0]);
|
||||||
|
switch (opCode)
|
||||||
|
{
|
||||||
|
case START_SESSION:
|
||||||
|
this->sessionManager_.startMeasurements();
|
||||||
|
break;
|
||||||
|
case STOP_SESSION:
|
||||||
|
this->sessionManager_.stopMeasurements();
|
||||||
|
break;
|
||||||
|
case TARE:
|
||||||
|
this->sessionManager_.tare();
|
||||||
|
break;
|
||||||
|
case QUERY_USER_LIST:
|
||||||
|
this->sendUserList(client);
|
||||||
|
break;
|
||||||
|
case QUERY_SESSION_LIST:
|
||||||
|
{
|
||||||
|
StaticJsonDocument<USER_STRING_ID_MAX_LEN + 16> doc;
|
||||||
|
deserializeMsgPack(doc, data, length);
|
||||||
|
String userId = doc.as<String>();
|
||||||
|
if (userId.length() > 0)
|
||||||
|
this->sendSessionList(client, userId);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
client.close(CloseReason_UnsupportedData);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (server_.poll())
|
||||||
|
{
|
||||||
|
clients_[nextFreeClient_] = server_.accept();
|
||||||
|
clients_[nextFreeClient_].onMessage(onMessage);
|
||||||
|
Serial.println("new websocket connection");
|
||||||
|
sendMessageOnConnection(clients_[nextFreeClient_]);
|
||||||
|
numSentMeasurements_[nextFreeClient_] = sessionManager_.session().numMeasurements();
|
||||||
|
nextFreeClient_ = (nextFreeClient_ + 1) % MAX_WEBSOCKET_CONNECTIONS;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i)
|
||||||
|
clients_[i].poll();
|
||||||
|
|
||||||
|
reportSessionUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SessionT>
|
||||||
|
void WebsocketServer<SessionT>::reportSessionUpdate()
|
||||||
|
{
|
||||||
|
if (!running_ && sessionManager_.isMeasuring())
|
||||||
|
{
|
||||||
|
sendSessionStartMessages();
|
||||||
|
for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i)
|
||||||
|
numSentMeasurements_[i] = 0;
|
||||||
|
}
|
||||||
|
else if (running_ && !sessionManager_.isMeasuring())
|
||||||
|
{
|
||||||
|
sendSessionStopMessages();
|
||||||
|
for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i)
|
||||||
|
numSentMeasurements_[i] = 0;
|
||||||
|
}
|
||||||
|
sendNewDataMessages();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SessionT>
|
||||||
|
void WebsocketServer<SessionT>::sendUserList(websockets::WebsocketsClient &client)
|
||||||
|
{
|
||||||
|
const auto numUsers = userStorage_.numUsers();
|
||||||
|
constexpr size_t constantSlack = 64;
|
||||||
|
DynamicJsonDocument result(JSON_ARRAY_SIZE(numUsers) + numUsers * (USER_STRING_ID_MAX_LEN + 2) + constantSlack);
|
||||||
|
JsonArray arr = result.to<JsonArray>();
|
||||||
|
for (auto userIt = userStorage_.beginWithoutUnassigned(); userIt != userStorage_.end(); ++userIt)
|
||||||
|
arr.add(userIt->stringId());
|
||||||
|
|
||||||
|
char buffer[MAX_USERS * (USER_STRING_ID_MAX_LEN + 1) + constantSlack];
|
||||||
|
size_t bytesWritten = serializeMsgPack(result, buffer, sizeof(buffer));
|
||||||
|
client.sendBinary(buffer, bytesWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SessionT>
|
||||||
|
void WebsocketServer<SessionT>::sendSessionList(websockets::WebsocketsClient &client, const String &userId)
|
||||||
|
{
|
||||||
|
|
||||||
|
User *user = userStorage_.getUserInfo(userId);
|
||||||
|
if (user != nullptr)
|
||||||
|
{
|
||||||
|
DynamicJsonDocument result(JSON_ARRAY_SIZE(user->numSessions()) + user->numSessions() * (sizeof(SessionIdType) + 8));
|
||||||
|
JsonArray arr = result.to<JsonArray>();
|
||||||
|
for (SessionIdType *sIt = user->sessionBegin(); sIt != user->sessionEnd(); ++sIt)
|
||||||
|
arr.add(*sIt);
|
||||||
|
|
||||||
|
size_t bytesToWrite = measureMsgPack(result);
|
||||||
|
char *buffer = (char *)malloc(bytesToWrite);
|
||||||
|
size_t bytesWritten = serializeMsgPack(result, buffer, bytesToWrite);
|
||||||
|
assert(bytesWritten <= bytesToWrite);
|
||||||
|
client.sendBinary(buffer, bytesWritten);
|
||||||
|
free(buffer);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
DynamicJsonDocument result(JSON_ARRAY_SIZE(1) + 8);
|
||||||
|
result.to<JsonArray>();
|
||||||
|
char buffer[32];
|
||||||
|
size_t bytesWritten = serializeMsgPack(result, buffer, sizeof(buffer));
|
||||||
|
client.sendBinary(buffer, bytesWritten);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SessionT>
|
||||||
|
void WebsocketServer<SessionT>::sendSessionStartMessages()
|
||||||
|
{
|
||||||
|
SessionStartedMessage msg(sessionManager_.session().getStartTime());
|
||||||
|
for (auto &c : clients_)
|
||||||
|
if (c.available())
|
||||||
|
msg.send(c);
|
||||||
|
running_ = sessionManager_.isMeasuring();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SessionT>
|
||||||
|
void WebsocketServer<SessionT>::sendSessionStopMessages()
|
||||||
|
{
|
||||||
|
SessionStoppedMessage msg;
|
||||||
|
for (auto &c : clients_)
|
||||||
|
if (c.available())
|
||||||
|
msg.send(c);
|
||||||
|
running_ = sessionManager_.isMeasuring();
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SessionT>
|
||||||
|
void WebsocketServer<SessionT>::sendNewDataMessages()
|
||||||
|
{
|
||||||
|
using MeasurementT = typename SessionT::MeasurementType;
|
||||||
|
auto &session = sessionManager_.session();
|
||||||
|
|
||||||
|
for (int i = 0; i < MAX_WEBSOCKET_CONNECTIONS; ++i)
|
||||||
|
{
|
||||||
|
auto &c = clients_[i];
|
||||||
|
if (c.available())
|
||||||
|
{
|
||||||
|
MeasurementT *dataToSend = session.getDataPointer() + numSentMeasurements_[i];
|
||||||
|
int32_t numMeasurementsToSend = int32_t(session.numMeasurements()) - int32_t(numSentMeasurements_[i]);
|
||||||
|
if (numMeasurementsToSend > 0)
|
||||||
|
{
|
||||||
|
SessionNewDataMessage<MeasurementT> msg(dataToSend, numMeasurementsToSend);
|
||||||
|
msg.send(c);
|
||||||
|
numSentMeasurements_[i] += msg.numMeasurements();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename SessionT>
|
||||||
|
void WebsocketServer<SessionT>::sendMessageOnConnection(WebsocketsClient &client)
|
||||||
|
{
|
||||||
|
using MeasurementT = typename SessionT::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 = 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);
|
||||||
|
}
|
|
@ -3,13 +3,23 @@
|
||||||
|
|
||||||
#include "WebsocketServer.h"
|
#include "WebsocketServer.h"
|
||||||
|
|
||||||
|
void WifiAPI::sendWifiState(websockets::WebsocketsClient &client)
|
||||||
|
{
|
||||||
|
StaticJsonDocument<128> data;
|
||||||
|
data["state"] = wifiManager_.stateStr();
|
||||||
|
sendToClient<64>(client, MessageCode::WIFI_STATE_RESPONSE, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiAPI::onClientConnect(websockets::WebsocketsClient &client)
|
||||||
|
{
|
||||||
|
sendWifiState(client);
|
||||||
|
}
|
||||||
|
|
||||||
bool WifiAPI::handleMessage(websockets::WebsocketsClient &client, MessageCode code, const char *payload, size_t size)
|
bool WifiAPI::handleMessage(websockets::WebsocketsClient &client, MessageCode code, const char *payload, size_t size)
|
||||||
{
|
{
|
||||||
if (code == MessageCode::WIFI_STATE_GET)
|
if (code == MessageCode::WIFI_STATE_GET)
|
||||||
{
|
{
|
||||||
StaticJsonDocument<128> data;
|
sendWifiState(client);
|
||||||
data["state"] = wifiManager_.stateStr();
|
|
||||||
sendToClient<64>(client, MessageCode::WIFI_STATE_GET, data);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
else if (code == MessageCode::WIFI_STATE_SET)
|
else if (code == MessageCode::WIFI_STATE_SET)
|
||||||
|
@ -42,5 +52,10 @@ bool WifiAPI::handleMessage(websockets::WebsocketsClient &client, MessageCode co
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else if (code == MessageCode::WIFI_TRIGGER_SCAN)
|
||||||
|
{
|
||||||
|
WiFi.scanNetworks(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,20 +15,73 @@ public:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void onClientConnect(websockets::WebsocketsClient &client) {}
|
void onClientConnect(websockets::WebsocketsClient &client);
|
||||||
bool handleMessage(websockets::WebsocketsClient &client, MessageCode code, const char *payload, size_t size);
|
bool handleMessage(websockets::WebsocketsClient &client, MessageCode code, const char *payload, size_t size);
|
||||||
|
|
||||||
template <typename TServer>
|
template <typename TServer>
|
||||||
void iteration(TServer &server)
|
void iteration(TServer &server);
|
||||||
{
|
|
||||||
if (restartScheduled_)
|
|
||||||
{
|
|
||||||
Serial.print("Restart triggered by WifiAPI");
|
|
||||||
ESP.restart();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
void sendWifiState(websockets::WebsocketsClient &client);
|
||||||
|
|
||||||
|
template <typename TServer>
|
||||||
|
void reportScanResultIfAvailable(TServer &server);
|
||||||
|
|
||||||
WifiManager &wifiManager_;
|
WifiManager &wifiManager_;
|
||||||
bool restartScheduled_;
|
bool restartScheduled_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename TServer>
|
||||||
|
void WifiAPI::iteration(TServer &server)
|
||||||
|
{
|
||||||
|
if (restartScheduled_)
|
||||||
|
{
|
||||||
|
Serial.print("Restart triggered by WifiAPI");
|
||||||
|
ESP.restart();
|
||||||
|
}
|
||||||
|
reportScanResultIfAvailable(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename TServer>
|
||||||
|
void WifiAPI::reportScanResultIfAvailable(TServer &server)
|
||||||
|
{
|
||||||
|
auto numNetworks = WiFi.scanComplete();
|
||||||
|
|
||||||
|
if (numNetworks >= 0)
|
||||||
|
{
|
||||||
|
DynamicJsonDocument response(192 * numNetworks);
|
||||||
|
for (uint16_t i = 0; i < numNetworks; ++i)
|
||||||
|
{
|
||||||
|
JsonObject wifiObj = response.createNestedObject();
|
||||||
|
wifiObj["ssid"] = WiFi.SSID(i);
|
||||||
|
wifiObj["rssi"] = WiFi.RSSI(i);
|
||||||
|
wifiObj["channel"] = WiFi.channel(i);
|
||||||
|
|
||||||
|
switch (WiFi.encryptionType(i))
|
||||||
|
{
|
||||||
|
case WIFI_AUTH_OPEN:
|
||||||
|
wifiObj["sec"] = "open";
|
||||||
|
break;
|
||||||
|
case WIFI_AUTH_WEP:
|
||||||
|
wifiObj["sec"] = "WEP";
|
||||||
|
break;
|
||||||
|
case WIFI_AUTH_WPA_PSK:
|
||||||
|
wifiObj["sec"] = "WPA_PSK";
|
||||||
|
break;
|
||||||
|
case WIFI_AUTH_WPA2_PSK:
|
||||||
|
wifiObj["sec"] = "WPA2_PSK";
|
||||||
|
break;
|
||||||
|
case WIFI_AUTH_WPA_WPA2_PSK:
|
||||||
|
wifiObj["sec"] = "WPA_WPA2_PSK";
|
||||||
|
break;
|
||||||
|
case WIFI_AUTH_WPA2_ENTERPRISE:
|
||||||
|
wifiObj["sec"] = "WPA2_ENTP";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
wifiObj["sec"] = "?";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
server.sendToAll(MessageCode::WIFI_SCAN_RESPONSE, response);
|
||||||
|
WiFi.scanDelete();
|
||||||
|
}
|
||||||
|
}
|
|
@ -20,19 +20,28 @@
|
||||||
#include "SimpleMeasurementSession.h"
|
#include "SimpleMeasurementSession.h"
|
||||||
#include "EspHttp.h"
|
#include "EspHttp.h"
|
||||||
#include "WebDAV.h"
|
#include "WebDAV.h"
|
||||||
#include "WebsocketServer.h"
|
|
||||||
#include "UserDB.h"
|
#include "UserDB.h"
|
||||||
|
|
||||||
|
// Api
|
||||||
|
#include "WebsocketServer.h"
|
||||||
|
#include "SessionAPI.h"
|
||||||
|
#include "WifiAPI.h"
|
||||||
|
|
||||||
using Session_T = SimpleMeasurementSession<MeasurementT, CONFIG_SESSION_MAX_SIZE>;
|
using Session_T = SimpleMeasurementSession<MeasurementT, CONFIG_SESSION_MAX_SIZE>;
|
||||||
SessionManager<Session_T> sessionManager;
|
SessionManager<Session_T> sessionManager;
|
||||||
|
|
||||||
UserStorage userStorage;
|
UserStorage userStorage;
|
||||||
|
|
||||||
EspHttp espHttpServer;
|
EspHttp espHttpServer;
|
||||||
WebsocketServer<Session_T> webSocketServer(sessionManager, userStorage, 81);
|
|
||||||
|
|
||||||
WifiManager wifiManager;
|
WifiManager wifiManager;
|
||||||
|
|
||||||
|
|
||||||
|
auto apiTuple = std::make_tuple(SessionAPI<Session_T>(sessionManager), WifiAPI(wifiManager));
|
||||||
|
WebsocketServer<decltype(apiTuple)> websocketServer(81, apiTuple);
|
||||||
|
|
||||||
|
//WebsocketServer<Session_T> webSocketServer(sessionManager, userStorage, 81);
|
||||||
|
|
||||||
extern const uint8_t certificate_pem[] asm("_binary_certificate_pem_start");
|
extern const uint8_t certificate_pem[] asm("_binary_certificate_pem_start");
|
||||||
|
|
||||||
bool firmwareUpdate()
|
bool firmwareUpdate()
|
||||||
|
@ -86,32 +95,38 @@ void sessionManagerSetup()
|
||||||
template <typename SessionT>
|
template <typename SessionT>
|
||||||
void httpSetup(SessionManager<SessionT> *sessionManager, WifiManager *wifiManager)
|
void httpSetup(SessionManager<SessionT> *sessionManager, WifiManager *wifiManager)
|
||||||
{
|
{
|
||||||
auto cbStartSession = [sessionManager](httpd_req_t *req) {
|
auto cbStartSession = [sessionManager](httpd_req_t *req)
|
||||||
|
{
|
||||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
httpd_resp_send(req, "Session started", -1);
|
httpd_resp_send(req, "Session started", -1);
|
||||||
sessionManager->startMeasurements();
|
sessionManager->startMeasurements();
|
||||||
Serial.println("Started session");
|
Serial.println("Started session");
|
||||||
};
|
};
|
||||||
auto cbStopSession = [sessionManager](httpd_req_t *req) {
|
auto cbStopSession = [sessionManager](httpd_req_t *req)
|
||||||
|
{
|
||||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
httpd_resp_send(req, "Session stopped", -1);
|
httpd_resp_send(req, "Session stopped", -1);
|
||||||
sessionManager->stopMeasurements();
|
sessionManager->stopMeasurements();
|
||||||
Serial.println("Stopped session");
|
Serial.println("Stopped session");
|
||||||
};
|
};
|
||||||
auto cbRestart = [](httpd_req_t *req) {
|
auto cbRestart = [](httpd_req_t *req)
|
||||||
|
{
|
||||||
Serial.println("Restarted requested");
|
Serial.println("Restarted requested");
|
||||||
ESP.restart();
|
ESP.restart();
|
||||||
};
|
};
|
||||||
auto cbTare = [sessionManager](httpd_req_t *req) {
|
auto cbTare = [sessionManager](httpd_req_t *req)
|
||||||
|
{
|
||||||
Serial.println("Tare");
|
Serial.println("Tare");
|
||||||
sessionManager->tare();
|
sessionManager->tare();
|
||||||
};
|
};
|
||||||
auto cbFirmwareUpdate = [](httpd_req_t *req) {
|
auto cbFirmwareUpdate = [](httpd_req_t *req)
|
||||||
|
{
|
||||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
httpd_resp_send(req, "OK", -1);
|
httpd_resp_send(req, "OK", -1);
|
||||||
firmwareUpdate();
|
firmwareUpdate();
|
||||||
};
|
};
|
||||||
auto cbStatus = [sessionManager](httpd_req_t *req) {
|
auto cbStatus = [sessionManager](httpd_req_t *req)
|
||||||
|
{
|
||||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
httpd_resp_set_hdr(req, "Content-Type", "application/json");
|
httpd_resp_set_hdr(req, "Content-Type", "application/json");
|
||||||
|
|
||||||
|
@ -167,7 +182,8 @@ void httpSetup(SessionManager<SessionT> *sessionManager, WifiManager *wifiManage
|
||||||
auto bytesWritten = serializeJson(json, jsonText);
|
auto bytesWritten = serializeJson(json, jsonText);
|
||||||
httpd_resp_send(req, jsonText, bytesWritten);
|
httpd_resp_send(req, jsonText, bytesWritten);
|
||||||
};
|
};
|
||||||
auto cbGetData = [sessionManager](httpd_req_t *req) {
|
auto cbGetData = [sessionManager](httpd_req_t *req)
|
||||||
|
{
|
||||||
auto sessionId = sessionManager->session().getStartTime();
|
auto sessionId = sessionManager->session().getStartTime();
|
||||||
uint32_t startIdx = getUrlQueryParameter(req, "startIdx", 0);
|
uint32_t startIdx = getUrlQueryParameter(req, "startIdx", 0);
|
||||||
//Serial.printf("Data request, start index: %d\n", startIdx);
|
//Serial.printf("Data request, start index: %d\n", startIdx);
|
||||||
|
@ -196,7 +212,8 @@ void httpSetup(SessionManager<SessionT> *sessionManager, WifiManager *wifiManage
|
||||||
httpd_resp_send(req, buf, totalSize);
|
httpd_resp_send(req, buf, totalSize);
|
||||||
free(buf);
|
free(buf);
|
||||||
};
|
};
|
||||||
auto cbWifiGet = [wifiManager](httpd_req_t *req) {
|
auto cbWifiGet = [wifiManager](httpd_req_t *req)
|
||||||
|
{
|
||||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
StaticJsonDocument<128> json;
|
StaticJsonDocument<128> json;
|
||||||
json["state"] = wifiManager->stateStr();
|
json["state"] = wifiManager->stateStr();
|
||||||
|
@ -204,7 +221,8 @@ void httpSetup(SessionManager<SessionT> *sessionManager, WifiManager *wifiManage
|
||||||
auto bytesWritten = serializeJson(json, jsonText);
|
auto bytesWritten = serializeJson(json, jsonText);
|
||||||
httpd_resp_send(req, jsonText, bytesWritten);
|
httpd_resp_send(req, jsonText, bytesWritten);
|
||||||
};
|
};
|
||||||
auto cbWifiPost = [wifiManager](httpd_req_t *req) {
|
auto cbWifiPost = [wifiManager](httpd_req_t *req)
|
||||||
|
{
|
||||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
StaticJsonDocument<1024> json;
|
StaticJsonDocument<1024> json;
|
||||||
char content[512];
|
char content[512];
|
||||||
|
@ -248,7 +266,8 @@ void httpSetup(SessionManager<SessionT> *sessionManager, WifiManager *wifiManage
|
||||||
httpd_resp_set_status(req, "400 Bad Request");
|
httpd_resp_set_status(req, "400 Bad Request");
|
||||||
httpd_resp_send(req, "Invalid keys in JSON", -1);
|
httpd_resp_send(req, "Invalid keys in JSON", -1);
|
||||||
};
|
};
|
||||||
auto cbSettingsGet = [](httpd_req_t *req) {
|
auto cbSettingsGet = [](httpd_req_t *req)
|
||||||
|
{
|
||||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
httpd_resp_set_hdr(req, "Content-Type", "application/json");
|
httpd_resp_set_hdr(req, "Content-Type", "application/json");
|
||||||
|
|
||||||
|
@ -269,7 +288,8 @@ void httpSetup(SessionManager<SessionT> *sessionManager, WifiManager *wifiManage
|
||||||
auto bytesWritten = serializeJson(json, jsonText);
|
auto bytesWritten = serializeJson(json, jsonText);
|
||||||
httpd_resp_send(req, jsonText, bytesWritten);
|
httpd_resp_send(req, jsonText, bytesWritten);
|
||||||
};
|
};
|
||||||
auto cbSettingsPost = [](httpd_req_t *req) {
|
auto cbSettingsPost = [](httpd_req_t *req)
|
||||||
|
{
|
||||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
StaticJsonDocument<1024> json;
|
StaticJsonDocument<1024> json;
|
||||||
char content[512];
|
char content[512];
|
||||||
|
@ -313,7 +333,8 @@ void httpSetup(SessionManager<SessionT> *sessionManager, WifiManager *wifiManage
|
||||||
sessionManagerSetup();
|
sessionManagerSetup();
|
||||||
httpd_resp_send(req, "OK", -1);
|
httpd_resp_send(req, "OK", -1);
|
||||||
};
|
};
|
||||||
auto cbSettingsDelete = [](httpd_req_t *req) {
|
auto cbSettingsDelete = [](httpd_req_t *req)
|
||||||
|
{
|
||||||
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*");
|
||||||
Preferences prefs;
|
Preferences prefs;
|
||||||
prefs.begin("st_prefs");
|
prefs.begin("st_prefs");
|
||||||
|
@ -419,13 +440,12 @@ void setup()
|
||||||
|
|
||||||
// HTTP & Websocket server
|
// HTTP & Websocket server
|
||||||
httpSetup(&sessionManager, &wifiManager);
|
httpSetup(&sessionManager, &wifiManager);
|
||||||
if (!wifiManager.inProvisioningMode())
|
websocketServer.begin();
|
||||||
webSocketServer.begin();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop()
|
void loop()
|
||||||
{
|
{
|
||||||
sessionManager.iteration();
|
sessionManager.iteration();
|
||||||
webSocketServer.iteration();
|
wifiManager.iteration();
|
||||||
wifiManager.wifiWatchdog();
|
websocketServer.iteration();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,36 +1,158 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
import websockets
|
import websockets
|
||||||
import struct
|
import struct
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from pprint import pprint
|
||||||
INITIAL_INFO = 1
|
import datetime
|
||||||
SESSION_STARTED = 2
|
import msgpack
|
||||||
SESSION_STOPPED = 3
|
import aiomonitor
|
||||||
SESSION_NEW_DATA = 4
|
|
||||||
|
|
||||||
|
|
||||||
async def hello():
|
class MsgManager:
|
||||||
uri = "ws://192.168.178.110:81"
|
def __init__(self):
|
||||||
|
self.msg_history = []
|
||||||
|
|
||||||
|
def add_msg(self, msg):
|
||||||
|
pprint(msg)
|
||||||
|
self.msg_history.append(msg)
|
||||||
|
|
||||||
|
|
||||||
|
send_functions = []
|
||||||
|
|
||||||
|
|
||||||
|
class MsgCode:
|
||||||
|
ERROR = 1
|
||||||
|
|
||||||
|
# device to frontend
|
||||||
|
INITIAL_INFO = 2
|
||||||
|
SESSION_STARTED = 3
|
||||||
|
SESSION_STOPPED = 4
|
||||||
|
SESSION_NEW_DATA = 5
|
||||||
|
ANSWER_USER_LIST = 6
|
||||||
|
ANSWER_SESSION_LIST = 7
|
||||||
|
WIFI_STATE_RESPONSE = 8
|
||||||
|
WIFI_SCAN_RESPONSE = 9
|
||||||
|
|
||||||
|
# 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
|
||||||
|
WIFI_TRIGGER_SCAN = 135
|
||||||
|
|
||||||
|
|
||||||
|
async def send_message(websocket, msg_type, payload=None):
|
||||||
|
payload = struct.pack("<B", msg_type)
|
||||||
|
if payload is not None:
|
||||||
|
payload += msgpack.packb(payload, use_bin_type=True)
|
||||||
|
await websocket.send(payload)
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------- Session API --------------------------------
|
||||||
|
|
||||||
|
def parse_session_initial_info(payload):
|
||||||
|
session_id = struct.unpack("<I", payload[1:5])[0]
|
||||||
|
return {'type': "session_initial_info",
|
||||||
|
'running': struct.unpack("<B", payload[0:1])[0],
|
||||||
|
'session_id': session_id,
|
||||||
|
'start_time': datetime.fromtimestamp(session_id),
|
||||||
|
'data': np.frombuffer(payload[5:], dtype=np.uint16),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_session_started(payload):
|
||||||
|
session_id = struct.unpack("<I", payload)[0]
|
||||||
|
return {'type': "session_started",
|
||||||
|
'session_id': session_id,
|
||||||
|
'start_time': datetime.fromtimestamp(session_id),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_session_stopped(payload):
|
||||||
|
assert len(payload) == 0
|
||||||
|
return {'type': "session_stopped"}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_session_new_data(payload):
|
||||||
|
return {'type': "session_new_data", 'data': np.frombuffer(payload, dtype=np.uint16)}
|
||||||
|
|
||||||
|
|
||||||
|
async def send_session_start(websocket):
|
||||||
|
await send_message(websocket, MsgCode.START_SESSION)
|
||||||
|
|
||||||
|
|
||||||
|
async def send_session_stop(websocket):
|
||||||
|
await send_message(websocket, MsgCode.STOP_SESSION)
|
||||||
|
|
||||||
|
|
||||||
|
send_functions += [send_session_start, send_session_stop]
|
||||||
|
|
||||||
|
# ------------------------------- WiFi API --------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def parse_wifi_state(payload):
|
||||||
|
return {'type': "wifi_state", 'data': msgpack.unpackb(payload, raw=True)}
|
||||||
|
|
||||||
|
|
||||||
|
def parse_wifi_scan(payload):
|
||||||
|
return {'type': "wifi_scan_response", 'data': msgpack.unpackb(payload, raw=True)}
|
||||||
|
|
||||||
|
|
||||||
|
async def send_wifi_state_get(websocket):
|
||||||
|
await send_message(websocket, MsgCode.WIFI_STATE_GET)
|
||||||
|
|
||||||
|
|
||||||
|
async def send_wifi_trigger_scan(websocket):
|
||||||
|
await send_message(websocket, MsgCode.WIFI_TRIGGER_SCAN)
|
||||||
|
|
||||||
|
|
||||||
|
send_functions += [send_wifi_state_get, send_wifi_trigger_scan]
|
||||||
|
|
||||||
|
# -------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
|
def parse_message(data):
|
||||||
|
parse_funcs = {
|
||||||
|
MsgCode.INITIAL_INFO: parse_session_initial_info,
|
||||||
|
MsgCode.SESSION_STARTED: parse_session_started,
|
||||||
|
MsgCode.SESSION_STOPPED: parse_session_stopped,
|
||||||
|
MsgCode.SESSION_NEW_DATA: parse_session_new_data,
|
||||||
|
MsgCode.WIFI_STATE_RESPONSE: parse_wifi_state,
|
||||||
|
MsgCode.WIFI_SCAN_RESPONSE: parse_wifi_scan,
|
||||||
|
}
|
||||||
|
msg_type = struct.unpack("<B", data[:1])[0]
|
||||||
|
payload = data[1:]
|
||||||
|
try:
|
||||||
|
parsed = parse_funcs[msg_type](payload)
|
||||||
|
except KeyError:
|
||||||
|
raise KeyError(f"Unknown message type {msg_type}")
|
||||||
|
|
||||||
|
return parsed
|
||||||
|
|
||||||
|
|
||||||
|
msg_manager = MsgManager()
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
global msg_manager
|
||||||
|
uri = "ws://192.168.42.1:81"
|
||||||
async with websockets.connect(uri) as websocket:
|
async with websockets.connect(uri) as websocket:
|
||||||
|
for send_func in send_functions:
|
||||||
|
async def bound_func(*args, **kwargs):
|
||||||
|
await send_func(websocket, *args, **kwargs)
|
||||||
|
setattr(msg_manager, send_func.__name__, bound_func)
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
res = await websocket.recv()
|
res = await websocket.recv()
|
||||||
msg_type = struct.unpack("<B", res[:1])[0]
|
msg = parse_message(res)
|
||||||
payload = res[1:]
|
msg_manager.add_msg(msg)
|
||||||
if msg_type == INITIAL_INFO:
|
|
||||||
running = struct.unpack("<B", payload[0:1])[0]
|
|
||||||
session_id = struct.unpack("<I", payload[1:5])[0]
|
|
||||||
data = np.frombuffer(payload[5:], dtype=np.uint16)
|
|
||||||
print(f"Initial info: running {running} session_id {session_id} data", data)
|
|
||||||
elif msg_type == SESSION_STARTED:
|
|
||||||
id = struct.unpack("<I", payload)[0]
|
|
||||||
print(f"Session with id {id} started")
|
|
||||||
elif msg_type == SESSION_STOPPED:
|
|
||||||
assert len(payload) == 0
|
|
||||||
print("Session stopped")
|
|
||||||
elif msg_type == SESSION_NEW_DATA:
|
|
||||||
data = np.frombuffer(payload, dtype=np.uint16)
|
|
||||||
print("New data", data)
|
|
||||||
else:
|
|
||||||
print("Got unexpected packet of type", msg_type)
|
|
||||||
|
|
||||||
asyncio.get_event_loop().run_until_complete(hello())
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
with aiomonitor.start_monitor(loop=loop, locals={'m': msg_manager}):
|
||||||
|
loop.run_until_complete(main())
|
||||||
|
|
Loading…
Reference in New Issue