feat: add doxygen headers to all functions

This commit is contained in:
Eduard Iten 2025-07-19 09:10:05 +02:00
parent 85892deb2b
commit cbbd5f5fea
17 changed files with 405 additions and 960 deletions

9
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,9 @@
{
"C_Cpp.default.compileCommands": "${workspaceFolder}/build/compile_commands.json",
"files.associations": {
"log.h": "c",
"kernel.h": "c",
"dns_resolve.h": "c",
"net_if.h": "c"
}
}

11
.vscode/tasks.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "shell",
"label": "Build Zephyr app",
"command": "west build -b esp32c6_devkitc .",
"group": "build"
}
]
}

View File

@ -4,4 +4,5 @@ cmake_minimum_required(VERSION 3.20.0)
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(blinky)
target_sources(app PRIVATE src/main.c src/mqtt_client.c src/mqtt_client_shell.c)
target_sources(app PRIVATE src/main.c)
target_sources(app PRIVATE src/net.c)

43
Kconfig
View File

@ -1,4 +1,47 @@
menu "Home Assistant MQTT Options"
config WIFI_SSID
string "WiFi SSID"
default "ItenIOT"
help
The SSID of the WiFi network to connect to.
config WIFI_PASSWORD
string "WiFi Password"
default "DasPferd"
help
The password for the WiFi network. Leave empty if the network is open.
config NET_DHCP
bool "Enable DHCP"
default y
help
Enable DHCP for the network interface. If disabled, static IP configuration must be provided.
config NET_IP_ADDR
string "Static IP address"
default "192.168.1.77"
help
Static IP address to use if DHCP is disabled. Must be in the same subnet as the router.
config NET_IP_MASK
string "Static IP netmask"
default "255.255.255.0"
help
Static IP netmask to use if DHCP is disabled. Must be in the same subnet as the router.
config NET_IP_GATEWAY
string "Static IP gateway"
default "192.168.1.1"
help
Static IP gateway to use if DHCP is disabled. This is usually the IP address of the router.
config NET_DNS_SERVER
string "Static IP DNS server"
default "192.168.1.1"
help
Static IP DNS server to use if DHCP is disabled. This is usually the IP address of the router.
config HA_MQTT_BROKER_HOSTNAME
string "MQTT broker hostname"
default "homeassistant.local"

View File

@ -0,0 +1,3 @@
CONFIG_WIFI=y
CONFIG_WIFI_ESP32=y
CONFIG_ESP32_WIFI_STA_RECONNECT=y

View File

@ -1,13 +1,2 @@
/ {
switches {
compatible = "gpio-keys";
sw0: switch_0 {
label = "User switch";
gpios = <&gpio0 11 GPIO_ACTIVE_LOW>;
};
};
aliases {
switch0 = &sw0;
};
};

View File

@ -1,78 +1,26 @@
# =============================================================================
# LOGGING AND DEBUGGING
# =============================================================================
CONFIG_LOG=y
#CONFIG_MQTT_LOG_LEVEL_DBG=y
# =============================================================================
# SYSTEM AND HARDWARE
# =============================================================================
CONFIG_GPIO=y
CONFIG_POSIX_API=y
CONFIG_MAIN_STACK_SIZE=4096
CONFIG_REBOOT=y
# =============================================================================
# SHELL AND COMMAND LINE INTERFACE
# =============================================================================
CONFIG_SHELL=y
CONFIG_NET_SHELL=y
CONFIG_SHELL_PROMPT_UART="(MQTT GW)> "
CONFIG_SHELL_BACKEND_TELNET=y
CONFIG_SHELL_TELNET_PORT=23
CONFIG_SHELL_PROMPT_TELNET="(MQTT GW)> "
# =============================================================================
# NETWORKING CORE
# =============================================================================
# Networking core
CONFIG_NETWORKING=y
CONFIG_NET_SOCKETS=y
CONFIG_NET_TCP=y
CONFIG_NET_LOG=y
CONFIG_NET_CONFIG_SETTINGS=y
CONFIG_ZVFS_POLL_MAX=10
# =============================================================================
# IPv4 NETWORKING
# =============================================================================
CONFIG_NET_IPV4=y
CONFIG_NET_UDP=y
CONFIG_NET_ARP=y
CONFIG_NET_MGMT=y
CONFIG_NET_MGMT_EVENT=y
# DHCP and DNS
CONFIG_NET_DHCPV4=y
# =============================================================================
# MQTT PROTOCOL
# =============================================================================
CONFIG_MQTT_LIB=y
# =============================================================================
# ENTROPY AND RANDOM NUMBER GENERATION
# =============================================================================
CONFIG_ENTROPY_GENERATOR=y
CONFIG_TEST_RANDOM_GENERATOR=y
# =============================================================================
# HWINFO FOR UUID
# =============================================================================
CONFIG_HWINFO=y
# =============================================================================
# JSON ENCODING SUPPORT
# =============================================================================
CONFIG_JSON_LIBRARY=y
# =============================================================================
# SETTINGS SUBSYSTEM
# =============================================================================
CONFIG_FLASH=y
CONFIG_FLASH_MAP=y
CONFIG_SETTINGS=y
CONFIG_NVS=y
CONFIG_SETTINGS_NVS=y
# =============================================================================
# DNS/mDNS RESOLVER SUPPORT FOR .local HOSTNAMES
# =============================================================================
CONFIG_NET_DHCPV4_OPTION_CALLBACKS=y
CONFIG_DNS_RESOLVER=y
CONFIG_MDNS_RESOLVER=y
CONFIG_DNS_RESOLVER_MAX_SERVERS=2
CONFIG_DNS_RESOLVER_LOG_LEVEL_DBG=y
# Logging and shell
CONFIG_NET_LOG=y
CONFIG_LOG=y
CONFIG_NET_SHELL=y
# Debugging and stack
CONFIG_INIT_STACKS=y
# IPv6 (disabled)
CONFIG_NET_IPV6=n
# CONFIG_NET_DHCP=n

View File

@ -1,6 +1,7 @@
#!/bin/bash
while true; do
build/zephyr/zephyr.exe --flash=flash.bin --uart_stdinout
# build/zephyr/zephyr.exe --flash=flash.bin --uart_stdinout
build/zephyr/zephyr.exe --uart_stdinout
if [ $? -eq 0 ]; then
break
fi

