Compare commits
5 Commits
e631ea0a20
...
e84e388521
Author | SHA1 | Date | |
---|---|---|---|
|
e84e388521 | ||
|
b99245085e | ||
|
d13bf67443 | ||
|
67169240f9 | ||
|
a4cab0931f |
79
include/controller.h
Normal file
79
include/controller.h
Normal file
@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#include "server.h"
|
||||
#include "servo.h"
|
||||
#include "message.h"
|
||||
#include "storage.h"
|
||||
#include "fresh.h"
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
struct WifiConfiguration {
|
||||
|
||||
// The WiFi network to connect to
|
||||
const char* ssid;
|
||||
|
||||
// The WiFi password to connect to the above network
|
||||
const char* password;
|
||||
|
||||
// The name of the device on the network
|
||||
const char* networkName;
|
||||
|
||||
// The interval to reconnect to WiFi if the connection is broken
|
||||
uint32_t reconnectInterval;
|
||||
};
|
||||
|
||||
struct KeyConfiguration {
|
||||
|
||||
const uint8_t* remoteKey;
|
||||
|
||||
const uint8_t* localKey;
|
||||
};
|
||||
|
||||
class SesameController: public ServerConnectionCallbacks {
|
||||
|
||||
public:
|
||||
SesameController(uint16_t localWebServerPort, uint8_t remoteDeviceCount);
|
||||
|
||||
void configure(ServoConfiguration servoConfig, ServerConfiguration serverConfig, TimeConfiguration timeConfig, WifiConfiguration wifiConfig, KeyConfiguration keyConfig);
|
||||
|
||||
void loop(uint32_t millis);
|
||||
|
||||
private:
|
||||
|
||||
ServerConnection server;
|
||||
ServoController servo;
|
||||
AsyncWebServer localWebServer;
|
||||
TimeCheck timeCheck;
|
||||
Storage storage;
|
||||
|
||||
WifiConfiguration wifiConfig;
|
||||
KeyConfiguration keyConfig;
|
||||
|
||||
bool isReconnecting = false;
|
||||
|
||||
// The buffer to hold a received message while it is read
|
||||
uint8_t receivedMessageBuffer[AUTHENTICATED_MESSAGE_SIZE];
|
||||
|
||||
// The buffer to hold a response while it is sent
|
||||
uint8_t responseBuffer[AUTHENTICATED_MESSAGE_SIZE+1];
|
||||
SesameEvent* responseStatus;
|
||||
AuthenticatedMessage* responseMessage;
|
||||
uint16_t responseSize = 0;
|
||||
|
||||
void ensureWiFiConnection(uint32_t time);
|
||||
void ensureWebSocketConnection();
|
||||
|
||||
void handleLocalMessage(AsyncWebServerRequest *request);
|
||||
// Based on https://stackoverflow.com/a/23898449/266720
|
||||
bool convertHexMessageToBinary(const char* str);
|
||||
|
||||
void handleServerMessage(uint8_t* payload, size_t length);
|
||||
void sendServerError(SesameEvent event);
|
||||
|
||||
void processMessage(AuthenticatedMessage* message);
|
||||
SesameEvent verifyAndProcessReceivedMessage(AuthenticatedMessage* message);
|
||||
|
||||
uint16_t prepareResponseBuffer(SesameEvent event, uint8_t deviceId = 0);
|
||||
void sendPreparedLocalResponse(AsyncWebServerRequest *request);
|
||||
void sendPreparedServerResponse();
|
||||
};
|
@ -52,11 +52,7 @@ constexpr uint32_t wifiReconnectInterval = 10000;
|
||||
/* Local server */
|
||||
|
||||
// The port for the local server to directly receive messages over WiFi
|
||||
constexpr uint16_t localPort = 80;
|
||||
|
||||
// The url parameter to send the message to the local server
|
||||
constexpr char messageUrlParameter[] = "m";
|
||||
|
||||
constexpr uint16_t localWebServerPort = 80;
|
||||
|
||||
/* Server */
|
||||
|
||||
|
165
include/fresh.h
165
include/fresh.h
@ -1,117 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
#include "config.h"
|
||||
|
||||
/**
|
||||
* @brief The size of the message counter in bytes (uint32_t)
|
||||
*/
|
||||
#define MESSAGE_COUNTER_SIZE sizeof(uint32_t)
|
||||
struct TimeConfiguration {
|
||||
|
||||
/**
|
||||
* @brief Configure an NTP server to get the current time
|
||||
*
|
||||
* @param offsetToGMT The timezone offset in seconds
|
||||
* @param offsetDaylightSavings The daylight savings offset in seconds
|
||||
* @param serverUrl The url of the NTP server
|
||||
*/
|
||||
void configureNTP(int32_t offsetToGMT, int32_t offsetDaylightSavings, const char* serverUrl);
|
||||
/**
|
||||
* @brief The timezone offset in seconds
|
||||
*/
|
||||
int32_t offsetToGMT;
|
||||
|
||||
/**
|
||||
* @brief Print the current time to the serial output
|
||||
*
|
||||
* The time must be initialized by calling `configureNTP()` before use.
|
||||
*/
|
||||
void printLocalTime();
|
||||
/**
|
||||
* @brief The daylight savings offset in seconds
|
||||
*/
|
||||
int32_t offsetDaylightSavings;
|
||||
|
||||
/**
|
||||
* Gets the current epoch time
|
||||
*/
|
||||
uint32_t getEpochTime();
|
||||
/**
|
||||
* @brief The url of the NTP server
|
||||
*/
|
||||
const char* ntpServerUrl;
|
||||
|
||||
/**
|
||||
* @brief The allowed time discrepancy (in seconds)
|
||||
*
|
||||
* Specifies the allowed discrepancy between the time of a received message
|
||||
* and the device time (in seconds).
|
||||
*
|
||||
* A stricter (lower) value better prevents against replay attacks,
|
||||
* but may lead to issues when dealing with slow networks and other
|
||||
* routing delays.
|
||||
*
|
||||
* @param offset The offset in both directions (seconds)
|
||||
*/
|
||||
void setMessageTimeAllowedOffset(uint32_t offset);
|
||||
/**
|
||||
* @brief The allowed discrepancy between the time of a received message
|
||||
* and the device time (in seconds)
|
||||
*
|
||||
* A stricter (lower) value better prevents against replay attacks,
|
||||
* but may lead to issues when dealing with slow networks and other
|
||||
* routing delays.
|
||||
*/
|
||||
uint32_t allowedTimeOffset;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Check wether the time of a message is within the allowed bounds regarding freshness.
|
||||
*
|
||||
* The timestamp is used to ensure 'freshness' of the messages,
|
||||
* i.e. that they are not unreasonably delayed or captured and
|
||||
* later replayed by an attacker.
|
||||
*
|
||||
* @param messageTime The timestamp of the message (seconds since epoch)
|
||||
* @return true The time is within the acceptable offset of the local time
|
||||
* @return false The message time is invalid
|
||||
*/
|
||||
bool isMessageTimeAcceptable(uint32_t messageTime);
|
||||
class TimeCheck {
|
||||
|
||||
/**
|
||||
* @brief Initialize the use of the message counter API
|
||||
*
|
||||
* The message counter is stored in EEPROM, which must be initialized before use.
|
||||
*
|
||||
* @note The ESP32 does not have a true EEPROM,
|
||||
* which is emulated using a section of the flash memory.
|
||||
*/
|
||||
void prepareMessageCounterUsage();
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Get the expected count for the next message.
|
||||
*
|
||||
* The counter is stored in EEPROM to persist across restarts
|
||||
*
|
||||
* @return The next counter to use by the remote
|
||||
*/
|
||||
uint32_t getNextMessageCounter(uint8_t deviceId);
|
||||
/**
|
||||
* @brief Create a time checker instance
|
||||
*/
|
||||
TimeCheck();
|
||||
|
||||
/**
|
||||
* @brief Print info about the current message counter to the serial output
|
||||
*
|
||||
*/
|
||||
void printMessageCounters();
|
||||
/**
|
||||
* @brief Set the configuration
|
||||
*/
|
||||
void configure(TimeConfiguration configuration);
|
||||
|
||||
bool isDeviceIdValid(uint8_t deviceId);
|
||||
/**
|
||||
* @brief Configure the NTP server to get the current time
|
||||
*/
|
||||
void startNTP();
|
||||
|
||||
/**
|
||||
* @brief Check if a received counter is valid
|
||||
*
|
||||
* The counter is valid if it is larger than the previous counter
|
||||
* (larger or equal to the next expected counter).
|
||||
*
|
||||
* @param counter The counter to check
|
||||
* @return true The counter is valid
|
||||
* @return false The counter belongs to an old message
|
||||
*/
|
||||
bool isMessageCounterValid(uint32_t counter, uint8_t deviceId);
|
||||
/**
|
||||
* @brief Print the current time to the serial output
|
||||
*
|
||||
* The time must be initialized by calling `configureNTP()` before use.
|
||||
*/
|
||||
void printLocalTime();
|
||||
|
||||
/**
|
||||
* @brief Mark a counter of a message as used.
|
||||
*
|
||||
* The counter value is stored in EEPROM to persist across restarts.
|
||||
*
|
||||
* All messages with counters lower than the given one will become invalid.
|
||||
*
|
||||
* @param counter The counter used in the last message.
|
||||
*/
|
||||
void didUseMessageCounter(uint32_t counter, uint8_t deviceId);
|
||||
/**
|
||||
* Gets the current epoch time
|
||||
*/
|
||||
uint32_t getEpochTime();
|
||||
|
||||
/**
|
||||
* @brief Reset the message counter.
|
||||
*
|
||||
* @warning The counter should never be reset in production environments,
|
||||
* and only together with a new secret key. Otherwise old messages may be
|
||||
* used for replay attacks.
|
||||
*
|
||||
*/
|
||||
void resetMessageCounters();
|
||||
/**
|
||||
* @brief Check wether the time of a message is within the allowed bounds regarding freshness.
|
||||
*
|
||||
* The timestamp is used to ensure 'freshness' of the messages,
|
||||
* i.e. that they are not unreasonably delayed or captured and
|
||||
* later replayed by an attacker.
|
||||
*
|
||||
* @param messageTime The timestamp of the message (seconds since epoch)
|
||||
* @return true The time is within the acceptable offset of the local time
|
||||
* @return false The message time is invalid
|
||||
*/
|
||||
bool isMessageTimeAcceptable(uint32_t messageTime);
|
||||
|
||||
private:
|
||||
|
||||
TimeConfiguration config;
|
||||
};
|
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "stdint.h"
|
||||
#include <stdint.h>
|
||||
#include <stddef.h>
|
||||
|
||||
/**
|
||||
* @brief The size of a message authentication code
|
||||
@ -52,6 +53,8 @@ typedef struct {
|
||||
|
||||
} Message;
|
||||
|
||||
constexpr size_t messageCounterSize = sizeof(uint32_t);
|
||||
|
||||
/**
|
||||
* @brief An authenticated message by the mobile device to command unlocking.
|
||||
*
|
||||
@ -87,9 +90,9 @@ typedef struct {
|
||||
} AuthenticatedMessage;
|
||||
#pragma pack(pop)
|
||||
|
||||
#define MESSAGE_CONTENT_SIZE sizeof(Message)
|
||||
constexpr int MESSAGE_CONTENT_SIZE = sizeof(Message);
|
||||
|
||||
#define AUTHENTICATED_MESSAGE_SIZE sizeof(AuthenticatedMessage)
|
||||
constexpr int AUTHENTICATED_MESSAGE_SIZE = sizeof(AuthenticatedMessage);
|
||||
|
||||
/**
|
||||
* An event signaled from the device
|
||||
@ -97,19 +100,27 @@ typedef struct {
|
||||
enum class SesameEvent {
|
||||
TextReceived = 1,
|
||||
UnexpectedSocketEvent = 2,
|
||||
InvalidMessageData = 3,
|
||||
InvalidMessageSize = 3,
|
||||
MessageAuthenticationFailed = 4,
|
||||
MessageTimeMismatch = 5,
|
||||
MessageCounterInvalid = 6,
|
||||
MessageAccepted = 7,
|
||||
MessageDeviceInvalid = 8,
|
||||
InvalidUrlParameter = 9,
|
||||
InvalidResponseAuthentication = 10,
|
||||
DeviceSetupIncomplete = 11,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A callback for messages received over the socket
|
||||
*
|
||||
* The first parameter is the received message.
|
||||
* The second parameter is the response to the remote.
|
||||
* The return value is the type of event to respond with.
|
||||
* The first parameter is a pointer to the byte buffer.
|
||||
* The second parameter indicates the number of received bytes.
|
||||
*/
|
||||
typedef SesameEvent (*MessageCallback)(AuthenticatedMessage*, AuthenticatedMessage*);
|
||||
typedef void (*MessageCallback)(uint8_t* payload, size_t length);
|
||||
|
||||
/**
|
||||
* @brief A callback for socket errors
|
||||
*/
|
||||
typedef void (*ErrorCallback)(SesameEvent event);
|
||||
|
||||
|
108
include/server.h
108
include/server.h
@ -6,42 +6,81 @@
|
||||
#include <WiFiClientSecure.h>
|
||||
#include <WebSocketsClient.h>
|
||||
|
||||
struct ServerConfiguration {
|
||||
|
||||
/**
|
||||
* @brief The url of the remote server to connect to
|
||||
*/
|
||||
const char* url;
|
||||
|
||||
/**
|
||||
* @brief The server port
|
||||
*/
|
||||
int port;
|
||||
|
||||
/**
|
||||
* @brief The path on the server
|
||||
*/
|
||||
const char* path;
|
||||
|
||||
/**
|
||||
* @brief The authentication key for the server
|
||||
*/
|
||||
const char* key;
|
||||
|
||||
uint32_t reconnectTime;
|
||||
|
||||
};
|
||||
|
||||
class ServerConnectionCallbacks {
|
||||
|
||||
public:
|
||||
|
||||
virtual void sendServerError(SesameEvent event) = 0;
|
||||
virtual void handleServerMessage(uint8_t* payload, size_t length) = 0;
|
||||
};
|
||||
|
||||
class ServerConnection {
|
||||
|
||||
public:
|
||||
|
||||
ServerConnection(const char* url, int port, const char* path);
|
||||
ServerConnection();
|
||||
|
||||
void connect(const char* key, uint32_t reconnectTime = 5000);
|
||||
/**
|
||||
* @brief Set the configuration and the callback handler
|
||||
*
|
||||
* @param callback The handler to handle messages and errors
|
||||
*/
|
||||
void configure(ServerConfiguration configuration, ServerConnectionCallbacks* callbacks);
|
||||
|
||||
void connectSSL(const char* key, uint32_t reconnectTime = 5000);
|
||||
void connect();
|
||||
|
||||
void loop();
|
||||
|
||||
void onMessage(MessageCallback callback);
|
||||
/**
|
||||
* @brief Send a response message over the socket
|
||||
*
|
||||
* @param buffer The data buffer
|
||||
* @param length The number of bytes to send
|
||||
*/
|
||||
void sendResponse(uint8_t* buffer, uint16_t length);
|
||||
|
||||
bool isSocketConnected() {
|
||||
return socketIsConnected;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
ServerConfiguration configuration;
|
||||
|
||||
// Indicator that the socket is connected.
|
||||
bool socketIsConnected = false;
|
||||
|
||||
private:
|
||||
|
||||
const char* url;
|
||||
|
||||
int port;
|
||||
|
||||
const char* path;
|
||||
|
||||
const char* key = NULL;
|
||||
|
||||
MessageCallback messageCallback = NULL;
|
||||
ServerConnectionCallbacks* controller = NULL;
|
||||
|
||||
// WebSocket to connect to the control server
|
||||
WebSocketsClient webSocket;
|
||||
|
||||
void reconnectAfter(uint32_t reconnectTime);
|
||||
|
||||
void registerEventCallback();
|
||||
|
||||
/**
|
||||
* Callback for WebSocket events.
|
||||
*
|
||||
@ -51,35 +90,4 @@ private:
|
||||
* @param length The number of bytes received
|
||||
*/
|
||||
void webSocketEventHandler(WStype_t type, uint8_t * payload, size_t length);
|
||||
|
||||
/**
|
||||
* Process received binary data.
|
||||
*
|
||||
* Checks whether the received data is a valid and unused key,
|
||||
* and then signals that the motor should move.
|
||||
* Sends the event id to the server as a response to the request.
|
||||
*
|
||||
* If the key is valid, then `shouldStartOpening` is set to true.
|
||||
*
|
||||
* @param payload The pointer to the received data.
|
||||
* @param length The number of bytes received.
|
||||
*/
|
||||
void processReceivedBytes(uint8_t* payload, size_t length);
|
||||
|
||||
/**
|
||||
* Send a response event to the server and include the next key index.
|
||||
*
|
||||
* Sends the event type as three byte.
|
||||
* @param event The event type
|
||||
*/
|
||||
void sendFailureResponse(SesameEvent event);
|
||||
|
||||
/**
|
||||
* Send a response event to the server and include the next key index.
|
||||
*
|
||||
* Sends the event type as three byte.
|
||||
* @param event The event type
|
||||
*/
|
||||
void sendResponse(SesameEvent event, AuthenticatedMessage* message);
|
||||
|
||||
};
|
@ -3,6 +3,40 @@
|
||||
#include <stdint.h>
|
||||
#include <ESP32Servo.h> // To control the servo
|
||||
|
||||
struct ServoConfiguration {
|
||||
/**
|
||||
* @brief The timer to use for the servo control
|
||||
* number 0-3 indicating which timer to allocate in this library
|
||||
*/
|
||||
int pwmTimer;
|
||||
|
||||
/**
|
||||
* @brief The servo frequency (depending on the model used)
|
||||
*/
|
||||
int pwmFrequency;
|
||||
|
||||
/**
|
||||
* @brief The pin where the servo PWM line is connected
|
||||
*/
|
||||
int pin;
|
||||
|
||||
/**
|
||||
* @brief The duration (in ms) for which the button should remain pressed
|
||||
*/
|
||||
uint32_t openDuration;
|
||||
|
||||
/**
|
||||
* @brief The servo value (in µs) that specifies the 'pressed' state
|
||||
*/
|
||||
int pressedValue;
|
||||
|
||||
/**
|
||||
* @brief The servo value (in µs) that specifies the 'released' state
|
||||
*/
|
||||
int releasedValue;
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief A controller for the button control servo
|
||||
*
|
||||
@ -17,22 +51,16 @@ class ServoController {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Construct a new Servo Controller object
|
||||
*
|
||||
* @param timer The timer to use for the servo control
|
||||
* @param frequency The servo frequency (depending on the model used)
|
||||
* @param pin The pin where the servo PWM line is connected
|
||||
* @brief Construct a new servo controller
|
||||
*/
|
||||
ServoController(int timer, int frequency, int pin);
|
||||
ServoController();
|
||||
|
||||
/**
|
||||
* @brief Configure the button values
|
||||
* @brief Configure the servo
|
||||
*
|
||||
* @param openDuration The duration (in ms) for which the button should remain pressed
|
||||
* @param pressedValue The servo value (in µs) that specifies the 'pressed' state
|
||||
* @param releasedValue The servo value (in µs) that specifies the 'released' state
|
||||
* @param The configuration for the servo
|
||||
*/
|
||||
void configure(uint32_t openDuration, int pressedValue, int releasedValue);
|
||||
void configure(ServoConfiguration configuration);
|
||||
|
||||
/**
|
||||
* @brief Update the servo state periodically
|
||||
@ -43,7 +71,7 @@ public:
|
||||
* There is no required interval to call this function, but the accuracy of
|
||||
* the opening interval is dependent on the calling frequency.
|
||||
*/
|
||||
void loop();
|
||||
void loop(uint32_t millis);
|
||||
|
||||
/**
|
||||
* Push the door opener button down by moving the servo arm.
|
||||
@ -60,12 +88,6 @@ private:
|
||||
// Indicator that the door button is pushed
|
||||
bool buttonIsPressed = false;
|
||||
|
||||
int timer;
|
||||
|
||||
int frequency;
|
||||
|
||||
int pin;
|
||||
|
||||
uint32_t openDuration = 0;
|
||||
|
||||
int pressedValue = 0;
|
||||
|
83
include/storage.h
Normal file
83
include/storage.h
Normal file
@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
class Storage {
|
||||
|
||||
public:
|
||||
|
||||
Storage(uint8_t remoteDeviceCount) : remoteDeviceCount(remoteDeviceCount) { };
|
||||
|
||||
/**
|
||||
* @brief Initialize the use of the message counter API
|
||||
*
|
||||
* The message counter is stored in EEPROM, which must be initialized before use.
|
||||
*
|
||||
* @note The ESP32 does not have a true EEPROM,
|
||||
* which is emulated using a section of the flash memory.
|
||||
*/
|
||||
void configure();
|
||||
|
||||
/**
|
||||
* @brief Check if a device ID is allowed
|
||||
*
|
||||
* @param deviceId The ID to check
|
||||
* @return true The id is valid
|
||||
* @return false The id is invalid
|
||||
*/
|
||||
bool isDeviceIdValid(uint8_t deviceId);
|
||||
|
||||
/**
|
||||
* @brief Check if a received counter is valid
|
||||
*
|
||||
* The counter is valid if it is larger than the previous counter
|
||||
* (larger or equal to the next expected counter).
|
||||
*
|
||||
* @param counter The counter to check
|
||||
* @return true The counter is valid
|
||||
* @return false The counter belongs to an old message
|
||||
*/
|
||||
bool isMessageCounterValid(uint32_t counter, uint8_t deviceId);
|
||||
|
||||
/**
|
||||
* @brief Mark a counter of a message as used.
|
||||
*
|
||||
* The counter value is stored in EEPROM to persist across restarts.
|
||||
*
|
||||
* All messages with counters lower than the given one will become invalid.
|
||||
*
|
||||
* @param counter The counter used in the last message.
|
||||
*/
|
||||
void didUseMessageCounter(uint32_t counter, uint8_t deviceId);
|
||||
|
||||
/**
|
||||
* @brief Get the expected count for the next message.
|
||||
*
|
||||
* The counter is stored in EEPROM to persist across restarts
|
||||
*
|
||||
* @return The next counter to use by the remote
|
||||
*/
|
||||
uint32_t getNextMessageCounter(uint8_t deviceId);
|
||||
|
||||
/**
|
||||
* @brief Print info about the current message counter to the serial output
|
||||
*
|
||||
*/
|
||||
void printMessageCounters();
|
||||
|
||||
/**
|
||||
* @brief Reset the message counter.
|
||||
*
|
||||
* @warning The counter should never be reset in production environments,
|
||||
* and only together with a new secret key. Otherwise old messages may be
|
||||
* used for replay attacks.
|
||||
*
|
||||
*/
|
||||
void resetMessageCounters();
|
||||
|
||||
private:
|
||||
|
||||
uint8_t remoteDeviceCount;
|
||||
|
||||
void setMessageCounter(uint32_t counter, uint8_t deviceId);
|
||||
};
|
218
src/controller.cpp
Normal file
218
src/controller.cpp
Normal file
@ -0,0 +1,218 @@
|
||||
#include "controller.h"
|
||||
#include "crypto.h"
|
||||
#include "config.h"
|
||||
|
||||
#include <WiFi.h>
|
||||
|
||||
// The url parameter to send the message to the local server
|
||||
constexpr char messageUrlParameter[] = "m";
|
||||
|
||||
SesameController::SesameController(uint16_t localWebServerPort, uint8_t remoteDeviceCount) :
|
||||
storage(remoteDeviceCount), localWebServer(localWebServerPort) {
|
||||
|
||||
// Set up response buffer
|
||||
responseStatus = (SesameEvent*) responseBuffer;
|
||||
responseMessage = (AuthenticatedMessage*) (responseBuffer + 1);
|
||||
}
|
||||
|
||||
void SesameController::configure(ServoConfiguration servoConfig, ServerConfiguration serverConfig, TimeConfiguration timeConfig, WifiConfiguration wifiConfig, KeyConfiguration keyConfig) {
|
||||
this->wifiConfig = wifiConfig;
|
||||
this->keyConfig = keyConfig;
|
||||
|
||||
// Prepare EEPROM for reading and writing
|
||||
storage.configure();
|
||||
Serial.println("[INFO] Storage configured");
|
||||
|
||||
servo.configure(servoConfig);
|
||||
Serial.println("[INFO] Servo configured");
|
||||
|
||||
// Direct messages and errors over the websocket to the controller
|
||||
server.configure(serverConfig, this);
|
||||
Serial.println("[INFO] Server connection configured");
|
||||
|
||||
timeCheck.configure(timeConfig);
|
||||
|
||||
|
||||
// Direct messages from the local web server to the controller
|
||||
localWebServer.on("/message", HTTP_POST, [this] (AsyncWebServerRequest *request) {
|
||||
this->handleLocalMessage(request);
|
||||
this->sendPreparedLocalResponse(request);
|
||||
});
|
||||
|
||||
Serial.println("[INFO] Local web server configured");
|
||||
|
||||
//storage.resetMessageCounters();
|
||||
storage.printMessageCounters();
|
||||
}
|
||||
|
||||
void SesameController::loop(uint32_t millis) {
|
||||
server.loop();
|
||||
servo.loop(millis);
|
||||
|
||||
ensureWiFiConnection(millis);
|
||||
ensureWebSocketConnection();
|
||||
}
|
||||
|
||||
// MARK: Local
|
||||
|
||||
void
|
||||
SesameController::handleLocalMessage(AsyncWebServerRequest *request) {
|
||||
if (!request->hasParam(messageUrlParameter)) {
|
||||
Serial.println("Missing url parameter");
|
||||
prepareResponseBuffer(SesameEvent::InvalidUrlParameter);
|
||||
return;
|
||||
}
|
||||
String encoded = request->getParam(messageUrlParameter)->value();
|
||||
if (!convertHexMessageToBinary(encoded.c_str())) {
|
||||
Serial.println("Invalid hex encoding");
|
||||
prepareResponseBuffer(SesameEvent::InvalidMessageSize);
|
||||
return;
|
||||
}
|
||||
processMessage((AuthenticatedMessage*) receivedMessageBuffer);
|
||||
}
|
||||
|
||||
void SesameController::sendPreparedLocalResponse(AsyncWebServerRequest *request) {
|
||||
request->send_P(200, "application/octet-stream", responseBuffer, responseSize);
|
||||
Serial.printf("[INFO] Local response %u (%u bytes)\n", responseBuffer[0], responseSize);
|
||||
}
|
||||
|
||||
// MARK: Server
|
||||
|
||||
void SesameController::sendServerError(SesameEvent event) {
|
||||
prepareResponseBuffer(event);
|
||||
sendPreparedServerResponse();
|
||||
}
|
||||
|
||||
void SesameController::handleServerMessage(uint8_t* payload, size_t length) {
|
||||
if (length != AUTHENTICATED_MESSAGE_SIZE) {
|
||||
prepareResponseBuffer(SesameEvent::InvalidMessageSize);
|
||||
return;
|
||||
}
|
||||
|
||||
processMessage((AuthenticatedMessage*) payload);
|
||||
sendPreparedServerResponse();
|
||||
}
|
||||
|
||||
void SesameController::sendPreparedServerResponse() {
|
||||
server.sendResponse(responseBuffer, responseSize);
|
||||
Serial.printf("[INFO] Server response %u (%u bytes)\n", responseBuffer[0], responseSize);
|
||||
}
|
||||
|
||||
// MARK: Message handling
|
||||
|
||||
void SesameController::processMessage(AuthenticatedMessage* message) {
|
||||
SesameEvent event = verifyAndProcessReceivedMessage(message);
|
||||
prepareResponseBuffer(event, message->message.device);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a received message.
|
||||
*
|
||||
* Checks whether the received data is a valid,
|
||||
* and then signals that the motor should move.
|
||||
*
|
||||
* @param message The message received from the remote
|
||||
* @return The response to signal to the server.
|
||||
*/
|
||||
SesameEvent SesameController::verifyAndProcessReceivedMessage(AuthenticatedMessage* message) {
|
||||
if (!isAuthenticMessage(message, keyConfig.remoteKey, keySize)) {
|
||||
return SesameEvent::MessageAuthenticationFailed;
|
||||
}
|
||||
if (!storage.isDeviceIdValid(message->message.device)) {
|
||||
return SesameEvent::MessageDeviceInvalid;
|
||||
}
|
||||
if (!storage.isMessageCounterValid(message->message.id, message->message.device)) {
|
||||
return SesameEvent::MessageCounterInvalid;
|
||||
}
|
||||
if (!timeCheck.isMessageTimeAcceptable(message->message.time)) {
|
||||
return SesameEvent::MessageTimeMismatch;
|
||||
}
|
||||
|
||||
storage.didUseMessageCounter(message->message.id, message->message.device);
|
||||
// Move servo
|
||||
servo.pressButton();
|
||||
Serial.printf("[Info] Accepted message %d\n", message->message.id);
|
||||
return SesameEvent::MessageAccepted;
|
||||
}
|
||||
|
||||
bool allowMessageResponse(SesameEvent event) {
|
||||
switch (event) {
|
||||
case SesameEvent::MessageTimeMismatch:
|
||||
case SesameEvent::MessageCounterInvalid:
|
||||
case SesameEvent::MessageAccepted:
|
||||
case SesameEvent::MessageDeviceInvalid:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t SesameController::prepareResponseBuffer(SesameEvent event, uint8_t deviceId) {
|
||||
*responseStatus = event;
|
||||
if (!allowMessageResponse(event)) {
|
||||
return 1;
|
||||
}
|
||||
responseMessage->message.time = timeCheck.getEpochTime();
|
||||
responseMessage->message.id = storage.getNextMessageCounter(deviceId);
|
||||
responseMessage->message.device = deviceId;
|
||||
if (!authenticateMessage(responseMessage, keyConfig.localKey, keySize)) {
|
||||
*responseStatus = SesameEvent::InvalidResponseAuthentication;
|
||||
return 1;
|
||||
}
|
||||
return 1 + AUTHENTICATED_MESSAGE_SIZE;
|
||||
}
|
||||
|
||||
// MARK: Reconnecting
|
||||
|
||||
void SesameController::ensureWiFiConnection(uint32_t millis) {
|
||||
static uint32_t nextWifiReconnect = 0;
|
||||
// Reconnect to WiFi
|
||||
if(millis > nextWifiReconnect && WiFi.status() != WL_CONNECTED) {
|
||||
Serial.println("[INFO] Reconnecting WiFi...");
|
||||
WiFi.setHostname(wifiConfig.networkName);
|
||||
WiFi.begin(wifiConfig.ssid, wifiConfig.password);
|
||||
isReconnecting = true;
|
||||
nextWifiReconnect = millis + wifiConfig.reconnectInterval;
|
||||
}
|
||||
}
|
||||
|
||||
void SesameController::ensureWebSocketConnection() {
|
||||
if (isReconnecting && WiFi.status() == WL_CONNECTED) {
|
||||
isReconnecting = false;
|
||||
Serial.print("WiFi IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
server.connect();
|
||||
timeCheck.startNTP();
|
||||
timeCheck.printLocalTime();
|
||||
localWebServer.begin();
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Helper
|
||||
|
||||
// Based on https://stackoverflow.com/a/23898449/266720
|
||||
bool SesameController::convertHexMessageToBinary(const char* str) {
|
||||
// TODO: Fail if invalid hex values are used
|
||||
uint8_t idx0, idx1;
|
||||
|
||||
// mapping of ASCII characters to hex values
|
||||
const uint8_t hashmap[] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 01234567
|
||||
0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89:;<=>?
|
||||
0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, // @ABCDEFG
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // HIJKLMNO
|
||||
};
|
||||
|
||||
size_t len = strlen(str);
|
||||
if (len != AUTHENTICATED_MESSAGE_SIZE * 2) {
|
||||
// Require exact message size
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t pos = 0; pos < len; pos += 2) {
|
||||
idx0 = ((uint8_t)str[pos+0] & 0x1F) ^ 0x10;
|
||||
idx1 = ((uint8_t)str[pos+1] & 0x1F) ^ 0x10;
|
||||
receivedMessageBuffer[pos/2] = (uint8_t)(hashmap[idx0] << 4) | hashmap[idx1];
|
||||
};
|
||||
return true;
|
||||
}
|
@ -1,28 +1,19 @@
|
||||
#include "fresh.h"
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Arduino.h> // configTime()
|
||||
#include <time.h>
|
||||
#include <EEPROM.h>
|
||||
|
||||
/**
|
||||
* @brief The allowed discrepancy between the time of a received message
|
||||
* and the device time (in seconds)
|
||||
*
|
||||
* A stricter (lower) value better prevents against replay attacks,
|
||||
* but may lead to issues when dealing with slow networks and other
|
||||
* routing delays.
|
||||
*/
|
||||
uint32_t allowedOffset = 60;
|
||||
TimeCheck::TimeCheck() { }
|
||||
|
||||
void setMessageTimeAllowedOffset(uint32_t offset) {
|
||||
allowedOffset = offset;
|
||||
void TimeCheck::configure(TimeConfiguration configuration) {
|
||||
config = configuration;
|
||||
}
|
||||
|
||||
void configureNTP(int32_t offsetToGMT, int32_t offsetDaylightSavings, const char* serverUrl) {
|
||||
configTime(offsetToGMT, offsetDaylightSavings, serverUrl);
|
||||
void TimeCheck::startNTP() {
|
||||
configTime(config.offsetToGMT, config.offsetDaylightSavings, config.ntpServerUrl);
|
||||
}
|
||||
|
||||
void printLocalTime() {
|
||||
void TimeCheck::printLocalTime() {
|
||||
struct tm timeinfo;
|
||||
if (getLocalTime(&timeinfo)) {
|
||||
Serial.println(&timeinfo, "[INFO] Time is %A, %d. %B %Y %H:%M:%S");
|
||||
@ -31,7 +22,7 @@ void printLocalTime() {
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t getEpochTime() {
|
||||
uint32_t TimeCheck::getEpochTime() {
|
||||
time_t now;
|
||||
struct tm timeinfo;
|
||||
if (!getLocalTime(&timeinfo)) {
|
||||
@ -42,67 +33,17 @@ uint32_t getEpochTime() {
|
||||
return now;
|
||||
}
|
||||
|
||||
bool isMessageTimeAcceptable(uint32_t t) {
|
||||
bool TimeCheck::isMessageTimeAcceptable(uint32_t t) {
|
||||
uint32_t localTime = getEpochTime();
|
||||
if (localTime == 0) {
|
||||
Serial.println("No epoch time available");
|
||||
return false;
|
||||
}
|
||||
if (t > localTime + allowedOffset) {
|
||||
if (t > localTime + config.allowedTimeOffset) {
|
||||
return false;
|
||||
}
|
||||
if (t < localTime - allowedOffset) {
|
||||
if (t < localTime - config.allowedTimeOffset) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void prepareMessageCounterUsage() {
|
||||
EEPROM.begin(MESSAGE_COUNTER_SIZE * remoteDeviceCount);
|
||||
}
|
||||
|
||||
uint32_t getNextMessageCounter(uint8_t deviceId) {
|
||||
int offset = deviceId * MESSAGE_COUNTER_SIZE;
|
||||
uint32_t counter = (uint32_t) EEPROM.read(offset + 0) << 24;
|
||||
counter += (uint32_t) EEPROM.read(offset + 1) << 16;
|
||||
counter += (uint32_t) EEPROM.read(offset + 2) << 8;
|
||||
counter += (uint32_t) EEPROM.read(offset + 3);
|
||||
return counter;
|
||||
}
|
||||
|
||||
void printMessageCounters() {
|
||||
Serial.print("[INFO] Next message numbers:");
|
||||
for (uint8_t i = 0; i < remoteDeviceCount; i += 1) {
|
||||
Serial.printf(" %u", getNextMessageCounter(i));
|
||||
}
|
||||
Serial.println("");
|
||||
}
|
||||
|
||||
bool isDeviceIdValid(uint8_t deviceId) {
|
||||
return deviceId < remoteDeviceCount;
|
||||
}
|
||||
|
||||
bool isMessageCounterValid(uint32_t counter, uint8_t deviceId) {
|
||||
return counter >= getNextMessageCounter(deviceId);
|
||||
}
|
||||
|
||||
void setMessageCounter(uint32_t counter, uint8_t deviceId) {
|
||||
int offset = deviceId * MESSAGE_COUNTER_SIZE;
|
||||
EEPROM.write(offset + 0, (counter >> 24) & 0xFF);
|
||||
EEPROM.write(offset + 1, (counter >> 16) & 0xFF);
|
||||
EEPROM.write(offset + 2, (counter >> 8) & 0xFF);
|
||||
EEPROM.write(offset + 3, counter & 0xFF);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
void didUseMessageCounter(uint32_t counter, uint8_t deviceId) {
|
||||
// Store the next counter, so that resetting starts at 0
|
||||
setMessageCounter(counter+1, deviceId);
|
||||
}
|
||||
|
||||
void resetMessageCounters() {
|
||||
for (uint8_t i = 0; i < remoteDeviceCount; i += 1) {
|
||||
setMessageCounter(0, i);
|
||||
}
|
||||
Serial.println("[WARN] Message counters reset");
|
||||
}
|
||||
}
|
209
src/main.cpp
209
src/main.cpp
@ -6,196 +6,61 @@
|
||||
* physical button.
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#include <WiFi.h>
|
||||
#include <ESPAsyncWebServer.h>
|
||||
|
||||
#include "crypto.h"
|
||||
#include "fresh.h"
|
||||
#include "message.h"
|
||||
#include "server.h"
|
||||
#include "servo.h"
|
||||
#include "controller.h"
|
||||
#include "config.h"
|
||||
|
||||
/* Global variables */
|
||||
|
||||
ServerConnection server(serverUrl, serverPort, serverPath);
|
||||
|
||||
ServoController servo(pwmTimer, servoFrequency, servoPin);
|
||||
|
||||
AsyncWebServer local(localPort);
|
||||
|
||||
// The buffer to hold a received message while it is read
|
||||
uint8_t receivedMessageBuffer[AUTHENTICATED_MESSAGE_SIZE];
|
||||
|
||||
/* Event callbacks */
|
||||
|
||||
SesameEvent handleReceivedMessage(AuthenticatedMessage* payload, AuthenticatedMessage* response);
|
||||
|
||||
// Forward declare monitoring functions
|
||||
void ensureWiFiConnection(uint32_t time);
|
||||
void ensureWebSocketConnection(uint32_t time);
|
||||
|
||||
void sendFailureResponse(AsyncWebServerRequest *request, SesameEvent event);
|
||||
void sendMessageResponse(AsyncWebServerRequest *request, SesameEvent event, AuthenticatedMessage* message);
|
||||
void sendResponse(AsyncWebServerRequest *request, uint8_t* buffer, uint8_t size);
|
||||
void hexToBin(const char * str, uint8_t * bytes, size_t blen);
|
||||
|
||||
/* Logic */
|
||||
SesameController controller(localWebServerPort, remoteDeviceCount);
|
||||
|
||||
void setup() {
|
||||
Serial.begin(serialBaudRate);
|
||||
Serial.setDebugOutput(true);
|
||||
Serial.println("[INFO] Device started");
|
||||
|
||||
servo.configure(lockOpeningDuration, servoPressedState, servoReleasedState);
|
||||
Serial.println("[INFO] Servo configured");
|
||||
ServoConfiguration servoConfig {
|
||||
.pwmTimer = pwmTimer,
|
||||
.pwmFrequency = servoFrequency,
|
||||
.pin = servoPin,
|
||||
.openDuration = lockOpeningDuration,
|
||||
.pressedValue = servoPressedState,
|
||||
.releasedValue = servoReleasedState,
|
||||
};
|
||||
|
||||
prepareMessageCounterUsage();
|
||||
//resetMessageCounters();
|
||||
printMessageCounters();
|
||||
ServerConfiguration serverConfig {
|
||||
.url = serverUrl,
|
||||
.port = serverPort,
|
||||
.path = serverPath,
|
||||
.key = serverAccessKey,
|
||||
.reconnectTime = 5000,
|
||||
};
|
||||
|
||||
server.onMessage(handleReceivedMessage);
|
||||
TimeConfiguration timeConfig {
|
||||
.offsetToGMT = timeOffsetToGMT,
|
||||
.offsetDaylightSavings = timeOffsetDaylightSavings,
|
||||
.ntpServerUrl = ntpServerUrl,
|
||||
.allowedTimeOffset = 60,
|
||||
};
|
||||
|
||||
local.on("/message", HTTP_POST, [] (AsyncWebServerRequest *request) {
|
||||
if (!request->hasParam(messageUrlParameter)) {
|
||||
Serial.println("Missing url parameter");
|
||||
sendFailureResponse(request, SesameEvent::InvalidMessageData);
|
||||
return;
|
||||
}
|
||||
String encoded = request->getParam(messageUrlParameter)->value();
|
||||
hexToBin(encoded.c_str(), receivedMessageBuffer, AUTHENTICATED_MESSAGE_SIZE);
|
||||
// Process received message
|
||||
AuthenticatedMessage* message = (AuthenticatedMessage*) receivedMessageBuffer;
|
||||
AuthenticatedMessage responseMessage;
|
||||
SesameEvent event = handleReceivedMessage(message, &responseMessage);
|
||||
sendMessageResponse(request, event, &responseMessage);
|
||||
});
|
||||
WifiConfiguration wifiConfig {
|
||||
.ssid = wifiSSID,
|
||||
.password = wifiPassword,
|
||||
.networkName = networkName,
|
||||
.reconnectInterval = wifiReconnectInterval,
|
||||
};
|
||||
|
||||
KeyConfiguration keyConfig {
|
||||
.remoteKey = remoteKey,
|
||||
.localKey = localKey,
|
||||
};
|
||||
|
||||
controller.configure(servoConfig, serverConfig, timeConfig, wifiConfig, keyConfig);
|
||||
}
|
||||
|
||||
void loop() {
|
||||
uint32_t time = millis();
|
||||
|
||||
server.loop();
|
||||
servo.loop();
|
||||
|
||||
ensureWiFiConnection(time);
|
||||
ensureWebSocketConnection(time);
|
||||
}
|
||||
|
||||
uint32_t nextWifiReconnect = 0;
|
||||
bool isReconnecting = false;
|
||||
|
||||
void ensureWiFiConnection(uint32_t time) {
|
||||
// Reconnect to WiFi
|
||||
if(time > nextWifiReconnect && WiFi.status() != WL_CONNECTED) {
|
||||
Serial.println("[INFO] Reconnecting WiFi...");
|
||||
WiFi.setHostname(networkName);
|
||||
WiFi.begin(wifiSSID, wifiPassword);
|
||||
isReconnecting = true;
|
||||
nextWifiReconnect = time + wifiReconnectInterval;
|
||||
}
|
||||
}
|
||||
|
||||
void ensureWebSocketConnection(uint32_t time) {
|
||||
if (isReconnecting && WiFi.status() == WL_CONNECTED) {
|
||||
isReconnecting = false;
|
||||
Serial.println("IP address: ");
|
||||
Serial.println(WiFi.localIP());
|
||||
Serial.println("[INFO] WiFi connected, opening socket");
|
||||
server.connectSSL(serverAccessKey);
|
||||
configureNTP(timeOffsetToGMT, timeOffsetDaylightSavings, ntpServerUrl);
|
||||
printLocalTime();
|
||||
local.begin();
|
||||
}
|
||||
}
|
||||
|
||||
SesameEvent processMessage(AuthenticatedMessage* message) {
|
||||
if (!isDeviceIdValid(message->message.device)) {
|
||||
return SesameEvent::MessageDeviceInvalid;
|
||||
}
|
||||
if (!isMessageCounterValid(message->message.id, message->message.device)) {
|
||||
return SesameEvent::MessageCounterInvalid;
|
||||
}
|
||||
if (!isMessageTimeAcceptable(message->message.time)) {
|
||||
return SesameEvent::MessageTimeMismatch;
|
||||
}
|
||||
if (!isAuthenticMessage(message, remoteKey, keySize)) {
|
||||
return SesameEvent::MessageAuthenticationFailed;
|
||||
}
|
||||
return SesameEvent::MessageAccepted;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process received binary data.
|
||||
*
|
||||
* Checks whether the received data is a valid and unused key,
|
||||
* and then signals that the motor should move.
|
||||
*
|
||||
* @param payload The pointer to the received data.
|
||||
* @param length The number of bytes received.
|
||||
* @return The event to signal to the server.
|
||||
*/
|
||||
SesameEvent handleReceivedMessage(AuthenticatedMessage* message, AuthenticatedMessage* response) {
|
||||
SesameEvent event = processMessage(message);
|
||||
|
||||
// Only open when message is valid
|
||||
if (event == SesameEvent::MessageAccepted) {
|
||||
didUseMessageCounter(message->message.id, message->message.device);
|
||||
// Move servo
|
||||
servo.pressButton();
|
||||
Serial.printf("[Info] Accepted message %d\n", message->message.id);
|
||||
}
|
||||
|
||||
// Create response for all cases
|
||||
response->message.time = getEpochTime();
|
||||
response->message.id = getNextMessageCounter(message->message.device);
|
||||
if (!authenticateMessage(response, localKey, keySize)) {
|
||||
return SesameEvent::MessageAuthenticationFailed;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
|
||||
void sendFailureResponse(AsyncWebServerRequest *request, SesameEvent event) {
|
||||
uint8_t response = static_cast<uint8_t>(event);
|
||||
sendResponse(request, &response, 1);
|
||||
}
|
||||
|
||||
void sendMessageResponse(AsyncWebServerRequest *request, SesameEvent event, AuthenticatedMessage* message) {
|
||||
uint8_t response[AUTHENTICATED_MESSAGE_SIZE+1];
|
||||
response[0] = static_cast<uint8_t>(event);
|
||||
memcpy(response+1, (uint8_t*) message, AUTHENTICATED_MESSAGE_SIZE);
|
||||
sendResponse(request, response, AUTHENTICATED_MESSAGE_SIZE+1);
|
||||
}
|
||||
|
||||
void sendResponse(AsyncWebServerRequest *request, uint8_t* buffer, uint8_t size) {
|
||||
request->send_P(200, "application/octet-stream", buffer, size);
|
||||
Serial.printf("[INFO] Local response %d\n", buffer[0]);
|
||||
}
|
||||
|
||||
// Based on https://stackoverflow.com/a/23898449/266720
|
||||
void hexToBin(const char * str, uint8_t * bytes, size_t blen) {
|
||||
uint8_t idx0, idx1;
|
||||
|
||||
// mapping of ASCII characters to hex values
|
||||
const uint8_t hashmap[] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // 01234567
|
||||
0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 89:;<=>?
|
||||
0x00, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x00, // @ABCDEFG
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // HIJKLMNO
|
||||
};
|
||||
|
||||
memset(bytes, 0, blen);
|
||||
|
||||
size_t len = strlen(str);
|
||||
if (len % 2) {
|
||||
// Require two chars per byte
|
||||
return;
|
||||
}
|
||||
size_t end = min(blen*2, len);
|
||||
|
||||
for (size_t pos = 0; pos < end; pos += 2) {
|
||||
idx0 = ((uint8_t)str[pos+0] & 0x1F) ^ 0x10;
|
||||
idx1 = ((uint8_t)str[pos+1] & 0x1F) ^ 0x10;
|
||||
bytes[pos/2] = (uint8_t)(hashmap[idx0] << 4) | hashmap[idx1];
|
||||
};
|
||||
controller.loop(time);
|
||||
}
|
@ -4,44 +4,33 @@ constexpr int32_t pingInterval = 10000;
|
||||
constexpr uint32_t pongTimeout = 5000;
|
||||
uint8_t disconnectTimeoutCount = 3;
|
||||
|
||||
ServerConnection::ServerConnection(const char* url, int port, const char* path) :
|
||||
url(url), port(port), path(path) {
|
||||
ServerConnection::ServerConnection() { }
|
||||
|
||||
void ServerConnection::configure(ServerConfiguration configuration, ServerConnectionCallbacks *callbacks) {
|
||||
controller = callbacks;
|
||||
this->configuration = configuration;
|
||||
}
|
||||
|
||||
void ServerConnection::connect(const char* key, uint32_t reconnectTime) {
|
||||
webSocket.begin(url, port, path);
|
||||
registerEventCallback();
|
||||
reconnectAfter(reconnectTime);
|
||||
}
|
||||
|
||||
void ServerConnection::connectSSL(const char* key, uint32_t reconnectTime) {
|
||||
void ServerConnection::connect() {
|
||||
if (socketIsConnected) {
|
||||
return;
|
||||
}
|
||||
this->key = key;
|
||||
webSocket.beginSSL(url, port, path);
|
||||
registerEventCallback();
|
||||
reconnectAfter(reconnectTime);
|
||||
}
|
||||
if (controller == NULL) {
|
||||
Serial.println("[ERROR] No callbacks set for server");
|
||||
return;
|
||||
}
|
||||
|
||||
void ServerConnection::loop() {
|
||||
webSocket.loop();
|
||||
}
|
||||
webSocket.beginSSL(configuration.url, configuration.port, configuration.path);
|
||||
|
||||
void ServerConnection::onMessage(MessageCallback callback) {
|
||||
messageCallback = callback;
|
||||
}
|
||||
|
||||
void ServerConnection::reconnectAfter(uint32_t reconnectTime) {
|
||||
webSocket.setReconnectInterval(reconnectTime);
|
||||
}
|
||||
|
||||
void ServerConnection::registerEventCallback() {
|
||||
std::function<void(WStype_t, uint8_t *, size_t)> f = [this](WStype_t type, uint8_t *payload, size_t length) {
|
||||
this->webSocketEventHandler(type, payload, length);
|
||||
};
|
||||
webSocket.onEvent(f);
|
||||
webSocket.setReconnectInterval(configuration.reconnectTime);
|
||||
}
|
||||
|
||||
void ServerConnection::loop() {
|
||||
webSocket.loop();
|
||||
}
|
||||
|
||||
void ServerConnection::webSocketEventHandler(WStype_t type, uint8_t * payload, size_t length) {
|
||||
@ -52,15 +41,15 @@ switch(type) {
|
||||
break;
|
||||
case WStype_CONNECTED:
|
||||
socketIsConnected = true;
|
||||
webSocket.sendTXT(key);
|
||||
webSocket.sendTXT(configuration.key);
|
||||
Serial.printf("[INFO] Socket connected to url: %s\n", payload);
|
||||
webSocket.enableHeartbeat(pingInterval, pongTimeout, disconnectTimeoutCount);
|
||||
break;
|
||||
case WStype_TEXT:
|
||||
sendFailureResponse(SesameEvent::TextReceived);
|
||||
controller->sendServerError(SesameEvent::TextReceived);
|
||||
break;
|
||||
case WStype_BIN:
|
||||
processReceivedBytes(payload, length);
|
||||
controller->handleServerMessage(payload, length);
|
||||
break;
|
||||
case WStype_PONG:
|
||||
break;
|
||||
@ -70,36 +59,11 @@ switch(type) {
|
||||
case WStype_FRAGMENT_BIN_START:
|
||||
case WStype_FRAGMENT:
|
||||
case WStype_FRAGMENT_FIN:
|
||||
sendFailureResponse(SesameEvent::UnexpectedSocketEvent);
|
||||
controller->sendServerError(SesameEvent::UnexpectedSocketEvent);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ServerConnection::processReceivedBytes(uint8_t* payload, size_t length) {
|
||||
if (length != AUTHENTICATED_MESSAGE_SIZE) {
|
||||
sendFailureResponse(SesameEvent::InvalidMessageData);
|
||||
return;
|
||||
}
|
||||
AuthenticatedMessage* message = (AuthenticatedMessage*) payload;
|
||||
if (messageCallback == NULL) {
|
||||
sendFailureResponse(SesameEvent::MessageAuthenticationFailed);
|
||||
return;
|
||||
}
|
||||
AuthenticatedMessage responseMessage;
|
||||
SesameEvent event = messageCallback(message, &responseMessage);
|
||||
sendResponse(event, &responseMessage);
|
||||
}
|
||||
|
||||
void ServerConnection::sendFailureResponse(SesameEvent event) {
|
||||
uint8_t response = static_cast<uint8_t>(event);
|
||||
webSocket.sendBIN(&response, 1);
|
||||
Serial.printf("[INFO] Socket failure %d\n", response);
|
||||
}
|
||||
|
||||
void ServerConnection::sendResponse(SesameEvent event, AuthenticatedMessage* message) {
|
||||
uint8_t response[AUTHENTICATED_MESSAGE_SIZE+1];
|
||||
response[0] = static_cast<uint8_t>(event);
|
||||
memcpy(response+1, (uint8_t*) message, AUTHENTICATED_MESSAGE_SIZE);
|
||||
webSocket.sendBIN(response, AUTHENTICATED_MESSAGE_SIZE+1);
|
||||
Serial.printf("[INFO] Socket response %d\n", response[0]);
|
||||
void ServerConnection::sendResponse(uint8_t* buffer, uint16_t length) {
|
||||
webSocket.sendBIN(buffer, length);
|
||||
}
|
@ -2,18 +2,15 @@
|
||||
|
||||
#include <esp32-hal.h> // For `millis()`
|
||||
|
||||
ServoController::ServoController(int timer, int frequency, int pin)
|
||||
: timer(timer), frequency(frequency), pin(pin) {
|
||||
|
||||
}
|
||||
ServoController::ServoController() { }
|
||||
|
||||
void ServoController::configure(uint32_t openDuration, int pressedValue, int releasedValue) {
|
||||
this->openDuration = openDuration;
|
||||
this->pressedValue = pressedValue;
|
||||
this->releasedValue = releasedValue;
|
||||
ESP32PWM::allocateTimer(timer);
|
||||
servo.setPeriodHertz(frequency);
|
||||
servo.attach(pin);
|
||||
void ServoController::configure(ServoConfiguration configuration) {
|
||||
openDuration = configuration.openDuration;
|
||||
pressedValue = configuration.pressedValue;
|
||||
releasedValue = configuration.releasedValue;
|
||||
ESP32PWM::allocateTimer(configuration.pwmTimer);
|
||||
servo.setPeriodHertz(configuration.pwmFrequency);
|
||||
servo.attach(configuration.pin);
|
||||
releaseButton();
|
||||
}
|
||||
|
||||
@ -28,8 +25,8 @@ void ServoController::releaseButton() {
|
||||
buttonIsPressed = false;
|
||||
}
|
||||
|
||||
void ServoController::loop() {
|
||||
if (buttonIsPressed && millis() > openingEndTime) {
|
||||
void ServoController::loop(uint32_t millis) {
|
||||
if (buttonIsPressed && millis > openingEndTime) {
|
||||
releaseButton();
|
||||
}
|
||||
}
|
||||
|
53
src/storage.cpp
Normal file
53
src/storage.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
#include "storage.h"
|
||||
#include "message.h"
|
||||
#include <EEPROM.h>
|
||||
|
||||
void Storage::configure() {
|
||||
EEPROM.begin(messageCounterSize * remoteDeviceCount);
|
||||
}
|
||||
|
||||
bool Storage::isDeviceIdValid(uint8_t deviceId) {
|
||||
return deviceId < remoteDeviceCount;
|
||||
}
|
||||
|
||||
bool Storage::isMessageCounterValid(uint32_t counter, uint8_t deviceId) {
|
||||
return counter >= getNextMessageCounter(deviceId);
|
||||
}
|
||||
|
||||
void Storage::didUseMessageCounter(uint32_t counter, uint8_t deviceId) {
|
||||
// Store the next counter, so that resetting starts at 0
|
||||
setMessageCounter(counter+1, deviceId);
|
||||
}
|
||||
|
||||
void Storage::setMessageCounter(uint32_t counter, uint8_t deviceId) {
|
||||
int offset = deviceId * messageCounterSize;
|
||||
EEPROM.write(offset + 0, (counter >> 24) & 0xFF);
|
||||
EEPROM.write(offset + 1, (counter >> 16) & 0xFF);
|
||||
EEPROM.write(offset + 2, (counter >> 8) & 0xFF);
|
||||
EEPROM.write(offset + 3, counter & 0xFF);
|
||||
EEPROM.commit();
|
||||
}
|
||||
|
||||
uint32_t Storage::getNextMessageCounter(uint8_t deviceId) {
|
||||
int offset = deviceId * messageCounterSize;
|
||||
uint32_t counter = (uint32_t) EEPROM.read(offset + 0) << 24;
|
||||
counter += (uint32_t) EEPROM.read(offset + 1) << 16;
|
||||
counter += (uint32_t) EEPROM.read(offset + 2) << 8;
|
||||
counter += (uint32_t) EEPROM.read(offset + 3);
|
||||
return counter;
|
||||
}
|
||||
|
||||
void Storage::printMessageCounters() {
|
||||
Serial.print("[INFO] Next message numbers:");
|
||||
for (uint8_t i = 0; i < remoteDeviceCount; i += 1) {
|
||||
Serial.printf(" %u", getNextMessageCounter(i));
|
||||
}
|
||||
Serial.println("");
|
||||
}
|
||||
|
||||
void Storage::resetMessageCounters() {
|
||||
for (uint8_t i = 0; i < remoteDeviceCount; i += 1) {
|
||||
setMessageCounter(0, i);
|
||||
}
|
||||
Serial.println("[WARN] Message counters reset");
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user