Skip to content

Commit

Permalink
Availability working
Browse files Browse the repository at this point in the history
  • Loading branch information
roopesh committed Apr 18, 2021
1 parent cdfc76c commit 2f1e026
Show file tree
Hide file tree
Showing 5 changed files with 114 additions and 22 deletions.
6 changes: 2 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ Fully self-contained AppDaemon app. If you have HA MQTT Discovery turned on, yo

You’ll also have an alarm_control_panel for each partition. If you use the alarm panel component in HA, you don't have to worry about sending commands to the panel. It'll all be auto-magiced with MQTT discovery.

Utilizes the MQTT plugin's `will_topic` to detect if AppDaemon is offline. In order for this to work, MQTT plugin's `will_topic` and `birth_topic` _*must*_ be the same. If they are not the same, AppDaemon's availability will be ignored and the `alarm_control_panel` and any `binary_sensor`'s statuses can be out of sync with reality during/after restarts.

Arguments in apps.yaml:

```yaml
Expand Down Expand Up @@ -89,10 +91,6 @@ Known issues:

- When the app reloads, sometimes it doesn’t reconnect to the socket and it just hangs the entire app. The only way I’ve been able to recover is to restart AppDaemon. If anyone has a way to detect and fix this, let me know or issue a pull request.

~- I’m not yet processing arming/disarming events. The requests will work 💯, but the partition doesn’t get updated with the status. I put in another INFO request so the partition sensor will update but it’s a bit hacky for now. If you’re listening to the topics or watching logs, you’ll see a bunch of noise associated with this hack.~

~- Partition status being tracked as a `binary_sensor` instead of `alarm_control_panel`.~

- MQTT Discovery is being published to `homeassistant/<component_type>`. I’ll make this a config in the future. This is the default MQTT Discovery topic so I think most people will be fine.

## I hope this works for everyone! Hit me up with feedback
37 changes: 34 additions & 3 deletions apps/ad-qolsys/door_window.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import re

class door_window:
def __init__(self, zoneid: int, name: str, state: str, partition_id: int, device_class="door"):
def __init__(self, zoneid: int, name: str, state: str, partition_id: int, device_class="door", **kwargs):
""" Arguments:
zoneid: int
name: str
state: str
partition_id: int
device_class: str = door"""

self.__c_will_topic__ = "will_topic"
self.__c_will_payload__ = "will_payload"
self.__c_birth_topic__ = "birth_topic"
self.__c_birth_payload__ = "birth_payload"
self.zoneid = zoneid
self.friendly_name = name
self.entity_id = re.sub("\W", "_", self.friendly_name).lower()
Expand All @@ -19,14 +22,42 @@ def __init__(self, zoneid: int, name: str, state: str, partition_id: int, device
self.payload_off = "Closed"
self.config_topic = "homeassistant/binary_sensor/" + self.entity_id + "/config"
self.state_topic = "mqtt_states/binary_sensor/" + self.entity_id + "/state"
self.availability_topic = "mqtt_availability/binary_sensor/" + self.entity_id + "/availability"
self.payload_available = "online"
self.payload_not_available = "offline"
self.will_topic = kwargs[self.__c_will_topic__] if self.__c_will_topic__ in kwargs else "mqtt-client/status"
self.birth_topic = kwargs[self.__c_birth_topic__] if self.__c_birth_topic__ in kwargs else "mqtt-client/status"
self.will_payload = kwargs[self.__c_will_payload__] if self.__c_will_payload__ in kwargs else "offline"
self.birth_payload = kwargs[self.__c_birth_payload__] if self.__c_birth_payload__ in kwargs else "online"

@property
def availability_list(self):
al = [
{
"payload_available": self.payload_available,
"payload_not_available": self.payload_not_available,
"topic": self.availability_topic
}
]
if self.birth_topic == self.will_topic:
al.append(
{
"payload_available": self.birth_payload,
"payload_not_available": self.will_payload,
"topic": self.will_topic
}
)
return al

def config_payload(self):
payload = {
"name": self.friendly_name,
"device_class": self.device_class,
"state_topic": self.state_topic,
"payload_on": self.payload_on,
"payload_off": self.payload_off
"payload_off": self.payload_off,
"availability_mode": "all",
"availability": self.availability_list
}
return payload

Expand Down
47 changes: 40 additions & 7 deletions apps/ad-qolsys/partition.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re

class partition:
def __init__(self, p_id: int, name: str, status: str, code: int, confirm_code_arm: bool, confirm_code_disarm: bool, token: str):
def __init__(self, p_id: int, name: str, status: str, code: int, confirm_code_arm: bool, confirm_code_disarm: bool, token: str, **kwargs):
""" Arguments:
id: int
name: str
Expand All @@ -17,28 +17,61 @@ def __init__(self, p_id: int, name: str, status: str, code: int, confirm_code_ar
self.__c_disarmed__ = "disarmed"
self.__c_armed_home__ = "armed_home"
self.__c_armed_away__ = "armed_away"

# self.config_topic = "homeassistant/binary_sensor/" + self.entity_id + "/config"
# self.state_topic = "mqtt_states/binary_sensor/" + self.entity_id + "/state"
self.__c_command_topic__ = "command_topic"
self.__c_will_topic__ = "will_topic"
self.__c_will_payload__ = "will_payload"
self.__c_birth_topic__ = "birth_topic"
self.__c_birth_payload__ = "birth_payload"

self.alarm_panel_config_topic = "homeassistant/alarm_control_panel/qolsys/" + self.entity_id + "/config"
self.alarm_panel_state_topic = "mqtt_states/alarm_control_panel/qolsys/" + self.entity_id + "/state"
self.availability_topic = "mqtt_availability/alarm_control_panel/qolsys/" + self.entity_id + "/availability"
self.status = status
self.code = code
self.confirm_code_arm = confirm_code_arm
self.confirm_code_disarm = confirm_code_disarm
self.token = token
self.payload_available = "online"
self.payload_not_available = "offline"
self.command_template = '{"event":"{% if action == \"ARM_HOME\" or action == \"ARM_AWAY\" %}ARM","arm_type":"{% if action == \"ARM_HOME\" %}stay{% else %}away{% endif %}"{% else %}{{action}}", "usercode":"' + str(self.code) + '"{% endif %}, "token":"' + self.token + '", "partition_id":"' + str(self.p_id) + '"}'
self.command_topic = kwargs[self.__c_command_topic__] if self.__c_command_topic__ in kwargs else "qolsys/requests"
self.will_topic = kwargs[self.__c_will_topic__] if self.__c_will_topic__ in kwargs else "mqtt-client/status"
self.birth_topic = kwargs[self.__c_birth_topic__] if self.__c_birth_topic__ in kwargs else "mqtt-client/status"
self.will_payload = kwargs[self.__c_will_payload__] if self.__c_will_payload__ in kwargs else "offline"
self.birth_payload = kwargs[self.__c_birth_payload__] if self.__c_birth_payload__ in kwargs else "online"

@property
def availability_list(self):
al = [
{
"payload_available": self.payload_available,
"payload_not_available": self.payload_not_available,
"topic": self.availability_topic
}
]
if self.birth_topic == self.will_topic:
al.append(
{
"payload_available": self.birth_payload,
"payload_not_available": self.will_payload,
"topic": self.will_topic
}
)
return al

def alarm_config_payload(self):
payload = {
"name": self.name,
"state_topic": self.alarm_panel_state_topic,
"code_disarm_required": self.confirm_code_disarm,
"code_arm_required": self.confirm_code_arm,
"command_topic":"qolsys/requests",
"command_template":'{"event":"{% if action == \"ARM_HOME\" or action == \"ARM_AWAY\" %}ARM","arm_type":"{% if action == \"ARM_HOME\" %}stay{% else %}away{% endif %}"{% else %}{{action}}", "usercode":"' + str(self.code) + '"{% endif %}, "token":"' + self.token + '", "partition_id":"' + str(self.p_id) + '"}'
"command_topic": self.command_topic,
"command_template": self.command_template,
"availability_mode": "all",
"availability": self.availability_list
}
if self.confirm_code_disarm or self.confirm_code_arm:
payload.update({"code":self.code})
payload.update({"code": self.code})
return payload

# def config_payload(self):
Expand Down
15 changes: 11 additions & 4 deletions apps/ad-qolsys/qolsys_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
# qolsys_confirm_disarm_code: True/False (Optional) Require the code for disarming
# qolsys_confirm_arm_code: True/False (Optional) Require the code for arming
# qolsys_disarm_code: (Required - if you want to disarm the alarm)
#

class QolsysClient(mqtt.Mqtt):
def initialize(self):
Expand Down Expand Up @@ -66,6 +67,7 @@ def initialize(self):
self.qolsys_disarm_code = self.args[self.__c_qolsys_disarm_code__] if self.__c_qolsys_disarm_code__ in self.args else 9999
self.qolsys_confirm_disarm_code = self.args[self.__c_qolsys_confirm_disarm_code__] if self.__c_qolsys_confirm_disarm_code__ in self.args else False
self.qolsys_confirm_arm_code = self.args[self.__c_qolsys_confirm_arm_code__] if self.__c_qolsys_confirm_arm_code__ in self.args else False
self.mqtt_plugin_config = self.get_plugin_config(namespace=self.mqtt_namespace)

self.log("qolsys_host: %s, qolsys_port: %s, qolsys_token: %s, qolsys_timeout: %s, request_topic: %s", self.qolsys_host, self.qolsys_port, self.qolsys_token, self.qolsys_timeout, self.request_topic, level="DEBUG")
self.log("Creating qolsys_socket", level="INFO")
Expand Down Expand Up @@ -98,6 +100,9 @@ def initialize(self):
info_payload = {"event":"INFO", "token":self.qolsys_token}
self.call_service("mqtt/publish", namespace=self.mqtt_namespace, topic=self.request_topic, payload=json.dumps(info_payload))

# config = self.get_plugin_config(namespace=self.mqtt_namespace)
# self.log(config)

def terminate(self):
try:
self.qolsys.close_socket()
Expand All @@ -107,17 +112,19 @@ def terminate(self):

try:
for zone in self.zones:
self.call_service("mqtt/publish", topic=self.zones[zone].config_topic, namespace=self.mqtt_namespace)
# self.call_service("mqtt/publish", topic=self.zones[zone].config_topic, namespace=self.mqtt_namespace)
self.call_service("mqtt/publish", namespace=self.mqtt_namespace, topic=self.zones[zone].availability_topic, payload=self.zones[zone].payload_not_available, retain=True)
self.log("Zones removed")
except:
self.log("Error publishing empty zone: %s, %s", zone, sys.exc_info(), level="ERROR")

try:
for part in self.partitions:
self.call_service("mqtt/publish", topic=self.partitions[part].alarm_config_topic, namespace=self.mqtt_namespace)
self.log("Partitions removed")
#self.call_service("mqtt/publish", topic=self.partitions[part].alarm_config_topic, namespace=self.mqtt_namespace)
self.call_service("mqtt/publish", namespace=self.mqtt_namespace, topic=self.partitions[part].availability_topic, payload=self.partitions[part].payload_not_available, retain=True)
self.log("Partitions set to unavailable")
except:
self.log("Error publishing empty partition: %s, %s", part, sys.exc_info(), level="ERROR")
self.log("Error publishing offlining partition: %s, %s", part, sys.exc_info(), level="ERROR")

self.log("Terminated")

Expand Down
31 changes: 27 additions & 4 deletions apps/ad-qolsys/qolsys_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,31 @@ def mqtt_info_event_received(self, event_name, data, kwargs):
partition_id = part["partition_id"]
partition_name = part["name"]
partition_status = part["status"]
this_partition = partition.partition(p_id=partition_id, name=partition_name, status=partition_status, code=self.app.qolsys_disarm_code, confirm_code_arm=self.app.qolsys_confirm_arm_code, confirm_code_disarm=self.app.qolsys_confirm_disarm_code, token=self.app.qolsys_token)
self.app.log(self.app.mqtt_plugin_config)
will_topic = self.app.mqtt_plugin_config["will_topic"]
will_payload = self.app.mqtt_plugin_config["will_payload"]
birth_topic = self.app.mqtt_plugin_config["birth_topic"]
birth_payload = self.app.mqtt_plugin_config["birth_payload"]
this_partition = partition.partition(
p_id=partition_id,
name=partition_name,
status=partition_status,
code=self.app.qolsys_disarm_code,
confirm_code_arm=self.app.qolsys_confirm_arm_code,
confirm_code_disarm=self.app.qolsys_confirm_disarm_code,
token=self.app.qolsys_token,
will_topic = will_topic,
will_payload = will_payload,
birth_topic = birth_topic,
birth_payload = birth_payload
)
self.app.update_partition(partition_id, this_partition)
# self.app.call_service("mqtt/publish", namespace=self.app.mqtt_namespace, topic=this_partition.config_topic, payload=json.dumps(this_partition.config_payload()))
# self.app.call_service("mqtt/publish", namespace=self.app.mqtt_namespace, topic=this_partition.state_topic, payload=this_partition.status)
# self.app.log("topic: %s, payload: %s", this_partition.alarm_panel_config_topic, this_partition.alarm_config_payload())
self.app.call_service("mqtt/publish", namespace=self.app.mqtt_namespace, topic=this_partition.alarm_panel_config_topic, payload=json.dumps(this_partition.alarm_config_payload()))
self.app.call_service("mqtt/publish", namespace=self.app.mqtt_namespace, topic=this_partition.alarm_panel_config_topic, retain=True, payload=json.dumps(this_partition.alarm_config_payload()))
self.app.call_service("mqtt/publish", namespace=self.app.mqtt_namespace, topic=this_partition.alarm_panel_state_topic, payload=this_partition.status)
self.app.call_service("mqtt/publish", namespace=self.app.mqtt_namespace, topic=this_partition.availability_topic, payload=this_partition.payload_available)


# self.app.partitions[partition_id] = partition_name
Expand All @@ -98,15 +116,20 @@ def mqtt_info_event_received(self, event_name, data, kwargs):
name = friendly_name,
state = state,
partition_id = partition_id,
device_class = self.__device_class_mapping__(zone_type)
device_class = self.__device_class_mapping__(zone_type),
will_topic = will_topic,
will_payload = will_payload,
birth_topic = birth_topic,
birth_payload = birth_payload
)
#self.app.zones[zoneid] = this_zone


self.app.update_zone(zoneid, this_zone)
self.app.log("Publishing zone: %s", json.dumps(this_zone.config_payload()), level="DEBUG")
self.app.call_service("mqtt/publish", namespace=self.app.mqtt_namespace, topic=this_zone.config_topic, payload=json.dumps(this_zone.config_payload()))
self.app.call_service("mqtt/publish", namespace=self.app.mqtt_namespace, topic=this_zone.config_topic, retain=True, payload=json.dumps(this_zone.config_payload()))
self.app.call_service("mqtt/publish", namespace=self.app.mqtt_namespace, topic=this_zone.state_topic, payload=this_zone.state)
self.app.call_service("mqtt/publish", namespace=self.app.mqtt_namespace, topic=this_zone.availability_topic, payload=this_zone.payload_available)

# done creating the zones
# self.app.log("Partitions: %s", self.app.partitions, level="INFO")
Expand Down

0 comments on commit 2f1e026

Please sign in to comment.