From f9ab06f30e4bb4a31e19c5cff256c4f54038a6d4 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Fri, 4 Aug 2023 23:03:21 +0200 Subject: [PATCH 001/126] clear log when toggled --- contrib/data/www/index.html | 58 +++++++++++++++---------------------- 1 file changed, 24 insertions(+), 34 deletions(-) diff --git a/contrib/data/www/index.html b/contrib/data/www/index.html index 9d1e844f..e09eb0c7 100644 --- a/contrib/data/www/index.html +++ b/contrib/data/www/index.html @@ -1194,6 +1194,7 @@

File Viewer

this.handleModeChange = this.handleModeChange.bind(this); this.hexToStr = this.hexToStr.bind(this); this.littleEndianToNum = this.littleEndianToNum.bind(this); + this.messagesEnd = React.createRef(); } componentDidMount() { @@ -1208,6 +1209,15 @@

File Viewer

this.deinitSSE(); } } + if (this.state.enabled && this.state.messages !== prevState.messages) { + this.messagesEnd.current.scrollIntoView({ behavior: 'smooth' }); + } + } + + appendMessage = (text) => { + this.setState(prevState => ({ + messages: [...prevState.messages, text] + })); } hexToStr(hex) { @@ -1242,11 +1252,8 @@

File Viewer

initSSE = () => { const eventSource = new EventSource('/api/sse'); - eventSource.onopen = e => { - console.log("Connection to server opened", e); - }; - eventSource.addEventListener('keep-alive', e => { + /* maybe later we have the box's ID from the certificate so we could show online states */ console.log("keep-alive event received:", e); }); @@ -1258,7 +1265,6 @@

File Viewer

})); }); - eventSource.addEventListener('rtnl-raw-log2', e => { const data = JSON.parse(e.data); const functionHandlers = { @@ -1266,10 +1272,7 @@

File Viewer

"15-15452": (payload) => { if (payload.length === 16) { const rearrangedPayload = payload.slice(8) + payload.slice(0, 8); - this.setState(prevState => ({ - messages: [...prevState.messages, - `UnknownTag | ${rearrangedPayload}`] - })); + this.appendMessage(`UnknownTag | ${rearrangedPayload}`); } else { console.log(`Incorrect payload length for '${payload}'. Unable to rearrange.`); } @@ -1277,10 +1280,7 @@

File Viewer

"15-16065": (payload) => { if (payload.length === 16) { const rearrangedPayload = payload.slice(8) + payload.slice(0, 8); - this.setState(prevState => ({ - messages: [...prevState.messages, - `KnownTag | ${rearrangedPayload}`] - })); + this.appendMessage(`KnownTag | ${rearrangedPayload}`); } else { console.log(`Incorrect payload length for '${payload}'. Unable to rearrange.`); } @@ -1288,10 +1288,7 @@

File Viewer

"12-15427": (payload) => { if (payload.length === 8) { const value = this.littleEndianToNum(payload); - this.setState(prevState => ({ - messages: [...prevState.messages, - `UpsideState | ${value}`] - })); + this.appendMessage(`UpsideState | ${value}`); } else { console.log(`Incorrect payload length for '${payload}'. Unable to rearrange.`); } @@ -1299,29 +1296,22 @@

File Viewer

"12-15426": (payload) => { if (payload.length === 8) { const value = this.littleEndianToNum(payload); - this.setState(prevState => ({ - messages: [...prevState.messages, - `UprightState | ${value}`] - })); + this.appendMessage(`UprightState | ${value}`); } else { console.log(`Incorrect payload length for '${payload}'. Unable to rearrange.`); } }, }; - - console.log("raw", data); - this.setState(prevState => ({ - messages: [...prevState.messages, + this.appendMessage( "Raw2 |" + " #" + data.data.sequence + " Uptime: " + data.data.uptime + - " Func: " + data.data.function_group + "-" + data.data.function + + " Func: " + data.data.function_group.toString().padStart(2, ' ') + "-" + data.data.function + " Payload: '" + data.data.field6 + "'" + - " ASCII: '" + this.hexToStr(data.data.field6) + "'"] - })); + " ASCII: '" + this.hexToStr(data.data.field6) + "'"); - const funcKey = `${data.data.function_group}-${data.data.function}`; + const funcKey = `${data.data.function_group.toString().padStart(2, ' ')}-${data.data.function}`; if (functionHandlers[funcKey]) { functionHandlers[funcKey](data.data.field6); } @@ -1331,12 +1321,10 @@

File Viewer

const data = JSON.parse(e.data); console.log("raw", data); - this.setState(prevState => ({ - messages: [...prevState.messages, + this.appendMessage( "Raw3 |" + " Datetime: " + data.data.datetime + - " Unknown: " + data.data.field2] - })); + " Unknown: " + data.data.field2); }); eventSource.onerror = e => { @@ -1358,7 +1346,8 @@

File Viewer

handleModeChange = () => { this.setState(prevState => ({ - enabled: !prevState.enabled + enabled: !prevState.enabled, + messages: [] })); }; @@ -1383,6 +1372,7 @@

RTNL-Log

{message} ))} +
)} From 6be01f1116a9f7038549b888bd6289234389ccb2 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Fri, 4 Aug 2023 23:06:49 +0200 Subject: [PATCH 002/126] add mqtt and home assistant support --- Makefile | 5 + include/home_assistant.h | 108 +++ include/macros.h | 21 + include/mqtt.h | 8 + include/net_config.h | 11 +- include/settings.h | 1 + src/cloud_request.c | 3 + .../cyclone_tcp/mqtt/mqtt_client_transport.c | 704 ++++++++++++++++++ .../cyclone_tcp/mqtt/mqtt_client_transport.h | 66 ++ src/handler_rtnl.c | 104 ++- src/home_assistant.c | 450 +++++++++++ src/main.c | 2 + src/mqtt.c | 247 +++++- src/settings.c | 2 + 14 files changed, 1687 insertions(+), 45 deletions(-) create mode 100644 include/home_assistant.h create mode 100644 include/macros.h create mode 100644 include/mqtt.h create mode 100644 src/cyclone/cyclone_tcp/mqtt/mqtt_client_transport.c create mode 100644 src/cyclone/cyclone_tcp/mqtt/mqtt_client_transport.h create mode 100644 src/home_assistant.c diff --git a/Makefile b/Makefile index bc1f3860..c96c0dd8 100644 --- a/Makefile +++ b/Makefile @@ -172,6 +172,9 @@ CYCLONE_SOURCES = \ cyclone/cyclone_tcp/http/http_common.c \ cyclone/cyclone_tcp/http/http_server.c \ cyclone/cyclone_tcp/http/http_server_misc.c \ + cyclone/cyclone_tcp/mqtt/mqtt_client.c \ + cyclone/cyclone_tcp/mqtt/mqtt_client_packet.c \ + cyclone/cyclone_tcp/mqtt/mqtt_client_misc.c \ cyclone/cyclone_ssl/tls.c \ cyclone/cyclone_ssl/tls_cipher_suites.c \ cyclone/cyclone_ssl/tls_handshake.c \ @@ -245,6 +248,7 @@ CYCLONE_SOURCES := $(filter-out \ cyclone/cyclone_tcp/http/http_server.c \ cyclone/cyclone_tcp/http/http_server_misc.c \ cyclone/cyclone_ssl/tls_certificate.c \ + cyclone/cyclone_tcp/mqtt/mqtt_client_transport.c \ , $(CYCLONE_SOURCES)) # and add modified ones @@ -252,6 +256,7 @@ CYCLONE_SOURCES += \ src/cyclone/common/debug.c \ src/cyclone/cyclone_tcp/http/http_server.c \ src/cyclone/cyclone_tcp/http/http_server_misc.c \ + src/cyclone/cyclone_tcp/mqtt/mqtt_client_transport.c \ src/cyclone/cyclone_ssl/tls_certificate.c CFLAGS += -D GPL_LICENSE_TERMS_ACCEPTED diff --git a/include/home_assistant.h b/include/home_assistant.h new file mode 100644 index 00000000..8bd353d2 --- /dev/null +++ b/include/home_assistant.h @@ -0,0 +1,108 @@ +#pragma once + +#include +#include + +#define MAX_LEN 32 +#define MAX_ENTITIES (3 * 9 + 16 * 7 + 32) + +typedef enum +{ + ha_unused = 0, + /* https://www.home-assistant.io/integrations/text.mqtt/ */ + ha_text, + /* https://www.home-assistant.io/integrations/sensor.mqtt/ */ + ha_sensor, + /* https://www.home-assistant.io/integrations/number.mqtt/ */ + ha_number, + /* https://www.home-assistant.io/integrations/button.mqtt/ */ + ha_button, + /* https://www.home-assistant.io/integrations/select.mqtt/ */ + ha_select, + /* https://www.home-assistant.io/integrations/binary_sensor.mqtt/ */ + ha_binary_sensor, + /* https://www.home-assistant.io/integrations/ha_light.mqtt/ */ + ha_light +} t_ha_device_type; + +typedef struct s_ha_entity t_ha_entity; + +struct s_ha_entity +{ + t_ha_device_type type; + + const char *name; + const char *id; + + /* used by: sensor */ + const char *unit_of_meas; + /* used by: sensor */ + const char *val_tpl; + /* used by: sensor */ + const char *dev_class; + /* used by: sensor */ + const char *state_class; + /* used by: button, number, text */ + const char *cmd_t; + /* used by: sensor, binary_sensor, number, text */ + const char *stat_t; + /* used by: light */ + const char *rgb_t; + const char *rgbw_t; + /* used by: switch, comma separated */ + const char *options; + /* used by: number */ + float min; + /* used by: number */ + float max; + /* used by: number */ + const char *mode; + /* icon */ + const char *ic; + /* entity_category */ + const char *ent_cat; + + /* used by: light */ + const char *fx_cmd_t; + /* used by: light */ + const char *fx_stat_t; + /* used by: light, comma separated */ + const char *fx_list; + + /* alternative client name */ + const char *alt_name; + + void (*received)(const t_ha_entity *, void *, const char *); + void *received_ctx; + void (*rgb_received)(const t_ha_entity *, void *, const char *); + void *rgb_received_ctx; + void (*fx_received)(const t_ha_entity *, void *, const char *); + void *fx_received_ctx; + void (*transmit)(const t_ha_entity *, void *); + void *transmit_ctx; +}; + +typedef struct +{ + char name[MAX_LEN]; + char id[MAX_LEN]; + char cu[MAX_LEN]; + char mf[MAX_LEN]; + char mdl[MAX_LEN]; + char sw[MAX_LEN]; + t_ha_entity entities[MAX_ENTITIES]; + int entitiy_count; +} t_ha_info; + +void ha_setup(); +void ha_connected(); +bool ha_loop(); +void ha_transmit_all(); +void ha_publish(); +void ha_add(t_ha_entity *entity); +void ha_addmqtt(char *json_str, const char *name, const char *value, t_ha_entity *entity, bool last); +void ha_received(char *topic, const char *payload); +void ha_transmit(const t_ha_entity *entity, const char *value); +void ha_transmit_topic(const char *stat_t, const char *value); +int ha_parse_index(const char *options, const char *message); +void ha_get_index(const char *options, int index, char *text); diff --git a/include/macros.h b/include/macros.h new file mode 100644 index 00000000..18a173d3 --- /dev/null +++ b/include/macros.h @@ -0,0 +1,21 @@ +#ifndef __MACROS_H__ +#define __MACROS_H__ + +// #define min(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a < _b ? _a : _b; }) +// #define max(a, b) ({ __typeof__ (a) _a = (a); __typeof__ (b) _b = (b); _a > _b ? _a : _b; }) +#define coerce(val, min, max) \ + do \ + { \ + if ((val) > (max)) \ + { \ + val = max; \ + } \ + else if ((val) < (min)) \ + { \ + val = min; \ + } \ + } while (0) +#define xstr(s) str(s) +#define str(s) #s + +#endif diff --git a/include/mqtt.h b/include/mqtt.h new file mode 100644 index 00000000..65ed6b62 --- /dev/null +++ b/include/mqtt.h @@ -0,0 +1,8 @@ +#pragma once + +#include "error.h" + +void mqtt_init(); +error_t mqtt_sendEvent(const char *eventname, const char *content); +bool mqtt_publish(const char *item_topic, const char *content); +bool mqtt_subscribe(const char *item_topic); diff --git a/include/net_config.h b/include/net_config.h index 4c7f5b24..cd63d3ef 100644 --- a/include/net_config.h +++ b/include/net_config.h @@ -41,6 +41,11 @@ typedef struct #define HTTP_SERVER_PRIVATE_CONTEXT http_connection_private_t private; #define HTTP_SERVER_PERSISTENT_CONN_SUPPORT ENABLED +#define MQTT_CLIENT_SUPPORT ENABLED +#define MQTT_CLIENT_TLS_SUPPORT ENABLED +#define MQTT_CLIENT_MAX_PASSWORD_LEN 64 +#define MQTT_CLIENT_MAX_USERNAME_LEN 64 + // Trace level for TCP/IP stack debugging #define MEM_TRACE_LEVEL TRACE_LEVEL_INFO #define NIC_TRACE_LEVEL TRACE_LEVEL_INFO @@ -73,7 +78,7 @@ typedef struct #define COAP_TRACE_LEVEL TRACE_LEVEL_INFO #define FTP_TRACE_LEVEL TRACE_LEVEL_INFO #define HTTP_TRACE_LEVEL TRACE_LEVEL_INFO -#define MQTT_TRACE_LEVEL TRACE_LEVEL_INFO +#define MQTT_TRACE_LEVEL TRACE_LEVEL_WARNING #define MQTT_SN_TRACE_LEVEL TRACE_LEVEL_INFO #define SMTP_TRACE_LEVEL TRACE_LEVEL_INFO #define SNMP_TRACE_LEVEL TRACE_LEVEL_INFO @@ -179,7 +184,7 @@ typedef struct #define HTTP_SERVER_MULTIPART_TYPE_SUPPORT ENABLED /* match original cloud settings */ -#define HTTP_SERVER_IDLE_TIMEOUT (5*60000) -#define HTTP_SERVER_TIMEOUT (1*60000) +#define HTTP_SERVER_IDLE_TIMEOUT (5 * 60000) +#define HTTP_SERVER_TIMEOUT (1 * 60000) #endif diff --git a/include/settings.h b/include/settings.h index bffb7ce6..f68c46f3 100644 --- a/include/settings.h +++ b/include/settings.h @@ -41,6 +41,7 @@ typedef struct { bool enabled; char *hostname; + uint32_t port; char *username; char *password; char *identification; diff --git a/src/cloud_request.c b/src/cloud_request.c index 8fd92eff..c1b31bfd 100644 --- a/src/cloud_request.c +++ b/src/cloud_request.c @@ -25,6 +25,7 @@ #include "tls_adapter.h" #include "handler_api.h" #include "settings.h" +#include "mqtt.h" #include "platform.h" #include "handler_cloud.h" @@ -115,6 +116,8 @@ int_t cloud_request(const char *server, int port, bool https, const char *uri, c return ERROR_ADDRESS_NOT_FOUND; } + mqtt_sendEvent("CloudRequest", uri); + HttpClientContext httpClientContext; if (!server) diff --git a/src/cyclone/cyclone_tcp/mqtt/mqtt_client_transport.c b/src/cyclone/cyclone_tcp/mqtt/mqtt_client_transport.c new file mode 100644 index 00000000..bc266f6d --- /dev/null +++ b/src/cyclone/cyclone_tcp/mqtt/mqtt_client_transport.c @@ -0,0 +1,704 @@ +/** + * @file mqtt_client_transport.c + * @brief Transport protocol abstraction layer + * + * @section License + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2010-2023 Oryx Embedded SARL. All rights reserved. + * + * This file is part of CycloneTCP Open. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * @author Oryx Embedded SARL (www.oryx-embedded.com) + * @version 2.3.0 + **/ + +// Switch to the appropriate trace level +#define TRACE_LEVEL MQTT_TRACE_LEVEL + +// Dependencies +#include "core/net.h" +#include "core/tcp_misc.h" +#include "mqtt/mqtt_client.h" +#include "mqtt/mqtt_client_packet.h" +#include "mqtt/mqtt_client_transport.h" +#include "mqtt/mqtt_client_misc.h" +#include "debug.h" + +// Check TCP/IP stack configuration +#if (MQTT_CLIENT_SUPPORT == ENABLED) + +#if (MQTT_CLIENT_WS_SUPPORT == ENABLED && MQTT_CLIENT_TLS_SUPPORT == ENABLED) + +/** + * @brief TLS initialization callback + * @param[in] webSocket Handle to a WebSocket + * @param[in] tlsContext Pointer to the TLS context + * @return Error code + **/ + +error_t mqttClientWebSocketTlsInitCallback(WebSocket *webSocket, + TlsContext *tlsContext) +{ + MqttClientContext *context; + + // Point to the MQTT client context + context = webSocket->tlsInitParam; + + // Invoke user-defined callback + return context->callbacks.tlsInitCallback(context, tlsContext); +} + +#endif + +/** + * @brief Open network connection + * @param[in] context Pointer to the MQTT client context + * @return Error code + **/ + +error_t mqttClientOpenConnection(MqttClientContext *context) +{ + error_t error; + + // TCP transport protocol? + if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TCP) + { + // Open a TCP socket + context->socket = socketOpen(SOCKET_TYPE_STREAM, SOCKET_IP_PROTO_TCP); + + // Valid socket handle? + if (context->socket != NULL) + { + // Associate the socket with the relevant interface + error = socketBindToInterface(context->socket, context->interface); + } + else + { + // Report an error + error = ERROR_OPEN_FAILED; + } + } +#if (MQTT_CLIENT_TLS_SUPPORT == ENABLED) + // TLS transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TLS) + { + // Open a TCP socket + context->socket = socketOpen(SOCKET_TYPE_STREAM, SOCKET_IP_PROTO_TCP); + + // Valid socket handle? + if (context->socket != NULL) + { + // Associate the socket with the relevant interface + error = socketBindToInterface(context->socket, context->interface); + + // Check status code + if (!error) + { + // Allocate TLS context + context->tlsContext = tlsInit(); + + // Valid TLS handle? + if (context->tlsContext != NULL) + { + // Select client operation mode + error = tlsSetConnectionEnd(context->tlsContext, + TLS_CONNECTION_END_CLIENT); + + // Check status code + if (!error) + { + // Bind TLS to the relevant socket + error = tlsSetSocket(context->tlsContext, context->socket); + } + + // Check status code + if (!error) + { + // Restore TLS session, if any + error = tlsRestoreSessionState(context->tlsContext, + &context->tlsSession); + } + + // Check status code + if (!error) + { + // Invoke user-defined callback, if any + if (context->callbacks.tlsInitCallback != NULL) + { + // Perform TLS related initialization + error = context->callbacks.tlsInitCallback(context, + context->tlsContext); + } + } + } + else + { + // Report an error + error = ERROR_OPEN_FAILED; + } + } + } + else + { + // Report an error + error = ERROR_OPEN_FAILED; + } + } +#endif +#if (MQTT_CLIENT_WS_SUPPORT == ENABLED) + // WebSocket transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WS) + { + // Open a WebSocket + context->webSocket = webSocketOpen(); + + // Valid WebSocket handle? + if (context->webSocket != NULL) + { + // Associate the WebSocket with the relevant interface + error = webSocketBindToInterface(context->webSocket, + context->interface); + } + else + { + // Report an error + error = ERROR_OPEN_FAILED; + } + } +#endif +#if (MQTT_CLIENT_WS_SUPPORT == ENABLED && MQTT_CLIENT_TLS_SUPPORT == ENABLED && \ + WEB_SOCKET_TLS_SUPPORT == ENABLED) + // Secure WebSocket transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WSS) + { + // Open a WebSocket + context->webSocket = webSocketOpen(); + + // Valid WebSocket handle? + if (context->webSocket != NULL) + { + // Associate the WebSocket with the relevant interface + error = webSocketBindToInterface(context->webSocket, + context->interface); + + // Check status code + if (!error) + { + // Attach MQTT client context + context->webSocket->tlsInitParam = context; + + // Register TLS initialization callback + error = webSocketRegisterTlsInitCallback(context->webSocket, + mqttClientWebSocketTlsInitCallback); + } + } + else + { + // Report an error + error = ERROR_OPEN_FAILED; + } + } +#endif + // Unknown transport protocol? + else + { + // Report an error + error = ERROR_INVALID_PROTOCOL; + } + + // Return status code + return error; +} + +/** + * @brief Establish network connection + * @param[in] context Pointer to the MQTT client context + * @param[in] serverIpAddr IP address of the MQTT server to connect to + * @param[in] serverPort TCP port number that will be used to establish the + * connection + * @return Error code + **/ + +error_t mqttClientEstablishConnection(MqttClientContext *context, + const IpAddr *serverIpAddr, uint16_t serverPort) +{ + error_t error; + + // TCP transport protocol? + if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TCP) + { + // Set timeout + error = socketSetTimeout(context->socket, context->settings.timeout); + + // Check status code + if (!error) + { + // Connect to the MQTT server using TCP + error = socketConnect(context->socket, serverIpAddr, serverPort); + } + } +#if (MQTT_CLIENT_TLS_SUPPORT == ENABLED) + // TLS transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TLS) + { + // Set timeout + error = socketSetTimeout(context->socket, context->settings.timeout); + + // Check status code + if (!error) + { + // Connect to the MQTT server using TCP + error = socketConnect(context->socket, serverIpAddr, serverPort); + } + + // Check status code + if (!error) + { + // Perform TLS handshake + error = tlsConnect(context->tlsContext); + } + + // Successful connection? + if (!error) + { + // Save TLS session + error = tlsSaveSessionState(context->tlsContext, &context->tlsSession); + } + } +#endif +#if (MQTT_CLIENT_WS_SUPPORT == ENABLED) + // WebSocket transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WS || + context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WSS) + { + // Set timeout + error = webSocketSetTimeout(context->webSocket, context->settings.timeout); + + // Check status code + if (!error) + { + // Set the hostname of the remote server + error = webSocketSetHost(context->webSocket, context->settings.host); + } + + // Check status code + if (!error) + { + // The client MUST include "mqtt" in the list of WebSocket + // sub-protocols it offers + error = webSocketSetSubProtocol(context->webSocket, "mqtt"); + } + + // Check status code + if (!error) + { + // Connect to the MQTT server using WebSocket + error = webSocketConnect(context->webSocket, serverIpAddr, + serverPort, context->settings.uri); + } + } +#endif + // Unknown transport protocol? + else + { + // Report an error + error = ERROR_INVALID_PROTOCOL; + } + + // Return status code + return error; +} + +/** + * @brief Shutdown network connection + * @param[in] context Pointer to the MQTT client context + * @return Error code + **/ + +error_t mqttClientShutdownConnection(MqttClientContext *context) +{ + error_t error; + + // TCP transport protocol? + if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TCP) + { + // Set timeout + error = socketSetTimeout(context->socket, context->settings.timeout); + + // Check status code + if (!error) + { + // Shutdown TCP connection + error = socketShutdown(context->socket, SOCKET_SD_BOTH); + } + } +#if (MQTT_CLIENT_TLS_SUPPORT == ENABLED) + // TLS transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TLS) + { + // Set timeout + error = socketSetTimeout(context->socket, context->settings.timeout); + + // Check status code + if (!error) + { + // Shutdown TLS session + error = tlsShutdown(context->tlsContext); + } + + // Check status code + if (!error) + { + // Shutdown TCP connection + error = socketShutdown(context->socket, SOCKET_SD_BOTH); + } + } +#endif +#if (MQTT_CLIENT_WS_SUPPORT == ENABLED) + // WebSocket transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WS || + context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WSS) + { + // Set timeout + error = webSocketSetTimeout(context->webSocket, context->settings.timeout); + + // Check status code + if (!error) + { + // Shutdown WebSocket connection + error = webSocketShutdown(context->webSocket); + } + } +#endif + // Unknown transport protocol? + else + { + // Report an error + error = ERROR_INVALID_PROTOCOL; + } + + // Return status code + return error; +} + +/** + * @brief Close network connection + * @param[in] context Pointer to the MQTT client context + **/ + +void mqttClientCloseConnection(MqttClientContext *context) +{ + // TCP transport protocol? + if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TCP) + { + // Close TCP connection + if (context->socket != NULL) + { + socketClose(context->socket); + context->socket = NULL; + } + } +#if (MQTT_CLIENT_TLS_SUPPORT == ENABLED) + // TLS transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TLS) + { + // Release TLS context + if (context->tlsContext != NULL) + { + tlsFree(context->tlsContext); + context->tlsContext = NULL; + } + + // Close TCP connection + if (context->socket != NULL) + { + socketClose(context->socket); + context->socket = NULL; + } + } +#endif +#if (MQTT_CLIENT_WS_SUPPORT == ENABLED) + // WebSocket transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WS || + context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WSS) + { + // Close WebSocket connection + if (context->webSocket != NULL) + { + webSocketClose(context->webSocket); + context->webSocket = NULL; + } + } +#endif +} + +/** + * @brief Send data using the relevant transport protocol + * @param[in] context Pointer to the MQTT client context + * @param[in] data Pointer to a buffer containing the data to be transmitted + * @param[in] length Number of bytes to be transmitted + * @param[out] written Actual number of bytes written (optional parameter) + * @param[in] flags Set of flags that influences the behavior of this function + * @return Error code + **/ + +error_t mqttClientSendData(MqttClientContext *context, + const void *data, size_t length, size_t *written, uint_t flags) +{ + error_t error; + + // TCP transport protocol? + if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TCP) + { + // Set timeout + error = socketSetTimeout(context->socket, context->settings.timeout); + + // Check status code + if (!error) + { + // Transmit data + error = socketSend(context->socket, data, length, written, flags); + } + } +#if (MQTT_CLIENT_TLS_SUPPORT == ENABLED) + // TLS transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TLS) + { + // Set timeout + error = socketSetTimeout(context->socket, context->settings.timeout); + + // Check status code + if (!error) + { + // Transmit data + error = tlsWrite(context->tlsContext, data, length, written, flags); + } + } +#endif +#if (MQTT_CLIENT_WS_SUPPORT == ENABLED) + // WebSocket transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WS || + context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WSS) + { + // Set timeout + error = webSocketSetTimeout(context->webSocket, context->settings.timeout); + + // Check status code + if (!error) + { + // MQTT control packets must be sent in WebSocket binary data frames + error = webSocketSend(context->webSocket, data, length, + WS_FRAME_TYPE_BINARY, written); + } + } +#endif + // Unknown transport protocol? + else + { + // Report an error + error = ERROR_INVALID_PROTOCOL; + } + + // Return status code + return error; +} + +/** + * @brief Receive data using the relevant transport protocol + * @param[in] context Pointer to the MQTT client context + * @param[out] data Buffer into which received data will be placed + * @param[in] size Maximum number of bytes that can be received + * @param[out] received Number of bytes that have been received + * @param[in] flags Set of flags that influences the behavior of this function + * @return Error code + **/ + +error_t mqttClientReceiveData(MqttClientContext *context, + void *data, size_t size, size_t *received, uint_t flags) +{ + error_t error; + + // No data has been read yet + *received = 0; + + // TCP transport protocol? + if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TCP) + { + // Set timeout + error = socketSetTimeout(context->socket, context->settings.timeout); + + // Check status code + if (!error) + { + // Receive data + error = socketReceive(context->socket, data, size, received, flags); + } + } +#if (MQTT_CLIENT_TLS_SUPPORT == ENABLED) + // TLS transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TLS) + { + // Set timeout + error = socketSetTimeout(context->socket, context->settings.timeout); + + // Check status code + if (!error) + { + // Receive data + error = tlsRead(context->tlsContext, data, size, received, flags); + } + } +#endif +#if (MQTT_CLIENT_WS_SUPPORT == ENABLED) + // WebSocket transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WS || + context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WSS) + { + WebSocketFrameType type; + + // Set timeout + error = webSocketSetTimeout(context->webSocket, context->settings.timeout); + + // Check status code + if (!error) + { + // Receive data + error = webSocketReceive(context->webSocket, data, size, &type, received); + } + + // Check status code + if (!error) + { + // MQTT control packets must be sent in WebSocket binary data frames. If + // any other type of data frame is received the recipient must close the + // network connection + if (type != WS_FRAME_TYPE_BINARY && type != WS_FRAME_TYPE_CONTINUATION) + error = ERROR_INVALID_TYPE; + } + } +#endif + // Unknown transport protocol? + else + { + // Report an error + error = ERROR_INVALID_PROTOCOL; + } + + // Return status code + return error; +} + +/** + * @brief Wait for incoming data + * @param[in] context Pointer to the MQTT client context + * @param[in] timeout Maximum time to wait before returning + * @return Error code + **/ + +error_t mqttClientWaitForData(MqttClientContext *context, systime_t timeout) +{ + uint_t event; + + // TCP transport protocol? + if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TCP) + { + // Wait for some data to be available for reading + event = tcpWaitForEvents(context->socket, SOCKET_EVENT_RX_READY, timeout); + } +#if (MQTT_CLIENT_TLS_SUPPORT == ENABLED) + // TLS transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_TLS) + { + // Sanity check + if (context->tlsContext == NULL) + return ERROR_FAILURE; + + // Check whether some data is pending in the receive buffer + if (context->tlsContext->rxBufferLen > 0) + { + // No need to poll the underlying socket for incoming traffic... + event = SOCKET_EVENT_RX_READY; + } + else + { + // Wait for some data to be available for reading + event = tcpWaitForEvents(context->socket, SOCKET_EVENT_RX_READY, timeout); + } + } +#endif +#if (MQTT_CLIENT_WS_SUPPORT == ENABLED) + // WebSocket transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WS) + { + // Sanity check + if (context->webSocket == NULL) + return ERROR_FAILURE; + + // Get exclusive access + osAcquireMutex(&netMutex); + // Wait for some data to be available for reading + event = tcpWaitForEvents(context->webSocket->socket, SOCKET_EVENT_RX_READY, timeout); + // Release exclusive access + osReleaseMutex(&netMutex); + } +#endif +#if (MQTT_CLIENT_WS_SUPPORT == ENABLED && WEB_SOCKET_TLS_SUPPORT) + // Secure WebSocket transport protocol? + else if (context->settings.transportProtocol == MQTT_TRANSPORT_PROTOCOL_WSS) + { + // Sanity check + if (context->webSocket == NULL || context->webSocket->tlsContext == NULL) + return ERROR_FAILURE; + + // Check whether some data is pending in the receive buffer + if (context->webSocket->tlsContext->rxBufferLen > 0) + { + // No need to poll the underlying socket for incoming traffic... + event = SOCKET_EVENT_RX_READY; + } + else + { + // Get exclusive access + osAcquireMutex(&netMutex); + // Wait for some data to be available for reading + event = tcpWaitForEvents(context->webSocket->socket, SOCKET_EVENT_RX_READY, timeout); + // Release exclusive access + osReleaseMutex(&netMutex); + } + } +#endif + // Unknown transport protocol? + else + { + // Report an error + return ERROR_INVALID_PROTOCOL; + } + + // Check whether some data is available for reading + if (event == SOCKET_EVENT_RX_READY) + { + return NO_ERROR; + } + else + { + return ERROR_TIMEOUT; + } +} + +#endif diff --git a/src/cyclone/cyclone_tcp/mqtt/mqtt_client_transport.h b/src/cyclone/cyclone_tcp/mqtt/mqtt_client_transport.h new file mode 100644 index 00000000..091d0085 --- /dev/null +++ b/src/cyclone/cyclone_tcp/mqtt/mqtt_client_transport.h @@ -0,0 +1,66 @@ +/** + * @file mqtt_client_transport.h + * @brief Transport protocol abstraction layer + * + * @section License + * + * SPDX-License-Identifier: GPL-2.0-or-later + * + * Copyright (C) 2010-2023 Oryx Embedded SARL. All rights reserved. + * + * This file is part of CycloneTCP Open. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * @author Oryx Embedded SARL (www.oryx-embedded.com) + * @version 2.3.0 + **/ + +#ifndef _MQTT_CLIENT_TRANSPORT_H +#define _MQTT_CLIENT_TRANSPORT_H + +//Dependencies +#include "core/net.h" +#include "mqtt/mqtt_client.h" + +//C++ guard +#ifdef __cplusplus +extern "C" { +#endif + +//MQTT client related functions +error_t mqttClientOpenConnection(MqttClientContext *context); + +error_t mqttClientEstablishConnection(MqttClientContext *context, + const IpAddr *serverIpAddr, uint16_t serverPort); + +error_t mqttClientShutdownConnection(MqttClientContext *context); + +void mqttClientCloseConnection(MqttClientContext *context); + +error_t mqttClientSendData(MqttClientContext *context, + const void *data, size_t length, size_t *written, uint_t flags); + +error_t mqttClientReceiveData(MqttClientContext *context, + void *data, size_t size, size_t *received, uint_t flags); + +error_t mqttClientWaitForData(MqttClientContext *context, systime_t timeout); + +//C++ guard +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index 34496044..f8db6716 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -13,6 +13,7 @@ #include "handler_rtnl.h" #include "settings.h" #include "stats.h" +#include "mqtt.h" #include "fs_ext.h" #include "cloud_request.h" @@ -140,6 +141,11 @@ error_t handleRtnl(HttpConnection *connection, const char_t *uri, const char_t * return NO_ERROR; } +int32_t read_little_endian(const uint8_t *buf) +{ + return (int32_t)(buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24); +} + void rtnlEvent(TonieRtnlRPC *rpc) { char_t buffer[4096]; @@ -200,13 +206,105 @@ void rtnlEvent(TonieRtnlRPC *rpc) if (rpc->log3) { - if (rpc->log3->field2 == 1) + switch (rpc->log3->field2) { + case 1: sse_sendEvent("pressed", "ear-big", true); + mqtt_sendEvent("VolUp", "ON"); + mqtt_sendEvent("VolUp", "OFF"); + break; + case 2: + sse_sendEvent("pressed", "ear-small", true); + mqtt_sendEvent("VolDown", "ON"); + mqtt_sendEvent("VolDown", "OFF"); + break; + case 3: + sse_sendEvent("knock", "forward", true); + mqtt_sendEvent("KnockForward", "ON"); + mqtt_sendEvent("KnockForward", "OFF"); + break; + case 4: + sse_sendEvent("knock", "backward", true); + mqtt_sendEvent("KnockBackward", "ON"); + mqtt_sendEvent("KnockBackward", "OFF"); + break; + case 5: + sse_sendEvent("tilt", "forward", true); + mqtt_sendEvent("TiltForward", "ON"); + mqtt_sendEvent("TiltForward", "OFF"); + break; + case 6: + sse_sendEvent("tilt", "backward", true); + mqtt_sendEvent("TiltBackward", "ON"); + mqtt_sendEvent("TiltBackward", "OFF"); + break; + case 11: + sse_sendEvent("playback", "starting", true); + mqtt_sendEvent("Playback", "ON"); + mqtt_sendEvent("TagInvalid", ""); + break; + case 12: + sse_sendEvent("playback", "started", true); + mqtt_sendEvent("Playback", "ON"); + mqtt_sendEvent("TagInvalid", ""); + break; + case 13: + sse_sendEvent("playback", "stopped", true); + mqtt_sendEvent("Playback", "OFF"); + mqtt_sendEvent("TagValid", ""); + mqtt_sendEvent("TagInvalid", ""); + break; + default: + TRACE_WARNING("Not-yet-known log3 type: %d\r\n", rpc->log3->field2); + break; } - else if (rpc->log3->field2 == 2) + } + + if (rpc->log2) + { + const char *eventname = NULL; + char buffer[33]; + + /* ESP32 sends tag IDs, even if unknown */ + if (rpc->log2->function_group == 15 && rpc->log2->function == 15452) { - sse_sendEvent("pressed", "ear-small", true); + eventname = "TagInvalid"; + if (rpc->log2->field6.len == 8) + { + for (size_t i = 0; i < rpc->log2->field6.len; i++) + { + osSprintf(&buffer[i * 2], "%02X", rpc->log2->field6.data[(i + 4) % 8]); + } + } + } + else if (rpc->log2->function_group == 15 && rpc->log2->function == 16065) + { + eventname = "TagValid"; + if (rpc->log2->field6.len == 8) + { + for (size_t i = 0; i < rpc->log2->field6.len; i++) + { + osSprintf(&buffer[i * 2], "%02X", rpc->log2->field6.data[(i + 4) % 8]); + } + } + } + else if (rpc->log2->function_group == 12 && rpc->log2->function == 15427) + { + eventname = "BoxTilt"; + int32_t angle = read_little_endian(rpc->log2->field6.data); + osSprintf(buffer, "%d", angle); + } + else if (rpc->log2->function_group == 12 && rpc->log2->function == 15426) + { + eventname = "BoxTilt"; + int32_t angle = read_little_endian(rpc->log2->field6.data); + osSprintf(buffer, "%d", angle); + } + + if (eventname) + { + sse_sendEvent(eventname, buffer, true); + mqtt_sendEvent(eventname, buffer); } } } diff --git a/src/home_assistant.c b/src/home_assistant.c new file mode 100644 index 00000000..c9566079 --- /dev/null +++ b/src/home_assistant.c @@ -0,0 +1,450 @@ + +#include + +#include "debug.h" +#include "stats.h" +#include "home_assistant.h" +#include "macros.h" +#include "mqtt.h" + +t_ha_info ha_info; +static const char *mqtt_client = "teddyCloud"; + +void ha_addstrarray(char *json_str, const char *name, const char *value, bool last) +{ + char tmp_buf[256]; + + if (value && strlen(value) > 0) + { + int pos = 0; + char values_buf[128]; + int out_pos = 0; + + values_buf[out_pos++] = '"'; + + bool done = false; + while (!done && out_pos < sizeof(values_buf)) + { + switch (value[pos]) + { + case ';': + values_buf[out_pos++] = '"'; + if (value[pos + 1]) + { + values_buf[out_pos++] = ','; + values_buf[out_pos++] = '"'; + } + break; + + case 0: + values_buf[out_pos++] = '"'; + done = true; + break; + + default: + values_buf[out_pos++] = value[pos]; + break; + } + pos++; + } + values_buf[out_pos++] = '\000'; + + osSnprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": [%s]%c ", name, values_buf, (last ? ' ' : ',')); + strcat(json_str, tmp_buf); + } +} + +void ha_addstr(char *json_str, const char *name, const char *value, bool last) +{ + char tmp_buf[128]; + + if (value && strlen(value) > 0) + { + osSnprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%s\"%c ", name, value, (last ? ' ' : ',')); + strcat(json_str, tmp_buf); + } +} + +void ha_addmqtt(char *json_str, const char *name, const char *value, t_ha_entity *entity, bool last) +{ + char tmp_buf[128]; + + if (value && strlen(value) > 0) + { + char path_buffer[64]; + + if (entity && entity->alt_name) + { + osSprintf(path_buffer, value, entity->alt_name); + } + else + { + osSprintf(path_buffer, value, mqtt_client); + } + osSnprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%s\"%c ", name, path_buffer, (last ? ' ' : ',')); + osStrcat(json_str, tmp_buf); + } +} + +void ha_addfloat(char *json_str, const char *name, float value, bool last) +{ + char tmp_buf[64]; + + osSnprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%f\"%c ", name, value, (last ? ' ' : ',')); + strcat(json_str, tmp_buf); +} + +void ha_addint(char *json_str, const char *name, int value, bool last) +{ + char tmp_buf[64]; + + osSnprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%d\"%c ", name, value, (last ? ' ' : ',')); + strcat(json_str, tmp_buf); +} + +void ha_publish() +{ + char *json_str = (char *)malloc(1024); + char mqtt_path[128]; + char uniq_id[128]; + + TRACE_INFO("[HA] Publish\n"); + + osSprintf(ha_info.cu, "http://%s/", "teddyCloud"); + + for (int pos = 0; pos < ha_info.entitiy_count; pos++) + { + const char *type = NULL; + + switch (ha_info.entities[pos].type) + { + case ha_sensor: + // TRACE_INFO("[HA] sensor\n"); + type = "sensor"; + break; + case ha_text: + // TRACE_INFO("[HA] text\n"); + type = "text"; + break; + case ha_number: + // TRACE_INFO("[HA] number\n"); + type = "number"; + break; + case ha_button: + // TRACE_INFO("[HA] button\n"); + type = "button"; + break; + case ha_binary_sensor: + // TRACE_INFO("[HA] binary_sensor\n"); + type = "binary_sensor"; + break; + case ha_select: + // TRACE_INFO("[HA] select\n"); + type = "select"; + break; + case ha_light: + // TRACE_INFO("[HA] light\n"); + type = "light"; + break; + default: + // TRACE_INFO("[HA] last one\n"); + break; + } + + if (!type) + { + break; + } + + osSprintf(uniq_id, "%s_%s", ha_info.id, ha_info.entities[pos].id); + + // TRACE_INFO("[HA] uniq_id %s\n", uniq_id); + osSprintf(mqtt_path, "homeassistant/%s/%s/%s/config", type, ha_info.id, ha_info.entities[pos].id); + + // TRACE_INFO("[HA] mqtt_path %s\n", mqtt_path); + + strcpy(json_str, "{"); + ha_addstr(json_str, "name", ha_info.entities[pos].name, false); + ha_addstr(json_str, "uniq_id", uniq_id, false); + ha_addstr(json_str, "dev_cla", ha_info.entities[pos].dev_class, false); + ha_addstr(json_str, "stat_cla", ha_info.entities[pos].state_class, false); + ha_addstr(json_str, "ic", ha_info.entities[pos].ic, false); + ha_addstr(json_str, "mode", ha_info.entities[pos].mode, false); + ha_addstr(json_str, "ent_cat", ha_info.entities[pos].ent_cat, false); + ha_addmqtt(json_str, "cmd_t", ha_info.entities[pos].cmd_t, &ha_info.entities[pos], false); + ha_addmqtt(json_str, "stat_t", ha_info.entities[pos].stat_t, &ha_info.entities[pos], false); + ha_addmqtt(json_str, "rgbw_cmd_t", ha_info.entities[pos].rgbw_t, &ha_info.entities[pos], false); + ha_addmqtt(json_str, "rgb_cmd_t", ha_info.entities[pos].rgb_t, &ha_info.entities[pos], false); + ha_addmqtt(json_str, "fx_cmd_t", ha_info.entities[pos].fx_cmd_t, &ha_info.entities[pos], false); + ha_addmqtt(json_str, "fx_stat_t", ha_info.entities[pos].fx_stat_t, &ha_info.entities[pos], false); + ha_addstrarray(json_str, "fx_list", ha_info.entities[pos].fx_list, false); + ha_addmqtt(json_str, "val_tpl", ha_info.entities[pos].val_tpl, &ha_info.entities[pos], false); + ha_addstrarray(json_str, "options", ha_info.entities[pos].options, false); + ha_addstr(json_str, "unit_of_meas", ha_info.entities[pos].unit_of_meas, false); + + switch (ha_info.entities[pos].type) + { + case ha_number: + ha_addint(json_str, "min", ha_info.entities[pos].min, false); + ha_addint(json_str, "max", ha_info.entities[pos].max, false); + break; + default: + break; + } + + strcat(json_str, "\"dev\": {"); + ha_addstr(json_str, "name", ha_info.name, false); + ha_addstr(json_str, "ids", ha_info.id, false); + ha_addstr(json_str, "cu", ha_info.cu, false); + ha_addstr(json_str, "mf", ha_info.mf, false); + ha_addstr(json_str, "mdl", ha_info.mdl, false); + ha_addstr(json_str, "sw", ha_info.sw, true); + strcat(json_str, "}}"); + + // TRACE_INFO("[HA] topic '%s'\n", mqtt_path); + // TRACE_INFO("[HA] content '%s'\n", json_str); + + if (!mqtt_publish(mqtt_path, json_str)) + { + TRACE_INFO("[HA] publish failed\n"); + } + } + + TRACE_INFO("[HA] done\n"); + free(json_str); +} + +void ha_received(char *topic, const char *payload) +{ + for (int pos = 0; pos < ha_info.entitiy_count; pos++) + { + char item_topic[128]; + + if (ha_info.entities[pos].cmd_t && ha_info.entities[pos].received) + { + osSprintf(item_topic, ha_info.entities[pos].cmd_t, mqtt_client); + if (!strcmp(topic, item_topic)) + { + ha_info.entities[pos].received(&ha_info.entities[pos], ha_info.entities[pos].received_ctx, payload); + + if (ha_info.entities[pos].transmit) + { + ha_info.entities[pos].transmit(&ha_info.entities[pos], ha_info.entities[pos].transmit_ctx); + } + } + } + + if (ha_info.entities[pos].rgb_t && ha_info.entities[pos].rgb_received) + { + osSprintf(item_topic, ha_info.entities[pos].rgb_t, mqtt_client); + if (!strcmp(topic, item_topic)) + { + ha_info.entities[pos].rgb_received(&ha_info.entities[pos], ha_info.entities[pos].rgb_received_ctx, payload); + + if (ha_info.entities[pos].transmit) + { + ha_info.entities[pos].transmit(&ha_info.entities[pos], ha_info.entities[pos].transmit_ctx); + } + } + } + + if (ha_info.entities[pos].fx_cmd_t && ha_info.entities[pos].fx_received) + { + osSprintf(item_topic, ha_info.entities[pos].fx_cmd_t, mqtt_client); + if (!strcmp(topic, item_topic)) + { + ha_info.entities[pos].fx_received(&ha_info.entities[pos], ha_info.entities[pos].fx_received_ctx, payload); + + if (ha_info.entities[pos].transmit) + { + ha_info.entities[pos].transmit(&ha_info.entities[pos], ha_info.entities[pos].transmit_ctx); + } + } + } + } +} + +void ha_transmit(const t_ha_entity *entity, const char *value) +{ + if (!entity) + { + return; + } + + if (!entity->stat_t) + { + return; + } + char item_topic[128]; + osSprintf(item_topic, entity->stat_t, mqtt_client); + + if (!mqtt_publish(item_topic, value)) + { + TRACE_INFO("[HA] publish failed\n"); + } +} + +void ha_transmit_topic(const char *stat_t, const char *value) +{ + if (!stat_t) + { + return; + } + + char item_topic[128]; + osSprintf(item_topic, stat_t, mqtt_client); + + if (!mqtt_publish(item_topic, value)) + { + TRACE_INFO("[HA] publish failed\n"); + } +} + +void ha_transmit_all() +{ + for (int pos = 0; pos < ha_info.entitiy_count; pos++) + { + if (ha_info.entities[pos].transmit) + { + ha_info.entities[pos].transmit(&ha_info.entities[pos], ha_info.entities[pos].transmit_ctx); + } + } +} + +void ha_setup() +{ + memset(&ha_info, 0x00, sizeof(ha_info)); + + osSprintf(ha_info.name, "%s", mqtt_client); + osSprintf(ha_info.id, "%s", "teddyCloud"); + osSprintf(ha_info.cu, "http://%s/", "teddyCloud"); + osSprintf(ha_info.mf, "RevvoX"); + osSprintf(ha_info.mdl, "%s", "teddyCloud"); + osSprintf(ha_info.sw, "" BUILD_GIT_TAG " (" BUILD_GIT_SHORT_SHA ")"); + ha_info.entitiy_count = 0; +} + +void ha_connected() +{ + for (int pos = 0; pos < ha_info.entitiy_count; pos++) + { + char item_topic[128]; + if (ha_info.entities[pos].cmd_t && ha_info.entities[pos].received) + { + osSprintf(item_topic, ha_info.entities[pos].cmd_t, mqtt_client); + mqtt_subscribe(item_topic); + } + if (ha_info.entities[pos].rgb_t && ha_info.entities[pos].rgb_received) + { + osSprintf(item_topic, ha_info.entities[pos].rgb_t, mqtt_client); + mqtt_subscribe(item_topic); + } + if (ha_info.entities[pos].fx_cmd_t && ha_info.entities[pos].fx_received) + { + osSprintf(item_topic, ha_info.entities[pos].fx_cmd_t, mqtt_client); + mqtt_subscribe(item_topic); + } + } + ha_publish(); + ha_transmit_all(); +} + +bool ha_loop() +{ + systime_t time = osGetSystemTime(); + static systime_t nextTime = 0; + + if (time >= nextTime) + { + ha_publish(); + ha_transmit_all(); + nextTime = time + 60000; + } + + return false; +} + +void ha_add(t_ha_entity *entity) +{ + if (!entity) + { + return; + } + + if (ha_info.entitiy_count >= MAX_ENTITIES) + { + return; + } + memcpy(&ha_info.entities[ha_info.entitiy_count++], entity, sizeof(t_ha_entity)); +} + +int ha_parse_index(const char *options, const char *message) +{ + if (!options) + { + return -1; + } + + int pos = 0; + char tmp_buf[128]; + char *cur_elem = tmp_buf; + + strncpy(tmp_buf, options, sizeof(tmp_buf)); + + while (true) + { + char *next_elem = strchr(cur_elem, ';'); + if (next_elem) + { + *next_elem = '\000'; + } + if (!strcmp(cur_elem, message)) + { + return pos; + } + + if (!next_elem) + { + return -1; + } + + cur_elem = next_elem + 1; + pos++; + } +} + +void ha_get_index(const char *options, int index, char *text) +{ + if (!options || !text) + { + return; + } + + int pos = 0; + char tmp_buf[128]; + char *cur_elem = tmp_buf; + + strncpy(tmp_buf, options, sizeof(tmp_buf)); + + while (true) + { + char *next_elem = strchr(cur_elem, ';'); + if (next_elem) + { + *next_elem = '\000'; + } + if (pos == index) + { + strcpy(text, cur_elem); + return; + } + + if (!next_elem) + { + return; + } + + cur_elem = next_elem + 1; + pos++; + } +} diff --git a/src/main.c b/src/main.c index 37ee6662..45be5e8e 100644 --- a/src/main.c +++ b/src/main.c @@ -23,6 +23,7 @@ #include "settings.h" #include "esp32.h" +#include "mqtt.h" void platform_init(void); void platform_deinit(void); @@ -247,6 +248,7 @@ int_t main(int argc, char *argv[]) } else { + mqtt_init(); server_init(); } diff --git a/src/mqtt.c b/src/mqtt.c index 49ec4c69..5436f8ee 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -1,20 +1,24 @@ #include #include +#include #include "core/net.h" #include "core/ip.h" #include "core/tcp.h" #include "settings.h" +#include "platform.h" #include "mqtt/mqtt_client.h" +#include "home_assistant.h" #include "debug.h" -#if 0 bool_t mqttConnected = FALSE; error_t error; MqttClientContext mqtt_context; +static const char *mqtt_client = "teddyCloud"; +bool mqtt_fail = false; void mqttTestPublishCallback(MqttClientContext *context, const char_t *topic, const uint8_t *message, size_t length, @@ -30,66 +34,103 @@ void mqttTestPublishCallback(MqttClientContext *context, TRACE_INFO(" Message (%" PRIuSIZE " bytes):\r\n", length); TRACE_INFO_ARRAY(" ", message, length); - // Check topic name - if (!strcmp(topic, "teddybench/pong")) - { - if (length == 2 && !strncasecmp((char_t *)message, "on", 2)) - { - } - } + ha_received((char *)topic, (const char *)message); +} + +error_t mqtt_sendEvent(const char *eventname, const char *content) +{ + char topic[128]; + + osSnprintf(topic, sizeof(topic), "%s/event/%s", mqtt_client, eventname); + mqttClientPublish(&mqtt_context, topic, content, osStrlen(content), MQTT_QOS_LEVEL_0, false, NULL); + + return NO_ERROR; +} + +bool mqtt_publish(const char *item_topic, const char *content) +{ + mqttClientPublish(&mqtt_context, item_topic, content, osStrlen(content), MQTT_QOS_LEVEL_0, false, NULL); + + return true; +} + +bool mqtt_subscribe(const char *item_topic) +{ + mqttClientSubscribe(&mqtt_context, item_topic, MQTT_QOS_LEVEL_1, NULL); + + return true; } error_t mqttConnect(MqttClientContext *mqtt_context) { error_t error; - IpAddr mqttIp; + mqttClientInit(mqtt_context); mqttClientSetVersion(mqtt_context, MQTT_VERSION_3_1_1); mqttClientSetTransportProtocol(mqtt_context, MQTT_TRANSPORT_PROTOCOL_TCP); - // Register publish callback function + mqttClientRegisterPublishCallback(mqtt_context, mqttTestPublishCallback); mqttClientSetTimeout(mqtt_context, 20000); mqttClientSetKeepAlive(mqtt_context, 30); - mqttClientSetIdentifier(mqtt_context, "teddycloud"); - mqttClientSetAuthInfo(mqtt_context, "username", "password"); - mqttClientSetWillMessage(mqtt_context, "teddycloud/status", + const char *server = settings_get_string("mqtt.hostname"); + uint32_t port = settings_get_unsigned("mqtt.port"); + + mqttClientSetIdentifier(mqtt_context, settings_get_string("mqtt.identification")); + mqttClientSetAuthInfo(mqtt_context, settings_get_string("mqtt.username"), settings_get_string("mqtt.password")); + mqttClientSetWillMessage(mqtt_context, "teddyCloud/status", "offline", 7, MQTT_QOS_LEVEL_1, FALSE); do { - // Establish connection with the MQTT server - error = mqttClientConnect(mqtt_context, - &mqttIp, 1883, TRUE); - // Any error to report? + TRACE_INFO("Connect to '%s'\r\n", server); + void *resolve_ctx = resolve_host(server); + if (!resolve_ctx) + { + TRACE_ERROR("Failed to resolve ipv4 address!\r\n"); + return ERROR_FAILURE; + } + + int pos = 0; + do + { + IpAddr mqttIp; + if (!resolve_get_ip(resolve_ctx, pos, &mqttIp)) + { + TRACE_ERROR("Failed to connect to MQTT server!\r\n"); + return ERROR_FAILURE; + } + char_t host[129]; + + ipv4AddrToString(mqttIp.ipv4Addr, host); + TRACE_INFO(" trying IP: %s\n", host); + + error = mqttClientConnect(mqtt_context, &mqttIp, port, TRUE); + } while (0); + if (error) + { + TRACE_ERROR("Failed to connect to ipv4 address %d\r\n", error); break; + } - // Subscribe to the desired topics error = mqttClientSubscribe(mqtt_context, - "teddycloud/*", MQTT_QOS_LEVEL_1, NULL); - // Any error to report? + "teddyCloud/*", MQTT_QOS_LEVEL_1, NULL); if (error) break; - // Send PUBLISH packet - error = mqttClientPublish(mqtt_context, "teddycloud/status", + error = mqttClientPublish(mqtt_context, "teddyCloud/status", "online", 6, MQTT_QOS_LEVEL_1, TRUE, NULL); - // Any error to report? if (error) break; - // End of exception handling block } while (0); - // Check status code if (error) { - // Close connection mqttClientClose(mqtt_context); } - // Return status code return error; } @@ -97,32 +138,160 @@ void mqtt_thread() { while (!settings_get_bool("internal.exit")) { + if (!settings_get_bool("mqtt.enabled")) + { + osDelayTask(1000); + continue; + } + if (!mqttConnected) { + TRACE_WARNING("Connecting\r\n"); error = mqttConnect(&mqtt_context); if (!error) { + TRACE_WARNING("Connected\r\n"); mqttConnected = TRUE; + mqtt_fail = false; + ha_connected(); + } + else + { + osDelayTask(10000); } } error = NO_ERROR; - error = mqttClientPublish(&mqtt_context, "teddycloud/ping", - "pong", 4, MQTT_QOS_LEVEL_1, TRUE, NULL); - if (!error) - { - // Process events - error = mqttClientTask(&mqtt_context, 100); - } - if (error) + error = mqttClientTask(&mqtt_context, 100); + + if (error || mqtt_fail) { - // Close connection mqttClientClose(&mqtt_context); - // Update connection state mqttConnected = FALSE; - // Recovery delay osDelayTask(2000); } + ha_loop(); + } +} + +void mqtt_publish_string(const char *name, const char *value) +{ + char path_buffer[128]; + + sprintf(path_buffer, name, mqtt_client); + + if (!mqtt_publish(path_buffer, value)) + { + mqtt_fail = true; + } +} + +void mqtt_publish_float(const char *name, float value) +{ + char path_buffer[128]; + char buffer[32]; + + sprintf(path_buffer, name, mqtt_client); + sprintf(buffer, "%0.4f", value); + + if (!mqtt_publish(path_buffer, buffer)) + { + mqtt_fail = true; + } +} + +void mqtt_publish_int(const char *name, uint32_t value) +{ + char path_buffer[128]; + char buffer[32]; + + if (value == 0x7FFFFFFF) + { + return; + } + sprintf(path_buffer, name, mqtt_client); + sprintf(buffer, "%d", value); + + if (!mqtt_publish(path_buffer, buffer)) + { + mqtt_fail = true; } } -#endif +void mqtt_init() +{ + osCreateTask("MQTT", &mqtt_thread, NULL, 1024, 0); + + ha_setup(); + + t_ha_entity entity; + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "status"; + entity.name = "Status message"; + entity.type = ha_sensor; + entity.stat_t = "%s/status"; + ha_add(&entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "TagInvalid"; + entity.name = "Tag Invalid"; + entity.type = ha_sensor; + entity.stat_t = "%s/event/TagInvalid"; + ha_add(&entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "TagValid"; + entity.name = "Tag Valid"; + entity.type = ha_sensor; + entity.stat_t = "%s/event/TagValid"; + ha_add(&entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "VolUp"; + entity.name = "Volume Up"; + entity.type = ha_binary_sensor; + entity.stat_t = "%s/event/VolUp"; + ha_add(&entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "VolDown"; + entity.name = "Volume Down"; + entity.type = ha_binary_sensor; + entity.stat_t = "%s/event/VolDown"; + ha_add(&entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "CloudRequest"; + entity.name = "Cloud Request"; + entity.type = ha_sensor; + entity.stat_t = "%s/event/CloudRequest"; + ha_add(&entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "BoxTilt"; + entity.name = "Box Tilt"; + entity.type = ha_sensor; + entity.stat_t = "%s/event/BoxTilt"; + ha_add(&entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "TiltForward"; + entity.name = "Tilt Forward"; + entity.type = ha_sensor; + entity.stat_t = "%s/event/TiltForward"; + ha_add(&entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "TiltBackward"; + entity.name = "Tilt Backward"; + entity.type = ha_sensor; + entity.stat_t = "%s/event/TiltBackward"; + ha_add(&entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "Playback"; + entity.name = "Playback"; + entity.type = ha_sensor; + entity.stat_t = "%s/event/Playback"; + ha_add(&entity); +} diff --git a/src/settings.c b/src/settings.c index dbc8d992..e712b8c0 100644 --- a/src/settings.c +++ b/src/settings.c @@ -112,6 +112,8 @@ static void option_map_init(uint8_t settingsId) OPTION_BOOL("mqtt.enabled", &settings->mqtt.enabled, FALSE, "Enable MQTT service") OPTION_STRING("mqtt.hostname", &settings->mqtt.hostname, "", "MQTT hostname") + OPTION_UNSIGNED("mqtt.port", &settings->mqtt.port, 1833, 1, 65535, "Port of MQTT server") + OPTION_STRING("mqtt.username", &settings->mqtt.username, "", "Username") OPTION_STRING("mqtt.password", &settings->mqtt.password, "", "Password") OPTION_STRING("mqtt.identification", &settings->mqtt.identification, "", "Client identification") From f10366920a677ff15a8ba224d0181cd21b203b6a Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Fri, 4 Aug 2023 23:07:09 +0200 Subject: [PATCH 003/126] fix ip resolve not settings ip address length --- src/platform/platform_linux.c | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/platform/platform_linux.c b/src/platform/platform_linux.c index 200e3930..752d3df0 100644 --- a/src/platform/platform_linux.c +++ b/src/platform/platform_linux.c @@ -400,6 +400,7 @@ bool resolve_get_ip(void *ctx, int pos, IpAddr *ipAddr) // ai_addr is a pointer to a sockaddr, which we know is a sockaddr_in because ai_family == AF_INET. struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr; memcpy(&ipAddr->ipv4Addr, &(ipv4->sin_addr), sizeof(struct in_addr)); + ipAddr->length = 4; return true; } // Handle the case of an IPv6 address @@ -407,6 +408,7 @@ bool resolve_get_ip(void *ctx, int pos, IpAddr *ipAddr) { struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr; memcpy(&ipAddr->ipv6Addr, &(ipv6->sin6_addr), sizeof(struct in6_addr)); + ipAddr->length = 6; return true; } } @@ -420,3 +422,41 @@ void resolve_free(void *res) { freeaddrinfo(res); } + +/** + * @brief Wait for a particular TCP event + * @param[in] socket Handle referencing the socket + * @param[in] eventMask Logic OR of all the TCP events that will complete the wait + * @param[in] timeout Maximum time to wait + * @return Logic OR of all the TCP events that satisfied the wait + **/ + +uint_t tcpWaitForEvents(Socket *socket, uint_t eventMask, systime_t timeout) +{ + fd_set read_fds; + struct timeval tv; + + if (socket == NULL) + return 0; + + socket_info_t *sock = (socket_info_t *)socket; + + // Initialize the file descriptor set. + FD_ZERO(&read_fds); + FD_SET(sock->sockfd, &read_fds); + + // Set timeout. + tv.tv_sec = timeout; + tv.tv_usec = 0; + + // Wait for the event. + int result = select(sock->sockfd + 1, &read_fds, NULL, NULL, &tv); + + // Check if socket is ready for reading. + if (result > 0 && FD_ISSET(sock->sockfd, &read_fds)) + { + return eventMask; + } + + return 0; +} From edd5845bfae10d2b6ca4edcc7ac706ef16ac6cc0 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Fri, 4 Aug 2023 23:07:24 +0200 Subject: [PATCH 004/126] use os* routines --- src/stats.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stats.c b/src/stats.c index 97995c92..f026c9da 100644 --- a/src/stats.c +++ b/src/stats.c @@ -15,7 +15,7 @@ void stats_update(const char *item, int count) int pos = 0; while (statistics[pos].name) { - if (!strcmp(item, statistics[pos].name)) + if (!osStrcmp(item, statistics[pos].name)) { statistics[pos].value += count; return; From a9459a8df97b858d217bc4a2078c000bbe9586b2 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Fri, 4 Aug 2023 23:26:24 +0200 Subject: [PATCH 005/126] add missing function for windows build --- src/platform/platform_windows.c | 38 +++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/src/platform/platform_windows.c b/src/platform/platform_windows.c index 28e88397..2395ef0c 100644 --- a/src/platform/platform_windows.c +++ b/src/platform/platform_windows.c @@ -477,3 +477,41 @@ struct tm *localtime_r(const time_t *timer, struct tm *result) else return NULL; } + +/** + * @brief Wait for a particular TCP event + * @param[in] socket Handle referencing the socket + * @param[in] eventMask Logic OR of all the TCP events that will complete the wait + * @param[in] timeout Maximum time to wait + * @return Logic OR of all the TCP events that satisfied the wait + **/ + +uint_t tcpWaitForEvents(Socket *socket, uint_t eventMask, systime_t timeout) +{ + fd_set read_fds; + struct timeval tv; + + if (socket == NULL) + return 0; + + socket_info_t *sock = (socket_info_t *)socket; + + // Initialize the file descriptor set. + FD_ZERO(&read_fds); + FD_SET(sock->sockfd, &read_fds); + + // Set timeout. + tv.tv_sec = timeout; + tv.tv_usec = 0; + + // Wait for the event. + int result = select(sock->sockfd + 1, &read_fds, NULL, NULL, &tv); + + // Check if socket is ready for reading. + if (result > 0 && FD_ISSET(sock->sockfd, &read_fds)) + { + return eventMask; + } + + return 0; +} \ No newline at end of file From f9920c68e1bfa4d2ebc4b757224fa31c37cb5b85 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Fri, 4 Aug 2023 23:33:02 +0200 Subject: [PATCH 006/126] remove warning log --- src/mqtt.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/mqtt.c b/src/mqtt.c index 5436f8ee..75f7952c 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -146,11 +146,10 @@ void mqtt_thread() if (!mqttConnected) { - TRACE_WARNING("Connecting\r\n"); error = mqttConnect(&mqtt_context); if (!error) { - TRACE_WARNING("Connected\r\n"); + TRACE_INFO("Connected\r\n"); mqttConnected = TRUE; mqtt_fail = false; ha_connected(); From 3fc1edc6cb225d4bd7f1e9a780e08eccb494e198 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 14:16:58 +0200 Subject: [PATCH 007/126] add TagValid for CC boxes --- src/handler_rtnl.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index f8db6716..d4d8cb3b 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -288,6 +288,18 @@ void rtnlEvent(TonieRtnlRPC *rpc) } } } + /* CC also. this is for valid tags */ + else if (rpc->log2->function_group == 15 && rpc->log2->function == 8646) + { + eventname = "TagValid"; + if (rpc->log2->field6.len == 8) + { + for (size_t i = 0; i < rpc->log2->field6.len; i++) + { + osSprintf(&buffer[i * 2], "%02X", rpc->log2->field6.data[(i + 4) % 8]); + } + } + } else if (rpc->log2->function_group == 12 && rpc->log2->function == 15427) { eventname = "BoxTilt"; From 6ff038ce41f8558b4ce9e8bc9a0c3de2b7940e7e Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 14:25:22 +0200 Subject: [PATCH 008/126] fix socket timeout being handled as seconds instead of milliseconds, increase MQTT read timeout --- src/mqtt.c | 2 +- src/platform/platform_linux.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/mqtt.c b/src/mqtt.c index 75f7952c..2eb80c78 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -160,7 +160,7 @@ void mqtt_thread() } } error = NO_ERROR; - error = mqttClientTask(&mqtt_context, 100); + error = mqttClientTask(&mqtt_context, 500); if (error || mqtt_fail) { diff --git a/src/platform/platform_linux.c b/src/platform/platform_linux.c index 752d3df0..8130a5e4 100644 --- a/src/platform/platform_linux.c +++ b/src/platform/platform_linux.c @@ -446,8 +446,8 @@ uint_t tcpWaitForEvents(Socket *socket, uint_t eventMask, systime_t timeout) FD_SET(sock->sockfd, &read_fds); // Set timeout. - tv.tv_sec = timeout; - tv.tv_usec = 0; + tv.tv_sec = 0; + tv.tv_usec = timeout * 1000; // Wait for the event. int result = select(sock->sockfd + 1, &read_fds, NULL, NULL, &tv); From 51bdd15cad28a96fc054e3ea1054cc4634a1e9e5 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 14:45:49 +0200 Subject: [PATCH 009/126] fix TagValid/Invalid for CC boxes --- src/handler_rtnl.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index d4d8cb3b..af6feb6f 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -290,6 +290,17 @@ void rtnlEvent(TonieRtnlRPC *rpc) } /* CC also. this is for valid tags */ else if (rpc->log2->function_group == 15 && rpc->log2->function == 8646) + { + eventname = "TagInvalid"; + if (rpc->log2->field6.len == 8) + { + for (size_t i = 0; i < rpc->log2->field6.len; i++) + { + osSprintf(&buffer[i * 2], "%02X", rpc->log2->field6.data[(i + 4) % 8]); + } + } + } + else if (rpc->log2->function_group == 15 && rpc->log2->function == 8627) { eventname = "TagValid"; if (rpc->log2->field6.len == 8) From 49184c27c26e39aa65d85a652d980186f1fa34e3 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 14:46:13 +0200 Subject: [PATCH 010/126] use configurable MQTT prefix --- include/settings.h | 2 ++ src/home_assistant.c | 33 ++++++++++------------------- src/mqtt.c | 49 +++++++++++++++++++++++++------------------- src/settings.c | 3 ++- 4 files changed, 43 insertions(+), 44 deletions(-) diff --git a/include/settings.h b/include/settings.h index f68c46f3..3abd85b3 100644 --- a/include/settings.h +++ b/include/settings.h @@ -45,6 +45,8 @@ typedef struct char *username; char *password; char *identification; + char *topic; + char *host_url; } settings_mqtt_t; typedef struct diff --git a/src/home_assistant.c b/src/home_assistant.c index c9566079..f5eb2b04 100644 --- a/src/home_assistant.c +++ b/src/home_assistant.c @@ -8,7 +8,6 @@ #include "mqtt.h" t_ha_info ha_info; -static const char *mqtt_client = "teddyCloud"; void ha_addstrarray(char *json_str, const char *name, const char *value, bool last) { @@ -79,7 +78,7 @@ void ha_addmqtt(char *json_str, const char *name, const char *value, t_ha_entity } else { - osSprintf(path_buffer, value, mqtt_client); + osSprintf(path_buffer, value, settings_get_string("mqtt.topic")); } osSnprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%s\"%c ", name, path_buffer, (last ? ' ' : ',')); osStrcat(json_str, tmp_buf); @@ -110,8 +109,6 @@ void ha_publish() TRACE_INFO("[HA] Publish\n"); - osSprintf(ha_info.cu, "http://%s/", "teddyCloud"); - for (int pos = 0; pos < ha_info.entitiy_count; pos++) { const char *type = NULL; @@ -119,35 +116,27 @@ void ha_publish() switch (ha_info.entities[pos].type) { case ha_sensor: - // TRACE_INFO("[HA] sensor\n"); type = "sensor"; break; case ha_text: - // TRACE_INFO("[HA] text\n"); type = "text"; break; case ha_number: - // TRACE_INFO("[HA] number\n"); type = "number"; break; case ha_button: - // TRACE_INFO("[HA] button\n"); type = "button"; break; case ha_binary_sensor: - // TRACE_INFO("[HA] binary_sensor\n"); type = "binary_sensor"; break; case ha_select: - // TRACE_INFO("[HA] select\n"); type = "select"; break; case ha_light: - // TRACE_INFO("[HA] light\n"); type = "light"; break; default: - // TRACE_INFO("[HA] last one\n"); break; } @@ -222,7 +211,7 @@ void ha_received(char *topic, const char *payload) if (ha_info.entities[pos].cmd_t && ha_info.entities[pos].received) { - osSprintf(item_topic, ha_info.entities[pos].cmd_t, mqtt_client); + osSprintf(item_topic, ha_info.entities[pos].cmd_t, settings_get_string("mqtt.topic")); if (!strcmp(topic, item_topic)) { ha_info.entities[pos].received(&ha_info.entities[pos], ha_info.entities[pos].received_ctx, payload); @@ -236,7 +225,7 @@ void ha_received(char *topic, const char *payload) if (ha_info.entities[pos].rgb_t && ha_info.entities[pos].rgb_received) { - osSprintf(item_topic, ha_info.entities[pos].rgb_t, mqtt_client); + osSprintf(item_topic, ha_info.entities[pos].rgb_t, settings_get_string("mqtt.topic")); if (!strcmp(topic, item_topic)) { ha_info.entities[pos].rgb_received(&ha_info.entities[pos], ha_info.entities[pos].rgb_received_ctx, payload); @@ -250,7 +239,7 @@ void ha_received(char *topic, const char *payload) if (ha_info.entities[pos].fx_cmd_t && ha_info.entities[pos].fx_received) { - osSprintf(item_topic, ha_info.entities[pos].fx_cmd_t, mqtt_client); + osSprintf(item_topic, ha_info.entities[pos].fx_cmd_t, settings_get_string("mqtt.topic")); if (!strcmp(topic, item_topic)) { ha_info.entities[pos].fx_received(&ha_info.entities[pos], ha_info.entities[pos].fx_received_ctx, payload); @@ -276,7 +265,7 @@ void ha_transmit(const t_ha_entity *entity, const char *value) return; } char item_topic[128]; - osSprintf(item_topic, entity->stat_t, mqtt_client); + osSprintf(item_topic, entity->stat_t, settings_get_string("mqtt.topic")); if (!mqtt_publish(item_topic, value)) { @@ -292,7 +281,7 @@ void ha_transmit_topic(const char *stat_t, const char *value) } char item_topic[128]; - osSprintf(item_topic, stat_t, mqtt_client); + osSprintf(item_topic, stat_t, settings_get_string("mqtt.topic")); if (!mqtt_publish(item_topic, value)) { @@ -315,9 +304,9 @@ void ha_setup() { memset(&ha_info, 0x00, sizeof(ha_info)); - osSprintf(ha_info.name, "%s", mqtt_client); + osSprintf(ha_info.name, "%s", settings_get_string("mqtt.topic")); osSprintf(ha_info.id, "%s", "teddyCloud"); - osSprintf(ha_info.cu, "http://%s/", "teddyCloud"); + osSprintf(ha_info.cu, "%s", settings_get_string("mqtt.host_url")); osSprintf(ha_info.mf, "RevvoX"); osSprintf(ha_info.mdl, "%s", "teddyCloud"); osSprintf(ha_info.sw, "" BUILD_GIT_TAG " (" BUILD_GIT_SHORT_SHA ")"); @@ -331,17 +320,17 @@ void ha_connected() char item_topic[128]; if (ha_info.entities[pos].cmd_t && ha_info.entities[pos].received) { - osSprintf(item_topic, ha_info.entities[pos].cmd_t, mqtt_client); + osSprintf(item_topic, ha_info.entities[pos].cmd_t, settings_get_string("mqtt.topic")); mqtt_subscribe(item_topic); } if (ha_info.entities[pos].rgb_t && ha_info.entities[pos].rgb_received) { - osSprintf(item_topic, ha_info.entities[pos].rgb_t, mqtt_client); + osSprintf(item_topic, ha_info.entities[pos].rgb_t, settings_get_string("mqtt.topic")); mqtt_subscribe(item_topic); } if (ha_info.entities[pos].fx_cmd_t && ha_info.entities[pos].fx_received) { - osSprintf(item_topic, ha_info.entities[pos].fx_cmd_t, mqtt_client); + osSprintf(item_topic, ha_info.entities[pos].fx_cmd_t, settings_get_string("mqtt.topic")); mqtt_subscribe(item_topic); } } diff --git a/src/mqtt.c b/src/mqtt.c index 2eb80c78..05434be7 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -17,9 +17,29 @@ bool_t mqttConnected = FALSE; error_t error; MqttClientContext mqtt_context; -static const char *mqtt_client = "teddyCloud"; bool mqtt_fail = false; +#define MQTT_TOPIC_STRING_LENGTH 128 + +char *mqtt_prefix(const char *path) +{ + static char buffer[MQTT_TOPIC_STRING_LENGTH]; + + osSnprintf(buffer, sizeof(buffer), "%s/%s", settings_get_string("mqtt.topic"), path); + + return buffer; +} + +error_t mqtt_sendEvent(const char *eventname, const char *content) +{ + char topic[MQTT_TOPIC_STRING_LENGTH]; + + osSnprintf(topic, sizeof(topic), "%s/event/%s", settings_get_string("mqtt.topic"), eventname); + mqttClientPublish(&mqtt_context, topic, content, osStrlen(content), MQTT_QOS_LEVEL_0, false, NULL); + + return NO_ERROR; +} + void mqttTestPublishCallback(MqttClientContext *context, const char_t *topic, const uint8_t *message, size_t length, bool_t dup, MqttQosLevel qos, bool_t retain, uint16_t packetId) @@ -37,16 +57,6 @@ void mqttTestPublishCallback(MqttClientContext *context, ha_received((char *)topic, (const char *)message); } -error_t mqtt_sendEvent(const char *eventname, const char *content) -{ - char topic[128]; - - osSnprintf(topic, sizeof(topic), "%s/event/%s", mqtt_client, eventname); - mqttClientPublish(&mqtt_context, topic, content, osStrlen(content), MQTT_QOS_LEVEL_0, false, NULL); - - return NO_ERROR; -} - bool mqtt_publish(const char *item_topic, const char *content) { mqttClientPublish(&mqtt_context, item_topic, content, osStrlen(content), MQTT_QOS_LEVEL_0, false, NULL); @@ -78,8 +88,7 @@ error_t mqttConnect(MqttClientContext *mqtt_context) mqttClientSetIdentifier(mqtt_context, settings_get_string("mqtt.identification")); mqttClientSetAuthInfo(mqtt_context, settings_get_string("mqtt.username"), settings_get_string("mqtt.password")); - mqttClientSetWillMessage(mqtt_context, "teddyCloud/status", - "offline", 7, MQTT_QOS_LEVEL_1, FALSE); + mqttClientSetWillMessage(mqtt_context, mqtt_prefix("status"), "offline", 7, MQTT_QOS_LEVEL_1, FALSE); do { @@ -110,17 +119,15 @@ error_t mqttConnect(MqttClientContext *mqtt_context) if (error) { - TRACE_ERROR("Failed to connect to ipv4 address %d\r\n", error); + TRACE_ERROR("Failed to connect to MQTT: %d\r\n", error); break; } - error = mqttClientSubscribe(mqtt_context, - "teddyCloud/*", MQTT_QOS_LEVEL_1, NULL); + error = mqttClientSubscribe(mqtt_context, mqtt_prefix("*"), MQTT_QOS_LEVEL_1, NULL); if (error) break; - error = mqttClientPublish(mqtt_context, "teddyCloud/status", - "online", 6, MQTT_QOS_LEVEL_1, TRUE, NULL); + error = mqttClientPublish(mqtt_context, mqtt_prefix("status"), "online", 6, MQTT_QOS_LEVEL_1, TRUE, NULL); if (error) break; @@ -176,7 +183,7 @@ void mqtt_publish_string(const char *name, const char *value) { char path_buffer[128]; - sprintf(path_buffer, name, mqtt_client); + sprintf(path_buffer, name, settings_get_string("mqtt.topic")); if (!mqtt_publish(path_buffer, value)) { @@ -189,7 +196,7 @@ void mqtt_publish_float(const char *name, float value) char path_buffer[128]; char buffer[32]; - sprintf(path_buffer, name, mqtt_client); + sprintf(path_buffer, name, settings_get_string("mqtt.topic")); sprintf(buffer, "%0.4f", value); if (!mqtt_publish(path_buffer, buffer)) @@ -207,7 +214,7 @@ void mqtt_publish_int(const char *name, uint32_t value) { return; } - sprintf(path_buffer, name, mqtt_client); + sprintf(path_buffer, name, settings_get_string("mqtt.topic")); sprintf(buffer, "%d", value); if (!mqtt_publish(path_buffer, buffer)) diff --git a/src/settings.c b/src/settings.c index e712b8c0..ba4a022a 100644 --- a/src/settings.c +++ b/src/settings.c @@ -113,10 +113,11 @@ static void option_map_init(uint8_t settingsId) OPTION_BOOL("mqtt.enabled", &settings->mqtt.enabled, FALSE, "Enable MQTT service") OPTION_STRING("mqtt.hostname", &settings->mqtt.hostname, "", "MQTT hostname") OPTION_UNSIGNED("mqtt.port", &settings->mqtt.port, 1833, 1, 65535, "Port of MQTT server") - OPTION_STRING("mqtt.username", &settings->mqtt.username, "", "Username") OPTION_STRING("mqtt.password", &settings->mqtt.password, "", "Password") OPTION_STRING("mqtt.identification", &settings->mqtt.identification, "", "Client identification") + OPTION_STRING("mqtt.topic", &settings->mqtt.topic, "teddyCloud", "Topic prefix") + OPTION_STRING("mqtt.host_url", &settings->mqtt.host_url, "http://localhost/", "URL to teddyCloud server") OPTION_END() if (Option_Map_Overlay[settingsId] == NULL) From 07a498262d48cae87903dfb16a73c9e3a62011ff Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 14:56:56 +0200 Subject: [PATCH 011/126] use os* functions --- src/home_assistant.c | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/src/home_assistant.c b/src/home_assistant.c index f5eb2b04..08f36cb9 100644 --- a/src/home_assistant.c +++ b/src/home_assistant.c @@ -1,6 +1,7 @@ #include +#include "platform.h" #include "debug.h" #include "stats.h" #include "home_assistant.h" @@ -49,7 +50,7 @@ void ha_addstrarray(char *json_str, const char *name, const char *value, bool la values_buf[out_pos++] = '\000'; osSnprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": [%s]%c ", name, values_buf, (last ? ' ' : ',')); - strcat(json_str, tmp_buf); + osStrcat(json_str, tmp_buf); } } @@ -60,7 +61,7 @@ void ha_addstr(char *json_str, const char *name, const char *value, bool last) if (value && strlen(value) > 0) { osSnprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%s\"%c ", name, value, (last ? ' ' : ',')); - strcat(json_str, tmp_buf); + osStrcat(json_str, tmp_buf); } } @@ -90,7 +91,7 @@ void ha_addfloat(char *json_str, const char *name, float value, bool last) char tmp_buf[64]; osSnprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%f\"%c ", name, value, (last ? ' ' : ',')); - strcat(json_str, tmp_buf); + osStrcat(json_str, tmp_buf); } void ha_addint(char *json_str, const char *name, int value, bool last) @@ -98,12 +99,12 @@ void ha_addint(char *json_str, const char *name, int value, bool last) char tmp_buf[64]; osSnprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%d\"%c ", name, value, (last ? ' ' : ',')); - strcat(json_str, tmp_buf); + osStrcat(json_str, tmp_buf); } void ha_publish() { - char *json_str = (char *)malloc(1024); + char *json_str = (char *)osAllocMem(1024); char mqtt_path[128]; char uniq_id[128]; @@ -152,7 +153,7 @@ void ha_publish() // TRACE_INFO("[HA] mqtt_path %s\n", mqtt_path); - strcpy(json_str, "{"); + osStrcpy(json_str, "{"); ha_addstr(json_str, "name", ha_info.entities[pos].name, false); ha_addstr(json_str, "uniq_id", uniq_id, false); ha_addstr(json_str, "dev_cla", ha_info.entities[pos].dev_class, false); @@ -181,14 +182,14 @@ void ha_publish() break; } - strcat(json_str, "\"dev\": {"); + osStrcat(json_str, "\"dev\": {"); ha_addstr(json_str, "name", ha_info.name, false); ha_addstr(json_str, "ids", ha_info.id, false); ha_addstr(json_str, "cu", ha_info.cu, false); ha_addstr(json_str, "mf", ha_info.mf, false); ha_addstr(json_str, "mdl", ha_info.mdl, false); ha_addstr(json_str, "sw", ha_info.sw, true); - strcat(json_str, "}}"); + osStrcat(json_str, "}}"); // TRACE_INFO("[HA] topic '%s'\n", mqtt_path); // TRACE_INFO("[HA] content '%s'\n", json_str); @@ -199,8 +200,7 @@ void ha_publish() } } - TRACE_INFO("[HA] done\n"); - free(json_str); + osFreeMem(json_str); } void ha_received(char *topic, const char *payload) @@ -212,7 +212,7 @@ void ha_received(char *topic, const char *payload) if (ha_info.entities[pos].cmd_t && ha_info.entities[pos].received) { osSprintf(item_topic, ha_info.entities[pos].cmd_t, settings_get_string("mqtt.topic")); - if (!strcmp(topic, item_topic)) + if (!osStrcmp(topic, item_topic)) { ha_info.entities[pos].received(&ha_info.entities[pos], ha_info.entities[pos].received_ctx, payload); @@ -226,7 +226,7 @@ void ha_received(char *topic, const char *payload) if (ha_info.entities[pos].rgb_t && ha_info.entities[pos].rgb_received) { osSprintf(item_topic, ha_info.entities[pos].rgb_t, settings_get_string("mqtt.topic")); - if (!strcmp(topic, item_topic)) + if (!osStrcmp(topic, item_topic)) { ha_info.entities[pos].rgb_received(&ha_info.entities[pos], ha_info.entities[pos].rgb_received_ctx, payload); @@ -240,7 +240,7 @@ void ha_received(char *topic, const char *payload) if (ha_info.entities[pos].fx_cmd_t && ha_info.entities[pos].fx_received) { osSprintf(item_topic, ha_info.entities[pos].fx_cmd_t, settings_get_string("mqtt.topic")); - if (!strcmp(topic, item_topic)) + if (!osStrcmp(topic, item_topic)) { ha_info.entities[pos].fx_received(&ha_info.entities[pos], ha_info.entities[pos].fx_received_ctx, payload); @@ -302,7 +302,7 @@ void ha_transmit_all() void ha_setup() { - memset(&ha_info, 0x00, sizeof(ha_info)); + osMemset(&ha_info, 0x00, sizeof(ha_info)); osSprintf(ha_info.name, "%s", settings_get_string("mqtt.topic")); osSprintf(ha_info.id, "%s", "teddyCloud"); @@ -364,7 +364,7 @@ void ha_add(t_ha_entity *entity) { return; } - memcpy(&ha_info.entities[ha_info.entitiy_count++], entity, sizeof(t_ha_entity)); + osMemcpy(&ha_info.entities[ha_info.entitiy_count++], entity, sizeof(t_ha_entity)); } int ha_parse_index(const char *options, const char *message) @@ -378,7 +378,7 @@ int ha_parse_index(const char *options, const char *message) char tmp_buf[128]; char *cur_elem = tmp_buf; - strncpy(tmp_buf, options, sizeof(tmp_buf)); + osStrncpy(tmp_buf, options, sizeof(tmp_buf)); while (true) { @@ -387,7 +387,7 @@ int ha_parse_index(const char *options, const char *message) { *next_elem = '\000'; } - if (!strcmp(cur_elem, message)) + if (!osStrcmp(cur_elem, message)) { return pos; } @@ -413,7 +413,7 @@ void ha_get_index(const char *options, int index, char *text) char tmp_buf[128]; char *cur_elem = tmp_buf; - strncpy(tmp_buf, options, sizeof(tmp_buf)); + osStrncpy(tmp_buf, options, sizeof(tmp_buf)); while (true) { @@ -424,7 +424,7 @@ void ha_get_index(const char *options, int index, char *text) } if (pos == index) { - strcpy(text, cur_elem); + osStrcpy(text, cur_elem); return; } From 2a6a2a8bffb901582703ef3311e20bc1c1136a1c Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 15:08:08 +0200 Subject: [PATCH 012/126] handle event sending a bit differently --- src/handler_rtnl.c | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index af6feb6f..e7f33de6 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -262,13 +262,11 @@ void rtnlEvent(TonieRtnlRPC *rpc) if (rpc->log2) { - const char *eventname = NULL; char buffer[33]; /* ESP32 sends tag IDs, even if unknown */ if (rpc->log2->function_group == 15 && rpc->log2->function == 15452) { - eventname = "TagInvalid"; if (rpc->log2->field6.len == 8) { for (size_t i = 0; i < rpc->log2->field6.len; i++) @@ -276,10 +274,12 @@ void rtnlEvent(TonieRtnlRPC *rpc) osSprintf(&buffer[i * 2], "%02X", rpc->log2->field6.data[(i + 4) % 8]); } } + sse_sendEvent("TagInvalid", buffer, true); + mqtt_sendEvent("TagInvalid", buffer); + mqtt_sendEvent("TagValid", ""); } else if (rpc->log2->function_group == 15 && rpc->log2->function == 16065) { - eventname = "TagValid"; if (rpc->log2->field6.len == 8) { for (size_t i = 0; i < rpc->log2->field6.len; i++) @@ -287,11 +287,13 @@ void rtnlEvent(TonieRtnlRPC *rpc) osSprintf(&buffer[i * 2], "%02X", rpc->log2->field6.data[(i + 4) % 8]); } } + sse_sendEvent("TagValid", buffer, true); + mqtt_sendEvent("TagValid", buffer); + mqtt_sendEvent("TagInvalid", ""); } /* CC also. this is for valid tags */ else if (rpc->log2->function_group == 15 && rpc->log2->function == 8646) { - eventname = "TagInvalid"; if (rpc->log2->field6.len == 8) { for (size_t i = 0; i < rpc->log2->field6.len; i++) @@ -299,10 +301,12 @@ void rtnlEvent(TonieRtnlRPC *rpc) osSprintf(&buffer[i * 2], "%02X", rpc->log2->field6.data[(i + 4) % 8]); } } + sse_sendEvent("TagInvalid", buffer, true); + mqtt_sendEvent("TagInvalid", buffer); + mqtt_sendEvent("TagValid", ""); } else if (rpc->log2->function_group == 15 && rpc->log2->function == 8627) { - eventname = "TagValid"; if (rpc->log2->field6.len == 8) { for (size_t i = 0; i < rpc->log2->field6.len; i++) @@ -310,24 +314,23 @@ void rtnlEvent(TonieRtnlRPC *rpc) osSprintf(&buffer[i * 2], "%02X", rpc->log2->field6.data[(i + 4) % 8]); } } + sse_sendEvent("TagValid", buffer, true); + mqtt_sendEvent("TagValid", buffer); + mqtt_sendEvent("TagInvalid", ""); } else if (rpc->log2->function_group == 12 && rpc->log2->function == 15427) { - eventname = "BoxTilt"; int32_t angle = read_little_endian(rpc->log2->field6.data); osSprintf(buffer, "%d", angle); + sse_sendEvent("BoxTilt", buffer, true); + mqtt_sendEvent("BoxTilt", buffer); } else if (rpc->log2->function_group == 12 && rpc->log2->function == 15426) { - eventname = "BoxTilt"; int32_t angle = read_little_endian(rpc->log2->field6.data); osSprintf(buffer, "%d", angle); - } - - if (eventname) - { - sse_sendEvent(eventname, buffer, true); - mqtt_sendEvent(eventname, buffer); + sse_sendEvent("BoxTilt", buffer, true); + mqtt_sendEvent("BoxTilt", buffer); } } } From d069285f11a5ba1b6f81417a182687a1c1ad60c4 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 15:09:53 +0200 Subject: [PATCH 013/126] fix comment --- src/handler_rtnl.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index e7f33de6..a30ab328 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -291,7 +291,7 @@ void rtnlEvent(TonieRtnlRPC *rpc) mqtt_sendEvent("TagValid", buffer); mqtt_sendEvent("TagInvalid", ""); } - /* CC also. this is for valid tags */ + /* CC also sends messages */ else if (rpc->log2->function_group == 15 && rpc->log2->function == 8646) { if (rpc->log2->field6.len == 8) From 30c9fae7e973fe86c8683d2756a55bc4358a9000 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 17:37:02 +0200 Subject: [PATCH 014/126] added ESP32 volume levels --- src/handler_rtnl.c | 12 ++++++++++++ src/mqtt.c | 15 +++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index a30ab328..53130cbb 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -332,6 +332,18 @@ void rtnlEvent(TonieRtnlRPC *rpc) sse_sendEvent("BoxTilt", buffer, true); mqtt_sendEvent("BoxTilt", buffer); } + else if (rpc->log2->function_group == 27 && rpc->log2->function == 15524) + { + /* 963C0000 D8FFFFFF 00000000 */ + int32_t volumedB = read_little_endian(&rpc->log2->field6.data[4]); + int32_t volumeLevel = read_little_endian(&rpc->log2->field6.data[8]); + osSprintf(buffer, "%d", volumeLevel); + sse_sendEvent("VolumeLevel", buffer, true); + mqtt_sendEvent("VolumeLevel", buffer); + osSprintf(buffer, "%d", volumedB); + sse_sendEvent("VolumedB", buffer, true); + mqtt_sendEvent("VolumedB", buffer); + } } } diff --git a/src/mqtt.c b/src/mqtt.c index 05434be7..36da462b 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -300,4 +300,19 @@ void mqtt_init() entity.type = ha_sensor; entity.stat_t = "%s/event/Playback"; ha_add(&entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "VolumeLevel"; + entity.name = "Volume Level"; + entity.type = ha_sensor; + entity.stat_t = "%s/event/VolumeLevel"; + ha_add(&entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "VolumedB"; + entity.name = "Volume dB"; + entity.type = ha_sensor; + entity.stat_t = "%s/event/VolumedB"; + entity.unit_of_meas = "dB"; + ha_add(&entity); } From ebec7a7e3550e65412cc9b36fe54bee328fc896e Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 18:19:03 +0200 Subject: [PATCH 015/126] fix possible disconnect issues with claim handler --- src/handler_cloud.c | 15 ++++++++++++++- src/platform/platform_linux.c | 4 ++-- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/src/handler_cloud.c b/src/handler_cloud.c index 687a356c..049929f8 100644 --- a/src/handler_cloud.c +++ b/src/handler_cloud.c @@ -136,6 +136,7 @@ error_t handleCloudLog(HttpConnection *connection, const char_t *uri, const char error_t handleCloudClaim(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx) { + error_t ret = NO_ERROR; char ruid[17]; uint8_t *token = connection->private.authentication_token; @@ -160,6 +161,11 @@ error_t handleCloudClaim(HttpConnection *connection, const char_t *uri, const ch getContentPathFromCharRUID(ruid, &tonieInfo.contentPath, client_ctx->settings); tonieInfo = getTonieInfo(tonieInfo.contentPath); + /* allow to override HTTP status code if needed */ + bool served = false; + httpPrepareHeader(connection, NULL, 0); + connection->response.statusCode = 200; + if (!tonieInfo.nocloud) { if (checkCustomTonie(ruid, token, client_ctx->settings)) @@ -173,6 +179,7 @@ error_t handleCloudClaim(HttpConnection *connection, const char_t *uri, const ch cbr_ctx_t ctx; req_cbr_t cbr = getCloudCbr(connection, uri, queryString, V1_CLAIM, &ctx, client_ctx); cloud_request_get(NULL, 0, uri, queryString, token, &cbr); + served = true; } else { @@ -183,9 +190,15 @@ error_t handleCloudClaim(HttpConnection *connection, const char_t *uri, const ch { TRACE_INFO(" >> nocloud content, nothing forwarded\r\n"); } + freeTonieInfo(&tonieInfo); - return NO_ERROR; + if (!served) + { + ret = httpWriteResponse(connection, NULL, 0, false); + } + + return ret; } error_t handleCloudContent(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx, bool_t noPassword) diff --git a/src/platform/platform_linux.c b/src/platform/platform_linux.c index 8130a5e4..63a2e954 100644 --- a/src/platform/platform_linux.c +++ b/src/platform/platform_linux.c @@ -446,8 +446,8 @@ uint_t tcpWaitForEvents(Socket *socket, uint_t eventMask, systime_t timeout) FD_SET(sock->sockfd, &read_fds); // Set timeout. - tv.tv_sec = 0; - tv.tv_usec = timeout * 1000; + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; // Wait for the event. int result = select(sock->sockfd + 1, &read_fds, NULL, NULL, &tv); From 4fb8eaf31e6bc649da43a0168d30fcfd5a5bd582 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 18:50:32 +0200 Subject: [PATCH 016/126] added FlexTonies, which can get reassigned dynamically --- include/settings.h | 3 ++ src/handler_cloud.c | 129 +++++++++++++++++++++++++++++++------------- src/settings.c | 3 ++ 3 files changed, 97 insertions(+), 38 deletions(-) diff --git a/include/settings.h b/include/settings.h index 3abd85b3..d9edeb75 100644 --- a/include/settings.h +++ b/include/settings.h @@ -118,6 +118,9 @@ typedef struct settings_cert_opt_t server_cert; settings_cert_opt_t client_cert; char *allowOrigin; + + bool flex_enabled; + char *flex_uid; } settings_core_t; typedef struct diff --git a/src/handler_cloud.c b/src/handler_cloud.c index 049929f8..d4911301 100644 --- a/src/handler_cloud.c +++ b/src/handler_cloud.c @@ -107,6 +107,7 @@ bool checkCustomTonie(char *ruid, uint8_t *token, settings_t *settings) } return false; } + void markCustomTonie(tonie_info_t *tonieInfo) { int maxLen = 255; @@ -123,6 +124,22 @@ void markCustomTonie(tonie_info_t *tonieInfo) TRACE_INFO("Marked custom tonie with file %s\r\n", contentPathDot); } +void markLiveTonie(tonie_info_t *tonieInfo) +{ + int maxLen = 255; + char subDir[256]; + char contentPathDot[256]; + + snprintf(subDir, maxLen, "%s", tonieInfo->contentPath); + subDir[osStrlen(subDir) - 9] = '\0'; + fsCreateDir(subDir); + snprintf(contentPathDot, maxLen, "%s.live", tonieInfo->contentPath); + + FsFile *file = fsOpenFile(contentPathDot, FS_FILE_MODE_WRITE | FS_FILE_MODE_CREATE); + fsCloseFile(file); + TRACE_INFO("Marked custom tonie with file %s\r\n", contentPathDot); +} + error_t handleCloudLog(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx) { if (settings_get_bool("cloud.enabled") && settings_get_bool("cloud.enableV1Log")) @@ -241,52 +258,88 @@ error_t handleCloudContent(HttpConnection *connection, const char_t *uri, const } settings_t *settings = get_settings(); - if (!tonieInfo.exists && osStrlen(settings->internal.assign_unknown) > 0) + + bool assignFile = false; + bool setLive = false; + + if (osStrlen(settings->internal.assign_unknown) > 0) { - char *path = settings->internal.assign_unknown; - if (fsFileExists(path)) + if (!tonieInfo.exists) { - tonie_info_t tonieInfoAssign = getTonieInfo(path); - if (tonieInfoAssign.valid) - { - char *dir = strdup(tonieInfo.contentPath); - dir[osStrlen(dir) - 8] = '\0'; - fsCreateDir(dir); - osFreeMem(dir); + assignFile = true; + } - if ((error = fsCopyFile(path, tonieInfo.contentPath, false)) == NO_ERROR) - { - char *oldFile = strdup(tonieInfo.contentPath); - freeTonieInfo(&tonieInfo); - tonieInfo = getTonieInfo(oldFile); - free(oldFile); - if (tonieInfo.valid) - { - TRACE_INFO("Assigned unknown set to %s\r\n", path); - settings_set_string("internal.assign_unknown", ""); - } - else - { - TRACE_ERROR("TAF header of assign unknown invalid, delete it again: %s\r\n", tonieInfo.contentPath) - fsDeleteFile(tonieInfo.contentPath); - } - } - else - { - freeTonieInfo(&tonieInfoAssign); - TRACE_ERROR("Could not copy %s to %s, error=%" PRIu32 "\r\n", path, tonieInfo.contentPath, error) - } + if (settings->core.flex_enabled) + { + char uid[17]; + for (int pos = 0; pos < 16; pos += 2) + { + osStrncpy(&uid[pos], &ruid[14 - pos], 2); } - else + uid[16] = 0; + if (!osStrcasecmp(uid, settings->core.flex_uid)) { - freeTonieInfo(&tonieInfoAssign); - TRACE_ERROR("TAF header of assign unknown invalid: %s\r\n", path) + TRACE_INFO(" >> this is a flex tonie\r\n"); + assignFile = true; + setLive = true; } } - else + } + + if (assignFile) + { + do { - TRACE_ERROR("Assign unknown path not available: %s\r\n", path) - } + char *path = settings->internal.assign_unknown; + if (!fsFileExists(path)) + { + TRACE_ERROR("Path to assign not available: %s\r\n", path); + break; + } + + tonie_info_t tonieInfoAssign = getTonieInfo(path); + if (!tonieInfoAssign.valid) + { + freeTonieInfo(&tonieInfoAssign); + TRACE_ERROR("TAF header invalid: %s\r\n", path); + break; + } + + char *dir = strdup(tonieInfo.contentPath); + dir[osStrlen(dir) - 8] = '\0'; + fsCreateDir(dir); + osFreeMem(dir); + + error = fsCopyFile(path, tonieInfo.contentPath, true); + if (error != NO_ERROR) + { + freeTonieInfo(&tonieInfoAssign); + TRACE_ERROR("Could not copy %s to %s, error=%" PRIu32 "\r\n", path, tonieInfo.contentPath, error); + break; + } + + char *oldFile = strdup(tonieInfo.contentPath); + freeTonieInfo(&tonieInfo); + tonieInfo = getTonieInfo(oldFile); + free(oldFile); + + if (!tonieInfo.valid) + { + TRACE_ERROR("TAF headerinvalid, delete it again: %s\r\n", tonieInfo.contentPath); + fsDeleteFile(tonieInfo.contentPath); + break; + } + + TRACE_INFO("Assigned to %s\r\n", path); + + if (setLive) + { + markLiveTonie(&tonieInfo); + } + + } while (0); + + settings_set_string("internal.assign_unknown", ""); error = NO_ERROR; } diff --git a/src/settings.c b/src/settings.c index ba4a022a..251a2c4c 100644 --- a/src/settings.c +++ b/src/settings.c @@ -54,6 +54,9 @@ static void option_map_init(uint8_t settingsId) OPTION_STRING("core.allowOrigin", &settings->core.allowOrigin, "", "Set CORS Access-Control-Allow-Origin header") + OPTION_BOOL("core.flex_enabled", &settings->core.flex_enabled, TRUE, "When enabled this UID always gets assigned the audio assigned from web interface") + OPTION_STRING("core.flex_uid", &settings->core.flex_uid, "", "UID which shall get selected audio files assigned") + OPTION_INTERNAL_STRING("internal.server.ca", &settings->internal.server.ca, "", "Server CA data") OPTION_INTERNAL_STRING("internal.server.crt", &settings->internal.server.crt, "", "Server certificate data") OPTION_INTERNAL_STRING("internal.server.key", &settings->internal.server.key, "", "Server key data") From 468d9b471a41388cceda521401bdc5a7e2e7165d Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 18:58:19 +0200 Subject: [PATCH 017/126] automatically handle FlexTonies as .live --- src/handler_cloud.c | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/handler_cloud.c b/src/handler_cloud.c index d4911301..56bdc149 100644 --- a/src/handler_cloud.c +++ b/src/handler_cloud.c @@ -436,6 +436,9 @@ error_t handleCloudFreshnessCheck(HttpConnection *connection, const char_t *uri, { uint8_t data[BODY_BUFFER_SIZE]; size_t size; + + settings_t *settings = get_settings(); + if (BODY_BUFFER_SIZE <= connection->request.byteCount) { TRACE_ERROR("Body size %zu bigger than buffer size %i bytes", connection->request.byteCount, BODY_BUFFER_SIZE); @@ -505,12 +508,21 @@ error_t handleCloudFreshnessCheck(HttpConnection *connection, const char_t *uri, freshReqCloud.tonie_infos[freshReqCloud.n_tonie_infos++] = freshReq->tonie_infos[i]; } + bool isFlex = false; + + char uid[17]; + osSprintf(uid, "%016" PRIX64, freshReq->tonie_infos[i]->uid); + + if (settings->core.flex_enabled && !osStrcasecmp(settings->core.flex_uid, uid)) + { + isFlex = true; + } (void)custom_box; (void)custom_server; TRACE_INFO(" uid: %016" PRIX64 ", nocloud: %d, live: %d, updated: %d, audioid: %08X (%s%s)", freshReq->tonie_infos[i]->uid, tonieInfo.nocloud, - tonieInfo.live, + tonieInfo.live || isFlex, tonieInfo.updated, freshReq->tonie_infos[i]->audio_id, date_buffer_box, @@ -526,7 +538,7 @@ error_t handleCloudFreshnessCheck(HttpConnection *connection, const char_t *uri, TRACE_INFO_RESUME("\r\n"); - if (tonieInfo.live || tonieInfo.updated) + if (tonieInfo.live || tonieInfo.updated || isFlex) { freshResp.tonie_marked[freshResp.n_tonie_marked++] = freshReq->tonie_infos[i]->uid; } From 78cdf2b8f77c05ee85ba76ffe0e97708690d2290 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 22:42:34 +0200 Subject: [PATCH 018/126] properly sanitize paths, code cleanups --- src/handler_api.c | 144 ++++++++++++++++++++++++---------------------- 1 file changed, 75 insertions(+), 69 deletions(-) diff --git a/src/handler_api.c b/src/handler_api.c index a4489f20..096829d0 100644 --- a/src/handler_api.c +++ b/src/handler_api.c @@ -28,6 +28,24 @@ typedef enum #define SAVE_SIZE 80 #define BUFFER_SIZE (DATA_SIZE + SAVE_SIZE) +void pathSafeCanonicalize(char *path) +{ + if (!path || osStrlen(path) == 0) + { + return; + } + + pathCanonicalize(path); + + const char *pattern = "../"; + const size_t pattern_len = osStrlen(pattern); + + while (osStrncmp(path, pattern, pattern_len) == 0) + { + osMemmove(path, path + pattern_len, 1 + osStrlen(path + pattern_len)); + } +} + bool queryGet(const char *query, const char *key, char *data, size_t data_len); error_t handleApiAssignUnknown(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx) @@ -72,11 +90,11 @@ error_t handleApiAssignUnknown(HttpConnection *connection, const char_t *uri, co if (ret == NO_ERROR) { - pathCanonicalize(path); - char *pathAbsolute = osAllocMem(strlen(rootPath) + osStrlen(path) + 2); + pathSafeCanonicalize(path); + char *pathAbsolute = osAllocMem(osStrlen(rootPath) + osStrlen(path) + 2); osSprintf(pathAbsolute, "%s/%s", rootPath, path); - pathCanonicalize(pathAbsolute); + pathSafeCanonicalize(pathAbsolute); TRACE_INFO("Set '%s' for next unknown request\r\n", pathAbsolute); @@ -166,32 +184,32 @@ error_t handleApiTrigger(HttpConnection *connection, const char_t *uri, const ch const char *item = &uri[5]; char response[256]; - sprintf(response, "FAILED"); + osSprintf(response, "FAILED"); if (!strcmp(item, "triggerExit")) { TRACE_INFO("Triggered Exit\r\n"); settings_set_bool("internal.exit", TRUE); settings_set_signed("internal.returncode", RETURNCODE_USER_QUIT); - sprintf(response, "OK"); + osSprintf(response, "OK"); } else if (!strcmp(item, "triggerRestart")) { TRACE_INFO("Triggered Restart\r\n"); settings_set_bool("internal.exit", TRUE); settings_set_signed("internal.returncode", RETURNCODE_USER_RESTART); - sprintf(response, "OK"); + osSprintf(response, "OK"); } else if (!strcmp(item, "triggerReloadConfig")) { TRACE_INFO("Triggered ReloadConfig\r\n"); - sprintf(response, "OK"); + osSprintf(response, "OK"); settings_load(); } else if (!strcmp(item, "triggerWriteConfig")) { TRACE_INFO("Triggered WriteConfig\r\n"); - sprintf(response, "OK"); + osSprintf(response, "OK"); settings_save(); } @@ -217,20 +235,20 @@ error_t handleApiGet(HttpConnection *connection, const char_t *uri, const char_t switch (opt->type) { case TYPE_BOOL: - sprintf(response, "%s", settings_get_bool(item) ? "true" : "false"); + osSprintf(response, "%s", settings_get_bool(item) ? "true" : "false"); break; case TYPE_HEX: case TYPE_UNSIGNED: - sprintf(response, "%d", settings_get_unsigned(item)); + osSprintf(response, "%d", settings_get_unsigned(item)); break; case TYPE_SIGNED: - sprintf(response, "%d", settings_get_signed(item)); + osSprintf(response, "%d", settings_get_signed(item)); break; case TYPE_STRING: response_ptr = settings_get_string(item); break; case TYPE_FLOAT: - sprintf(response, "%f", settings_get_float(item)); + osSprintf(response, "%f", settings_get_float(item)); break; default: break; @@ -247,7 +265,7 @@ error_t handleApiGet(HttpConnection *connection, const char_t *uri, const char_t error_t handleApiSet(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx) { char response[256]; - sprintf(response, "ERROR"); + osSprintf(response, "ERROR"); const char *item = &uri[9]; char_t data[BODY_BUFFER_SIZE]; @@ -269,67 +287,47 @@ error_t handleApiSet(HttpConnection *connection, const char_t *uri, const char_t TRACE_INFO("Setting: '%s' to '%s'\r\n", item, data); setting_item_t *opt = settings_get_by_name(item); + bool success = false; + if (opt) { switch (opt->type) { case TYPE_BOOL: { - if (settings_set_bool(item, !strcasecmp(data, "true"))) - { - osStrcpy(response, "OK"); - } + success = settings_set_bool(item, !strcasecmp(data, "true")); break; } case TYPE_STRING: { - if (settings_set_string(item, data)) - { - osStrcpy(response, "OK"); - } + success = settings_set_string(item, data); break; } case TYPE_HEX: { uint32_t value = strtoul(data, NULL, 16); - - if (settings_set_unsigned(item, value)) - { - osStrcpy(response, "OK"); - } + success = settings_set_unsigned(item, value); break; } case TYPE_UNSIGNED: { uint32_t value = strtoul(data, NULL, 10); - - if (settings_set_unsigned(item, value)) - { - osStrcpy(response, "OK"); - } + success = settings_set_unsigned(item, value); break; } case TYPE_SIGNED: { int32_t value = strtol(data, NULL, 10); - - if (settings_set_signed(item, value)) - { - osStrcpy(response, "OK"); - } + success = settings_set_signed(item, value); break; } case TYPE_FLOAT: { float value = strtof(data, NULL); - - if (settings_set_float(item, value)) - { - osStrcpy(response, "OK"); - } + success = settings_set_float(item, value); break; } @@ -339,9 +337,13 @@ error_t handleApiSet(HttpConnection *connection, const char_t *uri, const char_t } else { - TRACE_ERROR("Setting '%s' is unknown", item); } + + if (success) + { + osStrcpy(response, "OK"); + } } httpPrepareHeader(connection, "text/plain; charset=utf-8", 0); @@ -465,9 +467,13 @@ error_t handleApiFileIndex(HttpConnection *connection, const char_t *uri, const osStrcpy(path, "/"); } - snprintf(pathAbsolute, sizeof(pathAbsolute), "%s/%s", rootPath, path); + pathSafeCanonicalize(path); + + osSnprintf(pathAbsolute, sizeof(pathAbsolute), "%s/%s", rootPath, path); pathAbsolute[sizeof(pathAbsolute) - 1] = 0; + pathSafeCanonicalize(pathAbsolute); + int pos = 0; FsDir *dir = fsOpenDir(pathAbsolute); if (dir == NULL) @@ -496,23 +502,23 @@ error_t handleApiFileIndex(HttpConnection *connection, const char_t *uri, const char dateString[64]; - osSprintf(dateString, " %04" PRIu16 "-%02" PRIu8 "-%02" PRIu8 ", %02" PRIu8 ":%02" PRIu8 ":%02" PRIu8, - entry.modified.year, entry.modified.month, entry.modified.day, - entry.modified.hours, entry.modified.minutes, entry.modified.seconds); + osSnprintf(dateString, sizeof(dateString), " %04" PRIu16 "-%02" PRIu8 "-%02" PRIu8 ", %02" PRIu8 ":%02" PRIu8 ":%02" PRIu8, + entry.modified.year, entry.modified.month, entry.modified.day, + entry.modified.hours, entry.modified.minutes, entry.modified.seconds); char filePathAbsolute[384]; - snprintf(filePathAbsolute, sizeof(filePathAbsolute), "%s/%s", pathAbsolute, entry.name); + osSnprintf(filePathAbsolute, sizeof(filePathAbsolute), "%s/%s", pathAbsolute, entry.name); char desc[64]; desc[0] = 0; tonie_info_t tafInfo = getTonieInfo(filePathAbsolute); if (tafInfo.valid) { - snprintf(desc, sizeof(desc), "TAF:%08X:", tafInfo.tafHeader->audio_id); + osSnprintf(desc, sizeof(desc), "TAF:%08X:", tafInfo.tafHeader->audio_id); for (int pos = 0; pos < tafInfo.tafHeader->sha1_hash.len; pos++) { char tmp[3]; - sprintf(tmp, "%02X", tafInfo.tafHeader->sha1_hash.data[pos]); + osSprintf(tmp, "%02X", tafInfo.tafHeader->sha1_hash.data[pos]); osStrcat(desc, tmp); } } @@ -579,13 +585,13 @@ FsFile *multipartStart(const char *rootPath, const char *filename, char *message // Ensure filename does not contain any directory separators if (strchr(filename, '\\') || strchr(filename, '/')) { - snprintf(message, message_max, "Filename '%s' contains directory separators!", filename); + osSnprintf(message, message_max, "Filename '%s' contains directory separators!", filename); TRACE_ERROR("Filename '%s' contains directory separators!\r\n", filename); return NULL; } char fullPath[1024]; // or a sufficiently large size for your paths - snprintf(fullPath, sizeof(fullPath), "%s/%s", rootPath, filename); + osSnprintf(fullPath, sizeof(fullPath), "%s/%s", rootPath, filename); if (fsFileExists(fullPath)) { @@ -626,7 +632,7 @@ int find_string(const void *haystack, size_t haystack_len, size_t haystack_start for (size_t i = haystack_start; i <= haystack_len - str_len; i++) { - if (memcmp((uint8_t *)haystack + i, str, str_len) == 0) + if (osMemcmp((uint8_t *)haystack + i, str, str_len) == 0) { return i; } @@ -717,12 +723,12 @@ error_t parse_multipart_content(HttpConnection *connection, const char *rootPath } /* when the payload starts with --, then leave */ - if (!memcmp(buffer, "--", 2)) + if (!osMemcmp(buffer, "--", 2)) { TRACE_DEBUG("Received multipart end\r\n"); return NO_ERROR; } - if (memcmp(buffer, "\r\n", 2)) + if (osMemcmp(buffer, "\r\n", 2)) { TRACE_DEBUG("No valid multipart\r\n"); return NO_ERROR; @@ -762,7 +768,7 @@ error_t parse_multipart_content(HttpConnection *connection, const char *rootPath int inLen = fn_end - fn_start; int len = (inLen < sizeof(filename)) ? inLen : (sizeof(filename) - 1); TRACE_INFO("FN length %d %d %d %d\r\n", inLen, len, fn_start, fn_end); - strncpy(filename, &buffer[fn_start], len); + osStrncpy(filename, &buffer[fn_start], len); filename[len] = '\0'; file = multipartStart(rootPath, filename, message, message_max); @@ -881,7 +887,7 @@ error_t handleApiUploadCert(HttpConnection *connection, const char_t *uri, const if (rootPath == NULL || !fsDirExists(rootPath)) { statusCode = 500; - snprintf(message, sizeof(message), "core.certdir not set to a valid path"); + osSnprintf(message, sizeof(message), "core.certdir not set to a valid path"); TRACE_ERROR("core.certdir not set to a valid path\r\n"); } else @@ -890,7 +896,7 @@ error_t handleApiUploadCert(HttpConnection *connection, const char_t *uri, const { case NO_ERROR: statusCode = 200; - snprintf(message, sizeof(message), "OK"); + osSnprintf(message, sizeof(message), "OK"); break; default: statusCode = 500; @@ -976,18 +982,18 @@ error_t handleApiFileUpload(HttpConnection *connection, const char_t *uri, const sanitizePath(path, true); char pathAbsolute[256]; - snprintf(pathAbsolute, sizeof(pathAbsolute), "%s/%s", rootPath, path); + osSnprintf(pathAbsolute, sizeof(pathAbsolute), "%s/%s", rootPath, path); pathAbsolute[sizeof(pathAbsolute) - 1] = 0; uint_t statusCode = 500; char message[256]; - snprintf(message, sizeof(message), "OK"); + osSnprintf(message, sizeof(message), "OK"); if (!fsDirExists(pathAbsolute)) { statusCode = 500; - snprintf(message, sizeof(message), "invalid path: '%s'", path); + osSnprintf(message, sizeof(message), "invalid path: '%s'", path); TRACE_ERROR("invalid path: '%s' -> '%s'\r\n", path, pathAbsolute); } else @@ -1034,20 +1040,20 @@ error_t handleApiDirectoryCreate(HttpConnection *connection, const char_t *uri, sanitizePath(path, true); char pathAbsolute[256 + 2]; - snprintf(pathAbsolute, sizeof(pathAbsolute), "%s/%s", rootPath, path); + osSnprintf(pathAbsolute, sizeof(pathAbsolute), "%s/%s", rootPath, path); pathAbsolute[sizeof(pathAbsolute) - 1] = 0; uint_t statusCode = 200; char message[256 + 64]; - snprintf(message, sizeof(message), "OK"); + osSnprintf(message, sizeof(message), "OK"); error_t err = fsCreateDir(pathAbsolute); if (err != NO_ERROR) { statusCode = 500; - snprintf(message, sizeof(message), "Error creating directory '%s', error %d", path, err); + osSnprintf(message, sizeof(message), "Error creating directory '%s', error %d", path, err); TRACE_ERROR("Error creating directory '%s' -> '%s', error %d\r\n", path, pathAbsolute, err); } httpPrepareHeader(connection, "text/plain; charset=utf-8", osStrlen(message)); @@ -1081,20 +1087,20 @@ error_t handleApiDirectoryDelete(HttpConnection *connection, const char_t *uri, sanitizePath(path, true); char pathAbsolute[256 + 2]; - snprintf(pathAbsolute, sizeof(pathAbsolute), "%s/%s", rootPath, path); + osSnprintf(pathAbsolute, sizeof(pathAbsolute), "%s/%s", rootPath, path); pathAbsolute[sizeof(pathAbsolute) - 1] = 0; uint_t statusCode = 200; char message[256 + 64]; - snprintf(message, sizeof(message), "OK"); + osSnprintf(message, sizeof(message), "OK"); error_t err = fsRemoveDir(pathAbsolute); if (err != NO_ERROR) { statusCode = 500; - snprintf(message, sizeof(message), "Error deleting directory '%s', error %d", path, err); + osSnprintf(message, sizeof(message), "Error deleting directory '%s', error %d", path, err); TRACE_ERROR("Error deleting directory '%s' -> '%s', error %d\r\n", path, pathAbsolute, err); } httpPrepareHeader(connection, "text/plain; charset=utf-8", osStrlen(message)); @@ -1128,20 +1134,20 @@ error_t handleApiFileDelete(HttpConnection *connection, const char_t *uri, const sanitizePath(path, false); char pathAbsolute[256 + 2]; - snprintf(pathAbsolute, sizeof(pathAbsolute), "%s/%s", rootPath, path); + osSnprintf(pathAbsolute, sizeof(pathAbsolute), "%s/%s", rootPath, path); pathAbsolute[sizeof(pathAbsolute) - 1] = 0; uint_t statusCode = 200; char message[256 + 64]; - snprintf(message, sizeof(message), "OK"); + osSnprintf(message, sizeof(message), "OK"); error_t err = fsDeleteFile(pathAbsolute); if (err != NO_ERROR) { statusCode = 500; - snprintf(message, sizeof(message), "Error deleting file '%s', error %d", path, err); + osSnprintf(message, sizeof(message), "Error deleting file '%s', error %d", path, err); TRACE_ERROR("Error deleting file '%s' -> '%s', error %d\r\n", path, pathAbsolute, err); } httpPrepareHeader(connection, "text/plain; charset=utf-8", osStrlen(message)); From 558044f81fba13ccae4ebf6786df7f6dc8f317ea Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 22:50:47 +0200 Subject: [PATCH 019/126] further code cleanups --- include/path_ext.h | 6 +++ include/server_helpers.h | 8 ++++ src/handler_api.c | 93 +--------------------------------------- src/path_ext.c | 20 +++++++++ src/server.c | 5 +-- src/server_helpers.c | 75 ++++++++++++++++++++++++++++++++ 6 files changed, 113 insertions(+), 94 deletions(-) create mode 100644 include/path_ext.h create mode 100644 include/server_helpers.h create mode 100644 src/path_ext.c create mode 100644 src/server_helpers.c diff --git a/include/path_ext.h b/include/path_ext.h new file mode 100644 index 00000000..ad85444e --- /dev/null +++ b/include/path_ext.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include "path.h" + +void pathSafeCanonicalize(char *path); diff --git a/include/server_helpers.h b/include/server_helpers.h new file mode 100644 index 00000000..e59414dc --- /dev/null +++ b/include/server_helpers.h @@ -0,0 +1,8 @@ +#pragma once + +#include +#include +#include + +int urldecode(char *dest, const char *src); +bool queryGet(const char *query, const char *key, char *data, size_t data_len); diff --git a/src/handler_api.c b/src/handler_api.c index 096829d0..7267eb3d 100644 --- a/src/handler_api.c +++ b/src/handler_api.c @@ -7,6 +7,8 @@ #include #include "path.h" +#include "path_ext.h" +#include "server_helpers.h" #include "fs_port.h" #include "handler.h" #include "handler_api.h" @@ -28,26 +30,6 @@ typedef enum #define SAVE_SIZE 80 #define BUFFER_SIZE (DATA_SIZE + SAVE_SIZE) -void pathSafeCanonicalize(char *path) -{ - if (!path || osStrlen(path) == 0) - { - return; - } - - pathCanonicalize(path); - - const char *pattern = "../"; - const size_t pattern_len = osStrlen(pattern); - - while (osStrncmp(path, pattern, pattern_len) == 0) - { - osMemmove(path, path + pattern_len, 1 + osStrlen(path + pattern_len)); - } -} - -bool queryGet(const char *query, const char *key, char *data, size_t data_len); - error_t handleApiAssignUnknown(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx) { const char *rootPath = settings_get_string("internal.contentdirfull"); @@ -350,77 +332,6 @@ error_t handleApiSet(HttpConnection *connection, const char_t *uri, const char_t return httpWriteResponseString(connection, response, false); } -int urldecode(char *dest, const char *src) -{ - char a, b; - while (*src) - { - if ((*src == '%') && - ((a = src[1]) && (b = src[2])) && - (isxdigit(a) && isxdigit(b))) - { - if (a >= 'a') - a -= 'a' - 'A'; - if (a >= 'A') - a -= ('A' - 10); - else - a -= '0'; - if (b >= 'a') - b -= 'a' - 'A'; - if (b >= 'A') - b -= ('A' - 10); - else - b -= '0'; - *dest++ = 16 * a + b; - src += 3; - } - else if (*src == '+') - { - *dest++ = ' '; - src++; - } - else - { - *dest++ = *src++; - } - } - *dest++ = '\0'; - return dest - src; -} - -bool queryGet(const char *query, const char *key, char *data, size_t data_len) -{ - const char *q = query; - size_t key_len = osStrlen(key); - while ((q = strstr(q, key))) - { - if (q[key_len] == '=') - { - // Found the key, let's start copying the value - q += key_len + 1; // Skip past the key and the '=' - char buffer[1024]; // Temporary buffer for decoding - char *b = buffer; - while (*q && *q != '&') - { - if (b - buffer < sizeof(buffer) - 1) - { // Prevent buffer overflow - *b++ = *q++; - } - else - { - // The value is too long, truncate it - break; - } - } - *b = '\0'; // Null-terminate the buffer - urldecode(data, buffer); // Decode and copy the value - return true; - } - q += key_len; // Skip past the key - } - return false; // Key not found -} - error_t handleApiFileIndex(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx) { char *jsonString = strdup("{\"files\":[]}"); diff --git a/src/path_ext.c b/src/path_ext.c new file mode 100644 index 00000000..3a5f5651 --- /dev/null +++ b/src/path_ext.c @@ -0,0 +1,20 @@ + +#include "path_ext.h" + +void pathSafeCanonicalize(char *path) +{ + if (!path || osStrlen(path) == 0) + { + return; + } + + pathCanonicalize(path); + + const char *pattern = "../"; + const size_t pattern_len = osStrlen(pattern); + + while (osStrncmp(path, pattern, pattern_len) == 0) + { + osMemmove(path, path + pattern_len, 1 + osStrlen(path + pattern_len)); + } +} diff --git a/src/server.c b/src/server.c index ad3f2791..ad205625 100644 --- a/src/server.c +++ b/src/server.c @@ -23,6 +23,8 @@ #include "settings.h" #include "returncodes.h" +#include "server_helpers.h" + #include "path.h" #include "debug.h" #include "os_port.h" @@ -54,9 +56,6 @@ typedef struct error_t (*handler)(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx); } request_type_t; -/* ToDo: a bit diry */ -bool queryGet(const char *query, const char *key, char *data, size_t data_len); - error_t handleContent(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx) { const char *rootPath = settings_get_string("internal.contentdirfull"); diff --git a/src/server_helpers.c b/src/server_helpers.c new file mode 100644 index 00000000..4d257522 --- /dev/null +++ b/src/server_helpers.c @@ -0,0 +1,75 @@ + +#include +#include "os_port.h" +#include "server_helpers.h" + +int urldecode(char *dest, const char *src) +{ + char a, b; + while (*src) + { + if ((*src == '%') && + ((a = src[1]) && (b = src[2])) && + (isxdigit(a) && isxdigit(b))) + { + if (a >= 'a') + a -= 'a' - 'A'; + if (a >= 'A') + a -= ('A' - 10); + else + a -= '0'; + if (b >= 'a') + b -= 'a' - 'A'; + if (b >= 'A') + b -= ('A' - 10); + else + b -= '0'; + *dest++ = 16 * a + b; + src += 3; + } + else if (*src == '+') + { + *dest++ = ' '; + src++; + } + else + { + *dest++ = *src++; + } + } + *dest++ = '\0'; + return dest - src; +} + +bool queryGet(const char *query, const char *key, char *data, size_t data_len) +{ + const char *q = query; + size_t key_len = osStrlen(key); + while ((q = strstr(q, key))) + { + if (q[key_len] == '=') + { + // Found the key, let's start copying the value + q += key_len + 1; // Skip past the key and the '=' + char buffer[1024]; // Temporary buffer for decoding + char *b = buffer; + while (*q && *q != '&') + { + if (b - buffer < sizeof(buffer) - 1) + { // Prevent buffer overflow + *b++ = *q++; + } + else + { + // The value is too long, truncate it + break; + } + } + *b = '\0'; // Null-terminate the buffer + urldecode(data, buffer); // Decode and copy the value + return true; + } + q += key_len; // Skip past the key + } + return false; // Key not found +} From 3853da7ad558bbd31224812a4fa3b1100f011429 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 22:57:33 +0200 Subject: [PATCH 020/126] further cleanups --- include/server_helpers.h | 5 + src/server.c | 191 +++++++-------------------------------- src/server_helpers.c | 120 ++++++++++++++++++++++++ 3 files changed, 160 insertions(+), 156 deletions(-) diff --git a/include/server_helpers.h b/include/server_helpers.h index e59414dc..129ebf18 100644 --- a/include/server_helpers.h +++ b/include/server_helpers.h @@ -4,5 +4,10 @@ #include #include +#include "core/net.h" + int urldecode(char *dest, const char *src); bool queryGet(const char *query, const char *key, char *data, size_t data_len); +char_t *ipAddrToString(const IpAddr *ipAddr, char_t *str); +char_t *ipv6AddrToString(const Ipv6Addr *ipAddr, char_t *str); +char_t *ipv4AddrToString(Ipv4Addr ipAddr, char_t *str); diff --git a/src/server.c b/src/server.c index ad205625..1b2f01b9 100644 --- a/src/server.c +++ b/src/server.c @@ -56,6 +56,39 @@ typedef struct error_t (*handler)(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx); } request_type_t; +/* const for now. later maybe dynamic? */ +request_type_t request_paths[] = { + /*binary handler (rtnl)*/ + {REQ_ANY, "*binary", &handleRtnl}, + /* reverse proxy handler */ + {REQ_ANY, "/reverse", &handleReverse}, + /* web interface directory */ + {REQ_GET, "/content/", &handleContent}, + /* custom API */ + {REQ_POST, "/api/fileDelete", &handleApiFileDelete}, + {REQ_POST, "/api/dirDelete", &handleApiDirectoryDelete}, + {REQ_POST, "/api/dirCreate", &handleApiDirectoryCreate}, + {REQ_POST, "/api/uploadCert", &handleApiUploadCert}, + {REQ_POST, "/api/fileUpload", &handleApiFileUpload}, + {REQ_GET, "/api/fileIndex", &handleApiFileIndex}, + {REQ_GET, "/api/stats", &handleApiStats}, + + {REQ_GET, "/api/trigger", &handleApiTrigger}, + {REQ_GET, "/api/getIndex", &handleApiGetIndex}, + {REQ_POST, "/api/assignUnknown", &handleApiAssignUnknown}, + {REQ_GET, "/api/get/", &handleApiGet}, + {REQ_POST, "/api/set/", &handleApiSet}, + {REQ_GET, "/api/sse", &handleApiSse}, + /* official boxine API */ + {REQ_GET, "/v1/time", &handleCloudTime}, + {REQ_GET, "/v1/ota", &handleCloudOTA}, + {REQ_GET, "/v1/claim", &handleCloudClaim}, + {REQ_GET, "/v1/content", &handleCloudContentV1}, + {REQ_GET, "/v2/content", &handleCloudContentV2}, + {REQ_POST, "/v1/freshness-check", &handleCloudFreshnessCheck}, + {REQ_POST, "/v1/log", &handleCloudLog}, + {REQ_POST, "/v1/cloud-reset", &handleCloudReset}}; + error_t handleContent(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx) { const char *rootPath = settings_get_string("internal.contentdirfull"); @@ -215,157 +248,6 @@ error_t handleContent(HttpConnection *connection, const char_t *uri, const char_ return error; } -/* const for now. later maybe dynamic? */ -request_type_t request_paths[] = { - /*binary handler (rtnl)*/ - {REQ_ANY, "*binary", &handleRtnl}, - /* reverse proxy handler */ - {REQ_ANY, "/reverse", &handleReverse}, - /* web interface directory */ - {REQ_GET, "/content/", &handleContent}, - /* custom API */ - {REQ_POST, "/api/fileDelete", &handleApiFileDelete}, - {REQ_POST, "/api/dirDelete", &handleApiDirectoryDelete}, - {REQ_POST, "/api/dirCreate", &handleApiDirectoryCreate}, - {REQ_POST, "/api/uploadCert", &handleApiUploadCert}, - {REQ_POST, "/api/fileUpload", &handleApiFileUpload}, - {REQ_GET, "/api/fileIndex", &handleApiFileIndex}, - {REQ_GET, "/api/stats", &handleApiStats}, - - {REQ_GET, "/api/trigger", &handleApiTrigger}, - {REQ_GET, "/api/getIndex", &handleApiGetIndex}, - {REQ_POST, "/api/assignUnknown", &handleApiAssignUnknown}, - {REQ_GET, "/api/get/", &handleApiGet}, - {REQ_POST, "/api/set/", &handleApiSet}, - {REQ_GET, "/api/sse", &handleApiSse}, - /* official boxine API */ - {REQ_GET, "/v1/time", &handleCloudTime}, - {REQ_GET, "/v1/ota", &handleCloudOTA}, - {REQ_GET, "/v1/claim", &handleCloudClaim}, - {REQ_GET, "/v1/content", &handleCloudContentV1}, - {REQ_GET, "/v2/content", &handleCloudContentV2}, - {REQ_POST, "/v1/freshness-check", &handleCloudFreshnessCheck}, - {REQ_POST, "/v1/log", &handleCloudLog}, - {REQ_POST, "/v1/cloud-reset", &handleCloudReset}}; - -char_t *ipv4AddrToString(Ipv4Addr ipAddr, char_t *str) -{ - uint8_t *p; - static char_t buffer[16]; - - // If the NULL pointer is given as parameter, then the internal buffer is used - if (str == NULL) - str = buffer; - - // Cast the address to byte array - p = (uint8_t *)&ipAddr; - // Format IPv4 address - osSprintf(str, "%" PRIu8 ".%" PRIu8 ".%" PRIu8 ".%" PRIu8 "", p[0], p[1], p[2], p[3]); - - // Return a pointer to the formatted string - return str; -} -char_t *ipv6AddrToString(const Ipv6Addr *ipAddr, char_t *str) -{ - static char_t buffer[40]; - uint_t i; - uint_t j; - char_t *p; - - // Best run of zeroes - uint_t zeroRunStart = 0; - uint_t zeroRunEnd = 0; - - // If the NULL pointer is given as parameter, then the internal buffer is used - if (str == NULL) - str = buffer; - - // Find the longest run of zeros for "::" short-handing - for (i = 0; i < 8; i++) - { - // Compute the length of the current sequence of zeroes - for (j = i; j < 8 && !ipAddr->w[j]; j++) - ; - - // Keep track of the longest one - if ((j - i) > 1 && (j - i) > (zeroRunEnd - zeroRunStart)) - { - // The symbol "::" should not be used to shorten just one zero field - zeroRunStart = i; - zeroRunEnd = j; - } - } - - // Format IPv6 address - for (p = str, i = 0; i < 8; i++) - { - // Are we inside the best run of zeroes? - if (i >= zeroRunStart && i < zeroRunEnd) - { - // Append a separator - *(p++) = ':'; - // Skip the sequence of zeroes - i = zeroRunEnd - 1; - } - else - { - // Add a separator between each 16-bit word - if (i > 0) - *(p++) = ':'; - - // Convert the current 16-bit word to string - p += osSprintf(p, "%" PRIx16, ntohs(ipAddr->w[i])); - } - } - - // A trailing run of zeroes has been found? - if (zeroRunEnd == 8) - *(p++) = ':'; - - // Properly terminate the string - *p = '\0'; - - // Return a pointer to the formatted string - return str; -} -char_t *ipAddrToString(const IpAddr *ipAddr, char_t *str) -{ -#if (IPV4_SUPPORT == ENABLED) - // IPv4 address? - if (ipAddr->length == sizeof(Ipv4Addr)) - { - // Convert IPv4 address to string representation - return ipv4AddrToString(ipAddr->ipv4Addr, str); - } - else -#endif -#if (IPV6_SUPPORT == ENABLED) - // IPv6 address? - if (ipAddr->length == sizeof(Ipv6Addr)) - { - // Convert IPv6 address to string representation - return ipv6AddrToString(&ipAddr->ipv6Addr, str); - } - else -#endif - // Invalid IP address? - { - static char_t c; - - // The last parameter is optional - if (str == NULL) - { - str = &c; - } - - // Properly terminate the string - str[0] = '\0'; - - // Return an empty string - return str; - } -} - error_t resGetData(const char_t *path, const uint8_t **data, size_t *length) { TRACE_INFO("resGetData: %s (static response)\n", path); @@ -376,9 +258,7 @@ error_t resGetData(const char_t *path, const uint8_t **data, size_t *length) return NO_ERROR; } -error_t -httpServerRequestCallback(HttpConnection *connection, - const char_t *uri) +error_t httpServerRequestCallback(HttpConnection *connection, const char_t *uri) { error_t error = NO_ERROR; @@ -439,8 +319,7 @@ httpServerRequestCallback(HttpConnection *connection, return error; } -error_t httpServerUriNotFoundCallback(HttpConnection *connection, - const char_t *uri) +error_t httpServerUriNotFoundCallback(HttpConnection *connection, const char_t *uri) { error_t error = NO_ERROR; char *fnf = "404.html"; diff --git a/src/server_helpers.c b/src/server_helpers.c index 4d257522..6c40a743 100644 --- a/src/server_helpers.c +++ b/src/server_helpers.c @@ -73,3 +73,123 @@ bool queryGet(const char *query, const char *key, char *data, size_t data_len) } return false; // Key not found } + +char_t *ipv4AddrToString(Ipv4Addr ipAddr, char_t *str) +{ + uint8_t *p; + static char_t buffer[16]; + + // If the NULL pointer is given as parameter, then the internal buffer is used + if (str == NULL) + str = buffer; + + // Cast the address to byte array + p = (uint8_t *)&ipAddr; + // Format IPv4 address + osSprintf(str, "%" PRIu8 ".%" PRIu8 ".%" PRIu8 ".%" PRIu8 "", p[0], p[1], p[2], p[3]); + + // Return a pointer to the formatted string + return str; +} + +char_t *ipv6AddrToString(const Ipv6Addr *ipAddr, char_t *str) +{ + static char_t buffer[40]; + uint_t i; + uint_t j; + char_t *p; + + // Best run of zeroes + uint_t zeroRunStart = 0; + uint_t zeroRunEnd = 0; + + // If the NULL pointer is given as parameter, then the internal buffer is used + if (str == NULL) + str = buffer; + + // Find the longest run of zeros for "::" short-handing + for (i = 0; i < 8; i++) + { + // Compute the length of the current sequence of zeroes + for (j = i; j < 8 && !ipAddr->w[j]; j++) + ; + + // Keep track of the longest one + if ((j - i) > 1 && (j - i) > (zeroRunEnd - zeroRunStart)) + { + // The symbol "::" should not be used to shorten just one zero field + zeroRunStart = i; + zeroRunEnd = j; + } + } + + // Format IPv6 address + for (p = str, i = 0; i < 8; i++) + { + // Are we inside the best run of zeroes? + if (i >= zeroRunStart && i < zeroRunEnd) + { + // Append a separator + *(p++) = ':'; + // Skip the sequence of zeroes + i = zeroRunEnd - 1; + } + else + { + // Add a separator between each 16-bit word + if (i > 0) + *(p++) = ':'; + + // Convert the current 16-bit word to string + p += osSprintf(p, "%" PRIx16, ntohs(ipAddr->w[i])); + } + } + + // A trailing run of zeroes has been found? + if (zeroRunEnd == 8) + *(p++) = ':'; + + // Properly terminate the string + *p = '\0'; + + // Return a pointer to the formatted string + return str; +} + +char_t *ipAddrToString(const IpAddr *ipAddr, char_t *str) +{ +#if (IPV4_SUPPORT == ENABLED) + // IPv4 address? + if (ipAddr->length == sizeof(Ipv4Addr)) + { + // Convert IPv4 address to string representation + return ipv4AddrToString(ipAddr->ipv4Addr, str); + } + else +#endif +#if (IPV6_SUPPORT == ENABLED) + // IPv6 address? + if (ipAddr->length == sizeof(Ipv6Addr)) + { + // Convert IPv6 address to string representation + return ipv6AddrToString(&ipAddr->ipv6Addr, str); + } + else +#endif + // Invalid IP address? + { + static char_t c; + + // The last parameter is optional + if (str == NULL) + { + str = &c; + } + + // Properly terminate the string + str[0] = '\0'; + + // Return an empty string + return str; + } +} From 26365ce25bb15ce45d9ae3ee870581561f6fed58 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sat, 5 Aug 2023 23:33:07 +0200 Subject: [PATCH 021/126] add missing declaration --- include/handler_cloud.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/handler_cloud.h b/include/handler_cloud.h index 514fd5a2..544633c6 100644 --- a/include/handler_cloud.h +++ b/include/handler_cloud.h @@ -23,5 +23,6 @@ error_t handleCloudContentV1(HttpConnection *connection, const char_t *uri, cons error_t handleCloudContentV2(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx); error_t handleCloudFreshnessCheck(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx); error_t handleCloudReset(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx); +error_t handleContent(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx); #endif \ No newline at end of file From 328a1cbdb166f7816f24872f40b290a09a1af767 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sun, 6 Aug 2023 22:39:40 +0200 Subject: [PATCH 022/126] split server settings and box status for HA --- include/home_assistant.h | 42 +++-- src/home_assistant.c | 184 ++++++++++--------- src/mqtt.c | 372 ++++++++++++++++++++++++++++++++++++--- 3 files changed, 469 insertions(+), 129 deletions(-) diff --git a/include/home_assistant.h b/include/home_assistant.h index 8bd353d2..a029ecc9 100644 --- a/include/home_assistant.h +++ b/include/home_assistant.h @@ -22,10 +22,13 @@ typedef enum /* https://www.home-assistant.io/integrations/binary_sensor.mqtt/ */ ha_binary_sensor, /* https://www.home-assistant.io/integrations/ha_light.mqtt/ */ - ha_light + ha_light, + /* https://www.home-assistant.io/integrations/switch.mqtt/ */ + ha_switch } t_ha_device_type; typedef struct s_ha_entity t_ha_entity; +typedef struct s_ha_info t_ha_info; struct s_ha_entity { @@ -72,18 +75,19 @@ struct s_ha_entity /* alternative client name */ const char *alt_name; - void (*received)(const t_ha_entity *, void *, const char *); + void (*received)(t_ha_info *ha_info, const t_ha_entity *, void *, const char *); void *received_ctx; - void (*rgb_received)(const t_ha_entity *, void *, const char *); + void (*rgb_received)(t_ha_info *ha_info, const t_ha_entity *, void *, const char *); void *rgb_received_ctx; - void (*fx_received)(const t_ha_entity *, void *, const char *); + void (*fx_received)(t_ha_info *ha_info, const t_ha_entity *, void *, const char *); void *fx_received_ctx; - void (*transmit)(const t_ha_entity *, void *); + void (*transmit)(t_ha_info *ha_info, const t_ha_entity *, void *); void *transmit_ctx; }; -typedef struct +struct s_ha_info { + char base_topic[MAX_LEN]; char name[MAX_LEN]; char id[MAX_LEN]; char cu[MAX_LEN]; @@ -92,17 +96,17 @@ typedef struct char sw[MAX_LEN]; t_ha_entity entities[MAX_ENTITIES]; int entitiy_count; -} t_ha_info; +}; -void ha_setup(); -void ha_connected(); -bool ha_loop(); -void ha_transmit_all(); -void ha_publish(); -void ha_add(t_ha_entity *entity); -void ha_addmqtt(char *json_str, const char *name, const char *value, t_ha_entity *entity, bool last); -void ha_received(char *topic, const char *payload); -void ha_transmit(const t_ha_entity *entity, const char *value); -void ha_transmit_topic(const char *stat_t, const char *value); -int ha_parse_index(const char *options, const char *message); -void ha_get_index(const char *options, int index, char *text); +void ha_setup(t_ha_info *ha_info); +void ha_connected(t_ha_info *ha_info); +bool ha_loop(t_ha_info *ha_info); +void ha_transmit_all(t_ha_info *ha_info); +void ha_publish(t_ha_info *ha_info); +void ha_add(t_ha_info *ha_info, t_ha_entity *entity); +void ha_addmqtt(t_ha_info *ha_info, char *json_str, const char *name, const char *value, t_ha_entity *entity, bool last); +void ha_received(t_ha_info *ha_info, char *topic, const char *payload); +void ha_transmit(t_ha_info *ha_info, const t_ha_entity *entity, const char *value); +void ha_transmit_topic(t_ha_info *ha_info, const char *stat_t, const char *value); +int ha_parse_index(t_ha_info *ha_info, const char *options, const char *message); +void ha_get_index(t_ha_info *ha_info, const char *options, int index, char *text); diff --git a/src/home_assistant.c b/src/home_assistant.c index 08f36cb9..d698a546 100644 --- a/src/home_assistant.c +++ b/src/home_assistant.c @@ -8,8 +8,6 @@ #include "macros.h" #include "mqtt.h" -t_ha_info ha_info; - void ha_addstrarray(char *json_str, const char *name, const char *value, bool last) { char tmp_buf[256]; @@ -65,7 +63,7 @@ void ha_addstr(char *json_str, const char *name, const char *value, bool last) } } -void ha_addmqtt(char *json_str, const char *name, const char *value, t_ha_entity *entity, bool last) +void ha_addmqtt(t_ha_info *ha_info, char *json_str, const char *name, const char *value, t_ha_entity *entity, bool last) { char tmp_buf[128]; @@ -79,7 +77,7 @@ void ha_addmqtt(char *json_str, const char *name, const char *value, t_ha_entity } else { - osSprintf(path_buffer, value, settings_get_string("mqtt.topic")); + osSprintf(path_buffer, value, ha_info->base_topic); } osSnprintf(tmp_buf, sizeof(tmp_buf), "\"%s\": \"%s\"%c ", name, path_buffer, (last ? ' ' : ',')); osStrcat(json_str, tmp_buf); @@ -102,7 +100,7 @@ void ha_addint(char *json_str, const char *name, int value, bool last) osStrcat(json_str, tmp_buf); } -void ha_publish() +void ha_publish(t_ha_info *ha_info) { char *json_str = (char *)osAllocMem(1024); char mqtt_path[128]; @@ -110,11 +108,11 @@ void ha_publish() TRACE_INFO("[HA] Publish\n"); - for (int pos = 0; pos < ha_info.entitiy_count; pos++) + for (int pos = 0; pos < ha_info->entitiy_count; pos++) { const char *type = NULL; - switch (ha_info.entities[pos].type) + switch (ha_info->entities[pos].type) { case ha_sensor: type = "sensor"; @@ -137,6 +135,9 @@ void ha_publish() case ha_light: type = "light"; break; + case ha_switch: + type = "switch"; + break; default: break; } @@ -146,49 +147,55 @@ void ha_publish() break; } - osSprintf(uniq_id, "%s_%s", ha_info.id, ha_info.entities[pos].id); + osSprintf(uniq_id, "%s_%s", ha_info->id, ha_info->entities[pos].id); // TRACE_INFO("[HA] uniq_id %s\n", uniq_id); - osSprintf(mqtt_path, "homeassistant/%s/%s/%s/config", type, ha_info.id, ha_info.entities[pos].id); + osSprintf(mqtt_path, "homeassistant/%s/%s/%s/config", type, ha_info->id, ha_info->entities[pos].id); // TRACE_INFO("[HA] mqtt_path %s\n", mqtt_path); osStrcpy(json_str, "{"); - ha_addstr(json_str, "name", ha_info.entities[pos].name, false); + ha_addstr(json_str, "name", ha_info->entities[pos].name, false); ha_addstr(json_str, "uniq_id", uniq_id, false); - ha_addstr(json_str, "dev_cla", ha_info.entities[pos].dev_class, false); - ha_addstr(json_str, "stat_cla", ha_info.entities[pos].state_class, false); - ha_addstr(json_str, "ic", ha_info.entities[pos].ic, false); - ha_addstr(json_str, "mode", ha_info.entities[pos].mode, false); - ha_addstr(json_str, "ent_cat", ha_info.entities[pos].ent_cat, false); - ha_addmqtt(json_str, "cmd_t", ha_info.entities[pos].cmd_t, &ha_info.entities[pos], false); - ha_addmqtt(json_str, "stat_t", ha_info.entities[pos].stat_t, &ha_info.entities[pos], false); - ha_addmqtt(json_str, "rgbw_cmd_t", ha_info.entities[pos].rgbw_t, &ha_info.entities[pos], false); - ha_addmqtt(json_str, "rgb_cmd_t", ha_info.entities[pos].rgb_t, &ha_info.entities[pos], false); - ha_addmqtt(json_str, "fx_cmd_t", ha_info.entities[pos].fx_cmd_t, &ha_info.entities[pos], false); - ha_addmqtt(json_str, "fx_stat_t", ha_info.entities[pos].fx_stat_t, &ha_info.entities[pos], false); - ha_addstrarray(json_str, "fx_list", ha_info.entities[pos].fx_list, false); - ha_addmqtt(json_str, "val_tpl", ha_info.entities[pos].val_tpl, &ha_info.entities[pos], false); - ha_addstrarray(json_str, "options", ha_info.entities[pos].options, false); - ha_addstr(json_str, "unit_of_meas", ha_info.entities[pos].unit_of_meas, false); - - switch (ha_info.entities[pos].type) + ha_addstr(json_str, "dev_cla", ha_info->entities[pos].dev_class, false); + ha_addstr(json_str, "stat_cla", ha_info->entities[pos].state_class, false); + ha_addstr(json_str, "ic", ha_info->entities[pos].ic, false); + ha_addstr(json_str, "mode", ha_info->entities[pos].mode, false); + ha_addstr(json_str, "ent_cat", ha_info->entities[pos].ent_cat, false); + ha_addmqtt(ha_info, json_str, "cmd_t", ha_info->entities[pos].cmd_t, &ha_info->entities[pos], false); + ha_addmqtt(ha_info, json_str, "stat_t", ha_info->entities[pos].stat_t, &ha_info->entities[pos], false); + ha_addmqtt(ha_info, json_str, "rgbw_cmd_t", ha_info->entities[pos].rgbw_t, &ha_info->entities[pos], false); + ha_addmqtt(ha_info, json_str, "rgb_cmd_t", ha_info->entities[pos].rgb_t, &ha_info->entities[pos], false); + ha_addmqtt(ha_info, json_str, "fx_cmd_t", ha_info->entities[pos].fx_cmd_t, &ha_info->entities[pos], false); + ha_addmqtt(ha_info, json_str, "fx_stat_t", ha_info->entities[pos].fx_stat_t, &ha_info->entities[pos], false); + ha_addstrarray(json_str, "fx_list", ha_info->entities[pos].fx_list, false); + ha_addmqtt(ha_info, json_str, "val_tpl", ha_info->entities[pos].val_tpl, &ha_info->entities[pos], false); + ha_addstrarray(json_str, "options", ha_info->entities[pos].options, false); + ha_addstr(json_str, "unit_of_meas", ha_info->entities[pos].unit_of_meas, false); + + switch (ha_info->entities[pos].type) { case ha_number: - ha_addint(json_str, "min", ha_info.entities[pos].min, false); - ha_addint(json_str, "max", ha_info.entities[pos].max, false); + ha_addint(json_str, "min", ha_info->entities[pos].min, false); + ha_addint(json_str, "max", ha_info->entities[pos].max, false); + break; + case ha_switch: + ha_addstr(json_str, "payload_on", "TRUE", false); + ha_addstr(json_str, "payload_off", "FALSE", false); + ha_addstr(json_str, "state_on", "TRUE", false); + ha_addstr(json_str, "state_off", "FALSE", false); break; default: break; } osStrcat(json_str, "\"dev\": {"); - ha_addstr(json_str, "name", ha_info.name, false); - ha_addstr(json_str, "ids", ha_info.id, false); - ha_addstr(json_str, "cu", ha_info.cu, false); - ha_addstr(json_str, "mf", ha_info.mf, false); - ha_addstr(json_str, "mdl", ha_info.mdl, false); - ha_addstr(json_str, "sw", ha_info.sw, true); + ha_addstr(json_str, "name", ha_info->name, false); + ha_addstr(json_str, "ids", ha_info->id, false); + ha_addstr(json_str, "cu", ha_info->cu, false); + ha_addstr(json_str, "mf", ha_info->mf, false); + ha_addstr(json_str, "mdl", ha_info->mdl, false); + ha_addstr(json_str, "sw", ha_info->sw, true); osStrcat(json_str, "}}"); // TRACE_INFO("[HA] topic '%s'\n", mqtt_path); @@ -203,57 +210,57 @@ void ha_publish() osFreeMem(json_str); } -void ha_received(char *topic, const char *payload) +void ha_received(t_ha_info *ha_info, char *topic, const char *payload) { - for (int pos = 0; pos < ha_info.entitiy_count; pos++) + for (int pos = 0; pos < ha_info->entitiy_count; pos++) { char item_topic[128]; - if (ha_info.entities[pos].cmd_t && ha_info.entities[pos].received) + if (ha_info->entities[pos].cmd_t && ha_info->entities[pos].received) { - osSprintf(item_topic, ha_info.entities[pos].cmd_t, settings_get_string("mqtt.topic")); + osSprintf(item_topic, ha_info->entities[pos].cmd_t, ha_info->base_topic); if (!osStrcmp(topic, item_topic)) { - ha_info.entities[pos].received(&ha_info.entities[pos], ha_info.entities[pos].received_ctx, payload); + ha_info->entities[pos].received(ha_info, &ha_info->entities[pos], ha_info->entities[pos].received_ctx, payload); - if (ha_info.entities[pos].transmit) + if (ha_info->entities[pos].transmit) { - ha_info.entities[pos].transmit(&ha_info.entities[pos], ha_info.entities[pos].transmit_ctx); + ha_info->entities[pos].transmit(ha_info, &ha_info->entities[pos], ha_info->entities[pos].transmit_ctx); } } } - if (ha_info.entities[pos].rgb_t && ha_info.entities[pos].rgb_received) + if (ha_info->entities[pos].rgb_t && ha_info->entities[pos].rgb_received) { - osSprintf(item_topic, ha_info.entities[pos].rgb_t, settings_get_string("mqtt.topic")); + osSprintf(item_topic, ha_info->entities[pos].rgb_t, ha_info->base_topic); if (!osStrcmp(topic, item_topic)) { - ha_info.entities[pos].rgb_received(&ha_info.entities[pos], ha_info.entities[pos].rgb_received_ctx, payload); + ha_info->entities[pos].rgb_received(ha_info, &ha_info->entities[pos], ha_info->entities[pos].rgb_received_ctx, payload); - if (ha_info.entities[pos].transmit) + if (ha_info->entities[pos].transmit) { - ha_info.entities[pos].transmit(&ha_info.entities[pos], ha_info.entities[pos].transmit_ctx); + ha_info->entities[pos].transmit(ha_info, &ha_info->entities[pos], ha_info->entities[pos].transmit_ctx); } } } - if (ha_info.entities[pos].fx_cmd_t && ha_info.entities[pos].fx_received) + if (ha_info->entities[pos].fx_cmd_t && ha_info->entities[pos].fx_received) { - osSprintf(item_topic, ha_info.entities[pos].fx_cmd_t, settings_get_string("mqtt.topic")); + osSprintf(item_topic, ha_info->entities[pos].fx_cmd_t, ha_info->base_topic); if (!osStrcmp(topic, item_topic)) { - ha_info.entities[pos].fx_received(&ha_info.entities[pos], ha_info.entities[pos].fx_received_ctx, payload); + ha_info->entities[pos].fx_received(ha_info, &ha_info->entities[pos], ha_info->entities[pos].fx_received_ctx, payload); - if (ha_info.entities[pos].transmit) + if (ha_info->entities[pos].transmit) { - ha_info.entities[pos].transmit(&ha_info.entities[pos], ha_info.entities[pos].transmit_ctx); + ha_info->entities[pos].transmit(ha_info, &ha_info->entities[pos], ha_info->entities[pos].transmit_ctx); } } } } } -void ha_transmit(const t_ha_entity *entity, const char *value) +void ha_transmit(t_ha_info *ha_info, const t_ha_entity *entity, const char *value) { if (!entity) { @@ -265,7 +272,7 @@ void ha_transmit(const t_ha_entity *entity, const char *value) return; } char item_topic[128]; - osSprintf(item_topic, entity->stat_t, settings_get_string("mqtt.topic")); + osSprintf(item_topic, entity->stat_t, ha_info->base_topic); if (!mqtt_publish(item_topic, value)) { @@ -273,7 +280,7 @@ void ha_transmit(const t_ha_entity *entity, const char *value) } } -void ha_transmit_topic(const char *stat_t, const char *value) +void ha_transmit_topic(t_ha_info *ha_info, const char *stat_t, const char *value) { if (!stat_t) { @@ -281,7 +288,7 @@ void ha_transmit_topic(const char *stat_t, const char *value) } char item_topic[128]; - osSprintf(item_topic, stat_t, settings_get_string("mqtt.topic")); + osSprintf(item_topic, stat_t, ha_info->base_topic); if (!mqtt_publish(item_topic, value)) { @@ -289,85 +296,86 @@ void ha_transmit_topic(const char *stat_t, const char *value) } } -void ha_transmit_all() +void ha_transmit_all(t_ha_info *ha_info) { - for (int pos = 0; pos < ha_info.entitiy_count; pos++) + for (int pos = 0; pos < ha_info->entitiy_count; pos++) { - if (ha_info.entities[pos].transmit) + if (ha_info->entities[pos].transmit) { - ha_info.entities[pos].transmit(&ha_info.entities[pos], ha_info.entities[pos].transmit_ctx); + ha_info->entities[pos].transmit(ha_info, &ha_info->entities[pos], ha_info->entities[pos].transmit_ctx); } } } -void ha_setup() +void ha_setup(t_ha_info *ha_info) { - osMemset(&ha_info, 0x00, sizeof(ha_info)); - - osSprintf(ha_info.name, "%s", settings_get_string("mqtt.topic")); - osSprintf(ha_info.id, "%s", "teddyCloud"); - osSprintf(ha_info.cu, "%s", settings_get_string("mqtt.host_url")); - osSprintf(ha_info.mf, "RevvoX"); - osSprintf(ha_info.mdl, "%s", "teddyCloud"); - osSprintf(ha_info.sw, "" BUILD_GIT_TAG " (" BUILD_GIT_SHORT_SHA ")"); - ha_info.entitiy_count = 0; + osMemset(ha_info, 0x00, sizeof(t_ha_info)); + + osSprintf(ha_info->base_topic, "%s", "teddyCloud"); + osSprintf(ha_info->name, "%s", ha_info->base_topic); + osSprintf(ha_info->id, "%s", "teddyCloud"); + osSprintf(ha_info->cu, "%s", settings_get_string("mqtt.host_url")); + osSprintf(ha_info->mf, "RevvoX"); + osSprintf(ha_info->mdl, "%s", "teddyCloud"); + osSprintf(ha_info->sw, "" BUILD_GIT_TAG " (" BUILD_GIT_SHORT_SHA ")"); + ha_info->entitiy_count = 0; } -void ha_connected() +void ha_connected(t_ha_info *ha_info) { - for (int pos = 0; pos < ha_info.entitiy_count; pos++) + for (int pos = 0; pos < ha_info->entitiy_count; pos++) { char item_topic[128]; - if (ha_info.entities[pos].cmd_t && ha_info.entities[pos].received) + if (ha_info->entities[pos].cmd_t && ha_info->entities[pos].received) { - osSprintf(item_topic, ha_info.entities[pos].cmd_t, settings_get_string("mqtt.topic")); + osSprintf(item_topic, ha_info->entities[pos].cmd_t, ha_info->base_topic); mqtt_subscribe(item_topic); } - if (ha_info.entities[pos].rgb_t && ha_info.entities[pos].rgb_received) + if (ha_info->entities[pos].rgb_t && ha_info->entities[pos].rgb_received) { - osSprintf(item_topic, ha_info.entities[pos].rgb_t, settings_get_string("mqtt.topic")); + osSprintf(item_topic, ha_info->entities[pos].rgb_t, ha_info->base_topic); mqtt_subscribe(item_topic); } - if (ha_info.entities[pos].fx_cmd_t && ha_info.entities[pos].fx_received) + if (ha_info->entities[pos].fx_cmd_t && ha_info->entities[pos].fx_received) { - osSprintf(item_topic, ha_info.entities[pos].fx_cmd_t, settings_get_string("mqtt.topic")); + osSprintf(item_topic, ha_info->entities[pos].fx_cmd_t, ha_info->base_topic); mqtt_subscribe(item_topic); } } - ha_publish(); - ha_transmit_all(); + ha_publish(ha_info); + ha_transmit_all(ha_info); } -bool ha_loop() +bool ha_loop(t_ha_info *ha_info) { systime_t time = osGetSystemTime(); static systime_t nextTime = 0; if (time >= nextTime) { - ha_publish(); - ha_transmit_all(); + ha_publish(ha_info); + ha_transmit_all(ha_info); nextTime = time + 60000; } return false; } -void ha_add(t_ha_entity *entity) +void ha_add(t_ha_info *ha_info, t_ha_entity *entity) { if (!entity) { return; } - if (ha_info.entitiy_count >= MAX_ENTITIES) + if (ha_info->entitiy_count >= MAX_ENTITIES) { return; } - osMemcpy(&ha_info.entities[ha_info.entitiy_count++], entity, sizeof(t_ha_entity)); + osMemcpy(&ha_info->entities[ha_info->entitiy_count++], entity, sizeof(t_ha_entity)); } -int ha_parse_index(const char *options, const char *message) +int ha_parse_index(t_ha_info *ha_info, const char *options, const char *message) { if (!options) { @@ -402,7 +410,7 @@ int ha_parse_index(const char *options, const char *message) } } -void ha_get_index(const char *options, int index, char *text) +void ha_get_index(t_ha_info *ha_info, const char *options, int index, char *text) { if (!options || !text) { diff --git a/src/mqtt.c b/src/mqtt.c index 36da462b..8610c429 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -2,6 +2,7 @@ #include #include #include +#include #include "core/net.h" #include "core/ip.h" @@ -13,6 +14,11 @@ #include "home_assistant.h" #include "debug.h" +#include "mqtt.h" + +/* need one per box in future */ +t_ha_info ha_box_instance; +t_ha_info ha_server_instance; bool_t mqttConnected = FALSE; error_t error; @@ -21,6 +27,18 @@ bool mqtt_fail = false; #define MQTT_TOPIC_STRING_LENGTH 128 +typedef struct +{ + bool used; + char *topic; + char *payload; +} mqtt_tx_buffer; + +OsMutex mqtt_tx_buffer_mutex; + +#define MQTT_TX_BUFFERS 512 +mqtt_tx_buffer mqtt_tx_buffers[MQTT_TX_BUFFERS]; + char *mqtt_prefix(const char *path) { static char buffer[MQTT_TOPIC_STRING_LENGTH]; @@ -45,23 +63,43 @@ void mqttTestPublishCallback(MqttClientContext *context, bool_t dup, MqttQosLevel qos, bool_t retain, uint16_t packetId) { // Debug message - TRACE_INFO("PUBLISH packet received...\r\n"); + TRACE_INFO("packet received...\r\n"); TRACE_INFO(" Dup: %u\r\n", dup); TRACE_INFO(" QoS: %u\r\n", qos); TRACE_INFO(" Retain: %u\r\n", retain); TRACE_INFO(" Packet Identifier: %u\r\n", packetId); TRACE_INFO(" Topic: %s\r\n", topic); - TRACE_INFO(" Message (%" PRIuSIZE " bytes):\r\n", length); - TRACE_INFO_ARRAY(" ", message, length); + TRACE_INFO(" Message (%" PRIuSIZE " bytes): '%.*s'\r\n", length, (int)length, (char *)message); - ha_received((char *)topic, (const char *)message); + char *payload = osAllocMem(length + 1); + osMemcpy(payload, message, length); + payload[length] = 0; + + ha_received(&ha_box_instance, (char *)topic, (const char *)payload); + ha_received(&ha_server_instance, (char *)topic, (const char *)payload); + + osFreeMem(payload); } bool mqtt_publish(const char *item_topic, const char *content) { - mqttClientPublish(&mqtt_context, item_topic, content, osStrlen(content), MQTT_QOS_LEVEL_0, false, NULL); + bool success = false; + osAcquireMutex(&mqtt_tx_buffer_mutex); - return true; + for (int pos = 0; pos < MQTT_TX_BUFFERS; pos++) + { + if (!mqtt_tx_buffers[pos].used) + { + mqtt_tx_buffers[pos].topic = strdup(item_topic); + mqtt_tx_buffers[pos].payload = strdup(content); + mqtt_tx_buffers[pos].used = true; + success = true; + break; + } + } + osReleaseMutex(&mqtt_tx_buffer_mutex); + + return success; } bool mqtt_subscribe(const char *item_topic) @@ -159,7 +197,8 @@ void mqtt_thread() TRACE_INFO("Connected\r\n"); mqttConnected = TRUE; mqtt_fail = false; - ha_connected(); + ha_connected(&ha_box_instance); + ha_connected(&ha_server_instance); } else { @@ -175,7 +214,23 @@ void mqtt_thread() mqttConnected = FALSE; osDelayTask(2000); } - ha_loop(); + + /* process buffered Tx actions */ + osAcquireMutex(&mqtt_tx_buffer_mutex); + for (int pos = 0; pos < MQTT_TX_BUFFERS; pos++) + { + if (mqtt_tx_buffers[pos].used) + { + mqttClientPublish(&mqtt_context, mqtt_tx_buffers[pos].topic, mqtt_tx_buffers[pos].payload, osStrlen(mqtt_tx_buffers[pos].payload), MQTT_QOS_LEVEL_0, false, NULL); + osFreeMem(mqtt_tx_buffers[pos].topic); + osFreeMem(mqtt_tx_buffers[pos].payload); + mqtt_tx_buffers[pos].used = false; + } + } + osReleaseMutex(&mqtt_tx_buffer_mutex); + + ha_loop(&ha_box_instance); + ha_loop(&ha_server_instance); } } @@ -223,90 +278,363 @@ void mqtt_publish_int(const char *name, uint32_t value) } } +char *mqtt_settingname_clean(const char *str) +{ + int length = osStrlen(str) + 1; + + char *new_str = osAllocMem(length); + if (new_str == NULL) + { + return NULL; + } + osStrcpy(new_str, str); + + for (int pos = 0; pos < osStrlen(new_str); pos++) + { + if (new_str[pos] == '.') + { + new_str[pos] = '-'; + } + } + + return new_str; +} + +char *mqtt_fmt_create(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + // Calculate the length of the final string + va_list tmp_args; + va_copy(tmp_args, args); + int length = osVsnprintf(NULL, 0, fmt, tmp_args); + va_end(tmp_args); + + if (length < 0) + { + return NULL; + } + + // Allocate memory for the new string + char *new_str = osAllocMem(length + 1); // Add 1 for the null terminator + if (new_str == NULL) + { + return NULL; + } + + // Format the new string + osVsnprintf(new_str, length + 1, fmt, args); + + va_end(args); + + return new_str; +} + +char *mqtt_topic_str(const char *fmt, char *param) +{ + char *first_s = osStrstr(fmt, "%s"); + if (first_s == NULL) + { + return "none"; + } + + int length = osStrlen(fmt) + osStrlen(param) - 2; + + char *new_str = osAllocMem(length + 1); + if (new_str == NULL) + { + return NULL; + } + osStrcpy(new_str, "%s"); + + osSprintf(&new_str[2], &fmt[2], param); + + return new_str; +} + +void mqtt_publish_settings() +{ + int index = 0; + do + { + setting_item_t *s = settings_get(index); + if (!s) + { + break; + } + if (s->internal) + { + index++; + continue; + } + + char *name = mqtt_settingname_clean(s->option_name); + char *status_topic = mqtt_topic_str("%s/%s/status", name); + + switch (s->type) + { + case TYPE_BOOL: + mqtt_publish_string(status_topic, (*(bool *)s->ptr) ? "TRUE" : "FALSE"); + break; + case TYPE_FLOAT: + mqtt_publish_float(status_topic, *(float *)s->ptr); + break; + case TYPE_HEX: + case TYPE_UNSIGNED: + mqtt_publish_int(status_topic, *(uint32_t *)s->ptr); + break; + case TYPE_SIGNED: + mqtt_publish_int(status_topic, *(uint32_t *)s->ptr); + break; + case TYPE_STRING: + mqtt_publish_string(status_topic, *(char **)s->ptr); + break; + default: + break; + } + index++; + + osFreeMem(name); + osFreeMem(status_topic); + } while (1); +} + +void mqtt_settings_tx(t_ha_info *ha_info, const t_ha_entity *entity, void *ctx) +{ + setting_item_t *s = ctx; + const char *status_topic = entity->stat_t; + + if (!s || !status_topic) + { + return; + } + + switch (s->type) + { + case TYPE_BOOL: + mqtt_publish_string(status_topic, (*(bool *)s->ptr) ? "TRUE" : "FALSE"); + break; + case TYPE_FLOAT: + mqtt_publish_float(status_topic, *(float *)s->ptr); + break; + case TYPE_HEX: + case TYPE_UNSIGNED: + mqtt_publish_int(status_topic, *(uint32_t *)s->ptr); + break; + case TYPE_SIGNED: + mqtt_publish_int(status_topic, *(uint32_t *)s->ptr); + break; + case TYPE_STRING: + mqtt_publish_string(status_topic, *(char **)s->ptr); + break; + default: + break; + } +} + +void mqtt_settings_rx(t_ha_info *ha_info, const t_ha_entity *entity, void *ctx, const char *payload) +{ + setting_item_t *s = ctx; + if (!s) + { + return; + } + + switch (s->type) + { + case TYPE_BOOL: + settings_set_bool(s->option_name, !osStrcasecmp(payload, "TRUE")); + break; + case TYPE_FLOAT: + { + float val; + sscanf(payload, "%f", &val); + settings_set_float(s->option_name, val); + break; + } + case TYPE_HEX: + { + uint32_t val = strtoul(payload, NULL, 16); + settings_set_unsigned(s->option_name, val); + break; + } + case TYPE_UNSIGNED: + { + uint32_t val; + sscanf(payload, "%u", &val); + settings_set_unsigned(s->option_name, val); + break; + } + case TYPE_SIGNED: + { + int32_t val; + sscanf(payload, "%d", &val); + settings_set_signed(s->option_name, val); + break; + } + case TYPE_STRING: + { + settings_set_string(s->option_name, payload); + break; + } + default: + break; + } +} + void mqtt_init() { + osCreateMutex(&mqtt_tx_buffer_mutex); osCreateTask("MQTT", &mqtt_thread, NULL, 1024, 0); - ha_setup(); - t_ha_entity entity; + ha_setup(&ha_server_instance); + osSprintf(ha_server_instance.name, "%s - Server", settings_get_string("mqtt.topic")); + osStrcpy(ha_server_instance.id, "teddyCloudSettings"); + + int index = 0; + do + { + setting_item_t *s = settings_get(index); + if (!s) + { + break; + } + if (s->internal) + { + index++; + continue; + } + memset(&entity, 0x00, sizeof(entity)); + + char *name = mqtt_settingname_clean(s->option_name); + entity.id = name; + entity.name = mqtt_fmt_create("%s - %s", s->option_name, s->description); + + entity.stat_t = mqtt_topic_str("%s/%s/status", name); + entity.cmd_t = mqtt_topic_str("%s/%s/command", name); + entity.transmit = &mqtt_settings_tx; + entity.transmit_ctx = s; + entity.received = &mqtt_settings_rx; + entity.received_ctx = s; + + entity.type = ha_unused; + switch (s->type) + { + case TYPE_BOOL: + entity.type = ha_switch; + break; + case TYPE_FLOAT: + entity.type = ha_number; + entity.min = s->min.float_value; + entity.max = s->max.float_value; + break; + case TYPE_HEX: + case TYPE_UNSIGNED: + entity.type = ha_number; + entity.min = s->min.unsigned_value; + entity.max = s->max.unsigned_value; + break; + case TYPE_SIGNED: + entity.type = ha_number; + entity.min = s->min.signed_value; + entity.max = s->max.signed_value; + break; + case TYPE_STRING: + entity.type = ha_text; + break; + default: + break; + } + if (entity.type != ha_unused) + { + ha_add(&ha_server_instance, &entity); + } + index++; + } while (1); + + ha_setup(&ha_box_instance); + osSprintf(ha_box_instance.name, "%sBox", settings_get_string("mqtt.topic")); + osStrcpy(ha_box_instance.id, "teddyCloudBox"); + memset(&entity, 0x00, sizeof(entity)); entity.id = "status"; entity.name = "Status message"; entity.type = ha_sensor; entity.stat_t = "%s/status"; - ha_add(&entity); + ha_add(&ha_box_instance, &entity); memset(&entity, 0x00, sizeof(entity)); entity.id = "TagInvalid"; entity.name = "Tag Invalid"; entity.type = ha_sensor; entity.stat_t = "%s/event/TagInvalid"; - ha_add(&entity); + ha_add(&ha_box_instance, &entity); memset(&entity, 0x00, sizeof(entity)); entity.id = "TagValid"; entity.name = "Tag Valid"; entity.type = ha_sensor; entity.stat_t = "%s/event/TagValid"; - ha_add(&entity); + ha_add(&ha_box_instance, &entity); memset(&entity, 0x00, sizeof(entity)); entity.id = "VolUp"; entity.name = "Volume Up"; entity.type = ha_binary_sensor; entity.stat_t = "%s/event/VolUp"; - ha_add(&entity); + ha_add(&ha_box_instance, &entity); memset(&entity, 0x00, sizeof(entity)); entity.id = "VolDown"; entity.name = "Volume Down"; entity.type = ha_binary_sensor; entity.stat_t = "%s/event/VolDown"; - ha_add(&entity); + ha_add(&ha_box_instance, &entity); memset(&entity, 0x00, sizeof(entity)); entity.id = "CloudRequest"; entity.name = "Cloud Request"; entity.type = ha_sensor; entity.stat_t = "%s/event/CloudRequest"; - ha_add(&entity); + ha_add(&ha_box_instance, &entity); memset(&entity, 0x00, sizeof(entity)); entity.id = "BoxTilt"; entity.name = "Box Tilt"; entity.type = ha_sensor; entity.stat_t = "%s/event/BoxTilt"; - ha_add(&entity); + ha_add(&ha_box_instance, &entity); memset(&entity, 0x00, sizeof(entity)); entity.id = "TiltForward"; entity.name = "Tilt Forward"; entity.type = ha_sensor; entity.stat_t = "%s/event/TiltForward"; - ha_add(&entity); + ha_add(&ha_box_instance, &entity); memset(&entity, 0x00, sizeof(entity)); entity.id = "TiltBackward"; entity.name = "Tilt Backward"; entity.type = ha_sensor; entity.stat_t = "%s/event/TiltBackward"; - ha_add(&entity); + ha_add(&ha_box_instance, &entity); memset(&entity, 0x00, sizeof(entity)); entity.id = "Playback"; entity.name = "Playback"; entity.type = ha_sensor; entity.stat_t = "%s/event/Playback"; - ha_add(&entity); + ha_add(&ha_box_instance, &entity); memset(&entity, 0x00, sizeof(entity)); entity.id = "VolumeLevel"; entity.name = "Volume Level"; entity.type = ha_sensor; entity.stat_t = "%s/event/VolumeLevel"; - ha_add(&entity); + ha_add(&ha_box_instance, &entity); memset(&entity, 0x00, sizeof(entity)); entity.id = "VolumedB"; @@ -314,5 +642,5 @@ void mqtt_init() entity.type = ha_sensor; entity.stat_t = "%s/event/VolumedB"; entity.unit_of_meas = "dB"; - ha_add(&entity); + ha_add(&ha_box_instance, &entity); } From 19619f88b8f52853aa7e02f64c76bfd0e1ae426f Mon Sep 17 00:00:00 2001 From: manuel Date: Sun, 6 Aug 2023 22:54:46 +0200 Subject: [PATCH 023/126] Add core.sslkeylogfile to log SSL/TLS master keys See https://everything.curl.dev/usingcurl/tls/sslkeylogfile --- include/settings.h | 3 ++- include/tls_adapter.h | 2 ++ include/tls_config.h | 3 +++ src/cloud_request.c | 4 +++- src/server.c | 4 +++- src/settings.c | 1 + src/tls_adapter.c | 36 ++++++++++++++++++++++++++++++++++++ 7 files changed, 50 insertions(+), 3 deletions(-) diff --git a/include/settings.h b/include/settings.h index d9edeb75..6dfa8390 100644 --- a/include/settings.h +++ b/include/settings.h @@ -115,6 +115,7 @@ typedef struct char *librarydir; char *datadir; char *wwwdir; + char *sslkeylogfile; settings_cert_opt_t server_cert; settings_cert_opt_t client_cert; char *allowOrigin; @@ -413,4 +414,4 @@ float settings_get_float_ovl(const char *item, const char *overlay_name); */ bool settings_set_float(const char *item, float value); -#endif \ No newline at end of file +#endif diff --git a/include/tls_adapter.h b/include/tls_adapter.h index a545f272..48a3b99e 100644 --- a/include/tls_adapter.h +++ b/include/tls_adapter.h @@ -12,4 +12,6 @@ extern TlsCache *tlsCache; extern YarrowContext yarrowContext; +void tls_context_key_log_init(TlsContext *context); + #endif diff --git a/include/tls_config.h b/include/tls_config.h index ca9865f5..70bfb917 100644 --- a/include/tls_config.h +++ b/include/tls_config.h @@ -44,6 +44,9 @@ // DTLS support #define DTLS_SUPPORT DISABLED +//Key logging +#define TLS_KEY_LOG_SUPPORT ENABLED + // Client mode of operation #define TLS_CLIENT_SUPPORT ENABLED // Server mode of operation diff --git a/src/cloud_request.c b/src/cloud_request.c index c1b31bfd..dc034d25 100644 --- a/src/cloud_request.c +++ b/src/cloud_request.c @@ -87,6 +87,8 @@ error_t httpClientTlsInitCallback(HttpClientContext *context, if (error) return error; + tls_context_key_log_init(tlsContext); + TRACE_INFO("Initializing TLS done\r\n"); // Successful processing @@ -395,4 +397,4 @@ int_t cloud_request(const char *server, int port, bool https, const char *uri, c httpClientDeinit(&httpClientContext); return 0; -} \ No newline at end of file +} diff --git a/src/server.c b/src/server.c index 1b2f01b9..85d30fcf 100644 --- a/src/server.c +++ b/src/server.c @@ -403,6 +403,8 @@ error_t httpServerTlsInitCallback(HttpConnection *connection, TlsContext *tlsCon if (error) return error; + tls_context_key_log_init(tlsContext); + // Session cache that will be used to save/resume TLS sessions error = tlsSetCache(tlsContext, tlsCache); // Any error to report? @@ -551,4 +553,4 @@ void server_init() TRACE_INFO("Exiting TeddyCloud with returncode %d\r\n", ret); exit(ret); -} \ No newline at end of file +} diff --git a/src/settings.c b/src/settings.c index 251a2c4c..3b6d3e2e 100644 --- a/src/settings.c +++ b/src/settings.c @@ -36,6 +36,7 @@ static void option_map_init(uint8_t settingsId) OPTION_STRING("core.librarydir", &settings->core.librarydir, "library", "Directory wof the audio library") OPTION_STRING("core.datadir", &settings->core.datadir, "data", "Base directory for contentdir/wwwdir when relative") OPTION_STRING("core.wwwdir", &settings->core.wwwdir, "www", "Directory where web content is placed") + OPTION_STRING("core.sslkeylogfile", &settings->core.sslkeylogfile, "", "SSL/TLS key log filename") OPTION_STRING("core.server_cert.file.ca", &settings->core.server_cert.file.ca, "certs/server/ca-root.pem", "Server CA") OPTION_STRING("core.server_cert.file.crt", &settings->core.server_cert.file.crt, "certs/server/teddy-cert.pem", "Server certificate") diff --git a/src/tls_adapter.c b/src/tls_adapter.c index da39d26c..d7897978 100644 --- a/src/tls_adapter.c +++ b/src/tls_adapter.c @@ -11,6 +11,7 @@ #include "debug.h" #include "settings.h" #include "fs_port.h" +#include "fs_ext.h" // tsl_certificate.c function Dependencies #include @@ -307,6 +308,41 @@ error_t read_certificate(const char_t *filename, char_t **buffer, size_t *length return error; } +static void keylog_write(TlsContext *context, const char_t *key) +{ + static bool failed = false; + const char *logfile = settings_get_string("core.sslkeylogfile"); + if (!logfile || !osStrlen(logfile)) + return; + + FsFile *keyLogFile = fsOpenFileEx(logfile, "a"); + if (keyLogFile == NULL) + { + if (!failed) + { + TRACE_ERROR("Failed to open ssl key log file \"%s\"\r\n", logfile); + failed = true; + } + return; + } + + char buf[256]; // key is at most 194 chars. see tlsDumpSecret + size_t len = osStrlen(key); + if (len > sizeof(buf) - 2) + return; + osMemcpy(buf, key, len); + buf[len++] = '\n'; + buf[len] = '\0'; + fsWriteFile(keyLogFile, buf, len); + fsCloseFile(keyLogFile); + failed = false; +} + +void tls_context_key_log_init(TlsContext *context) +{ + (void)tlsSetKeyLogCallback(context, keylog_write); +} + error_t tls_adapter_deinit() { // Release PRNG context From e8a7e5007bde4b927b39c5caefd5a4337b248db1 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Sun, 6 Aug 2023 23:33:31 +0200 Subject: [PATCH 024/126] added per-box HA entities --- include/handler_rtnl.h | 6 +- include/home_assistant.h | 3 +- include/mqtt.h | 1 + src/handler_rtnl.c | 81 ++++---- src/home_assistant.c | 5 +- src/mqtt.c | 438 ++++++++++++++++++++++++--------------- 6 files changed, 319 insertions(+), 215 deletions(-) diff --git a/include/handler_rtnl.h b/include/handler_rtnl.h index 8258adff..21f28b35 100644 --- a/include/handler_rtnl.h +++ b/include/handler_rtnl.h @@ -37,8 +37,8 @@ typedef enum } rtnl_function; error_t handleRtnl(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *ctx); -void rtnlEvent(TonieRtnlRPC *rpc); -void rtnlEventLog(TonieRtnlRPC *rpc); -void rtnlEventDump(TonieRtnlRPC *rpc, settings_t *settings); +void rtnlEvent(HttpConnection *connection, TonieRtnlRPC *rpc); +void rtnlEventLog(HttpConnection *connection, TonieRtnlRPC *rpc); +void rtnlEventDump(HttpConnection *connection, TonieRtnlRPC *rpc, settings_t *settings); #endif \ No newline at end of file diff --git a/include/home_assistant.h b/include/home_assistant.h index a029ecc9..59de26b5 100644 --- a/include/home_assistant.h +++ b/include/home_assistant.h @@ -3,7 +3,7 @@ #include #include -#define MAX_LEN 32 +#define MAX_LEN 128 #define MAX_ENTITIES (3 * 9 + 16 * 7 + 32) typedef enum @@ -87,6 +87,7 @@ struct s_ha_entity struct s_ha_info { + bool initialized; char base_topic[MAX_LEN]; char name[MAX_LEN]; char id[MAX_LEN]; diff --git a/include/mqtt.h b/include/mqtt.h index 65ed6b62..931bdb51 100644 --- a/include/mqtt.h +++ b/include/mqtt.h @@ -4,5 +4,6 @@ void mqtt_init(); error_t mqtt_sendEvent(const char *eventname, const char *content); +error_t mqtt_sendBoxEvent(const char *box_id, const char *eventname, const char *content); bool mqtt_publish(const char *item_topic, const char *content); bool mqtt_subscribe(const char *item_topic); diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index 53130cbb..02d55f94 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -127,9 +127,9 @@ error_t handleRtnl(HttpConnection *connection, const char_t *uri, const char_t * pos += protoLength; if (rpc && (rpc->log2 || rpc->log3)) { - rtnlEvent(rpc); - rtnlEventLog(rpc); - rtnlEventDump(rpc, client_ctx->settings); + rtnlEvent(connection, rpc); + rtnlEventLog(connection, rpc); + rtnlEventDump(connection, rpc, client_ctx->settings); } tonie_rtnl_rpc__free_unpacked(rpc, NULL); } while (true); @@ -146,9 +146,16 @@ int32_t read_little_endian(const uint8_t *buf) return (int32_t)(buf[0] | buf[1] << 8 | buf[2] << 16 | buf[3] << 24); } -void rtnlEvent(TonieRtnlRPC *rpc) +void rtnlEvent(HttpConnection *connection, TonieRtnlRPC *rpc) { char_t buffer[4096]; + const char *box_id = NULL; + + if (connection->tlsContext != NULL && osStrlen(connection->tlsContext->client_cert_issuer)) + { + box_id = connection->tlsContext->client_cert_subject; + } + if (rpc->log2) { sse_startEventRaw("rtnl-raw-log2"); @@ -210,49 +217,49 @@ void rtnlEvent(TonieRtnlRPC *rpc) { case 1: sse_sendEvent("pressed", "ear-big", true); - mqtt_sendEvent("VolUp", "ON"); - mqtt_sendEvent("VolUp", "OFF"); + mqtt_sendBoxEvent(box_id, "VolUp", "ON"); + mqtt_sendBoxEvent(box_id, "VolUp", "OFF"); break; case 2: sse_sendEvent("pressed", "ear-small", true); - mqtt_sendEvent("VolDown", "ON"); - mqtt_sendEvent("VolDown", "OFF"); + mqtt_sendBoxEvent(box_id, "VolDown", "ON"); + mqtt_sendBoxEvent(box_id, "VolDown", "OFF"); break; case 3: sse_sendEvent("knock", "forward", true); - mqtt_sendEvent("KnockForward", "ON"); - mqtt_sendEvent("KnockForward", "OFF"); + mqtt_sendBoxEvent(box_id, "KnockForward", "ON"); + mqtt_sendBoxEvent(box_id, "KnockForward", "OFF"); break; case 4: sse_sendEvent("knock", "backward", true); - mqtt_sendEvent("KnockBackward", "ON"); - mqtt_sendEvent("KnockBackward", "OFF"); + mqtt_sendBoxEvent(box_id, "KnockBackward", "ON"); + mqtt_sendBoxEvent(box_id, "KnockBackward", "OFF"); break; case 5: sse_sendEvent("tilt", "forward", true); - mqtt_sendEvent("TiltForward", "ON"); - mqtt_sendEvent("TiltForward", "OFF"); + mqtt_sendBoxEvent(box_id, "TiltForward", "ON"); + mqtt_sendBoxEvent(box_id, "TiltForward", "OFF"); break; case 6: sse_sendEvent("tilt", "backward", true); - mqtt_sendEvent("TiltBackward", "ON"); - mqtt_sendEvent("TiltBackward", "OFF"); + mqtt_sendBoxEvent(box_id, "TiltBackward", "ON"); + mqtt_sendBoxEvent(box_id, "TiltBackward", "OFF"); break; case 11: sse_sendEvent("playback", "starting", true); - mqtt_sendEvent("Playback", "ON"); - mqtt_sendEvent("TagInvalid", ""); + mqtt_sendBoxEvent(box_id, "Playback", "ON"); + mqtt_sendBoxEvent(box_id, "TagInvalid", ""); break; case 12: sse_sendEvent("playback", "started", true); - mqtt_sendEvent("Playback", "ON"); - mqtt_sendEvent("TagInvalid", ""); + mqtt_sendBoxEvent(box_id, "Playback", "ON"); + mqtt_sendBoxEvent(box_id, "TagInvalid", ""); break; case 13: sse_sendEvent("playback", "stopped", true); - mqtt_sendEvent("Playback", "OFF"); - mqtt_sendEvent("TagValid", ""); - mqtt_sendEvent("TagInvalid", ""); + mqtt_sendBoxEvent(box_id, "Playback", "OFF"); + mqtt_sendBoxEvent(box_id, "TagValid", ""); + mqtt_sendBoxEvent(box_id, "TagInvalid", ""); break; default: TRACE_WARNING("Not-yet-known log3 type: %d\r\n", rpc->log3->field2); @@ -275,8 +282,8 @@ void rtnlEvent(TonieRtnlRPC *rpc) } } sse_sendEvent("TagInvalid", buffer, true); - mqtt_sendEvent("TagInvalid", buffer); - mqtt_sendEvent("TagValid", ""); + mqtt_sendBoxEvent(box_id, "TagInvalid", buffer); + mqtt_sendBoxEvent(box_id, "TagValid", ""); } else if (rpc->log2->function_group == 15 && rpc->log2->function == 16065) { @@ -288,8 +295,8 @@ void rtnlEvent(TonieRtnlRPC *rpc) } } sse_sendEvent("TagValid", buffer, true); - mqtt_sendEvent("TagValid", buffer); - mqtt_sendEvent("TagInvalid", ""); + mqtt_sendBoxEvent(box_id, "TagValid", buffer); + mqtt_sendBoxEvent(box_id, "TagInvalid", ""); } /* CC also sends messages */ else if (rpc->log2->function_group == 15 && rpc->log2->function == 8646) @@ -302,8 +309,8 @@ void rtnlEvent(TonieRtnlRPC *rpc) } } sse_sendEvent("TagInvalid", buffer, true); - mqtt_sendEvent("TagInvalid", buffer); - mqtt_sendEvent("TagValid", ""); + mqtt_sendBoxEvent(box_id, "TagInvalid", buffer); + mqtt_sendBoxEvent(box_id, "TagValid", ""); } else if (rpc->log2->function_group == 15 && rpc->log2->function == 8627) { @@ -315,22 +322,22 @@ void rtnlEvent(TonieRtnlRPC *rpc) } } sse_sendEvent("TagValid", buffer, true); - mqtt_sendEvent("TagValid", buffer); - mqtt_sendEvent("TagInvalid", ""); + mqtt_sendBoxEvent(box_id, "TagValid", buffer); + mqtt_sendBoxEvent(box_id, "TagInvalid", ""); } else if (rpc->log2->function_group == 12 && rpc->log2->function == 15427) { int32_t angle = read_little_endian(rpc->log2->field6.data); osSprintf(buffer, "%d", angle); sse_sendEvent("BoxTilt", buffer, true); - mqtt_sendEvent("BoxTilt", buffer); + mqtt_sendBoxEvent(box_id, "BoxTilt", buffer); } else if (rpc->log2->function_group == 12 && rpc->log2->function == 15426) { int32_t angle = read_little_endian(rpc->log2->field6.data); osSprintf(buffer, "%d", angle); sse_sendEvent("BoxTilt", buffer, true); - mqtt_sendEvent("BoxTilt", buffer); + mqtt_sendBoxEvent(box_id, "BoxTilt", buffer); } else if (rpc->log2->function_group == 27 && rpc->log2->function == 15524) { @@ -339,15 +346,15 @@ void rtnlEvent(TonieRtnlRPC *rpc) int32_t volumeLevel = read_little_endian(&rpc->log2->field6.data[8]); osSprintf(buffer, "%d", volumeLevel); sse_sendEvent("VolumeLevel", buffer, true); - mqtt_sendEvent("VolumeLevel", buffer); + mqtt_sendBoxEvent(box_id, "VolumeLevel", buffer); osSprintf(buffer, "%d", volumedB); sse_sendEvent("VolumedB", buffer, true); - mqtt_sendEvent("VolumedB", buffer); + mqtt_sendBoxEvent(box_id, "VolumedB", buffer); } } } -void rtnlEventLog(TonieRtnlRPC *rpc) +void rtnlEventLog(HttpConnection *connection, TonieRtnlRPC *rpc) { TRACE_DEBUG("RTNL: \r\n"); if (rpc->log2) @@ -384,7 +391,7 @@ void rtnlEventLog(TonieRtnlRPC *rpc) } } -void rtnlEventDump(TonieRtnlRPC *rpc, settings_t *settings) +void rtnlEventDump(HttpConnection *connection, TonieRtnlRPC *rpc, settings_t *settings) { if (settings->rtnl.logHuman) { diff --git a/src/home_assistant.c b/src/home_assistant.c index d698a546..aea2a6df 100644 --- a/src/home_assistant.c +++ b/src/home_assistant.c @@ -103,8 +103,8 @@ void ha_addint(char *json_str, const char *name, int value, bool last) void ha_publish(t_ha_info *ha_info) { char *json_str = (char *)osAllocMem(1024); - char mqtt_path[128]; - char uniq_id[128]; + char mqtt_path[2 * MAX_LEN + 1]; + char uniq_id[2 * MAX_LEN + 1]; TRACE_INFO("[HA] Publish\n"); @@ -319,6 +319,7 @@ void ha_setup(t_ha_info *ha_info) osSprintf(ha_info->mdl, "%s", "teddyCloud"); osSprintf(ha_info->sw, "" BUILD_GIT_TAG " (" BUILD_GIT_SHORT_SHA ")"); ha_info->entitiy_count = 0; + ha_info->initialized = true; } void ha_connected(t_ha_info *ha_info) diff --git a/src/mqtt.c b/src/mqtt.c index 8610c429..e79643e3 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -16,8 +16,12 @@ #include "debug.h" #include "mqtt.h" -/* need one per box in future */ -t_ha_info ha_box_instance; +OsMutex mqtt_tx_buffer_mutex; +OsMutex mqtt_box_mutex; + +#define MQTT_BOX_INSTANCES 32 +t_ha_info *mqtt_get_box(const char *box_id); +t_ha_info ha_box_instances[MQTT_BOX_INSTANCES]; t_ha_info ha_server_instance; bool_t mqttConnected = FALSE; @@ -34,11 +38,107 @@ typedef struct char *payload; } mqtt_tx_buffer; -OsMutex mqtt_tx_buffer_mutex; - #define MQTT_TX_BUFFERS 512 mqtt_tx_buffer mqtt_tx_buffers[MQTT_TX_BUFFERS]; +char *mqtt_sanitize_id(const char *input) +{ + char *new_str = osAllocMem(osStrlen(input) + 1); + if (new_str == NULL) + { + return NULL; + } + + char *dst = new_str; + const char *src = input; + while (*src) + { + if (isalnum((unsigned char)*src) || *src == '_' || *src == '-') + { + *dst++ = *src; + } + src++; + } + *dst = '\0'; // null terminate the string + + return new_str; +} + +char *mqtt_settingname_clean(const char *str) +{ + int length = osStrlen(str) + 1; + + char *new_str = osAllocMem(length); + if (new_str == NULL) + { + return NULL; + } + osStrcpy(new_str, str); + + for (int pos = 0; pos < osStrlen(new_str); pos++) + { + if (new_str[pos] == '.') + { + new_str[pos] = '-'; + } + } + + return new_str; +} + +char *mqtt_fmt_create(const char *fmt, ...) +{ + va_list args; + va_start(args, fmt); + + // Calculate the length of the final string + va_list tmp_args; + va_copy(tmp_args, args); + int length = osVsnprintf(NULL, 0, fmt, tmp_args); + va_end(tmp_args); + + if (length < 0) + { + return NULL; + } + + // Allocate memory for the new string + char *new_str = osAllocMem(length + 1); // Add 1 for the null terminator + if (new_str == NULL) + { + return NULL; + } + + // Format the new string + osVsnprintf(new_str, length + 1, fmt, args); + + va_end(args); + + return new_str; +} + +char *mqtt_topic_str(const char *fmt, const char *param) +{ + char *first_s = osStrstr(fmt, "%s"); + if (first_s == NULL) + { + return "none"; + } + + int length = osStrlen(fmt) + osStrlen(param) - 2; + + char *new_str = osAllocMem(length + 1); + if (new_str == NULL) + { + return NULL; + } + osStrcpy(new_str, "%s"); + + osSprintf(&new_str[2], &fmt[2], param); + + return new_str; +} + char *mqtt_prefix(const char *path) { static char buffer[MQTT_TOPIC_STRING_LENGTH]; @@ -58,6 +158,15 @@ error_t mqtt_sendEvent(const char *eventname, const char *content) return NO_ERROR; } +error_t mqtt_sendBoxEvent(const char *box_id, const char *eventname, const char *content) +{ + t_ha_info *ha_info = mqtt_get_box(box_id); + char *topic = mqtt_fmt_create("%%s/%s", eventname); + ha_transmit_topic(ha_info, topic, content); + osFreeMem(topic); + return NO_ERROR; +} + void mqttTestPublishCallback(MqttClientContext *context, const char_t *topic, const uint8_t *message, size_t length, bool_t dup, MqttQosLevel qos, bool_t retain, uint16_t packetId) @@ -75,7 +184,15 @@ void mqttTestPublishCallback(MqttClientContext *context, osMemcpy(payload, message, length); payload[length] = 0; - ha_received(&ha_box_instance, (char *)topic, (const char *)payload); + osAcquireMutex(&mqtt_box_mutex); + for (int pos = 0; pos < MQTT_BOX_INSTANCES; pos++) + { + if (ha_box_instances[pos].initialized) + { + ha_received(&ha_box_instances[pos], (char *)topic, (const char *)payload); + } + } + osReleaseMutex(&mqtt_box_mutex); ha_received(&ha_server_instance, (char *)topic, (const char *)payload); osFreeMem(payload); @@ -197,7 +314,15 @@ void mqtt_thread() TRACE_INFO("Connected\r\n"); mqttConnected = TRUE; mqtt_fail = false; - ha_connected(&ha_box_instance); + osAcquireMutex(&mqtt_box_mutex); + for (int pos = 0; pos < MQTT_BOX_INSTANCES; pos++) + { + if (ha_box_instances[pos].initialized) + { + ha_connected(&ha_box_instances[pos]); + } + } + osReleaseMutex(&mqtt_box_mutex); ha_connected(&ha_server_instance); } else @@ -229,7 +354,15 @@ void mqtt_thread() } osReleaseMutex(&mqtt_tx_buffer_mutex); - ha_loop(&ha_box_instance); + osAcquireMutex(&mqtt_box_mutex); + for (int pos = 0; pos < MQTT_BOX_INSTANCES; pos++) + { + if (ha_box_instances[pos].initialized) + { + ha_loop(&ha_box_instances[pos]); + } + } + osReleaseMutex(&mqtt_box_mutex); ha_loop(&ha_server_instance); } } @@ -278,81 +411,6 @@ void mqtt_publish_int(const char *name, uint32_t value) } } -char *mqtt_settingname_clean(const char *str) -{ - int length = osStrlen(str) + 1; - - char *new_str = osAllocMem(length); - if (new_str == NULL) - { - return NULL; - } - osStrcpy(new_str, str); - - for (int pos = 0; pos < osStrlen(new_str); pos++) - { - if (new_str[pos] == '.') - { - new_str[pos] = '-'; - } - } - - return new_str; -} - -char *mqtt_fmt_create(const char *fmt, ...) -{ - va_list args; - va_start(args, fmt); - - // Calculate the length of the final string - va_list tmp_args; - va_copy(tmp_args, args); - int length = osVsnprintf(NULL, 0, fmt, tmp_args); - va_end(tmp_args); - - if (length < 0) - { - return NULL; - } - - // Allocate memory for the new string - char *new_str = osAllocMem(length + 1); // Add 1 for the null terminator - if (new_str == NULL) - { - return NULL; - } - - // Format the new string - osVsnprintf(new_str, length + 1, fmt, args); - - va_end(args); - - return new_str; -} - -char *mqtt_topic_str(const char *fmt, char *param) -{ - char *first_s = osStrstr(fmt, "%s"); - if (first_s == NULL) - { - return "none"; - } - - int length = osStrlen(fmt) + osStrlen(param) - 2; - - char *new_str = osAllocMem(length + 1); - if (new_str == NULL) - { - return NULL; - } - osStrcpy(new_str, "%s"); - - osSprintf(&new_str[2], &fmt[2], param); - - return new_str; -} - void mqtt_publish_settings() { int index = 0; @@ -483,9 +541,134 @@ void mqtt_settings_rx(t_ha_info *ha_info, const t_ha_entity *entity, void *ctx, } } +void mqtt_init_box(const char *box_id_in, t_ha_info *ha_box_instance) +{ + t_ha_entity entity; + char *box_id = mqtt_sanitize_id(box_id_in); + + ha_setup(ha_box_instance); + osSprintf(ha_box_instance->name, "Toniebox: '%s'", box_id_in); + osSprintf(ha_box_instance->id, "teddyCloud_Box_%s", box_id); + osSprintf(ha_box_instance->base_topic, "%s/box/%s", settings_get_string("mqtt.topic"), box_id); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "TagInvalid"; + entity.name = "Tag Invalid"; + entity.type = ha_sensor; + entity.stat_t = "%s/TagInvalid"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "TagValid"; + entity.name = "Tag Valid"; + entity.type = ha_sensor; + entity.stat_t = "%s/TagValid"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "VolUp"; + entity.name = "Volume Up"; + entity.type = ha_binary_sensor; + entity.stat_t = "%s/VolUp"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "VolDown"; + entity.name = "Volume Down"; + entity.type = ha_binary_sensor; + entity.stat_t = "%s/VolDown"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "CloudRequest"; + entity.name = "Cloud Request"; + entity.type = ha_sensor; + entity.stat_t = "%s/CloudRequest"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "BoxTilt"; + entity.name = "Box Tilt"; + entity.type = ha_sensor; + entity.stat_t = "%s/BoxTilt"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "TiltForward"; + entity.name = "Tilt Forward"; + entity.type = ha_sensor; + entity.stat_t = "%s/TiltForward"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "TiltBackward"; + entity.name = "Tilt Backward"; + entity.type = ha_sensor; + entity.stat_t = "%s/TiltBackward"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "Playback"; + entity.name = "Playback"; + entity.type = ha_sensor; + entity.stat_t = "%s/Playback"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "VolumeLevel"; + entity.name = "Volume Level"; + entity.type = ha_sensor; + entity.stat_t = "%s/VolumeLevel"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "VolumedB"; + entity.name = "Volume dB"; + entity.type = ha_sensor; + entity.stat_t = "%s/VolumedB"; + entity.unit_of_meas = "dB"; + ha_add(ha_box_instance, &entity); + + osFreeMem(box_id); +} + +t_ha_info *mqtt_get_box(const char *box_id) +{ + t_ha_info *ret = NULL; + char *name = mqtt_fmt_create("teddyCloud_Box_%s", box_id); + + osAcquireMutex(&mqtt_box_mutex); + for (int pos = 0; pos < MQTT_BOX_INSTANCES; pos++) + { + if (ha_box_instances[pos].initialized && !osStrcasecmp(name, ha_box_instances[pos].id)) + { + ret = &ha_box_instances[pos]; + break; + } + } + if (!ret) + { + for (int pos = 0; pos < MQTT_BOX_INSTANCES; pos++) + { + if (!ha_box_instances[pos].initialized) + { + ret = &ha_box_instances[pos]; + mqtt_init_box(box_id, ret); + ha_connected(ret); + break; + } + } + } + osReleaseMutex(&mqtt_box_mutex); + osFreeMem(name); + return ret; +} + void mqtt_init() { osCreateMutex(&mqtt_tx_buffer_mutex); + osCreateMutex(&mqtt_box_mutex); + osCreateTask("MQTT", &mqtt_thread, NULL, 1024, 0); t_ha_entity entity; @@ -493,6 +676,7 @@ void mqtt_init() ha_setup(&ha_server_instance); osSprintf(ha_server_instance.name, "%s - Server", settings_get_string("mqtt.topic")); osStrcpy(ha_server_instance.id, "teddyCloudSettings"); + osStrcpy(ha_server_instance.base_topic, settings_get_string("mqtt.topic")); int index = 0; do @@ -512,7 +696,6 @@ void mqtt_init() char *name = mqtt_settingname_clean(s->option_name); entity.id = name; entity.name = mqtt_fmt_create("%s - %s", s->option_name, s->description); - entity.stat_t = mqtt_topic_str("%s/%s/status", name); entity.cmd_t = mqtt_topic_str("%s/%s/command", name); entity.transmit = &mqtt_settings_tx; @@ -554,93 +737,4 @@ void mqtt_init() } index++; } while (1); - - ha_setup(&ha_box_instance); - osSprintf(ha_box_instance.name, "%sBox", settings_get_string("mqtt.topic")); - osStrcpy(ha_box_instance.id, "teddyCloudBox"); - - memset(&entity, 0x00, sizeof(entity)); - entity.id = "status"; - entity.name = "Status message"; - entity.type = ha_sensor; - entity.stat_t = "%s/status"; - ha_add(&ha_box_instance, &entity); - - memset(&entity, 0x00, sizeof(entity)); - entity.id = "TagInvalid"; - entity.name = "Tag Invalid"; - entity.type = ha_sensor; - entity.stat_t = "%s/event/TagInvalid"; - ha_add(&ha_box_instance, &entity); - - memset(&entity, 0x00, sizeof(entity)); - entity.id = "TagValid"; - entity.name = "Tag Valid"; - entity.type = ha_sensor; - entity.stat_t = "%s/event/TagValid"; - ha_add(&ha_box_instance, &entity); - - memset(&entity, 0x00, sizeof(entity)); - entity.id = "VolUp"; - entity.name = "Volume Up"; - entity.type = ha_binary_sensor; - entity.stat_t = "%s/event/VolUp"; - ha_add(&ha_box_instance, &entity); - - memset(&entity, 0x00, sizeof(entity)); - entity.id = "VolDown"; - entity.name = "Volume Down"; - entity.type = ha_binary_sensor; - entity.stat_t = "%s/event/VolDown"; - ha_add(&ha_box_instance, &entity); - - memset(&entity, 0x00, sizeof(entity)); - entity.id = "CloudRequest"; - entity.name = "Cloud Request"; - entity.type = ha_sensor; - entity.stat_t = "%s/event/CloudRequest"; - ha_add(&ha_box_instance, &entity); - - memset(&entity, 0x00, sizeof(entity)); - entity.id = "BoxTilt"; - entity.name = "Box Tilt"; - entity.type = ha_sensor; - entity.stat_t = "%s/event/BoxTilt"; - ha_add(&ha_box_instance, &entity); - - memset(&entity, 0x00, sizeof(entity)); - entity.id = "TiltForward"; - entity.name = "Tilt Forward"; - entity.type = ha_sensor; - entity.stat_t = "%s/event/TiltForward"; - ha_add(&ha_box_instance, &entity); - - memset(&entity, 0x00, sizeof(entity)); - entity.id = "TiltBackward"; - entity.name = "Tilt Backward"; - entity.type = ha_sensor; - entity.stat_t = "%s/event/TiltBackward"; - ha_add(&ha_box_instance, &entity); - - memset(&entity, 0x00, sizeof(entity)); - entity.id = "Playback"; - entity.name = "Playback"; - entity.type = ha_sensor; - entity.stat_t = "%s/event/Playback"; - ha_add(&ha_box_instance, &entity); - - memset(&entity, 0x00, sizeof(entity)); - entity.id = "VolumeLevel"; - entity.name = "Volume Level"; - entity.type = ha_sensor; - entity.stat_t = "%s/event/VolumeLevel"; - ha_add(&ha_box_instance, &entity); - - memset(&entity, 0x00, sizeof(entity)); - entity.id = "VolumedB"; - entity.name = "Volume dB"; - entity.type = ha_sensor; - entity.stat_t = "%s/event/VolumedB"; - entity.unit_of_meas = "dB"; - ha_add(&ha_box_instance, &entity); } From 27ea0c13d5e2f7dd1940aa4de71b8bc8747539c7 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Mon, 7 Aug 2023 00:39:47 +0200 Subject: [PATCH 025/126] add charger state --- src/handler_rtnl.c | 8 ++++++++ src/mqtt.c | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index 02d55f94..8cc2ee7a 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -245,6 +245,14 @@ void rtnlEvent(HttpConnection *connection, TonieRtnlRPC *rpc) mqtt_sendBoxEvent(box_id, "TiltBackward", "ON"); mqtt_sendBoxEvent(box_id, "TiltBackward", "OFF"); break; + case 7: + sse_sendEvent("charger", "on", true); + mqtt_sendBoxEvent(box_id, "Charger", "ON"); + break; + case 8: + sse_sendEvent("charger", "on", true); + mqtt_sendBoxEvent(box_id, "Charger", "OFF"); + break; case 11: sse_sendEvent("playback", "starting", true); mqtt_sendBoxEvent(box_id, "Playback", "ON"); diff --git a/src/mqtt.c b/src/mqtt.c index e79643e3..25b446c5 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -629,6 +629,13 @@ void mqtt_init_box(const char *box_id_in, t_ha_info *ha_box_instance) entity.unit_of_meas = "dB"; ha_add(ha_box_instance, &entity); + memset(&entity, 0x00, sizeof(entity)); + entity.id = "Charger"; + entity.name = "Charger"; + entity.type = ha_binary_sensor; + entity.stat_t = "%s/Charger"; + ha_add(ha_box_instance, &entity); + osFreeMem(box_id); } From f1c4212970c4a32cb76a4e9be92099b44fa83a7b Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Mon, 7 Aug 2023 01:02:16 +0200 Subject: [PATCH 026/126] added LastSeen field --- src/handler_rtnl.c | 23 +++++++++++++++++++++++ src/mqtt.c | 16 ++++++++++++++++ src/platform/platform_linux.c | 10 ++++++++++ 3 files changed, 49 insertions(+) diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index 8cc2ee7a..f866b226 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -80,10 +80,33 @@ static void escapeString(const char_t *input, size_t size, char_t *output) output[j] = '\0'; } +static void time_format(time_t time, char_t *buffer) +{ + DateTime dateTime; + + convertUnixTimeToDate(time, &dateTime); + + osSprintf(buffer, "%04" PRIu16 "-%02" PRIu8 "-%02" PRIu8 "T%02" PRIu8 ":%02" PRIu8 ":%02" PRIu8 "Z", + dateTime.year, dateTime.month, dateTime.day, dateTime.hours, dateTime.minutes, + dateTime.seconds); +} + error_t handleRtnl(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx) { char_t *buffer = connection->buffer; size_t size = connection->response.contentLength; + const char *box_id = NULL; + + if (connection->tlsContext != NULL && osStrlen(connection->tlsContext->client_cert_issuer)) + { + box_id = connection->tlsContext->client_cert_subject; + } + + time_t time = getCurrentUnixTime(); + char current_time[64]; + time_format(time, current_time); + + mqtt_sendBoxEvent(box_id, "LastSeen", current_time); size_t pos = 0; do diff --git a/src/mqtt.c b/src/mqtt.c index 25b446c5..1552f37f 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -161,6 +161,10 @@ error_t mqtt_sendEvent(const char *eventname, const char *content) error_t mqtt_sendBoxEvent(const char *box_id, const char *eventname, const char *content) { t_ha_info *ha_info = mqtt_get_box(box_id); + if (!ha_info) + { + return ERROR_FAILURE; + } char *topic = mqtt_fmt_create("%%s/%s", eventname); ha_transmit_topic(ha_info, topic, content); osFreeMem(topic); @@ -636,11 +640,23 @@ void mqtt_init_box(const char *box_id_in, t_ha_info *ha_box_instance) entity.stat_t = "%s/Charger"; ha_add(ha_box_instance, &entity); + memset(&entity, 0x00, sizeof(entity)); + entity.id = "LastSeen"; + entity.name = "LastSeen"; + entity.type = ha_sensor; + entity.stat_t = "%s/LastSeen"; + entity.dev_class = "timestamp"; + ha_add(ha_box_instance, &entity); + osFreeMem(box_id); } t_ha_info *mqtt_get_box(const char *box_id) { + if (!box_id) + { + return NULL; + } t_ha_info *ret = NULL; char *name = mqtt_fmt_create("teddyCloud_Box_%s", box_id); diff --git a/src/platform/platform_linux.c b/src/platform/platform_linux.c index 63a2e954..c78adbab 100644 --- a/src/platform/platform_linux.c +++ b/src/platform/platform_linux.c @@ -460,3 +460,13 @@ uint_t tcpWaitForEvents(Socket *socket, uint_t eventMask, systime_t timeout) return 0; } + +/** + * @brief Get current time + * @return Unix timestamp + **/ + +time_t getCurrentUnixTime(void) +{ + return time(NULL); +} \ No newline at end of file From 1cd972d3177e46c75372ba2f845466bd8f813b90 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Mon, 7 Aug 2023 01:15:50 +0200 Subject: [PATCH 027/126] limit MQTT traffic when sending same topic or payload --- src/mqtt.c | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/src/mqtt.c b/src/mqtt.c index 1552f37f..2101c608 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -202,8 +202,25 @@ void mqttTestPublishCallback(MqttClientContext *context, osFreeMem(payload); } +/** + * @brief Publishes an MQTT message by placing it into a transmission buffer. + * + * This function attempts to queue an MQTT message for transmission based on the topic and content provided. + * The function looks for an available slot in the transmission buffer or tries to find an existing message + * with the same topic. If a message with the same topic is found: + * - If the content matches and it is the last message in queue, the message is considered already queued. + * - If the content does not match and the topic has been seen more than twice, the existing message's content + * is replaced to reduce traffic. + * + * Note: The function ensures thread-safety by acquiring and releasing a mutex during the buffer operations. + * + * @param item_topic The topic of the MQTT message. + * @param content The content (payload) of the MQTT message. + * @return Returns true if the message was successfully queued or is already in the queue, otherwise false. + */ bool mqtt_publish(const char *item_topic, const char *content) { + int entries = 0; bool success = false; osAcquireMutex(&mqtt_tx_buffer_mutex); @@ -211,12 +228,39 @@ bool mqtt_publish(const char *item_topic, const char *content) { if (!mqtt_tx_buffers[pos].used) { + /* found the first empty slot */ + if (success) + { + /* was the content already queued before? */ + break; + } + /* new content to send */ mqtt_tx_buffers[pos].topic = strdup(item_topic); mqtt_tx_buffers[pos].payload = strdup(content); mqtt_tx_buffers[pos].used = true; success = true; break; } + else if (!osStrcmp(mqtt_tx_buffers[pos].topic, item_topic)) + { + /* topic matches, assume content differs */ + success = false; + + if (!osStrcmp(mqtt_tx_buffers[pos].payload, content)) + { + /* content matched */ + success = true; + } + else if (++entries > 2) + { + /* when seen more than twice, replace the last one to reduce traffic */ + osFreeMem(mqtt_tx_buffers[pos].payload); + mqtt_tx_buffers[pos].payload = strdup(content); + mqtt_tx_buffers[pos].used = true; + success = true; + break; + } + } } osReleaseMutex(&mqtt_tx_buffer_mutex); From 1c6867da20bf282a903288ad5d1db8d160c0f9f0 Mon Sep 17 00:00:00 2001 From: SciLor Date: Mon, 7 Aug 2023 06:06:14 +0000 Subject: [PATCH 028/126] use mutex manager for mqtt --- src/mqtt.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/mqtt.c b/src/mqtt.c index 2101c608..a8c67709 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -14,6 +14,7 @@ #include "home_assistant.h" #include "debug.h" +#include "mutex_manager.h" #include "mqtt.h" OsMutex mqtt_tx_buffer_mutex; @@ -188,7 +189,7 @@ void mqttTestPublishCallback(MqttClientContext *context, osMemcpy(payload, message, length); payload[length] = 0; - osAcquireMutex(&mqtt_box_mutex); + mutex_lock(MUTEX_MQTT_BOX); for (int pos = 0; pos < MQTT_BOX_INSTANCES; pos++) { if (ha_box_instances[pos].initialized) @@ -196,7 +197,7 @@ void mqttTestPublishCallback(MqttClientContext *context, ha_received(&ha_box_instances[pos], (char *)topic, (const char *)payload); } } - osReleaseMutex(&mqtt_box_mutex); + mutex_unlock(MUTEX_MQTT_BOX); ha_received(&ha_server_instance, (char *)topic, (const char *)payload); osFreeMem(payload); @@ -222,8 +223,8 @@ bool mqtt_publish(const char *item_topic, const char *content) { int entries = 0; bool success = false; - osAcquireMutex(&mqtt_tx_buffer_mutex); + mutex_lock(MUTEX_MQTT_TX_BUFFER); for (int pos = 0; pos < MQTT_TX_BUFFERS; pos++) { if (!mqtt_tx_buffers[pos].used) @@ -262,7 +263,7 @@ bool mqtt_publish(const char *item_topic, const char *content) } } } - osReleaseMutex(&mqtt_tx_buffer_mutex); + mutex_unlock(MUTEX_MQTT_TX_BUFFER); return success; } @@ -362,7 +363,7 @@ void mqtt_thread() TRACE_INFO("Connected\r\n"); mqttConnected = TRUE; mqtt_fail = false; - osAcquireMutex(&mqtt_box_mutex); + mutex_lock(MUTEX_MQTT_BOX); for (int pos = 0; pos < MQTT_BOX_INSTANCES; pos++) { if (ha_box_instances[pos].initialized) @@ -370,7 +371,7 @@ void mqtt_thread() ha_connected(&ha_box_instances[pos]); } } - osReleaseMutex(&mqtt_box_mutex); + mutex_unlock(MUTEX_MQTT_BOX); ha_connected(&ha_server_instance); } else @@ -389,7 +390,7 @@ void mqtt_thread() } /* process buffered Tx actions */ - osAcquireMutex(&mqtt_tx_buffer_mutex); + mutex_lock(MUTEX_MQTT_TX_BUFFER); for (int pos = 0; pos < MQTT_TX_BUFFERS; pos++) { if (mqtt_tx_buffers[pos].used) @@ -400,9 +401,9 @@ void mqtt_thread() mqtt_tx_buffers[pos].used = false; } } - osReleaseMutex(&mqtt_tx_buffer_mutex); + mutex_unlock(MUTEX_MQTT_TX_BUFFER); - osAcquireMutex(&mqtt_box_mutex); + mutex_lock(MUTEX_MQTT_BOX); for (int pos = 0; pos < MQTT_BOX_INSTANCES; pos++) { if (ha_box_instances[pos].initialized) @@ -410,7 +411,7 @@ void mqtt_thread() ha_loop(&ha_box_instances[pos]); } } - osReleaseMutex(&mqtt_box_mutex); + mutex_unlock(MUTEX_MQTT_BOX); ha_loop(&ha_server_instance); } } @@ -704,7 +705,7 @@ t_ha_info *mqtt_get_box(const char *box_id) t_ha_info *ret = NULL; char *name = mqtt_fmt_create("teddyCloud_Box_%s", box_id); - osAcquireMutex(&mqtt_box_mutex); + mutex_lock(MUTEX_MQTT_BOX); for (int pos = 0; pos < MQTT_BOX_INSTANCES; pos++) { if (ha_box_instances[pos].initialized && !osStrcasecmp(name, ha_box_instances[pos].id)) @@ -726,7 +727,7 @@ t_ha_info *mqtt_get_box(const char *box_id) } } } - osReleaseMutex(&mqtt_box_mutex); + mutex_unlock(MUTEX_MQTT_BOX); osFreeMem(name); return ret; } From 0836d7fd01ec67a7afe9bbb53b279480850ea544 Mon Sep 17 00:00:00 2001 From: SciLor Date: Mon, 7 Aug 2023 06:09:18 +0000 Subject: [PATCH 029/126] add new mutex to mutex_manager --- include/mutex_manager.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/include/mutex_manager.h b/include/mutex_manager.h index bac4b1cb..e2130c51 100644 --- a/include/mutex_manager.h +++ b/include/mutex_manager.h @@ -8,6 +8,8 @@ typedef enum MUTEX_SSE_CTX, MUTEX_SSE_EVENT, MUTEX_RTNL_FILE, + MUTEX_MQTT_TX_BUFFER, + MUTEX_MQTT_BOX, MUTEX_LAST } mutex_id_t; From 5c38b29ea58e228479ff8f573009e169cbc341f4 Mon Sep 17 00:00:00 2001 From: SciLor Date: Mon, 7 Aug 2023 06:17:08 +0000 Subject: [PATCH 030/126] move host url to core --- include/settings.h | 2 +- src/home_assistant.c | 2 +- src/settings.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/include/settings.h b/include/settings.h index d9edeb75..f664a025 100644 --- a/include/settings.h +++ b/include/settings.h @@ -46,7 +46,6 @@ typedef struct char *password; char *identification; char *topic; - char *host_url; } settings_mqtt_t; typedef struct @@ -110,6 +109,7 @@ typedef struct { uint32_t http_port; uint32_t https_port; + char *host_url; char *certdir; char *contentdir; char *librarydir; diff --git a/src/home_assistant.c b/src/home_assistant.c index aea2a6df..f57547dd 100644 --- a/src/home_assistant.c +++ b/src/home_assistant.c @@ -314,7 +314,7 @@ void ha_setup(t_ha_info *ha_info) osSprintf(ha_info->base_topic, "%s", "teddyCloud"); osSprintf(ha_info->name, "%s", ha_info->base_topic); osSprintf(ha_info->id, "%s", "teddyCloud"); - osSprintf(ha_info->cu, "%s", settings_get_string("mqtt.host_url")); + osSprintf(ha_info->cu, "%s", settings_get_string("core.host_url")); osSprintf(ha_info->mf, "RevvoX"); osSprintf(ha_info->mdl, "%s", "teddyCloud"); osSprintf(ha_info->sw, "" BUILD_GIT_TAG " (" BUILD_GIT_SHORT_SHA ")"); diff --git a/src/settings.c b/src/settings.c index 251a2c4c..e90fb4ba 100644 --- a/src/settings.c +++ b/src/settings.c @@ -31,6 +31,7 @@ static void option_map_init(uint8_t settingsId) /* settings for HTTPS server */ OPTION_UNSIGNED("core.server.https_port", &settings->core.http_port, 443, 1, 65535, "HTTPS port") OPTION_UNSIGNED("core.server.http_port", &settings->core.https_port, 80, 1, 65535, "HTTP port") + OPTION_STRING("core.host_url", &settings->core.host_url, "http://localhost/", "URL to teddyCloud server") OPTION_STRING("core.certdir", &settings->core.certdir, "certs/client", "Directory where to upload genuine client certs to") OPTION_STRING("core.contentdir", &settings->core.contentdir, "default", "Directory where cloud content is placed") OPTION_STRING("core.librarydir", &settings->core.librarydir, "library", "Directory wof the audio library") @@ -120,7 +121,6 @@ static void option_map_init(uint8_t settingsId) OPTION_STRING("mqtt.password", &settings->mqtt.password, "", "Password") OPTION_STRING("mqtt.identification", &settings->mqtt.identification, "", "Client identification") OPTION_STRING("mqtt.topic", &settings->mqtt.topic, "teddyCloud", "Topic prefix") - OPTION_STRING("mqtt.host_url", &settings->mqtt.host_url, "http://localhost/", "URL to teddyCloud server") OPTION_END() if (Option_Map_Overlay[settingsId] == NULL) From cfdd53fd97535002546cbc0f9ff26208dc43114a Mon Sep 17 00:00:00 2001 From: SciLor Date: Mon, 7 Aug 2023 06:33:09 +0000 Subject: [PATCH 031/126] Add QoS to settings --- include/settings.h | 1 + src/mqtt.c | 4 ++-- src/settings.c | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/settings.h b/include/settings.h index f664a025..c478c738 100644 --- a/include/settings.h +++ b/include/settings.h @@ -46,6 +46,7 @@ typedef struct char *password; char *identification; char *topic; + uint32_t qosLevel; } settings_mqtt_t; typedef struct diff --git a/src/mqtt.c b/src/mqtt.c index a8c67709..8b40f506 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -154,7 +154,7 @@ error_t mqtt_sendEvent(const char *eventname, const char *content) char topic[MQTT_TOPIC_STRING_LENGTH]; osSnprintf(topic, sizeof(topic), "%s/event/%s", settings_get_string("mqtt.topic"), eventname); - mqttClientPublish(&mqtt_context, topic, content, osStrlen(content), MQTT_QOS_LEVEL_0, false, NULL); + mqttClientPublish(&mqtt_context, topic, content, osStrlen(content), settings_get_unsigned("mqtt.qosLevel"), false, NULL); return NO_ERROR; } @@ -395,7 +395,7 @@ void mqtt_thread() { if (mqtt_tx_buffers[pos].used) { - mqttClientPublish(&mqtt_context, mqtt_tx_buffers[pos].topic, mqtt_tx_buffers[pos].payload, osStrlen(mqtt_tx_buffers[pos].payload), MQTT_QOS_LEVEL_0, false, NULL); + mqttClientPublish(&mqtt_context, mqtt_tx_buffers[pos].topic, mqtt_tx_buffers[pos].payload, osStrlen(mqtt_tx_buffers[pos].payload), settings_get_unsigned("mqtt.qosLevel"), false, NULL); osFreeMem(mqtt_tx_buffers[pos].topic); osFreeMem(mqtt_tx_buffers[pos].payload); mqtt_tx_buffers[pos].used = false; diff --git a/src/settings.c b/src/settings.c index e90fb4ba..4964d2d5 100644 --- a/src/settings.c +++ b/src/settings.c @@ -121,6 +121,7 @@ static void option_map_init(uint8_t settingsId) OPTION_STRING("mqtt.password", &settings->mqtt.password, "", "Password") OPTION_STRING("mqtt.identification", &settings->mqtt.identification, "", "Client identification") OPTION_STRING("mqtt.topic", &settings->mqtt.topic, "teddyCloud", "Topic prefix") + OPTION_UNSIGNED("mqtt.qosLevel", &settings->mqtt.qosLevel, 0, 0, 2, "QoS level") OPTION_END() if (Option_Map_Overlay[settingsId] == NULL) From 86f1864da28fa26ecf35ba1c69faedd94401db93 Mon Sep 17 00:00:00 2001 From: SciLor Date: Mon, 7 Aug 2023 06:33:31 +0000 Subject: [PATCH 032/126] Increase QoS lvl for sub + online/last will --- src/mqtt.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mqtt.c b/src/mqtt.c index 8b40f506..c429217d 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -270,7 +270,7 @@ bool mqtt_publish(const char *item_topic, const char *content) bool mqtt_subscribe(const char *item_topic) { - mqttClientSubscribe(&mqtt_context, item_topic, MQTT_QOS_LEVEL_1, NULL); + mqttClientSubscribe(&mqtt_context, item_topic, MQTT_QOS_LEVEL_2, NULL); return true; } @@ -292,7 +292,7 @@ error_t mqttConnect(MqttClientContext *mqtt_context) mqttClientSetIdentifier(mqtt_context, settings_get_string("mqtt.identification")); mqttClientSetAuthInfo(mqtt_context, settings_get_string("mqtt.username"), settings_get_string("mqtt.password")); - mqttClientSetWillMessage(mqtt_context, mqtt_prefix("status"), "offline", 7, MQTT_QOS_LEVEL_1, FALSE); + mqttClientSetWillMessage(mqtt_context, mqtt_prefix("status"), "offline", 7, MQTT_QOS_LEVEL_2, FALSE); do { @@ -327,11 +327,11 @@ error_t mqttConnect(MqttClientContext *mqtt_context) break; } - error = mqttClientSubscribe(mqtt_context, mqtt_prefix("*"), MQTT_QOS_LEVEL_1, NULL); + error = mqttClientSubscribe(mqtt_context, mqtt_prefix("*"), MQTT_QOS_LEVEL_2, NULL); if (error) break; - error = mqttClientPublish(mqtt_context, mqtt_prefix("status"), "online", 6, MQTT_QOS_LEVEL_1, TRUE, NULL); + error = mqttClientPublish(mqtt_context, mqtt_prefix("status"), "online", 6, MQTT_QOS_LEVEL_2, TRUE, NULL); if (error) break; From 857bde815e3080316d106175159ffc9aef96b3f5 Mon Sep 17 00:00:00 2001 From: SciLor Date: Mon, 7 Aug 2023 06:50:57 +0000 Subject: [PATCH 033/126] RTNL Add log3 enum --- include/handler_rtnl.h | 49 +++++++++++++++++++++++++++--------------- src/handler_rtnl.c | 24 ++++++++++----------- 2 files changed, 44 insertions(+), 29 deletions(-) diff --git a/include/handler_rtnl.h b/include/handler_rtnl.h index 21f28b35..b856edb5 100644 --- a/include/handler_rtnl.h +++ b/include/handler_rtnl.h @@ -15,26 +15,41 @@ typedef enum { - RTNL_FUGR_NETWORK_HTTP = 6, - RTNL_FUGR_FIRMWARE = 8, - RTNL_FUGR_AUDIO = 22, - RTNL_FUGR_VOLUME = 27, - RTNL_FUGR_NETWORK_TCP = 37, -} rtnl_function_group; + RTNL2_FUGR_NETWORK_HTTP = 6, + RTNL2_FUGR_FIRMWARE = 8, + RTNL2_FUGR_AUDIO = 22, + RTNL2_FUGR_VOLUME = 27, + RTNL2_FUGR_NETWORK_TCP = 37, +} rtnl_log2_function_group; typedef enum { - RTNL_FUNC_NETWORK_HTTP_PATH = 110, - RTNL_FUNC_FIRMWARE_NAME = 703, - RTNL_FUNC_FIRMWARE_VERSION = 704, - RTNL_FUNC_NETWORK_HTTP_OTA_LONG = 785, // TODO some other values? - RTNL_FUNC_NETWORK_HTTP_OTA = 791, - RTNL_FUNC_NETWORK_HTTP_FIRMWARE_PATH = 806, - RTNL_FUNC_NETWORK_URL = 1009, - RTNL_FUNC_AUDIO_1 = 6212, // TODO, content path like audio_play - RTNL_FUNC_AUDIO_PLAY = 6480, - RTNL_FUNC_VOLUME_CHANGE = 8672, -} rtnl_function; + RTNL2_FUNC_NETWORK_HTTP_PATH = 110, + RTNL2_FUNC_FIRMWARE_NAME = 703, + RTNL2_FUNC_FIRMWARE_VERSION = 704, + RTNL2_FUNC_NETWORK_HTTP_OTA_LONG = 785, // TODO some other values? + RTNL2_FUNC_NETWORK_HTTP_OTA = 791, + RTNL2_FUNC_NETWORK_HTTP_FIRMWARE_PATH = 806, + RTNL2_FUNC_NETWORK_URL = 1009, + RTNL2_FUNC_AUDIO_1 = 6212, // TODO, content path like audio_play + RTNL2_FUNC_AUDIO_PLAY = 6480, + RTNL2_FUNC_VOLUME_CHANGE = 8672, +} rtnl_log2_function; + +typedef enum +{ + RTNL3_TYPE_EAR_BIG = 1, + RTNL3_TYPE_EAR_SMALL = 2, + RTNL3_TYPE_KNOCK_FORWARD = 3, + RTNL3_TYPE_KNOCK_BACKWARD = 4, + RTNL3_TYPE_TILT_FORWARD = 5, + RTNL3_TYPE_TILT_BACKWARD = 6, + RTNL3_TYPE_CHARGER_ON = 7, + RTNL3_TYPE_CHARGER_OFF = 8, + RTNL3_TYPE_PLAYBACK_STARTING = 11, + RTNL3_TYPE_PLAYBACK_STARTED = 12, + RTNL3_TYPE_PLAYBACK_STOPPED = 13, +} rtnl_log3_type; error_t handleRtnl(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *ctx); void rtnlEvent(HttpConnection *connection, TonieRtnlRPC *rpc); diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index f866b226..de434c58 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -238,55 +238,55 @@ void rtnlEvent(HttpConnection *connection, TonieRtnlRPC *rpc) { switch (rpc->log3->field2) { - case 1: + case RTNL3_TYPE_EAR_BIG: sse_sendEvent("pressed", "ear-big", true); mqtt_sendBoxEvent(box_id, "VolUp", "ON"); mqtt_sendBoxEvent(box_id, "VolUp", "OFF"); break; - case 2: + case RTNL3_TYPE_EAR_SMALL: sse_sendEvent("pressed", "ear-small", true); mqtt_sendBoxEvent(box_id, "VolDown", "ON"); mqtt_sendBoxEvent(box_id, "VolDown", "OFF"); break; - case 3: + case RTNL3_TYPE_KNOCK_FORWARD: sse_sendEvent("knock", "forward", true); mqtt_sendBoxEvent(box_id, "KnockForward", "ON"); mqtt_sendBoxEvent(box_id, "KnockForward", "OFF"); break; - case 4: + case RTNL3_TYPE_KNOCK_BACKWARD: sse_sendEvent("knock", "backward", true); mqtt_sendBoxEvent(box_id, "KnockBackward", "ON"); mqtt_sendBoxEvent(box_id, "KnockBackward", "OFF"); break; - case 5: + case RTNL3_TYPE_TILT_FORWARD: sse_sendEvent("tilt", "forward", true); mqtt_sendBoxEvent(box_id, "TiltForward", "ON"); mqtt_sendBoxEvent(box_id, "TiltForward", "OFF"); break; - case 6: + case RTNL3_TYPE_TILT_BACKWARD: sse_sendEvent("tilt", "backward", true); mqtt_sendBoxEvent(box_id, "TiltBackward", "ON"); mqtt_sendBoxEvent(box_id, "TiltBackward", "OFF"); break; - case 7: + case RTNL3_TYPE_CHARGER_ON: sse_sendEvent("charger", "on", true); mqtt_sendBoxEvent(box_id, "Charger", "ON"); break; - case 8: - sse_sendEvent("charger", "on", true); + case RTNL3_TYPE_CHARGER_OFF: + sse_sendEvent("charger", "off", true); mqtt_sendBoxEvent(box_id, "Charger", "OFF"); break; - case 11: + case RTNL3_TYPE_PLAYBACK_STARTING: sse_sendEvent("playback", "starting", true); mqtt_sendBoxEvent(box_id, "Playback", "ON"); mqtt_sendBoxEvent(box_id, "TagInvalid", ""); break; - case 12: + case RTNL3_TYPE_PLAYBACK_STARTED: sse_sendEvent("playback", "started", true); mqtt_sendBoxEvent(box_id, "Playback", "ON"); mqtt_sendBoxEvent(box_id, "TagInvalid", ""); break; - case 13: + case RTNL3_TYPE_PLAYBACK_STOPPED: sse_sendEvent("playback", "stopped", true); mqtt_sendBoxEvent(box_id, "Playback", "OFF"); mqtt_sendBoxEvent(box_id, "TagValid", ""); From 7e778969d1ed762577d8a98d71686f12cb62f44b Mon Sep 17 00:00:00 2001 From: SciLor Date: Mon, 7 Aug 2023 07:12:16 +0000 Subject: [PATCH 034/126] use rtnl enums --- include/handler_rtnl.h | 11 ++++++++++- src/handler_rtnl.c | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/include/handler_rtnl.h b/include/handler_rtnl.h index b856edb5..6d2ec8c6 100644 --- a/include/handler_rtnl.h +++ b/include/handler_rtnl.h @@ -17,6 +17,8 @@ typedef enum { RTNL2_FUGR_NETWORK_HTTP = 6, RTNL2_FUGR_FIRMWARE = 8, + RTNL2_FUGR_TILT = 12, + RTNL2_FUGR_TAG = 15, RTNL2_FUGR_AUDIO = 22, RTNL2_FUGR_VOLUME = 27, RTNL2_FUGR_NETWORK_TCP = 37, @@ -33,7 +35,14 @@ typedef enum RTNL2_FUNC_NETWORK_URL = 1009, RTNL2_FUNC_AUDIO_1 = 6212, // TODO, content path like audio_play RTNL2_FUNC_AUDIO_PLAY = 6480, - RTNL2_FUNC_VOLUME_CHANGE = 8672, + RTNL2_FUNC_TAG_VALID_CC3200 = 8627, + RTNL2_FUNC_TAG_INVALID_CC3200 = 8646, + RTNL2_FUNC_VOLUME_CHANGE = 8672, // CC3200? + RTNL2_FUNC_VOLUME_CHANGE_ESP32 = 15524, + RTNL2_FUNC_TILT_A_ESP32 = 15426, //? + RTNL2_FUNC_TILT_B_ESP32 = 15427, //? + RTNL2_FUNC_TAG_INVALID_ESP32 = 15452, + RTNL2_FUNC_TAG_VALID_ESP32 = 16065, } rtnl_log2_function; typedef enum diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index de434c58..ad16b0d3 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -303,7 +303,7 @@ void rtnlEvent(HttpConnection *connection, TonieRtnlRPC *rpc) char buffer[33]; /* ESP32 sends tag IDs, even if unknown */ - if (rpc->log2->function_group == 15 && rpc->log2->function == 15452) + if (rpc->log2->function_group == RTNL2_FUGR_TAG && rpc->log2->function == RTNL2_FUNC_TAG_INVALID_ESP32) { if (rpc->log2->field6.len == 8) { @@ -316,7 +316,7 @@ void rtnlEvent(HttpConnection *connection, TonieRtnlRPC *rpc) mqtt_sendBoxEvent(box_id, "TagInvalid", buffer); mqtt_sendBoxEvent(box_id, "TagValid", ""); } - else if (rpc->log2->function_group == 15 && rpc->log2->function == 16065) + else if (rpc->log2->function_group == RTNL2_FUGR_TAG && rpc->log2->function == RTNL2_FUNC_TAG_VALID_ESP32) { if (rpc->log2->field6.len == 8) { @@ -329,8 +329,8 @@ void rtnlEvent(HttpConnection *connection, TonieRtnlRPC *rpc) mqtt_sendBoxEvent(box_id, "TagValid", buffer); mqtt_sendBoxEvent(box_id, "TagInvalid", ""); } - /* CC also sends messages */ - else if (rpc->log2->function_group == 15 && rpc->log2->function == 8646) + /* CC3200 also sends messages */ + else if (rpc->log2->function_group == RTNL2_FUGR_TAG && rpc->log2->function == RTNL2_FUNC_TAG_INVALID_CC3200) { if (rpc->log2->field6.len == 8) { @@ -343,7 +343,7 @@ void rtnlEvent(HttpConnection *connection, TonieRtnlRPC *rpc) mqtt_sendBoxEvent(box_id, "TagInvalid", buffer); mqtt_sendBoxEvent(box_id, "TagValid", ""); } - else if (rpc->log2->function_group == 15 && rpc->log2->function == 8627) + else if (rpc->log2->function_group == RTNL2_FUGR_TAG && rpc->log2->function == RTNL2_FUNC_TAG_VALID_CC3200) { if (rpc->log2->field6.len == 8) { @@ -356,21 +356,21 @@ void rtnlEvent(HttpConnection *connection, TonieRtnlRPC *rpc) mqtt_sendBoxEvent(box_id, "TagValid", buffer); mqtt_sendBoxEvent(box_id, "TagInvalid", ""); } - else if (rpc->log2->function_group == 12 && rpc->log2->function == 15427) + else if (rpc->log2->function_group == RTNL2_FUGR_TILT && rpc->log2->function == RTNL2_FUNC_TILT_A_ESP32) { int32_t angle = read_little_endian(rpc->log2->field6.data); osSprintf(buffer, "%d", angle); - sse_sendEvent("BoxTilt", buffer, true); + sse_sendEvent("BoxTilt-A", buffer, true); mqtt_sendBoxEvent(box_id, "BoxTilt", buffer); } - else if (rpc->log2->function_group == 12 && rpc->log2->function == 15426) + else if (rpc->log2->function_group == RTNL2_FUGR_TILT && rpc->log2->function == RTNL2_FUNC_TILT_B_ESP32) { int32_t angle = read_little_endian(rpc->log2->field6.data); osSprintf(buffer, "%d", angle); - sse_sendEvent("BoxTilt", buffer, true); + sse_sendEvent("BoxTilt-B", buffer, true); mqtt_sendBoxEvent(box_id, "BoxTilt", buffer); } - else if (rpc->log2->function_group == 27 && rpc->log2->function == 15524) + else if (rpc->log2->function_group == RTNL2_FUGR_VOLUME && rpc->log2->function == RTNL2_FUNC_VOLUME_CHANGE_ESP32) { /* 963C0000 D8FFFFFF 00000000 */ int32_t volumedB = read_little_endian(&rpc->log2->field6.data[4]); From 44744df28497feefc975c1d176a01cea90022696 Mon Sep 17 00:00:00 2001 From: SciLor Date: Mon, 7 Aug 2023 14:32:41 +0000 Subject: [PATCH 035/126] Prepare overlayed settings detection --- include/settings.h | 35 ++++++++++++++++++----------------- src/settings.c | 8 ++++++++ 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/include/settings.h b/include/settings.h index c478c738..18bcd3bb 100644 --- a/include/settings.h +++ b/include/settings.h @@ -213,26 +213,27 @@ typedef struct setting_value_t min; setting_value_t max; bool internal; + bool overlayed; } setting_item_t; #define OPTION_START() setting_item_t option_map_array[] = { -#define OPTION_ADV_BOOL(o, p, d, desc, i) {.option_name = o, .ptr = p, .init = {.bool_value = d}, .type = TYPE_BOOL, .description = desc, .internal = i}, -#define OPTION_ADV_SIGNED(o, p, d, minVal, maxVal, desc, i) {.option_name = o, .ptr = p, .init = {.signed_value = d}, .min = {.signed_value = minVal}, .max = {.signed_value = maxVal}, .type = TYPE_SIGNED, .description = desc, .internal = i}, -#define OPTION_ADV_UNSIGNED(o, p, d, minVal, maxVal, desc, i) {.option_name = o, .ptr = p, .init = {.unsigned_value = d}, .min = {.unsigned_value = minVal}, .max = {.unsigned_value = maxVal}, .type = TYPE_UNSIGNED, .description = desc, .internal = i}, -#define OPTION_ADV_FLOAT(o, p, d, minVal, maxVal, desc, i) {.option_name = o, .ptr = p, .init = {.float_value = d}, .min = {.float_value = minVal}, .max = {.float_value = maxVal}, .type = TYPE_FLOAT, .description = desc, .internal = i}, -#define OPTION_ADV_STRING(o, p, d, desc, i) {.option_name = o, .ptr = p, .init = {.string_value = d}, .type = TYPE_STRING, .description = desc, .internal = i}, - -#define OPTION_BOOL(o, p, d, desc) OPTION_ADV_BOOL(o, p, d, desc, false) -#define OPTION_SIGNED(o, p, d, min, max, desc) OPTION_ADV_SIGNED(o, p, d, min, max, desc, false) -#define OPTION_UNSIGNED(o, p, d, min, max, desc) OPTION_ADV_UNSIGNED(o, p, d, min, max, desc, false) -#define OPTION_FLOAT(o, p, d, min, max, desc) OPTION_ADV_FLOAT(o, p, d, min, max, desc, false) -#define OPTION_STRING(o, p, d, desc) OPTION_ADV_STRING(o, p, d, desc, false) - -#define OPTION_INTERNAL_BOOL(o, p, d, desc) OPTION_ADV_BOOL(o, p, d, desc, true) -#define OPTION_INTERNAL_SIGNED(o, p, d, min, max, desc) OPTION_ADV_SIGNED(o, p, d, min, max, desc, true) -#define OPTION_INTERNAL_UNSIGNED(o, p, d, min, max, desc) OPTION_ADV_UNSIGNED(o, p, d, min, max, desc, true) -#define OPTION_INTERNAL_FLOAT(o, p, d, min, max, desc) OPTION_ADV_FLOAT(o, p, d, min, max, desc, true) -#define OPTION_INTERNAL_STRING(o, p, d, desc) OPTION_ADV_STRING(o, p, d, desc, true) +#define OPTION_ADV_BOOL(o, p, d, desc, i, ov) {.option_name = o, .ptr = p, .init = {.bool_value = d}, .type = TYPE_BOOL, .description = desc, .internal = i, .overlayed = ov}, +#define OPTION_ADV_SIGNED(o, p, d, minVal, maxVal, desc, i, ov) {.option_name = o, .ptr = p, .init = {.signed_value = d}, .min = {.signed_value = minVal}, .max = {.signed_value = maxVal}, .type = TYPE_SIGNED, .description = desc, .internal = i, .overlayed = ov}, +#define OPTION_ADV_UNSIGNED(o, p, d, minVal, maxVal, desc, i, ov) {.option_name = o, .ptr = p, .init = {.unsigned_value = d}, .min = {.unsigned_value = minVal}, .max = {.unsigned_value = maxVal}, .type = TYPE_UNSIGNED, .description = desc, .internal = i, .overlayed = ov}, +#define OPTION_ADV_FLOAT(o, p, d, minVal, maxVal, desc, i, ov) {.option_name = o, .ptr = p, .init = {.float_value = d}, .min = {.float_value = minVal}, .max = {.float_value = maxVal}, .type = TYPE_FLOAT, .description = desc, .internal = i, .overlayed = ov}, +#define OPTION_ADV_STRING(o, p, d, desc, i, ov) {.option_name = o, .ptr = p, .init = {.string_value = d}, .type = TYPE_STRING, .description = desc, .internal = i, .overlayed = ov}, + +#define OPTION_BOOL(o, p, d, desc) OPTION_ADV_BOOL(o, p, d, desc, false, false) +#define OPTION_SIGNED(o, p, d, min, max, desc) OPTION_ADV_SIGNED(o, p, d, min, max, desc, false, false) +#define OPTION_UNSIGNED(o, p, d, min, max, desc) OPTION_ADV_UNSIGNED(o, p, d, min, max, desc, false, false) +#define OPTION_FLOAT(o, p, d, min, max, desc) OPTION_ADV_FLOAT(o, p, d, min, max, desc, false, false) +#define OPTION_STRING(o, p, d, desc) OPTION_ADV_STRING(o, p, d, desc, false, false) + +#define OPTION_INTERNAL_BOOL(o, p, d, desc) OPTION_ADV_BOOL(o, p, d, desc, true, false) +#define OPTION_INTERNAL_SIGNED(o, p, d, min, max, desc) OPTION_ADV_SIGNED(o, p, d, min, max, desc, true, false) +#define OPTION_INTERNAL_UNSIGNED(o, p, d, min, max, desc) OPTION_ADV_UNSIGNED(o, p, d, min, max, desc, true, false) +#define OPTION_INTERNAL_FLOAT(o, p, d, min, max, desc) OPTION_ADV_FLOAT(o, p, d, min, max, desc, true, false) +#define OPTION_INTERNAL_STRING(o, p, d, desc) OPTION_ADV_STRING(o, p, d, desc, true, false) #define OPTION_END() \ { \ diff --git a/src/settings.c b/src/settings.c index 4964d2d5..3c6b792b 100644 --- a/src/settings.c +++ b/src/settings.c @@ -281,6 +281,7 @@ void settings_deinit(uint8_t overlayId) while (option_map[pos].type != TYPE_END) { setting_item_t *opt = &option_map[pos]; + opt->overlayed = false; switch (opt->type) { @@ -509,6 +510,8 @@ void settings_load_ovl(bool overlay) { free(Settings_Overlay[i].internal.overlayName); Settings_Overlay[i].internal.overlayName = strdup(overlay_name); + setting_item_t *opt = settings_get_by_name_id("internal.overlayName", i); + opt->overlayed = true; // setting_item_t *opt = settings_get_by_name_id("internal.overlayName", i); //*((char **)opt->ptr) = strdup(*((char **)opt->ptr)); break; @@ -524,6 +527,10 @@ void settings_load_ovl(bool overlay) if (opt != NULL) { // Update the setting value based on the type + if (overlay) + { + opt->overlayed = true; + } switch (opt->type) { case TYPE_BOOL: @@ -555,6 +562,7 @@ void settings_load_ovl(bool overlay) break; default: + opt->overlayed = false; break; } } From 33e5b1200026e20a167f0912f8a7adee25e17035 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Mon, 7 Aug 2023 18:08:34 +0200 Subject: [PATCH 036/126] add notifications for last cloud activities --- include/server_helpers.h | 4 ++++ src/handler_cloud.c | 28 +++++++++++++++++++++++ src/handler_rtnl.c | 31 ++++---------------------- src/mqtt.c | 48 ++++++++++++++++++++++++++++++++++++++++ src/server_helpers.c | 30 +++++++++++++++++++++++++ 5 files changed, 114 insertions(+), 27 deletions(-) diff --git a/include/server_helpers.h b/include/server_helpers.h index 129ebf18..fecfd16c 100644 --- a/include/server_helpers.h +++ b/include/server_helpers.h @@ -5,9 +5,13 @@ #include #include "core/net.h" +#include "http/http_server.h" int urldecode(char *dest, const char *src); bool queryGet(const char *query, const char *key, char *data, size_t data_len); char_t *ipAddrToString(const IpAddr *ipAddr, char_t *str); char_t *ipv6AddrToString(const Ipv6Addr *ipAddr, char_t *str); char_t *ipv4AddrToString(Ipv4Addr ipAddr, char_t *str); +void time_format(time_t time, char_t *buffer); +void time_format_current(char_t *buffer); +const char *get_box_id(HttpConnection *connection); diff --git a/src/handler_cloud.c b/src/handler_cloud.c index 56bdc149..ed65172f 100644 --- a/src/handler_cloud.c +++ b/src/handler_cloud.c @@ -10,10 +10,17 @@ #include "handler_cloud.h" #include "http/http_client.h" +#include "mqtt.h" +#include "server_helpers.h" + error_t handleCloudTime(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx) { TRACE_INFO(" >> respond with current time\r\n"); + char current_time[64]; + time_format_current(current_time); + mqtt_sendBoxEvent(get_box_id(connection), "LastCloudTime", current_time); + char response[32]; if (!settings_get_bool("cloud.enabled") || !settings_get_bool("cloud.enableV1Time")) @@ -61,6 +68,10 @@ error_t handleCloudOTA(HttpConnection *connection, const char_t *uri, const char TRACE_INFO(" >> OTA-Request for %u with timestamp %" PRIuTIME " (%s)\r\n", fileId, timestamp, date_buffer); + char current_time[64]; + time_format_current(current_time); + mqtt_sendBoxEvent(get_box_id(connection), "LastCloudOtaTime", current_time); + if (settings_get_bool("cloud.enabled") && settings_get_bool("cloud.enableV1Ota")) { cbr_ctx_t ctx; @@ -174,6 +185,10 @@ error_t handleCloudClaim(HttpConnection *connection, const char_t *uri, const ch } TRACE_INFO(" >> client requested rUID %s, auth %s\r\n", ruid, msg); + char current_time[64]; + time_format_current(current_time); + mqtt_sendBoxEvent(get_box_id(connection), "LastCloudClaimTime", current_time); + tonie_info_t tonieInfo; getContentPathFromCharRUID(ruid, &tonieInfo.contentPath, client_ctx->settings); tonieInfo = getTonieInfo(tonieInfo.contentPath); @@ -232,6 +247,10 @@ error_t handleCloudContent(HttpConnection *connection, const char_t *uri, const TRACE_INFO(" >> client requested partial download\r\n"); } + char current_time[64]; + time_format_current(current_time); + mqtt_sendBoxEvent(get_box_id(connection), "LastCloudContentTime", current_time); + if (osStrlen(ruid) != 16) { TRACE_WARNING(" >> invalid URI\r\n"); @@ -437,6 +456,10 @@ error_t handleCloudFreshnessCheck(HttpConnection *connection, const char_t *uri, uint8_t data[BODY_BUFFER_SIZE]; size_t size; + char current_time[64]; + time_format_current(current_time); + mqtt_sendBoxEvent(get_box_id(connection), "LastCloudFreshnessCheckTime", current_time); + settings_t *settings = get_settings(); if (BODY_BUFFER_SIZE <= connection->request.byteCount) @@ -584,6 +607,11 @@ error_t handleCloudFreshnessCheck(HttpConnection *connection, const char_t *uri, error_t handleCloudReset(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx) { + + char current_time[64]; + time_format_current(current_time); + mqtt_sendBoxEvent(get_box_id(connection), "LastCloudResetTime", current_time); + // EMPTY POST REQUEST? if (settings_get_bool("cloud.enabled") && settings_get_bool("cloud.enableV1CloudReset")) { diff --git a/src/handler_rtnl.c b/src/handler_rtnl.c index ad16b0d3..03fca1a8 100644 --- a/src/handler_rtnl.c +++ b/src/handler_rtnl.c @@ -16,6 +16,7 @@ #include "mqtt.h" #include "fs_ext.h" #include "cloud_request.h" +#include "server_helpers.h" #include "proto/toniebox.pb.rtnl.pb-c.h" @@ -80,33 +81,14 @@ static void escapeString(const char_t *input, size_t size, char_t *output) output[j] = '\0'; } -static void time_format(time_t time, char_t *buffer) -{ - DateTime dateTime; - - convertUnixTimeToDate(time, &dateTime); - - osSprintf(buffer, "%04" PRIu16 "-%02" PRIu8 "-%02" PRIu8 "T%02" PRIu8 ":%02" PRIu8 ":%02" PRIu8 "Z", - dateTime.year, dateTime.month, dateTime.day, dateTime.hours, dateTime.minutes, - dateTime.seconds); -} - error_t handleRtnl(HttpConnection *connection, const char_t *uri, const char_t *queryString, client_ctx_t *client_ctx) { char_t *buffer = connection->buffer; size_t size = connection->response.contentLength; - const char *box_id = NULL; - - if (connection->tlsContext != NULL && osStrlen(connection->tlsContext->client_cert_issuer)) - { - box_id = connection->tlsContext->client_cert_subject; - } - time_t time = getCurrentUnixTime(); char current_time[64]; - time_format(time, current_time); - - mqtt_sendBoxEvent(box_id, "LastSeen", current_time); + time_format_current(current_time); + mqtt_sendBoxEvent(get_box_id(connection), "LastSeen", current_time); size_t pos = 0; do @@ -172,12 +154,7 @@ int32_t read_little_endian(const uint8_t *buf) void rtnlEvent(HttpConnection *connection, TonieRtnlRPC *rpc) { char_t buffer[4096]; - const char *box_id = NULL; - - if (connection->tlsContext != NULL && osStrlen(connection->tlsContext->client_cert_issuer)) - { - box_id = connection->tlsContext->client_cert_subject; - } + const char *box_id = get_box_id(connection); if (rpc->log2) { diff --git a/src/mqtt.c b/src/mqtt.c index c429217d..ee6e18b1 100644 --- a/src/mqtt.c +++ b/src/mqtt.c @@ -693,6 +693,54 @@ void mqtt_init_box(const char *box_id_in, t_ha_info *ha_box_instance) entity.dev_class = "timestamp"; ha_add(ha_box_instance, &entity); + memset(&entity, 0x00, sizeof(entity)); + entity.id = "LastCloudTime"; + entity.name = "LastCloudTime"; + entity.type = ha_sensor; + entity.stat_t = "%s/LastCloudTime"; + entity.dev_class = "timestamp"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "LastCloudOtaTime"; + entity.name = "LastCloudOtaTime"; + entity.type = ha_sensor; + entity.stat_t = "%s/LastCloudOtaTime"; + entity.dev_class = "timestamp"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "LastCloudClaimTime"; + entity.name = "LastCloudClaimTime"; + entity.type = ha_sensor; + entity.stat_t = "%s/LastCloudClaimTime"; + entity.dev_class = "timestamp"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "LastCloudContentTime"; + entity.name = "LastCloudContentTime"; + entity.type = ha_sensor; + entity.stat_t = "%s/LastCloudContentTime"; + entity.dev_class = "timestamp"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "LastCloudFreshnessCheckTime"; + entity.name = "LastCloudFreshnessCheckTime"; + entity.type = ha_sensor; + entity.stat_t = "%s/LastCloudFreshnessCheckTime"; + entity.dev_class = "timestamp"; + ha_add(ha_box_instance, &entity); + + memset(&entity, 0x00, sizeof(entity)); + entity.id = "LastCloudResetTime"; + entity.name = "LastCloudResetTime"; + entity.type = ha_sensor; + entity.stat_t = "%s/LastCloudResetTime"; + entity.dev_class = "timestamp"; + ha_add(ha_box_instance, &entity); + osFreeMem(box_id); } diff --git a/src/server_helpers.c b/src/server_helpers.c index 6c40a743..528ce602 100644 --- a/src/server_helpers.c +++ b/src/server_helpers.c @@ -193,3 +193,33 @@ char_t *ipAddrToString(const IpAddr *ipAddr, char_t *str) return str; } } + +void time_format(time_t time, char_t *buffer) +{ + DateTime dateTime; + + convertUnixTimeToDate(time, &dateTime); + + osSprintf(buffer, "%04" PRIu16 "-%02" PRIu8 "-%02" PRIu8 "T%02" PRIu8 ":%02" PRIu8 ":%02" PRIu8 "Z", + dateTime.year, dateTime.month, dateTime.day, dateTime.hours, dateTime.minutes, + dateTime.seconds); +} + +void time_format_current(char_t *buffer) +{ + time_t time = getCurrentUnixTime(); + + time_format(time, buffer); +} + +const char *get_box_id(HttpConnection *connection) +{ + const char *box_id = NULL; + + if (connection->tlsContext != NULL && osStrlen(connection->tlsContext->client_cert_issuer)) + { + box_id = connection->tlsContext->client_cert_subject; + } + + return box_id; +} From 919fb69fd49055c7748e00c6a8bd537db53f2135 Mon Sep 17 00:00:00 2001 From: g3gg0 Date: Mon, 7 Aug 2023 18:09:09 +0200 Subject: [PATCH 037/126] increase http throughput --- include/net_config.h | 1 + 1 file changed, 1 insertion(+) diff --git a/include/net_config.h b/include/net_config.h index cd63d3ef..8c439f75 100644 --- a/include/net_config.h +++ b/include/net_config.h @@ -186,5 +186,6 @@ typedef struct /* match original cloud settings */ #define HTTP_SERVER_IDLE_TIMEOUT (5 * 60000) #define HTTP_SERVER_TIMEOUT (1 * 60000) +#define HTTP_SERVER_BUFFER_SIZE 8192 #endif From 61c15711fe32f4a7afb5cec8ab8be8d24e8b10d8 Mon Sep 17 00:00:00 2001 From: SciLor Date: Mon, 7 Aug 2023 16:42:09 +0000 Subject: [PATCH 038/126] save overlay settings (changes tbd) --- src/settings.c | 101 ++++++++++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 38 deletions(-) diff --git a/src/settings.c b/src/settings.c index 3c6b792b..27e8c1aa 100644 --- a/src/settings.c +++ b/src/settings.c @@ -371,7 +371,8 @@ void settings_init(char *cwd) void settings_save() { - settings_save_ovl(NULL); + settings_save_ovl(false); + settings_save_ovl(true); } void settings_save_ovl(bool overlay) @@ -386,48 +387,74 @@ void settings_save_ovl(bool overlay) return; } - if (overlay) + for (size_t i = 0; i < MAX_OVERLAYS; i++) { - fsCloseFile(file); - return; - } - - Settings_Overlay[0].configVersion = CONFIG_VERSION; + int pos = 0; + char buffer[256]; // Buffer to hold the file content - int pos = 0; - char buffer[256]; // Buffer to hold the file content - while (get_option_map(NULL)[pos].type != TYPE_END) - { - if (!get_option_map(NULL)[pos].internal || !osStrcmp(get_option_map(NULL)[pos].option_name, "configVersion")) + if (i == 0 && overlay) { - setting_item_t *opt = &get_option_map(NULL)[pos]; + i++; + } + else if (i > 0 && !overlay) + { + break; + } + Settings_Overlay[i].configVersion = CONFIG_VERSION; - switch (opt->type) + setting_item_t *option_map = Option_Map_Overlay[i]; + while (option_map[pos].type != TYPE_END) + { + setting_item_t *opt = &option_map[pos]; + if (!opt->internal || !osStrcmp(opt->option_name, "configVersion") || (overlay && !osStrcmp(opt->option_name, "commonName"))) { - case TYPE_BOOL: - sprintf(buffer, "%s=%s\n", opt->option_name, *((bool *)opt->ptr) ? "true" : "false"); - break; - case TYPE_SIGNED: - sprintf(buffer, "%s=%d\n", opt->option_name, *((int32_t *)opt->ptr)); - break; - case TYPE_UNSIGNED: - case TYPE_HEX: - sprintf(buffer, "%s=%u\n", opt->option_name, *((uint32_t *)opt->ptr)); - break; - case TYPE_FLOAT: - sprintf(buffer, "%s=%f\n", opt->option_name, *((float *)opt->ptr)); - break; - case TYPE_STRING: - sprintf(buffer, "%s=%s\n", opt->option_name, *((char **)opt->ptr)); - break; - default: - buffer[0] = 0; - break; + char *overlayPrefix; + if (overlay) + { + if (!opt->overlayed) + { + pos++; + continue; // Only write overlay settings if they were overlayed + } + overlayPrefix = osAllocMem(8 + osStrlen(Settings_Overlay[i].internal.overlayName) + 1 + 1); // overlay.[NAME]. + osStrcpy(overlayPrefix, "overlay."); + osStrcat(overlayPrefix, Settings_Overlay[i].internal.overlayName); + osStrcat(overlayPrefix, "."); + } + else + { + overlayPrefix = osAllocMem(1); + osStrcpy(overlayPrefix, ""); + } + + switch (opt->type) + { + case TYPE_BOOL: + sprintf(buffer, "%s%s=%s\n", overlayPrefix, opt->option_name, *((bool *)opt->ptr) ? "true" : "false"); + break; + case TYPE_SIGNED: + sprintf(buffer, "%s%s=%d\n", overlayPrefix, opt->option_name, *((int32_t *)opt->ptr)); + break; + case TYPE_UNSIGNED: + case TYPE_HEX: + sprintf(buffer, "%s%s=%u\n", overlayPrefix, opt->option_name, *((uint32_t *)opt->ptr)); + break; + case TYPE_FLOAT: + sprintf(buffer, "%s%s=%f\n", overlayPrefix, opt->option_name, *((float *)opt->ptr)); + break; + case TYPE_STRING: + sprintf(buffer, "%s%s=%s\n", overlayPrefix, opt->option_name, *((char **)opt->ptr)); + break; + default: + buffer[0] = '\0'; + break; + } + if (osStrlen(buffer) > 0) + fsWriteFile(file, buffer, osStrlen(buffer)); + osFreeMem(overlayPrefix); } - if (osStrlen(buffer) > 0) - fsWriteFile(file, buffer, osStrlen(buffer)); + pos++; } - pos++; } fsCloseFile(file); Settings_Overlay[0].internal.config_changed = false; @@ -510,8 +537,6 @@ void settings_load_ovl(bool overlay) { free(Settings_Overlay[i].internal.overlayName); Settings_Overlay[i].internal.overlayName = strdup(overlay_name); - setting_item_t *opt = settings_get_by_name_id("internal.overlayName", i); - opt->overlayed = true; // setting_item_t *opt = settings_get_by_name_id("internal.overlayName", i); //*((char **)opt->ptr) = strdup(*((char **)opt->ptr)); break; From c609a08918920a85c796cb777ac464a23a7aa280 Mon Sep 17 00:00:00 2001 From: SciLor Date: Mon, 7 Aug 2023 17:00:55 +0000 Subject: [PATCH 039/126] Allow change of overlay settings and save it. --- include/settings.h | 5 +++++ src/settings.c | 29 ++++++++++++++++++++++++----- 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/include/settings.h b/include/settings.h index 18bcd3bb..cc7c76a7 100644 --- a/include/settings.h +++ b/include/settings.h @@ -327,6 +327,7 @@ setting_item_t *settings_get_by_name_id(const char *item, uint8_t settingsId); * @param value The new value for the setting item. */ bool settings_set_bool(const char *item, bool value); +bool settings_set_bool_ovl(const char *item, bool value, const char *overlay_name); /** * @brief Gets the value of a boolean setting item. @@ -353,6 +354,7 @@ int32_t settings_get_signed_ovl(const char *item, const char *overlay_name); * @param value The new value for the setting item. */ bool settings_set_signed(const char *item, int32_t value); +bool settings_set_signed_ovl(const char *item, int32_t value, const char *overlay_name); /** * @brief Gets the value of an unsigned integer setting item. @@ -370,6 +372,7 @@ uint32_t settings_get_unsigned_ovl(const char *item, const char *overlay_name); * @param value The new value for the setting item. */ bool settings_set_unsigned(const char *item, uint32_t value); +bool settings_set_unsigned_ovl(const char *item, uint32_t value, const char *overlay_name); /** * @brief Retrieves a setting item by its name. @@ -396,6 +399,7 @@ const char *settings_get_string_ovl(const char *item, const char *overlay_name); * @param value The new string value for the setting item. */ bool settings_set_string(const char *item, const char *value); +bool settings_set_string_ovl(const char *item, const char *value, const char *overlay_name); bool settings_set_string_id(const char *item, const char *value, uint8_t settingsId); /** @@ -414,5 +418,6 @@ float settings_get_float_ovl(const char *item, const char *overlay_name); * @param value The variable where the floating point value of the setting item will be stored. If the item does not exist or is not a float, the behavior is undefined. */ bool settings_set_float(const char *item, float value); +bool settings_set_float_ovl(const char *item, float value, const char *overlay_name); #endif \ No newline at end of file diff --git a/src/settings.c b/src/settings.c index 27e8c1aa..f970515c 100644 --- a/src/settings.c +++ b/src/settings.c @@ -719,13 +719,17 @@ bool settings_get_bool_ovl(const char *item, const char *overlay_name) } bool settings_set_bool(const char *item, bool value) +{ + return settings_set_bool_ovl(item, value, NULL); +} +bool settings_set_bool_ovl(const char *item, bool value, const char *overlay_name) { if (!item) { return false; } - setting_item_t *opt = settings_get_by_name(item); + setting_item_t *opt = settings_get_by_name_ovl(item, overlay_name); if (!opt || opt->type != TYPE_BOOL) { return false; @@ -762,13 +766,17 @@ int32_t settings_get_signed_ovl(const char *item, const char *overlay_name) } bool settings_set_signed(const char *item, int32_t value) +{ + return settings_set_signed_ovl(item, value, NULL); +} +bool settings_set_signed_ovl(const char *item, int32_t value, const char *overlay_name) { if (!item) { return false; } - setting_item_t *opt = settings_get_by_name(item); + setting_item_t *opt = settings_get_by_name_ovl(item, overlay_name); if (!opt || opt->type != TYPE_SIGNED) { return false; @@ -810,13 +818,17 @@ uint32_t settings_get_unsigned_ovl(const char *item, const char *overlay_name) } bool settings_set_unsigned(const char *item, uint32_t value) +{ + return settings_set_unsigned_ovl(item, value, NULL); +} +bool settings_set_unsigned_ovl(const char *item, uint32_t value, const char *overlay_name) { if (!item) { return false; } - setting_item_t *opt = settings_get_by_name(item); + setting_item_t *opt = settings_get_by_name_ovl(item, overlay_name); if (!opt || opt->type != TYPE_UNSIGNED) { return false; @@ -858,13 +870,17 @@ float settings_get_float_ovl(const char *item, const char *overlay_name) } bool settings_set_float(const char *item, float value) +{ + return settings_set_float_ovl(item, value, NULL); +} +bool settings_set_float_ovl(const char *item, float value, const char *overlay_name) { if (!item) { return false; } - setting_item_t *opt = settings_get_by_name(item); + setting_item_t *opt = settings_get_by_name_ovl(item, overlay_name); if (!opt || opt->type != TYPE_FLOAT) { return false; @@ -910,7 +926,10 @@ bool settings_set_string(const char *item, const char *value) { return settings_set_string_id(item, value, 0); } - +bool settings_set_string_ovl(const char *item, const char *value, const char *overlay_name) +{ + return settings_set_string_id(item, value, get_overlay_id(overlay_name)); +} bool settings_set_string_id(const char *item, const char *value, uint8_t settingsId) { if (!item || !value) From 4eef26137361fa522880d672709490be8df40531 Mon Sep 17 00:00:00 2001 From: SciLor Date: Mon, 7 Aug 2023 17:20:31 +0000 Subject: [PATCH 040/126] init overlayed during settings reload --- src/settings.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/settings.c b/src/settings.c index f970515c..47b06e73 100644 --- a/src/settings.c +++ b/src/settings.c @@ -493,6 +493,21 @@ void settings_load_ovl(bool overlay) return; } + if (overlay) + { + for (size_t i = 1; i < MAX_OVERLAYS; i++) + { + int pos = 0; + setting_item_t *option_map = Option_Map_Overlay[i]; + while (option_map[pos].type != TYPE_END) + { + setting_item_t *opt = &option_map[pos]; + opt->overlayed = false; + pos++; + } + } + } + // Buffer to hold the file content char buffer[256]; size_t from_read; From 164f663b701225e40dac3e086df99c5528dcdb86 Mon Sep 17 00:00:00 2001 From: SciLor Date: Mon, 7 Aug 2023 17:30:28 +0000 Subject: [PATCH 041/126] update web --- contrib/data/www/web/asset-manifest.json | 6 +++--- contrib/data/www/web/index.html | 2 +- contrib/data/www/web/static/js/main.01d9f9c0.js | 3 --- contrib/data/www/web/static/js/main.01d9f9c0.js.map | 1 - contrib/data/www/web/static/js/main.3c1d6764.js | 3 +++ ...01d9f9c0.js.LICENSE.txt => main.3c1d6764.js.LICENSE.txt} | 0 contrib/data/www/web/static/js/main.3c1d6764.js.map | 1 + contrib/data/www/web/translations/de.json | 4 ++++ contrib/data/www/web/translations/en.json | 4 ++++ teddycloud_web | 2 +- 10 files changed, 17 insertions(+), 9 deletions(-) delete mode 100644 contrib/data/www/web/static/js/main.01d9f9c0.js delete mode 100644 contrib/data/www/web/static/js/main.01d9f9c0.js.map create mode 100644 contrib/data/www/web/static/js/main.3c1d6764.js rename contrib/data/www/web/static/js/{main.01d9f9c0.js.LICENSE.txt => main.3c1d6764.js.LICENSE.txt} (100%) create mode 100644 contrib/data/www/web/static/js/main.3c1d6764.js.map diff --git a/contrib/data/www/web/asset-manifest.json b/contrib/data/www/web/asset-manifest.json index 72ed1f99..c3b68fee 100644 --- a/contrib/data/www/web/asset-manifest.json +++ b/contrib/data/www/web/asset-manifest.json @@ -1,15 +1,15 @@ { "files": { "main.css": "/web/static/css/main.e6c13ad2.css", - "main.js": "/web/static/js/main.01d9f9c0.js", + "main.js": "/web/static/js/main.3c1d6764.js", "static/media/logo.png": "/web/static/media/logo.2f09160138082fd1c6f8.png", "index.html": "/web/index.html", "static/media/getFetch.cjs": "/web/static/media/getFetch.40f37ddea2378391108f.cjs", "main.e6c13ad2.css.map": "/web/static/css/main.e6c13ad2.css.map", - "main.01d9f9c0.js.map": "/web/static/js/main.01d9f9c0.js.map" + "main.3c1d6764.js.map": "/web/static/js/main.3c1d6764.js.map" }, "entrypoints": [ "static/css/main.e6c13ad2.css", - "static/js/main.01d9f9c0.js" + "static/js/main.3c1d6764.js" ] } \ No newline at end of file diff --git a/contrib/data/www/web/index.html b/contrib/data/www/web/index.html index 6dfe7715..13230602 100644 --- a/contrib/data/www/web/index.html +++ b/contrib/data/www/web/index.html @@ -1 +1 @@ -TeddyCloud
\ No newline at end of file +TeddyCloud
\ No newline at end of file diff --git a/contrib/data/www/web/static/js/main.01d9f9c0.js b/contrib/data/www/web/static/js/main.01d9f9c0.js deleted file mode 100644 index 9fac32fe..00000000 --- a/contrib/data/www/web/static/js/main.01d9f9c0.js +++ /dev/null @@ -1,3 +0,0 @@ -/*! For license information please see main.01d9f9c0.js.LICENSE.txt */ -!function(){var e={694:function(e,t){var n;!function(){"use strict";var r={}.hasOwnProperty;function o(){for(var e=[],t=0;t=t?e:""+Array(t+1-r.length).join(n)+e},b={s:m,z:function(e){var t=-e.utcOffset(),n=Math.abs(t),r=Math.floor(n/60),o=n%60;return(t<=0?"+":"-")+m(r,2,"0")+":"+m(o,2,"0")},m:function e(t,n){if(t.date()1)return e(i[0])}else{var c=t.name;w[c]=t,o=c}return!r&&o&&(y=o),o||!r&&y},C=function(e,t){if(x(e))return e.clone();var n="object"==typeof t?t:{};return n.date=e,n.args=arguments,new E(n)},k=b;k.l=S,k.i=x,k.w=function(e,t){return C(e,{locale:t.$L,utc:t.$u,x:t.$x,$offset:t.$offset})};var E=function(){function g(e){this.$L=S(e.locale,null,!0),this.parse(e)}var m=g.prototype;return m.parse=function(e){this.$d=function(e){var t=e.date,n=e.utc;if(null===t)return new Date(NaN);if(k.u(t))return new Date;if(t instanceof Date)return new Date(t);if("string"==typeof t&&!/Z$/i.test(t)){var r=t.match(h);if(r){var o=r[2]-1||0,a=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],o,r[3]||1,r[4]||0,r[5]||0,r[6]||0,a)):new Date(r[1],o,r[3]||1,r[4]||0,r[5]||0,r[6]||0,a)}}return new Date(t)}(e),this.$x=e.x||{},this.init()},m.init=function(){var e=this.$d;this.$y=e.getFullYear(),this.$M=e.getMonth(),this.$D=e.getDate(),this.$W=e.getDay(),this.$H=e.getHours(),this.$m=e.getMinutes(),this.$s=e.getSeconds(),this.$ms=e.getMilliseconds()},m.$utils=function(){return k},m.isValid=function(){return!(this.$d.toString()===p)},m.isSame=function(e,t){var n=C(e);return this.startOf(t)<=n&&n<=this.endOf(t)},m.isAfter=function(e,t){return C(e)68?1900:2e3)},c=function(e){return function(t){this[e]=+t}},l=[/[+-]\d\d:?(\d\d)?|Z/,function(e){(this.zone||(this.zone={})).offset=function(e){if(!e)return 0;if("Z"===e)return 0;var t=e.match(/([+-]|\d\d)/g),n=60*t[1]+(+t[2]||0);return 0===n?0:"+"===t[0]?-n:n}(e)}],s=function(e){var t=a[e];return t&&(t.indexOf?t:t.s.concat(t.f))},u=function(e,t){var n,r=a.meridiem;if(r){for(var o=1;o<=24;o+=1)if(e.indexOf(r(o,0,t))>-1){n=o>12;break}}else n=e===(t?"pm":"PM");return n},d={A:[o,function(e){this.afternoon=u(e,!1)}],a:[o,function(e){this.afternoon=u(e,!0)}],S:[/\d/,function(e){this.milliseconds=100*+e}],SS:[n,function(e){this.milliseconds=10*+e}],SSS:[/\d{3}/,function(e){this.milliseconds=+e}],s:[r,c("seconds")],ss:[r,c("seconds")],m:[r,c("minutes")],mm:[r,c("minutes")],H:[r,c("hours")],h:[r,c("hours")],HH:[r,c("hours")],hh:[r,c("hours")],D:[r,c("day")],DD:[n,c("day")],Do:[o,function(e){var t=a.ordinal,n=e.match(/\d+/);if(this.day=n[0],t)for(var r=1;r<=31;r+=1)t(r).replace(/\[|\]/g,"")===e&&(this.day=r)}],M:[r,c("month")],MM:[n,c("month")],MMM:[o,function(e){var t=s("months"),n=(s("monthsShort")||t.map((function(e){return e.slice(0,3)}))).indexOf(e)+1;if(n<1)throw new Error;this.month=n%12||n}],MMMM:[o,function(e){var t=s("months").indexOf(e)+1;if(t<1)throw new Error;this.month=t%12||t}],Y:[/[+-]?\d+/,c("year")],YY:[n,function(e){this.year=i(e)}],YYYY:[/\d{4}/,c("year")],Z:l,ZZ:l};function f(n){var r,o;r=n,o=a&&a.formats;for(var i=(n=r.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,(function(t,n,r){var a=r&&r.toUpperCase();return n||o[r]||e[r]||o[a].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,(function(e,t,n){return t||n.slice(1)}))}))).match(t),c=i.length,l=0;l-1)return new Date(("X"===t?1e3:1)*e);var r=f(t)(e),o=r.year,a=r.month,i=r.day,c=r.hours,l=r.minutes,s=r.seconds,u=r.milliseconds,d=r.zone,p=new Date,h=i||(o||a?1:p.getDate()),v=o||p.getFullYear(),g=0;o&&!a||(g=a>0?a-1:p.getMonth());var m=c||0,b=l||0,y=s||0,w=u||0;return d?new Date(Date.UTC(v,g,h,m,b,y,w+60*d.offset*1e3)):n?new Date(Date.UTC(v,g,h,m,b,y,w)):new Date(v,g,h,m,b,y,w)}catch(e){return new Date("")}}(t,c,r),this.init(),d&&!0!==d&&(this.$L=this.locale(d).$L),u&&t!=this.format(c)&&(this.$d=new Date("")),a={}}else if(c instanceof Array)for(var p=c.length,h=1;h<=p;h+=1){i[1]=c[h-1];var v=n.apply(this,i);if(v.isValid()){this.$d=v.$d,this.$L=v.$L,this.init();break}h===p&&(this.$d=new Date(""))}else o.call(this,e)}}}()},36:function(e){e.exports=function(){"use strict";return function(e,t,n){var r=t.prototype,o=function(e){return e&&(e.indexOf?e:e.s)},a=function(e,t,n,r,a){var i=e.name?e:e.$locale(),c=o(i[t]),l=o(i[n]),s=c||l.map((function(e){return e.slice(0,r)}));if(!a)return s;var u=i.weekStart;return s.map((function(e,t){return s[(t+(u||0))%7]}))},i=function(){return n.Ls[n.locale()]},c=function(e,t){return e.formats[t]||function(e){return e.replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,(function(e,t,n){return t||n.slice(1)}))}(e.formats[t.toUpperCase()])},l=function(){var e=this;return{months:function(t){return t?t.format("MMMM"):a(e,"months")},monthsShort:function(t){return t?t.format("MMM"):a(e,"monthsShort","months",3)},firstDayOfWeek:function(){return e.$locale().weekStart||0},weekdays:function(t){return t?t.format("dddd"):a(e,"weekdays")},weekdaysMin:function(t){return t?t.format("dd"):a(e,"weekdaysMin","weekdays",2)},weekdaysShort:function(t){return t?t.format("ddd"):a(e,"weekdaysShort","weekdays",3)},longDateFormat:function(t){return c(e.$locale(),t)},meridiem:this.$locale().meridiem,ordinal:this.$locale().ordinal}};r.localeData=function(){return l.bind(this)()},n.localeData=function(){var e=i();return{firstDayOfWeek:function(){return e.weekStart||0},weekdays:function(){return n.weekdays()},weekdaysShort:function(){return n.weekdaysShort()},weekdaysMin:function(){return n.weekdaysMin()},months:function(){return n.months()},monthsShort:function(){return n.monthsShort()},longDateFormat:function(t){return c(e,t)},meridiem:e.meridiem,ordinal:e.ordinal}},n.months=function(){return a(i(),"months")},n.monthsShort=function(){return a(i(),"monthsShort","months",3)},n.weekdays=function(e){return a(i(),"weekdays",null,null,e)},n.weekdaysShort=function(e){return a(i(),"weekdaysShort","weekdays",3,e)},n.weekdaysMin=function(e){return a(i(),"weekdaysMin","weekdays",2,e)}}}()},216:function(e){e.exports=function(){"use strict";var e="week",t="year";return function(n,r,o){var a=r.prototype;a.week=function(n){if(void 0===n&&(n=null),null!==n)return this.add(7*(n-this.week()),"day");var r=this.$locale().yearStart||1;if(11===this.month()&&this.date()>25){var a=o(this).startOf(t).add(1,t).date(r),i=o(this).endOf(e);if(a.isBefore(i))return 1}var c=o(this).startOf(t).date(r).startOf(e).subtract(1,"millisecond"),l=this.diff(c,e,!0);return l<0?o(this).startOf("week").week():Math.ceil(l)},a.weeks=function(e){return void 0===e&&(e=null),this.week(e)}}}()},834:function(e){e.exports=function(){"use strict";return function(e,t){t.prototype.weekYear=function(){var e=this.month(),t=this.week(),n=this.year();return 1===t&&11===e?n+1:0===e&&t>=52?n-1:n}}}()},334:function(e){e.exports=function(){"use strict";return function(e,t){t.prototype.weekday=function(e){var t=this.$locale().weekStart||0,n=this.$W,r=(n