SMTP blocking and innacurate log timestamps fix/hack

Contribute source code here. Anything not accompanied by source code will be removed.

SMTP blocking and innacurate log timestamps fix/hack

Postby trialnerror » Wed Mar 21, 2018 7:20 am

As discussed in this thread http://www.alarmdecoder.com/forums/viewtopic.php?f=3&t=755, if a SMTP service is down for some reason, SMTP notifiers can temporarily block recording of events in the AD log, throw off their timestamps, and also block other notification vectors. As a quick and inelegant solution to the problem, I modified the EmailNotification class in the /opt/alarmdecoder-webapp/ad2web/notifications/types.py module to spin off a separate thread to handle the sending whenever an SMTP notification is needed. This prevents blocked SMTP from delaying the processing of other notifications (but does nothing to solve the same problem if other notification vectors block).

You can adjust the variables retryinterval_sec (socket timeout on a send attempt) and finaltimeout_sec (how long to keep attempting to send) as you see fit. If a send finally times out, or otherwise fails, it will post the error and some related info in the App Log. As noted in the other thread, I've tested this with over 250 notifications spun up in threads this way without any apparent adverse impact on AD performance (I did not release the 250 threads for sending because I wasn't sure how my ISP would take that many nearly simultaneous connection requests from one IP). Testing with 3-4 blocked notifications (using iptables to block/unblock) and releasing to send before the retry window closed works fine.

I'm not submitting this as a pull request to the developers because it doesn't address the issue in a particularly clean way, or for all notifiers. Also, this disclaimer: "I am not a programmer".

Code: Select all
class EmailNotification(BaseNotification):
    def __init__(self, obj):
        BaseNotification.__init__(self, obj)

        self.source = obj.get_setting('source')
        self.destination = obj.get_setting('destination')
        self.subject = obj.get_setting('subject')
        self.server = obj.get_setting('server')
        self.port = obj.get_setting('port', default=25)
        self.tls = obj.get_setting('tls', default=False)
        self.ssl = obj.get_setting('ssl', default=False)
        self.authentication_required = obj.get_setting('authentication_required', default=False)
        self.username = obj.get_setting('username')
        self.password = obj.get_setting('password')
        self.suppress_timestamp = obj.get_setting('suppress_timestamp',default=False)

    def send(self, type, text):
        message_timestamp = time.ctime(time.time())

        if self.suppress_timestamp == False:
            text = text + "\r\n\r\nMessage sent at " + message_timestamp + "."

        if check_time_restriction(self.starttime, self.endtime):
            msg = MIMEText(text)

            if self.suppress_timestamp == False:
                self.subject = self.subject + " (" + message_timestamp + ")"

            msg['Subject'] = self.subject
            msg['From'] = self.source
            recipients = re.split('\s*;\s*|\s*,\s*', self.destination)
            msg['To'] = ', '.join(recipients)

            sself=self
            t=threading.Thread(target=self.sendthread,args=(sself,current_app._get_current_object(),recipients,msg,message_timestamp,))
            t.start()


    def sendthread(self, sself, caller_app, recipients, msg, message_timestamp):
        s = None
        retryinterval_sec = 60*1                    #set as you see fit
        finaltimeout_sec = retryinterval_sec * 5    #set as you see fit
        timeout_exception = False
        other_exception = False

        numretries = int(finaltimeout_sec/retryinterval_sec)

        for x in range(numretries):
            timeout_exception = False
            other_exception = False

            # Since AD won't catch any exceptions in this separate thread, put everything
            # important in the try loop and catch so a warning can be sent to the AD log file.
            try:
                if sself.ssl:
                    s = smtplib.SMTP_SSL(sself.server, sself.port, timeout=retryinterval_sec)

                else:
                    s = smtplib.SMTP(sself.server, sself.port, timeout=retryinterval_sec)

                if sself.tls and not sself.ssl:
                    s.starttls()

                if sself.authentication_required:
                    s.login(str(sself.username), str(sself.password))

                s.sendmail(sself.source, recipients, msg.as_string())

            except socket.timeout:
                errinfo = sys.exc_info()
                timeout_exception = True

            except:
                errinfo = sys.exc_info()
                other_exception = True

            if (other_exception is True) or (timeout_exception is False):
                break

        if (timeout_exception is True) or (other_exception is True):
            caller_app.logger.error('Notification Email send failed: '
                                    + msg['Subject'] + ' '
                                    + message_timestamp + ' ('
                                    + str(errinfo[0]) + '; '
                                    + str(errinfo[1]) + ')')
        s.quit()
trialnerror
Junior Nut
Junior Nut
 
Posts: 38
Joined: Wed Jan 03, 2018 11:10 am

Re: SMTP blocking and innacurate log timestamps fix/hack

Postby mathewss » Wed Mar 21, 2018 9:11 am

I have been tinkering in the code for notifications over the past few days in my effort to get push notifications / subscriptions working with SmartThings at my house. I would like feedback from Kevin on this subject as well.

I am also open to suggestions on how to avoid blocking the main thread and your solution seems reasonable to me. Maybe keeping track of the threads in an array to provide some additional support for protection from too many threads or if a thread runs too long.

This is just test code as I work on figuring out the best way to implement this. I believe the proper place for most of this patch is in the notification code notifications/views.py as a new class. I do not see much configuration of PUSH notifications other than to maybe enable/disable it or limit how many subscribers. This will have to send these NOTIFY * requests to potentially multiple subscribers so blocking would be a real problem.

