From bb1eaad3004217b6f38505678d1626ce38d527a1 Mon Sep 17 00:00:00 2001 From: Sean Brogan Date: Fri, 11 Feb 2022 18:48:58 -0800 Subject: [PATCH] Mqtt improvements (#77) and other major features --- docs/configuration.md | 184 ++++++++++++------ docs/docker_deploy.md | 50 +++-- docs/hardware.md | 11 +- docs/mqtt.md | 32 ++- docs/overview.md | 21 +- docs/plugin.md | 4 +- readme.md | 14 +- rvc2mqtt/app.py | 2 +- rvc2mqtt/entity/__init__.py | 5 + rvc2mqtt/entity/{light.py => light_switch.py} | 88 ++++----- rvc2mqtt/entity/tank_level_sensor.py | 33 +++- rvc2mqtt/entity/tank_warmer.py | 23 ++- rvc2mqtt/entity/temperature.py | 21 +- rvc2mqtt/entity/water_heater.py | 118 +++++++---- rvc2mqtt/entity/water_pump.py | 100 +++++++--- rvc2mqtt/mqtt.py | 22 ++- 16 files changed, 464 insertions(+), 264 deletions(-) rename rvc2mqtt/entity/{light.py => light_switch.py} (72%) diff --git a/docs/configuration.md b/docs/configuration.md index 36af423..0a2df21 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -1,17 +1,108 @@ # Configuration -The main idea is that a yaml config file is used as the single parameter for controlling the service +There are three different files used for configuration. These are in yaml format (json is valid yaml). +## Floor plan 1 or 2 -## Example +These two files are both optional but without some floor plan nodes this software doesn't do anything. These files contain a `floorplan` node and then have subnodes with the different devices in your RV. A device should only be defined in one floor plan file. The main reason to allow for +two input files is to easily support a "HA addon" where a main file might exist and then user entered +text from the WebUI might be written to floor plan 2. + +### Example ``` yaml -# -# define the name of the can device -# -interface: - name: can0 +floorplan: + - name: DC_LOAD_STATUS + instance: 1 + type: light_switch + instance_name: bedroom light + + - name: DC_LOAD_STATUS + instance: 2 + type: light_switch + instance_name: living room light + + - name: DC_LOAD_STATUS + instance: 8 + type: light_switch + instance_name: awning light + + - name: THERMOSTAT_AMBIENT_STATUS + instance: 2 + type: temperature + instance_name: bedroom temperature + + - name: TANK_STATUS + instance: 0 + type: tank_level + instance_name: fresh water tank + + - name: TANK_STATUS + instance: 1 + type: tank_level + instance_name: black waste tank + + - name: TANK_STATUS + instance: 2 + type: tank_level + instance_name: rear gray waste tank + + - name: TANK_STATUS + instance: 18 + type: tank_level + instance_name: galley gray waste tank + + - name: TANK_STATUS + instance: 20 + type: tank_level + instance_name: what tank is this 20 + + - name: TANK_STATUS + instance: 21 + type: tank_level + instance_name: what tank is this 21 + + - name: WATER_PUMP_STATUS + type: water_pump + instance_name: fresh water pump + + - name: WATERHEATER_STATUS + type: waterheater + instance: 1 + instance_name: main waterheater + + - name: DC_LOAD_STATUS + type: tank_warmer + instance: 34 + instance_name: waste tank heater + + - name: DC_LOAD_STATUS + type: tank_warmer + instance: 35 + instance_name: fresh water tank heater + +``` + + +## Log Config File + +This is optional and allows for complex logging to be setup. If provided the yaml file needs to follow + + +If not provided the app will do basic docker logging. + + +## Example + +This example setups up 3 log files and a basic logger to console. It assumes you have a volume mapped at +`/config` of the container. + +`RVC2MQTT.log` is a basic INFO level logger for the app +`RVC_FULL_BUS_TRACE.log` will capture all rvc messages (in/out) +`UNHANDLED_RVC.log` will capture all the rvc messages that are not handled by an object. + +``` yaml # # Logging info # See https://docs.python.org/3/library/logging.config.html#logging-config-dictschema @@ -25,77 +116,52 @@ logger: formatter: brief stream: ext://sys.stdout debug_file_handler: - level: DEBUG + level: INFO class: logging.FileHandler formatter: default - filename: rvc_debug.log + filename: /config/RVC2MQTT.log mode: w unhandled_file_handler: level: DEBUG class: logging.FileHandler formatter: time-only - filename: unhandled_rvc.log + filename: /config/UNHANDLED_RVC.log mode: w - loggers: - '': # root logger - handlers: + rvc_bus_trace_handler: + level: DEBUG + class: logging.FileHandler + formatter: time-only + filename: /config/RVC_FULL_BUS_TRACE.log + mode: w + + loggers: + "": # root logger + handlers: - debug_console_handler - debug_file_handler level: DEBUG propagate: False - 'unhandled_rvc': # special logger for messages not handled + + "unhandled_rvc": # unhandled messages handlers: - unhandled_file_handler level: DEBUG propagate: False + + "rvc_bus_trace": # all bus messages + handlers: + - rvc_bus_trace_handler + level: DEBUG + propagate: False + formatters: brief: - format: '%(message)s' + format: "%(message)s" time-only: - format: '%(asctime)s %(message)s' - datefmt: '%Y-%m-%d %H:%M:%S' + format: "%(asctime)s %(message)s" + datefmt: "%d %H:%M:%S" default: - format: '%(asctime)s %(levelname)-8s %(name)-15s %(message)s' - datefmt: '%Y-%m-%d %H:%M:%S' - -# -# External Plugins for device entity -# -plugins: - paths: - - /home/pi/config -# - - -# -# RV - floor plan -# This is a list of which devices are in your RV -# -# name: must be unique -map: - - dgn: 1FFBD - instance: 1 - group: '00000000' - type: light - instance_name: bedroom_light - - dgn: 1FFBD - instance: 2 - group: '00000000' - type: light - instance_name: main_light - - dgn: 1FF9C - instance: 2 - type: temperature - instance_name: bedroom_temp - - name: WATER_PUMP_STATUS - type: water_pump - instance_name: water pump + format: "%(asctime)s %(levelname)-8s %(name)-15s %(message)s" + datefmt: "%Y-%m-%d %H:%M:%S" -# -# MQTT specific settings -# -mqtt: - broker: - username: - password: - ``` diff --git a/docs/docker_deploy.md b/docs/docker_deploy.md index 22fdc42..c06965f 100644 --- a/docs/docker_deploy.md +++ b/docs/docker_deploy.md @@ -1,21 +1,41 @@ # docker Plan is to deploy using docker. -For now you must build your own. -## build it +Builds are available for released version and `main` branch. + -```bash -docker build -t rvc2mqtt . -``` +Image: `ghcr.io/spbrogan/rvc2mqtt:main` + +## Settings and configuration + +The image uses environment variables for all configuration and file paths. + +`CAN_INTERFACE_NAME` : the network can interface name. default value: `can0` + +`FLOORPLAN_FILE_1` : path to the floor plan file. Recommendation is mount a volume from the host with your floor plan + +`FLOORPLAN_FILE_2` : path to the 2nd floor plan file. This is optional but for HA addons this allows UI generated content to be added. + +`LOG_CONFIG_FILE` : path to a logging configuration file. Optional yaml file for more complex logging options. -## config file +`MQTT_HOST` : host url for mqtt server -The image expects a config file located at /config/config.yaml -You should bind a host directory to this with your config.yaml. -It also works well for your log files +`MQTT_PORT` : host port for mqtt server -See configuration.md for a sample config.yaml +`MQTT_USERNAME` : username to connect with + +`MQTT_PASSWORD` : password to connect with + +`MQTT_CLIENT_ID` : mqtt client id and the bridge node name in mqtt path. default is `bridge` + +Optional values if using TLS (not implemented yet!) + +`MQTT_CA` : CA cert for Mqtt server +`MQTT_CERT` : Cert for client +`MQTT_KEY` : key for mqtt + +See configuration.md for a sample files are more details. ## run it @@ -25,16 +45,14 @@ Might need to bring up the can0 interface on host like ```bash sudo ip link set can0 down -sudo ip link set can0 up type can bitrate 250000 +sudo ip link set can0 up type can ``` Then to run the docker image -```bash -docker run --network=host --restart=always -v ~/config:/config rvc2mqtt -``` +you will need to setup a bunch of env variables so the command is pretty length. Sorry. A docker compose file would probably be helpful. -## run in the container yourself +## build it locally ```bash -docker run --network=host --restart=always -v ~/config:/config -it rvc2mqtt bash +docker build -t rvc2mqtt . ``` diff --git a/docs/hardware.md b/docs/hardware.md index 7bac068..5441d4e 100644 --- a/docs/hardware.md +++ b/docs/hardware.md @@ -23,10 +23,9 @@ This has been used on Raspberry Pi OS Lite ### Enable CanBus support in Kernel -* Add waveshare doc and steps here -* +See the waveshare doc here: -Need to update /boot/config.txt to enable rs485 can hat +### Update /boot/config.txt to enable rs485 can hat If you are using HomeAssistantOS you will need to remove the SD card and find the file on the boot partition. @@ -47,16 +46,16 @@ TBD - figure out how to do automatically. Too many different linux network serv Manually this can be done doing ``` bash -sudo ip link set can0 type can bitrate 250000 +sudo ip link set can0 type can sudo ip link set can0 up ``` -### Make a config.yaml file in a folder you can mount as /config for the container +### Make a floorplan.yaml file and/or logging.yaml in a folder you can mount as /config for the container See [configuration.md](configuration.md) -### Build and deploy with docker +### deploy with docker see docker_deploy.md diff --git a/docs/mqtt.md b/docs/mqtt.md index d8c8058..07cb49b 100644 --- a/docs/mqtt.md +++ b/docs/mqtt.md @@ -16,14 +16,7 @@ This schema can support multiple rvc2mqtt bridges using the client-id to provide an isolated namespace. Please make sure this is unique if you have more than one bridge -```yml -mqtt: - broker: : - username: - password: - client-id: #optional default=bridge - -``` +Setting the config for MQTT can be done as command line parameters or thru environment variables. For Docker env is suggested. ## Topic hierarchy @@ -33,21 +26,21 @@ is located at here: `rvc2mqtt/` More specifically: -`rvc2mqtt/` - this reports the connected state of our bridge to the mqtt broker (`online` or `offline`) +`rvc2mqtt//state` - this reports the connected state of our bridge to the mqtt broker (`online` or `offline`) `rvc2mqtt//info` - contains json defined metadata about this bridge and the rvc2mqtt software Devices managed by rvc2mqtt are listed by their unique device id `rvc2mqtt//d/` -### Light +### Light Switch -The Light object is used to describe a light. +The Light Switch object is used to describe an switch. A light can have on / off | Topic | rvc2mqtt operation | Description | |--- | :---: | --- | -|`` | publish | status of light (`on` or `off`) | -|`cmd` | subscribe | command the light with payload `on` or `off` | +|`/state`| publish | status of light (`on` or `off`) | +|`/cmd` | subscribe | command the light with payload `on` or `off` | @@ -60,7 +53,7 @@ It only updates the mqtt topic when the temperature changes. | Topic | rvc2mqtt operation | Description | |--- | :---: | --- | -|`` | publish | temperature in C | +|`/state` | publish | temperature in C | ### HVAC @@ -75,11 +68,12 @@ Home assistant has created mqtt auto-discovery. This describes how rvc2mqtt int with mqtt auto-discovery. -follows path like: //[/]/config - - is the discovery prefix +follows path like: `////config` - will be a bridge this is needed because if two bridges talk to same server there could be conflicting ids +`homeassistant` is the discovery prefix +`component` is one of the home assistant component types +`unique_device_id` is the sensors unique id. This will be a concatination that includes the rvc2mqtt_client-id_object +`entity_id` is the entity id within the device -payload is json that matches HA config (at least all required) +config payload is json that matches HA config (at least all required) diff --git a/docs/overview.md b/docs/overview.md index c85dc11..c88e87b 100644 --- a/docs/overview.md +++ b/docs/overview.md @@ -19,16 +19,16 @@ to make it more manageable and maintainable. The overall application entrypoint that drives the initial setup and configuration as well as the main loop of checking queues for messages and processing. -Configuration - Parse cli parameters (config.yaml file) and use it to setup everything. This includes MQTT client, CANBUS interface, RVC Decoder (using spec file), and user supplied plugins. +Configuration - Parse cli parameters and use it to setup everything. This includes MQTT client, CANBUS interface, RVC Decoder (using spec file), and user supplied plugins. -Entity Mgmt - From the config parse out the `map` which is a description of the sensors in the RV. Then instantiate entities for each entry to deal with state changes and command requests. +Entity Mgmt - From the floor plan files parse out the `floorplan` which is a description of the sensors in the RV. Then instantiate entities for each entry to deal with state changes and command requests. Process CANBUS Rx - The CAN watcher will listen to all messages on the RV CAN Bus and add them to the Rx Queue. The App must: 1. Take a message from queue and decode it to RVC 2. Ask the instantiated entities to process the message. - If of interest the entity will do something with it (ie. Update state, etc) - Else - ignore it so other entities can process - 3. If no entity log it (common) + 3. If no entity log the message. Process CANBUS Tx - The entities may want to send a RVC message. To do this they put a message into the Tx Queue and then the app must: 1. Translate RVC DGN to CANBUS arbitration id @@ -39,12 +39,11 @@ Thats it for the app. ### MQTT Support See [mqtt.md](mqtt.md) for more details about MQTT mapping. -Overall, the process is using `Paho.mqtt Python Library` to create a client. The entities then publish information and subscribe to commands. +Overall, the process is using `paho-mqtt` Python Library to create a client. The entities then publish information and subscribe to commands. ### CAN Watcher -This is a simple class using 'python-can' to support bi-directional communication on the CANBUS. This mostly runs its -own thread and gets and puts messages into the correct queues (rx/tx) +This is a simple class using `python-can` to support bi-directional communication on the CANBUS. This mostly runs its own thread and gets and puts messages into the correct queues (rx/tx) ### RVC Decoder @@ -57,7 +56,7 @@ data as well as friendly parsed and converted data. Plugin support does a few important things. * Load python modules (an entity representing a device) from internal and sources -* Parse `map` config to find the correct entity per entry. +* Parse `floorplan` config to find the correct entity per entry. * Instantiate the entry with the supplied configuration. This model will allow end user customization and extension without requiring modifying the entire projects source code. It also keeps each entity (device) source isolated for easier code readability and maintenance. @@ -75,16 +74,14 @@ The base class for any entity is `EntityPluginBaseClass` and all entities must b * * -### Paho.Mqtt +### Paho-Mqtt * * -### PyYaml +### ruyaml -* -* -* +* diff --git a/docs/plugin.md b/docs/plugin.md index 3186462..4500d88 100644 --- a/docs/plugin.md +++ b/docs/plugin.md @@ -2,7 +2,7 @@ All entities or devices in the system are handled by a plugin. Some plugins will be generally maintained while others can be customized and supplied -in the configuration file independent of the rvc2mqtt code. +in the plugin directory independent of the rvc2mqtt code. At the moment it is unclear how consistent and specification compliant RV-C devices are and this allows greater flexibility while still hopefully creating an easy system. @@ -34,7 +34,7 @@ Don't use relative imports. Due to how it is loaded this doesn't work right. `self.mqtt_support: MQTT_Support` - mqtt_support object used for pub/sub operations -`self.send_queue: queue` - queue used to transmit any RVC can bus messages. Msg must a dictionary and must supply at least the `dgn` string and 8 byte `data` array. +`self.send_queue: queue` - queue used to transmit any RVC can bus messages. Msg must be a dictionary and must supply at least the `dgn` string and 8 byte `data` array. ## Functions diff --git a/readme.md b/readme.md index 2a6455b..9ad8438 100644 --- a/readme.md +++ b/readme.md @@ -3,18 +3,22 @@ This is an experimental project to connect an RV can bus to mqtt with the intent to monitor and control features of your RV. +## Docker Image Available + +`ghcr.io/spbrogan/rvc2mqtt:main` + +See the github repo [__docs__](docs/overview.md) folder + ## Current Status | Host Type | Toolchain | Build Status | Test Status | Code Coverage | | :-------- | :-------- | :----- | :---- | :--- | -| Linux | Python 3.9 | | | | - +| Linux | Python 3.10 | | | | ## License All content in this repository is licensed under [Apache 2 License](LICENSE). - ## Contribution Process This project welcomes all types of contributions. @@ -22,7 +26,3 @@ This project welcomes all types of contributions. - Question - Ask it in the github discussion forum - Code change - Open an Issue describing your change and then submit a PR. - See docs for more details - -## Documentation - -See the github repo [__docs__](docs/overview.md) folder \ No newline at end of file diff --git a/rvc2mqtt/app.py b/rvc2mqtt/app.py index e79dc7c..952a1f0 100644 --- a/rvc2mqtt/app.py +++ b/rvc2mqtt/app.py @@ -227,7 +227,7 @@ def main(): # optional settings parser.add_argument("--MQTT_CLIENT_ID", "--mqtt_client_id", dest="mqtt_client_id", - help="client id for mqtt", default=os.environ.get("MQTT_CLIENT_ID")) + help="client id for mqtt", default=os.environ.get("MQTT_CLIENT_ID", "bridge")) parser.add_argument("--MQTT_CA", "--mqtt_ca", dest="mqtt_ca", help="ca for mqtt", default=os.environ.get("MQTT_CA")) parser.add_argument("--MQTT_CERT", "--mqtt_cert", dest="mqtt_cert", diff --git a/rvc2mqtt/entity/__init__.py b/rvc2mqtt/entity/__init__.py index 809ee18..61d6c44 100644 --- a/rvc2mqtt/entity/__init__.py +++ b/rvc2mqtt/entity/__init__.py @@ -40,6 +40,7 @@ def __init__(self, data:dict, mqtt_support: MQTT_Support): # Make the required one status/state topic self.status_topic: str = mqtt_support.make_device_topic_string(self.id, None, True) + self.unique_device_id = mqtt_support.TOPIC_BASE + "_" + mqtt_support.client_id + "_" + self.id def process_rvc_msg(self, new_message: dict) -> bool: """ Process an incoming rvc message and determine if it @@ -87,3 +88,7 @@ def set_rvc_send_queue(self, send_queue: queue): items be formatted as python-can messages""" self.send_queue: queue = send_queue + def get_availability_discovery_info_for_ha(self) -> dict: + """ return the availability fields in dict format""" + return { "availability_topic": self.mqtt_support.bridge_state_topic } + diff --git a/rvc2mqtt/entity/light.py b/rvc2mqtt/entity/light_switch.py similarity index 72% rename from rvc2mqtt/entity/light.py rename to rvc2mqtt/entity/light_switch.py index 0345245..7feabc7 100644 --- a/rvc2mqtt/entity/light.py +++ b/rvc2mqtt/entity/light_switch.py @@ -1,5 +1,5 @@ """ -A light +A light switch Copyright 2022 Sean Brogan SPDX-License-Identifier: Apache-2.0 @@ -27,63 +27,50 @@ from rvc2mqtt.entity import EntityPluginBaseClass -class LightBaseClass(EntityPluginBaseClass): - LIGHT_ON = "on" - LIGHT_OFF = "off" - - def __init__(self, data: dict, mqtt_support: MQTT_Support): - super().__init__(data, mqtt_support) - self.Logger = logging.getLogger(__class__.__name__) - - # Allow MQTT to control light - self.command_topic = mqtt_support.make_device_topic_string( - self.id, None, False) - self.mqtt_support.register(self.command_topic, self.process_mqtt_msg) - - def process_rvc_msg(self, new_message: dict) -> bool: - """ Process an incoming message and determine if it - is of interest to this object. - - If relevant - Process the message and return True - else - return False - """ - raise NotImplementedError() - - def process_mqtt_msg(self, topic, payload): - self.Logger.error(f"Incomplete handler MQTT Message {topic} {payload}") - - -class Light_FromDGN_1FFBD(LightBaseClass): - FACTORY_MATCH_ATTRIBUTES = {"dgn": "1FFBD", "type": "light"} +class LightSwitch_DC_LOAD_STATUS(EntityPluginBaseClass): + FACTORY_MATCH_ATTRIBUTES = {"name": "DC_LOAD_STATUS", "type": "light_switch"} """ - Subclass of light that is tied to RVC DGN of DC_LOAD_STATUS and DC_LOAD_COMMAND + Light switch that is tied to RVC DGN of DC_LOAD_STATUS and DC_LOAD_COMMAND Supports ON/OFF TODO: can it support brightness """ + LIGHT_ON = "on" + LIGHT_OFF = "off" def __init__(self, data: dict, mqtt_support: MQTT_Support): self.id = "light-1FFBD-i" + str(data["instance"]) - super().__init__(data, mqtt_support) self.Logger = logging.getLogger(__class__.__name__) + # Allow MQTT to control light + self.command_topic = mqtt_support.make_device_topic_string( + self.id, None, False) + self.mqtt_support.register(self.command_topic, self.process_mqtt_msg) + # RVC message must match the following to be this device - self.rvc_match_status = { - "dgn": "1FFBD", "instance": data['instance']} - self.rvc_match_command= { "dgn": "1FFBC", "instance": data['instance']} + self.rvc_match_status = { "name": "DC_LOAD_STATUS", "instance": data['instance']} + self.rvc_match_command= { "name": "DC_LOAD_COMMAND", "instance": data['instance']} - self.Logger.debug(f"Must match: {str(self.rvc_match_status)}") - # ignore for now self.rvc_match_command = {"dgn": "1FFBC", "instance": data['instance'], "group": data['group'] } + self.Logger.debug(f"Must match: {str(self.rvc_match_status)} or {str(self.rvc_match_command)}") # save these for later to send rvc msg self.rvc_instance = data['instance'] - self.rvc_group = data['group'] + self.rvc_group = '00000000' + if 'group' in data: + self.rvc_group = data['group'] self.name = data['instance_name'] self.state = "unknown" + self.device = {"manufacturer": "RV-C", + "via_device": self.mqtt_support.get_bridge_ha_name(), + "identifiers": self.unique_device_id, + "name": self.name, + "model": "RV-C Light from DC_LOAD_STATUS" + } + def process_rvc_msg(self, new_message: dict) -> bool: """ Process an incoming message and determine if it is of interest to this object. @@ -95,9 +82,9 @@ def process_rvc_msg(self, new_message: dict) -> bool: if self._is_entry_match(self.rvc_match_status, new_message): self.Logger.debug(f"Msg Match Status: {str(new_message)}") if new_message["operating_status"] == 100.0: - self.state = LightBaseClass.LIGHT_ON + self.state = LightSwitch_DC_LOAD_STATUS.LIGHT_ON elif new_message["operating_status"] == 0.0: - self.state = LightBaseClass.LIGHT_OFF + self.state = LightSwitch_DC_LOAD_STATUS.LIGHT_OFF else: self.state = "UNEXPECTED(" + \ str(new_message["operating_status"]) + ")" @@ -120,11 +107,11 @@ def process_mqtt_msg(self, topic, payload): f"MQTT Msg Received on topic {topic} with payload {payload}") if topic == self.command_topic: - if payload.lower() == LightBaseClass.LIGHT_OFF: - if self.state != LightBaseClass.LIGHT_OFF: + if payload.lower() == LightSwitch_DC_LOAD_STATUS.LIGHT_OFF: + if self.state != LightSwitch_DC_LOAD_STATUS.LIGHT_OFF: self._rvc_light_off() - elif payload.lower() == LightBaseClass.LIGHT_ON: - if self.state != LightBaseClass.LIGHT_ON: + elif payload.lower() == LightSwitch_DC_LOAD_STATUS.LIGHT_ON: + if self.state != LightSwitch_DC_LOAD_STATUS.LIGHT_ON: self._rvc_light_on() else: self.Logger.warning( @@ -156,14 +143,21 @@ def initialize(self): """ # produce the HA MQTT discovery config json - config = {"name": self.name, "state_topic": self.status_topic, - "command_topic": self.command_topic, "qos": 1, "retain": False, - "payload_on": LightBaseClass.LIGHT_ON, "payload_off": LightBaseClass.LIGHT_OFF} + config = {"name": self.name, + "state_topic": self.status_topic, + "command_topic": self.command_topic, + "qos": 1, "retain": False, + "payload_on": LightSwitch_DC_LOAD_STATUS.LIGHT_ON, + "payload_off": LightSwitch_DC_LOAD_STATUS.LIGHT_OFF, + "unique_id": self.unique_device_id, + "device": self.device} + + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "switch") + self.unique_device_id, "switch") # publish info to mqtt self.mqtt_support.client.publish( diff --git a/rvc2mqtt/entity/tank_level_sensor.py b/rvc2mqtt/entity/tank_level_sensor.py index 9f1105c..83b16c8 100644 --- a/rvc2mqtt/entity/tank_level_sensor.py +++ b/rvc2mqtt/entity/tank_level_sensor.py @@ -25,8 +25,8 @@ from rvc2mqtt.mqtt import MQTT_Support from rvc2mqtt.entity import EntityPluginBaseClass -class TankLevelSensor_FromDGN_1FFB7(EntityPluginBaseClass): - FACTORY_MATCH_ATTRIBUTES = {"type": "tank_level", "dgn": "1FFB7"} +class TankLevelSensor_TANK_STATUS(EntityPluginBaseClass): + FACTORY_MATCH_ATTRIBUTES = {"type": "tank_level", "name": "TANK_STATUS"} """ Provide basic tank level values using DGN TANK_STATUS @@ -38,16 +38,25 @@ def __init__(self, data: dict, mqtt_support: MQTT_Support): self.Logger = logging.getLogger(__class__.__name__) # RVC message must match the following to be this device - self.rvc_match_status = {"dgn": "1FFB7", "instance": data['instance']} - self.level = 100 # should never get this hot in C + self.rvc_match_status = {"name": "TANK_STATUS", "instance": data['instance']} + self.level = 100 self.Logger.debug(f"Must match: {str(self.rvc_match_status)}") self.name = data['instance_name'] self.instance = data['instance'] self.instance_name = self._get_instance_name(self.instance) + self.device = {"manufacturer": "RV-C", + "via_device": self.mqtt_support.get_bridge_ha_name(), + "identifiers": self.unique_device_id, + "name": self.name, + "model": "RV-C Tank from TANK_STATUS" + } + self.waiting_for_first_msg = True + + def process_rvc_msg(self, new_message: dict) -> bool: """ Process an incoming message and determine if it is of interest to this object. @@ -69,8 +78,10 @@ def process_rvc_msg(self, new_message: dict) -> bool: # mark first msg sent self.waiting_for_first_msg = False - if new_message["relative_level"] != self.level: - self.level = new_message["relative_level"] / self.resolution + + new_level = new_message["relative_level"] / self.resolution + if new_level != self.level: + self.level = new_level self.mqtt_support.client.publish( self.status_topic, self.level, retain=True) return True @@ -95,16 +106,20 @@ def initialize(self): def _send_ha_mqtt_discovery_info(self): # produce the HA MQTT discovery config json - config = {"name": self.name, "state_topic": self.status_topic, + config = {"name": self.name, + "state_topic": self.status_topic, "qos": 1, "retain": False, "unit_of_meas": 'percentage', "state_class": "measurement", - "value_template": '{{value}}'} + "value_template": '{{value}}', + "unique_id": self.unique_device_id, + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "sensor") + self.unique_device_id, "sensor") # publish info to mqtt self.mqtt_support.client.publish( diff --git a/rvc2mqtt/entity/tank_warmer.py b/rvc2mqtt/entity/tank_warmer.py index 1abec44..9afd072 100644 --- a/rvc2mqtt/entity/tank_warmer.py +++ b/rvc2mqtt/entity/tank_warmer.py @@ -51,8 +51,13 @@ def __init__(self, data: dict, mqtt_support: MQTT_Support): self.rvc_instance = data['instance'] self.name = data['instance_name'] self.state = "unknown" - super().__init__(data, mqtt_support) - self.Logger = logging.getLogger(__class__.__name__) + + self.device = {"manufacturer": "RV-C", + "via_device": self.mqtt_support.get_bridge_ha_name(), + "identifiers": self.unique_device_id, + "name": self.name, + "model": "RV-C Tank Warner from DC_LOAD_STATUS" + } # Allow MQTT to control heater self.command_topic = mqtt_support.make_device_topic_string( @@ -164,14 +169,20 @@ def initialize(self): """ # produce the HA MQTT discovery config json - config = {"name": self.name, "state_topic": self.status_topic, - "command_topic": self.command_topic, "qos": 1, "retain": False, - "payload_on": TankWarmer_DC_LOAD_STATUS.ON, "payload_off": TankWarmer_DC_LOAD_STATUS.OFF} + config = {"name": self.name, + "state_topic": self.status_topic, + "command_topic": self.command_topic, + "qos": 1, "retain": False, + "payload_on": TankWarmer_DC_LOAD_STATUS.ON, + "payload_off": TankWarmer_DC_LOAD_STATUS.OFF, + "unique_id": self.unique_device_id, + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "switch") + self.unique_device_id, "switch") # publish info to mqtt self.mqtt_support.client.publish( diff --git a/rvc2mqtt/entity/temperature.py b/rvc2mqtt/entity/temperature.py index 62da129..a424aca 100644 --- a/rvc2mqtt/entity/temperature.py +++ b/rvc2mqtt/entity/temperature.py @@ -25,10 +25,10 @@ from rvc2mqtt.entity import EntityPluginBaseClass -class Temperature_FromDGN_1FF9C(EntityPluginBaseClass): - FACTORY_MATCH_ATTRIBUTES = {"type": "temperature", "dgn": "1FF9C"} +class TemperatureSensor_THERMOSTAT_AMBIENT_STATUS(EntityPluginBaseClass): + FACTORY_MATCH_ATTRIBUTES = {"type": "temperature", "name": "THERMOSTAT_AMBIENT_STATUS"} - """ Provide basic temperature values using DGN THERMOSTAT_AMBIENT_STATUS + """ Provide basic temperature values using THERMOSTAT_AMBIENT_STATUS """ @@ -38,11 +38,17 @@ def __init__(self, data: dict, mqtt_support: MQTT_Support): self.Logger = logging.getLogger(__class__.__name__) # RVC message must match the following to be this device - self.rvc_match_status = {"dgn": "1FF9C", "instance": data['instance']} + self.rvc_match_status = {"name": "THERMOSTAT_AMBIENT_STATUS", "instance": data['instance']} self.reported_temp = 100 # should never get this hot in C self.Logger.debug(f"Must match: {str(self.rvc_match_status)}") self.name = data['instance_name'] + self.device = {"manufacturer": "RV-C", + "via_device": self.mqtt_support.get_bridge_ha_name(), + "identifiers": self.unique_device_id, + "name": self.name, + "model": "RV-C Temperature Sensor from THERMOSTAT_AMBIENT_STATUS" + } def process_rvc_msg(self, new_message: dict) -> bool: """ Process an incoming message and determine if it @@ -78,12 +84,15 @@ def initialize(self): "unit_of_meas": '°C', "device_class": "temperature", "state_class": "measurement", - "value_template": '{{value}}'} + "value_template": '{{value}}', + "unique_id": self.unique_device_id, + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "sensor") + self.unique_device_id, "sensor") # publish info to mqtt self.mqtt_support.client.publish( diff --git a/rvc2mqtt/entity/water_heater.py b/rvc2mqtt/entity/water_heater.py index ca3f620..529fc5d 100644 --- a/rvc2mqtt/entity/water_heater.py +++ b/rvc2mqtt/entity/water_heater.py @@ -103,6 +103,13 @@ def __init__(self, data: dict, mqtt_support: MQTT_Support): self.failure_dc_power = "unknown" # RO mqtt and RVC (power present, power not present) self.failure_dc_warning = "unknown" # RO mqtt and RVC (power ok, power low) + self.device = {"manufacturer": "RV-C", + "via_device": self.mqtt_support.get_bridge_ha_name(), + "identifiers": self.unique_device_id, + "name": self.name, + "model": "RV-C Water Heater from WATERHEATER_STATUS" + } + # Allow MQTT to control gas - on off self.status_gas_topic = mqtt_support.make_device_topic_string(self.id, "gas", True) self.command_gas_topic = mqtt_support.make_device_topic_string(self.id, "gas", False) @@ -139,6 +146,8 @@ def __init__(self, data: dict, mqtt_support: MQTT_Support): self.status_failure_low_dc_topic = mqtt_support.make_device_topic_string(self.id, "failure_low_dc", True) + + def process_rvc_msg(self, new_message: dict) -> bool: """ Process an incoming message and determine if it is of interest to this object. @@ -342,14 +351,20 @@ def initialize(self): """ # Gas switch - produce the HA MQTT discovery config json for - config = {"name": self.name + " Gas", "state_topic": self.status_gas_topic, - "command_topic": self.command_gas_topic, "qos": 1, "retain": False, - "payload_on": WaterHeaterClass.ON, "payload_off": WaterHeaterClass.OFF} + config = {"name": self.name + " Gas", + "state_topic": self.status_gas_topic, + "command_topic": self.command_gas_topic, + "qos": 1, "retain": False, + "payload_on": WaterHeaterClass.ON, + "payload_off": WaterHeaterClass.OFF, + "unique_id": self.unique_device_id + "_gas_mode", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "switch", "gas_mode") + self.unique_device_id, "switch", "gas_mode") # publish info to mqtt self.mqtt_support.client.publish( @@ -360,12 +375,15 @@ def initialize(self): # AC element switch - produce the HA MQTT discovery config json for config = {"name": self.name + " AC", "state_topic": self.status_ac_topic, "command_topic": self.command_ac_topic, "qos": 1, "retain": False, - "payload_on": WaterHeaterClass.ON, "payload_off": WaterHeaterClass.OFF} + "payload_on": WaterHeaterClass.ON, "payload_off": WaterHeaterClass.OFF, + "unique_id": self.unique_device_id + "_electric_mode", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "switch", "electric_mode") + self.unique_device_id, "switch", "electric_mode") # publish info to mqtt self.mqtt_support.client.publish( @@ -379,12 +397,15 @@ def initialize(self): "unit_of_meas": '°C', "device_class": "temperature", "state_class": "measurement", - "value_template": '{{value}}'} + "value_template": '{{value}}', + "unique_id": self.unique_device_id + "_set_point_temperature", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "number", "set_point_temperature") + self.unique_device_id, "number", "set_point_temperature") # publish info to mqtt self.mqtt_support.client.publish( @@ -394,17 +415,20 @@ def initialize(self): # Water Temperature sensor - produce the HA MQTT discovery config json for - config = {"name": self.name + " water temperature", "state_topic": self.status_water_temp_topic, + config = {"name": self.name + " Water Temperature", "state_topic": self.status_water_temp_topic, "qos": 1, "retain": False, "unit_of_meas": '°C', "device_class": "temperature", "state_class": "measurement", - "value_template": '{{value}}'} + "value_template": '{{value}}', + "unique_id": self.unique_device_id + "_water_temperature", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "sensor", "water-temperature") + self.unique_device_id, "sensor", "water_temperature") # publish info to mqtt self.mqtt_support.client.publish( @@ -414,14 +438,17 @@ def initialize(self): # thermostate status binary sensor - produce the HA MQTT discovery config json for - config = {"name": self.name + " thermostat status", "state_topic": self.status_thermostat_topic, + config = {"name": self.name + " Thermostat Status", "state_topic": self.status_thermostat_topic, "qos": 1, "retain": False, - "payload_on": WaterHeaterClass.ON, "payload_off": WaterHeaterClass.OFF} + "payload_on": WaterHeaterClass.ON, "payload_off": WaterHeaterClass.OFF, + "unique_id": self.unique_device_id + "_thermostat", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "binary_sensor", "thermostat") + self.unique_device_id, "binary_sensor", "thermostat") # publish info to mqtt self.mqtt_support.client.publish( @@ -431,15 +458,18 @@ def initialize(self): # Gas Burner Status binary sensor - produce the HA MQTT discovery config json for - config = {"name": self.name + " gas burner" , "state_topic": self.status_gas_burner_topic, + config = {"name": self.name + " Gas Burner" , "state_topic": self.status_gas_burner_topic, "qos": 1, "retain": False, "payload_on": WaterHeaterClass.ON, - "payload_off": WaterHeaterClass.OFF} + "payload_off": WaterHeaterClass.OFF, + "unique_id": self.unique_device_id + "_gas_burner_status", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "binary_sensor", "gbs") + self.unique_device_id, "binary_sensor", "gas_burner_status") # publish info to mqtt self.mqtt_support.client.publish( @@ -448,15 +478,18 @@ def initialize(self): self.status_gas_burner_topic, self.burner_status, retain=True) # AC Element Status binary sensor - produce the HA MQTT discovery config json for - config = {"name": self.name + " ac element" , "state_topic": self.status_ac_element_topic, + config = {"name": self.name + " AC Element" , "state_topic": self.status_ac_element_topic, "qos": 1, "retain": False, "payload_on": WaterHeaterClass.ON, - "payload_off": WaterHeaterClass.OFF} + "payload_off": WaterHeaterClass.OFF, + "unique_id": self.unique_device_id + "_ac_element_status", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "binary_sensor", "ace") + self.unique_device_id, "binary_sensor", "ac_element_status") # publish info to mqtt self.mqtt_support.client.publish( @@ -465,15 +498,18 @@ def initialize(self): self.status_ac_element_topic, self.ac_element_status, retain=True) # High temp limit switch Status binary sensor - produce the HA MQTT discovery config json for - config = {"name": self.name + " high temp limit" , "state_topic": self.status_high_temp_topic, + config = {"name": self.name + " High-Temp Limit" , "state_topic": self.status_high_temp_topic, "qos": 1, "retain": False, "payload_on": WaterHeaterClass.ON, - "payload_off": WaterHeaterClass.OFF} + "payload_off": WaterHeaterClass.OFF, + "unique_id": self.unique_device_id + "_high_temp_limit_switch_status", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "binary_sensor", "htl") + self.unique_device_id, "binary_sensor", "high_temp_limit_switch_status") # publish info to mqtt self.mqtt_support.client.publish( @@ -482,15 +518,18 @@ def initialize(self): self.status_high_temp_topic, self.high_temp_switch_status, retain=True) # Failure to ignite Status binary sensor - produce the HA MQTT discovery config json for - config = {"name": self.name + "gas failure" , "state_topic": self.status_failure_gas_topic, + config = {"name": self.name + " Gas Igniter Failure" , "state_topic": self.status_failure_gas_topic, "qos": 1, "retain": False, "payload_on": WaterHeaterClass.ON, - "payload_off": WaterHeaterClass.OFF} + "payload_off": WaterHeaterClass.OFF, + "unique_id": self.unique_device_id + "_failure_to_ignite_status", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "binary_sensor", "fgi") + self.unique_device_id, "binary_sensor", "failure_to_ignite_status") # publish info to mqtt self.mqtt_support.client.publish( @@ -499,15 +538,18 @@ def initialize(self): self.status_failure_gas_topic, self.failure_to_ignite, retain=True) # Failure AC Power Status binary sensor - produce the HA MQTT discovery config json for - config = {"name": self.name + "AC Power failure" , "state_topic": self.status_failure_ac_topic, + config = {"name": self.name + " AC Power Failure" , "state_topic": self.status_failure_ac_topic, "qos": 1, "retain": False, "payload_on": WaterHeaterClass.ON, - "payload_off": WaterHeaterClass.OFF} + "payload_off": WaterHeaterClass.OFF, + "unique_id": self.unique_device_id + "_failure_ac_power_status", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "binary_sensor", "facp") + self.unique_device_id, "binary_sensor", "failure_ac_power_status") # publish info to mqtt self.mqtt_support.client.publish( @@ -516,15 +558,18 @@ def initialize(self): self.status_failure_ac_topic, self.failure_ac_power, retain=True) # Failure DC Power Status binary sensor - produce the HA MQTT discovery config json for - config = {"name": self.name + "DC Power failure" , "state_topic": self.status_failure_dc_topic, + config = {"name": self.name + " DC Power Failure" , "state_topic": self.status_failure_dc_topic, "qos": 1, "retain": False, "payload_on": WaterHeaterClass.ON, - "payload_off": WaterHeaterClass.OFF} + "payload_off": WaterHeaterClass.OFF, + "unique_id": self.unique_device_id + "_failure_dc_power_status", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "binary_sensor", "fdcp") + self.unique_device_id, "binary_sensor", "failure_dc_power_status") # publish info to mqtt self.mqtt_support.client.publish( @@ -533,15 +578,18 @@ def initialize(self): self.status_failure_dc_topic, self.failure_dc_power, retain=True) # Failure DC Power warning Status binary sensor - produce the HA MQTT discovery config json for - config = {"name": self.name + "DC Low Power failure" , "state_topic": self.status_failure_low_dc_topic, + config = {"name": self.name + " DC Low Power Warning" , "state_topic": self.status_failure_low_dc_topic, "qos": 1, "retain": False, "payload_on": WaterHeaterClass.ON, - "payload_off": WaterHeaterClass.OFF} + "payload_off": WaterHeaterClass.OFF, + "unique_id": self.unique_device_id + "_failure_dc_power_warning_status", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "binary_sensor", "fdclp") + self.unique_device_id, "binary_sensor", "failure_dc_power_warning_status") # publish info to mqtt self.mqtt_support.client.publish( diff --git a/rvc2mqtt/entity/water_pump.py b/rvc2mqtt/entity/water_pump.py index f8d3b83..c96ae33 100644 --- a/rvc2mqtt/entity/water_pump.py +++ b/rvc2mqtt/entity/water_pump.py @@ -42,12 +42,14 @@ 'regulator_pressure_setting': 0, 'operating_current': 0} ''' + class WaterPumpClass(EntityPluginBaseClass): ''' Water pump switch, status, and pressure sensors based on the Water Pump Status / Command RV-C DGN ''' - FACTORY_MATCH_ATTRIBUTES = {"name": "WATER_PUMP_STATUS", "type": "water_pump"} + FACTORY_MATCH_ATTRIBUTES = { + "name": "WATER_PUMP_STATUS", "type": "water_pump"} ON = "on" OFF = "off" OUTSIDE_WATER_CONNECTED = "connected" @@ -63,22 +65,35 @@ def __init__(self, data: dict, mqtt_support: MQTT_Support): self.rvc_match_status = {"name": "WATER_PUMP_STATUS"} self.rvc_match_command = {"name": "WATER_PUMP_COMMAND"} - self.Logger.debug(f"Must match: {str(self.rvc_match_status)} {str(self.rvc_match_command)}") - + self.Logger.debug( + f"Must match: {str(self.rvc_match_status)} {str(self.rvc_match_command)}") + # fields for a water pump object self.name = data["instance_name"] self.power_state = "unknown" # R/W mqtt and RVC - self.running_state = "unknown" # RO mqtt and RVC - self.external_water_hookup = "unknown" # RO mqtt and RVC - self.system_pressure = 0.0 # RO mqtt and RVC (this is configurable but ignore for now as i don't think my trailer supports it) - - # Allow MQTT to control power - self.command_topic = mqtt_support.make_device_topic_string(self.id, None, False) - self.running_status_topic = mqtt_support.make_device_topic_string(self.id, "running", True) - self.external_water_status_topic = mqtt_support.make_device_topic_string(self.id, "external_water", True) - self.system_pressure_status_topic = mqtt_support.make_device_topic_string(self.id, "system_pressure", True) + self.running_state = "unknown" # RO mqtt and RVC + self.external_water_hookup = "unknown" # RO mqtt and RVC + # RO mqtt and RVC (this is configurable but ignore for now as i don't think my trailer supports it) + self.system_pressure = 0.0 + + # Allow MQTT to control power + self.command_topic = mqtt_support.make_device_topic_string( + self.id, None, False) + self.running_status_topic = mqtt_support.make_device_topic_string( + self.id, "running", True) + self.external_water_status_topic = mqtt_support.make_device_topic_string( + self.id, "external_water", True) + self.system_pressure_status_topic = mqtt_support.make_device_topic_string( + self.id, "system_pressure", True) self.mqtt_support.register(self.command_topic, self.process_mqtt_msg) + self.device = {"manufacturer": "RV-C", + "via_device": self.mqtt_support.get_bridge_ha_name(), + "identifiers": self.unique_device_id, + "name": self.name, + "model": "RV-C Waterpump from WATER_PUMP_STATUS" + } + def process_rvc_msg(self, new_message: dict) -> bool: """ Process an incoming message and determine if it is of interest to this object. @@ -101,7 +116,8 @@ def process_rvc_msg(self, new_message: dict) -> bool: self.Logger.error( f"Unexpected RVC value {str(new_message['operating_status'])}") - self.mqtt_support.client.publish(self.status_topic, self.power_state, retain=True) + self.mqtt_support.client.publish( + self.status_topic, self.power_state, retain=True) # Running State if new_message["pump_status"] == "01": @@ -114,7 +130,8 @@ def process_rvc_msg(self, new_message: dict) -> bool: self.Logger.error( f"Unexpected RVC value {str(new_message['pump_status'])}") - self.mqtt_support.client.publish(self.running_status_topic, self.running_state, retain=True) + self.mqtt_support.client.publish( + self.running_status_topic, self.running_state, retain=True) # External Water Hookup State if new_message["water_hookup_detected"] == "01": @@ -127,11 +144,13 @@ def process_rvc_msg(self, new_message: dict) -> bool: self.Logger.error( f"Unexpected RVC value {str(new_message['water_hookup_detected'])}") - self.mqtt_support.client.publish(self.external_water_status_topic, self.external_water_hookup, retain=True) + self.mqtt_support.client.publish( + self.external_water_status_topic, self.external_water_hookup, retain=True) # System Pressure self.system_pressure = new_message['current_system_pressure'] - self.mqtt_support.client.publish(self.system_pressure_status_topic, self.system_pressure, retain=True) + self.mqtt_support.client.publish( + self.system_pressure_status_topic, self.system_pressure, retain=True) return True @@ -144,8 +163,9 @@ def process_rvc_msg(self, new_message: dict) -> bool: def process_mqtt_msg(self, topic, payload): """ mqtt message to turn on or off the power switch for the pump""" - - self.Logger.debug(f"MQTT Msg Received on topic {topic} with payload {payload}") + + self.Logger.debug( + f"MQTT Msg Received on topic {topic} with payload {payload}") if topic == self.command_topic: if payload.lower() == WaterPumpClass.OFF: @@ -181,14 +201,19 @@ def initialize(self): """ # power state switch - produce the HA MQTT discovery config json for - config = {"name": self.name + " power", "state_topic": self.status_topic, + config = {"name": self.name + " power", + "state_topic": self.status_topic, "command_topic": self.command_topic, "qos": 1, "retain": False, - "payload_on": WaterPumpClass.ON, "payload_off": WaterPumpClass.OFF} + "payload_on": WaterPumpClass.ON, + "payload_off": WaterPumpClass.OFF, + "unique_id": self.unique_device_id + "_power", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "switch", "power") + self.unique_device_id, "switch", "power") # publish info to mqtt self.mqtt_support.client.publish( @@ -197,14 +222,19 @@ def initialize(self): self.status_topic, self.power_state, retain=True) # running state binary sensor - produce the HA MQTT discovery config json for - config = {"name": self.name + " running status", "state_topic": self.running_status_topic, + config = {"name": self.name + " running status", + "state_topic": self.running_status_topic, "qos": 1, "retain": False, - "payload_on": WaterPumpClass.ON, "payload_off": WaterPumpClass.OFF} + "payload_on": WaterPumpClass.ON, + "payload_off": WaterPumpClass.OFF, + "unique_id": self.unique_device_id + "_running", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "binary_sensor", "rs") + self.unique_device_id, "binary_sensor", "running") # publish info to mqtt self.mqtt_support.client.publish( @@ -213,15 +243,19 @@ def initialize(self): self.running_status_topic, self.running_state, retain=True) # External Water Connected binary sensor - produce the HA MQTT discovery config json for - config = {"name": self.name + " external water" , "state_topic": self.external_water_status_topic, + config = {"name": self.name + " external water", + "state_topic": self.external_water_status_topic, "qos": 1, "retain": False, "payload_on": WaterPumpClass.OUTSIDE_WATER_CONNECTED, - "payload_off": WaterPumpClass.OUTSIDE_WATER_DISCONNECTED} + "payload_off": WaterPumpClass.OUTSIDE_WATER_DISCONNECTED, + "unique_id": self.unique_device_id + "_external_water", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "binary_sensor", "ew") + self.unique_device_id, "binary_sensor", "external_water") # publish info to mqtt self.mqtt_support.client.publish( @@ -230,17 +264,21 @@ def initialize(self): self.external_water_status_topic, self.external_water_hookup, retain=True) # System Pressure sensor - produce the HA MQTT discovery config json for - config = {"name": self.name + " system pressure", "state_topic": self.system_pressure_status_topic, + config = {"name": self.name + " system pressure", + "state_topic": self.system_pressure_status_topic, "qos": 1, "retain": False, - "unit_of_meas": 'Pa', + "unit_of_meas": 'Pa', "device_class": "pressure", "state_class": "measurement", - "value_template": '{{value}}'} + "value_template": '{{value}}', + "unique_id": self.unique_device_id + "_system_pressure", + "device": self.device} + config.update(self.get_availability_discovery_info_for_ha()) config_json = json.dumps(config) ha_config_topic = self.mqtt_support.make_ha_auto_discovery_config_topic( - self.id, "sensor", "sp") + self.unique_device_id, "sensor", "system_pressure") # publish info to mqtt self.mqtt_support.client.publish( diff --git a/rvc2mqtt/mqtt.py b/rvc2mqtt/mqtt.py index 9621905..e9eb838 100644 --- a/rvc2mqtt/mqtt.py +++ b/rvc2mqtt/mqtt.py @@ -85,15 +85,17 @@ def make_device_topic_string(self, id: str, field:str, state:bool) -> str: if field is not None: s += "/" + self._prepare_topic_string_node(field) - if not state: + if state: + s += "/state" + else: s += "/set" return s def make_ha_auto_discovery_config_topic(self, id: str, ha_component: str, sub_type: str = None) -> str: """ make a config topic string for a Home Assistant auto discovery config""" - topic = MQTT_Support.HA_AUTO_BASE + "/" + ha_component + "/" + self.client_id + "/" + id + topic = MQTT_Support.HA_AUTO_BASE + "/" + ha_component + "/" + self._prepare_topic_string_node(id) if sub_type is not None: - topic += "-" + sub_type + topic += "/" + self._prepare_topic_string_node(sub_type) return topic + "/config" @@ -106,6 +108,12 @@ def _prepare_topic_string_node(self, input:str) -> str: """ return input.translate(input.maketrans(" /", "__", "()")).lower() + def get_bridge_ha_name(self) -> str: + """ return a string that is used to identify the bridge HA as a device.""" + return self._prepare_topic_string_node(self.root_topic) + + + def shutdown(self): """ shutdown. Tell server we are going offline""" self.client.publish(self.bridge_state_topic, "offline", retain=True) @@ -123,24 +131,22 @@ def on_mqtt_subscribe(client, userdata, mid, granted_qos): def on_mqtt_message(client, userdata, msg): gMQTTObj.on_message(client, userdata, msg) -def MqttInitalize(host, port, user, password, client_id): +def MqttInitalize(host:str, port:str, user:str, password:str, client_id:str): """ main function to parse config and initialize the mqtt client. """ global gMQTTObj - client_id = "bridge" - if client_id is not None: - client_id = client_id gMQTTObj = MQTT_Support(client_id) port = int(port) - mqttc = mqc.Client() + mqttc = mqc.Client(client_id=client_id) gMQTTObj.set_client(mqttc) mqttc.on_connect = on_mqtt_connect mqttc.on_subscribe = on_mqtt_subscribe mqttc.on_message = on_mqtt_message mqttc.username_pw_set(user, password) + try: logging.getLogger(__name__).info(f"Connecting to MQTT broker {host}:{port}")