... | ... |
@@ -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() |