Browse code

add sms alert functionality

Gandolf authored on 12/09/2021 02:11:18
Showing 2 changed files
... ...
@@ -49,8 +49,19 @@ import json
49 49
 import ina260 # power sensor
50 50
 import tmp102 # temperature sensor
51 51
 
52
+# Import custom libraries
53
+import smsalert
54
+
52 55
     ### ENVIRONMENT ###
56
+
53 57
 _USER = os.environ['USER']
58
+_HOSTNAME = os.uname()[1]
59
+
60
+    ### SMS RECIPIENTS ###
61
+
62
+_SMS_CALLSIGN = '{your callsign}'
63
+_SMS_PASSCODE = '{your passcode}'
64
+_SMS_PHONE_NUMBER = '{your phone number}'
54 65
 
55 66
     ### SENSOR BUS ADDRESSES ###
56 67
 
... ...
@@ -89,11 +100,16 @@ _CHART_WIDTH = 600
89 100
 _CHART_HEIGHT = 150
90 101
 # chart average line color
91 102
 _AVERAGE_LINE_COLOR = '#006600'
103
+# low voltage alert threshold
104
+_DEFAULT_CRITICAL_LOW_VOLTAGE = 12.0
92 105
 
93 106
    ### GLOBAL VARIABLES ###
94 107
 
95
-# Container for sensor objects.
96
-dSensors = {}
108
+# Sensor instance objects.
109
+power1 = None
110
+battemp = None
111
+ambtemp = None
112
+sms = None
97 113
 
98 114
 # turns on or off extensive debugging messages
99 115
 debugMode = False
... ...
@@ -107,6 +123,8 @@ chartUpdateInterval = _CHART_UPDATE_INTERVAL
107 123
 failedUpdateCount = 0
108 124
 # sensor status
109 125
 deviceOnline = False
126
+# sms message sent status
127
+bSMSmsgSent = False
110 128
 
111 129
   ###  PRIVATE METHODS  ###
112 130
 
... ...
@@ -182,11 +200,11 @@ def getSensorData(dData):
182 200
     dData["time"] = getTimeStamp()
183 201
  
184 202
     try:
185
-        dData["current"] = dSensors['power'].getCurrent()
186
-        dData["voltage"] = dSensors['power'].getVoltage()
187
-        dData["power"] = dSensors['power'].getPower()
188
-        dData["battemp"] = dSensors['battemp'].getTempF()
189
-        dData["ambtemp"] = dSensors['ambtemp'].getTempF()
203
+        dData["current"] = power1.getCurrent()
204
+        dData["voltage"] = power1.getVoltage()
205
+        dData["power"] =   power1.getPower()
206
+        dData["battemp"] = battemp.getTempF()
207
+        dData["ambtemp"] = ambtemp.getTempF()
190 208
     except Exception as exError:
191 209
         print("%s sensor error: %s" % (getTimeStamp(), exError))
192 210
         return False
... ...
@@ -196,6 +214,34 @@ def getSensorData(dData):
196 214
     return True
197 215
 ## end def
198 216
 
217
+def convertData(dData):
218
+    """
219
+    Converts data items and verifies threshold crossings.
220
+    Parameters: dData - a dictionary object that contains the sensor data
221
+    Returns: True if successful, False otherwise
222
+    """
223
+    global bSMSmsgSent
224
+
225
+    if not bSMSmsgSent and dData["voltage"] <= _DEFAULT_CRITICAL_LOW_VOLTAGE:
226
+        # Format a text alert message.
227
+        message = "%s %s low voltage alert: %s volts" % \
228
+                  (getTimeStamp(), _HOSTNAME, dData["voltage"])
229
+        print(message)
230
+        # Send the text alert to recipient phone numbers.
231
+        sms.sendSMS(_SMS_PHONE_NUMBER, message)
232
+        bSMSmsgSent = True
233
+    elif bSMSmsgSent and dData["voltage"] > _DEFAULT_CRITICAL_LOW_VOLTAGE:
234
+        # Format a text alert message.
235
+        message = "%s %s voltage normal: %s volts" % \
236
+                  (getTimeStamp(), _HOSTNAME, dData["voltage"])
237
+        print(message)
238
+        # Send the text alert to recipient phone numbers.
239
+        sms.sendSMS(_SMS_PHONE_NUMBER, message)
240
+        bSMSmsgSent = False
241
+    ## end if
242
+    return True
243
+## end def
244
+
199 245
 def writeOutputFile(dData):
200 246
     """
201 247
     Write sensor data items to the output data file, formatted as 
... ...
@@ -492,13 +538,15 @@ def getCLarguments():
492 538
         index += 1
493 539
 ##end def
494 540
 
495
-def main():
541
+def main_setup():
496 542
     """
497 543
     Handles timing of events and acts as executive routine managing
498 544
     all other functions.
499 545
     Parameters: none
500 546
     Returns: nothing
