首页 > 其他分享 >Home Assistant MQTT发现服务的实现

Home Assistant MQTT发现服务的实现

时间:2023-02-08 15:48:28浏览次数:76  
标签:cJSON Assistant MQTT mosquitto rc device Home mosq payload

Home Assistant MQTT发现服务的实现

MQTT收发基于mosquito

以下内容摘自官方文档,可查看文档原文
MQTT Discovery

The discovery of MQTT devices will enable one to use MQTT devices with only minimal configuration effort on the side of Home Assistant. The configuration is done on the device itself and the topic used by the device. Similar to the HTTP binary sensor and the HTTP sensor. To prevent multiple identical entries if a device reconnects, a unique identifier is necessary. Two parts are required on the device side: The configuration topic which contains the necessary device type and unique identifier, and the remaining device configuration without the device type.

MQTT discovery is enabled by default, but can be disabled. The prefix for the discovery topic (default homeassistant) can be changed. See the MQTT Options sections
Discovery messages
Discovery topic

The discovery topic needs to follow a specific format:

<discovery_prefix>/<component>/[<node_id>/]<object_id>/config
  • component : One of the supported MQTT components, eg. binary_sensor.
  • <node_id> (Optional): ID of the node providing the topic, this is not used by Home Assistant but may be used to structure the MQTT topic. The ID of the node must only consist of characters from the character class [a-zA-Z0-9_-] (alphanumerics, underscore and hyphen).
  • <object_id>: The ID of the device. This is only to allow for separate topics for each device and is not used for the entity_id. The ID of the device must only consist of characters from the character class [a-zA-Z0-9_-] (alphanumerics, underscore and hyphen).

The <node_id> level can be used by clients to only subscribe to their own (command) topics by using one wildcard topic like <discovery_prefix>/+/<node_id>/+/set.

Best practice for entities with a unique_id is to set <object_id> to unique_id and omit the <node_id>.
Discovery payload

The payload must be a serialized JSON dictionary and will be checked like an entry in your configuration.yaml file if a new device is added, with the exception that unknown configuration keys are allowed but ignored. This means that missing variables will be filled with the component’s default values. All configuration variables which are required must be present in the payload. The reason for allowing unknown documentation keys is allow some backwards compatibility, software generating MQTT discovery messages can then be used with older Home Assistant versions which will simply ignore new features.

Subsequent messages on a topic where a valid payload has been received will be handled as a configuration update, and a configuration update with an empty payload will cause a previously discovered device to be deleted.

A base topic ~ may be defined in the payload to conserve memory when the same topic base is used multiple times. In the value of configuration variables ending with _topic, ~ will be replaced with the base topic, if the ~ occurs at the beginning or end of the value.

Configuration variable names in the discovery payload may be abbreviated to conserve memory when sending a discovery message from memory constrained devices.

How to?

  1. 向特定topic发送按格式要求的payload以创建设备(device)或者实体(entry)

  2. 向注册的状态topic上报信息

实现过程源码

/**
 * @file main.c
 * @author IotaHydrae ([email protected])
 * @brief
 * @version 0.1
 * @date 2023-02-07
 *
 * MIT License
 *
 * Copyright 2022 IotaHydrae([email protected])
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 *
 */

#include <mosquitto.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <pthread.h>
#include <cjson/cJSON.h>

#define DISCOVERY_PERFIX "homeassistant"

#define HA_ID       "000102adssdds"
#define HA_NAME     "Loona"
#define HA_SW_VER   "05881cd52242187d49a38e3fd7d675a41d5f564f"
#define HA_MODEL    "Revision a"
#define HA_MANUFACTURER "KEYi TECH"

#define NODE_CPU_TEMPERATURE    "/sys/class/hwmon/hwmon0/temp1_input"
#define NODE_BATTERY_PERCENT    "/sys/bus/i2c/devices/0-0062/cw2015_cur_percent"

