From 3688d6253968c6af29f2f321ddf93f95f6952a64 Mon Sep 17 00:00:00 2001 From: Patrick McDonagh Date: Tue, 3 Jul 2018 16:51:51 -0500 Subject: [PATCH] Adds public_ip_address, fixes pressure graph --- html_templates/Device.html | 47 +++++++++++++ html_templates/Overview.html | 2 +- python_driver/channels_pondlevel.csv | 21 ++++++ python_driver/config.txt | 19 ++--- python_driver/driverConfig.json | 8 ++- python_driver/file_logger.py | 18 +++++ python_driver/pondlevel.py | 100 ++++++++++++++++----------- python_driver/utilities.py | 51 ++++++++++++++ 8 files changed, 213 insertions(+), 53 deletions(-) create mode 100644 html_templates/Device.html create mode 100644 python_driver/channels_pondlevel.csv create mode 100644 python_driver/file_logger.py create mode 100644 python_driver/utilities.py diff --git a/html_templates/Device.html b/html_templates/Device.html new file mode 100644 index 0000000..56c5df1 --- /dev/null +++ b/html_templates/Device.html @@ -0,0 +1,47 @@ +
+
+

Public IP Address

+

<%= channels["pondlevel.public_ip_address"].value %>

+

+ + + +
+
+
+ + diff --git a/html_templates/Overview.html b/html_templates/Overview.html index 8b9862b..9611242 100644 --- a/html_templates/Overview.html +++ b/html_templates/Overview.html @@ -95,7 +95,7 @@
-
+
<% } %> diff --git a/python_driver/channels_pondlevel.csv b/python_driver/channels_pondlevel.csv new file mode 100644 index 0000000..52265d3 --- /dev/null +++ b/python_driver/channels_pondlevel.csv @@ -0,0 +1,21 @@ +id,name,deviceTypeId,fromMe,io,subTitle,helpExplanation,channelType,dataType,defaultValue,regex,regexErrMsg,units,min,max,change,guaranteedReportPeriod,minReportTime +12764,pond_level,412,FALSE,readonly,Pond Level,Level in Ft.,device,float,,,,,,,,, +12769,high_level_limit,412,FALSE,readwrite,High Level Limit,Limit in ft.,user input,float,25,,,,,,,, +12770,low_level_limit,412,FALSE,readwrite,Low Level Limit, Limit in ft.,user input,float,1,,,,,,,, +13352,setrawmax,412,FALSE,readwrite,Raw Max Setpoint,in mA,device,float,,,,,,,,, +13353,setrawmin,412,FALSE,readwrite,Raw Min Setpoint,in mA,device,float,,,,,,,,, +13354,seteumax,412,FALSE,readwrite,EU Max Setpoint,in Ft.,device,float,,,,,,,,, +13355,seteumin,412,FALSE,readwrite,EU Min Setpoint,in Ft.,device,float,,,,,,,,, +13356,log,412,FALSE,readwrite,Log,Log,device,string,,,,,,,,, +13357,sync,412,FALSE,readwrite,Sync,Sycn,device,string,,,,,,,,, +13358,calibration_data,412,FALSE,readonly,Calibration Data,All calibration data,device,string,,,,,,,,, +13359,addcalibrationpoint,412,FALSE,readwrite,Add Calibration Point,Add a single calibration point,device,string,,,,,,,,, +13360,deletecalibrationpoint,412,FALSE,readwrite,Delete Calibration Point,Delete a Calibration Point,device,integer,,,,,,,,, +13361,pond_volume,412,FALSE,readonly,Pond Volume,in BBL,device,float,0,,,,,,,, +13642,pressure_psi,412,FALSE,readonly,Pressure,in PSI,device,float,,,,,,,,, +13643,setpsirawmin,412,FALSE,readwrite,Pressure Raw Min Setpoint,in PSI,device,float,,,,,,,,, +13644,setpsirawmax,412,FALSE,readwrite,Pressure Raw Max Setpoint,in PSI,device,float,,,,,,,,, +13645,setpsieumax,412,FALSE,readwrite,Pressure EU Max Setpoint,in PSI,device,float,,,,,,,,, +13646,setpsieumin,412,FALSE,readwrite,Pressure EU Min Setpoint,in PSI,device,float,,,,,,,,, +13647,high_pressure_limit,412,FALSE,readwrite,High Pressure Input,Send an alert when pressure > high_pressure_input,user input,float,30,,,,,,,, +,public_ip_address,412,FALSE,readonly,Public IP Address,Network Address,device,string,,,,,,,,, \ No newline at end of file diff --git a/python_driver/config.txt b/python_driver/config.txt index 9995b85..27aa8af 100644 --- a/python_driver/config.txt +++ b/python_driver/config.txt @@ -1,10 +1,13 @@ { - "files": { - "file2": "calibration_db.py", - "file1": "pondlevel.py" - }, + "driverFileName": "pondlevel.py", "deviceName": "pondlevel", - "driverId": "0130", - "releaseVersion": "3", - "driverFileName": "pondlevel.py" -} + "driverId": "130", + "releaseVersion": "4", + "files": { + "file1": "pondlevel.py", + "file2": "calibration_db.py", + "file3": "persistence.py", + "file4": "utilities.py", + "file5": "file_logger.py" + } +} \ No newline at end of file diff --git a/python_driver/driverConfig.json b/python_driver/driverConfig.json index f73b9f4..8f03a70 100644 --- a/python_driver/driverConfig.json +++ b/python_driver/driverConfig.json @@ -4,8 +4,10 @@ "driverId": "0130", "additionalDriverFiles": [ "calibration_db.py", - "persistence.py" + "persistence.py", + "utilities.py", + "file_logger.py" ], - "version": 1, + "version": 4, "s3BucketName": "pondlevel" -} +} \ No newline at end of file diff --git a/python_driver/file_logger.py b/python_driver/file_logger.py new file mode 100644 index 0000000..8abf998 --- /dev/null +++ b/python_driver/file_logger.py @@ -0,0 +1,18 @@ +"""Logging setup for pondlevel""" +import logging +from logging.handlers import RotatingFileHandler +import sys + +log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s') +log_file = './pondlevel.log' +my_handler = RotatingFileHandler(log_file, mode='a', maxBytes=500*1024, + backupCount=2, encoding=None, delay=0) +my_handler.setFormatter(log_formatter) +my_handler.setLevel(logging.INFO) +filelogger = logging.getLogger('pondlevel') +filelogger.setLevel(logging.INFO) +filelogger.addHandler(my_handler) + +console_out = logging.StreamHandler(sys.stdout) +console_out.setFormatter(log_formatter) +filelogger.addHandler(console_out) diff --git a/python_driver/pondlevel.py b/python_driver/pondlevel.py index 777266e..4b80d2c 100644 --- a/python_driver/pondlevel.py +++ b/python_driver/pondlevel.py @@ -5,6 +5,8 @@ from device_base import deviceBase import time import json import persistence +from utilities import get_public_ip_address +from file_logger import filelogger as log _ = None @@ -45,11 +47,14 @@ class start(threading.Thread, deviceBase): deviceBase.__init__(self, name=name, number=number, mac=mac, Q=Q, mcu=mcu, companyId=companyId, offset=offset, mqtt=mqtt, Nodes=Nodes) self.daemon = True - self.version = "3" + self.version = "4" self.finished = threading.Event() self.forceSend = False threading.Thread.start(self) + self.public_ip_address = "" + self.public_ip_address_last_checked = 0 + self.PL_RAWMAX = 20.0 self.PL_RAWMIN = 4.0 self.PL_EUMAX = 23.068 @@ -85,9 +90,9 @@ class start(threading.Thread, deviceBase): self.PL_EUMAX = scaling_persist['eu_max'] self.PL_EUMIN = scaling_persist['eu_min'] except KeyError: - print("No persistence data for pond level scaling parameters.") + log.warning("No persistence data for pond level scaling parameters.") except TypeError: - print("No persistence data for pond level scaling parameters.") + log.warning("No persistence data for pond level scaling parameters.") try: self.PSI_RAWMAX = scaling_persist['psi_raw_max'] @@ -95,27 +100,28 @@ class start(threading.Thread, deviceBase): self.PSI_EUMAX = scaling_persist['psi_eu_max'] self.PSI_EUMIN = scaling_persist['psi_eu_min'] except KeyError: - print("No persistence data for pressure scaling parameters.") + log.warning("No persistence data for pressure scaling parameters.") except TypeError: - print("No persistence data for pressure scaling parameters.") + log.warning("No persistence data for pressure scaling parameters.") try: if len(strapping_persist.keys()) > 0: for k in strapping_persist.keys(): self.strapping_table[float(k)] = strapping_persist[k] - print("Using stored strapping table:\n{}".format(self.strapping_table)) + log.info("Using stored strapping table:\n{}".format(self.strapping_table)) else: - print("No stored strapping table found.") + log.warning("No stored strapping table found.") except Exception as e: - print("No stored strapping table. Got Error: {}".format(e)) + log.warning("No stored strapping table. Got Error: {}".format(e)) wait_sec = 30 for i in range(0, wait_sec): - print("pondlevel driver will start in {} seconds".format(wait_sec - i)) + log.info("pondlevel driver will start in {} seconds".format(wait_sec - i)) time.sleep(1) - print("BOOM! Starting pondlevel driver...") + log.info("BOOM! Starting pondlevel driver...") # db.setup_calibration_table(drop_first=False) self.send_calibration_points() + self._check_ip_address() self.sendtodb("setrawmax", self.PL_RAWMAX, 0) self.sendtodb("setrawmin", self.PL_RAWMIN, 0) @@ -128,40 +134,39 @@ class start(threading.Thread, deviceBase): self.sendtodb("setpsieumin", self.PSI_EUMIN, 0) send_loops = 0 + ip_check_after = 60 + while True: + now = time.time() pl_send_now = False psi_send_now = False if self.forceSend: - print "FORCE SEND: TRUE" + log.warning("FORCE SEND: TRUE") pl_send_now = True psi_send_now = True + try: + mcu_status = self.mcu.getDict() # Gets a dictionary of the IO states + cloop_val = float(mcu_status['cloop']) + analog1_val = float(mcu_status['analog1']) + temp_pl = scale_to_eu(cloop_val, self.PL_RAWMAX, self.PL_RAWMIN, self.PL_EUMAX, self.PL_EUMIN) + temp_psi = scale_to_eu(analog1_val, self.PSI_RAWMAX, self.PSI_RAWMIN, self.PSI_EUMAX, self.PSI_EUMIN) + except AttributeError as err: + log.error("Could not connect to MCU object, this device might not have an MCU: {}".format(err)) + except KeyError as err: + log.error("Connected to MCU, but {} does not exist.".format(err)) - mcu_status = self.mcu.getDict() # Gets a dictionary of the IO states - cloop_val = float(mcu_status['cloop']) - analog1_val = float(mcu_status['analog1']) + - temp_pl = scale_to_eu(cloop_val, self.PL_RAWMAX, self.PL_RAWMIN, self.PL_EUMAX, self.PL_EUMIN) - temp_psi = scale_to_eu(analog1_val, self.PSI_RAWMAX, self.PSI_RAWMIN, self.PSI_EUMAX, self.PSI_EUMIN) - - # Check if pond level needs to be sent - # 4/30/2018 -- COMMENTING OUT - # Pond level was sending irregular data - # -- Patrick McDonagh - # if abs(temp_pl - pond_level['value']) > send_delta: - # print("Sending pond level {} due to value change".format(temp_pl)) - # pl_send_now = True - # 4/30/2018 -- END COMMENTING OUT - - if (time.time() - pond_level['timestamp']) > send_time: - print("Sending pond level {} due to time limit".format(temp_pl)) + if (now - pond_level['timestamp']) > send_time: + log.info("Sending pond level {} due to time limit".format(temp_pl)) pl_send_now = True # Check if pressure needs to be sent if abs(temp_psi - psi_reading['value']) > send_delta: - print("Sending pressure psi {} due to value change".format(temp_psi)) + log.info("Sending pressure psi {} due to value change".format(temp_psi)) psi_send_now = True - if (time.time() - psi_reading['timestamp']) > send_time: - print("Sending pressure psi {} due to time limit".format(temp_psi)) + if (now - psi_reading['timestamp']) > send_time: + log.info("Sending pressure psi {} due to time limit".format(temp_psi)) psi_send_now = True if pl_send_now: @@ -174,23 +179,36 @@ class start(threading.Thread, deviceBase): self.sendtodb('pond_volume', pond_volume, 0) pond_level['value'] = temp_pl - pond_level['timestamp'] = time.time() + pond_level['timestamp'] = now if psi_send_now: self.sendtodb('pressure_psi', temp_psi, 0) psi_reading['value'] = temp_psi - psi_reading['timestamp'] = time.time() + psi_reading['timestamp'] = now - print("pondlevel driver still alive...") + if (now - self.public_ip_address_last_checked) > ip_check_after or self.forceSend: + self._check_ip_address() + + log.info("pondlevel driver still alive...") if self.forceSend: if send_loops > 2: - print("Turning off forceSend") + log.info("Turning off forceSend") self.forceSend = False send_loops = 0 else: send_loops += 1 + + time.sleep(10) + def _check_ip_address(self): + """Check the public IP address and send to Meshify if changed.""" + self.public_ip_address_last_checked = time.time() + test_public_ip = get_public_ip_address() + if not test_public_ip == self.public_ip_address: + self.sendtodb('public_ip_address', test_public_ip, 0) + self.public_ip_address = test_public_ip + def send_calibration_points(self): """Send calibration data from database to Meshify.""" strapping_table_list = [] @@ -212,14 +230,14 @@ class start(threading.Thread, deviceBase): """Add a JSON calibration point to the database.""" try: new_point = json.loads(value.replace("'", '"')) - print("Trying to add Height: {}, volume: {}".format(new_point['height'], new_point['volume'])) + log.info("Trying to add Height: {}, volume: {}".format(new_point['height'], new_point['volume'])) self.strapping_table[float(new_point['height'])] = float(new_point['volume']) self.store_strapping_table() # db.insert_calibration_data(new_point['height'], new_point['volume']) self.send_calibration_points() return True except Exception as e: - print("EXCEPTION: {}".format(e)) + log.error("EXCEPTION: {}".format(e)) def pondlevel_deletecalibrationpoint(self, name, value): """Delete a calibration point from the database.""" @@ -230,7 +248,7 @@ class start(threading.Thread, deviceBase): self.send_calibration_points() return True except Exception as e: - print("Error deleting calibration point: {}".format(e)) + log.error("Error deleting calibration point: {}".format(e)) return(e) def store_scaling_data(self): @@ -261,8 +279,8 @@ class start(threading.Thread, deviceBase): for height_i in height_list: strapping_table_list.append([float(height_i), float(self.strapping_table[height_i])]) - print("Strapping Table Data\n===") - print(strapping_table_list) + # print("Strapping Table Data\n===") + # print(strapping_table_list) cal_min_height = strapping_table_list[0][0] cal_max_height = strapping_table_list[-1][0] @@ -286,7 +304,7 @@ class start(threading.Thread, deviceBase): return line_m * height + line_b except Exception as e: - print("Error in get_volume_for_height: {}".format(e)) + log.error("Error in get_volume_for_height: {}".format(e)) return 0.0 def pondlevel_setrawmin(self, name, value): diff --git a/python_driver/utilities.py b/python_driver/utilities.py new file mode 100644 index 0000000..58c7ab0 --- /dev/null +++ b/python_driver/utilities.py @@ -0,0 +1,51 @@ +"""Utility functions for the driver.""" +import socket +import struct + + +def get_public_ip_address(): + """Find the public IP Address of the host device.""" + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.connect(("8.8.8.8", 80)) + ip = s.getsockname()[0] + s.close() + return ip + + +def int_to_float16(int_to_convert): + """Convert integer into float16 representation.""" + bin_rep = ('0' * 16 + '{0:b}'.format(int_to_convert))[-16:] + sign = 1.0 + if int(bin_rep[0]) == 1: + sign = -1.0 + exponent = float(int(bin_rep[1:6], 2)) + fraction = float(int(bin_rep[6:17], 2)) + + if exponent == float(0b00000): + return sign * 2 ** -14 * fraction / (2.0 ** 10.0) + elif exponent == float(0b11111): + if fraction == 0: + return sign * float("inf") + else: + return float("NaN") + else: + frac_part = 1.0 + fraction / (2.0 ** 10.0) + return sign * (2 ** (exponent - 15)) * frac_part + + +def ints_to_float(int1, int2): + """Convert 2 registers into a floating point number.""" + mypack = struct.pack('>HH', int1, int2) + f = struct.unpack('>f', mypack) + print("[{}, {}] >> {}".format(int1, int2, f[0])) + return f[0] + + +def degf_to_degc(temp_f): + """Convert deg F to deg C.""" + return (temp_f - 32.0) * (5.0/9.0) + + +def degc_to_degf(temp_c): + """Convert deg C to deg F.""" + return temp_c * 1.8 + 32.0