501 547
     """
548
+    global power1, battemp, ambtemp, sms
549
+
502 550
     signal.signal(signal.SIGTERM, terminateAgentProcess)
503 551
     signal.signal(signal.SIGINT, terminateAgentProcess)
504 552
 
... ...
@@ -518,15 +566,15 @@ def main():
518 566
         exit(1)
519 567
 
520 568
     # Create sensor objects.  This also initializes each sensor.
521
-    dSensors['power'] = ina260.ina260(_PWR_SENSOR_ADDR, _BUS_NUMBER,
569
+    power1 = ina260.ina260(_PWR_SENSOR_ADDR, _BUS_NUMBER,
522 570
                             debug=debugMode)
523
-    dSensors['battemp'] = tmp102.tmp102(_BAT_TMP_SENSOR_ADDR, _BUS_NUMBER,
571
+    battemp = tmp102.tmp102(_BAT_TMP_SENSOR_ADDR, _BUS_NUMBER,
524 572
                             debug=debugMode)
525
-    dSensors['ambtemp'] = tmp102.tmp102(_AMB_TMP_SENSOR_ADDR, _BUS_NUMBER,
573
+    ambtemp = tmp102.tmp102(_AMB_TMP_SENSOR_ADDR, _BUS_NUMBER,
526 574
                             debug=debugMode)
575
+    # Create instance of SMS alert class
576
+    sms = smsalert.smsalert(_SMS_CALLSIGN, _SMS_PASSCODE, debug=debugMode)
527 577
 
528
-    # Enter main loop.
529
-    main_loop()
530 578
 ## end def
531 579
 
532 580
 def main_loop():
... ...
@@ -550,9 +598,13 @@ def main_loop():
550 598
             dData = {}
551 599
 
552 600
             # Get the data from the sensors.
553
-            result =getSensorData(dData)
601
+            result = getSensorData(dData)
554 602
  
555
-            # If get data successful, write data to data files.
603
+            # If get data successful, convert the data and verify thresholds.
604
+            if result:
605
+                result = convertData(dData)
606
+
607
+            # If convert data successful, write data to data files.
556 608
             if result:
557 609
                 result = writeOutputFile(dData)
558 610
 
... ...
@@ -589,4 +641,5 @@ def main_loop():
589 641
 ## end def
590 642
 
591 643
 if __name__ == '__main__':
592
-    main()
644
+    main_setup()
645
+    main_loop()
593 646
new file mode 100755
... ...
@@ -0,0 +1,79 @@
1
+#!/usr/bin/python
2
+
3
+# courtsey ruler for editing script - 80 characters max line length
4
+#2345678901234567890123456789012345678901234567890123456789012345678901234567890
5
+
6
+import telnetlib
7
+
8
+_DEFAULT_HOST = '{your APRS-IS server hostname}'
9
+_DEFAULT_PORT = '{your APRS-IS message port}'
10
+_DEFAULT_SERVER = '{your APRS-IS server name}'
11
+
12
+class smsalert:
13
+
14
+    def __init__(self, callsign, passcode, host=_DEFAULT_HOST, \
15
+      port=_DEFAULT_PORT, server=_DEFAULT_SERVER, debug=False):
16
+        """
17
+        Initialize an instance of this class.
18
+        Parameters:
19
+          callsign - amateur radio callsign of user (must be verified)
20
+          passcode - passcode for verified callsign
21
+          host - domain name or IP address of APRS-IS server
22
+          port - port on which the APRS-IS server receives messages
23
+          server - APRS service name
24
+          debug - set equal to True for debug output
25
+        Returns: nothing
26
+        """
27
+        # Initialize class instance variables.
28
+        self.callsign = callsign
29
+        self.passcode = passcode
30
+        self.host = host
31
+        self.port = port
32
+        self.server = server
33
+        self.debug = debug
34
+    ## end def
35
+
36
+    def sendSMS(self, phone_number, text_message):
37
+        """
38
+        Sends an SMS text message to the provided phone number.
39
+        Parameters:
40
+          phone_number - phone number to which to send the text message
41
+          text_message - text message to be sent to the provided phone number
42
+        Returns: True if successful, False otherwise
43
+        """
44
+        # Establish network connection to APRS-IS server.
45
+        tn = telnetlib.Telnet(self.host, self.port)
46
+        tn.read_until('# aprsc 2.1.8-gf8824e8')
47
+
48
+        # Login and verify passcode accepted.
49
+        tn.write('user ' + self.callsign + ' pass ' + self.passcode + '\n')
50
+        response = tn.read_until(self.server)
51
+        if self.debug:
52
+            print('response: ' + response[2:])
53
+        if not response.find('verified'):
54
+            print('smsalert error: unverified user')
55
+            del tn
56
+            return False
57
+
58
+        # Format and send SMS message to SMS gateway.
59
+        cmd = '%s>%s::SMSGTE:@%s %s' % \
60
+          (self.callsign, self.server, phone_number, text_message)
61
+        if self.debug:
62
+            print('cmd: ' + cmd)
63
+        tn.write(cmd + '\n')
64
+        del tn
65
+        return True
66
+    ## end def
67
+## end class
68
+
69
+def test_smsalert():
70
+    # Initialize a telnet instance.  Default host, port, and server
71
+    # automatically defined if not included in function call.
72
+    sm = smsalert('{your callsign}', '{your passcode}', debug=True)
73
+
74
+    # Send a text message to a phone number.
75
+    sm.sendSMS('{your phone number}', 'Test message send from smsalert.py')
76
+## end def
77
+
78
+if __name__ == '__main__':
79
+    test_smsalert()