int send_sensor_temperature_discovery(struct mosquitto *mosq)
{

    int rc;
    char *string = NULL;

    cJSON *device = cJSON_CreateObject();
    cJSON *payload = cJSON_CreateObject();

    cJSON_AddStringToObject(payload, "~", "Loona/sensor/temperature_cpu");
    cJSON_AddStringToObject(payload, "name", "Temperature CPU");

    cJSON_AddStringToObject(payload, "state_topic", "~/state");

    cJSON_AddStringToObject(payload, "unique_id", "0a7476cc-d6c1-40ba-8ae1-606518c3497f");

    cJSON_AddStringToObject(payload, "unit_of_measurement", "°C");

    // if (cJSON_AddStringToObject(payload, "device", device) == NULL)
    // {
    //     goto end;
    // }
    cJSON_AddItemToObject(payload, "device", device);

    cJSON_AddStringToObject(device, "identifiers", HA_ID);


    cJSON_AddStringToObject(device, "name", HA_NAME);

    cJSON_AddStringToObject(device, "sw_version", HA_SW_VER);

    cJSON_AddStringToObject(device, "model", HA_MODEL);

    cJSON_AddStringToObject(device, "manufacturer", HA_MANUFACTURER);

    // cJSON_AddItemToObject(payload, "device", cJSON_CreateObject());

    cJSON_AddStringToObject(payload, "force_update", "False");

    string = cJSON_PrintUnformatted(payload);
    if (string == NULL) {
        fprintf(stderr, "Failed to print monitor.\n");
    }
    printf("%s\n", string);

    rc = mosquitto_publish(mosq, NULL,
                           DISCOVERY_PERFIX "/sensor/Loona/temperature_cpu/config",
                           strlen(string), string, 2, false);
    if (rc != MOSQ_ERR_SUCCESS) {
        fprintf(stderr, "Error publishing: %s\n", mosquitto_strerror(rc));
    }
    return 0;
}

int get_temperature(void)
{
    sleep(1); /* Prevent a storm of messages - this pretend sensor works at 1Hz */

    int fd_temp_cpu = open(NODE_CPU_TEMPERATURE, O_RDONLY);
    char temp[10];

    read(fd_temp_cpu, temp, sizeof(temp) / sizeof(temp[0]));
    // printf("%s\n", temperatue);
    close(fd_temp_cpu);

    return atoi(temp);
}

void *publish_sensor_data(void *data)
{
    char payload[20];
    int temp;
    int rc;
    struct mosquitto *mosq = (struct mosquitto *)data;

    while (true) {
        /* Get our pretend data */
        temp = get_temperature();
        printf("temperature : %d.%d\n", temp / 1000, temp % 1000);
        /* Print it to a string for easy human reading - payload format is highly
         * application dependent. */
        snprintf(payload, sizeof(payload), "%d.%d", temp / 1000, temp % 1000);

        /* Publish the message
         * mosq - our client instance
         * *mid = NULL - we don't want to know what the message id for this message is
         * topic = "example/temperature" - the topic on which this message will be published
         * payloadlen = strlen(payload) - the length of our payload in bytes
         * payload - the actual payload
         * qos = 2 - publish with QoS 2 for this example
         * retain = false - do not use the retained message feature for this message
         */
        // rc = mosquitto_publish(mosq, NULL, "example/temperature", strlen(payload), payload, 2, false);
        // rc = mosquitto_publish(mosq, NULL, "home/kitchen/light", strlen(payload), payload, 2, false);
        rc = mosquitto_publish(mosq, NULL, "Loona/sensor/temperature_cpu/state",
                               strlen(payload), payload, 2, false);
        if (rc != MOSQ_ERR_SUCCESS) {
            fprintf(stderr, "Error publishing: %s\n", mosquitto_strerror(rc));
        }
    }
}

