Initial commit: Set up Zephyr MQTT project

This commit is contained in:
Eduard Iten 2025-07-17 08:49:06 +02:00
commit febaa06799
16 changed files with 1191 additions and 0 deletions

13
.gitignore vendored Normal file
View File

@ -0,0 +1,13 @@
# Zephyr build artifacts
build/
run/
samples/
flash.bin
*.bin
*.hex
*.elf
*.map
*.lst
*.obj
*.o
*.d

16
.vscode/c_cpp_properties.json vendored Normal file
View File

@ -0,0 +1,16 @@
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**"
],
"defines": [],
"cStandard": "c17",
"cppStandard": "gnu++17",
"intelliSenseMode": "linux-gcc-x64",
"compileCommands": "${workspaceFolder}/build/compile_commands.json"
}
],
"version": 4
}

7
CMakeLists.txt Normal file
View File

@ -0,0 +1,7 @@
# SPDX-License-Identifier: Apache-2.0
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)

46
Kconfig Normal file
View File

@ -0,0 +1,46 @@
menu "Home Assistant MQTT Options"
config HA_MQTT_BROKER_HOSTNAME
string "MQTT broker hostname"
default "homeassistant.local"
help
Hostname or IP address of the MQTT broker.
config HA_MQTT_BROKER_PORT
int "MQTT broker port"
default 1883
help
Port of the MQTT broker.
config HA_MQTT_USERNAME
string "MQTT username"
default "test"
help
Username for MQTT authentication.
config HA_MQTT_PASSWORD
string "MQTT password"
default "pytest"
help
Password for MQTT authentication.
config HA_MQTT_NAME
string "Device name for Home Assistant device info (name)"
default "Irrigation System Modbus MQTT Gateway"
help
Sets the product name string in the Home Assistant discovery message.
config HA_MQTT_MANUFACTURER
string "Manufacturer for Home Assistant device info (mf)"
default "Iten Engineering"
help
Sets the manufacturer string in the Home Assistant discovery message.
config HA_MQTT_MODEL
string "Product name for Home Assistant device info (model)"
default "Modbus MQTT Gateway"
help
Sets the product name string in the Home Assistant discovery message.
endmenu
source "Kconfig.zephyr"

97
README.rst Normal file
View File

@ -0,0 +1,97 @@
.. zephyr:code-sample:: blinky
:name: Blinky
:relevant-api: gpio_interface
Blink an LED forever using the GPIO API.
Overview
********
The Blinky sample blinks an LED forever using the :ref:`GPIO API <gpio_api>`.
The source code shows how to:
#. Get a pin specification from the :ref:`devicetree <dt-guide>` as a
:c:struct:`gpio_dt_spec`
#. Configure the GPIO pin as an output
#. Toggle the pin forever
See :zephyr:code-sample:`pwm-blinky` for a similar sample that uses the PWM API instead.
.. _blinky-sample-requirements:
Requirements
************
Your board must:
#. Have an LED connected via a GPIO pin (these are called "User LEDs" on many of
Zephyr's :ref:`boards`).
#. Have the LED configured using the ``led0`` devicetree alias.
Building and Running
********************
Build and flash Blinky as follows, changing ``reel_board`` for your board:
.. zephyr-app-commands::
:zephyr-app: samples/basic/blinky
:board: reel_board
:goals: build flash
:compact:
After flashing, the LED starts to blink and messages with the current LED state
are printed on the console. If a runtime error occurs, the sample exits without
printing to the console.
Build errors
************
You will see a build error at the source code line defining the ``struct
gpio_dt_spec led`` variable if you try to build Blinky for an unsupported
board.
On GCC-based toolchains, the error looks like this:
.. code-block:: none
error: '__device_dts_ord_DT_N_ALIAS_led_P_gpios_IDX_0_PH_ORD' undeclared here (not in a function)
Adding board support
********************
To add support for your board, add something like this to your devicetree:
.. code-block:: DTS
/ {
aliases {
led0 = &myled0;
};
leds {
compatible = "gpio-leds";
myled0: led_0 {
gpios = <&gpio0 13 GPIO_ACTIVE_LOW>;
};
};
};
The above sets your board's ``led0`` alias to use pin 13 on GPIO controller
``gpio0``. The pin flags :c:macro:`GPIO_ACTIVE_HIGH` mean the LED is on when
the pin is set to its high state, and off when the pin is in its low state.
Tips:
- See :dtcompatible:`gpio-leds` for more information on defining GPIO-based LEDs
in devicetree.
- If you're not sure what to do, check the devicetrees for supported boards which
use the same SoC as your target. See :ref:`get-devicetree-outputs` for details.
- See :zephyr_file:`include/zephyr/dt-bindings/gpio/gpio.h` for the flags you can use
in devicetree.
- If the LED is built in to your board hardware, the alias should be defined in
your :ref:`BOARD.dts file <devicetree-in-out-files>`. Otherwise, you can
define one in a :ref:`devicetree overlay <set-devicetree-overlays>`.

View File

@ -0,0 +1,5 @@
&flash0 {
reg = <0x0 0x400000>; /* 4MB flash */
};
#include "espressif/partitions_0x0_default_4M.dtsi"

4
boards/native_sim.conf Normal file
View File

@ -0,0 +1,4 @@
CONFIG_FLASH_SIMULATOR=y
# Disable native logging backends so we don't get conflicts with the shell if we start it with --uart_stdinout
CONFIG_LOG_BACKEND_NATIVE_POSIX=n

13
boards/native_sim.overlay Normal file
View File

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

78
prj.conf Normal file
View File

