WIP
This commit is contained in:
parent
8fdc409a39
commit
138edf9c52
5
firmware/components/protocol/CMakeLists.txt
Normal file
5
firmware/components/protocol/CMakeLists.txt
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
idf_component_register(
|
||||||
|
SRCS "protocol_interface.c"
|
||||||
|
INCLUDE_DIRS "."
|
||||||
|
REQUIRES alarms
|
||||||
|
)
|
244
firmware/components/protocol/protocol_interface.c
Normal file
244
firmware/components/protocol/protocol_interface.c
Normal file
@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Copyright (c) Camden Dixie O'Brien
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "protocol_interface.h"
|
||||||
|
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "lwip/sockets.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#define TAG "Protocol interface"
|
||||||
|
|
||||||
|
#define PORT 3498
|
||||||
|
|
||||||
|
static CommandHandler handlers[COMMAND_INSTRUCTION_COUNT];
|
||||||
|
|
||||||
|
static Message command;
|
||||||
|
static Message response;
|
||||||
|
|
||||||
|
static char string_buffer[CONFIG_MESSAGE_STRING_BUFFER_SIZE];
|
||||||
|
static char *string_buffer_free_ptr;
|
||||||
|
|
||||||
|
static bool receive(int socket, unsigned n, char *p)
|
||||||
|
{
|
||||||
|
int len;
|
||||||
|
do {
|
||||||
|
len = recv(socket, p, n, 0);
|
||||||
|
if (len < 0) {
|
||||||
|
ESP_LOGE(TAG, "Error reading from socket: %d", errno);
|
||||||
|
return false;
|
||||||
|
} else if (len == 0) {
|
||||||
|
ESP_LOGI(TAG, "Connection closed");
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
n -= len;
|
||||||
|
p += len;
|
||||||
|
}
|
||||||
|
} while (n > 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool receive_param(int socket, unsigned i)
|
||||||
|
{
|
||||||
|
MessageParam *param = &command.params[i];
|
||||||
|
|
||||||
|
uint8_t type_buf;
|
||||||
|
if (!receive(socket, 1, &type_buf))
|
||||||
|
return false;
|
||||||
|
// TODO handle invalid param type
|
||||||
|
param->type = (MessageParamType)type_buf;
|
||||||
|
|
||||||
|
uint8_t len, alarm_buf[3];
|
||||||
|
switch (param->type) {
|
||||||
|
case MESSAGE_PARAM_TYPE_U8:
|
||||||
|
if (!receive(socket, 1, (char *)¶m->u8))
|
||||||
|
return false;
|
||||||
|
break;
|
||||||
|
case MESSAGE_PARAM_TYPE_U16:
|
||||||
|
if (!receive(socket, 2, (char *)¶m->u16))
|
||||||
|
return false;
|
||||||
|
param->u16 = ntohs(param->u16);
|
||||||
|
break;
|
||||||
|
case MESSAGE_PARAM_TYPE_U32:
|
||||||
|
if (!receive(socket, 4, (char *)¶m->u32))
|
||||||
|
return false;
|
||||||
|
param->u32 = ntohl(param->u32);
|
||||||
|
break;
|
||||||
|
case MESSAGE_PARAM_TYPE_U64:
|
||||||
|
if (!receive(socket, 8, (char *)¶m->u64))
|
||||||
|
return false;
|
||||||
|
param->u64 = ntohll(param->u64);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MESSAGE_PARAM_TYPE_STRING:
|
||||||
|
if (!receive(socket, 1, &len))
|
||||||
|
return false;
|
||||||
|
char *buf = alloc_message_string(len);
|
||||||
|
// TODO handle out-of-memory
|
||||||
|
if (!receive(socket, len, buf))
|
||||||
|
return false;
|
||||||
|
param->string = buf;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case MESSAGE_PARAM_TYPE_ALARM:
|
||||||
|
if (!receive(socket, 3, alarm_buf))
|
||||||
|
return false;
|
||||||
|
param->alarm.time.hour = alarm_buf[0];
|
||||||
|
param->alarm.time.minute = alarm_buf[1];
|
||||||
|
for (unsigned i = 0; i < WEEK_DAY_COUNT; ++i)
|
||||||
|
param->alarm.days[i] = (alarm_buf[2] >> i) & 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool receive_command(int socket)
|
||||||
|
{
|
||||||
|
char header_buf[2];
|
||||||
|
if (!receive(socket, 2, header_buf))
|
||||||
|
return false;
|
||||||
|
// TODO handle invalid instruction
|
||||||
|
command.instruction = (CommandInstruction)header_buf[0];
|
||||||
|
command.param_count = (uint8_t)header_buf[1];
|
||||||
|
|
||||||
|
for (unsigned i = 0; i < command.param_count; ++i) {
|
||||||
|
if (!receive_param(socket, i))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void send_response(int socket)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
static void drop_message_strings(void)
|
||||||
|
{
|
||||||
|
string_buffer_free_ptr = string_buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handle_command(void)
|
||||||
|
{
|
||||||
|
const CommandHandler handler = handlers[command.instruction];
|
||||||
|
if (handler == NULL) {
|
||||||
|
ESP_LOGW(
|
||||||
|
TAG, "Unhandled command, instruction: %u", command.instruction);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ResponseStatus status = handler(&command, &response);
|
||||||
|
if (status != RESPONSE_STATUS_OK) {
|
||||||
|
response.status = status;
|
||||||
|
response.param_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
send_response();
|
||||||
|
drop_message_strings();
|
||||||
|
}
|
||||||
|
|
||||||
|
void server_task(void *arg)
|
||||||
|
{
|
||||||
|
int status;
|
||||||
|
|
||||||
|
// Create listening socket
|
||||||
|
const int listen_socket = socket(AF_INET6, SOCK_STREAM, IPPROTO_IPV6);
|
||||||
|
if (listen_socket < 0) {
|
||||||
|
ESP_LOGE(TAG, "Error creating socket: %d", errno);
|
||||||
|
goto error_no_cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configure listening socket
|
||||||
|
int opt = 1;
|
||||||
|
setsockopt(listen_socket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
|
||||||
|
|
||||||
|
// Bind socket to port (any address)
|
||||||
|
struct sockaddr_storage server_addr;
|
||||||
|
struct sockaddr_in6 *server_addr_ip6
|
||||||
|
= (struct sockaddr_in6 *)&server_addr;
|
||||||
|
memset(
|
||||||
|
&server_addr_ip6->sin6_addr.un, 0,
|
||||||
|
sizeof(server_addr_ip6->sin6_addr.un));
|
||||||
|
server_addr_ip6->sin6_family = AF_INET6;
|
||||||
|
server_addr_ip6->sin6_port = htons(PORT);
|
||||||
|
status = bind(
|
||||||
|
listen_socket, (struct sockaddr *)&server_addr, sizeof(server_addr));
|
||||||
|
if (status != 0) {
|
||||||
|
ESP_LOGE(TAG, "Error binding socket: %d", errno);
|
||||||
|
goto error_cleanup_socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen on socket
|
||||||
|
status = listen(listen_socket, 1);
|
||||||
|
if (status != 0) {
|
||||||
|
ESP_LOGE(TAG, "Error listening on socket: %d", errno);
|
||||||
|
goto error_cleanup_socket;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
struct sockaddr_storage client_addr;
|
||||||
|
socklen_t addr_len = sizeof(client_addr);
|
||||||
|
|
||||||
|
// Accept incoming connection
|
||||||
|
int socket = accept(
|
||||||
|
listen_socket, (struct sockaddr *)&client_addr, &addr_len);
|
||||||
|
if (socket < 0) {
|
||||||
|
ESP_LOGE(TAG, "Error accepting connection: %d", errno);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Log client address, run command-response loop until
|
||||||
|
// connection closed.
|
||||||
|
const char *addr_str
|
||||||
|
= ((struct sockaddr_in6 *)&client_addr)->sin6_addr;
|
||||||
|
ESP_LOGI(TAG, "Accepted connection from %s", addr_str);
|
||||||
|
while (1) {
|
||||||
|
if (!receive_command())
|
||||||
|
break;
|
||||||
|
handle_command();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup connection
|
||||||
|
shutdown(socket, 0);
|
||||||
|
close(socket);
|
||||||
|
}
|
||||||
|
|
||||||
|
error_cleanup_socket:
|
||||||
|
close(listen_socket);
|
||||||
|
error_no_cleanup:
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void protocol_interface_init(void)
|
||||||
|
{
|
||||||
|
memset(handlers, 0, sizeof(handlers));
|
||||||
|
string_buffer_free = string_buffer;
|
||||||
|
memset(command, 0, sizeof(command));
|
||||||
|
memset(response, 0, sizeof(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_command_handler(
|
||||||
|
CommandInstruction instruction, CommandHandler handler)
|
||||||
|
{
|
||||||
|
if (instruction < COMMAND_INSTRUCTION_COUNT) {
|
||||||
|
if (handlers[instruction] != NULL)
|
||||||
|
ESP_LOGW(TAG, "Handler #%u overwritten", instruction);
|
||||||
|
handlers[instruction] = handler;
|
||||||
|
} else {
|
||||||
|
ESP_LOGE(TAG, "Invalid instruction passed to %s()", __func__);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char *alloc_message_string(unsigned length)
|
||||||
|
{
|
||||||
|
if (string_buffer_free_ptr + length + 1
|
||||||
|
< string_buffer + CONFIG_MESSAGE_STRING_BUFFER_SIZE) {
|
||||||
|
char *s = string_buffer_free_ptr;
|
||||||
|
string_buffer_free_ptr += length + 1;
|
||||||
|
s[length] = '\0';
|
||||||
|
return s;
|
||||||
|
} else {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
}
|
43
firmware/components/protocol/protocol_interface.h
Normal file
43
firmware/components/protocol/protocol_interface.h
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Copyright (c) Camden Dixie O'Brien
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PROTOCOL_INTERFACE_H
|
||||||
|
#define PROTOCOL_INTERFACE_H
|
||||||
|
|
||||||
|
#include "protocol_messages.h"
|
||||||
|
|
||||||
|
typedef ResponseStatus (*CommandHandler)(
|
||||||
|
const Message *command, Message *response_out);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the protocol interface and start listening for commands.
|
||||||
|
*/
|
||||||
|
void protocol_interface_init(void);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the handler for commands with a given intruction.
|
||||||
|
*
|
||||||
|
* The handler will be invoked when/if a command with the specified
|
||||||
|
* instruction is received. The response status should be returned,
|
||||||
|
* and upon success the response should be populated. Strings in the
|
||||||
|
* response must be allocated with alloc_message_string(); these will
|
||||||
|
* be freed by the interface when the response has been sent.
|
||||||
|
*
|
||||||
|
* Only a single handler can be set for a given instruction; if
|
||||||
|
* multiple are added then only the one added last will be used.
|
||||||
|
*/
|
||||||
|
void set_command_handler(
|
||||||
|
CommandInstruction instruction, CommandHandler handler);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allocate a buffer for a string with the given length (in bytes),
|
||||||
|
* returning a pointer to it or NULL if space has ran out.
|
||||||
|
*
|
||||||
|
* The actual space allocated will be length + 1 bytes, and the last
|
||||||
|
* byte in this buffer will be set to the null character.
|
||||||
|
*/
|
||||||
|
char *alloc_message_string(unsigned length);
|
||||||
|
|
||||||
|
#endif
|
69
firmware/components/protocol/protocol_messages.h
Normal file
69
firmware/components/protocol/protocol_messages.h
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
* Copyright (c) Camden Dixie O'Brien
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef PROTOCOL_MESSAGES_H
|
||||||
|
#define PROTOCOL_MESSAGES_H
|
||||||
|
|
||||||
|
#include "alarm_types.h"
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#define MESSAGE_MAX_PARAMS 16
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
MESSAGE_PARAM_TYPE_U8 = 0,
|
||||||
|
MESSAGE_PARAM_TYPE_U16 = 1,
|
||||||
|
MESSAGE_PARAM_TYPE_U32 = 2,
|
||||||
|
MESSAGE_PARAM_TYPE_U64 = 3,
|
||||||
|
MESSAGE_PARAM_TYPE_STRING = 4,
|
||||||
|
MESSAGE_PARAM_TYPE_ALARM = 5,
|
||||||
|
|
||||||
|
MESSAGE_PARAM_TYPE_COUNT,
|
||||||
|
} MessageParamType;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
MessageParamType type;
|
||||||
|
union {
|
||||||
|
uint8_t u8;
|
||||||
|
uint16_t u16;
|
||||||
|
uint32_t u32;
|
||||||
|
uint64_t u64;
|
||||||
|
const char *string;
|
||||||
|
Alarm alarm;
|
||||||
|
};
|
||||||
|
} MessageParam;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
COMMAND_INSTRUCTION_GET_VERSION = 0,
|
||||||
|
COMMAND_INSTRUCTION_SET_TIME = 1,
|
||||||
|
COMMAND_INSTRUCTION_LIST_ALARMS = 2,
|
||||||
|
COMMAND_INSTRUCTION_ADD_ALARM = 3,
|
||||||
|
COMMAND_INSTRUCTION_REMOVE_ALARM = 4,
|
||||||
|
COMMAND_INSTRUCTION_LIST_SETTINGS = 5,
|
||||||
|
COMMAND_INSTRUCTION_SET_SETTING = 6,
|
||||||
|
|
||||||
|
COMMAND_INSTRUCTION_COUNT,
|
||||||
|
} CommandInstruction;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
RESPONSE_STATUS_OK = 0,
|
||||||
|
RESPONSE_STATUS_BUSY = 1,
|
||||||
|
RESPONSE_STATUS_INVALID_COMMAND = 2,
|
||||||
|
RESPONSE_STATUS_INTERNAL_ERROR = 3,
|
||||||
|
RESPONSE_STATUS_OUT_OF_SPACE = 4,
|
||||||
|
|
||||||
|
RESPONSE_STATUS_COUNT,
|
||||||
|
} ResponseStatus;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
union {
|
||||||
|
CommandInstruction instruction;
|
||||||
|
ResponseStatus status;
|
||||||
|
};
|
||||||
|
uint8_t param_count;
|
||||||
|
MessageParam params[MESSAGE_MAX_PARAMS];
|
||||||
|
} Message;
|
||||||
|
|
||||||
|
#endif
|
Loading…
x
Reference in New Issue
Block a user