int send_sensor_battery_percent_discovery(struct mosquitto *mosq)
{
    int rc;
    char *string = NULL;

    cJSON *device = cJSON_CreateObject();
    cJSON *payload = cJSON_CreateObject();

    /*
    {
        '~': 'XeniaClock/sensor/inside/temperature_roof',
        'name': 'Temperature Roof',
        'state_topic': '~/state',
        'unique_id': '0a7476cc-d6c1-40ba-8ae1-606518c3497f',
        'unit_of_measurement': '°C',
        'device': {'identifiers': '000102aabbcc', 'name': 'XeniaClock', 'sw_version': '05881cd52242187d49a38e3fd7d675a41d5f564f', 'model': 'Revision A', 'manufacturer': 'embeddedboys'},
        'force_update': False
    }
    */
    /* build payload */

    cJSON_AddStringToObject(payload, "~", "Loona/sensor/battery_percent");
    cJSON_AddStringToObject(payload, "name", "Battery Percent");

    cJSON_AddStringToObject(payload, "state_topic", "~/state");

    cJSON_AddStringToObject(payload, "unique_id", "0a7476cc-d6c1-40ba-8ae1-606518c3497d");

    cJSON_AddStringToObject(payload, "unit_of_measurement", "%");

    // if (cJSON_AddStringToObject(payload, "device", device) == NULL)
    // {
    //     goto end;
    // }
    cJSON_AddItemToObject(payload, "device", device);

    cJSON_AddStringToObject(device, "identifiers", HA_ID);


    cJSON_AddStringToObject(device, "name", HA_NAME);

    cJSON_AddStringToObject(device, "sw_version", HA_SW_VER);

    cJSON_AddStringToObject(device, "model", HA_MODEL);

    cJSON_AddStringToObject(device, "manufacturer", HA_MANUFACTURER);

    // cJSON_AddItemToObject(payload, "device", cJSON_CreateObject());

    cJSON_AddStringToObject(payload, "force_update", "False");

    string = cJSON_PrintUnformatted(payload);
    if (string == NULL) {
        fprintf(stderr, "Failed to print monitor.\n");
    }
    printf("%s\n", string);

    rc = mosquitto_publish(mosq, NULL,
                           DISCOVERY_PERFIX "/sensor/Loona/battery_percent/config",
                           strlen(string), string, 2, false);
    if (rc != MOSQ_ERR_SUCCESS) {
        fprintf(stderr, "Error publishing: %s\n", mosquitto_strerror(rc));
    }

    return 0;
}

int get_battery_percent()
{
    sleep(1); /* Prevent a storm of messages - this pretend sensor works at 1Hz */

    int fd_bat_percent = open(NODE_BATTERY_PERCENT, O_RDONLY);
    char percent[5];

    read(fd_bat_percent, percent, sizeof(percent) / sizeof(percent[0]));
    // printf("%s\n", temperatue);
    close(fd_bat_percent);
    return atoi(percent);
}

void *publish_battery_info(void *data)
{
    char payload[20];
    int percent;
    int rc;
    struct mosquitto *mosq = (struct mosquitto *)data;

    while (true) {
        /* Get our pretend data */
        percent = get_battery_percent();
        printf("Battery : %d\n", percent);
        /* Print it to a string for easy human reading - payload format is highly
        * application dependent. */
        snprintf(payload, sizeof(payload), "%d", percent);

        /* Publish the message
        * mosq - our client instance
        * *mid = NULL - we don't want to know what the message id for this message is
        * topic = "example/temperature" - the topic on which this message will be published
        * payloadlen = strlen(payload) - the length of our payload in bytes
        * payload - the actual payload
        * qos = 2 - publish with QoS 2 for this example
        * retain = false - do not use the retained message feature for this message
        */
        // rc = mosquitto_publish(mosq, NULL, "example/temperature", strlen(payload), payload, 2, false);
        // rc = mosquitto_publish(mosq, NULL, "home/kitchen/light", strlen(payload), payload, 2, false);
        rc = mosquitto_publish(mosq, NULL, "Loona/sensor/battery_percent/state",
                               strlen(payload), payload, 2, false);
        if (rc != MOSQ_ERR_SUCCESS) {
            fprintf(stderr, "Error publishing: %s\n", mosquitto_strerror(rc));
        }
    }
}

int send_discovery(struct mosquitto *mosq)
{
    printf("Sending Temperature sensor Discovery message ...");
    send_sensor_temperature_discovery(mosq);
    printf("Sending Battery Percent sensor Discovery message ...");
    send_sensor_battery_percent_discovery(mosq);

    return 0;
}

void on_connect(struct mosquitto *mosq, void *obj, int reason_code)
{
    int rc;

    printf("%s, %d, %s\n", __func__, __LINE__, mosquitto_connack_string(reason_code));
    if (reason_code != 0) {
        /* If the connection fails for any reason, we don't want to keep on
         * retrying in this example, so disconnect. Without this, the client
         * will attempt to reconnect. */
        mosquitto_disconnect(mosq);
    }
    send_discovery(mosq);
}

