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