|
@@ -0,0 +1,431 @@
|
|
|
+#include "esp_system.h"
|
|
|
+#include "esp_log.h"
|
|
|
+#include "improv.h"
|
|
|
+#include "tools.h"
|
|
|
+#include "string.h"
|
|
|
+static ImprovCommandStruct_t last_command;
|
|
|
+
|
|
|
+static callback_table_t callbacks[] = {
|
|
|
+ {IMPROV_CMD_UNKNOWN, NULL},
|
|
|
+ {IMPROV_CMD_WIFI_SETTINGS, NULL},
|
|
|
+ {IMPROV_CMD_GET_CURRENT_STATE, NULL},
|
|
|
+ {IMPROV_CMD_GET_DEVICE_INFO, NULL},
|
|
|
+ {IMPROV_CMD_GET_WIFI_NETWORKS, NULL},
|
|
|
+ {IMPROV_CMD_BAD_CHECKSUM, NULL},
|
|
|
+ {-1, NULL}};
|
|
|
+const char *improv_get_error_desc(ImprovError_t error)
|
|
|
+{
|
|
|
+ switch (error)
|
|
|
+ {
|
|
|
+ ENUM_TO_STRING(IMPROV_ERROR_NONE)
|
|
|
+ ENUM_TO_STRING(IMPROV_ERROR_INVALID_RPC)
|
|
|
+ ENUM_TO_STRING(IMPROV_ERROR_UNKNOWN_RPC)
|
|
|
+ ENUM_TO_STRING(IMPROV_ERROR_UNABLE_TO_CONNECT)
|
|
|
+ ENUM_TO_STRING(IMPROV_ERROR_NOT_AUTHORIZED)
|
|
|
+ ENUM_TO_STRING(IMPROV_ERROR_UNKNOWN)
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+}
|
|
|
+const char *improv_get_command_desc(ImprovCommand_t command)
|
|
|
+{
|
|
|
+ switch (command)
|
|
|
+ {
|
|
|
+ ENUM_TO_STRING(IMPROV_CMD_UNKNOWN)
|
|
|
+ ENUM_TO_STRING(IMPROV_CMD_WIFI_SETTINGS)
|
|
|
+ ENUM_TO_STRING(IMPROV_CMD_GET_CURRENT_STATE)
|
|
|
+ ENUM_TO_STRING(IMPROV_CMD_GET_DEVICE_INFO)
|
|
|
+ ENUM_TO_STRING(IMPROV_CMD_GET_WIFI_NETWORKS)
|
|
|
+ ENUM_TO_STRING(IMPROV_CMD_BAD_CHECKSUM)
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+};
|
|
|
+static improv_send_callback_t send_callback = NULL;
|
|
|
+const uint8_t improv_prefix[] = {'I', 'M', 'P', 'R', 'O', 'V', IMPROV_SERIAL_VERSION};
|
|
|
+typedef struct __attribute__((__packed__))
|
|
|
+{
|
|
|
+ uint8_t prefix[6];
|
|
|
+ uint8_t version;
|
|
|
+ uint8_t packet_type;
|
|
|
+ uint8_t data_len;
|
|
|
+} improv_packet_t;
|
|
|
+#define PACKET_CHECKSUM_SIZE sizeof(uint8_t)
|
|
|
+#define PACKET_PAYLOAD(packet) ((uint8_t *)packet) + sizeof(improv_packet_t)
|
|
|
+
|
|
|
+static ImprovAPListStruct_t *ap_list = NULL;
|
|
|
+static size_t ap_list_size = 0;
|
|
|
+static size_t ap_list_actual = 0;
|
|
|
+void improv_wifi_list_free()
|
|
|
+{
|
|
|
+ ap_list_actual = 0;
|
|
|
+ ImprovAPListStruct_t *current = ap_list;
|
|
|
+ for (int i = 0; i < ap_list_actual; i++)
|
|
|
+ {
|
|
|
+ if (!current)
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ FREE_AND_NULL(current->rssi);
|
|
|
+ FREE_AND_NULL(current->ssid);
|
|
|
+ FREE_AND_NULL(current->auth_req);
|
|
|
+ current++;
|
|
|
+ }
|
|
|
+ FREE_AND_NULL(ap_list);
|
|
|
+}
|
|
|
+bool improv_wifi_list_allocate(size_t num_entries)
|
|
|
+{
|
|
|
+ improv_wifi_list_free();
|
|
|
+ ap_list = malloc_init_external(num_entries * sizeof(ImprovAPListStruct_t) + 1); // last byte will always be null
|
|
|
+ ap_list_size = num_entries;
|
|
|
+ ap_list_actual = 0;
|
|
|
+ return ap_list != NULL;
|
|
|
+}
|
|
|
+
|
|
|
+bool improv_wifi_list_add(const char *ssid, int8_t rssi, bool auth_req)
|
|
|
+{
|
|
|
+ const size_t yes_no_length = 4;
|
|
|
+ ImprovAPListStruct_t *current = ap_list + ap_list_actual;
|
|
|
+ if (ap_list_actual > ap_list_size || !current)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ current->ssid = strdup_psram(ssid);
|
|
|
+ size_t length = snprintf(NULL, 0, "%02d", rssi) + 1;
|
|
|
+ current->auth_req = malloc_init_external(yes_no_length); // enough for YES/NO to fit
|
|
|
+ current->rssi = (char *)malloc_init_external(length);
|
|
|
+ if (!current->rssi || !current->auth_req)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ snprintf(current->rssi, length, "%02d", rssi);
|
|
|
+ snprintf(current->auth_req, yes_no_length, "%s", auth_req ? "YES" : "NO");
|
|
|
+ ap_list_actual++;
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
+void improv_parse_data(ImprovCommandStruct_t *improv_command, const uint8_t *data, size_t length, bool check_checksum)
|
|
|
+{
|
|
|
+
|
|
|
+ ImprovCommand_t command = (ImprovCommand_t)data[0];
|
|
|
+ uint8_t data_length = data[1];
|
|
|
+
|
|
|
+ if (data_length != length - 2 - check_checksum)
|
|
|
+ {
|
|
|
+ improv_command->command = IMPROV_CMD_UNKNOWN;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (check_checksum)
|
|
|
+ {
|
|
|
+ uint8_t checksum = data[length - 1];
|
|
|
+
|
|
|
+ uint32_t calculated_checksum = 0;
|
|
|
+ for (uint8_t i = 0; i < length - 1; i++)
|
|
|
+ {
|
|
|
+ calculated_checksum += data[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ if ((uint8_t)calculated_checksum != checksum)
|
|
|
+ {
|
|
|
+ improv_command->command = IMPROV_CMD_BAD_CHECKSUM;
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (command == IMPROV_CMD_WIFI_SETTINGS)
|
|
|
+ {
|
|
|
+ uint8_t ssid_length = data[2];
|
|
|
+ uint8_t ssid_start = 3;
|
|
|
+ size_t ssid_end = ssid_start + ssid_length;
|
|
|
+
|
|
|
+ uint8_t pass_length = data[ssid_end];
|
|
|
+ size_t pass_start = ssid_end + 1;
|
|
|
+ size_t pass_end = pass_start + pass_length;
|
|
|
+
|
|
|
+ improv_command->ssid = malloc(ssid_length + 1);
|
|
|
+ memset(improv_command->ssid, 0x00, ssid_length + 1);
|
|
|
+ memcpy(improv_command->ssid, &data[ssid_start], ssid_length);
|
|
|
+ improv_command->password = NULL;
|
|
|
+ if (pass_length > 0)
|
|
|
+ {
|
|
|
+ improv_command->password = malloc(pass_length + 1);
|
|
|
+ memset(improv_command->password, 0x00, pass_length + 1);
|
|
|
+ memcpy(improv_command->password, &data[pass_start], pass_length);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ improv_command->command = command;
|
|
|
+}
|
|
|
+
|
|
|
+bool improv_parse_serial_byte(size_t position, uint8_t byte, const uint8_t *buffer,
|
|
|
+ improv_command_callback_t callback, on_error_callback_t on_error)
|
|
|
+{
|
|
|
+ ImprovCommandStruct_t command = {0};
|
|
|
+ if (position < 7)
|
|
|
+ return byte == improv_prefix[position];
|
|
|
+ if (position <= 8)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ uint8_t command_type = buffer[7];
|
|
|
+ uint8_t data_len = buffer[8];
|
|
|
+
|
|
|
+ if (position <= 8 + data_len)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ if (position == 8 + data_len + 1)
|
|
|
+ {
|
|
|
+ uint8_t checksum = 0x00;
|
|
|
+ for (size_t i = 0; i < position; i++)
|
|
|
+ checksum += buffer[i];
|
|
|
+
|
|
|
+ if (checksum != byte)
|
|
|
+ {
|
|
|
+ on_error(IMPROV_ERROR_INVALID_RPC);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (command_type == IMPROV_PACKET_TYPE_RPC)
|
|
|
+ {
|
|
|
+
|
|
|
+ improv_parse_data(&command, &buffer[9], data_len, false);
|
|
|
+ callback(&command);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+void improv_set_send_callback(improv_send_callback_t callback)
|
|
|
+{
|
|
|
+ send_callback = callback;
|
|
|
+}
|
|
|
+
|
|
|
+bool improv_set_callback(ImprovCommand_t command, improv_command_callback_t callback)
|
|
|
+{
|
|
|
+ callback_table_t *pCt = &callbacks;
|
|
|
+ while (pCt->index > -1)
|
|
|
+ {
|
|
|
+ if (pCt->index == command)
|
|
|
+ {
|
|
|
+ pCt->callback = callback;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+bool improv_handle_callback(ImprovCommandStruct_t *command)
|
|
|
+{
|
|
|
+ const callback_table_t *pCt = &callbacks;
|
|
|
+ while (pCt->index > -1)
|
|
|
+ {
|
|
|
+ if (pCt->index == command->command)
|
|
|
+ {
|
|
|
+ return pCt->callback && pCt->callback(command);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+}
|
|
|
+bool improv_send_packet(uint8_t *packet, size_t msg_len)
|
|
|
+{
|
|
|
+ bool result = false;
|
|
|
+ if (send_callback && packet && msg_len > 0)
|
|
|
+ {
|
|
|
+ result = send_callback(packet, msg_len);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+}
|
|
|
+bool improv_send_byte(ImprovSerialType_t packet_type, uint8_t data)
|
|
|
+{
|
|
|
+ size_t msg_len;
|
|
|
+ uint8_t *packet = improv_build_response(packet_type, (const char *)&data, 1, &msg_len);
|
|
|
+ bool result = improv_send_packet(packet, msg_len);
|
|
|
+ FREE_AND_NULL(packet);
|
|
|
+ return result;
|
|
|
+}
|
|
|
+
|
|
|
+bool improv_send_current_state(ImprovState_t state)
|
|
|
+{
|
|
|
+ return improv_send_byte(IMPROV_PACKET_TYPE_CURRENT_STATE, (uint8_t)state);
|
|
|
+}
|
|
|
+bool improv_send_error(ImprovError_t error)
|
|
|
+{
|
|
|
+ return improv_send_byte(IMPROV_PACKET_TYPE_ERROR_STATE, (uint8_t)error);
|
|
|
+}
|
|
|
+size_t improv_wifi_get_wifi_list_count(){
|
|
|
+ return ap_list_actual;
|
|
|
+}
|
|
|
+bool improv_wifi_list_send()
|
|
|
+{
|
|
|
+ size_t msglen = 0;
|
|
|
+ bool result = true;
|
|
|
+ if (ap_list_actual == 0)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ for (int i = 0; i < ap_list_actual && result; i++)
|
|
|
+ {
|
|
|
+ uint8_t *packet = improv_build_rpc_response(IMPROV_CMD_GET_WIFI_NETWORKS,(const char **) &ap_list[i], IMPROV_AP_STRUCT_NUM_STR, &msglen);
|
|
|
+ result = improv_send_packet(packet, msglen);
|
|
|
+ FREE_AND_NULL(packet);
|
|
|
+ }
|
|
|
+ uint8_t *packet = improv_build_rpc_response(IMPROV_CMD_GET_WIFI_NETWORKS, NULL, 0, &msglen);
|
|
|
+ result = improv_send_packet(packet, msglen);
|
|
|
+ FREE_AND_NULL(packet);
|
|
|
+ return result;
|
|
|
+}
|
|
|
+bool improv_send_device_url(ImprovCommand_t from_command, const char *url)
|
|
|
+{
|
|
|
+ size_t msglen = 0;
|
|
|
+ uint8_t *packet = NULL;
|
|
|
+ bool result = false;
|
|
|
+ if (url && strlen(url))
|
|
|
+ {
|
|
|
+ packet = improv_build_rpc_response(from_command, &url, 1, &msglen);
|
|
|
+ if (!packet)
|
|
|
+ return false;
|
|
|
+ result = improv_send_packet(packet, msglen);
|
|
|
+ FREE_AND_NULL(packet);
|
|
|
+ }
|
|
|
+ packet = improv_build_rpc_response(from_command, "", 0, &msglen);
|
|
|
+ if (!packet)
|
|
|
+ return false;
|
|
|
+ result = improv_send_packet(packet, msglen);
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+ return result;
|
|
|
+}
|
|
|
+bool improv_send_device_info(const char *firmware_name, const char *firmware_version, const char *hardware_chip_variant, const char *device_name)
|
|
|
+{
|
|
|
+ ImprovDeviceInfoStruct_t device_info;
|
|
|
+ size_t msglen = 0;
|
|
|
+ device_info.device_name = device_name;
|
|
|
+ device_info.firmware_name = firmware_name;
|
|
|
+ device_info.firmware_version = firmware_version;
|
|
|
+ device_info.hardware_chip_variant = hardware_chip_variant;
|
|
|
+ device_info.nullptr = NULL;
|
|
|
+ uint8_t *packet = improv_build_rpc_response(IMPROV_CMD_GET_DEVICE_INFO, &device_info, IMPROV_DEVICE_INFO_NUM_STRINGS, &msglen);
|
|
|
+ if (!packet)
|
|
|
+ return false;
|
|
|
+ bool result = improv_send_packet(packet, msglen);
|
|
|
+ FREE_AND_NULL(packet);
|
|
|
+ return true;
|
|
|
+}
|
|
|
+bool parse_improv_serial_line(const uint8_t *buffer)
|
|
|
+{
|
|
|
+ const uint8_t *b = buffer;
|
|
|
+ const uint8_t *p = improv_prefix;
|
|
|
+ const uint8_t *data = NULL;
|
|
|
+ uint8_t checksum = 0x00;
|
|
|
+ uint8_t rec_checksum = 0x00;
|
|
|
+
|
|
|
+ while (*p != '\0' && *b != '\0')
|
|
|
+ {
|
|
|
+ // check if line prefix matches the standard
|
|
|
+ if (*p++ != *b++)
|
|
|
+ {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ uint8_t command_type = *p++;
|
|
|
+ if (command_type == 0)
|
|
|
+ return false;
|
|
|
+ uint8_t data_len = *p++;
|
|
|
+ data = p;
|
|
|
+ rec_checksum = buffer[sizeof(improv_prefix) + data_len];
|
|
|
+ for (size_t i = 0; i < sizeof(improv_prefix) + data_len; i++)
|
|
|
+ {
|
|
|
+ checksum += buffer[i];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (checksum != rec_checksum)
|
|
|
+ {
|
|
|
+ improv_send_error(IMPROV_ERROR_INVALID_RPC);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (command_type == IMPROV_PACKET_TYPE_RPC)
|
|
|
+ {
|
|
|
+ improv_parse_data(&last_command, &data, data_len, false);
|
|
|
+ return improv_handle_callback(&last_command);
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+// Improv packet format
|
|
|
+// 1-6 Header will equal IMPROV
|
|
|
+// 7 Version CURRENT VERSION = 1
|
|
|
+// 8 Type (see below)
|
|
|
+// 9 Length
|
|
|
+// 10...X Data
|
|
|
+// X + 10 Checksum
|
|
|
+improv_packet_t *improv_alloc_prefix(size_t data_len, ImprovSerialType_t packet_type, size_t *out_len)
|
|
|
+{
|
|
|
+ size_t buffer_len = sizeof(improv_packet_t) + data_len + 1; // one byte for checksum
|
|
|
+ if (out_len)
|
|
|
+ {
|
|
|
+ *out_len = buffer_len;
|
|
|
+ }
|
|
|
+ improv_packet_t *out = (improv_packet_t *)malloc_init_external(buffer_len + 1);
|
|
|
+ memcpy(out, improv_prefix, sizeof(improv_prefix));
|
|
|
+ out->packet_type = (uint8_t)packet_type;
|
|
|
+ out->data_len = (uint8_t)data_len;
|
|
|
+ return out;
|
|
|
+}
|
|
|
+uint8_t improv_set_checksum(improv_packet_t *data, size_t buffer_len)
|
|
|
+{
|
|
|
+ uint32_t calculated_checksum = 0;
|
|
|
+ for (int b = 0; b < buffer_len - 1; b++)
|
|
|
+ {
|
|
|
+ calculated_checksum += ((uint8_t *)data)[b];
|
|
|
+ }
|
|
|
+ calculated_checksum = calculated_checksum & 0xFF;
|
|
|
+ ((uint8_t *)data)[buffer_len - 1] = (uint8_t)calculated_checksum;
|
|
|
+ return calculated_checksum;
|
|
|
+}
|
|
|
+uint8_t *improv_build_response(ImprovSerialType_t packet_type, const char *datum, size_t len, size_t *out_len)
|
|
|
+{
|
|
|
+ size_t buffer_len = 0;
|
|
|
+ improv_packet_t *improv_packet = improv_alloc_prefix(len, packet_type, &buffer_len);
|
|
|
+ if (out_len)
|
|
|
+ {
|
|
|
+ *out_len = buffer_len;
|
|
|
+ }
|
|
|
+ uint8_t *p = PACKET_PAYLOAD(improv_packet);
|
|
|
+ for (int i = 0; i < len; i++)
|
|
|
+ {
|
|
|
+ *p++ = datum[i]; // string 1
|
|
|
+ }
|
|
|
+ improv_set_checksum(improv_packet, buffer_len);
|
|
|
+ return (uint8_t *)improv_packet;
|
|
|
+}
|
|
|
+uint8_t *improv_build_rpc_response(ImprovCommand_t command, const char **results, size_t num_strings, size_t *out_len)
|
|
|
+{
|
|
|
+ size_t buffer_len = 0;
|
|
|
+ size_t total_string_len = 0;
|
|
|
+ size_t string_buffer_len = 0;
|
|
|
+ for (int i = 0; i < num_strings && (results[i] && (results[i])[0] != '\0'); i++)
|
|
|
+ {
|
|
|
+ size_t l = strlen(results[i]);
|
|
|
+ total_string_len += l;
|
|
|
+ string_buffer_len += l + 1; // length of the string plus byte for length
|
|
|
+ }
|
|
|
+
|
|
|
+ improv_packet_t *improv_packet = improv_alloc_prefix(string_buffer_len + 2, IMPROV_PACKET_TYPE_RPC_RESPONSE, &buffer_len); // 2 bytes for command and length of all strings
|
|
|
+ if (out_len)
|
|
|
+ {
|
|
|
+ *out_len = buffer_len;
|
|
|
+ }
|
|
|
+ uint8_t *p = PACKET_PAYLOAD(improv_packet);
|
|
|
+ *p++ = (uint8_t)command; // command being responded to
|
|
|
+ *p++ = (uint8_t)string_buffer_len; //
|
|
|
+ for (int i = 0; i < num_strings && results[i]; i++)
|
|
|
+ {
|
|
|
+ uint8_t curlel = (uint8_t)strlen(results[i]);
|
|
|
+ *p++ = curlel;
|
|
|
+ memcpy(p, results[i], curlel);
|
|
|
+ p += curlel;
|
|
|
+ }
|
|
|
+ improv_set_checksum(improv_packet, buffer_len);
|
|
|
+ return (uint8_t *)improv_packet;
|
|
|
+}
|