Restructure, use HMAC, NTP
Remove config details
This commit is contained in:
parent
35b171e0c4
commit
1c7011400c
55
include/crypto.h
Normal file
55
include/crypto.h
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "message.h"
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a message authentication code (MAC) for some data.
|
||||||
|
*
|
||||||
|
* @param data The data to authenticate
|
||||||
|
* @param dataLength The number of bytes to authenticate
|
||||||
|
* @param mac The output to store the MAC (must be at least 32 bytes)
|
||||||
|
* @param key The secret key used for authentication
|
||||||
|
* @param keyLength The length of the secret key
|
||||||
|
* @return true The MAC was successfully written
|
||||||
|
* @return false The MAC could not be created
|
||||||
|
*/
|
||||||
|
bool authenticateData(const uint8_t* data, size_t dataLength, uint8_t* mac, const uint8_t* key, size_t keyLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Calculate a MAC for message content.
|
||||||
|
*
|
||||||
|
* @param message The message for which to calculate the MAC.
|
||||||
|
* @param mac The output where the computed MAC is stored
|
||||||
|
* @param key The secret key used for authentication
|
||||||
|
* @param keyLength The length of the secret key
|
||||||
|
* @return true The MAC was successfully computed
|
||||||
|
* @return false The MAC could not be created
|
||||||
|
*/
|
||||||
|
bool authenticateMessage(Message* message, uint8_t* mac, const uint8_t* key, size_t keyLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a message authentication code (MAC) for a message.
|
||||||
|
*
|
||||||
|
* @param message The message to authenticate
|
||||||
|
* @param key The secret key used for authentication
|
||||||
|
* @param keyLength The length of the secret key
|
||||||
|
* @return true The MAC was successfully added to the message
|
||||||
|
* @return false The MAC could not be created
|
||||||
|
*/
|
||||||
|
bool authenticateMessage(AuthenticatedMessage* message, const uint8_t* key, size_t keyLength);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Check if a received unlock message is authentic
|
||||||
|
*
|
||||||
|
* This function computes the MAC of the message and compares it with
|
||||||
|
* the MAC included in the message. The message is authentic if both
|
||||||
|
* MACs are identical.
|
||||||
|
*
|
||||||
|
* @param message The message to authenticate
|
||||||
|
* @param key The secret key used for authentication
|
||||||
|
* @param keyLength The length of the key in bytes
|
||||||
|
* @return true The message is authentic
|
||||||
|
* @return false The message is invalid, or the MAC could not be calculated
|
||||||
|
*/
|
||||||
|
bool isAuthenticMessage(AuthenticatedMessage* message, const uint8_t* key, size_t keyLength);
|
114
include/fresh.h
Normal file
114
include/fresh.h
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The size of the message counter in bytes (uint32_t)
|
||||||
|
*/
|
||||||
|
#define MESSAGE_COUNTER_SIZE sizeof(uint32_t)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 Print the current time to the serial output
|
||||||
|
*
|
||||||
|
* The time must be initialized by calling `configureNTP()` before use.
|
||||||
|
*/
|
||||||
|
void printLocalTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the current epoch time
|
||||||
|
*/
|
||||||
|
uint32_t getEpochTime();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Print info about the current message counter to the serial output
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
void printMessageCounter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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 resetMessageCounter();
|
87
include/message.h
Normal file
87
include/message.h
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "stdint.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The size of a message authentication code
|
||||||
|
*
|
||||||
|
* The MAC size is determined by the size of the output
|
||||||
|
* of the hash function used. In this case, for SHA256,
|
||||||
|
* the size is 32 bytes (= 256 bit)
|
||||||
|
*/
|
||||||
|
#define SHA256_MAC_SIZE 32
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The content of an unlock message.
|
||||||
|
*
|
||||||
|
* The content is necessary to ensure freshness of the message
|
||||||
|
* by requiring a recent time and a monotonously increasing counter.
|
||||||
|
* This prevents messages from being delayed or being blocked and
|
||||||
|
* replayed later.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp of message creation
|
||||||
|
*
|
||||||
|
* The timestamp is encoded as the epoch time, i.e. seconds since 1970 (GMT).
|
||||||
|
*
|
||||||
|
* 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.
|
||||||
|
*/
|
||||||
|
uint32_t time;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The counter of unlock messages
|
||||||
|
*
|
||||||
|
* This counter must always increase with each message from the remote
|
||||||
|
* in order for the messages to be deemed valid. Transfering the counters
|
||||||
|
* back and forth also gives information about lost messages and potential
|
||||||
|
* attacks. Both the remote and the device keep a record of at least the
|
||||||
|
* last used counter.
|
||||||
|
*/
|
||||||
|
uint32_t id;
|
||||||
|
|
||||||
|
} Message;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief An authenticated message by the mobile device to command unlocking.
|
||||||
|
*
|
||||||
|
* The message is protected by a message authentication code (MAC) based on
|
||||||
|
* a symmetric key shared by the device and the remote. This code ensures
|
||||||
|
* that the contents of the request were not altered. The message further
|
||||||
|
* contains a timestamp to ensure that the message is recent, and not replayed
|
||||||
|
* by an attacker. An additional counter is also included for this purpose,
|
||||||
|
* which must continously increase for a message to be valid. This increases
|
||||||
|
* security a bit, since the timestamp validation must be tolerant to some
|
||||||
|
* inaccuracy due to mismatching clocks.
|
||||||
|
*/
|
||||||
|
typedef struct {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The authentication code of the message
|
||||||
|
*
|
||||||
|
* The code is created by performing HMAC-SHA256
|
||||||
|
* over the bytes of the `Message`.
|
||||||
|
*/
|
||||||
|
uint8_t mac[SHA256_MAC_SIZE];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The message content.
|
||||||
|
*
|
||||||
|
* The content is necessary to ensure freshness of the message
|
||||||
|
* by requiring a recent time and a monotonously increasing counter.
|
||||||
|
* This prevents messages from being delayed or being blocked and
|
||||||
|
* replayed later.
|
||||||
|
*/
|
||||||
|
Message message;
|
||||||
|
|
||||||
|
} AuthenticatedMessage;
|
||||||
|
#pragma pack(pop)
|
||||||
|
|
||||||
|
#define MESSAGE_CONTENT_SIZE sizeof(Message)
|
||||||
|
|
||||||
|
#define AUTHENTICATED_MESSAGE_SIZE sizeof(AuthenticatedMessage)
|
108
include/server.h
Normal file
108
include/server.h
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "message.h"
|
||||||
|
#include "crypto.h"
|
||||||
|
#include <WiFiMulti.h>
|
||||||
|
#include <WiFiClientSecure.h>
|
||||||
|
#include <WebSocketsClient.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An event signaled from the device
|
||||||
|
*/
|
||||||
|
enum class SesameEvent {
|
||||||
|
TextReceived = 1,
|
||||||
|
UnexpectedSocketEvent = 2,
|
||||||
|
InvalidPayloadSize = 3,
|
||||||
|
MessageAuthenticationFailed = 4,
|
||||||
|
MessageTimeMismatch = 5,
|
||||||
|
MessageCounterInvalid = 6,
|
||||||
|
MessageAccepted = 7,
|
||||||
|
InfoMessage = 8,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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.
|
||||||
|
*/
|
||||||
|
typedef SesameEvent (*MessageCallback)(AuthenticatedMessage*, AuthenticatedMessage*);
|
||||||
|
|
||||||
|
class ServerConnection {
|
||||||
|
|
||||||
|
public:
|
||||||
|
|
||||||
|
ServerConnection(const char* url, int port, const char* path);
|
||||||
|
|
||||||
|
void connect(const char* key, uint32_t reconnectTime = 5000);
|
||||||
|
|
||||||
|
void connectSSL(const char* key, uint32_t reconnectTime = 5000);
|
||||||
|
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
void onMessage(MessageCallback callback);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// WebSocket to connect to the control server
|
||||||
|
WebSocketsClient webSocket;
|
||||||
|
|
||||||
|
void reconnectAfter(uint32_t reconnectTime);
|
||||||
|
|
||||||
|
void registerEventCallback();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for WebSocket events.
|
||||||
|
*
|
||||||
|
* Updates the connection state and processes received keys.
|
||||||
|
*
|
||||||
|
* @param payload The pointer to received data
|
||||||
|
* @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);
|
||||||
|
|
||||||
|
};
|
84
include/servo.h
Normal file
84
include/servo.h
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <ESP32Servo.h> // To control the servo
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A controller for the button control servo
|
||||||
|
*
|
||||||
|
* The controller simply configures the servo for operation,
|
||||||
|
* and then sets the desired servo value for the 'pressed' and 'released' states.
|
||||||
|
* The controller requires periodic updating through the `loop()` function
|
||||||
|
* in order to release the button after the specified time.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
ServoController(int timer, int frequency, int pin);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Configure the button values
|
||||||
|
*
|
||||||
|
* @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
|
||||||
|
*/
|
||||||
|
void configure(uint32_t openDuration, int pressedValue, int releasedValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Update the servo state periodically
|
||||||
|
*
|
||||||
|
* This function should be periodically called to update the servo state,
|
||||||
|
* specifically to release the button after the opening time has elapsed.
|
||||||
|
*
|
||||||
|
* There is no required interval to call this function, but the accuracy of
|
||||||
|
* the opening interval is dependent on the calling frequency.
|
||||||
|
*/
|
||||||
|
void loop();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Push the door opener button down by moving the servo arm.
|
||||||
|
*/
|
||||||
|
void pressButton();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Release the door opener button by moving the servo arm.
|
||||||
|
*/
|
||||||
|
void releaseButton();
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
// Indicator that the door button is pushed
|
||||||
|
bool buttonIsPressed = false;
|
||||||
|
|
||||||
|
int timer;
|
||||||
|
|
||||||
|
int frequency;
|
||||||
|
|
||||||
|
int pin;
|
||||||
|
|
||||||
|
uint32_t openDuration = 0;
|
||||||
|
|
||||||
|
int pressedValue = 0;
|
||||||
|
|
||||||
|
int releasedValue = 0;
|
||||||
|
|
||||||
|
// The time (in ms since start) when the door opening should end
|
||||||
|
uint32_t openingEndTime = 0;
|
||||||
|
|
||||||
|
// Servo controller
|
||||||
|
Servo servo;
|
||||||
|
|
||||||
|
// PWM Module needed for the servo
|
||||||
|
ESP32PWM pwm;
|
||||||
|
|
||||||
|
};
|
45
src/crypto.cpp
Normal file
45
src/crypto.cpp
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
#include "crypto.h"
|
||||||
|
#include <string.h>
|
||||||
|
#include <mbedtls/md.h>
|
||||||
|
|
||||||
|
bool authenticateData(const uint8_t* data, size_t dataLength, uint8_t* mac, const uint8_t* key, size_t keyLength) {
|
||||||
|
mbedtls_md_context_t ctx;
|
||||||
|
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
|
||||||
|
int result;
|
||||||
|
|
||||||
|
mbedtls_md_init(&ctx);
|
||||||
|
result = mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 1);
|
||||||
|
if (result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result = mbedtls_md_hmac_starts(&ctx, key, keyLength);
|
||||||
|
if (result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result = mbedtls_md_hmac_update(&ctx, data, dataLength);
|
||||||
|
if (result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
result = mbedtls_md_hmac_finish(&ctx, mac);
|
||||||
|
if (result) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
mbedtls_md_free(&ctx);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool authenticateMessage(Message* message, uint8_t* mac, const uint8_t* key, size_t keyLength) {
|
||||||
|
return authenticateData((const uint8_t*) message, MESSAGE_CONTENT_SIZE, mac, key, keyLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool authenticateMessage(AuthenticatedMessage* message, const uint8_t* key, size_t keyLength) {
|
||||||
|
return authenticateMessage(&message->message, message->mac, key, keyLength);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isAuthenticMessage(AuthenticatedMessage* message, const uint8_t* key, size_t keyLength) {
|
||||||
|
uint8_t mac[SHA256_MAC_SIZE];
|
||||||
|
if (!authenticateMessage(&message->message, mac, key, keyLength)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return memcmp(mac, message->mac, SHA256_MAC_SIZE) == 0;
|
||||||
|
}
|
92
src/fresh.cpp
Normal file
92
src/fresh.cpp
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
#include "fresh.h"
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <time.h>
|
||||||
|
#include <EEPROM.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The size of the message counter in bytes (uint32_t)
|
||||||
|
*/
|
||||||
|
#define MESSAGE_COUNTER_SIZE sizeof(uint32_t)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @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;
|
||||||
|
|
||||||
|
void setMessageTimeAllowedOffset(uint32_t offset) {
|
||||||
|
allowedOffset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void configureNTP(int32_t offsetToGMT, int32_t offsetDaylightSavings, const char* serverUrl) {
|
||||||
|
configTime(offsetToGMT, offsetDaylightSavings, serverUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
void printLocalTime() {
|
||||||
|
struct tm timeinfo;
|
||||||
|
if (getLocalTime(&timeinfo)) {
|
||||||
|
Serial.println(&timeinfo, "[INFO] Time is %A, %d. %B %Y %H:%M:%S");
|
||||||
|
} else {
|
||||||
|
Serial.println("[WARN] No local time available");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getEpochTime() {
|
||||||
|
time_t now;
|
||||||
|
struct tm timeinfo;
|
||||||
|
if (!getLocalTime(&timeinfo)) {
|
||||||
|
Serial.println("[WARN] Failed to obtain local time");
|
||||||
|
return(0);
|
||||||
|
}
|
||||||
|
time(&now);
|
||||||
|
return now;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isMessageTimeAcceptable(uint32_t t) {
|
||||||
|
uint32_t localTime = getEpochTime();
|
||||||
|
if (localTime == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return t < localTime + allowedOffset && t > localTime - allowedOffset;
|
||||||
|
}
|
||||||
|
|
||||||
|
void prepareMessageCounterUsage() {
|
||||||
|
EEPROM.begin(MESSAGE_COUNTER_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getNextMessageCounter() {
|
||||||
|
uint32_t counter = (uint32_t) EEPROM.read(0) << 24;
|
||||||
|
counter += (uint32_t) EEPROM.read(1) << 16;
|
||||||
|
counter += (uint32_t) EEPROM.read(2) << 8;
|
||||||
|
counter += (uint32_t) EEPROM.read(3);
|
||||||
|
return counter;
|
||||||
|
}
|
||||||
|
|
||||||
|
void printMessageCounter() {
|
||||||
|
Serial.printf("[INFO] Next message number: %d\n", getNextMessageCounter());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isMessageCounterValid(uint32_t counter) {
|
||||||
|
return counter >= getNextMessageCounter();
|
||||||
|
}
|
||||||
|
|
||||||
|
void didUseMessageCounter(uint32_t counter) {
|
||||||
|
// Store the next counter, so that resetting starts at 0
|
||||||
|
counter += 1;
|
||||||
|
EEPROM.write(0, (counter >> 24) & 0xFF);
|
||||||
|
EEPROM.write(1, (counter >> 16) & 0xFF);
|
||||||
|
EEPROM.write(2, (counter >> 8) & 0xFF);
|
||||||
|
EEPROM.write(3, counter & 0xFF);
|
||||||
|
|
||||||
|
EEPROM.commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void resetMessageCounter() {
|
||||||
|
didUseMessageCounter(0);
|
||||||
|
Serial.println("[WARN] Message counter reset");
|
||||||
|
}
|
408
src/main.cpp
408
src/main.cpp
@ -7,11 +7,12 @@
|
|||||||
*/
|
*/
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
#include <WiFi.h>
|
#include <WiFi.h>
|
||||||
#include <WiFiMulti.h>
|
|
||||||
#include <WiFiClientSecure.h>
|
#include "crypto.h"
|
||||||
#include <WebSocketsClient.h>
|
#include "fresh.h"
|
||||||
#include <EEPROM.h> // To mark used keys as expired
|
#include "message.h"
|
||||||
#include <ESP32Servo.h> // To control the servo
|
#include "server.h"
|
||||||
|
#include "servo.h"
|
||||||
|
|
||||||
// TODO:
|
// TODO:
|
||||||
// - Handle WiFi disconnect
|
// - Handle WiFi disconnect
|
||||||
@ -20,41 +21,34 @@
|
|||||||
|
|
||||||
/* Settings */
|
/* Settings */
|
||||||
|
|
||||||
#define SERIAL_BAUD_RATE 115200
|
constexpr uint32_t serialBaudRate = 115200;
|
||||||
|
|
||||||
|
constexpr size_t keySize = 32;
|
||||||
|
constexpr const uint8_t remoteKey[keySize] = { 1, 2, 3};
|
||||||
|
constexpr const uint8_t localKey[keySize] = { 1, 2, 3};
|
||||||
|
|
||||||
// The WiFi network to connect to
|
// The WiFi network to connect to
|
||||||
constexpr const char* WIFI_SSID = "MyNetwork";
|
constexpr const char* wifiSSID = "MyNetwork";
|
||||||
constexpr const char* WIFI_PWD = "MyPassword";
|
constexpr const char* wifiPassword = "MyPassword";
|
||||||
|
constexpr uint32_t wifiReconnectInterval = 10000;
|
||||||
|
|
||||||
// The remote server to connect to
|
// The remote server to connect to
|
||||||
constexpr const char* SERVER_URL = "christophhagen.de";
|
constexpr const char* serverUrl = "mydomain.com";
|
||||||
constexpr const int SERVER_PORT = 443;
|
constexpr const int serverPort = 443;
|
||||||
constexpr const char* SERVER_PATH = "/sesame/listen";
|
constexpr const char* serverPath = "/sesame/listen";
|
||||||
constexpr const char* SERVER_PSK = "access token";
|
constexpr const char* serverAccessKey = "MyAccessToken";
|
||||||
#define USE_SSL // Use SSL for the Websocket connection to the server
|
|
||||||
|
|
||||||
// The interval to attempt to reconnect the socket to the server
|
|
||||||
constexpr unsigned long SOCKET_RECONNECT_TIME = 5000;
|
|
||||||
|
|
||||||
// Crypto setting for the security of keys
|
|
||||||
constexpr uint8_t KEY_STRENGTH = 128;
|
|
||||||
constexpr uint8_t KEY_BYTE_COUNT = KEY_STRENGTH / 8;
|
|
||||||
|
|
||||||
// The keys defined to allow opening
|
|
||||||
// Once a key is used, the corresponding byte in EEPROM is set to 1,
|
|
||||||
// to prevent it from being used again.
|
|
||||||
const uint8_t keys[][KEY_BYTE_COUNT] = {
|
|
||||||
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
|
|
||||||
};
|
|
||||||
const uint16_t KEY_COUNT = sizeof keys / KEY_BYTE_COUNT;
|
|
||||||
|
|
||||||
|
/* Time */
|
||||||
|
constexpr const char* ntpServerUrl = "pool.ntp.org";
|
||||||
|
constexpr int32_t timeOffsetToGMT = 3600;
|
||||||
|
constexpr int32_t timeOffsetDaylightSavings = 3600;
|
||||||
|
|
||||||
/* Servo */
|
/* Servo */
|
||||||
|
|
||||||
// Servo is Emax ES08MA II
|
// Servo is Emax ES08MA II
|
||||||
|
|
||||||
// The time (in ms) to keep the door button pressed
|
// The time (in ms) to keep the door button pressed
|
||||||
constexpr uint32_t OPENING_DURATION = 2000;
|
constexpr uint32_t lockOpeningDuration = 2000;
|
||||||
|
|
||||||
// The timer to use to control the servo
|
// The timer to use to control the servo
|
||||||
constexpr int pwmTimer = 0;
|
constexpr int pwmTimer = 0;
|
||||||
@ -65,141 +59,52 @@ constexpr int servoPin = 14;
|
|||||||
// The Emax is a standard 50 Hz servo
|
// The Emax is a standard 50 Hz servo
|
||||||
constexpr int servoFrequency = 50;
|
constexpr int servoFrequency = 50;
|
||||||
|
|
||||||
// The microseconds to set the servo to the pressed state
|
// The microseconds to set the servo to the pressed and released states
|
||||||
constexpr int PRESS_STATE_INTERVAL = 1600;
|
constexpr int servoPressedState = 1600;
|
||||||
|
constexpr int servoReleasedState = 1520;
|
||||||
// The microseconds to set the servo to the released state
|
|
||||||
constexpr int RELEASE_STATE_INTERVAL = 1520;
|
|
||||||
|
|
||||||
|
|
||||||
/* Global variables */
|
/* Global variables */
|
||||||
|
|
||||||
// Servo controller
|
ServerConnection server(serverUrl, serverPort, serverPath);
|
||||||
Servo servo;
|
|
||||||
|
|
||||||
// PWM Module needed for the servo
|
ServoController servo(pwmTimer, servoFrequency, servoPin);
|
||||||
ESP32PWM pwm;
|
|
||||||
|
|
||||||
// WiFi module to connect to the network
|
/* Event callbacks */
|
||||||
WiFiMulti WiFiMulti;
|
|
||||||
|
|
||||||
// WebSocket to connect to the control server
|
|
||||||
WebSocketsClient webSocket;
|
|
||||||
|
|
||||||
// Indicator that the socket is connected.
|
|
||||||
bool socketIsConnected = false;
|
|
||||||
|
|
||||||
// The index of the next valid key, which is the next key after the highest used key.
|
|
||||||
// If the index is larger or equal to the total key count, then no usable keys exist.
|
|
||||||
uint16_t nextKeyIndex = KEY_COUNT;
|
|
||||||
|
|
||||||
// Flag to signal that the door button should be pressed
|
|
||||||
bool shouldStartOpening = false;
|
|
||||||
|
|
||||||
// Indicator that the door button is pushed
|
|
||||||
bool buttonIsPressed = false;
|
|
||||||
|
|
||||||
// The time (in ms since start) when the door opening should end
|
|
||||||
uint32_t openingEndTime = 0;
|
|
||||||
|
|
||||||
|
|
||||||
/* Events */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An event occuring due to a server request
|
|
||||||
*/
|
|
||||||
enum class SesameEvent {
|
|
||||||
TextReceived = 1,
|
|
||||||
UnexpectedSocketEvent = 2,
|
|
||||||
InvalidPayloadSize = 3,
|
|
||||||
InvalidKeyIndex = 4,
|
|
||||||
InvalidKey = 5,
|
|
||||||
KeyAlreadyUsed = 6,
|
|
||||||
KeyWasSkipped = 7,
|
|
||||||
KeyAccepted = 8,
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/* Key management */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the index of the next key which wasn't used yet
|
|
||||||
*
|
|
||||||
* A key is unused, if the EEPROM corresponding to the key is zero.
|
|
||||||
* If all keys have been used, returns KEY_COUNT
|
|
||||||
*/
|
|
||||||
uint16_t indexOfNextUsableKey();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if a key was already marked as used.
|
|
||||||
*
|
|
||||||
* A key was used, if the EEPROM byte corresponding to the key is non-zero.
|
|
||||||
* @param keyIndex The index of the key in the 'keys' array.
|
|
||||||
* @return 0, if the key is unused, else non-zero.
|
|
||||||
*/
|
|
||||||
bool keyWasAlreadyUsed(uint16_t keyIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks a key as used
|
|
||||||
*
|
|
||||||
* Sets the corresponding EEPROM byte to non-zero.
|
|
||||||
* @param keyIndex The index of the key in the 'keys' array.
|
|
||||||
*/
|
|
||||||
void markKeyUsed(uint16_t keyIndex);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Marks all keys unused.
|
|
||||||
*
|
|
||||||
* Sets the EEPROM data for each key index to zero.
|
|
||||||
*
|
|
||||||
* WARNING: Only to be used when a new set of keys has been set.
|
|
||||||
* Otherwise replay attacks are possible with observed old keys.
|
|
||||||
*/
|
|
||||||
void markAllKeysUnused();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compares two keys in constant time
|
|
||||||
* @return 1, if the keys are equal, else 0
|
|
||||||
*/
|
|
||||||
bool keysAreEqual(const uint8_t* key1, const uint8_t* key2);
|
|
||||||
|
|
||||||
|
|
||||||
/* Servo management */
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Push the door opener button down by moving the servo arm.
|
|
||||||
*/
|
|
||||||
void pressButton();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Release the door opener button by moving the servo arm.
|
|
||||||
*/
|
|
||||||
void releaseButton();
|
|
||||||
|
|
||||||
|
SesameEvent handleReceivedMessage(AuthenticatedMessage* payload, AuthenticatedMessage* response);
|
||||||
|
|
||||||
/* Logic */
|
/* Logic */
|
||||||
|
|
||||||
/**
|
void setup() {
|
||||||
* Send a response event to the server and include the next key index.
|
Serial.begin(serialBaudRate);
|
||||||
*
|
Serial.setDebugOutput(true);
|
||||||
* Sends the event type as three byte.
|
Serial.println("[INFO] Device started");
|
||||||
* @param event The event type
|
|
||||||
*/
|
servo.configure(lockOpeningDuration, servoPressedState, servoReleasedState);
|
||||||
void sendResponse(SesameEvent event) {
|
Serial.println("[INFO] Servo configured");
|
||||||
uint8_t response[3];
|
|
||||||
response[0] = static_cast<uint8_t>(event);
|
prepareMessageCounterUsage();
|
||||||
response[1] = nextKeyIndex >> 8;
|
printMessageCounter();
|
||||||
response[2] = nextKeyIndex & 0xFF;
|
|
||||||
webSocket.sendBIN(response, 3);
|
Serial.printf("[INFO] Connecting to WiFi '%s'\n", wifiSSID);
|
||||||
//webSocket.sendTXT(text);
|
WiFi.begin(wifiSSID, wifiPassword);
|
||||||
Serial.printf("[INFO] Event %d\n", response[0]);
|
while(WiFi.status() != WL_CONNECTED) {
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
Serial.println("[INFO] WiFi connected");
|
||||||
|
|
||||||
|
configureNTP(timeOffsetToGMT, timeOffsetDaylightSavings, ntpServerUrl);
|
||||||
|
printLocalTime();
|
||||||
|
|
||||||
|
server.onMessage(handleReceivedMessage);
|
||||||
|
Serial.printf("[INFO] Opening SSL socket %s%s on port %d\n", serverUrl, serverPath, serverPort);
|
||||||
|
server.connectSSL(serverAccessKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
void loop() {
|
||||||
* Send the pre-shared key to the server to complete the socket connection.
|
server.loop();
|
||||||
*/
|
servo.loop();
|
||||||
void authenticateDevice() {
|
|
||||||
webSocket.sendTXT(SERVER_PSK);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -212,191 +117,26 @@ void authenticateDevice() {
|
|||||||
* @param length The number of bytes received.
|
* @param length The number of bytes received.
|
||||||
* @return The event to signal to the server.
|
* @return The event to signal to the server.
|
||||||
*/
|
*/
|
||||||
SesameEvent handleReceivedRequest(const uint8_t* payload, size_t length) {
|
SesameEvent handleReceivedMessage(AuthenticatedMessage* message, AuthenticatedMessage* response) {
|
||||||
if (length != KEY_BYTE_COUNT + 2) {
|
|
||||||
return SesameEvent::InvalidPayloadSize;
|
if (!isMessageCounterValid(message->message.id)) {
|
||||||
|
return SesameEvent::MessageCounterInvalid;
|
||||||
}
|
}
|
||||||
Serial.println("Key received");
|
if (!isMessageTimeAcceptable(message->message.time)) {
|
||||||
uint16_t keyIndex = ((uint16_t) payload[0] << 8) + payload[1];
|
return SesameEvent::MessageTimeMismatch;
|
||||||
if (keyIndex >= KEY_COUNT) {
|
|
||||||
return SesameEvent::InvalidKeyIndex;
|
|
||||||
}
|
}
|
||||||
if (!keysAreEqual(payload + 2, keys[keyIndex])) {
|
if (!isAuthenticMessage(message, remoteKey, keySize)) {
|
||||||
return SesameEvent::InvalidKey;
|
return SesameEvent::MessageAuthenticationFailed;
|
||||||
}
|
}
|
||||||
if (keyWasAlreadyUsed(keyIndex)) {
|
|
||||||
return SesameEvent::KeyAlreadyUsed;
|
response->message.time = getEpochTime();
|
||||||
|
response->message.id = getNextMessageCounter();
|
||||||
|
if (!authenticateMessage(response, localKey, keySize)) {
|
||||||
|
return SesameEvent::MessageAuthenticationFailed;
|
||||||
}
|
}
|
||||||
if (nextKeyIndex > keyIndex) {
|
|
||||||
return SesameEvent::KeyWasSkipped;
|
|
||||||
}
|
|
||||||
markKeyUsed(keyIndex);
|
|
||||||
nextKeyIndex = keyIndex + 1;
|
|
||||||
// Move servo
|
// Move servo
|
||||||
shouldStartOpening = true;
|
servo.pressButton();
|
||||||
Serial.printf("[Info] Used key %d\n", keyIndex);
|
Serial.printf("[Info] Accepted message %d\n", message->message.id);
|
||||||
return SesameEvent::KeyAccepted;
|
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.
|
|
||||||
* 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) {
|
|
||||||
SesameEvent event = handleReceivedRequest(payload, length);
|
|
||||||
sendResponse(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback for WebSocket events.
|
|
||||||
*
|
|
||||||
* Updates the connection state and processes received keys.
|
|
||||||
*
|
|
||||||
* @param payload The pointer to received data
|
|
||||||
* @param length The number of bytes received
|
|
||||||
*/
|
|
||||||
void webSocketEvent(WStype_t type, uint8_t * payload, size_t length) {
|
|
||||||
switch(type) {
|
|
||||||
case WStype_DISCONNECTED:
|
|
||||||
socketIsConnected = false;
|
|
||||||
Serial.println("[INFO] Socket disconnected.");
|
|
||||||
break;
|
|
||||||
case WStype_CONNECTED:
|
|
||||||
socketIsConnected = true;
|
|
||||||
authenticateDevice();
|
|
||||||
Serial.printf("[INFO] Socket connected to url: %s\n", payload);
|
|
||||||
break;
|
|
||||||
case WStype_TEXT:
|
|
||||||
sendResponse(SesameEvent::TextReceived);
|
|
||||||
break;
|
|
||||||
case WStype_BIN:
|
|
||||||
processReceivedBytes(payload, length);
|
|
||||||
break;
|
|
||||||
case WStype_PONG:
|
|
||||||
case WStype_PING:
|
|
||||||
case WStype_ERROR:
|
|
||||||
case WStype_FRAGMENT_TEXT_START:
|
|
||||||
case WStype_FRAGMENT_BIN_START:
|
|
||||||
case WStype_FRAGMENT:
|
|
||||||
case WStype_FRAGMENT_FIN:
|
|
||||||
sendResponse(SesameEvent::UnexpectedSocketEvent);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void setup() {
|
|
||||||
Serial.begin(115200);
|
|
||||||
Serial.setDebugOutput(true);
|
|
||||||
Serial.println("[INFO] Device started");
|
|
||||||
|
|
||||||
//markAllKeysUnused();
|
|
||||||
//Serial.println("[WARN] All keys reset");
|
|
||||||
|
|
||||||
ESP32PWM::allocateTimer(pwmTimer);
|
|
||||||
servo.setPeriodHertz(servoFrequency);
|
|
||||||
servo.attach(servoPin);
|
|
||||||
releaseButton();
|
|
||||||
Serial.println("[INFO] Servo configured");
|
|
||||||
|
|
||||||
Serial.printf("[INFO] Key security: %d bit (%d byte keys)\n", KEY_STRENGTH, KEY_BYTE_COUNT);
|
|
||||||
nextKeyIndex = indexOfNextUsableKey();
|
|
||||||
uint8_t percentage = (KEY_COUNT - nextKeyIndex) * 100 / KEY_COUNT;
|
|
||||||
Serial.printf("[INFO] %d of %d keys remaining (%d %%)\n", KEY_COUNT - nextKeyIndex, KEY_COUNT, percentage);
|
|
||||||
Serial.printf("[INFO] Connecting to WiFi '%s'\n", WIFI_SSID);
|
|
||||||
WiFiMulti.addAP(WIFI_SSID, WIFI_PWD);
|
|
||||||
|
|
||||||
while(WiFiMulti.run() != WL_CONNECTED) {
|
|
||||||
delay(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
Serial.println("[INFO] WiFi connected");
|
|
||||||
|
|
||||||
#ifdef USE_SSL
|
|
||||||
Serial.printf("[INFO] Opening SSL socket %s%s on port %d\n", SERVER_URL, SERVER_PATH, SERVER_PORT);
|
|
||||||
webSocket.beginSSL(SERVER_URL, SERVER_PORT, SERVER_PATH);
|
|
||||||
#else
|
|
||||||
Serial.printf("[INFO] Opening insecure socket %s%s on port %d\n", SERVER_URL, SERVER_PATH, SERVER_PORT);
|
|
||||||
webSocket.begin(SERVER_URL, SERVER_PORT, SERVER_PATH);
|
|
||||||
#endif
|
|
||||||
webSocket.onEvent(webSocketEvent);
|
|
||||||
|
|
||||||
// try again every 5000 ms if connection has failed
|
|
||||||
webSocket.setReconnectInterval(SOCKET_RECONNECT_TIME);
|
|
||||||
|
|
||||||
// Creates an EEPROM fake in RAM, which is committed to flash
|
|
||||||
// The RAM data can be released by calling `EEPROM.end()`
|
|
||||||
EEPROM.begin(KEY_COUNT);
|
|
||||||
}
|
|
||||||
|
|
||||||
void loop() {
|
|
||||||
webSocket.loop();
|
|
||||||
if (shouldStartOpening) {
|
|
||||||
shouldStartOpening = false;
|
|
||||||
openingEndTime = millis() + OPENING_DURATION;
|
|
||||||
pressButton();
|
|
||||||
}
|
|
||||||
if (buttonIsPressed && millis() > openingEndTime) {
|
|
||||||
releaseButton();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Key management */
|
|
||||||
|
|
||||||
uint16_t indexOfNextUsableKey() {
|
|
||||||
// Find the highest key which was previously used
|
|
||||||
for (uint16_t keyIndex = 0; keyIndex < KEY_COUNT; keyIndex += 1) {
|
|
||||||
if (EEPROM.read(KEY_COUNT - keyIndex)) {
|
|
||||||
// The following key is the next unused
|
|
||||||
return keyIndex + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// No key previously used
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool keyWasAlreadyUsed(uint16_t keyIndex) {
|
|
||||||
return EEPROM.read(keyIndex) > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void markKeyUsed(uint16_t keyIndex) {
|
|
||||||
EEPROM.write(keyIndex, 1);
|
|
||||||
EEPROM.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void markAllKeysUnused() {
|
|
||||||
for (uint16_t keyIndex = 0; keyIndex < KEY_COUNT; keyIndex += 1) {
|
|
||||||
EEPROM.write(keyIndex, 0);
|
|
||||||
}
|
|
||||||
EEPROM.commit();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool keysAreEqual(const uint8_t* key1, const uint8_t* key2) {
|
|
||||||
uint8_t result = 0;
|
|
||||||
for (uint8_t i = 0; i < KEY_BYTE_COUNT; i += 1) {
|
|
||||||
result |= key1[i] ^ key2[i];
|
|
||||||
}
|
|
||||||
if (result) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Servo management */
|
|
||||||
|
|
||||||
void pressButton() {
|
|
||||||
servo.write(PRESS_STATE_INTERVAL);
|
|
||||||
buttonIsPressed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void releaseButton() {
|
|
||||||
servo.write(RELEASE_STATE_INTERVAL);
|
|
||||||
buttonIsPressed = false;
|
|
||||||
}
|
}
|
||||||
|
96
src/server.cpp
Normal file
96
src/server.cpp
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#include "server.h"
|
||||||
|
|
||||||
|
ServerConnection::ServerConnection(const char* url, int port, const char* path) :
|
||||||
|
url(url), port(port), path(path) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
this->key = key;
|
||||||
|
webSocket.beginSSL(url, port, path);
|
||||||
|
registerEventCallback();
|
||||||
|
reconnectAfter(reconnectTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerConnection::loop() {
|
||||||
|
webSocket.loop();
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerConnection::webSocketEventHandler(WStype_t type, uint8_t * payload, size_t length) {
|
||||||
|
switch(type) {
|
||||||
|
case WStype_DISCONNECTED:
|
||||||
|
socketIsConnected = false;
|
||||||
|
Serial.println("[INFO] Socket disconnected.");
|
||||||
|
break;
|
||||||
|
case WStype_CONNECTED:
|
||||||
|
socketIsConnected = true;
|
||||||
|
webSocket.sendTXT(key);
|
||||||
|
Serial.printf("[INFO] Socket connected to url: %s\n", payload);
|
||||||
|
break;
|
||||||
|
case WStype_TEXT:
|
||||||
|
sendFailureResponse(SesameEvent::TextReceived);
|
||||||
|
break;
|
||||||
|
case WStype_BIN:
|
||||||
|
processReceivedBytes(payload, length);
|
||||||
|
break;
|
||||||
|
case WStype_PONG:
|
||||||
|
case WStype_PING:
|
||||||
|
case WStype_ERROR:
|
||||||
|
case WStype_FRAGMENT_TEXT_START:
|
||||||
|
case WStype_FRAGMENT_BIN_START:
|
||||||
|
case WStype_FRAGMENT:
|
||||||
|
case WStype_FRAGMENT_FIN:
|
||||||
|
sendFailureResponse(SesameEvent::UnexpectedSocketEvent);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServerConnection::processReceivedBytes(uint8_t* payload, size_t length) {
|
||||||
|
if (length != AUTHENTICATED_MESSAGE_SIZE) {
|
||||||
|
sendFailureResponse(SesameEvent::InvalidPayloadSize);
|
||||||
|
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]);
|
||||||
|
}
|
37
src/servo.cpp
Normal file
37
src/servo.cpp
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
#include "servo.h"
|
||||||
|
|
||||||
|
#include <esp32-hal.h> // For `millis()`
|
||||||
|
|
||||||
|
ServoController::ServoController(int timer, int frequency, int pin)
|
||||||
|
: timer(timer), frequency(frequency), pin(pin) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
releaseButton();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServoController::pressButton() {
|
||||||
|
servo.write(pressedValue);
|
||||||
|
buttonIsPressed = true;
|
||||||
|
openingEndTime = millis() + openDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServoController::releaseButton() {
|
||||||
|
servo.write(releasedValue);
|
||||||
|
buttonIsPressed = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ServoController::loop() {
|
||||||
|
if (buttonIsPressed && millis() > openingEndTime) {
|
||||||
|
releaseButton();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user