int main(int argc, char **argv)
{
    int rc;

    /* Required before calling other mosquitto functions */
    mosquitto_lib_init();

    struct mosquitto *mosq = mosquitto_new(NULL, true, NULL);
    if (!mosq) {
        fprintf(stderr, "oom\n");
        return 1;
    }

    mosquitto_connect_callback_set(mosq, on_connect);

    mosquitto_username_pw_set(mosq, "keyi", "keyifamily");
    rc = mosquitto_connect(mosq, "192.168.0.190", 1883, 60);
    if (rc != MOSQ_ERR_SUCCESS) {
        mosquitto_destroy(mosq);
        fprintf(stderr, "Error: %s\n", mosquitto_strerror(rc));
        return 1;
    }

    /* Run the network loop in a background thread, this call returns quickly. */
    rc = mosquitto_loop_start(mosq);
    if (rc != MOSQ_ERR_SUCCESS) {
        mosquitto_destroy(mosq);
        fprintf(stderr, "Error: %s\n", mosquitto_strerror(rc));
        return 1;
    }

    /* here to create threads */
    pthread_t tid_temperature_cpu, tid_battery_percent;

    pthread_create(&tid_temperature_cpu, NULL, publish_sensor_data, mosq);
    pthread_create(&tid_battery_percent, NULL, publish_battery_info, mosq);

    while (true) {
        // publish_sensor_data(mosq);
        // publish_battery_info(mosq);
        sleep(1);
    }

    mosquitto_loop_forever(mosq, -1, 1);
    mosquitto_lib_cleanup();
    return 0;
}

参考资料

mqtt发现服务官方文档 https://www.home-assistant.io/integrations/mqtt/#mqtt-discovery
基于python的mqtt发现服务 https://github.com/leech001/hass-mqtt-discovery

标签:cJSON,Assistant,MQTT,mosquitto,rc,device,Home,mosq,payload
From: https://www.cnblogs.com/hfwz/p/17101973.html

相关文章

  • 整合MQTT
    1、步骤(1)dependencecom.google.code.gsongsonorg.springframework.integrationspring-integration-streamorg.springframework.integrationspring-integration......
  • MQTT协议详解
    MQTT协议详解 MQTT是基于Publish/Subscribe(发布订阅)模式的物联网通信协议特点:简单易实现支持Qos(服务质量)报文小MQTT协议构建于TCP/IP协议之上发布订阅模式:......
  • JAVAHOME的配置--Java基础003
    有时候可能需要更换Jdk的目录,但是经常修改path的值可能会不小心修改其他的路径,解决方法:1、 创建一个JAVA_HOME的变量。 2、 JAVA_HOME的值为JDK的安装目录。3、配置path......
  • PLC利用函数块连接MQTT订阅消息(一)
    在亿佰特介绍了西门子PLC如何通过函数块连接MQTT服务器和发布消息,本文为大家介绍如何通过函数与函数块实现MQTT云消息的订阅,直接切入重点。一、飞燕物联网平台配置这里......
  • PLC利用函数块连接MQTT订阅消息(一)
    在亿佰特介绍了西门子PLC如何通过函数块连接MQTT服务器和发布消息,本文为大家介绍如何通过函数与函数块实现MQTT云消息的订阅,直接切入重点。一、飞燕物联网平台配置这里的配......
  • homeassistant接入rtsp摄像头
    米家摄像头没有开放onvif协议,家里又都是米家的摄像头我全网找了几篇破解刷固件的觉得不可靠又浪费时间这里就不得不提一句估计后续有时间我会用这个方案:https://github......
  • macOS M1安装HomeBrew
    macOSM1安装HomeBrewHomebrew是什么?Homebrew是一款MacOS平台下的软件包管理工具,拥有安装、卸载、更新、查看、搜索等很多实用的功能。简单的一条指令,就可以实现包管理......
  • 运行pm2命令只出现[PM2] Spawning PM2 daemon with pm2_home=/root/.pm2
    小程序上线过程中,pm2安装成功了,但是运行pm2命令只打印一句话root@iZm5e3iekfi2krh6udbikaZ:~/.pm2#pm2-v[PM2]SpawningPM2daemonwithpm2_home=/root/.pm2查看pm2日志......
  • Mac上Homebrew常用命令
    Mac上Homebrew常用命令官方网址https://brew.sh/常用命令安装(官方方法)/bin/bash-c"$(curl-fsSLhttps://raw.githubusercontent.com/Homebrew/install/HEAD/inst......
  • esphome-esp8266
    esp8266使用esphome接入hass对于生成配置文件的更改此处nodemcu泛指集成的开发板,一般十几块钱一块下方使用的是D1,对应的针脚是GPIO5esp8266:board:nodemcuv2......