#!/usr/bin/python3.7 ########## ########## ########## this file dated November 23, 2020 ########## there have undoubtably been changes ########## ########## Steve Wolf, PiRV@mail.W8IZ.com ########## ########## # these first lines import prior written code that is a part of Python import datetime, time, smtplib, os, bs4 # used for time, time, SMTP, OS, beautiful soup import RPi.GPIO as GPIO import paho.mqtt.client as mqtt # Import the MQTT library from email.mime.multipart import MIMEMultipart #DONT THINK THIS IS USED from email.mime.text import MIMEText # used for sending the email from requests import get # used for ip address request from w1thermsensor import W1ThermSensor email = "xxx@xxx.xxx" pas = "xxx" sms_gateway = 'xxx@xxx.xxx' smtp = "xxx.xxx.xxx" port = xxx serverAddress = "localhost" #################### # This is a placeholder for the mqtt message from the PiPiRV #################### def mqttMessageFunction (client, userdata, message): global PiRVREARFridgeTemperature, PiRVREARFreezerTemperature, PiRVREARInsideTemperature topic = str(message.topic) message = str(message.payload.decode("utf-8")) print("MQTT " + topic + ": " + message) if message == "PiRV has rebooted": #This forces a send of all temps to initialize PiRV PiRVREARInsideTemperature = -99 PiRVREARFridgeTemperature = -99 PiRVREARFreezerTemperature = -99 search_text="PiRV boot" replacement_text="PiRV boot " + time.strftime('%X %x') modify_page(search_text, replacement_text) if message.startswith("Speed:"): search_text="Speed:" replacement_text=message + " mph last heard from " + time.strftime('%X %x') modify_page(search_text, replacement_text) if message.startswith("Latitude:"): search_text="Latitude:" replacement_text=message modify_page(search_text, replacement_text) if message.startswith("Longitude:"): search_text="Longitude:" replacement_text=message modify_page(search_text, replacement_text) if message.startswith("Bearing:"): search_text="Bearing:" replacement_text=message modify_page(search_text, replacement_text) try: #set up mqtt PiRVREARClient = mqtt.Client("PiRVREAR_mqtt") # Create a MQTT client object PiRVREARClient.connect(serverAddress, 1883) # Connect to the test MQTT broker PiRVREARClient.subscribe("RV") # Subscribe to the topic PiRVREARClient.on_message = mqttMessageFunction # Attach the messageFunction to subscription PiRVREARClient.loop_start() # Start the MQTT client PiRVREARClient.publish("RV", "MQTT server is running") except: SendText("****PiRVREAR ON BOOT MQTT FAILURE*****") print("****PiRVREAR ON BOOT MQTT FAILURE*****") #set up GPIO GPIO.setmode(GPIO.BCM) GPIO.setwarnings(False) switch=26 # the magnetic switch is on GPIO 26 GPIO.setup(switch, GPIO.IN, GPIO.PUD_UP) # The program starts below all the subroutines that will be called. It is at the end. #################### # This is a subroutine to send a message to an email account. # That will be forwarded by the receiving email account to my phone as a text. #################### def SendText(TextToSend): try: # This will start our email server server = smtplib.SMTP(smtp,port) server.starttls() # Now we need to login server.login(email,pas) # Now we use the MIME module to structure our message. msg = MIMEMultipart() msg['From'] = email msg['To'] = sms_gateway # Make sure you add a new line in the subject # msg['Subject'] = TextToSend # Make sure you also add new lines to your body body = TextToSend # and then attach that body furthermore you can also send html content. msg.attach(MIMEText(body, 'plain')) sms = msg.as_string() # print (sms) server.sendmail(email,sms_gateway,sms) # lastly quit the server server.quit() except: print("INTERNAL: Mail server malfunction") ############################################################ # If a device is missing from /dev, the program will crash # if it trys to use it. This checks each device and sets # a True/False flag for each. ############################################################ #################### # Given a path, this checks to see if the device is logged #################### def exists(path): try: os.stat(path) except OSError: return False return True #################### # The test is in a subroutine #################### def test_devices(): global PiRVREARFridgeThermistor PiRVREARFridgeThermistor = False global PiRVREARFreezerThermistor PiRVREARFreezerThermistor = False global PiRVREARInsideThermistor PiRVREARInsideThermistor = False # test for PiRVREAR's Fridge thermistor if exists("/sys/bus/w1/devices/28-05473b6f8900"): print("INTERNAL: PiRVREAR's fridge thermistor is in /dev"); PiRVREARFridgeThermistor = True; else: print("INTERNAL: PiRVREAR's thermistor is missing from dev"); PiRVREARFridgeThermistor=False; search_text = "Fridge:" replacement_text = "Fridge: 99" modify_page(search_text, replacement_text) # test for PiRVREAR's Freezer thermistor if exists("/sys/bus/w1/devices/28-05473b5d8100"): print("INTERNAL: PiRVREAR's freezer thermistor is in /dev"); PiRVREARFreezerThermistor = True; else: print("INTERNAL: PiRVREAR's freezer thermistor is missing from dev"); PiRVREARFreezerThermistor=False; search_text = "Freezer:" replacement_text = "Freezer: 99" modify_page(search_text, replacement_text) # test for PiRVREAR's inside thermistor if exists("/sys/bus/w1/devices/28-04473b687400"): # Set up temperature sensor print("INTERNAL: PiRVREAR's inside thermistor is in /dev"); PiRVREARInsideThermistor = True; else: print("INTERNAL: PiRVREAR's inside thermistor is missing from dev"); PiRVREARInsideThermistor=False; search_text = "Inside:" replacement_text = "Inside: 99" modify_page(search_text, replacement_text) #################### # modify a line in the webpage # this means something changed and needs to be memorialized #################### def modify_page(search_text, replacement_text): # load the file with open("/var/www/html/PiRV.html") as inf: txt = inf.read() soup = bs4.BeautifulSoup(txt, "lxml") soup.find(text=lambda x: x.startswith(search_text)).replace_with(replacement_text) # save the file again with open("/var/www/html/PiRV.html", "w") as outf: outf.write(str(soup)) #################### # find a line in the webpage # this is checking to see if something else should happen # like checking if an alarm is on so that a text can be sent # or the alarm is off so the text is not sent # if the response is "None" the string is not on the page #################### def find_on_page(search_text): global soup found_on_page = "" # load the file with open("/var/www/html/PiRV.html") as inf: txt = inf.read() soup = bs4.BeautifulSoup(txt, "lxml") found_on_page = (soup.find(text=lambda x: x.startswith(search_text))) return(found_on_page) #################### #################### # THIS IS THE START OF THE MAIN PROGRAM #################### #################### # INITIALIZATION # if we rebooted, then we set the reboot flag here global PiRVREARFridgeTemperature global PiRVREARFreezerTemperature global PiRVREARInsideTemperature global soup JustBooted=True JustBootedIP=True # set TimeOfLastTemp PiRVREARFridgeTemperature=0 PiRVREARFreezerTemperature=0 PiRVREARInsideTemperature=0 #Alarm handling flags FirstEntryAlarmTime=True NextEntryAlarmTime=0 FirstIntAlarmTime=True NextIntAlarmTime=0 FirstFridgeAlarmTime=True NextFridgeAlarmTime=0 FirstFreezerAlarmTime=True NextFreezerAlarmTime=0 #Entry alarm variables EntrySecure=True EntryAlarm=True EntryAlarmTime=0 #Initialize web page alarm lines in case rebooted in alarm try: replacement_text = 'Entry Alarm: SECURE at '+ time.strftime('%X %x') search_text = 'Entry Alarm:' modify_page(search_text, replacement_text) except: x=0 try: replacement_text = 'Internal Alarm: WITHIN LIMITS at '+ time.strftime('%X %x') search_text = 'Internal Alarm:' modify_page(search_text, replacement_text) except: x=0 try: replacement_text = 'Fridge temp: WITHIN LIMITS at '+ time.strftime('%X %x') search_text = 'Fridge temp:' modify_page(search_text, replacement_text) except: x=0 try: replacement_text = 'Freezer temp: WITHIN LIMITS at '+ time.strftime('%X %x') search_text = 'Freezer temp:' modify_page(search_text, replacement_text) except: x=0 # Send the IP address at boot: try: IPAddress = get('https://api.ipify.org').text except: IPAddress="unreachable" NextIPCheck=time.time() + 60 # going to check every minute if IPAddress != "unreachable": SendText("PiRVREAR REBOOT IP: " + IPAddress + " at " + time.strftime('%X %x')) t=time.localtime() LastIPDate = t.tm_mday JustBootedIP=False search_text="PiRVREAR boot" replacement_text="PiRVREAR boot " + time.strftime('%X %x') modify_page(search_text, replacement_text) search_text="IP:" replacement_text="IP: " + IPAddress modify_page(search_text, replacement_text) # Test for device presence test_devices() #Set up temperature sensors if PiRVREARFridgeThermistor == True: PiRVREARFridgeSensor = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, "05473b6f8900") if PiRVREARFreezerThermistor == True: PiRVREARFreezerSensor = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, "05473b5d8100") if PiRVREARInsideThermistor == True: PiRVREARInsideSensor = W1ThermSensor(W1ThermSensor.THERM_SENSOR_DS18B20, "04473b687400") #################### # THIS IS THE MAIN PROGRAM LOOP #################### while True: # ##### Check for a new IP address as unreachable on boot # if JustBootedIP == True and time.time() > NextIPCheck: # Get new IP address try: NewIPAddress = get('https://api.ipify.org').text except: NewIPAddress="unreachable" # Check if it matches if NewIPAddress != IPAddress: SendText("PiRVREAR NETWORK REBOOT IP: " + NewIPAddress) IPAddress=NewIPAddress t=time.localtime() LastIPDate = t.tm_mday JustBootedIP=False search_text="IP:" replacement_text="IP: " + IPAddress modify_page(search_text, replacement_text) else: NextIPCheck = time.time()+60 # # We send a ping with a check of the IP at midnight # t=time.localtime() CurrentIPDate = t.tm_mday if JustBootedIP == False and LastIPDate != CurrentIPDate: # we are at midnight, check the IP address try: NewIPAddress = get('https://api.ipify.org').text except: NewIPAddress="unreachable" # Check if it matches if NewIPAddress != IPAddress: SendText("PiRVREAR MIDNIGHT ***NEW*** IP: " + NewIPAddress) IPAddress=NewIPAddress t=time.localtime() LastIPDate = t.tm_mday else: SendText("PiRVREAR Midnight IP: " + IPAddress) LastIPDate = t.tm_mday # Get the sensor temperatures: PiRVREARFridgeTemperature, PiRVREARFreezerTemperature, PiRVREARInsideTemperature if PiRVREARFridgeThermistor == True: try: temp = PiRVREARFridgeSensor.get_temperature(W1ThermSensor.DEGREES_F) # If the temp has changed, print it to the web page. if temp < (PiRVREARFridgeTemperature-1.5) or temp > (PiRVREARFridgeTemperature + 1.5): PiRVREARFridgeTemperature = temp search_text = "Fridge:" replacement_text = "Fridge: " + str(int(temp)) + " F " + time.strftime('%X %x') # Print it to the web page modify_page(search_text, replacement_text) print("INTERNAL: Fridge update at " + time.strftime('%X %x') + " " + str(int(temp))) #reporting back reportString = replacement_text PiRVREARClient.publish("RV", reportString) print("Published: " + reportString) except: print("INTERNAL: Fridge temp sensor error at " + time.strftime('%X %x')) if PiRVREARFreezerThermistor == True: try: temp = PiRVREARFreezerSensor.get_temperature(W1ThermSensor.DEGREES_F) # If the temp has changed, print it to the web page. if temp < (PiRVREARFreezerTemperature-1.5) or temp > (PiRVREARFreezerTemperature + 1.5): PiRVREARFreezerTemperature = temp search_text = "Freezer:" replacement_text = "Freezer: " + str(int(temp)) + " F " + time.strftime('%X %x') modify_page(search_text, replacement_text) print("INTERNAL: Freezer update at " + time.strftime('%X %x') + " " + str(int(temp))) #reporting back reportString = replacement_text PiRVREARClient.publish("RV", reportString) print("Published: " + reportString) except: print("INTERNAL: Freezer temp sensor error at " + time.strftime('%X %x')) if PiRVREARInsideThermistor == True: try: temp = PiRVREARInsideSensor.get_temperature(W1ThermSensor.DEGREES_F) # If the temp has changed, print it to the web page. global PiRVREARFridgeTemperature if temp < (PiRVREARInsideTemperature-1.5) or temp > (PiRVREARInsideTemperature + 1.5): PiRVREARInsideTemperature = temp search_text = "Inside:" replacement_text = "Inside: " + str(int(temp)) + " F " + time.strftime('%X %x') modify_page(search_text, replacement_text) print("INTERNAL: Inside update at " + time.strftime('%X %x') + " " + str(int(temp))) #reporting back reportString = replacement_text PiRVREARClient.publish("RV", reportString) print("Published: " + reportString) except: print("INTERNAL: Inside temp sensor error at " + time.strftime('%X %x')) # Get the alarm switches: EntryAlarm, IntAlarm, FridgeAlarm, FreezerAlarm EntryAlarm = find_on_page("Entry alarm is") IntAlarm = find_on_page("Int temp alarm is") FridgeAlarm = find_on_page("Fridge alarm is") FreezerAlarm = find_on_page("Freezer alarm is") # Get the current temps for the alarms from the web page: AlarmTempInside, AlarmTempFridge, AlarmTempFreezer AlarmTempInside = int(str(find_on_page("Inside:"))[8:10]) AlarmTempFridge = int(str(find_on_page("Fridge:"))[8:10]) AlarmTempFreezer = int(str(find_on_page("Freezer:"))[9:11]) # Get the temperatures that will trigger the alarms y = find_on_page('xxxxx') # this creates a soup AlarmTriggerInsideHigh = int(str(soup.find(id='lblTempHi'))[22:24]) AlarmTriggerInsideLow = int(str(soup.find(id='lblTempLo'))[22:24]) AlarmTriggerFridge = int(str(soup.find(id='lblFridgeTP'))[24:26]) AlarmTriggerFreezer = int(str(soup.find(id='lblFreezerTP'))[25:27]) #INTERNAL TEMP ALARM #We always set the website lines if the temps require it if AlarmTempInside > AlarmTriggerInsideHigh or AlarmTempInside < AlarmTriggerInsideLow: if FirstIntAlarmTime == True: search_text = 'Internal Alarm:' replacement_text = 'Internal Alarm: ALARM!!! at '+ time.strftime('%X %x') modify_page(search_text, replacement_text) FirstIntAlarmTime = False NextIntAlarmTime=0 # We see if we are monitoring the fridge if IntAlarm == "Int temp alarm is ON..": if time.time() > NextIntAlarmTime: SendText("PiRV ALARM! Internal temp now " + str(AlarmTempInside)) print("INTERNAL: INT TEMP ALARM - text sent at "+ time.strftime('%X %x')) NextIntAlarmTime = time.time() + 300 # every five minutes else: #Here we reset everything - if the below time is not zero, we just went out of alarm if NextIntAlarmTime != 0: #resetting the web page search_text = 'Internal Alarm:' replacement_text = 'Internal Alarm: WITHIN LIMITS at '+ time.strftime('%X %x') modify_page(search_text, replacement_text) print("INTERNAL: INT TEMP ALARM cleared") #sets the page now flag and change page flag NextIntAlarmTime = 0 FirstIntAlarmTime = True #FRIDGE ALARM if AlarmTempFridge > AlarmTriggerFridge: if FirstFridgeAlarmTime == True: search_text = 'Fridge temp: WITHIN LIMITS' replacement_text = 'Fridge temp: ALARM!!! at '+ time.strftime('%X %x') modify_page(search_text, replacement_text) FirstFridgeAlarmTime = False NextFridgeAlarmTime=0 # We see if we are monitoring the fridge if FridgeAlarm == "Fridge alarm is ON....": if time.time() > NextFridgeAlarmTime: SendText("PiRV ALARM! Fridge temp now " + str(AlarmTempFridge)) print("INTERNAL: FRIDGE ALARM - text sent at "+ time.strftime('%X %x')) NextFridgeAlarmTime = time.time() + 300 # every five minutes else: #Here we reset everything - if the below time is not zero, we just went out of alarm if NextFridgeAlarmTime != 0: #resetting the web page search_text = 'Fridge temp: ALARM!!!' replacement_text = 'Fridge temp: WITHIN LIMITS at '+ time.strftime('%X %x') modify_page(search_text, replacement_text) print("INTERNAL: FRIDGE ALARM cleared") #sets the page now flag and change page flag NextFridgeAlarmTime = 0 FirstFridgeAlarmTime = True #FREEZER ALARM if AlarmTempFreezer > AlarmTriggerFreezer: if FirstFreezerAlarmTime == True: search_text = 'Freezer temp: WITHIN LIMITS' replacement_text = 'Freezer temp: ALARM!!! at '+ time.strftime('%X %x') modify_page(search_text, replacement_text) FirstFreezerAlarmTime = False NextFreezerAlarmTime=0 # We see if we are monitoring the fridge if FreezerAlarm == "Freezer alarm is ON..": if time.time() > NextFreezerAlarmTime: SendText("PiRV ALARM! Freezer temp now " + str(AlarmTempFreezer)) print("INTERNAL: FREEZER ALARM - text sent at "+ time.strftime('%X %x')) NextFreezerAlarmTime = time.time() + 300 # every five minutes else: #Here we reset everything - if the below time is not zero, we just went out of alarm if NextFreezerAlarmTime != 0: #resetting the web page search_text = 'Freezer temp: ALARM!!!' replacement_text = 'Freezer temp: WITHIN LIMITS at '+ time.strftime('%X %x') modify_page(search_text, replacement_text) print("INTERNAL: FREEZER TEMP ALARM cleared") #sets the page now flag and change page flag NextFreezerAlarmTime = 0 FirstFreezerAlarmTime = True # PROCESSING ENTRY ALARM # If alarmed and both the alarm is on and a text has not been sent # for the last hour, send a text button_state = GPIO.input(switch) if button_state == GPIO.HIGH and EntrySecure==True: search_text = 'Entry Alarm: SECURE' replacement_text = 'Entry Alarm: ALARM!!! at '+ time.strftime('%X %x') modify_page(search_text, replacement_text) EntrySecure = False if EntryAlarm == "Entry alarm is ON......": SendText("PiRV: ENTRY ALARM!!!") EntryAlarmTime = time.time() print("INTERNAL: ENTRY ALARM!!!") else: if button_state != GPIO.HIGH and EntrySecure==False: search_text = 'Entry Alarm:' replacement_text = 'Entry Alarm: SECURE at '+ time.strftime('%X %x') modify_page(search_text, replacement_text) EntrySecure = True if EntryAlarmTime != 0 and find_on_page("Entry alarm is ON") != "None": SendText("PiRV: Entry alarm now cleared") EntryAlarmTime=0 print("INTERNAL: Entry alarm now cleared") # this resends the alarm every one hour found_line = find_on_page("Entry alarm is") if "Entry alarm is ON......" in found_line: if EntrySecure==False: if time.time() - EntryAlarmTime > 3600: #once per hour SendText("PiRV: STILL IN ENTRY ALARM!!!") EntryAlarmTime = time.time()