Initial commit: Set up Zephyr MQTT project
This commit is contained in:
commit
febaa06799
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Zephyr build artifacts
|
||||||
|
build/
|
||||||
|
run/
|
||||||
|
samples/
|
||||||
|
flash.bin
|
||||||
|
*.bin
|
||||||
|
*.hex
|
||||||
|
*.elf
|
||||||
|
*.map
|
||||||
|
*.lst
|
||||||
|
*.obj
|
||||||
|
*.o
|
||||||
|
*.d
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
|
@ -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"
|
||||||
|
|
@ -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>`.
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
&flash0 {
|
||||||
|
reg = <0x0 0x400000>; /* 4MB flash */
|
||||||
|
};
|
||||||
|
|
||||||
|
#include "espressif/partitions_0x0_default_4M.dtsi"
|
||||||
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
/ {
|
||||||
|
switches {
|
||||||
|
compatible = "gpio-keys";
|
||||||
|
sw0: switch_0 {
|
||||||
|
label = "User switch";
|
||||||
|
gpios = <&gpio0 11 GPIO_ACTIVE_LOW>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
aliases {
|
||||||
|
switch0 = &sw0;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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;
|
||||||
|
}
|
||||||
|
|
@ -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, ¶m);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
|
@ -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);
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue