We do not send LRR messages to smartthings and I have a need to capture these messages and use them to drive events. I want to be sure the LIGHTS are all ON during a smoke/co2 event and off or flashing if in ALARM. So my need is to get my hands on the LRR events and likely the RFX or other messages as well.
I may also implement sending PUSH of other notifications to SmartThings such as ARMED/DISARMED but that is not my immediate priority. Currently IMHO it is very slow to wait for an update on my phone when I disarm/arm my house and adding support for PUSH notification would improve this.
I still have some hard coded stuff as I said it was a sketch still. I got a bit lost in all the references in constants.py and I think I went a little crazy on the LRR constants and it is likely some are not needed. I want to be able to do RFX or REL etc later in the events that can be seen via PUSH.
# At some ping the push notification constructor will subscribe to more and likely be configurable.
self._events = [LRR,RFX,REL,EXP]
Also I have seen how the on_message processing in decoder.py is setup to capture LRR and RFX events already I guess to send it to all the websock clients? Seems odd so now I presume that this patch will end up subscribing to on_lrr_message multiple times.
I noticed a few #HACK notes from you regarding a publishing code that broke stuff. I presume at some point we can reverse that patch?
- Code: Select all
diff --git a/ad2web/api/views.py b/ad2web/api/views.py
index 6bf7d62..4f8157e 100644
--- a/ad2web/api/views.py
+++ b/ad2web/api/views.py
@@ -223,6 +223,23 @@ def alarmdecoder_send():
return jsonify(), NO_CONTENT
+@api.route('/alarmdecoder/event', methods=['SUBSCRIBE','UNSUBSCRIBE'])
+@crossdomain(origin="*", headers=['Content-type', 'api_key', 'Authorization'])
+@api_authorized
+def alarmdecoder_events():
+ device = current_app.decoder.device
+
+ if request.method == 'SUBSCRIBE':
+ ret = _build_alarmdecoder_configuration_data(device)
+
+ return jsonify(ret), OK
+
+ if request.method == 'UNSUBSCRIBE':
+ ret = _build_alarmdecoder_configuration_data(device)
+
+ return jsonify(ret), OK
+
+
@api.route('/alarmdecoder/reboot', methods=['POST'])
@crossdomain(origin="*", headers=['Content-type', 'api_key', 'Authorization'])
@api_authorized
diff --git a/ad2web/decoder.py b/ad2web/decoder.py
index b58669d..6e5c65c 100644
--- a/ad2web/decoder.py
+++ b/ad2web/decoder.py
@@ -40,9 +40,9 @@ from .updater.models import FirmwareUpdater
from .notifications.models import NotificationMessage
from .notifications.constants import (ARM, DISARM, POWER_CHANGED, ALARM, ALARM_RESTORED,
- FIRE, BYPASS, BOOT, CONFIG_RECEIVED, ZONE_FAULT,
+ FIRE, BYPASS, BOOT, LRR, CONFIG_RECEIVED, ZONE_FAULT,
ZONE_RESTORE, LOW_BATTERY, PANIC, RELAY_CHANGED,
- DEFAULT_EVENT_MESSAGES)
+ LRR, DEFAULT_EVENT_MESSAGES)
from .cameras import CameraSystem
from .cameras.models import Camera
@@ -64,6 +64,7 @@ EVENT_MAP = {
FIRE: 'on_fire',
BYPASS: 'on_bypass',
BOOT: 'on_boot',
+ LRR: 'on_lrr_message',
CONFIG_RECEIVED: 'on_config_received',
ZONE_FAULT: 'on_zone_fault',
ZONE_RESTORE: 'on_zone_restore',
diff --git a/ad2web/log/__init__.py b/ad2web/log/__init__.py
index bae639f..2369af8 100644
--- a/ad2web/log/__init__.py
+++ b/ad2web/log/__init__.py
@@ -2,6 +2,6 @@
from .constants import ARM, DISARM, POWER_CHANGED, ALARM, FIRE, BYPASS, BOOT, \
CONFIG_RECEIVED, ZONE_FAULT, ZONE_RESTORE, LOW_BATTERY, \
- PANIC, RELAY_CHANGED, EVENT_TYPES
+ PANIC, RELAY_CHANGED, EVENT_TYPES, LRR
from .models import EventLogEntry
from .views import log
diff --git a/ad2web/log/views.py b/ad2web/log/views.py
index 11aa56d..610d06e 100644
--- a/ad2web/log/views.py
+++ b/ad2web/log/views.py
@@ -12,7 +12,7 @@ from ..extensions import db
from ..decorators import admin_required
from .constants import ARM, DISARM, POWER_CHANGED, ALARM, FIRE, BYPASS, BOOT, \
CONFIG_RECEIVED, ZONE_FAULT, ZONE_RESTORE, LOW_BATTERY, \
- PANIC, RELAY_CHANGED, EVENT_TYPES
+ PANIC, RELAY_CHANGED, EVENT_TYPES, LRR
from .models import EventLogEntry
from ..logwatch import LogWatcher
from ..utils import INSTANCE_FOLDER_PATH
@@ -39,6 +39,7 @@ def log_context_processor():
'LOW_BATTERY': LOW_BATTERY,
'PANIC': PANIC,
'RELAY_CHANGED': RELAY_CHANGED,
+ 'LRR': LRR,
'TYPES': EVENT_TYPES
}
diff --git a/ad2web/notifications/constants.py b/ad2web/notifications/constants.py
index a09ac4e..dfe0856 100644
--- a/ad2web/notifications/constants.py
+++ b/ad2web/notifications/constants.py
@@ -16,6 +16,7 @@ LOW_BATTERY = 10
PANIC = 11
RELAY_CHANGED = 12
ALARM_RESTORED = 13
+LRR = 14
CRITICAL_EVENTS = [POWER_CHANGED, ALARM, BYPASS, ARM, DISARM, ZONE_FAULT, \
ZONE_RESTORE, FIRE, PANIC]
@@ -34,6 +35,7 @@ DEFAULT_EVENT_MESSAGES = {
ZONE_RESTORE: 'Zone {zone_name} ({zone}) has been restored.',
LOW_BATTERY: 'Low battery detected.',
PANIC: 'Panic!',
+ LRR: 'A LRR event was received',
RELAY_CHANGED: 'A relay has changed.'
}
@@ -51,6 +53,7 @@ EVENT_TYPES = {
LOW_BATTERY: 'low battery',
PANIC: 'panic',
RELAY_CHANGED: 'relay changed',
+ LRR: 'lrr',
ALARM_RESTORED: 'alarm restored'
}
@@ -64,6 +67,7 @@ GROWL = 6
CUSTOM = 7
TWIML = 8
SMARTTHINGS = 9
+UPNPPUSH = 10
NOTIFICATION_TYPES = {
EMAIL: 'email',
@@ -75,6 +79,7 @@ NOTIFICATION_TYPES = {
GROWL: 'growl',
CUSTOM: 'custom',
TWIML: 'twiml',
+ UPNPPUSH: 'upnppush',
SMARTTHINGS: 'smartthings'
}
@@ -88,6 +93,7 @@ NOTIFICATIONS = {
GROWL: ('growl', u'Growl'),
CUSTOM: ('custom', u'Custom'),
TWIML: ('twiml', u'TwiML'),
+ UPNPPUSH: ('upnppush', u'UPNP Push'),
SMARTTHINGS: ('smartthings', u'SmartThings Integration')
}
@@ -107,6 +113,7 @@ SUBSCRIPTIONS = OrderedDict([
(LOW_BATTERY, 'A low battery has been detected'),
(BOOT, 'The AlarmDecoder has rebooted'),
(RELAY_CHANGED, 'A relay has been changed'),
+ (LRR, 'A LRR event was detected'),
])
PUSHOVER_URL = "api.pushover.net:443"
diff --git a/ad2web/notifications/forms.py b/ad2web/notifications/forms.py
index ca88a9f..afb1ad2 100644
--- a/ad2web/notifications/forms.py
+++ b/ad2web/notifications/forms.py
@@ -15,7 +15,7 @@ from wtforms.validators import (Required, Length, EqualTo, Email, NumberRange,
from wtforms.widgets import ListWidget, CheckboxInput
from .constants import (NOTIFICATIONS, NOTIFICATION_TYPES, SUBSCRIPTIONS, DEFAULT_SUBSCRIPTIONS, EMAIL, GOOGLETALK, PUSHOVER, PUSHOVER_PRIORITIES,
NMA_PRIORITIES, LOWEST, LOW, NORMAL, HIGH, EMERGENCY, PROWL_PRIORITIES, GROWL, GROWL_PRIORITIES, GROWL_TITLE,
- URLENCODE, JSON, XML, CUSTOM_METHOD_POST, CUSTOM_METHOD_GET_TYPE)
+ URLENCODE, JSON, XML, CUSTOM_METHOD_POST, CUSTOM_METHOD_GET_TYPE, UPNPPUSH)
from .models import NotificationSetting
from ..widgets import ButtonField, MultiCheckboxField
@@ -555,6 +555,47 @@ class SmartThingsNotificationForm(Form):
return ret
+class UPNPPushNotificationInternalForm(Form):
+ token = TextField(u'Token', [Required(), Length(max=255)], description=u'Authentication token')
+
+ def __init__(self, *args, **kwargs):
+ kwargs['csrf_enabled'] = False
+ super(UPNPPushNotificationInternalForm, self).__init__(*args, **kwargs)
+
+
+class UPNPPushNotificationForm(Form):
+ type = HiddenField()
+ subscriptions = HiddenField()
+ description = HiddenField("UPNP Push Notifications Subscriptions")
+ form_field = FormField(UPNPPushNotificationInternalForm)
+
+ submit = SubmitField(u'Next')
+ cancel = ButtonField(u'Cancel', onclick="location.href='/settings/notifications'")
+
+ def populate_settings(self, settings, id=None):
+ settings['token'] = self.populate_setting('token', self.form_field.token.data)
+
+ def populate_from_settings(self, id):
+ self.form_field.token.data = self.populate_from_setting(id, 'token')
+
+ def populate_setting(self, name, value, id=None):
+ if id is not None:
+ setting = NotificationSetting.query.filter_by(notification_id=id, name=name).first()
+ else:
+ setting = NotificationSetting(name=name)
+
+ setting.value = value
+
+ return setting
+
+ def populate_from_setting(self, id, name, default=None):
+ ret = default
+
+ setting = NotificationSetting.query.filter_by(notification_id=id, name=name).first()
+ if setting is not None:
+ ret = setting.value
+
+ return ret
class ReviewNotificationForm(Form):
buttons = FormField(NotificationButtonForm)
diff --git a/ad2web/notifications/types.py b/ad2web/notifications/types.py
index 5680d8f..92b774c 100644
--- a/ad2web/notifications/types.py
+++ b/ad2web/notifications/types.py
@@ -62,7 +62,8 @@ from .constants import (EMAIL, GOOGLETALK, DEFAULT_EVENT_MESSAGES, PUSHOVER, TWI
PROWL_CONTENT_TYPE, PROWL_HEADER_CONTENT_TYPE, PROWL_USER_AGENT, GROWL_APP_NAME, GROWL_DEFAULT_NOTIFICATIONS,
GROWL_PRIORITIES, GROWL, CUSTOM, URLENCODE, JSON, XML, CUSTOM_CONTENT_TYPES, CUSTOM_USER_AGENT, CUSTOM_METHOD,
ZONE_FAULT, ZONE_RESTORE, BYPASS, CUSTOM_METHOD_GET, CUSTOM_METHOD_POST, CUSTOM_METHOD_GET_TYPE,
- CUSTOM_TIMESTAMP, CUSTOM_MESSAGE, CUSTOM_REPLACER_SEARCH, TWIML, ARM, DISARM, ALARM, PANIC, FIRE, SMARTTHINGS)
+ CUSTOM_TIMESTAMP, CUSTOM_MESSAGE, CUSTOM_REPLACER_SEARCH, TWIML, ARM, DISARM, ALARM, PANIC, FIRE, SMARTTHINGS,
+ UPNPPUSH, LRR)
from .models import Notification, NotificationSetting, NotificationMessage
from ..extensions import db
@@ -283,6 +284,51 @@ class LogNotification(object):
db.session.add(EventLogEntry(type=type, message=text))
db.session.commit()
+class UPNPPushNotification(BaseNotification):
+ def __init__(self, obj):
+ BaseNotification.__init__(self, obj)
+
+ self._events = [LRR]
+
+ self.api_token = obj.get_setting('token')
+ self.api_endpoint = obj.get_setting('url')
+
+ def subscribes_to(self, type, **kwargs):
+ return (type in self._events)
+
+ def send(self, type, text):
+ with current_app.app_context():
+ if type is None or type in self._events:
+ self._notify_subscribers()
+
+ def _notify_subscribers(self):
+ self._send_notify_event("http://XXX.XXX.XXX.XXX:39500", "/test/event/notify", "AlarmDecoder:1", "<xml>test message</xml>")
+
+ def _send_notify_event(self, notify_url, location, upnp_schema, notify_message):
+ """
+ Send out notify event to subscriber and return a response.
+ """
+ headers = {
+ 'SOAPAction': (
+ '"urn:schemas-upnp-org:service:{schema}:'
+ '1#AddPortMapping"'.format(schema=upnp_schema)
+ ),
+ 'Content-Type': 'text/xml'
+ }
+ parsed_url = urlparse(notify_url + "/" + location)
+
+ http_handler = HTTPConnection(parsed_url.netloc)
+ http_handler.request('NOTIFY', parsed_url.path, notify_message, headers)
+ http_response = http_handler.getresponse()
+
+ if http_response.status != 200:
+ error_msg = 'UPNPPush Notification failed: ({0}: {1})'.format(http_response.status, http_response.read())
+
+ current_app.logger.warning(error_msg)
+ raise Exception(error_msg)
+
+
+
class SmartThingsNotification(BaseNotification):
def __init__(self, obj):
BaseNotification.__init__(self, obj)
@@ -809,5 +855,6 @@ TYPE_MAP = {
GROWL: GrowlNotification,
CUSTOM: CustomNotification,
TWIML: TwiMLNotification,
+ UPNPPUSH: UPNPPushNotification,
SMARTTHINGS: SmartThingsNotification
}
diff --git a/ad2web/notifications/views.py b/ad2web/notifications/views.py
index 538482c..dbd8151 100644
--- a/ad2web/notifications/views.py
+++ b/ad2web/notifications/views.py
@@ -13,13 +13,14 @@ from .forms import (CreateNotificationForm, EditNotificationForm,
EmailNotificationForm, GoogleTalkNotificationForm, PushoverNotificationForm,
TwilioNotificationForm, TwiMLNotificationForm, NMANotificationForm, ProwlNotificationForm,
GrowlNotificationForm, CustomPostForm, ZoneFilterForm, ReviewNotificationForm,
- SmartThingsNotificationForm)
+ SmartThingsNotificationForm, UPNPPushNotificationForm)
from .models import Notification, NotificationSetting, NotificationMessage
from .constants import (EVENT_TYPES, NOTIFICATION_TYPES, DEFAULT_SUBSCRIPTIONS,
EMAIL, GOOGLETALK, PUSHOVER, TWILIO, NMA, PROWL, GROWL,
- CUSTOM, TWIML, SMARTTHINGS, ZONE_FAULT, ZONE_RESTORE)
+ CUSTOM, TWIML, SMARTTHINGS, ZONE_FAULT, ZONE_RESTORE,
+ UPNPPUSH)
NOTIFICATION_TYPE_DETAILS = {
'email': (EMAIL, EmailNotificationForm),
@@ -31,6 +32,7 @@ NOTIFICATION_TYPE_DETAILS = {
'growl': (GROWL, GrowlNotificationForm),
'custom': (CUSTOM, CustomPostForm),
'twiml': (TWIML, TwiMLNotificationForm),
+ 'upnppush': (UPNPPUSH, UPNPPushNotificationForm),
'smartthings': (SMARTTHINGS, SmartThingsNotificationForm)
}
diff --git a/ad2web/static/device_description.xml b/ad2web/static/device_description.xml
index b3733af..a06b83f 100644
--- a/ad2web/static/device_description.xml
+++ b/ad2web/static/device_description.xml
@@ -14,6 +14,13 @@
<modelURL>https://github.com/nutechsoftware/alarmdecoder-webapp</modelURL>
<UDN>uuid:2a7953e0-d4ab-11e7-8f1a-0800200c9a66</UDN>
<serviceList>
+ <service>
+ <serviceType>urn:schemas-upnp-org:service:AlarmDecoder:1</serviceType>
+ <serviceId>urn:upnp-org:serviceId:AlarmDecoder:1</serviceId>
+ <SCPDURL>/static/AlarmDecoder.xml</SCPDURL>
+ <eventSubURL>/api/v1/alarmdecoder/event</eventSubURL>
+ <controlURL>/api/v1/alarmdecoder</controlURL>
+ </service>
</serviceList>
</device>
</root>