Code: Select all
+#normal http connection support (future POST to custom url)
+try:
+    from http.client import HTTPConnection
+except ImportError:
+    from httplib import HTTPConnection
+
+from urlparse import urlparse
+
 EVENT_MAP = {
     ARM: 'on_arm',
     DISARM: 'on_disarm',
@@ -416,6 +424,9 @@ class Decoder(object):
             if ftype == 'panel':
                 self.last_message_received = message
 
+            if ftype == 'lrr':
+                self.notify_subscribers(ftype, kwargs)
+
             self.broadcast('message', { 'message': kwargs.get('message', None), 'message_type': ftype } )
 
         except Exception, err:
@@ -461,6 +472,38 @@ class Decoder(object):
 
         self._broadcast_packet(packet)
 
+    def notify_subscribers(self, ftype, data={}):
+        """
+        Notify any client that subscribed to push notifications of this event
+
+        :param ftype: Human-readable message type
+        :type ftype: string
+        :param data: Data to send to the subscribers
+        :type data: dict
+        """
+        self.app.logger.debug('notify subscriber')
+        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)
+        conn = HTTPConnection(parsed_url.netloc)
+        conn.request('NOTIFY', parsed_url.path, notify_message, headers)
+
+        response = conn.getresponse()
+        conn.close()
+
mathewss
Site Admin
Site Admin
 
Posts: 183
Joined: Fri Dec 06, 2013 11:14 am

Re: SMTP blocking and innacurate log timestamps fix/hack

Postby kevin » Wed Mar 21, 2018 6:06 pm

What about the existing smartthings notification type isn't suiting your need?

Seems a fair solution to the blocking problem but maybe scoped too closely to email itself, also agree with sean's idea of tracking threads in an array for more granular control - easy to say if len(threads) >= thread_max kill and remove oldest before spawning another

also best to not hardcode values if you can avoid it - modifying a base form and saving/retrieving from database is fairly trivial
Not an employee of the company. Just here to help and keep things clean.
kevin
Site Admin
Site Admin
 
Posts: 992
Joined: Fri Aug 16, 2013 10:10 am

Re: SMTP blocking and innacurate log timestamps fix/hack

Postby mathewss » Wed Mar 21, 2018 7:49 pm

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>
mathewss
Site Admin
Site Admin
 
Posts: 183
Joined: Fri Dec 06, 2013 11:14 am

Re: SMTP blocking and innacurate log timestamps fix/hack

Postby kevin » Wed Mar 21, 2018 8:00 pm

point the #HACK stuff out? Not sure...

Your code looks good (other than the hardcoded values such as host and port), or at least certainly follows the model presented

I'm not sure you have to worry about double binding in this instance, guess we'll find out, huh?
Not an employee of the company. Just here to help and keep things clean.
kevin
Site Admin
Site Admin
 
Posts: 992
Joined: Fri Aug 16, 2013 10:10 am

Re: SMTP blocking and innacurate log timestamps fix/hack

Postby mathewss » Wed Mar 21, 2018 8:21 pm

Ya that is just testing. The client is supposed to subscribe and provide the correct callback since only it knows what is a valid HOST:PORT. For Smartthings hubs they listen on TCP port 39500. Still need to build some sort of a LIST to keep all the subscribers in and the TIMEOUT values to remove them from the list since you can only subscribe for a limited duration and the client needs to RE subscribe from time to time.


It is here in forms and typs.py. I only found it because I was using #HACK myself to track my debug stuff.

Code: Select all
class EditNotificationForm(Form):
    type = HiddenField()
    description = TextField(u'Description', [Required(), Length(max=255)], description=u'Brief description of this notification')
    suppress_timestamp = BooleanField(u'Suppress Timestamp?', [Optional()], description=u'Removes Timestamp from Message Body and Subject')
    time_field = FormField(TimeSettingsInternalForm)
    subscriptions = MultiCheckboxField(u'Notification Events', choices=[(str(k), v) for k, v in SUBSCRIPTIONS.iteritems()])

    def populate_settings(self, settings, id=None):
        settings['subscriptions'] = self.populate_setting('subscriptions', json.dumps({str(k): True for k in self.subscriptions.data}))
        settings['starttime'] = self.populate_setting('starttime', self.time_field.starttime.data or '00:00:00')
        settings['endtime'] = self.populate_setting('endtime', self.time_field.endtime.data or '23:59:59')
        settings['delay'] = self.populate_setting('delay', self.time_field.delaytime.data)
        settings['suppress'] = self.populate_setting('suppress', self.time_field.suppress.data)
        settings['suppress_timestamp'] = self.populate_setting('suppress_timestamp', self.suppress_timestamp.data)

    def populate_from_settings(self, id):
        subscriptions = self.populate_from_setting(id, 'subscriptions')
        if subscriptions:
            self.subscriptions.data = [k if v == True else False for k, v in json.loads(subscriptions).iteritems()]

        self.time_field.starttime.data = self.populate_from_setting(id, 'starttime', default='00:00:00')
        self.time_field.endtime.data = self.populate_from_setting(id, 'endtime', default='23:59:59')
        self.time_field.delaytime.data = self.populate_from_setting(id, 'delay', default=0)
        # HACK: workaround for bad form that was pushed up.
        if self.time_field.delaytime.data is None or self.time_field.delaytime.data == '':
mathewss
Site Admin
Site Admin
 
Posts: 183
Joined: Fri Dec 06, 2013 11:14 am

Re: SMTP blocking and innacurate log timestamps fix/hack

Postby kevin » Wed Mar 21, 2018 8:27 pm

oh, that was just a workaround for people with old databases...
Not an employee of the company. Just here to help and keep things clean.
kevin
Site Admin
Site Admin
 
Posts: 992
Joined: Fri Aug 16, 2013 10:10 am


Return to Code Contributions

Who is online

Users browsing this forum: No registered users and 1 guest