Added Folders

Add all the driver folders
This commit is contained in:
2019-12-13 12:15:30 -06:00
parent 7ea92c19f6
commit 632dcdb3e8
226 changed files with 54771 additions and 0 deletions

298
plcpond/Channel.py Normal file
View File

@@ -0,0 +1,298 @@
"""Define Meshify channel class."""
import time
import urllib
from pycomm.ab_comm.clx import Driver as ClxDriver
from pycomm.cip.cip_base import CommError, DataError
from file_logger import filelogger as log
TAG_DATAERROR_SLEEPTIME = 5
def binarray(intval):
"""Split an integer into its bits."""
bin_string = '{0:08b}'.format(intval)
bin_arr = [i for i in bin_string]
bin_arr.reverse()
return bin_arr
def read_tag(addr, tag, plc_type="CLX"):
"""Read a tag from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
val = clx.read_tag(tag)
clx.close()
return val
except DataError as err:
clx.close()
time.sleep(TAG_DATAERROR_SLEEPTIME)
log.error("Data Error during readTag({}, {}): {}".format(addr, tag, err))
except CommError:
# err = c.get_status()
clx.close()
log.error("Could not connect during readTag({}, {})".format(addr, tag))
except AttributeError as err:
clx.close()
log.error("AttributeError during readTag({}, {}): \n{}".format(addr, tag, err))
clx.close()
return False
def read_array(addr, tag, start, end, plc_type="CLX"):
"""Read an array from the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
if clx.open(addr, direct_connection=direct):
arr_vals = []
try:
for i in range(start, end):
tag_w_index = tag + "[{}]".format(i)
val = clx.read_tag(tag_w_index)
arr_vals.append(round(val[0], 4))
if arr_vals:
clx.close()
return arr_vals
else:
log.error("No length for {}".format(addr))
clx.close()
return False
except Exception:
log.error("Error during readArray({}, {}, {}, {})".format(addr, tag, start, end))
err = clx.get_status()
clx.close()
log.error(err)
clx.close()
def write_tag(addr, tag, val, plc_type="CLX"):
"""Write a tag value to the PLC."""
direct = plc_type == "Micro800"
clx = ClxDriver()
try:
if clx.open(addr, direct_connection=direct):
try:
initial_val = clx.read_tag(tag)
write_status = clx.write_tag(tag, val, initial_val[1])
clx.close()
return write_status
except DataError as err:
clx_err = clx.get_status()
clx.close()
log.error("--\nDataError during writeTag({}, {}, {}, plc_type={}) -- {}\n{}\n".format(addr, tag, val, plc_type, err, clx_err))
except CommError as err:
clx_err = clx.get_status()
log.error("--\nCommError during write_tag({}, {}, {}, plc_type={})\n{}\n--".format(addr, tag, val, plc_type, err))
clx.close()
return False
class Channel(object):
"""Holds the configuration for a Meshify channel."""
def __init__(self, mesh_name, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
self.mesh_name = mesh_name
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def __str__(self):
"""Create a string for the channel."""
return "{}\nvalue: {}, last_send_time: {}".format(self.mesh_name, self.value, self.last_send_time)
def check(self, new_value, force_send=False):
"""Check to see if the new_value needs to be stored."""
send_needed = False
send_reason = ""
if self.data_type == 'BOOL' or self.data_type == 'STRING':
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.value != new_value:
if self.map_:
if not self.value == self.map_[new_value]:
send_needed = True
send_reason = "value change"
else:
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
else:
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif abs(self.value - new_value) > self.chg_threshold:
send_needed = True
send_reason = "change threshold"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.last_value = self.value
if self.map_:
try:
self.value = self.map_[new_value]
except KeyError:
log.error("Cannot find a map value for {} in {} for {}".format(new_value, self.map_, self.mesh_name))
self.value = new_value
else:
self.value = new_value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed
def read(self):
"""Read the value."""
pass
def identity(sent):
"""Return exactly what was sent to it."""
return sent
class ModbusChannel(Channel):
"""Modbus channel object."""
def __init__(self, mesh_name, register_number, data_type, chg_threshold, guarantee_sec, channel_size=1, map_=False, write_enabled=False, transform_fn=identity):
"""Initialize the channel."""
super(ModbusChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.mesh_name = mesh_name
self.register_number = register_number
self.channel_size = channel_size
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.transform_fn = transform_fn
def read(self, mbsvalue):
"""Return the transformed read value."""
return self.transform_fn(mbsvalue)
class PLCChannel(Channel):
"""PLC Channel Object."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False, plc_type='CLX'):
"""Initialize the channel."""
super(PLCChannel, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
self.plc_type = plc_type
def read(self):
"""Read the value."""
plc_value = None
if self.plc_tag and self.plc_ip:
read_value = read_tag(self.plc_ip, self.plc_tag, plc_type=self.plc_type)
if read_value:
plc_value = read_value[0]
return plc_value
class BoolArrayChannels(Channel):
"""Hold the configuration for a set of boolean array channels."""
def __init__(self, ip, mesh_name, plc_tag, data_type, chg_threshold, guarantee_sec, map_=False, write_enabled=False):
"""Initialize the channel."""
super(BoolArrayChannels, self).__init__(mesh_name, data_type, chg_threshold, guarantee_sec, map_, write_enabled)
self.plc_ip = ip
self.mesh_name = mesh_name
self.plc_tag = plc_tag
self.data_type = data_type
self.last_value = None
self.value = None
self.last_send_time = 0
self.chg_threshold = chg_threshold
self.guarantee_sec = guarantee_sec
self.map_ = map_
self.write_enabled = write_enabled
def compare_values(self, new_val_dict):
"""Compare new values to old values to see if the values need storing."""
send = False
for idx in new_val_dict:
try:
if new_val_dict[idx] != self.last_value[idx]:
send = True
except KeyError:
log.error("Key Error in self.compare_values for index {}".format(idx))
send = True
return send
def read(self, force_send=False):
"""Read the value and check to see if needs to be stored."""
send_needed = False
send_reason = ""
if self.plc_tag:
val = read_tag(self.plc_ip, self.plc_tag)
if val:
bool_arr = binarray(val[0])
new_val = {}
for idx in self.map_:
try:
new_val[self.map_[idx]] = bool_arr[idx]
except KeyError:
log.error("Not able to get value for index {}".format(idx))
if self.last_send_time == 0:
send_needed = True
send_reason = "no send time"
elif self.value is None:
send_needed = True
send_reason = "no value"
elif self.compare_values(new_val):
send_needed = True
send_reason = "value change"
elif (time.time() - self.last_send_time) > self.guarantee_sec:
send_needed = True
send_reason = "guarantee sec"
elif force_send:
send_needed = True
send_reason = "forced"
if send_needed:
self.value = new_val
self.last_value = self.value
self.last_send_time = time.time()
log.info("Sending {} for {} - {}".format(self.value, self.mesh_name, send_reason))
return send_needed

12
plcpond/config.txt Normal file
View File

@@ -0,0 +1,12 @@
{
"driverFileName": "plcpond.py",
"deviceName": "plcpond",
"driverId": "0220",
"releaseVersion": "3",
"files": {
"file1": "plcpond.py",
"file2": "utilities.py",
"file3": "Channel.py",
"file4": "file_logger.py"
}
}

18
plcpond/file_logger.py Normal file
View File

@@ -0,0 +1,18 @@
"""Logging setup for advvfdipp"""
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 = './advvfdipp.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('advvfdipp')
filelogger.setLevel(logging.INFO)
filelogger.addHandler(my_handler)
console_out = logging.StreamHandler(sys.stdout)
console_out.setFormatter(log_formatter)
filelogger.addHandler(console_out)

247
plcpond/plcpond.py Normal file
View File

@@ -0,0 +1,247 @@
"""Driver for plcpond"""
import threading
import sys
import json
import time
import logging
from random import randint
from device_base import deviceBase
from Channel import PLCChannel, read_tag, write_tag, TAG_DATAERROR_SLEEPTIME
from utilities import get_public_ip_address
_ = None
# LOGGING SETUP
from logging.handlers import RotatingFileHandler
log_formatter = logging.Formatter('%(asctime)s %(levelname)s %(funcName)s(%(lineno)d) %(message)s')
logFile = './plcpond.log'
my_handler = RotatingFileHandler(logFile, mode='a', maxBytes=500*1024, backupCount=2, encoding=None, delay=0)
my_handler.setFormatter(log_formatter)
my_handler.setLevel(logging.INFO)
logger = logging.getLogger('plcpond')
logger.setLevel(logging.INFO)
logger.addHandler(my_handler)
console_out = logging.StreamHandler(sys.stdout)
console_out.setFormatter(log_formatter)
logger.addHandler(console_out)
logger.info("plcpond startup")
# GLOBAL VARIABLES
WATCHDOG_SEND_PERIOD = 3600 # Seconds, the longest amount of time before sending the watchdog status
PLC_IP_ADDRESS = "192.168.1.12"
CHANNELS = [
PLCChannel(PLC_IP_ADDRESS, "cfgnumberofponds", "cfgNumberOfPonds", "REAL", 0.5, 600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond1height", "pond1Height", "REAL", 1.0, 600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond2height", "pond2Height", "REAL", 1.0, 600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond3height", "pond3Height", "REAL", 1.0, 600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond4height", "pond4Height", "REAL", 1.0, 600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond1volume", "pond1Volume", "REAL", 500.0, 600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond2volume", "pond2Volume", "REAL", 500.0, 600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond3volume", "pond3Volume", "REAL", 500.0, 600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pond4volume", "pond4Volume", "REAL", 500.0, 600, map_=False, write_enabled=False, plc_type='Micro800'),
PLCChannel(PLC_IP_ADDRESS, "pondvolumetotal", "pondVolumeTotal", "REAL", 1000.0, 600, map_=False, write_enabled=False, plc_type='Micro800')
]
CALIBRATION_TABLES = [[],[], [], [], []] # position 0 is a dummy table
class start(threading.Thread, deviceBase):
"""Start class required by Meshify."""
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None):
"""Initialize the driver."""
threading.Thread.__init__(self)
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.finished = threading.Event()
self.force_send = False
threading.Thread.start(self)
# this is a required function for all drivers, its goal is to upload some piece of data
# about your device so it can be seen on the web
def register(self):
"""Register the driver."""
# self.sendtodb("log", "BOOM! Booted.", 0)
pass
def run(self):
"""Actually run the driver."""
wait_sec = 30
for i in range(0, wait_sec):
print("plcpond driver will start in {} seconds".format(wait_sec - i))
time.sleep(1)
logger.info("BOOM! Starting plcpond driver...")
public_ip_address = get_public_ip_address()
self.sendtodbDev(1, 'public_ip_address', public_ip_address, 0, 'plcpond')
watchdog = self.plcpond_watchdog()
self.sendtodbDev(1, 'watchdog', watchdog, 0, 'plcpond')
watchdog_send_timestamp = time.time()
self.nodes["plcpond_0199"] = self
send_loops = 0
watchdog_loops = 0
watchdog_check_after = 5000
while True:
if self.force_send:
logger.warning("FORCE SEND: TRUE")
for c in CHANNELS:
v = c.read()
if v is not None: # read returns None if it fails
if c.check(v, self.force_send):
self.sendtodbDev(1, c.mesh_name, c.value, 0, 'plcpond')
#time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests
for pond_index in range(1, 5):
self.read_pond_calibration(pond_index)
# print("plcpond driver still alive...")
if self.force_send:
if send_loops > 2:
logger.warning("Turning off force_send")
self.force_send = False
send_loops = 0
else:
send_loops += 1
watchdog_loops += 1
if watchdog_loops >= watchdog_check_after:
test_watchdog = self.plcpond_watchdog()
if not test_watchdog == watchdog or (time.time() - watchdog_send_timestamp) > WATCHDOG_SEND_PERIOD:
self.sendtodbDev(1, 'watchdog', test_watchdog, 0, 'plcpond')
watchdog = test_watchdog
test_public_ip = get_public_ip_address()
if not test_public_ip == public_ip_address:
self.sendtodbDev(1, 'public_ip_address', test_public_ip, 0, 'plcpond')
public_ip_address = test_public_ip
watchdog_loops = 0
def read_pond_calibration(self, pond_number):
"""Read all calibration values for a specific pond."""
last_read_height = -1.0
cal_values = []
cal_index = 1
while last_read_height != 0 and cal_index <=10:
cal_tag_height = "pond{}CalibrationHeight[{}]".format(pond_number, cal_index)
cal_tag_volume = "pond{}CalibrationVolume[{}]".format(pond_number, cal_index)
read_height = read_tag(PLC_IP_ADDRESS, cal_tag_height, plc_type="Micro800")
#time.sleep(2)
read_volume = read_tag(PLC_IP_ADDRESS, cal_tag_volume, plc_type="Micro800")
#time.sleep(2)
if read_height and read_volume:
if read_height[0] > 0.0:
cal_values.append({"height": read_height[0], "volume": read_volume[0]})
last_read_height = read_height[0]
cal_index += 1
if cal_values != CALIBRATION_TABLES[pond_number] or self.force_send:
calibration_channel = "pond{}calibration".format(pond_number)
calibration_string = json.dumps(cal_values).replace('"', "'")
self.sendtodbDev(1, calibration_channel, calibration_string, 0, 'plcpond')
CALIBRATION_TABLES[pond_number] = cal_values
def plcpond_watchdog(self):
"""Write a random integer to the PLC and then 1 seconds later check that it has been decremented by 1."""
randval = randint(0, 32767)
write_tag(str(PLC_IP_ADDRESS), 'watchdog_INT', randval, plc_type="Micro800")
time.sleep(1)
watchdog_val = read_tag(str(PLC_IP_ADDRESS), 'watchdog_INT', plc_type="Micro800")
try:
return (randval - 1) == watchdog_val[0]
except (KeyError, TypeError):
return False
def plcpond_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
# self.sendtodb("log", "synced", 0)
return True
def plcpond_deletecalibrationpoint(self, name, value):
"""Delete a calibration point from a calibration table"""
# {"pond": int, "point": int}
value = value.replace("'", '"')
parsed = json.loads(value)
try:
pond_number = int(parsed['pond'])
point_number = int(parsed['point'])
write_pond = write_tag(PLC_IP_ADDRESS, "inpPondNumber", pond_number, plc_type="Micro800")
#time.sleep(2)
write_point = write_tag(PLC_IP_ADDRESS, "inpDeletePointIndex", point_number, plc_type="Micro800")
#time.sleep(2)
if write_pond and write_point:
write_cmd = write_tag(PLC_IP_ADDRESS, "cmdDeleteCalibrationPoint", 1, plc_type="Micro800")
#time.sleep(2)
if write_cmd:
read_val = read_tag(PLC_IP_ADDRESS, "deleteSuccess", plc_type="Micro800")
if read_val[0] == 1:
self.read_pond_calibration(pond_number)
return True
return "Wrote everything successfully, but delete didn't succeed (Check pond and point values)."
return "Didn't write delete command correctly."
return "Didn't write pond or point correctly."
except KeyError as e:
return "Couldn't parse input value: {} -- {}".format(value, e)
def plcpond_cfgnumberofponds(self, name, value):
"""Write the number of ponds to the plc."""
value = int(value)
return write_tag(PLC_IP_ADDRESS, "cfgNumberOfPonds", value, plc_type="Micro800")
def plcpond_addcalibrationpoint(self, name, value):
"""Add a calibration point to the calibration table"""
# value = {"pond": int, "height": float, "volume": float}
value = value.replace("'", '"')
parsed = json.loads(value)
try:
# parse json values, throw an error if one is missing
pond_number = int(parsed['pond'])
height = float(parsed['height'])
volume = float(parsed['volume'])
# write values to the tags
write_pond = write_tag(PLC_IP_ADDRESS, "inpPondNumber", pond_number, plc_type="Micro800")
#time.sleep(2)
write_height = write_tag(PLC_IP_ADDRESS, "inpPondHeight", height, plc_type="Micro800")
#time.sleep(2)
write_volume= write_tag(PLC_IP_ADDRESS, "inpPondVolume", volume, plc_type="Micro800")
#time.sleep(2)
if write_pond and write_height and write_volume:
write_cmd = write_tag(PLC_IP_ADDRESS, "cmdAddCalibrationPoint", 1, plc_type="Micro800")
#time.sleep(2)
if write_cmd:
read_val = read_tag(PLC_IP_ADDRESS, "addSuccess", plc_type="Micro800")
if read_val[0] == 1:
self.read_pond_calibration(pond_number)
return True
return "Wrote everything successfully, but delete didn't succeed (Check pond and point values)."
return "Didn't write delete command correctly."
return "Didn't write pond or point correctly."
except KeyError as e:
return "Couldn't parse input value: {} -- {}".format(value, e)
def plcpond_writeplctag(self, name, value):
"""Write a value to the PLC."""
new_val = json.loads(str(value).replace("'", '"'))
tag_n = str(new_val['tag']) # "cmd_Start"
val_n = new_val['val']
w = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n)
logger.info("Result of plcpond_writeplctag(self, {}, {}) = {}".format(name, value, w))
if w is None:
w = "Error writing to PLC..."
return w

51
plcpond/utilities.py Normal file
View File

@@ -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