Skip to content

Commit

Permalink
Adding support for pending and triggered alarm statuses
Browse files Browse the repository at this point in the history
  • Loading branch information
roopesh committed Jun 13, 2021
1 parent 6e4eb8a commit 583f30d
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 28 deletions.
37 changes: 20 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,23 +12,25 @@ Utilizes the MQTT plugin's `will_topic` to detect if AppDaemon is offline. In o
Arguments in apps.yaml:

```yaml
# mqtt_namespace: (optional) namespace for mqtt defined in appdaemon.yaml; defaults to ""
# qolsys_host: (Required) IP address or hostname for the qolsys panel
# qolsys_port: (Optional) Port on the qolsys panel to connect to; will default to 12345
# qolsys_token: (Required) Token from the qolsys panel
# request_topic: (Optional) The topic to listen to send commands to the qolsys panel; defaults to qolsys/requests
# qolsys_timeout: (Optional) The timeout (in seconds) to wait for any activity to/from the qolsys panel before disconnecting; defaults to 86400
# qolsys_info_topic: (Optional) The topic to publish qolsys INFO events to; defaults to qolsys/info
# qolsys_zone_event_topic: (Optional) The topic to publish ZONE_EVENT events to; defaults to qolsys/zone_event
# qolsys_alarming_event_topic: (Optional) The topic to publish ARMING events to; defaults to qolsys/arming
# qolsys_disarming_event_topic: (Optional) The topic to publish DISARMING events to; defaults to qolsys/disarming
# qolsys_disarm_code: (Required - if you want to disarm the alarm)
# qolsys_confirm_disarm_code: True/False (Optional) Require the code for disarming; defaults to False
# qolsys_confirm_arm_code: True/False (Optional) Require the code for arming; defaults to False
# qolsys_arm_away_always_instant: True/False (Optional) Set to true if all Arm Away commands should be instant; defaults to False
# homeassistant_mqtt_discovery_topic: homeassistant/ (Optional) The topic Home Assistant is using for MQTT Discovery (homeassistant/ is the default in HA and here)
# mqtt_state_topic: mqtt-states (Optional) The topic to publish state updates to for the alarm_control_panel and binary_sensor (default: mqtt-states)
# mqtt_availability_topic: mqtt-availability (Optional) The topic to publish availability events to for the alarm_control_panel and binary_sensor (default: mqtt-availability)
mqtt_namespace: (optional) namespace for mqtt defined in appdaemon.yaml; defaults to ""
qolsys_host: (Required) IP address or hostname for the qolsys panel
qolsys_port: (Optional) Port on the qolsys panel to connect to; will default to 12345
qolsys_token: (Required) Token from the qolsys panel
request_topic: (Optional) The topic to listen to send commands to the qolsys panel; defaults to qolsys/requests
qolsys_timeout: (Optional) The timeout (in seconds) to wait for any activity to/from the qolsys panel before disconnecting; defaults to 86400
qolsys_info_topic: (Optional) The topic to publish qolsys INFO events to; defaults to qolsys/info
qolsys_zone_event_topic: (Optional) The topic to publish ZONE_EVENT events to; defaults to qolsys/zone_event
qolsys_alarming_event_topic: (Optional) The topic to publish ARMING events to; defaults to qolsys/arming
qolsys_disarming_event_topic: (Optional) The topic to publish DISARMING events to; defaults to qolsys/disarming
qolsys_confirm_disarm_code: True/False (Optional) Require the code for disarming; defaults to False
qolsys_confirm_arm_code: True/False (Optional) Require the code for arming; defaults to False
qolsys_disarm_code: (Required - if you want to disarm the alarm)
qolsys_arm_away_always_instant: True/False (Optional) Set to true if all Arm Away commands should be instant; defaults to False
homeassistant_mqtt_discovery_topic: homeassistant/ (Optional) The topic Home Assistant is using for MQTT Discovery (homeassistant/ is the default in HA and here)
mqtt_state_topic: mqtt-states (Optional) The topic to publish state updates to for the alarm_control_panel and binary_sensor (default: mqtt-states)
mqtt_availability_topic: mqtt-availability (Optional) The topic to publish availability events to for the alarm_control_panel and binary_sensor (default: mqtt-availability)
qolsys_alarm_triggered_topic: (Optional) The topic to publish triggered events to; defaults to qolsys/alarm/triggered
qolsys_alarm_pending_topic: (Optional) The topic to publish pending events to; defaults to qolsys/alarm/pending
```
You’ll need you appdaemon's apps.yaml to include an app with this module and class:
Expand Down Expand Up @@ -65,6 +67,7 @@ appdaemon:
plugins:
HASS:
type: hass
namespace: default
# I added on the MQTT plugin
MQTT:
type: mqtt
Expand Down
11 changes: 9 additions & 2 deletions apps/ad-qolsys/partition.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ def __init__(self, p_id: int, name: str, status: str, code: int, confirm_code_ar
self.__c_armed_home__ = "armed_home"
self.__c_armed_away__ = "armed_away"
self.__c_arming__ = "arming"
self.__c_pending__ = "pending"
self.__c_triggered__ = "triggered"
self.__c_command_topic__ = "command_topic"
self.__c_will_topic__ = "will_topic"
self.__c_will_payload__ = "will_payload"
Expand Down Expand Up @@ -109,7 +111,8 @@ def status(self, status:str):
__c_ENTRY_DELAY__ = "ENTRY_DELAY"
__c_ARM_AWAY__ = "ARM_AWAY"
__c_ARM_AWAY_EXIT_DELAY__ = "ARM-AWAY-EXIT-DELAY"
valid_values = {__c_ARM_STAY__, __c_ARM_AWAY__, __c_ARM_DELAY__, __c_DISARM__, __c_ENTRY_DELAY__, __c_ARM_AWAY_EXIT_DELAY__}
__c_ALARM__ = "ALARM"
valid_values = {__c_ARM_STAY__, __c_ARM_AWAY__, __c_ARM_DELAY__, __c_DISARM__, __c_ENTRY_DELAY__, __c_ARM_AWAY_EXIT_DELAY__, __c_ALARM__}

if not status in valid_values:
self.__status = "unavailable"
Expand All @@ -118,8 +121,12 @@ def status(self, status:str):
self.__status = self.__c_armed_home__
elif status in {__c_ARM_DELAY__, __c_ARM_AWAY_EXIT_DELAY__}: # Maps values to arming status
self.__status = self.__c_arming__
elif status in {__c_ENTRY_DELAY__, __c_ARM_AWAY__}: # Maps panel status values to armed_away
elif status in {__c_ARM_AWAY__}: # Maps panel status values to armed_away
self.__status = self.__c_armed_away__
elif status in {__c_ENTRY_DELAY__}: # Maps panel status values to triggered
self.__status = self.__c_pending__
elif status in {__c_ALARM__}:
self.__status = self.__c_triggered__
elif status in {__c_DISARM__}: # Maps panel status values to disarmed
self.__status = self.__c_disarmed__
else:
Expand Down
28 changes: 25 additions & 3 deletions apps/ad-qolsys/qolsys_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
# qolsys_disarm_code: (Required - if you want to disarm the alarm)
# qolsys_arm_away_always_instant: True/False (Optional) Set to true if all Arm Away commands should be instant; defaults to False

# Developer documentation
# This is basically how shit flows:
# Get an event from the qolsys panel (qolsys_client.py) --> QolsysClient.qolsys_data_received.
# The event type and sub event (zone_event_type, arming_type, alarm_type) determine which mqtt queue to publish to.
# You need a listener for that topic in QolsysClient.initialize
class QolsysClient(mqtt.Mqtt):
def get_arg(self, name: str, arr: list, default=None):

Expand Down Expand Up @@ -83,6 +88,8 @@ def initialize(self):
self.__c_mqtt_will_payload__ = "will_payload"
self.__c_mqtt_birth_topic__ = "birth_topic"
self.__c_mqtt_birth_payload__ = "birth_payload"
self.__c_mqtt_alarm_triggered_topic__ = "qolsys_alarm_triggered_topic"
self.__c_mqtt_alarm_pending_topic__ = "qolsys_alarm_pending_topic"

# populate some variables we'll need to use throughout our app
self.mqtt_namespace = self.get_arg(name=self.__c_mqtt_namespace__, arr=self.args, default="")
Expand Down Expand Up @@ -110,7 +117,8 @@ def initialize(self):
self.mqtt_will_payload = self.get_arg(name=self.__c_mqtt_will_payload__, arr=self.mqtt_plugin_config)
self.mqtt_birth_topic = self.get_arg(name=self.__c_mqtt_birth_topic__, arr=self.mqtt_plugin_config)
self.mqtt_birth_payload = self.get_arg(name=self.__c_mqtt_birth_payload__, arr=self.mqtt_plugin_config)

self.qolsys_alarm_triggered_topic = self.args[self.__c_mqtt_alarm_triggered_topic__] if self.__c_mqtt_alarm_triggered_topic__ in self.args else "qolsys/alarm/triggered"
self.qolsys_alarm_pending_topic = self.args[self.__c_mqtt_alarm_pending_topic__] if self.__c_mqtt_alarm_pending_topic__ in self.args else "qolsys/alarm/pending"


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")
Expand Down Expand Up @@ -139,6 +147,13 @@ def initialize(self):
self.log("listener for arming topic: %s", self.qolsys_arming_event_topic, level="INFO")
mqtt_sub.listen(mqtt_sub.mqtt_arming_event_received, self.qolsys_arming_event_topic)

# Pending events come as ARMING / ENTRY_DELAY events... no need for a separate listener
# self.log("listner for pending topic: %s", self.qolsys_pending_topic, level="INFO")
# mqtt_sub.listen(mqtt_sub.mqtt_alarm_pending_event_received, self.qolsys_alarm_pending_topic)

self.log("listner for triggered (ALARM) topic: %s", self.qolsys_alarm_triggered_topic, level="INFO")
mqtt_sub.listen(mqtt_sub.mqtt_alarm_triggered_event_received, self.qolsys_alarm_triggered_topic)


# Populate the zones and partitions with an INFO call
info_payload = {"event":"INFO", "token":self.qolsys_token}
Expand Down Expand Up @@ -188,19 +203,26 @@ def qolsys_data_received(self, data:dict):
if event_type == "INFO":
topic = self.qolsys_info_topic

if event_type == "ZONE_EVENT":
elif event_type == "ZONE_EVENT":
zone_event_type = jdata["zone_event_type"]
# Two types of zone events: ZONE_UPDATE, ZONE_ACTIVE
# zone_event_type is unused for now
topic = self.qolsys_zone_event_topic
# if zone_event_type == "ZONE_UDPATE":
# topic = self.qolsys_zone_update_topic

if event_type == "ARMING":
elif event_type == "ARMING":
arming_type = jdata["arming_type"]
# Three types of arming: ARM_STAY, EXIT_DELAY, DISARM
topic = self.qolsys_arming_event_topic

elif event_type == "ALARM":
# The alarm is actually triggered
topic = self.qolsys_alarm_triggered_topic

else:
topic = "qolsys/unknown_events"

self.log("publishing %s event to: %s", event_type, topic, level="INFO")
self.log("data being published: %s", data, level="DEBUG")

Expand Down
20 changes: 19 additions & 1 deletion apps/ad-qolsys/qolsys_requests.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,22 @@ def mqtt_arming_event_received(self, event_name, data, kwargs):
self.app.log("Publishing to: %s, Payload: %s", this_partition.alarm_panel_state_topic, this_partition.status, level="INFO")
self.app.call_service("mqtt/publish", namespace=self.app.mqtt_namespace, topic=this_partition.alarm_panel_state_topic, payload=this_partition.status)

def mqtt_alarm_triggered_event_received(self, event_name, data, kwargs):
self.app.log("Got ALARM event: %s", data, level="INFO")
self.app.log("event_name: %s", event_name, level="INFO")
self.app.log("data: %s", data, level="INFO")
self.app.log("kwargs: %s", kwargs, level="INFO")
payload_json = self.__get_mqtt_payload_json__(data)
partition_id = payload_json["partition_id"]
alarm_type = payload_json["alarm_type"]
this_partition = self.app.partitions[partition_id]
this_partition.status = payload_json["event"] # This should be "ALARM"
self.app.update_zone(partition_id, this_partition)
self.app.log("Partitions: %s", self.app.partitions, level="INFO")
self.app.log("Publishing to: %s, Payload: %s", this_partition.alarm_panel_state_topic, this_partition.status, level="INFO")
self.app.call_service("mqtt/publish", namespace=self.app.mqtt_namespace, topic=this_partition.alarm_panel_state_topic, payload=this_partition.status)


def mqtt_zone_event_event_received(self, event_name, data, kwargs):
self.app.log("Got zone event: %s", data, level="DEBUG")
self.mqtt_zone_update_event_received(event_name, data, kwargs)
Expand Down Expand Up @@ -106,6 +122,7 @@ def mqtt_info_event_received(self, event_name, data, kwargs):
friendly_name = zone["name"]
state = zone["status"]
zone_type = zone["type"]
zone_unique_id = zone["id"]

# Add this zone to this partition
this_partition.add_zone(zoneid)
Expand All @@ -123,7 +140,8 @@ def mqtt_info_event_received(self, event_name, data, kwargs):
birth_payload = self.app.mqtt_birth_payload,
homeassistant_mqtt_discovery_topic = homeassistant_mqtt_discovery_topic,
mqtt_state_topic = self.app.mqtt_state_topic,
mqtt_availability_topic = self.app.mqtt_availability_topic
mqtt_availability_topic = self.app.mqtt_availability_topic,
unique_id = zone_unique_id
)
#self.app.zones[zoneid] = this_zone

Expand Down
10 changes: 5 additions & 5 deletions apps/ad-qolsys/qolsys_socket.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,10 @@ def _start_listener(self):
self._listening_thread.start()
self.app.log("started listener thread", level="INFO")

def _reset_socket(self):
self.close_socket()
def _reset_socket(self, timeout=1):
self.close_socket(timeout=2)
#self._listening_thread = threading.Thread(target=self.listen, args=([self._listener_callback]))
self.app.log("Creating socket", level="INFO")
self.app.log("Recreating socket", level="INFO")
self.__listening__ = True
self.create_socket(self._hostname, self._port, self._token, self._listener_callback, self._timeout)

Expand Down Expand Up @@ -126,11 +126,11 @@ def listen(self, cb: callable):
self.app.log("socket timeout", level="WARNING")
except NoDataError:
self._reset_socket()
raise NoDataError
# raise NoDataError
except TimeoutError:
self.app.log("qolsys socket TimeoutError: %s", sys.exc_info(), level="ERROR")
self._reset_socket
raise NoDataError
# raise NoDataError
except:
self.app.log("listen failed/stopped: %s", sys.exc_info(), level="ERROR")

Expand Down

0 comments on commit 583f30d

Please sign in to comment.