@ -0,0 +1,78 @@
# =============================================================================
# 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
# =============================================================================
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_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_DNS_RESOLVER=y
CONFIG_MDNS_RESOLVER=y
CONFIG_DNS_RESOLVER_MAX_SERVERS=2
CONFIG_DNS_RESOLVER_LOG_LEVEL_DBG=y

7
run_sim.sh Executable file
View File

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

12
sample.yaml Normal file
View File

@ -0,0 +1,12 @@
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

389
src/main.c Normal file
View File

@ -0,0 +1,389 @@
/*
* 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/net/socket.h>
#include <zephyr/net/mqtt.h>
#include <zephyr/random/random.h>
#include <zephyr/posix/poll.h>
#include <zephyr/net/net_ip.h>
#include <zephyr/settings/settings.h>
#include <zephyr/shell/shell.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>
#include "mqtt_client.h"
#include "mqtt_client_shell.h"
#define HA_DISCOVERY_PREFIX "homeassistant"
#define MQTT_CLIENTID "ha-switch-zephyr"
#define MQTT_BROKER_HOSTNAME CONFIG_HA_MQTT_BROKER_HOSTNAME
#define MQTT_BROKER_PORT CONFIG_HA_MQTT_BROKER_PORT
#define APP_CONNECT_TIMEOUT_MS 2000
#define APP_SLEEP_MSECS 500
#define APP_MQTT_BUFFER_SIZE 2048
#define SWITCH_NODE DT_ALIAS(switch0)
#define UUID_MAX_LEN 40
#define HA_MQTT_STR_MAX_LEN 48
// name, uuid, state, state_changed, mqtt_running, broker_host are now extern in mqtt_client.h
#define TOPIC_BUF_LEN 128
static const struct gpio_dt_spec sw = GPIO_DT_SPEC_GET_OR(SWITCH_NODE, gpios, {0});
static struct gpio_callback switch_cb_data;
static uint8_t rx_buffer[APP_MQTT_BUFFER_SIZE];
static struct sockaddr_storage broker;
// connected is only used locally in main.c
static bool connected;
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);
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;
}
}
// Im Main-Thread: MQTT-Start zentralisiert
err = ha_mqtt_start();
if (err) {
LOG_ERR("Failed to start MQTT client: %d", err);
return 0;
}
while (true) {
err = wait(APP_CONNECT_TIMEOUT_MS);
if (err) {
LOG_ERR("Failed to wait for MQTT client: %d", err);
err = mqtt_disconnect(&client_ctx, NULL);
if (err) {
LOG_ERR("Failed to disconnect MQTT client: %d", err);
}
clear_fds();
return 0;
}
err = mqtt_input(&client_ctx);
if (err) {
LOG_ERR("Failed to process MQTT input: %d", err);
continue;
}
if (!connected) {
continue;
}
err = ha_publish_discovery_document(&client_ctx);
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), "zephyr/%s/switch/set");
err = subscribe(&client_ctx, cmd_topic);
if (err) {
LOG_ERR("Failed to subscribe to topic: %d", err);
} else {
LOG_INF("Subscribed to topic %s", cmd_topic);
}
// Break if MQTT client is stopped externally (e.g., via shell)
if (!mqtt_running) {
break;
}
break;
}
while (true) {
if (!mqtt_running) {
break;
}
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;
}
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;
}
}
}
err = mqtt_disconnect(&client_ctx, NULL);
if (err) {
LOG_ERR("Failed to disconnect MQTT client: %d", err);
}
mqtt_running = false;
clear_fds();
return 0;
}

331
src/mqtt_client.c Normal file
View File

@ -0,0 +1,331 @@
// ...existing includes...
#include <stdbool.h>
#include <zephyr/posix/poll.h>
#include <zephyr/net/socket.h>
#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 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 bool connected;
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;
// Only one definition for each global variable
char broker_host[BROKER_HOST_MAX_LEN] = "127.0.0.1"; // Default broker host, replace as needed
bool mqtt_running = false;
void mqtt_evt_handler(struct mqtt_client *client, const struct mqtt_evt *evt) {
// TODO: Implement event handler logic or leave as stub if implemented elsewhere
}
bool state = false;
bool state_changed = false;
// ...existing includes...
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);
}
// ...existing code...
#include "mqtt_client.h"
#include <zephyr/logging/log.h>
#include <zephyr/net/socket.h>
#include <zephyr/net/mqtt.h>
#include <zephyr/random/random.h>
#include <zephyr/posix/poll.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>
#include <string.h>
// ...existing code...
#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
char name[HA_MQTT_STR_MAX_LEN] = CONFIG_HA_MQTT_NAME;
char uuid[UUID_MAX_LEN] = {0};
// ...existing code...
// ...existing code...
int mqtt_client_init_all(void) {
// Placeholder for future expansion
return 0;
}
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);
}
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);
return 0;
}
int ha_mqtt_stop(void)
{
if (!mqtt_running) {
LOG_INF("MQTT client is not running.");
return 0;
}
int err = mqtt_disconnect(&client_ctx, NULL);
if (err) {
LOG_ERR("Failed to disconnect MQTT client: %d", err);
return err;
}
clear_fds();
mqtt_running = false;
LOG_INF("MQTT client stopped.");
return 0;
}

46
src/mqtt_client.h Normal file
View File

@ -0,0 +1,46 @@
#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;
#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 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 bool mqtt_running;
extern bool state;
extern bool state_changed;
extern char uuid[];
extern char name[];
extern char broker_host[];
extern struct mqtt_client client_ctx;
#ifdef __cplusplus
}
#endif
#endif // MQTT_CLIENT_H

119
src/mqtt_client_shell.c Normal file
View File

@ -0,0 +1,119 @@
#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);
}

8
src/mqtt_client_shell.h Normal file
View File

@ -0,0 +1,8 @@
#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