diff options
Diffstat (limited to 'fw/app')
| -rw-r--r-- | fw/app/.gitignore | 1 | ||||
| -rw-r--r-- | fw/app/CMakeLists.txt | 41 | ||||
| -rw-r--r-- | fw/app/boards/nucleo_f767zi.conf | 5 | ||||
| -rw-r--r-- | fw/app/prj.conf | 45 | ||||
| -rw-r--r-- | fw/app/sections-rom.ld | 3 | ||||
| -rw-r--r-- | fw/app/src/heart.c | 52 | ||||
| -rw-r--r-- | fw/app/src/heart.h | 20 | ||||
| -rw-r--r-- | fw/app/src/http.c | 112 | ||||
| -rw-r--r-- | fw/app/src/index.html | 21 | ||||
| -rw-r--r-- | fw/app/src/iot-contact.js | 48 | ||||
| -rw-r--r-- | fw/app/src/network.c | 75 | ||||
| -rw-r--r-- | fw/app/src/syslog.c | 75 | ||||
| -rw-r--r-- | fw/app/src/ws.c | 251 | ||||
| -rw-r--r-- | fw/app/src/ws.h | 17 | 
14 files changed, 766 insertions, 0 deletions
diff --git a/fw/app/.gitignore b/fw/app/.gitignore new file mode 100644 index 0000000..b6b51e9 --- /dev/null +++ b/fw/app/.gitignore @@ -0,0 +1 @@ +my.conf diff --git a/fw/app/CMakeLists.txt b/fw/app/CMakeLists.txt new file mode 100644 index 0000000..ce10104 --- /dev/null +++ b/fw/app/CMakeLists.txt @@ -0,0 +1,41 @@ +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v. 2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at https://mozilla.org/MPL/2.0/. + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr +	REQUIRED +	HINTS +		"${ZEPHYR_BASE}" +) + +project(iot-contact-fw) + +target_sources(app +	PRIVATE +		"${CMAKE_CURRENT_SOURCE_DIR}/src/syslog.c" +		"${CMAKE_CURRENT_SOURCE_DIR}/src/network.c" +		"${CMAKE_CURRENT_SOURCE_DIR}/src/http.c" +		"${CMAKE_CURRENT_SOURCE_DIR}/src/ws.c" +		"${CMAKE_CURRENT_SOURCE_DIR}/src/heart.c" +) + +zephyr_linker_sources(SECTIONS sections-rom.ld) +zephyr_linker_section(NAME http_resource_desc_http_service +                      KVMA RAM_REGION GROUP RODATA_REGION +                      SUBALIGN ${CONFIG_LINKER_ITERABLE_SUBALIGN}) + +generate_inc_file_for_target( +	app +	src/index.html +	${ZEPHYR_BINARY_DIR}/include/generated/index.html.gz.inc +	--gzip +) + +generate_inc_file_for_target( +	app +	src/iot-contact.js +	${ZEPHYR_BINARY_DIR}/include/generated/iot-contact.js.gz.inc +	--gzip +) diff --git a/fw/app/boards/nucleo_f767zi.conf b/fw/app/boards/nucleo_f767zi.conf new file mode 100644 index 0000000..32e6d4a --- /dev/null +++ b/fw/app/boards/nucleo_f767zi.conf @@ -0,0 +1,5 @@ +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v. 2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at https://mozilla.org/MPL/2.0/. + +CONFIG_BOOTLOADER_MCUBOOT=y diff --git a/fw/app/prj.conf b/fw/app/prj.conf new file mode 100644 index 0000000..11dc04f --- /dev/null +++ b/fw/app/prj.conf @@ -0,0 +1,45 @@ +# This Source Code Form is subject to the terms of the Mozilla Public License, +# v. 2.0. If a copy of the MPL was not distributed with this file, You can +# obtain one at https://mozilla.org/MPL/2.0/. + +CONFIG_SHELL=y +CONFIG_SHELL_PROMPT_UART="[iot-contact] " + +CONFIG_NETWORKING=y +CONFIG_NET_SHELL=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_CONNECTION_MANAGER=y +CONFIG_NET_L2_ETHERNET_MGMT=y +CONFIG_NET_TCP=y + +CONFIG_NET_HOSTNAME_ENABLE=y +CONFIG_NET_HOSTNAME_DYNAMIC=y + +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_MY_IPV6_ADDR="fdb3:c9f2:efda:1::1" +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=4 + +CONFIG_LOG=y +CONFIG_LOG_BACKEND_NET=y +CONFIG_LOG_BACKEND_NET_SERVER="[2001:db8::2]:514" +CONFIG_LOG_BACKEND_NET_AUTOSTART=n +CONFIG_LOG_MODE_DEFERRED=y + +CONFIG_POSIX_C_LANG_SUPPORT_R=y + +CONFIG_HTTP_PARSER=y +CONFIG_HTTP_PARSER_URL=y + +CONFIG_HTTP_SERVER=y +CONFIG_HTTP_SERVER_WEBSOCKET=y + +CONFIG_FILE_SYSTEM=y + +CONFIG_EVENTFD=y + +CONFIG_ZVFS_OPEN_MAX=32 +CONFIG_ZVFS_POLL_MAX=32 + +CONFIG_ZBUS=y +CONFIG_ZBUS_MSG_SUBSCRIBER=y +CONFIG_HEAP_MEM_POOL_SIZE=2048 diff --git a/fw/app/sections-rom.ld b/fw/app/sections-rom.ld new file mode 100644 index 0000000..c2f7bc4 --- /dev/null +++ b/fw/app/sections-rom.ld @@ -0,0 +1,3 @@ +#include <zephyr/linker/iterable_sections.h> + +ITERABLE_SECTION_ROM(http_resource_desc_http_service, Z_LINK_ITERABLE_SUBALIGN) diff --git a/fw/app/src/heart.c b/fw/app/src/heart.c new file mode 100644 index 0000000..6bcced6 --- /dev/null +++ b/fw/app/src/heart.c @@ -0,0 +1,52 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + + +#include <stdbool.h> + +#include <zephyr/kernel.h> +#include <zephyr/logging/log.h> + +#include "heart.h" + + +LOG_MODULE_REGISTER(heart); + + +#define HEART_PERIOD          K_MSEC(1000) +#define HEART_STACK_SIZE               500 +#define HEART_PRIO       K_PRIO_PREEMPT(8) + + +ZBUS_CHAN_DEFINE( +	heartbeat_channel, +	struct heartbeat, +	NULL, +	NULL, +	ZBUS_OBSERVERS_EMPTY, +	ZBUS_MSG_INIT(.ttl_ms = 0) +); + + +static void heart_thread_function(void *ptr1, void *ptr2, void *ptr3) { +	LOG_INF("Starting to beat"); + +	struct heartbeat heartbeat = {.ttl_ms = 1100}; + +	while (true) { +		LOG_DBG("Heart beat"); +		int ret = zbus_chan_pub(&heartbeat_channel, &heartbeat, K_FOREVER); +		if (ret < 0) { +			LOG_ERR("Could not publish heartbeat"); +		} +		k_sleep(HEART_PERIOD); +	} +} + + +K_THREAD_DEFINE(heart_thread, HEART_STACK_SIZE, +                heart_thread_function, NULL, NULL, NULL, +                HEART_PRIO, 0, 0); diff --git a/fw/app/src/heart.h b/fw/app/src/heart.h new file mode 100644 index 0000000..cd32c15 --- /dev/null +++ b/fw/app/src/heart.h @@ -0,0 +1,20 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef SRC_HEART_H +#define SRC_HEART_H + + +#include <zephyr/zbus/zbus.h> + + +struct heartbeat { +	uint32_t ttl_ms; +}; + +ZBUS_CHAN_DECLARE(heartbeat_channel); + +#endif // !SRC_HEART_H diff --git a/fw/app/src/http.c b/fw/app/src/http.c new file mode 100644 index 0000000..e206f86 --- /dev/null +++ b/fw/app/src/http.c @@ -0,0 +1,112 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + + +#include <stdint.h> + +#include <zephyr/init.h> +#include <zephyr/logging/log.h> +#include <zephyr/net/http/server.h> +#include <zephyr/net/http/service.h> +#include <zephyr/net/http/status.h> + +#include "ws.h" + + +LOG_MODULE_REGISTER(http); + + +static const uint8_t index_html_gz[] = { +    #include "index.html.gz.inc" +}; + +struct http_resource_detail_static index_resource_detail = { +	.common = { +			.type = HTTP_RESOURCE_TYPE_STATIC, +			.bitmask_of_supported_http_methods = BIT(HTTP_GET), +			.content_encoding = "gzip", +			.content_type = "text/html", +		}, +	.static_data = index_html_gz, +	.static_data_len = sizeof(index_html_gz), +}; + +static const uint8_t js_html_gz[] = { +    #include "iot-contact.js.gz.inc" +}; + +struct http_resource_detail_static js_resource_detail = { +	.common = { +			.type = HTTP_RESOURCE_TYPE_STATIC, +			.bitmask_of_supported_http_methods = BIT(HTTP_GET), +			.content_encoding = "gzip", +			.content_type = "text/javascript", +		}, +	.static_data = js_html_gz, +	.static_data_len = sizeof(js_html_gz), +}; + +static int favicon_handler( +	struct http_client_ctx *client, +	enum http_data_status status, +	const struct http_request_ctx *request_ctx, +	struct http_response_ctx *response_ctx, +	void *user_data +) { +	LOG_DBG("Handling favicon request"); + +	response_ctx->body = NULL; +	response_ctx->body_len = 0; +	response_ctx->final_chunk = true; +	response_ctx->status = HTTP_204_NO_CONTENT; + +	LOG_DBG("Favicon request handled"); +	return 0; +} + +static struct http_resource_detail_dynamic favicon_resource_detail = { +	.common = { +			.type = HTTP_RESOURCE_TYPE_DYNAMIC, +			.bitmask_of_supported_http_methods = BIT(HTTP_GET), +		}, +	.cb = favicon_handler, +	.user_data = NULL, +}; + +static uint8_t websocket_read_buffer[1024]; + +struct http_resource_detail_websocket websocket_resource_detail = { +	.common = { +			.type = HTTP_RESOURCE_TYPE_WEBSOCKET, +			.bitmask_of_supported_http_methods = BIT(HTTP_GET), +		}, +	.cb = ws_upgrade_handler, +	.data_buffer = websocket_read_buffer, +	.data_buffer_len = sizeof(websocket_read_buffer), +}; + +static uint16_t http_port = 80; + +HTTP_SERVICE_DEFINE(http_service, NULL, &http_port, 1, 10, NULL, NULL); + +HTTP_RESOURCE_DEFINE(index_resource, http_service, "/", &index_resource_detail); +HTTP_RESOURCE_DEFINE(websocket_resource, http_service, "/", &websocket_resource_detail); +HTTP_RESOURCE_DEFINE(favicon_resource, http_service, "/favicon.ico", &favicon_resource_detail); +HTTP_RESOURCE_DEFINE(js_resource, http_service, "/iot-contact.js", &js_resource_detail); + +int init_http_server(void) { +	LOG_DBG("Starting HTTP server"); + +	int ret = http_server_start(); +	if (ret < 0) { +		LOG_ERR("Failed to start HTTP server (%d)", ret); +		return ret; +	} + +	LOG_INF("HTTP server was started"); +	return 0; +} +SYS_INIT(init_http_server, APPLICATION, 99); diff --git a/fw/app/src/index.html b/fw/app/src/index.html new file mode 100644 index 0000000..5817818 --- /dev/null +++ b/fw/app/src/index.html @@ -0,0 +1,21 @@ +<!DOCTYPE html> + +<!-- This Source Code Form is subject to the terms of the Mozilla Public +   - License, v. 2.0. If a copy of the MPL was not distributed with this +   - file, You can obtain one at https://mozilla.org/MPL/2.0/. --> + +<html lang="en"> +	<head> +		<meta charset="UTF-8"> +		<meta name="viewport" content="width=device-width, initial-scale=1"> +		<title>iot-contact</title> +		<script type="text/javascript" src="/iot-contact.js"></script> +	</head> +	<body> +		<h4>iot-contact</h4> +		<p> +			<label for="heartbeat">Heartbeat</label> +			<meter id="heartbeat" min="0" max="1" value="0"></meter> +		</p> +	</body> +</html> diff --git a/fw/app/src/iot-contact.js b/fw/app/src/iot-contact.js new file mode 100644 index 0000000..e8e966f --- /dev/null +++ b/fw/app/src/iot-contact.js @@ -0,0 +1,48 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +function blinkHeartbeat() { +	const heartbeat = document.getElementById("heartbeat"); +	heartbeat.value = 1; +	setTimeout(() => heartbeat.value = 0, 300); +} + +window.addEventListener("DOMContentLoaded", (ev) => { +	const ws = new WebSocket("/"); +	console.log("WebSocket object created"); + +	ws.onopen = (event) => { +		console.log("WebSocket connection opened"); +	}; + +	ws.onmessage = (event) => { +		try { +			const data = JSON.parse(event.data); +			console.log("Message received: ", data); +			if (data.type === 0 && data.ttl_ms) { +				blinkHeartbeat(); +			} +		} catch (error) { +			console.error("Invalid message received:", event.data); +		} +	}; + +	ws.onclose = (event) => { +		console.log("WebSocket connection closed"); +	}; + +	ws.onerror = (event) => { +		console.log("WebSocket error: ", event); +	}; + +	setInterval(() => { +		if (ws.readyState === WebSocket.OPEN) { +			const data = "{\"type\":0,\"ttl_ms\":1100}\n"; +			ws.send(data); +			console.log("Heartbeat sent: '", data, "'"); +		} +	}, 1000); +}) diff --git a/fw/app/src/network.c b/fw/app/src/network.c new file mode 100644 index 0000000..6e6eb17 --- /dev/null +++ b/fw/app/src/network.c @@ -0,0 +1,75 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + + +#include <stdint.h> +#include <string.h> + +#include <zephyr/init.h> +#include <zephyr/logging/log.h> +#include <zephyr/net/hostname.h> +#include <zephyr/net/net_if.h> +#include <zephyr/net/net_linkaddr.h> +#include <zephyr/net/net_mgmt.h> +#include <zephyr/net/ethernet.h> +#include <zephyr/net/ethernet_mgmt.h> +#include <zephyr/sys/util.h> + + +LOG_MODULE_REGISTER(network); + + +#define HOSTNAME "iot-contact" + + +/* will be read from an EEPROM chip in the future */ +static const uint8_t mac_address[NET_ETH_ADDR_LEN] = {0x00, 0x00, 0x5e, 0x00, 0x53, 0x01}; + +int init_mac_address(void) +{ +	LOG_DBG("Setting custom MAC address"); + +	struct net_if *interface = net_if_get_default(); + +	int ret = net_if_down(interface); +	if (ret < 0) { +		LOG_ERR("Failed to set interface down to set MAC address (%d)", ret); +		return ret; +	} + +	struct ethernet_req_params params = {0}; +	memcpy(params.mac_address.addr, mac_address, 6); +	ret = net_mgmt(NET_REQUEST_ETHERNET_SET_MAC_ADDRESS, interface, ¶ms, sizeof(params)); +	if (ret < 0) { +		LOG_ERR("Failed to set MAC address (%d)", ret); +		return ret; +	} + +	ret = net_if_up(interface); +	if (ret < 0) { +		LOG_ERR("Failed to set interface up after setting MAC address (%d)", ret); +		return ret; +	} + +	LOG_INF("Successfully set MAC address"); +	return 0; +} +SYS_INIT(init_mac_address, APPLICATION, 0); + +int init_hostname(void) +{ +	LOG_DBG("Setting hostname"); + +	int ret = net_hostname_set(HOSTNAME, sizeof(HOSTNAME)); +	if (ret < 0) { +		LOG_ERR("Failed to set hostname to '%s' (%d)", HOSTNAME, ret); +		return ret; +	} + +	LOG_INF("Successfully set hostname to '%s'", HOSTNAME); +	return 0; +} +SYS_INIT(init_hostname, APPLICATION, 1); diff --git a/fw/app/src/syslog.c b/fw/app/src/syslog.c new file mode 100644 index 0000000..b1a1077 --- /dev/null +++ b/fw/app/src/syslog.c @@ -0,0 +1,75 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +#include <stdbool.h> + +#include <zephyr/init.h> +#include <zephyr/kernel.h> +#include <zephyr/logging/log.h> +#include <zephyr/logging/log_backend.h> +#include <zephyr/logging/log_backend_net.h> +#include <zephyr/logging/log_ctrl.h> +#include <zephyr/logging/log_core.h> +#include <zephyr/net/conn_mgr_connectivity.h> + +LOG_MODULE_REGISTER(syslog); + +#define L4_EVENT_MASK (NET_EVENT_L4_CONNECTED | NET_EVENT_L4_DISCONNECTED) + +struct net_mgmt_event_callback l4_cb; +static K_SEM_DEFINE(network_connected, 0, 1); + +void l4_event_handler( +	struct net_mgmt_event_callback *cb, +	uint32_t event, +	struct net_if *iface) +{ +	LOG_DBG("Executing L4 event handler"); + +	switch (event) { +	case NET_EVENT_L4_CONNECTED: +		k_sem_give(&network_connected); +		LOG_INF("Network connected"); +		break; +	case NET_EVENT_L4_DISCONNECTED: +		LOG_INF("Network disconnected"); +		break; +	default: +		break; +	} +} + +int init_network_monitoring(void) +{ +	net_mgmt_init_event_callback(&l4_cb, l4_event_handler, L4_EVENT_MASK); +	net_mgmt_add_event_callback(&l4_cb); + +	return 0; +} +SYS_INIT(init_network_monitoring, APPLICATION, 0); + +int init_syslog(void) +{ +	LOG_DBG("Initializing syslog logging backend"); + +	LOG_DBG("Waiting for network ..."); +	k_sem_take(&network_connected, K_FOREVER); + +	LOG_DBG("Enabling syslog backend"); +	const struct log_backend *backend = log_backend_net_get(); +	if (log_backend_is_active(backend) == false) { +		/* flush log messages to ensure first syslog message is reproducible */ +		while (log_process()); +		log_backend_init(backend); +		log_backend_enable(backend, backend->cb->ctx, CONFIG_LOG_MAX_LEVEL); +		LOG_INF("Syslog backend enabled"); +	} else { +		LOG_INF("Syslog backend was already enabled"); +	} + +	return 0; +} +SYS_INIT(init_syslog, APPLICATION, 50); diff --git a/fw/app/src/ws.c b/fw/app/src/ws.c new file mode 100644 index 0000000..6f68538 --- /dev/null +++ b/fw/app/src/ws.c @@ -0,0 +1,251 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + + +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> + +#include <zephyr/kernel.h> +#include <zephyr/logging/log.h> +#include <zephyr/net/http/server.h> +#include <zephyr/net/websocket.h> +#include <zephyr/sys_clock.h> +#include <zephyr/zbus/zbus.h> + +#include "heart.h" + + +LOG_MODULE_REGISTER(ws); + + +#define WS_NUM_HANDLERS                           1 +#define WS_HANDLER_STACK_SIZE                  2048 +#define WS_HANDLER_PRIO           K_PRIO_PREEMPT(8) +#define WS_CONNECTION_BACKLOG_LEN                 5 +#define WS_TX_BUFFER_LEN                        150 +#define WS_CLIENT_HEARTBEAT_GRACE_TIME_MS     10000 + +K_MSGQ_DEFINE(ws_connection_backlog, sizeof(int), WS_CONNECTION_BACKLOG_LEN, 1); +ZBUS_MSG_SUBSCRIBER_DEFINE(ws_tx_messages); +ZBUS_CHAN_ADD_OBS(heartbeat_channel, ws_tx_messages, 3); + +static void ws_tx_thread_function(void *ptr1, void *ptr2, void *ptr3) { +	int fd = -1; +	struct k_msgq *tx_fds = ptr1; +	uint8_t buffer[WS_TX_BUFFER_LEN]; +	const struct zbus_channel *chan; +	struct heartbeat heartbeat; + +	while (true) { +		int ret = zbus_obs_set_enable(&ws_tx_messages, false); +		if (ret < 0) { +			LOG_ERR("Could not disable zbus observer (%d)", ret); +			continue; +		} + +		ret = k_msgq_get(tx_fds, &fd, K_FOREVER); +		if (ret < 0) { +			LOG_ERR("Error getting new file descriptor (%d)", ret); +			continue; +		} +		LOG_INF("Handling TX of connection with file descriptor %d", fd); + +		ret = zbus_obs_set_enable(&ws_tx_messages, true); +		if (ret < 0) { +			LOG_ERR("Could not enable zbus observer (%d)", ret); +			continue; +		} + +		while (true) { +			ret = zbus_sub_wait_msg(&ws_tx_messages, &chan, &heartbeat, K_FOREVER); +			if (ret < 0) { +				LOG_ERR("Could not wait for zbus messages (%d)", ret); +				break; +			} +			if (chan != &heartbeat_channel) { +				LOG_ERR("Unsupported channel"); +				continue; +			} + +			ret = snprintf((char *)buffer, sizeof(buffer), +			               "{\"type\":0,\"ttl_ms\":%d}\n", heartbeat.ttl_ms); +			if (ret < 0) { +				LOG_ERR("Could not serialize heartbeat message"); +				continue; +			} +			size_t len = ret; + +			int ret = websocket_send_msg( +				fd, (const uint8_t *)buffer, len, +				WEBSOCKET_OPCODE_DATA_TEXT, false, true, 100 +			); +			if (ret < 0) { +				if (ret == -EBADF) { +					LOG_DBG("Connection closed, waiting for new one"); +				} else { +					LOG_ERR("Error on websocket_send_msg (%d)", ret); +				} +				break; +			} +		} +	} +} + +static int ws_recv_full_message(int fd, void *buf, const size_t len, +                                uint32_t *message_type, int64_t deadline) { +	uint64_t remaining = 0; +	size_t read = 0; +	int ret; + +	while (true) { +		int64_t timeout = MIN(50, deadline - k_uptime_get()); +		if (timeout <= 0) { +			ret = -ETIMEDOUT; +			LOG_ERR("No time left to read message from %d (%d)", fd, ret); +			return ret; +		} + +		if (read >= len) { +			ret = -ENOSPC; +			LOG_ERR("No buffer space left to store message from %d (%d)", fd, ret); +			return ret; +		} + +		ret = websocket_recv_msg(fd, (unsigned char *)buf+read, len-read, +		                         message_type, &remaining, timeout); +		if (ret < 0) { +			if (ret == -EAGAIN || ret == -EINTR) { +				LOG_DBG("Trying again to read full message"); +				continue; +			} else { +				LOG_ERR("Unhandled error on message reading (%d)", ret); +				return ret; +			} +		} + +		read += ret; + +		if (remaining <= 0) { +			return read; +		} +	} +} + +static int ws_handle_data_text(const uint8_t *buf, size_t len, int64_t *deadline) { +	LOG_DBG("Got text message"); + +	static const char heartbeat[] = "{\"type\":0,\"ttl_ms\":1100}\n"; + +	if (len + 1 == sizeof(heartbeat)) { +		if (memcmp((const void *)buf, heartbeat, len) == 0) { +			LOG_DBG("Received heartbeat from client"); +			*deadline = k_uptime_get() + WS_CLIENT_HEARTBEAT_GRACE_TIME_MS; +			return 0; +		} +	} + +	LOG_HEXDUMP_DBG(buf, len, "Could not handle message"); +	return -EIO; +} + +static int ws_rx_handle_connection(int fd) { +	LOG_INF("Handling RX of connection with file descriptor %d", fd); + +	uint8_t rx_buf[100]; +	uint32_t message_type = 0; +	int64_t rx_heartbeat_deadline = k_uptime_get() + WS_CLIENT_HEARTBEAT_GRACE_TIME_MS; + +	while (true) { +		memset(rx_buf, 0, sizeof(rx_buf)); + +		int ret = ws_recv_full_message(fd, rx_buf, sizeof(rx_buf), +		                               &message_type, rx_heartbeat_deadline); +		if (ret < 0) { +			LOG_ERR("Could not receive full message from %d (%d)", fd, ret); +			return ret; +		} +		size_t len = ret; +		LOG_DBG("Received message with opcode %d of length %d", message_type, len); + +		if (message_type == WEBSOCKET_OPCODE_DATA_TEXT || +		    message_type == 3) {  // FIXME for some reason opcode 1 parses to 3 +			ret = ws_handle_data_text(rx_buf, len, &rx_heartbeat_deadline); +			if (ret < 0) { +				LOG_ERR("Failed to handle text data (%d)", ret); +			} +		} else if (message_type == WEBSOCKET_OPCODE_CLOSE) { +			LOG_INF("Client closed connection"); +			return 0; +		} else { +				LOG_WRN("Received unhandled message opcode %d", message_type); +				LOG_HEXDUMP_WRN(rx_buf, len, "Message content:"); +		} + +		if (k_uptime_get() > rx_heartbeat_deadline) { +			LOG_INF("Client heartbeat timeout expired on %d - closing", fd); +			return -ETIMEDOUT; +		} +	} +} + +static void ws_rx_thread_function(void *ptr1, void *ptr2, void *ptr3) { +	struct k_msgq *tx_fds = ptr1; + +	while (true) { +		int fd; +		int ret = k_msgq_get(&ws_connection_backlog, &fd, K_FOREVER); +		if (ret < 0) { +			LOG_ERR("Error getting new file descriptor (%d)", ret); +			continue; +		} + +		ret = k_msgq_put(tx_fds, (const void *)&fd, K_FOREVER); +		if (ret < 0) { +			LOG_ERR("Could not pass file descriptor %d to TX thread (%d)", fd, ret); +			goto unregister; +		} + +		ret = ws_rx_handle_connection(fd); +		if (ret < 0) { +			LOG_ERR("Failed to handle connection %d (%d)", fd, ret); +			goto unregister; +		} + +unregister: +		ret = websocket_unregister(fd); +		if (ret < 0) { +			LOG_ERR("Failed to unregister connection %d (%d)", fd, ret); +		} +	} +} + +K_MSGQ_DEFINE(ws_tx_fd_1, sizeof(int), 1, 1); +K_THREAD_DEFINE(ws_rx_thread_1, WS_HANDLER_STACK_SIZE, +                ws_rx_thread_function, &ws_tx_fd_1, NULL, NULL, +                WS_HANDLER_PRIO, 0, 0); +K_THREAD_DEFINE(ws_tx_thread_1, WS_HANDLER_STACK_SIZE, +                ws_tx_thread_function, &ws_tx_fd_1, NULL, NULL, +                WS_HANDLER_PRIO, 0, 0); + +int ws_upgrade_handler( +	int ws_socket, +	struct http_request_ctx *request_ctx, +	void *user_data +) { +	LOG_DBG("Handling WebSocket upgrade for file descriptor %d", ws_socket); + +	int ret = k_msgq_put(&ws_connection_backlog, (const void *)&ws_socket, +	                     K_NO_WAIT); +	if (ret < 0) { +		LOG_ERR("Connection backlog full, dropping file descriptor %d", ws_socket); +		return -ENOENT; +	} + +	LOG_INF("Added file descriptor %d to connection backlog", ws_socket); +	return 0; +} diff --git a/fw/app/src/ws.h b/fw/app/src/ws.h new file mode 100644 index 0000000..0c13039 --- /dev/null +++ b/fw/app/src/ws.h @@ -0,0 +1,17 @@ +/* + * This Source Code Form is subject to the terms of the Mozilla Public License, + * v. 2.0. If a copy of the MPL was not distributed with this file, You can + * obtain one at https://mozilla.org/MPL/2.0/. + */ + +#ifndef SRC_WS_H +#define SRC_WS_H + + +#include <zephyr/net/http/server.h> + + +int ws_upgrade_handler(int ws_socket, struct http_request_ctx *request_ctx, +                       void *user_data); + +#endif // !SRC_WS_H  | 
