From c28c6612ec6f12198d401a2ea6ed6dae66ac3b1d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:26:31 +0000 Subject: [PATCH 1/4] Initial plan From bfb01639f5160a317f277ad5e08d5903af97a079 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:34:15 +0000 Subject: [PATCH 2/4] Add Home Assistant integration subsystem and sample - Created subsys/mgmt/homeassistant with Kconfig, CMakeLists, and implementation - Added public API header in include/zephyr/mgmt/homeassistant/ - Integrated subsystem into main build system - Created basic sample in samples/subsys/homeassistant/basic/ - Added comprehensive documentation Co-authored-by: kartben <128251+kartben@users.noreply.github.com> --- .../zephyr/mgmt/homeassistant/homeassistant.h | 110 +++++++++ .../subsys/homeassistant/basic/CMakeLists.txt | 8 + samples/subsys/homeassistant/basic/README.rst | 144 +++++++++++ samples/subsys/homeassistant/basic/prj.conf | 40 +++ .../subsys/homeassistant/basic/sample.yaml | 21 ++ samples/subsys/homeassistant/basic/src/main.c | 137 +++++++++++ subsys/mgmt/CMakeLists.txt | 1 + subsys/mgmt/Kconfig | 2 + subsys/mgmt/homeassistant/CMakeLists.txt | 7 + subsys/mgmt/homeassistant/Kconfig | 64 +++++ subsys/mgmt/homeassistant/README.rst | 129 ++++++++++ subsys/mgmt/homeassistant/homeassistant.c | 231 ++++++++++++++++++ 12 files changed, 894 insertions(+) create mode 100644 include/zephyr/mgmt/homeassistant/homeassistant.h create mode 100644 samples/subsys/homeassistant/basic/CMakeLists.txt create mode 100644 samples/subsys/homeassistant/basic/README.rst create mode 100644 samples/subsys/homeassistant/basic/prj.conf create mode 100644 samples/subsys/homeassistant/basic/sample.yaml create mode 100644 samples/subsys/homeassistant/basic/src/main.c create mode 100644 subsys/mgmt/homeassistant/CMakeLists.txt create mode 100644 subsys/mgmt/homeassistant/Kconfig create mode 100644 subsys/mgmt/homeassistant/README.rst create mode 100644 subsys/mgmt/homeassistant/homeassistant.c diff --git a/include/zephyr/mgmt/homeassistant/homeassistant.h b/include/zephyr/mgmt/homeassistant/homeassistant.h new file mode 100644 index 0000000000000..bafee17eb6983 --- /dev/null +++ b/include/zephyr/mgmt/homeassistant/homeassistant.h @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2025 Benjamin Cabé + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_MGMT_HOMEASSISTANT_H_ +#define ZEPHYR_INCLUDE_MGMT_HOMEASSISTANT_H_ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Home Assistant Integration API + * @defgroup homeassistant_api Home Assistant Integration API + * @ingroup mgmt + * @{ + */ + +/** + * @brief Entity types supported by Home Assistant + */ +enum homeassistant_entity_type { + /** Sensor entity (read-only) */ + HOMEASSISTANT_ENTITY_SENSOR, + /** Binary sensor entity (on/off) */ + HOMEASSISTANT_ENTITY_BINARY_SENSOR, + /** Switch entity (controllable on/off) */ + HOMEASSISTANT_ENTITY_SWITCH, + /** Number entity (numeric value) */ + HOMEASSISTANT_ENTITY_NUMBER, +}; + +/** + * @brief Home Assistant entity configuration + */ +struct homeassistant_entity_config { + /** Name of the entity as it appears in Home Assistant */ + const char *name; + /** Type of entity */ + enum homeassistant_entity_type type; + /** Unit of measurement (for sensors, can be NULL) */ + const char *unit; + /** Device class (e.g., "temperature", "humidity", can be NULL) */ + const char *device_class; + /** Zbus channel associated with this entity */ + const struct zbus_channel *channel; +}; + +/** + * @brief Register a Zbus channel with Home Assistant + * + * This function registers a Zbus channel to be automatically synchronized + * with Home Assistant. The channel data will be published to Home Assistant + * as the specified entity type. + * + * @param config Entity configuration + * @return 0 on success, negative errno on failure + */ +int homeassistant_register_entity(const struct homeassistant_entity_config *config); + +/** + * @brief Initialize Home Assistant integration + * + * This function starts the Home Assistant integration service, which monitors + * registered Zbus channels and synchronizes them with Home Assistant. + * + * @return 0 on success, negative errno on failure + */ +int homeassistant_init(void); + +/** + * @brief Macro to define and register a Home Assistant entity + * + * This macro creates a Home Assistant entity configuration and automatically + * registers it at initialization time. + * + * @param _name Name of the entity + * @param _type Entity type (e.g., HOMEASSISTANT_ENTITY_SENSOR) + * @param _unit Unit of measurement (or NULL) + * @param _device_class Device class (or NULL) + * @param _channel Zbus channel to monitor + */ +#define HOMEASSISTANT_ENTITY_DEFINE(_name, _type, _unit, _device_class, _channel) \ + static const struct homeassistant_entity_config _homeassistant_entity_##_name = { \ + .name = STRINGIFY(_name), \ + .type = _type, \ + .unit = _unit, \ + .device_class = _device_class, \ + .channel = &_channel, \ + }; \ + static int _homeassistant_entity_init_##_name(void) \ + { \ + return homeassistant_register_entity(&_homeassistant_entity_##_name); \ + } \ + SYS_INIT(_homeassistant_entity_init_##_name, APPLICATION, 91) + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif + +#endif /* ZEPHYR_INCLUDE_MGMT_HOMEASSISTANT_H_ */ diff --git a/samples/subsys/homeassistant/basic/CMakeLists.txt b/samples/subsys/homeassistant/basic/CMakeLists.txt new file mode 100644 index 0000000000000..e70e206b9301d --- /dev/null +++ b/samples/subsys/homeassistant/basic/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) + +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(homeassistant_basic) + +target_sources(app PRIVATE src/main.c) diff --git a/samples/subsys/homeassistant/basic/README.rst b/samples/subsys/homeassistant/basic/README.rst new file mode 100644 index 0000000000000..22d4cddc3e9ca --- /dev/null +++ b/samples/subsys/homeassistant/basic/README.rst @@ -0,0 +1,144 @@ +.. zephyr:code-sample:: homeassistant-basic + :name: Home Assistant Integration Basic + :relevant-api: homeassistant_api zbus_apis + + Integrate Zephyr sensors with Home Assistant using Zbus channels. + +Overview +******** + +This sample demonstrates how to integrate Zephyr-based IoT devices with Home Assistant +using the Home Assistant integration library. The library bridges Zbus channels to +Home Assistant, allowing developers to expose sensor data and control entities without +writing any networking code. + +The sample creates three entities: + +* **Temperature sensor** - Reports temperature in Celsius +* **Humidity sensor** - Reports humidity percentage +* **Light switch** - Binary switch that can be toggled + +All sensor data is published through Zbus channels and automatically synchronized with +Home Assistant via the REST API. + +Requirements +************ + +* Home Assistant instance running and accessible on the network +* Long-lived access token from Home Assistant (create from your profile) +* Network connectivity (Ethernet or Wi-Fi configured) + +Configuration +************* + +Before building, update the following configurations in ``prj.conf``: + +.. code-block:: cfg + + CONFIG_HOMEASSISTANT_SERVER_ADDR="192.168.1.100" # Your Home Assistant IP + CONFIG_HOMEASSISTANT_SERVER_PORT=8123 # Home Assistant port + CONFIG_HOMEASSISTANT_API_TOKEN="your_token_here" # Your API token + CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.168.1.10" # Device IP address + +To generate a Home Assistant API token: + +1. Log into your Home Assistant web interface +2. Click on your profile (bottom left) +3. Scroll to "Long-Lived Access Tokens" +4. Click "Create Token" +5. Copy the token and update ``CONFIG_HOMEASSISTANT_API_TOKEN`` + +Building and Running +******************** + +This sample can be built for any board with networking support. Example for native_sim: + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/homeassistant/basic + :host-os: unix + :board: native_sim + :goals: build + :compact: + +Or for a real board with Ethernet (e.g., STM32 Nucleo): + +.. zephyr-app-commands:: + :zephyr-app: samples/subsys/homeassistant/basic + :host-os: unix + :board: nucleo_f767zi + :goals: build flash + :compact: + +Sample Output +============= + +.. code-block:: console + + *** Booting Zephyr OS build v3.x.x *** + [00:00:00.000,000] ha_sample: Home Assistant Integration Sample + [00:00:00.000,000] ha_sample: =================================== + [00:00:00.000,000] ha_sample: This sample demonstrates Zbus integration with Home Assistant + [00:00:00.000,000] ha_sample: Sensor data is automatically published to Home Assistant + [00:00:00.000,000] ha_sample: + [00:00:00.000,000] ha_sample: Configuration: + [00:00:00.000,000] ha_sample: Server: 192.168.1.100:8123 + [00:00:00.000,000] ha_sample: Update interval: 5000 ms + [00:00:00.000,000] homeassistant: Registered entity: temperature (type=0) + [00:00:00.000,000] homeassistant: Registered entity: humidity (type=0) + [00:00:00.000,000] homeassistant: Registered entity: light (type=2) + [00:00:00.000,000] homeassistant: Home Assistant integration initialized + [00:00:00.000,000] homeassistant: Home Assistant integration started + [00:00:00.000,000] homeassistant: Server: 192.168.1.100:8123 + [00:00:02.000,000] ha_sample: Starting sensor simulation... + [00:00:02.000,000] ha_sample: Temperature: 20°C + [00:00:02.000,000] ha_sample: Humidity: 40% + [00:00:02.000,000] ha_sample: Light switch: OFF + +Home Assistant Integration +*************************** + +Once the sample is running, the entities will appear in Home Assistant: + +* ``sensor.temperature`` - Temperature sensor with °C unit +* ``sensor.humidity`` - Humidity sensor with % unit +* ``sensor.light`` - Light switch control + +You can view these entities in Home Assistant's Developer Tools > States page. + +How It Works +************ + +The sample demonstrates the simplicity of the Home Assistant integration: + +1. **Define Zbus channels** - Create channels for your sensor data: + + .. code-block:: c + + ZBUS_CHAN_DEFINE(temperature_chan, struct temperature_msg, ...); + +2. **Register with Home Assistant** - Use the convenient macro: + + .. code-block:: c + + HOMEASSISTANT_ENTITY_DEFINE(temperature, + HOMEASSISTANT_ENTITY_SENSOR, + "°C", + "temperature", + temperature_chan); + +3. **Publish data to Zbus** - The library handles the rest: + + .. code-block:: c + + struct temperature_msg temp = { .value = 25 }; + zbus_chan_pub(&temperature_chan, &temp, K_NO_WAIT); + +The Home Assistant integration library automatically: + +* Monitors registered Zbus channels for updates +* Formats data as JSON payloads +* Sends HTTP POST requests to Home Assistant REST API +* Handles authentication with your API token +* Updates entities at the configured interval + +No networking code needed in your application! diff --git a/samples/subsys/homeassistant/basic/prj.conf b/samples/subsys/homeassistant/basic/prj.conf new file mode 100644 index 0000000000000..de01fb4a38ecb --- /dev/null +++ b/samples/subsys/homeassistant/basic/prj.conf @@ -0,0 +1,40 @@ +# Logging +CONFIG_LOG=y +CONFIG_LOG_MODE_MINIMAL=y +CONFIG_BOOT_BANNER=n + +# Zbus +CONFIG_ZBUS=y +CONFIG_ZBUS_LOG_LEVEL_INF=y +CONFIG_ZBUS_CHANNEL_NAME=y +CONFIG_ZBUS_OBSERVER_NAME=y + +# Networking +CONFIG_NETWORKING=y +CONFIG_NET_IPV4=y +CONFIG_NET_TCP=y +CONFIG_NET_SOCKETS=y +CONFIG_NET_SOCKETS_POSIX_NAMES=y + +# HTTP client +CONFIG_HTTP_CLIENT=y + +# DNS resolver +CONFIG_DNS_RESOLVER=y +CONFIG_DNS_RESOLVER_ADDITIONAL_BUF_CTR=5 +CONFIG_DNS_RESOLVER_ADDITIONAL_QUERIES=2 + +# Network configuration for testing +CONFIG_NET_CONFIG_SETTINGS=y +CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.168.1.10" +CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0" +CONFIG_NET_CONFIG_MY_IPV4_GW="192.168.1.1" + +# Home Assistant +CONFIG_HOMEASSISTANT=y +CONFIG_HOMEASSISTANT_SERVER_ADDR="192.168.1.100" +CONFIG_HOMEASSISTANT_SERVER_PORT=8123 +CONFIG_HOMEASSISTANT_API_TOKEN="your_token_here" +CONFIG_HOMEASSISTANT_DEVICE_NAME="Zephyr Sensor" +CONFIG_HOMEASSISTANT_UPDATE_INTERVAL=5000 +CONFIG_HOMEASSISTANT_LOG_LEVEL_INF=y diff --git a/samples/subsys/homeassistant/basic/sample.yaml b/samples/subsys/homeassistant/basic/sample.yaml new file mode 100644 index 0000000000000..23240f3fb4cdd --- /dev/null +++ b/samples/subsys/homeassistant/basic/sample.yaml @@ -0,0 +1,21 @@ +sample: + name: Home Assistant Integration Basic + description: Demonstrates Zbus integration with Home Assistant +common: + tags: + - homeassistant + - zbus + - networking + - iot + harness: console + harness_config: + type: one_line + regex: + - "Home Assistant Integration Sample" + integration_platforms: + - native_sim +tests: + sample.subsys.homeassistant.basic: + platform_allow: + - native_sim + - qemu_x86 diff --git a/samples/subsys/homeassistant/basic/src/main.c b/samples/subsys/homeassistant/basic/src/main.c new file mode 100644 index 0000000000000..8c8a218a220c6 --- /dev/null +++ b/samples/subsys/homeassistant/basic/src/main.c @@ -0,0 +1,137 @@ +/* + * Copyright (c) 2025 Benjamin Cabé + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +LOG_MODULE_REGISTER(ha_sample, LOG_LEVEL_INF); + +/** + * @brief Temperature sensor data structure + */ +struct temperature_msg { + int value; +}; + +/** + * @brief Humidity sensor data structure + */ +struct humidity_msg { + int value; +}; + +/** + * @brief Light switch data structure + */ +struct switch_msg { + int state; +}; + +/** + * Define Zbus channels for sensor data + */ +ZBUS_CHAN_DEFINE(temperature_chan, + struct temperature_msg, + NULL, + NULL, + ZBUS_OBSERVERS_EMPTY, + ZBUS_MSG_INIT(.value = 20) +); + +ZBUS_CHAN_DEFINE(humidity_chan, + struct humidity_msg, + NULL, + NULL, + ZBUS_OBSERVERS_EMPTY, + ZBUS_MSG_INIT(.value = 50) +); + +ZBUS_CHAN_DEFINE(light_switch_chan, + struct switch_msg, + NULL, + NULL, + ZBUS_OBSERVERS_EMPTY, + ZBUS_MSG_INIT(.state = 0) +); + +/** + * Register Home Assistant entities using the convenient macro + */ +HOMEASSISTANT_ENTITY_DEFINE(temperature, + HOMEASSISTANT_ENTITY_SENSOR, + "°C", + "temperature", + temperature_chan); + +HOMEASSISTANT_ENTITY_DEFINE(humidity, + HOMEASSISTANT_ENTITY_SENSOR, + "%", + "humidity", + humidity_chan); + +HOMEASSISTANT_ENTITY_DEFINE(light, + HOMEASSISTANT_ENTITY_SWITCH, + NULL, + "switch", + light_switch_chan); + +/** + * @brief Simulate sensor readings + */ +static void simulate_sensors(void) +{ + struct temperature_msg temp; + struct humidity_msg hum; + struct switch_msg sw; + int counter = 0; + + LOG_INF("Starting sensor simulation..."); + + while (1) { + /* Simulate temperature changes (20-30°C) */ + temp.value = 20 + (counter % 10); + zbus_chan_pub(&temperature_chan, &temp, K_MSEC(100)); + LOG_INF("Temperature: %d°C", temp.value); + + /* Simulate humidity changes (40-60%) */ + hum.value = 40 + ((counter * 2) % 20); + zbus_chan_pub(&humidity_chan, &hum, K_MSEC(100)); + LOG_INF("Humidity: %d%%", hum.value); + + /* Toggle light switch every 10 iterations */ + if (counter % 10 == 0) { + sw.state = !sw.state; + zbus_chan_pub(&light_switch_chan, &sw, K_MSEC(100)); + LOG_INF("Light switch: %s", sw.state ? "ON" : "OFF"); + } + + counter++; + + /* Wait before next reading */ + k_sleep(K_SECONDS(3)); + } +} + +int main(void) +{ + LOG_INF("Home Assistant Integration Sample"); + LOG_INF("==================================="); + LOG_INF("This sample demonstrates Zbus integration with Home Assistant"); + LOG_INF("Sensor data is automatically published to Home Assistant"); + LOG_INF(""); + LOG_INF("Configuration:"); + LOG_INF(" Server: %s:%d", CONFIG_HOMEASSISTANT_SERVER_ADDR, + CONFIG_HOMEASSISTANT_SERVER_PORT); + LOG_INF(" Update interval: %d ms", CONFIG_HOMEASSISTANT_UPDATE_INTERVAL); + LOG_INF(""); + + /* Start sensor simulation */ + simulate_sensors(); + + return 0; +} diff --git a/subsys/mgmt/CMakeLists.txt b/subsys/mgmt/CMakeLists.txt index 39ef3dd7a3c18..76b1825bb45f3 100644 --- a/subsys/mgmt/CMakeLists.txt +++ b/subsys/mgmt/CMakeLists.txt @@ -5,3 +5,4 @@ add_subdirectory_ifdef(CONFIG_MCUMGR mcumgr) add_subdirectory_ifdef(CONFIG_HAWKBIT hawkbit) add_subdirectory_ifdef(CONFIG_UPDATEHUB updatehub) add_subdirectory_ifdef(CONFIG_OSDP osdp) +add_subdirectory_ifdef(CONFIG_HOMEASSISTANT homeassistant) diff --git a/subsys/mgmt/Kconfig b/subsys/mgmt/Kconfig index d653b9f9a9530..97c3b29fd9836 100644 --- a/subsys/mgmt/Kconfig +++ b/subsys/mgmt/Kconfig @@ -14,4 +14,6 @@ source "subsys/mgmt/updatehub/Kconfig" source "subsys/mgmt/osdp/Kconfig" +source "subsys/mgmt/homeassistant/Kconfig" + endmenu diff --git a/subsys/mgmt/homeassistant/CMakeLists.txt b/subsys/mgmt/homeassistant/CMakeLists.txt new file mode 100644 index 0000000000000..c97a436d50855 --- /dev/null +++ b/subsys/mgmt/homeassistant/CMakeLists.txt @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: Apache-2.0 + +zephyr_library() + +zephyr_library_sources(homeassistant.c) + +zephyr_library_include_directories(${ZEPHYR_BASE}/subsys/net/lib) diff --git a/subsys/mgmt/homeassistant/Kconfig b/subsys/mgmt/homeassistant/Kconfig new file mode 100644 index 0000000000000..0609138898adb --- /dev/null +++ b/subsys/mgmt/homeassistant/Kconfig @@ -0,0 +1,64 @@ +# Home Assistant integration configuration options + +# Copyright (c) 2025 Benjamin Cabé +# SPDX-License-Identifier: Apache-2.0 + +menuconfig HOMEASSISTANT + bool "Home Assistant Integration" + depends on ZBUS + depends on NET_TCP + depends on NET_SOCKETS + depends on HTTP_CLIENT + help + Enable Home Assistant integration via Zbus channels. + This module automatically bridges Zbus channels to Home Assistant, + allowing devices to expose data without manual networking code. + +if HOMEASSISTANT + +config HOMEASSISTANT_SERVER_ADDR + string "Home Assistant server address" + default "192.168.1.100" + help + IP address or hostname of the Home Assistant server. + +config HOMEASSISTANT_SERVER_PORT + int "Home Assistant server port" + default 8123 + help + Port number of the Home Assistant server (default: 8123). + +config HOMEASSISTANT_API_TOKEN + string "Home Assistant API token" + default "" + help + Long-lived access token for Home Assistant API authentication. + Generate this from your Home Assistant profile settings. + +config HOMEASSISTANT_DEVICE_NAME + string "Device name" + default "Zephyr Device" + help + Name of the device as it will appear in Home Assistant. + +config HOMEASSISTANT_UPDATE_INTERVAL + int "Update interval in milliseconds" + default 5000 + range 100 3600000 + help + Interval at which to check for Zbus channel updates and + send them to Home Assistant (in milliseconds). + +config HOMEASSISTANT_LOG_LEVEL + int "Home Assistant integration log level" + default 3 + range 0 4 + help + Set log level for Home Assistant integration. + 0 = OFF, 1 = ERROR, 2 = WARNING, 3 = INFO, 4 = DEBUG + +module = HOMEASSISTANT +module-str = homeassistant +source "subsys/logging/Kconfig.template.log_config" + +endif # HOMEASSISTANT diff --git a/subsys/mgmt/homeassistant/README.rst b/subsys/mgmt/homeassistant/README.rst new file mode 100644 index 0000000000000..c97c77b926924 --- /dev/null +++ b/subsys/mgmt/homeassistant/README.rst @@ -0,0 +1,129 @@ +Home Assistant Integration +########################## + +The Home Assistant integration subsystem provides seamless integration between +Zephyr-based IoT devices and Home Assistant smart home platform. It bridges Zbus +channels to Home Assistant entities, allowing developers to expose sensor data and +control entities without writing any networking code. + +Features +******** + +* **Zero networking code** - Just publish to Zbus channels +* **Automatic synchronization** - Library handles all communication with Home Assistant +* **Multiple entity types** - Support for sensors, binary sensors, switches, and numbers +* **REST API integration** - Uses Home Assistant's REST API for state updates +* **Easy configuration** - Simple Kconfig options for server and authentication +* **Type-safe API** - Compile-time registration of entities + +Architecture +************ + +The Home Assistant integration consists of: + +1. **Entity Registration** - Register Zbus channels as Home Assistant entities +2. **Monitoring Thread** - Background thread that monitors registered channels +3. **HTTP Client** - Sends state updates to Home Assistant via REST API +4. **Authentication** - Handles API token authentication automatically + +Usage +***** + +Basic usage involves three steps: + +1. Define a Zbus channel for your data: + + .. code-block:: c + + struct sensor_data { + int value; + }; + + ZBUS_CHAN_DEFINE(sensor_chan, struct sensor_data, NULL, NULL, + ZBUS_OBSERVERS_EMPTY, ZBUS_MSG_INIT(.value = 0)); + +2. Register the channel with Home Assistant: + + .. code-block:: c + + HOMEASSISTANT_ENTITY_DEFINE(my_sensor, + HOMEASSISTANT_ENTITY_SENSOR, + "°C", + "temperature", + sensor_chan); + +3. Publish data to the Zbus channel: + + .. code-block:: c + + struct sensor_data data = { .value = 25 }; + zbus_chan_pub(&sensor_chan, &data, K_NO_WAIT); + +The library automatically sends updates to Home Assistant at the configured interval. + +Configuration +************* + +Enable the subsystem in your ``prj.conf``: + +.. code-block:: cfg + + CONFIG_HOMEASSISTANT=y + CONFIG_HOMEASSISTANT_SERVER_ADDR="192.168.1.100" + CONFIG_HOMEASSISTANT_SERVER_PORT=8123 + CONFIG_HOMEASSISTANT_API_TOKEN="your_long_lived_access_token" + +Required dependencies: + +.. code-block:: cfg + + CONFIG_ZBUS=y + CONFIG_NETWORKING=y + CONFIG_NET_TCP=y + CONFIG_NET_SOCKETS=y + CONFIG_HTTP_CLIENT=y + +Entity Types +************ + +The following Home Assistant entity types are supported: + +* ``HOMEASSISTANT_ENTITY_SENSOR`` - Read-only sensor (temperature, humidity, etc.) +* ``HOMEASSISTANT_ENTITY_BINARY_SENSOR`` - Binary state sensor (door, motion, etc.) +* ``HOMEASSISTANT_ENTITY_SWITCH`` - Controllable on/off switch +* ``HOMEASSISTANT_ENTITY_NUMBER`` - Numeric value that can be set + +API Reference +************* + +See ``include/zephyr/mgmt/homeassistant/homeassistant.h`` for complete API documentation. + +Key functions: + +* ``homeassistant_register_entity()`` - Manually register an entity +* ``homeassistant_init()`` - Initialize the subsystem (called automatically) +* ``HOMEASSISTANT_ENTITY_DEFINE()`` - Macro to define and register an entity + +Limitations +*********** + +Current limitations: + +* Maximum 16 entities per device (configurable in source) +* State updates only (no command reception from Home Assistant) +* Assumes simple data types (int) in Zbus messages +* No MQTT support (REST API only) + +Future enhancements may include: + +* MQTT discovery protocol support +* Bidirectional control (receive commands from Home Assistant) +* Configurable data type conversion callbacks +* TLS/HTTPS support +* Entity auto-discovery + +Samples +******* + +See ``samples/subsys/homeassistant/basic`` for a complete example demonstrating +temperature, humidity sensors and a light switch. diff --git a/subsys/mgmt/homeassistant/homeassistant.c b/subsys/mgmt/homeassistant/homeassistant.c new file mode 100644 index 0000000000000..bf2a6319069a4 --- /dev/null +++ b/subsys/mgmt/homeassistant/homeassistant.c @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2025 Benjamin Cabé + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +LOG_MODULE_REGISTER(homeassistant, CONFIG_HOMEASSISTANT_LOG_LEVEL); + +#define MAX_ENTITIES 16 +#define HTTP_RECV_BUF_SIZE 512 +#define HTTP_SEND_BUF_SIZE 1024 + +static struct homeassistant_entity_config const *registered_entities[MAX_ENTITIES]; +static int entity_count; +static K_MUTEX_DEFINE(entity_mutex); + +static uint8_t http_recv_buf[HTTP_RECV_BUF_SIZE]; + +/* Thread for monitoring Zbus channels and updating Home Assistant */ +static void homeassistant_thread(void *arg1, void *arg2, void *arg3); + +K_THREAD_DEFINE(homeassistant_tid, 2048, homeassistant_thread, + NULL, NULL, NULL, 5, 0, 0); + +/** + * @brief Format entity state as JSON payload + */ +static int format_entity_state(const struct homeassistant_entity_config *entity, + char *buf, size_t buf_size) +{ + const void *msg; + int ret; + + if (entity == NULL || entity->channel == NULL) { + return -EINVAL; + } + + msg = zbus_chan_const_msg(entity->channel); + if (msg == NULL) { + return -ENODATA; + } + + /* For simplicity, assume the channel message is a numeric type or string + * In a real implementation, you'd need type detection or user callbacks + */ + switch (entity->type) { + case HOMEASSISTANT_ENTITY_SENSOR: + case HOMEASSISTANT_ENTITY_NUMBER: { + /* Assume the message is a simple integer or float */ + int value = *(int *)msg; + + ret = snprintf(buf, buf_size, + "{\"state\":%d,\"attributes\":{\"unit_of_measurement\":\"%s\"}}", + value, entity->unit ? entity->unit : ""); + break; + } + case HOMEASSISTANT_ENTITY_BINARY_SENSOR: + case HOMEASSISTANT_ENTITY_SWITCH: { + /* Assume the message is a boolean (int 0 or 1) */ + int value = *(int *)msg; + + ret = snprintf(buf, buf_size, "{\"state\":\"%s\"}", + value ? "ON" : "OFF"); + break; + } + default: + return -ENOTSUP; + } + + if (ret < 0 || ret >= buf_size) { + return -ENOMEM; + } + + return ret; +} + +/** + * @brief Send state update to Home Assistant + */ +static int send_state_update(const struct homeassistant_entity_config *entity) +{ + char payload[HTTP_SEND_BUF_SIZE]; + char url[128]; + int sock; + struct sockaddr_in addr; + int ret; + + ret = format_entity_state(entity, payload, sizeof(payload)); + if (ret < 0) { + LOG_ERR("Failed to format entity state: %d", ret); + return ret; + } + + LOG_DBG("Payload: %s", payload); + + /* Create socket */ + sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if (sock < 0) { + LOG_ERR("Failed to create socket: %d", errno); + return -errno; + } + + /* Setup address */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(CONFIG_HOMEASSISTANT_SERVER_PORT); + ret = zsock_inet_pton(AF_INET, CONFIG_HOMEASSISTANT_SERVER_ADDR, + &addr.sin_addr); + if (ret < 0) { + LOG_ERR("Invalid server address"); + close(sock); + return -EINVAL; + } + + /* Connect to Home Assistant */ + ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr)); + if (ret < 0) { + LOG_ERR("Failed to connect to Home Assistant: %d", errno); + close(sock); + return -errno; + } + + /* Build URL for entity state update */ + snprintf(url, sizeof(url), "/api/states/sensor.%s", entity->name); + + /* Prepare HTTP request */ + struct http_request req; + + memset(&req, 0, sizeof(req)); + req.method = HTTP_POST; + req.url = url; + req.host = CONFIG_HOMEASSISTANT_SERVER_ADDR; + req.protocol = "HTTP/1.1"; + req.payload = payload; + req.payload_len = strlen(payload); + req.recv_buf = http_recv_buf; + req.recv_buf_len = sizeof(http_recv_buf); + + /* Add headers */ + static const char *headers[] = { + "Authorization: Bearer " CONFIG_HOMEASSISTANT_API_TOKEN "\r\n", + "Content-Type: application/json\r\n", + NULL + }; + req.header_fields = headers; + + /* Send request */ + ret = http_client_req(sock, &req, CONFIG_HOMEASSISTANT_UPDATE_INTERVAL, NULL); + + close(sock); + + if (ret < 0) { + LOG_ERR("HTTP request failed: %d", ret); + return ret; + } + + LOG_DBG("State update sent for %s", entity->name); + return 0; +} + +/** + * @brief Main thread for Home Assistant integration + */ +static void homeassistant_thread(void *arg1, void *arg2, void *arg3) +{ + ARG_UNUSED(arg1); + ARG_UNUSED(arg2); + ARG_UNUSED(arg3); + + LOG_INF("Home Assistant integration started"); + LOG_INF("Server: %s:%d", CONFIG_HOMEASSISTANT_SERVER_ADDR, + CONFIG_HOMEASSISTANT_SERVER_PORT); + + /* Wait a bit for network to be ready */ + k_sleep(K_SECONDS(2)); + + while (1) { + k_mutex_lock(&entity_mutex, K_FOREVER); + + /* Update all registered entities */ + for (int i = 0; i < entity_count; i++) { + if (registered_entities[i] != NULL) { + send_state_update(registered_entities[i]); + } + } + + k_mutex_unlock(&entity_mutex); + + /* Wait before next update cycle */ + k_sleep(K_MSEC(CONFIG_HOMEASSISTANT_UPDATE_INTERVAL)); + } +} + +int homeassistant_register_entity(const struct homeassistant_entity_config *config) +{ + if (config == NULL || config->channel == NULL) { + return -EINVAL; + } + + k_mutex_lock(&entity_mutex, K_FOREVER); + + if (entity_count >= MAX_ENTITIES) { + k_mutex_unlock(&entity_mutex); + LOG_ERR("Maximum number of entities reached"); + return -ENOMEM; + } + + registered_entities[entity_count++] = config; + + k_mutex_unlock(&entity_mutex); + + LOG_INF("Registered entity: %s (type=%d)", config->name, config->type); + return 0; +} + +int homeassistant_init(void) +{ + LOG_INF("Home Assistant integration initialized"); + return 0; +} + +SYS_INIT(homeassistant_init, APPLICATION, 90); From 995c1ec9caf7657d16baf204fd9aab7cd9dfe867 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:40:04 +0000 Subject: [PATCH 3/4] Fix API and configuration issues in Home Assistant integration - Fixed Kconfig to properly use logging template - Updated HOMEASSISTANT_ENTITY_DEFINE macro to accept string name parameter - Updated sample and documentation to match new macro signature - Simplified inet_pton usage to match other samples Co-authored-by: kartben <128251+kartben@users.noreply.github.com> --- include/zephyr/mgmt/homeassistant/homeassistant.h | 15 ++++++++------- samples/subsys/homeassistant/basic/README.rst | 3 ++- samples/subsys/homeassistant/basic/src/main.c | 9 ++++++--- subsys/mgmt/homeassistant/Kconfig | 9 +-------- subsys/mgmt/homeassistant/README.rst | 3 ++- subsys/mgmt/homeassistant/homeassistant.c | 8 +------- 6 files changed, 20 insertions(+), 27 deletions(-) diff --git a/include/zephyr/mgmt/homeassistant/homeassistant.h b/include/zephyr/mgmt/homeassistant/homeassistant.h index bafee17eb6983..e9e24e6948248 100644 --- a/include/zephyr/mgmt/homeassistant/homeassistant.h +++ b/include/zephyr/mgmt/homeassistant/homeassistant.h @@ -79,25 +79,26 @@ int homeassistant_init(void); * This macro creates a Home Assistant entity configuration and automatically * registers it at initialization time. * - * @param _name Name of the entity + * @param _var_name Variable name for the entity (used internally) + * @param _entity_name Name of the entity as a string (e.g., "temperature") * @param _type Entity type (e.g., HOMEASSISTANT_ENTITY_SENSOR) * @param _unit Unit of measurement (or NULL) * @param _device_class Device class (or NULL) * @param _channel Zbus channel to monitor */ -#define HOMEASSISTANT_ENTITY_DEFINE(_name, _type, _unit, _device_class, _channel) \ - static const struct homeassistant_entity_config _homeassistant_entity_##_name = { \ - .name = STRINGIFY(_name), \ +#define HOMEASSISTANT_ENTITY_DEFINE(_var_name, _entity_name, _type, _unit, _device_class, _channel) \ + static const struct homeassistant_entity_config _homeassistant_entity_##_var_name = { \ + .name = _entity_name, \ .type = _type, \ .unit = _unit, \ .device_class = _device_class, \ .channel = &_channel, \ }; \ - static int _homeassistant_entity_init_##_name(void) \ + static int _homeassistant_entity_init_##_var_name(void) \ { \ - return homeassistant_register_entity(&_homeassistant_entity_##_name); \ + return homeassistant_register_entity(&_homeassistant_entity_##_var_name); \ } \ - SYS_INIT(_homeassistant_entity_init_##_name, APPLICATION, 91) + SYS_INIT(_homeassistant_entity_init_##_var_name, APPLICATION, 91) /** * @} diff --git a/samples/subsys/homeassistant/basic/README.rst b/samples/subsys/homeassistant/basic/README.rst index 22d4cddc3e9ca..34218e165d110 100644 --- a/samples/subsys/homeassistant/basic/README.rst +++ b/samples/subsys/homeassistant/basic/README.rst @@ -120,7 +120,8 @@ The sample demonstrates the simplicity of the Home Assistant integration: .. code-block:: c - HOMEASSISTANT_ENTITY_DEFINE(temperature, + HOMEASSISTANT_ENTITY_DEFINE(temp_sensor, + "temperature", HOMEASSISTANT_ENTITY_SENSOR, "°C", "temperature", diff --git a/samples/subsys/homeassistant/basic/src/main.c b/samples/subsys/homeassistant/basic/src/main.c index 8c8a218a220c6..2d4764b992247 100644 --- a/samples/subsys/homeassistant/basic/src/main.c +++ b/samples/subsys/homeassistant/basic/src/main.c @@ -62,19 +62,22 @@ ZBUS_CHAN_DEFINE(light_switch_chan, /** * Register Home Assistant entities using the convenient macro */ -HOMEASSISTANT_ENTITY_DEFINE(temperature, +HOMEASSISTANT_ENTITY_DEFINE(temp_sensor, + "temperature", HOMEASSISTANT_ENTITY_SENSOR, "°C", "temperature", temperature_chan); -HOMEASSISTANT_ENTITY_DEFINE(humidity, +HOMEASSISTANT_ENTITY_DEFINE(hum_sensor, + "humidity", HOMEASSISTANT_ENTITY_SENSOR, "%", "humidity", humidity_chan); -HOMEASSISTANT_ENTITY_DEFINE(light, +HOMEASSISTANT_ENTITY_DEFINE(light_switch, + "light", HOMEASSISTANT_ENTITY_SWITCH, NULL, "switch", diff --git a/subsys/mgmt/homeassistant/Kconfig b/subsys/mgmt/homeassistant/Kconfig index 0609138898adb..e660952e8b14e 100644 --- a/subsys/mgmt/homeassistant/Kconfig +++ b/subsys/mgmt/homeassistant/Kconfig @@ -49,16 +49,9 @@ config HOMEASSISTANT_UPDATE_INTERVAL Interval at which to check for Zbus channel updates and send them to Home Assistant (in milliseconds). -config HOMEASSISTANT_LOG_LEVEL - int "Home Assistant integration log level" - default 3 - range 0 4 - help - Set log level for Home Assistant integration. - 0 = OFF, 1 = ERROR, 2 = WARNING, 3 = INFO, 4 = DEBUG - module = HOMEASSISTANT module-str = homeassistant +module-help = Enables logging for Home Assistant integration. source "subsys/logging/Kconfig.template.log_config" endif # HOMEASSISTANT diff --git a/subsys/mgmt/homeassistant/README.rst b/subsys/mgmt/homeassistant/README.rst index c97c77b926924..65ddfe4f50e0e 100644 --- a/subsys/mgmt/homeassistant/README.rst +++ b/subsys/mgmt/homeassistant/README.rst @@ -46,7 +46,8 @@ Basic usage involves three steps: .. code-block:: c - HOMEASSISTANT_ENTITY_DEFINE(my_sensor, + HOMEASSISTANT_ENTITY_DEFINE(temp_sensor, + "temperature", HOMEASSISTANT_ENTITY_SENSOR, "°C", "temperature", diff --git a/subsys/mgmt/homeassistant/homeassistant.c b/subsys/mgmt/homeassistant/homeassistant.c index bf2a6319069a4..641e302290dca 100644 --- a/subsys/mgmt/homeassistant/homeassistant.c +++ b/subsys/mgmt/homeassistant/homeassistant.c @@ -113,13 +113,7 @@ static int send_state_update(const struct homeassistant_entity_config *entity) memset(&addr, 0, sizeof(addr)); addr.sin_family = AF_INET; addr.sin_port = htons(CONFIG_HOMEASSISTANT_SERVER_PORT); - ret = zsock_inet_pton(AF_INET, CONFIG_HOMEASSISTANT_SERVER_ADDR, - &addr.sin_addr); - if (ret < 0) { - LOG_ERR("Invalid server address"); - close(sock); - return -EINVAL; - } + inet_pton(AF_INET, CONFIG_HOMEASSISTANT_SERVER_ADDR, &addr.sin_addr); /* Connect to Home Assistant */ ret = connect(sock, (struct sockaddr *)&addr, sizeof(addr)); From 7ef12ee44d72912a0c6b4cc35df81e46feb59b94 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 10 Nov 2025 10:41:58 +0000 Subject: [PATCH 4/4] Increase thread stack size for HTTP operations - Added configurable HOMEASSISTANT_THREAD_STACK_SIZE option - Increased default stack size from 2048 to 4096 bytes - This ensures sufficient stack for HTTP client operations Co-authored-by: kartben <128251+kartben@users.noreply.github.com> --- subsys/mgmt/homeassistant/Kconfig | 7 +++++++ subsys/mgmt/homeassistant/homeassistant.c | 4 ++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/subsys/mgmt/homeassistant/Kconfig b/subsys/mgmt/homeassistant/Kconfig index e660952e8b14e..7c74cba1846b8 100644 --- a/subsys/mgmt/homeassistant/Kconfig +++ b/subsys/mgmt/homeassistant/Kconfig @@ -49,6 +49,13 @@ config HOMEASSISTANT_UPDATE_INTERVAL Interval at which to check for Zbus channel updates and send them to Home Assistant (in milliseconds). +config HOMEASSISTANT_THREAD_STACK_SIZE + int "Thread stack size" + default 4096 + help + Stack size for the Home Assistant integration thread. + This thread handles HTTP communication with Home Assistant. + module = HOMEASSISTANT module-str = homeassistant module-help = Enables logging for Home Assistant integration. diff --git a/subsys/mgmt/homeassistant/homeassistant.c b/subsys/mgmt/homeassistant/homeassistant.c index 641e302290dca..eb76f0cb41663 100644 --- a/subsys/mgmt/homeassistant/homeassistant.c +++ b/subsys/mgmt/homeassistant/homeassistant.c @@ -28,8 +28,8 @@ static uint8_t http_recv_buf[HTTP_RECV_BUF_SIZE]; /* Thread for monitoring Zbus channels and updating Home Assistant */ static void homeassistant_thread(void *arg1, void *arg2, void *arg3); -K_THREAD_DEFINE(homeassistant_tid, 2048, homeassistant_thread, - NULL, NULL, NULL, 5, 0, 0); +K_THREAD_DEFINE(homeassistant_tid, CONFIG_HOMEASSISTANT_THREAD_STACK_SIZE, + homeassistant_thread, NULL, NULL, NULL, 5, 0, 0); /** * @brief Format entity state as JSON payload