View File

@ -1,12 +0,0 @@
sample:
name: Blinky Sample
tests:
sample.basic.blinky:
tags:
- LED
- gpio
filter: dt_enabled_alias_with_parent_compat("led0", "gpio-leds")
depends_on: gpio
harness: led
integration_platforms:
- frdm_k64f

View File

@ -1,265 +1,24 @@
/*
* Copyright (c) 2023-2024 Golioth, Inc.
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(ha_mqtt_switch, CONFIG_LOG_DEFAULT_LEVEL);
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/shell/shell.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_mgmt.h>
#include <zephyr/drivers/hwinfo.h>
#include <zephyr/random/random.h> // For sys_rand32_get
#include <string.h> // For strlen, strncpy
#include "mqtt_client.h"
#include "mqtt_client_shell.h"
#include <zephyr/net/wifi_mgmt.h>
#include <zephyr/logging/log.h>
#define SWITCH_NODE DT_ALIAS(switch0)
#include "net.h"
#define UUID_MAX_LEN 40
#define HA_MQTT_STR_MAX_LEN 48
LOG_MODULE_REGISTER(mqtt_gw, CONFIG_LOG_DEFAULT_LEVEL);
static const struct gpio_dt_spec sw = GPIO_DT_SPEC_GET_OR(SWITCH_NODE, gpios, {0});
static struct gpio_callback switch_cb_data;
void switch_callback(const struct device *dev, struct gpio_callback *cb, uint32_t pins)
{
state = !state;
state_changed = true;
LOG_INF("Switch state: %s (via GPIO)", state ? "ON" : "OFF");
}
// Settings handler für uuid, name, broker
static int settings_set_ha(const char *key, size_t len, settings_read_cb read_cb, void *cb_arg)
{
if (strcmp(key, "uuid") == 0 && len < UUID_MAX_LEN) {
ssize_t rc = read_cb(cb_arg, uuid, len);
if (rc > 0) uuid[rc] = '\0';
return 0;
}
if (strcmp(key, "name") == 0 && len < HA_MQTT_STR_MAX_LEN) {
ssize_t rc = read_cb(cb_arg, name, len);
if (rc > 0) name[rc] = '\0';
return 0;
}
if (strcmp(key, "broker") == 0 && len < BROKER_HOST_MAX_LEN) {
ssize_t rc = read_cb(cb_arg, broker_host, len);
if (rc > 0) broker_host[rc] = '\0';
return 0;
}
return -ENOENT;
}
static int settings_export_ha(int (*cb)(const char *name, const void *val, size_t val_len))
{
int ret = 0;
ret |= cb("uuid", uuid, strlen(uuid));
ret |= cb("name", name, strlen(name));
ret |= cb("broker", broker_host, strlen(broker_host));
return ret;
}
SETTINGS_STATIC_HANDLER_DEFINE(ha, "ha", NULL, settings_set_ha, NULL, settings_export_ha);
// Shell command: ha set name <name>
static int cmd_ha_set_name(const struct shell *shell, size_t argc, char **argv)
{
if (argc != 2) {
shell_print(shell, "Usage: ha set name <new_name>");
return -EINVAL;
}
if (strlen(argv[1]) >= HA_MQTT_STR_MAX_LEN) {
shell_print(shell, "Error: name too long (max %d chars)", HA_MQTT_STR_MAX_LEN - 1);
return -EINVAL;
}
strncpy(name, argv[1], HA_MQTT_STR_MAX_LEN - 1);
name[HA_MQTT_STR_MAX_LEN - 1] = '\0';
settings_save_one("ha/name", name, strlen(name));
shell_print(shell, "Name set to: %s", name);
/**
* @brief Main application entry point.
*
* This function initializes the MQTT gateway application. It logs the board
* name and initializes the network stack.
*
* @return 0 on success, otherwise a negative error code.
*/
int main(void) {
// k_sleep(K_SECONDS(1)); // Allow time for logging initialization
LOG_INF("MQTT Gateway. Board: %s", CONFIG_BOARD);
net_init();
return 0;
}
// Shell command nur noch für uuid
static int cmd_ha_set_uuid(const struct shell *shell, size_t argc, char **argv)
{
if (argc != 2) {
shell_print(shell, "Usage: ha set uuid <new_uuid>");
return -EINVAL;
}
if (strlen(argv[1]) >= UUID_MAX_LEN) {
shell_print(shell, "Error: uuid too long (max %d chars)", UUID_MAX_LEN - 1);
return -EINVAL;
}
strncpy(uuid, argv[1], UUID_MAX_LEN - 1);
uuid[UUID_MAX_LEN - 1] = '\0';
settings_save_one("ha/uuid", uuid, strlen(uuid));
shell_print(shell, "UUID set to: %s", uuid);
return 0;
}
static int cmd_ha_set_broker(const struct shell *shell, size_t argc, char **argv)
{
if (mqtt_running) {
shell_print(shell, "Cannot change broker while MQTT client is running. Stop the client first.");
return -EBUSY;
}
if (argc != 2) {
shell_print(shell, "Usage: ha set broker <hostname>");
return -EINVAL;
}
if (strlen(argv[1]) >= BROKER_HOST_MAX_LEN) {
shell_print(shell, "Error: broker hostname too long (max %d chars)", BROKER_HOST_MAX_LEN - 1);
return -EINVAL;
}
strncpy(broker_host, argv[1], BROKER_HOST_MAX_LEN - 1);
broker_host[BROKER_HOST_MAX_LEN - 1] = '\0';
settings_save_one("ha/broker", broker_host, strlen(broker_host));
shell_print(shell, "Broker hostname set to: %s", broker_host);
return 0;
}
static int cmd_ha_show(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
shell_print(shell, "UUID: %s", uuid);
shell_print(shell, "Name: %s", name);
shell_print(shell, "Broker: %s", broker_host);
return 0;
}
static int cmd_ha_stop(const struct shell *shell, size_t argc, char **argv)
{
int err = ha_mqtt_stop();
if (err) {
shell_print(shell, "Failed to stop MQTT client: %d", err);
return err;
}
shell_print(shell, "MQTT client stopped.");
return 0;
}
static int cmd_ha_start(const struct shell *shell, size_t argc, char **argv)
{
if (mqtt_running) {
shell_print(shell, "MQTT client is already running.");
return 0;
}
int err = ha_mqtt_start();
if (err) {
shell_print(shell, "Failed to start MQTT client: %d", err);
return err;
}
shell_print(shell, "MQTT client started.");
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(ha_set_cmds,
SHELL_CMD(uuid, NULL, "Set Home Assistant UUID", cmd_ha_set_uuid),
SHELL_CMD(name, NULL, "Set Home Assistant Name", cmd_ha_set_name),
SHELL_CMD(broker, NULL, "Set MQTT broker hostname", cmd_ha_set_broker),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(ha_cmds,
SHELL_CMD(set, &ha_set_cmds, "Set settings", NULL),
SHELL_CMD(show, NULL, "Show settings", cmd_ha_show),
SHELL_CMD(stop, NULL, "Stop MQTT client", cmd_ha_stop),
SHELL_CMD(start, NULL, "Start MQTT client and send discovery", cmd_ha_start),
SHELL_SUBCMD_SET_END
);
SHELL_CMD_REGISTER(ha, &ha_cmds, "Home Assistant commands", NULL);
int main(void)
{
int err;
settings_subsys_init();
settings_load();
LOG_INF("Loaded uuid: '%s'", uuid);
if (strlen(uuid) == 0) {
// UUID initialisieren, falls leer: HW-UUID holen
uint8_t hwid[16];
ssize_t len = hwinfo_get_device_id(hwid, sizeof(hwid));
if (len > 0) {
char *p = uuid;
for (ssize_t i = 0; i < len && (p - uuid) < UUID_MAX_LEN - 2; ++i) {
p += snprintf(p, UUID_MAX_LEN - (p - uuid), "%02X", hwid[i]);
}
*p = '\0';
settings_save_one("ha/uuid", uuid, strlen(uuid));
LOG_INF("Generated default UUID from HW: %s", uuid);
} else {
/* Fallback for native_sim oder Plattformen ohne HW UUID: random value */
uint32_t rnd1 = sys_rand32_get();
uint32_t rnd2 = sys_rand32_get();
snprintf(uuid, UUID_MAX_LEN, "SIM-%08X%08X", rnd1, rnd2);
settings_save_one("ha/uuid", uuid, strlen(uuid));
LOG_WRN("No HW UUID available, generated random UUID: %s", uuid);
}
}
if (strlen(uuid) == 0) {
LOG_ERR("uuid is not set! Use 'ha set uuid <uuid>' in the shell.");
return -EINVAL;
}
if (strlen(uuid) >= UUID_MAX_LEN) {
LOG_ERR("uuid too long (%zu >= %d), MQTT client will not start!", strlen(uuid), UUID_MAX_LEN);
return -EINVAL;
}
LOG_INF("HA MQTT Switch sample started");
if (!sw.port) {
LOG_ERR("Switch GPIO device not found in device tree!");
return 0;
}
if (!gpio_is_ready_dt(&sw)) {
LOG_ERR("Switch device %s is not ready", sw.port->name);
return 0;
}
err = gpio_pin_configure_dt(&sw, GPIO_INPUT);
if (err) {
LOG_ERR("Failed to configure switch pin: %d", err);
return 0;
}
err = gpio_pin_interrupt_configure_dt(&sw, GPIO_INT_EDGE_TO_ACTIVE);
if (err) {
LOG_ERR("Failed to configure switch interrupt: %d", err);
return 0;
}
gpio_init_callback(&switch_cb_data, switch_callback, BIT(sw.pin));
gpio_add_callback(sw.port, &switch_cb_data);
// Wait for DHCP to complete and get an IPv4 address
struct net_if *iface = net_if_get_default();
LOG_INF("Waiting for IPv4 address via DHCP...");
for (int i = 0; i < 200; ++i) { // Wait up to ~20s
const struct in_addr *addr = net_if_ipv4_get_global_addr(iface, NET_ADDR_PREFERRED);
if (addr && addr->s_addr != 0) {
char buf[NET_IPV4_ADDR_LEN];
LOG_INF("Got IPv4 address: %s", net_addr_ntop(AF_INET, addr, buf, sizeof(buf)));
break;
}
k_sleep(K_MSEC(100));
if (i == 199) {
LOG_ERR("No IPv4 address after DHCP timeout");
return 0;
}
}
return 0;
}
}

View File

@ -1,434 +0,0 @@
#include <stdbool.h>
#include <zephyr/posix/poll.h>
#include <zephyr/net/socket.h>
#include <errno.h>
#include <string.h>
#include "mqtt_client.h"
#include <zephyr/logging/log.h>
#include <zephyr/net/mqtt.h>
#include <zephyr/random/random.h>
#include <zephyr/net/net_ip.h>
#include <zephyr/settings/settings.h>
#include <zephyr/data/json.h>
#include <zephyr/device.h>
#include <zephyr/drivers/gpio.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_mgmt.h>
#include <zephyr/drivers/hwinfo.h>
LOG_MODULE_REGISTER(mqtt_client, LOG_LEVEL_DBG);
#define HA_DISCOVERY_PREFIX "homeassistant"
#define MQTT_CLIENTID "ha-switch-zephyr"
#define APP_CONNECT_TIMEOUT_MS 2000
#define APP_SLEEP_MSECS 500
#define APP_MQTT_BUFFER_SIZE 2048
#define UUID_MAX_LEN 40
#define HA_MQTT_STR_MAX_LEN 48
#define TOPIC_BUF_LEN 128
#define BROKER_HOST_MAX_LEN 64
static struct pollfd fds[1];
static int nfds;
static struct mqtt_utf8 mqtt_username;
static struct mqtt_utf8 mqtt_password;
static uint8_t rx_buffer[APP_MQTT_BUFFER_SIZE];
static uint8_t tx_buffer[APP_MQTT_BUFFER_SIZE];
struct mqtt_client client_ctx;
static struct sockaddr_storage broker;
char broker_host[BROKER_HOST_MAX_LEN] = "127.0.0.1"; // Default broker host, replace as needed
bool mqtt_running = false;
bool state = false;
bool state_changed = false;
char name[HA_MQTT_STR_MAX_LEN] = CONFIG_HA_MQTT_NAME;
char uuid[UUID_MAX_LEN] = {0};
static void prepare_fds(struct mqtt_client *client)
{
if (client->transport.type == MQTT_TRANSPORT_NON_SECURE) {
fds[0].fd = client->transport.tcp.sock;
}
#if defined(CONFIG_MQTT_LIB_TLS)
else if (client->transport.type == MQTT_TRANSPORT_SECURE) {
fds[0].fd = client->transport.tls.sock;
}
#endif
else {
nfds = 0;
return;
}
fds[0].events = POLLIN;
nfds = 1;
}
void clear_fds(void)
{
nfds = 0;
}
int wait(int timeout)
{
int ret = -EAGAIN;
if (nfds > 0) {
ret = poll(fds, nfds, timeout);
if (ret > 0) {
if (fds[0].revents & (POLLIN | POLLERR)) {
ret = 0;
} else {
ret = -EIO;
}
}
}
return ret;
}
void mqtt_auth_init(void)
{
static char username_buf[64];
static char password_buf[64];
strncpy(username_buf, CONFIG_HA_MQTT_USERNAME, sizeof(username_buf) - 1);
username_buf[sizeof(username_buf) - 1] = '\0';
strncpy(password_buf, CONFIG_HA_MQTT_PASSWORD, sizeof(password_buf) - 1);
password_buf[sizeof(password_buf) - 1] = '\0';
mqtt_username.utf8 = (uint8_t *)username_buf;
mqtt_username.size = strlen(username_buf);
mqtt_password.utf8 = (uint8_t *)password_buf;
mqtt_password.size = strlen(password_buf);
}
int client_init(void)
{
int err;
LOG_DBG("Initializing MQTT client...");
mqtt_client_init(&client_ctx);
client_ctx.broker = &broker;
client_ctx.evt_cb = mqtt_evt_handler;
client_ctx.client_id.utf8 = (uint8_t *)MQTT_CLIENTID;
client_ctx.client_id.size = strlen(MQTT_CLIENTID);
mqtt_auth_init();
client_ctx.user_name = &mqtt_username;
client_ctx.password = &mqtt_password;
client_ctx.protocol_version = MQTT_VERSION_3_1_1;
client_ctx.transport.type = MQTT_TRANSPORT_NON_SECURE;
client_ctx.rx_buf = rx_buffer;
client_ctx.rx_buf_size = sizeof(rx_buffer);
client_ctx.tx_buf = tx_buffer;
client_ctx.tx_buf_size = sizeof(tx_buffer);
struct sockaddr_in *broker4 = (struct sockaddr_in *)&broker;
broker4->sin_family = AF_INET;
broker4->sin_port = htons(CONFIG_HA_MQTT_BROKER_PORT);
err = zsock_inet_pton(AF_INET, broker_host, &broker4->sin_addr);
if (err < 0) {
LOG_ERR("Failed to set broker address: %d", err);
return err;
}
LOG_DBG("MQTT client initialized.");
return 0;
}
int publish(struct mqtt_client *client, enum mqtt_qos qos, const char *topic, const char *payload)
{
struct mqtt_publish_param param;
param.message.topic.qos = qos;
param.message.topic.topic.utf8 = (uint8_t *)topic;
param.message.topic.topic.size = strlen(topic);
param.message.payload.data = (uint8_t *)payload;
param.message.payload.len = strlen(payload);
param.message_id = sys_rand32_get();
param.dup_flag = 0U;
param.retain_flag = 0U;
return mqtt_publish(client, &param);
}
int subscribe(struct mqtt_client *client, const char *topic)
{
struct mqtt_topic sub_topic = {
.topic = {
.utf8 = (uint8_t *)topic,
.size = strlen(topic)
},
.qos = MQTT_QOS_0_AT_MOST_ONCE
};
const struct mqtt_subscription_list sub_list = {
.list = &sub_topic,
.list_count = 1U,
.message_id = sys_rand32_get(),
};
return mqtt_subscribe(client, &sub_list);
}
void build_topic(char *buf, size_t buflen, const char *fmt)
{
int n = snprintf(buf, buflen, fmt, uuid);
if (n < 0 || n >= buflen) {
LOG_ERR("Topic buffer overflow or encoding error! fmt='%s', uuid='%s'", fmt, uuid);
}
}
int ha_publish_discovery_document(struct mqtt_client *client)
{
struct device_info {
const char *identifiers;
const char *name;
const char *model;
const char *mf;
};
struct config_info {
const char *name;
const char *cmd_t;
const char *stat_t;
const char *uniq_id;
struct device_info device;
};
static const struct json_obj_descr device_descr[] = {
JSON_OBJ_DESCR_PRIM(struct device_info, identifiers, JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct device_info, name, JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct device_info, model, JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct device_info, mf, JSON_TOK_STRING),
};
static const struct json_obj_descr config_descr[] = {
JSON_OBJ_DESCR_PRIM(struct config_info, name, JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct config_info, cmd_t, JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct config_info, stat_t, JSON_TOK_STRING),
JSON_OBJ_DESCR_PRIM(struct config_info, uniq_id, JSON_TOK_STRING),
JSON_OBJ_DESCR_OBJECT(struct config_info, device, device_descr),
};
char cmd_topic[TOPIC_BUF_LEN];
char state_topic[TOPIC_BUF_LEN];
char uniq_id[UUID_MAX_LEN];
char ident[TOPIC_BUF_LEN];
char disc_topic[TOPIC_BUF_LEN];
snprintf(cmd_topic, sizeof(cmd_topic), HA_DISCOVERY_PREFIX "/switch/%s/set", uuid);
snprintf(state_topic, sizeof(state_topic), HA_DISCOVERY_PREFIX "/switch/%s/state", uuid);
strncpy(uniq_id, uuid, sizeof(uniq_id) - 1);
uniq_id[sizeof(uniq_id) - 1] = '\0';
snprintf(ident, sizeof(ident), "zephyr-ha-%s", uuid);
snprintf(disc_topic, sizeof(disc_topic), HA_DISCOVERY_PREFIX "/switch/%s/config", uuid);
LOG_DBG("Publishing discovery document to topic: %s", disc_topic);
LOG_DBG("Command topic: %s", cmd_topic);
LOG_DBG("State topic: %s", state_topic);
LOG_DBG("Unique ID: %s", uniq_id);
LOG_DBG("Device identifier: %s", ident);
char model[HA_MQTT_STR_MAX_LEN];
strncpy(model, CONFIG_HA_MQTT_MODEL, HA_MQTT_STR_MAX_LEN - 1);
model[HA_MQTT_STR_MAX_LEN - 1] = '\0';
char mf[HA_MQTT_STR_MAX_LEN];
strncpy(mf, CONFIG_HA_MQTT_MANUFACTURER, HA_MQTT_STR_MAX_LEN - 1);
mf[HA_MQTT_STR_MAX_LEN - 1] = '\0';
struct device_info dev = {
.identifiers = ident,
.name = name,
.model = model,
.mf = mf
};
struct config_info cfg = {
.name = name,
.cmd_t = cmd_topic,
.stat_t = state_topic,
.uniq_id = uniq_id,
.device = dev
};
char payload[APP_MQTT_BUFFER_SIZE];
int err;
LOG_DBG("Encoding JSON discovery document...");
err = json_obj_encode_buf(config_descr, ARRAY_SIZE(config_descr), &cfg, payload, sizeof(payload));
if (err < 0) {
LOG_ERR("Failed to encode JSON: %d", err);
return err;
}
LOG_DBG("Discovery document: %s", payload);
LOG_DBG("Publishing discovery document...");
return publish(client, MQTT_QOS_0_AT_MOST_ONCE, disc_topic, payload);
}
void mqtt_evt_handler(struct mqtt_client *client, const struct mqtt_evt *evt)
{
int err;
switch (evt->type) {
case MQTT_EVT_CONNACK:
if (evt->result != 0) {
LOG_ERR("MQTT connect failed: %d", evt->result);
mqtt_running = false;
break;
}
LOG_INF("MQTT client connected!");
// Publish discovery document and subscribe to command topic on successful connection
err = ha_publish_discovery_document(client);
if (err) {
LOG_ERR("Failed to publish discovery document: %d", err);
} else {
LOG_INF("Published discovery document");
}
char cmd_topic[TOPIC_BUF_LEN];
build_topic(cmd_topic, sizeof(cmd_topic), HA_DISCOVERY_PREFIX "/switch/%s/set");
err = subscribe(client, cmd_topic);
if (err) {
LOG_ERR("Failed to subscribe to topic: %d", err);
} else {
LOG_INF("Subscribed to topic %s", cmd_topic);
}
break;
case MQTT_EVT_DISCONNECT:
LOG_INF("MQTT client disconnected: %d", evt->result);
mqtt_running = false;
clear_fds();
break;
case MQTT_EVT_PUBLISH:
LOG_INF("MQTT PUBLISH event received!");
const struct mqtt_publish_param *p = &evt->param.publish;
char payload_str[APP_MQTT_BUFFER_SIZE]; // Use a fixed-size buffer
char topic_str[TOPIC_BUF_LEN]; // Use a fixed-size buffer
if (p->message.payload.len >= sizeof(payload_str)) {
LOG_ERR("Payload too long for buffer");
break;
}
memcpy(payload_str, p->message.payload.data, p->message.payload.len);
payload_str[p->message.payload.len] = '\0';
if (p->message.topic.topic.size >= sizeof(topic_str)) {
LOG_ERR("Topic too long for buffer");
break;
}
memcpy(topic_str, p->message.topic.topic.utf8, p->message.topic.topic.size);
topic_str[p->message.topic.topic.size] = '\0';
LOG_INF("Topic: %s, Payload: %s", topic_str, payload_str);
// Handle switch command
char cmd_topic_expected[TOPIC_BUF_LEN];
build_topic(cmd_topic_expected, sizeof(cmd_topic_expected), HA_DISCOVERY_PREFIX "/switch/%s/set");
if (strcmp(topic_str, cmd_topic_expected) == 0) {
if (strcmp(payload_str, "ON") == 0) {
state = true;
state_changed = true;
LOG_INF("Switch state set to ON (via MQTT)");
} else if (strcmp(payload_str, "OFF") == 0) {
state = false;
state_changed = true;
LOG_INF("Switch state set to OFF (via MQTT)");
} else {
LOG_WRN("Unknown command payload: %s", payload_str);
}
}
break;
case MQTT_EVT_SUBACK:
LOG_INF("MQTT SUBACK event received!");
break;
case MQTT_EVT_UNSUBACK:
LOG_INF("MQTT UNSUBACK event received!");
break;
case MQTT_EVT_PUBACK:
LOG_INF("MQTT PUBACK event received!");
break;
case MQTT_EVT_PUBREC:
LOG_INF("MQTT PUBREC event received!");
break;
case MQTT_EVT_PUBREL:
LOG_INF("MQTT PUBREL event received!");
break;
case MQTT_EVT_PUBCOMP:
LOG_INF("MQTT PUBCOMP event received!");
break;
default:
LOG_DBG("Unhandled MQTT event type: %d", evt->type);
break;
}
}
void mqtt_client_run_loop(void)
{
int err;
while (mqtt_running) {
err = wait(APP_SLEEP_MSECS);
if (err && err != -EAGAIN) {
LOG_ERR("Failed to wait for MQTT client: %d", err);
break;
}
err = mqtt_input(&client_ctx);
if (err) {
LOG_ERR("Failed to process MQTT input: %d", err);
// Continue to allow for potential recovery or disconnect event
}
err = mqtt_live(&client_ctx);
if (err && err != -EAGAIN) {
LOG_ERR("Failed to send MQTT keepalive: %d", err);
break;
} else if (err == 0) {
LOG_DBG("Sent MQTT keepalive (PINGREQ)");
}
if (state_changed) {
char state_topic[TOPIC_BUF_LEN];
snprintf(state_topic, sizeof(state_topic), HA_DISCOVERY_PREFIX "/switch/%s/state", uuid);
err = publish(&client_ctx, MQTT_QOS_0_AT_MOST_ONCE, state_topic, state ? "ON" : "OFF");
if (err) {
LOG_ERR("Failed to publish switch state: %d", err);
} else {
LOG_INF("Published switch state: %s", state ? "ON" : "OFF");
state_changed = false;
}
}
}
// Clean up after loop exits (e.g., mqtt_running becomes false)
err = mqtt_disconnect(&client_ctx, NULL);
if (err) {
LOG_ERR("Failed to disconnect MQTT client: %d", err);
}
clear_fds();
LOG_INF("MQTT client run loop terminated.");
}
int ha_mqtt_start(void)
{
int err = client_init();
if (err) {
LOG_ERR("Failed to initialize MQTT client: %d", err);
return err;
}
err = mqtt_connect(&client_ctx);
if (err) {
LOG_ERR("Failed to connect to MQTT broker: %d", err);
return err;
}
mqtt_running = true;
struct sockaddr_in *broker4 = (struct sockaddr_in *)&broker;
char ip_str[NET_IPV4_ADDR_LEN];
if (zsock_inet_ntop(AF_INET, &broker4->sin_addr, ip_str, sizeof(ip_str))) {
LOG_INF("Connected to MQTT broker at %s:%d", ip_str, ntohs(broker4->sin_port));
}
prepare_fds(&client_ctx);
// Start the MQTT client run loop in a separate thread or manage it here
// For now, we'll call it directly, assuming it's blocking until mqtt_running is false
// In a real application, you might want to k_thread_create this.
mqtt_client_run_loop();
return 0;
}
int ha_mqtt_stop(void)
{
if (!mqtt_running) {
LOG_INF("MQTT client is not running.");
return 0;
}
// Setting mqtt_running to false will cause the mqtt_client_run_loop to exit
mqtt_running = false;
LOG_INF("MQTT client stop requested.");
return 0;
}

View File

@ -1,43 +0,0 @@
#ifndef MQTT_CLIENT_H
#define MQTT_CLIENT_H
#define HA_MQTT_STR_MAX_LEN 48
#define UUID_MAX_LEN 40
#define BROKER_HOST_MAX_LEN 64
extern char broker_host[BROKER_HOST_MAX_LEN];
extern bool mqtt_running;
extern bool state;
extern bool state_changed;
extern char uuid[];
extern char name[];
extern char broker_host[];
#include <zephyr/net/mqtt.h>
#include <zephyr/kernel.h>
#ifdef __cplusplus
extern "C" {
#endif
int mqtt_client_init_all(void);
int ha_mqtt_start(void);
int ha_mqtt_stop(void);
int ha_publish_discovery_document(struct mqtt_client *client);
int subscribe(struct mqtt_client *client, const char *topic);
void mqtt_evt_handler(struct mqtt_client *client, const struct mqtt_evt *evt);
void mqtt_client_run_loop(void);
void build_topic(char *buf, size_t buflen, const char *fmt);
void clear_fds(void);
int wait(int timeout);
int publish(struct mqtt_client *client, enum mqtt_qos qos, const char *topic, const char *payload);
extern struct mqtt_client client_ctx;
#ifdef __cplusplus
}
#endif
#endif // MQTT_CLIENT_H

View File

@ -1,119 +0,0 @@
#include "mqtt_client_shell.h"
#include "mqtt_client.h"
#include <zephyr/shell/shell.h>
#include <zephyr/settings/settings.h>
#include <string.h>
// Shell command: ha set name <name>
static int cmd_ha_set_name(const struct shell *shell, size_t argc, char **argv)
{
if (argc != 2) {
shell_print(shell, "Usage: ha set name <new_name>");
return -EINVAL;
}
if (strlen(argv[1]) >= HA_MQTT_STR_MAX_LEN) {
shell_print(shell, "Error: name too long (max %d chars)", HA_MQTT_STR_MAX_LEN - 1);
return -EINVAL;
}
strncpy(name, argv[1], HA_MQTT_STR_MAX_LEN - 1);
name[HA_MQTT_STR_MAX_LEN - 1] = '\0';
settings_save_one("ha/name", name, strlen(name));
shell_print(shell, "Name set to: %s", name);
return 0;
}
// Shell command: ha set uuid <uuid>
static int cmd_ha_set_uuid(const struct shell *shell, size_t argc, char **argv)
{
if (argc != 2) {
shell_print(shell, "Usage: ha set uuid <new_uuid>");
return -EINVAL;
}
if (strlen(argv[1]) >= UUID_MAX_LEN) {
shell_print(shell, "Error: uuid too long (max %d chars)", UUID_MAX_LEN - 1);
return -EINVAL;
}
strncpy(uuid, argv[1], UUID_MAX_LEN - 1);
uuid[UUID_MAX_LEN - 1] = '\0';
settings_save_one("ha/uuid", uuid, strlen(uuid));
shell_print(shell, "UUID set to: %s", uuid);
return 0;
}
// Shell command: ha set broker <hostname>
static int cmd_ha_set_broker(const struct shell *shell, size_t argc, char **argv)
{
if (mqtt_running) {
shell_print(shell, "Cannot change broker while MQTT client is running. Stop the client first.");
return -EBUSY;
}
if (argc != 2) {
shell_print(shell, "Usage: ha set broker <hostname>");
return -EINVAL;
}
if (strlen(argv[1]) >= BROKER_HOST_MAX_LEN) {
shell_print(shell, "Error: broker hostname too long (max %d chars)", BROKER_HOST_MAX_LEN - 1);
return -EINVAL;
}
strncpy(broker_host, argv[1], BROKER_HOST_MAX_LEN - 1);
broker_host[BROKER_HOST_MAX_LEN - 1] = '\0';
settings_save_one("ha/broker", broker_host, strlen(broker_host));
shell_print(shell, "Broker hostname set to: %s", broker_host);
return 0;
}
static int cmd_ha_show(const struct shell *shell, size_t argc, char **argv)
{
ARG_UNUSED(argc);
ARG_UNUSED(argv);
shell_print(shell, "UUID: %s", uuid);
shell_print(shell, "Name: %s", name);
shell_print(shell, "Broker: %s", broker_host);
return 0;
}
static int cmd_ha_stop(const struct shell *shell, size_t argc, char **argv)
{
int err = ha_mqtt_stop();
if (err) {
shell_print(shell, "Failed to stop MQTT client: %d", err);
return err;
}
shell_print(shell, "MQTT client stopped.");
return 0;
}
static int cmd_ha_start(const struct shell *shell, size_t argc, char **argv)
{
if (mqtt_running) {
shell_print(shell, "MQTT client is already running.");
return 0;
}
int err = ha_mqtt_start();
if (err) {
shell_print(shell, "Failed to start MQTT client: %d", err);
return err;
}
shell_print(shell, "MQTT client started.");
return 0;
}
SHELL_STATIC_SUBCMD_SET_CREATE(ha_set_cmds,
SHELL_CMD(uuid, NULL, "Set Home Assistant UUID", cmd_ha_set_uuid),
SHELL_CMD(name, NULL, "Set Home Assistant Name", cmd_ha_set_name),
SHELL_CMD(broker, NULL, "Set MQTT broker hostname", cmd_ha_set_broker),
SHELL_SUBCMD_SET_END
);
SHELL_STATIC_SUBCMD_SET_CREATE(ha_cmds,
SHELL_CMD(set, &ha_set_cmds, "Set settings", NULL),
SHELL_CMD(show, NULL, "Show settings", cmd_ha_show),
SHELL_CMD(stop, NULL, "Stop MQTT client", cmd_ha_stop),
SHELL_CMD(start, NULL, "Start MQTT client and send discovery", cmd_ha_start),
SHELL_SUBCMD_SET_END
);
void mqtt_client_shell_register(void)
{
SHELL_CMD_REGISTER(ha, &ha_cmds, "Home Assistant commands", NULL);
}

View File

@ -1,8 +0,0 @@
#ifndef MQTT_CLIENT_SHELL_H
#define MQTT_CLIENT_SHELL_H
#include <zephyr/shell/shell.h>
void mqtt_client_shell_register(void);
#endif // MQTT_CLIENT_SHELL_H

280
src/net.c Normal file
View File

@ -0,0 +1,280 @@
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(net, LOG_LEVEL_DBG);
#include <zephyr/kernel.h>
#include <zephyr/linker/sections.h>
#include <errno.h>
#include <stdio.h>
#include <zephyr/net/dns_resolve.h>
#include <zephyr/net/net_context.h>
#include <zephyr/net/net_core.h>
#include <zephyr/net/net_if.h>
#include <zephyr/net/net_mgmt.h>
#ifdef CONFIG_WIFI
#include <zephyr/net/wifi_mgmt.h>
static struct net_mgmt_event_callback wifi_mgmt_cb;
static K_SEM_DEFINE(wifi_connected_sem, 0, 1);
#endif // CONFIG_WIFI
static struct net_mgmt_event_callback net_mgmt_cb;
#ifdef CONFIG_NET_DHCP
static bool dhcp_enabled = true;
#else
static bool dhcp_enabled = false;
#endif
static char *static_ip = CONFIG_NET_IP_ADDR;
static char *static_netmask = CONFIG_NET_IP_MASK;
static char *static_gateway = CONFIG_NET_IP_GATEWAY;
static char *static_dns = CONFIG_NET_DNS_SERVER;
/**
* @brief Starts the DHCPv4 client on the given network interface.
*
* @param iface The network interface.
* @param user_data A pointer to user data (unused).
*/
static void start_dhcpv4_client(struct net_if *iface, void *user_data)
{
ARG_UNUSED(user_data);
if (!iface) {
LOG_ERR("No network interface provided");
return;
}
if (!net_if_is_up(iface)) {
LOG_ERR("Network interface %s is not up", net_if_get_device(iface)->name);
return;
}
LOG_INF("Starting DHCPv4 client on interface: %s, index %d",
net_if_get_device(iface)->name,
net_if_get_by_iface(iface));
net_dhcpv4_start(iface);
}
/**
* @brief Network management event handler.
*
* This function is called when a network management event occurs. It logs
* information about the new IP address, subnet mask, router, and lease time
* when an IPv4 address is added.
*
* @param cb The network management event callback structure.
* @param mgmt_event The network management event.
* @param iface The network interface.
*/
static void net_mgmt_event_handler(
struct net_mgmt_event_callback *cb, uint64_t mgmt_event, struct net_if *iface)
{
if (mgmt_event == NET_EVENT_IPV4_ADDR_ADD) {
for (int i = 0; i < NET_IF_MAX_IPV4_ADDR; i++) {
char buf[NET_IPV4_ADDR_LEN];
if (iface->config.ip.ipv4->unicast[i].ipv4.addr_type != NET_ADDR_DHCP) {
continue;
}
LOG_INF(" Address[%d]: %s",
net_if_get_by_iface(iface),
net_addr_ntop(AF_INET,
&iface->config.ip.ipv4->unicast[i].ipv4.address.in_addr,
buf,
sizeof(buf)));
LOG_INF(" Subnet[%d]: %s",
net_if_get_by_iface(iface),
net_addr_ntop(AF_INET,
&iface->config.ip.ipv4->unicast[i].netmask,
buf,
sizeof(buf)));
LOG_INF(" Router[%d]: %s",
net_if_get_by_iface(iface),
net_addr_ntop(AF_INET, &iface->config.ip.ipv4->gw, buf, sizeof(buf)));
LOG_INF("Lease time[%d]: %u seconds",
net_if_get_by_iface(iface),
iface->config.dhcpv4.lease_time);
}
}
}
#ifdef CONFIG_WIFI
/**
* @brief Wi-Fi management event handler.
*
* This function is called when a Wi-Fi management event occurs. It gives the
* `wifi_connected_sem` semaphore when the Wi-Fi is connected.
*
* @param cb The network management event callback structure.
* @param mgmt_event The network management event.
* @param iface The network interface.
*/
static void wifi_mgmt_event_handler(struct net_mgmt_event_callback *cb,
uint64_t mgmt_event, struct net_if *iface)
{
if (mgmt_event == NET_EVENT_WIFI_CONNECT_RESULT) {
LOG_INF("Wi-Fi connected!");
k_sem_give(&wifi_connected_sem);
}
}
/**
* @brief Initializes the Wi-Fi connection.
*
* This function sets up the Wi-Fi connection parameters and starts the
* connection process. It registers an event handler to listen for connection
* results.
*/
void init_wifi(void)
{
// Netzwerk-Interface abrufen
struct net_if *iface = net_if_get_default();
if (!iface) {
LOG_ERR("Could not get network interface");
return;
}
// Wi-Fi-Verbindungsparameter
struct wifi_connect_req_params cnx_params = {
.ssid = "ItenIOT", // Ersetzen Sie dies mit Ihrer SSID
.ssid_length = sizeof("ItenIOT") - 1,
.psk = "DasPferd", // Ersetzen Sie dies mit Ihrem Passwort
.psk_length = sizeof("DasPferd") - 1,
.security = WIFI_SECURITY_TYPE_PSK,
.channel = WIFI_CHANNEL_ANY,
};
// Event-Handler registrieren
net_mgmt_init_event_callback(&wifi_mgmt_cb, wifi_mgmt_event_handler, NET_EVENT_WIFI_CONNECT_RESULT);
net_mgmt_add_event_callback(&wifi_mgmt_cb);
LOG_INF("Connecting to Wi-Fi...");
// Verbindungsanfrage senden
if (net_mgmt(NET_REQUEST_WIFI_CONNECT, iface, &cnx_params, sizeof(struct wifi_connect_req_params))) {
LOG_ERR("Wi-Fi connection request failed");
return;
}
// // Warten, bis die Verbindung hergestellt ist
if (k_sem_take(&wifi_connected_sem, K_SECONDS(30)) != 0) {
LOG_ERR("Wi-Fi connection timed out");
return;
}
// Ab hier ist die Wi-Fi-Verbindung aufgebaut und Sie können Netzwerkoperationen durchführen.
// Ein DHCP-Client wird automatisch gestartet, um eine IP-Adresse zu beziehen.
LOG_INF("Wi-Fi setup complete.");
}
#endif // CONFIG_WIFI
/**
* @brief Initializes the network stack.
*
* This function initializes the network stack. It initializes and adds a
* network management event callback to log network events. If Wi-Fi is
* enabled, it initializes the Wi-Fi connection. If DHCP is enabled, it
* starts the DHCP client on all interfaces. Otherwise, it sets a static IP
* configuration.
*
* @return 0 on success, otherwise a negative error code.
*/
int net_init(void)
{
int rc;
net_mgmt_init_event_callback(&net_mgmt_cb, net_mgmt_event_handler, NET_EVENT_IPV4_ADDR_ADD);
net_mgmt_add_event_callback(&net_mgmt_cb);
#ifdef CONFIG_WIFI
init_wifi();
#endif // CONFIG_WIFI
if (dhcp_enabled) {
LOG_INF("DHCP is enabled, starting DHCP client on all interfaces");
net_if_foreach(start_dhcpv4_client, NULL);
} else {
struct net_if *iface = net_if_get_default();
if (!iface) {
LOG_ERR("No default network interface found");
return -ENODEV;
}
LOG_INF("DHCP is disabled, setting static IP configuration on interface: %s",
net_if_get_device(iface)->name);
if (net_if_is_up(iface)) {
LOG_INF("Bringing down network interface %s before setting static IP",
net_if_get_device(iface)->name);
// Ensure the interface is down before applying static configuration
rc = net_if_down(iface);
if (rc < 0) {
LOG_ERR("Failed to bring down network interface %s: %d",
net_if_get_device(iface)->name,
rc);
return rc;
}
}
struct in_addr ipaddr, netmask, gw;
if (net_addr_pton(AF_INET, static_ip, &ipaddr) < 0 ||
net_addr_pton(AF_INET, static_netmask, &netmask) < 0 ||
net_addr_pton(AF_INET, static_gateway, &gw) < 0) {
LOG_ERR("Invalid static IP configuration");
return -EINVAL;
}
struct net_if_addr *ifaddr =
net_if_ipv4_addr_add(iface, &ipaddr, NET_ADDR_MANUAL, 0);
if (ifaddr == NULL) {
LOG_ERR("Failed to add IPv4 address");
return -EIO;
}
rc = net_if_ipv4_set_netmask_by_addr(iface, &ipaddr, &netmask);
if (rc < 0) {
LOG_ERR("Failed to set netmask: %d", rc);
return rc;
}
net_if_ipv4_set_gw(iface, &gw);
const char *dns_servers_str[] = {
static_dns,
NULL, // Terminate the list with NULL
};
struct sockaddr_in dns_server;
dns_server.sin_family = AF_INET;
dns_server.sin_port = htons(53);
rc = net_addr_pton(AF_INET, static_dns, &dns_server.sin_addr);
if (rc < 0) {
LOG_ERR("Invalid DNS server address");
return rc;
}
const struct sockaddr *dns_servers_sa[] = {
(struct sockaddr *)&dns_server,
NULL, // Terminate the list with NULL
};
struct dns_resolve_context dns_ctx;
rc = dns_resolve_init(&dns_ctx, dns_servers_str, dns_servers_sa);
if (rc < 0) {
LOG_ERR("Failed to add DNS server: %d", rc);
return rc;
}
rc = net_if_up(iface);
if (rc < 0) {
LOG_ERR("Failed to bring up network interface %s: %d",
net_if_get_device(iface)->name,
rc);
return rc;
}
LOG_INF("Static IP configuration set");
LOG_INF(" IP: %s", static_ip);
LOG_INF(" Netmask: %s", static_netmask);
LOG_INF(" Gateway: %s", static_gateway);
LOG_INF(" DNS: %s", static_dns);
}
return 0;
}

17
src/net.h Normal file
View File

@ -0,0 +1,17 @@
#ifndef __NET_H__
#define __NET_H__
/**
* @brief Initializes the network stack.
*
* This function initializes the network stack. It initializes and adds a
* network management event callback to log network events. If Wi-Fi is
* enabled, it initializes the Wi-Fi connection. If DHCP is enabled, it
* starts the DHCP client on all interfaces. Otherwise, it sets a static IP
* configuration.
*
* @return 0 on success, otherwise a negative error code.
*/
int net_init(void);
#endif // __NET_H__