This commit is contained in:
Nico Melone
2021-04-29 19:22:16 -04:00
parent edb7ad6a76
commit 0acf88a1e9
14 changed files with 1492 additions and 0 deletions

60
Example Taglist.csv Normal file
View File

@@ -0,0 +1,60 @@
fm1_flowrate,Val_FM1_FR,REAL,250,3600
fm2_flowrate,Val_FM2_FR,REAL,250,3600
fm3_flowrate,Val_FM3_FR,REAL,250,3600
fm4_flowrate,Val_FM4_FR,REAL,250,3600
fm5_flowrate,Val_FM5_FR,REAL,250,3600
fm6_flowrate,Val_FM6_FR,REAL,250,3600
fm7_flowrate,Val_FM7_FR,REAL,250,3600
fm8_flowrate,Val_FM8_FR,REAL,250,3600
fm9_flowrate,Val_FM9_FR,REAL,250,3600
fm10_flowrate,Val_FM10_FR,REAL,250,3600
fm1_lifetime,Val_FM1_T1,REAL,100,3600
fm2_lifetime,Val_FM2_T1,REAL,100,3600
fm3_lifetime,Val_FM3_T1,REAL,100,3600
fm4_lifetime,Val_FM4_T1,REAL,100,3600
fm5_lifetime,Val_FM5_T1,REAL,100,3600
fm6_lifetime,Val_FM6_T1,REAL,100,3600
fm7_lifetime,Val_FM7_T1,REAL,100,3600
fm8_lifetime,Val_FM8_T1,REAL,100,3600
fm9_lifetime,Val_FM9_T1,REAL,100,3600
fm10_lifetime,Val_FM10_T1,REAL,100,3600
fm1_month,Val_Flowmeter1MonthTotal,REAL,100,3600
fm2_month,Val_Flowmeter2MonthTotal,REAL,100,3600
fm3_month,Val_Flowmeter3MonthTotal,REAL,100,3600
fm4_month,Val_Flowmeter4MonthTotal,REAL,100,3600
fm5_month,Val_Flowmeter5MonthTotal,REAL,100,3600
fm6_month,Val_Flowmeter6MonthTotal,REAL,100,3600
fm7_month,Val_Flowmeter7MonthTotal,REAL,100,3600
fm8_month,Val_Flowmeter8MonthTotal,REAL,100,3600
fm9_month,Val_Flowmeter9MonthTotal,REAL,100,3600
fm10_month,Val_Flowmeter10MonthTotal,REAL,100,3600
fm1_lastmonth,Val_Flowmeter1LastMonthTotal,REAL,100,3600
fm2_lastmonth,Val_Flowmeter2LastMonthTotal,REAL,100,3600
fm3_lastmonth,Val_Flowmeter3LastMonthTotal,REAL,100,3600
fm4_lastmonth,Val_Flowmeter4LastMonthTotal,REAL,100,3600
fm5_lastmonth,Val_Flowmeter5LastMonthTotal,REAL,100,3600
fm6_lastmonth,Val_Flowmeter6LastMonthTotal,REAL,100,3600
fm7_lastmonth,Val_Flowmeter7LastMonthTotal,REAL,100,3600
fm8_lastmonth,Val_Flowmeter8LastMonthTotal,REAL,100,3600
fm9_lastmonth,Val_Flowmeter9LastMonthTotal,REAL,100,3600
fm10_lastmonth,Val_Flowmeter10LastMonthTotal,REAL,100,3600
fm1_yesterdays,Val_Flowmeter1YesterdaysTotal,REAL,100,3600
fm2_yesterdays,Val_Flowmeter2YesterdaysTotal,REAL,100,3600
fm3_yesterdays,Val_Flowmeter3YesterdaysTotal,REAL,100,3600
fm4_yesterdays,Val_Flowmeter4YesterdaysTotal,REAL,100,3600
fm5_yesterdays,Val_Flowmeter5YesterdaysTotal,REAL,100,3600
fm6_yesterdays,Val_Flowmeter6YesterdaysTotal,REAL,100,3600
fm7_yesterdays,Val_Flowmeter7YesterdaysTotal,REAL,100,3600
fm8_yesterdays,Val_Flowmeter8YesterdaysTotal,REAL,100,3600
fm9_yesterdays,Val_Flowmeter9YesterdaysTotal,REAL,100,3600
fm10_yesterdays,Val_Flowmeter10YesterdaysTotal,REAL,100,3600
fm1_todays,Val_Flowmeter1TodaysTotal,REAL,100,3600
fm2_todays,Val_Flowmeter2TodaysTotal,REAL,100,3600
fm3_todays,Val_Flowmeter3TodaysTotal,REAL,100,3600
fm4_todays,Val_Flowmeter4TodaysTotal,REAL,100,3600
fm5_todays,Val_Flowmeter5TodaysTotal,REAL,100,3600
fm6_todays,Val_Flowmeter6TodaysTotal,REAL,100,3600
fm7_todays,Val_Flowmeter7TodaysTotal,REAL,100,3600
fm8_todays,Val_Flowmeter8TodaysTotal,REAL,100,3600
fm9_todays,Val_Flowmeter9TodaysTotal,REAL,100,3600
fm10_todays,Val_Flowmeter10TodaysTotal,REAL,100,3600
1 fm1_flowrate Val_FM1_FR REAL 250 3600
2 fm2_flowrate Val_FM2_FR REAL 250 3600
3 fm3_flowrate Val_FM3_FR REAL 250 3600
4 fm4_flowrate Val_FM4_FR REAL 250 3600
5 fm5_flowrate Val_FM5_FR REAL 250 3600
6 fm6_flowrate Val_FM6_FR REAL 250 3600
7 fm7_flowrate Val_FM7_FR REAL 250 3600
8 fm8_flowrate Val_FM8_FR REAL 250 3600
9 fm9_flowrate Val_FM9_FR REAL 250 3600
10 fm10_flowrate Val_FM10_FR REAL 250 3600
11 fm1_lifetime Val_FM1_T1 REAL 100 3600
12 fm2_lifetime Val_FM2_T1 REAL 100 3600
13 fm3_lifetime Val_FM3_T1 REAL 100 3600
14 fm4_lifetime Val_FM4_T1 REAL 100 3600
15 fm5_lifetime Val_FM5_T1 REAL 100 3600
16 fm6_lifetime Val_FM6_T1 REAL 100 3600
17 fm7_lifetime Val_FM7_T1 REAL 100 3600
18 fm8_lifetime Val_FM8_T1 REAL 100 3600
19 fm9_lifetime Val_FM9_T1 REAL 100 3600
20 fm10_lifetime Val_FM10_T1 REAL 100 3600
21 fm1_month Val_Flowmeter1MonthTotal REAL 100 3600
22 fm2_month Val_Flowmeter2MonthTotal REAL 100 3600
23 fm3_month Val_Flowmeter3MonthTotal REAL 100 3600
24 fm4_month Val_Flowmeter4MonthTotal REAL 100 3600
25 fm5_month Val_Flowmeter5MonthTotal REAL 100 3600
26 fm6_month Val_Flowmeter6MonthTotal REAL 100 3600
27 fm7_month Val_Flowmeter7MonthTotal REAL 100 3600
28 fm8_month Val_Flowmeter8MonthTotal REAL 100 3600
29 fm9_month Val_Flowmeter9MonthTotal REAL 100 3600
30 fm10_month Val_Flowmeter10MonthTotal REAL 100 3600
31 fm1_lastmonth Val_Flowmeter1LastMonthTotal REAL 100 3600
32 fm2_lastmonth Val_Flowmeter2LastMonthTotal REAL 100 3600
33 fm3_lastmonth Val_Flowmeter3LastMonthTotal REAL 100 3600
34 fm4_lastmonth Val_Flowmeter4LastMonthTotal REAL 100 3600
35 fm5_lastmonth Val_Flowmeter5LastMonthTotal REAL 100 3600
36 fm6_lastmonth Val_Flowmeter6LastMonthTotal REAL 100 3600
37 fm7_lastmonth Val_Flowmeter7LastMonthTotal REAL 100 3600
38 fm8_lastmonth Val_Flowmeter8LastMonthTotal REAL 100 3600
39 fm9_lastmonth Val_Flowmeter9LastMonthTotal REAL 100 3600
40 fm10_lastmonth Val_Flowmeter10LastMonthTotal REAL 100 3600
41 fm1_yesterdays Val_Flowmeter1YesterdaysTotal REAL 100 3600
42 fm2_yesterdays Val_Flowmeter2YesterdaysTotal REAL 100 3600
43 fm3_yesterdays Val_Flowmeter3YesterdaysTotal REAL 100 3600
44 fm4_yesterdays Val_Flowmeter4YesterdaysTotal REAL 100 3600
45 fm5_yesterdays Val_Flowmeter5YesterdaysTotal REAL 100 3600
46 fm6_yesterdays Val_Flowmeter6YesterdaysTotal REAL 100 3600
47 fm7_yesterdays Val_Flowmeter7YesterdaysTotal REAL 100 3600
48 fm8_yesterdays Val_Flowmeter8YesterdaysTotal REAL 100 3600
49 fm9_yesterdays Val_Flowmeter9YesterdaysTotal REAL 100 3600
50 fm10_yesterdays Val_Flowmeter10YesterdaysTotal REAL 100 3600
51 fm1_todays Val_Flowmeter1TodaysTotal REAL 100 3600
52 fm2_todays Val_Flowmeter2TodaysTotal REAL 100 3600
53 fm3_todays Val_Flowmeter3TodaysTotal REAL 100 3600
54 fm4_todays Val_Flowmeter4TodaysTotal REAL 100 3600
55 fm5_todays Val_Flowmeter5TodaysTotal REAL 100 3600
56 fm6_todays Val_Flowmeter6TodaysTotal REAL 100 3600
57 fm7_todays Val_Flowmeter7TodaysTotal REAL 100 3600
58 fm8_todays Val_Flowmeter8TodaysTotal REAL 100 3600
59 fm9_todays Val_Flowmeter9TodaysTotal REAL 100 3600
60 fm10_todays Val_Flowmeter10TodaysTotal REAL 100 3600

View File

@@ -0,0 +1,6 @@
{
"driver_name": "pocloud_driver",
"ip_address": "192.168.1.10",
"device_type": ["CLX","Micro800", "None"]
}

View File

@@ -0,0 +1,19 @@
import meshify
import json
values = {
"channelType": 1,
"dataType": 3,
"defaultValue": "",
"deviceTypeId": 470,
"fromMe": False,
"helpExplanation": "write the the modbus unit number",
"io": True,
"name": "writeplctag",
"regex": "",
"regexErrMsg": "",
"subTitle": "Modbus Unit Number writer"
}
data = meshify.post_meshify_api("devicetypes/470/channels/", values)
print(json.dumps(data, indent=4, sort_keys=True))

View File

@@ -0,0 +1,404 @@
"""Query Meshify for data."""
import json
import csv
from os import getenv
import getpass
import pickle
from pathlib import Path
import requests
import click
MESHIFY_BASE_URL = "https://henry-pump.meshify.com/api/v3/"#getenv("MESHIFY_BASE_URL")
MESHIFY_USERNAME = "nmelone@henry-pump.com"#getenv("MESHIFY_USERNAME")
MESHIFY_PASSWORD = "zaq1ZAQ!"#getenv("MESHIFY_PASSWORD")
MESHIFY_AUTH = None
class NameNotFound(Exception):
"""Thrown when a name is not found in a list of stuff."""
def __init__(self, message, name, list_of_stuff, *args):
"""Initialize the NameNotFound Exception."""
self.message = message
self.name = name
self.list_of_stuff = list_of_stuff
super(NameNotFound, self).__init__(message, name, list_of_stuff, *args)
def dict_filter(it, *keys):
"""Filter dictionary results."""
for d in it:
yield dict((k, d[k]) for k in keys)
def check_setup():
"""Check the global parameters."""
global MESHIFY_USERNAME, MESHIFY_PASSWORD, MESHIFY_AUTH, MESHIFY_BASE_URL
if not MESHIFY_USERNAME or not MESHIFY_PASSWORD:
print("Simplify the usage by setting the meshify username and password as environment variables MESHIFY_USERNAME and MESHIFY_PASSWORD")
MESHIFY_USERNAME = input("Meshify Username: ")
MESHIFY_PASSWORD = getpass.getpass("Meshify Password: ")
MESHIFY_AUTH = requests.auth.HTTPBasicAuth(MESHIFY_USERNAME, MESHIFY_PASSWORD)
if not MESHIFY_BASE_URL:
print("Simplify the usage by setting the environment variable MESHIFY_BASE_URL")
MESHIFY_BASE_URL = input("Meshify Base URL: ")
def find_by_name(name, list_of_stuff):
"""Find an object in a list of stuff by its name parameter."""
for x in list_of_stuff:
if x['name'] == name:
return x
raise NameNotFound("Name not found!", name, list_of_stuff)
def query_meshify_api(endpoint):
"""Make a query to the meshify API."""
check_setup()
if endpoint[0] == "/":
endpoint = endpoint[1:]
q_url = MESHIFY_BASE_URL + endpoint
q_req = requests.get(q_url, auth=MESHIFY_AUTH)
return json.loads(q_req.text) if q_req.status_code == 200 else []
def post_meshify_api(endpoint, data):
"""Post data to the meshify API."""
check_setup()
q_url = MESHIFY_BASE_URL + endpoint
q_req = requests.post(q_url, data=json.dumps(data), auth=MESHIFY_AUTH)
if q_req.status_code != 200:
print(q_req.status_code)
return json.loads(q_req.text) if q_req.status_code == 200 else []
def decode_channel_parameters(channel):
"""Decode a channel object's parameters into human-readable format."""
channel_types = {
1: 'device',
5: 'static',
6: 'user input',
7: 'system'
}
io_options = {
0: 'readonly',
1: 'readwrite'
}
datatype_options = {
1: "float",
2: 'string',
3: 'integer',
4: 'boolean',
5: 'datetime',
6: 'timespan',
7: 'file',
8: 'latlng'
}
channel['channelType'] = channel_types[channel['channelType']]
channel['io'] = io_options[channel['io']]
channel['dataType'] = datatype_options[channel['dataType']]
return channel
def encode_channel_parameters(channel):
"""Encode a channel object from human-readable format."""
channel_types = {
'device': 1,
'static': 5,
'user input': 6,
'system': 7
}
io_options = {
'readonly': False,
'readwrite': True
}
datatype_options = {
"float": 1,
'string': 2,
'integer': 3,
'boolean': 4,
'datetime': 5,
'timespan': 6,
'file': 7,
'latlng': 8
}
try:
channel['deviceTypeId'] = int(channel['deviceTypeId'])
channel['fromMe'] = channel['fromMe'].lower() == 'true'
channel['channelType'] = channel_types[channel['channelType'].lower()]
channel['io'] = io_options[channel['io'].lower()]
channel['dataType'] = datatype_options[channel['dataType'].lower()]
# channel['id'] = 1
return channel
except KeyError as e:
click.echo("Unable to convert channel {} due to bad key: {}".format(channel['name'], e))
def make_modbusmap_channel(i, chan, device_type_name):
"""Make a channel object for a row in the CSV."""
json_obj = {
"ah": "",
"bytary": None,
"al": "",
"vn": chan['subTitle'], # Name
"ct": "number", # ChangeType
"le": "16", # Length(16 or 32)
"grp": str(chan['guaranteedReportPeriod']), # GuaranteedReportPeriod
"la": None,
"chn": chan['name'], # ChannelName
"un": "1", # DeviceNumber
"dn": device_type_name, # deviceName
"vm": None,
"lrt": "0",
"da": "300", # DeviceAddress
"a": chan['helpExplanation'], # TagName
"c": str(chan['change']), # Change
"misc_u": str(chan['units']), # Units
"f": "1", # FunctionCode
"mrt": str(chan['minReportTime']), # MinimumReportTime
"m": "none", # multiplier
"m1ch": "2-{}".format(i),
"mv": "0", # MultiplierValue
"s": "On",
"r": "{}-{}".format(chan['min'], chan['max']), # range
"t": "int" # type
}
return json_obj
def combine_modbusmap_and_channel(channel_obj, modbus_map):
"""Add the parameters from the modbus map to the channel object."""
channel_part = modbus_map["1"]["addresses"]["300"]
for c in channel_part:
if channel_part[c]["chn"] == channel_obj['name']:
channel_obj['units'] = channel_part[c]["misc_u"]
try:
min_max_range = channel_part[c]["r"].split("-")
channel_obj['min'] = int(min_max_range[0])
channel_obj['max'] = int(min_max_range[1])
except Exception:
channel_obj['min'] = None
channel_obj['max'] = None
channel_obj['change'] = float(channel_part[c]["c"])
channel_obj['guaranteedReportPeriod'] = int(channel_part[c]["grp"])
channel_obj['minReportTime'] = int(channel_part[c]["mrt"])
return channel_obj
return False
@click.group()
def cli():
"""Command Line Interface."""
pass
@click.command()
@click.argument("device_type_name")
@click.option("-o", '--output-file', default=None, help="Where to put the CSV of channels.")
@click.option("-m", '--modbusmap-file', default="modbusMap.p", help="The location of the modbusMap.p file")
def get_channel_csv(device_type_name, output_file, modbusmap_file):
"""Query the meshify API and create a CSV of the current channels."""
channel_fieldnames = [
'id',
'name',
'deviceTypeId',
'fromMe',
'io',
'subTitle',
'helpExplanation',
'channelType',
'dataType',
'defaultValue',
'regex',
'regexErrMsg',
'units',
'min',
'max',
'change',
'guaranteedReportPeriod',
'minReportTime'
]
devicetypes = query_meshify_api('devicetypes')
this_devicetype = find_by_name(device_type_name, devicetypes)
channels = query_meshify_api('devicetypes/{}/channels'.format(this_devicetype['id']))
modbus_map = None
if Path(modbusmap_file).exists():
with open(modbusmap_file, 'rb') as open_mbs_file:
modbus_map = pickle.load(open_mbs_file)
if not output_file:
output_file = 'channels_{}.csv'.format(device_type_name)
with open(output_file, 'w') as csvfile:
writer = csv.DictWriter(csvfile, fieldnames=channel_fieldnames)
writer.writeheader()
for ch in channels:
if not modbus_map:
ch['units'] = None
ch['min'] = None
ch['max'] = None
ch['change'] = None
ch['guaranteedReportPeriod'] = None
ch['minReportTime'] = None
else:
combined = combine_modbusmap_and_channel(ch, modbus_map)
if combined:
ch = combined
writer.writerow(decode_channel_parameters(ch))
click.echo("Wrote channels to {}".format(output_file))
@click.command()
@click.argument("device_type_name")
@click.argument("csv_file")
def post_channel_csv(device_type_name, csv_file):
"""Post values from a CSV to Meshify Channel API."""
devicetypes = query_meshify_api('devicetypes')
this_devicetype = find_by_name(device_type_name, devicetypes)
with open(csv_file, 'r') as inp_file:
reader = csv.DictReader(inp_file)
for row in dict_filter(reader, 'name',
'deviceTypeId',
'fromMe',
'io',
'subTitle',
'helpExplanation',
'channelType',
'dataType',
'defaultValue',
'regex',
'regexErrMsg'):
# print(row)
# print(encode_channel_parameters(row))
# click.echo(json.dumps(encode_channel_parameters(row), indent=4))
if post_meshify_api('devicetypes/{}/channels'.format(this_devicetype['id']), encode_channel_parameters(row)):
click.echo("Successfully added channel {}".format(row['name']))
else:
click.echo("Unable to add channel {}".format(row['name']))
@click.command()
def print_channel_options():
"""Print channel options for use with the csv files."""
channel_types = ['device', 'static', 'user input', 'system']
io_options = ['readonly', 'readwrite']
datatype_options = [
"float",
'string',
'integer',
'boolean',
'datetime',
'timespan',
'file',
'latlng'
]
click.echo("\n\nchannelType options")
click.echo("===================")
for chan in channel_types:
click.echo(chan)
click.echo("\n\nio options")
click.echo("==========")
for i in io_options:
click.echo(i)
click.echo("\n\ndataType options")
click.echo("================")
for d in datatype_options:
click.echo(d)
@click.command()
@click.argument("device_type_name")
@click.argument("csv_file")
def create_modbusMap(device_type_name, csv_file):
"""Create modbusMap.p from channel csv file."""
modbusMap = {
"1": {
"c": "ETHERNET/IP",
"b": "192.168.1.10",
"addresses": {
"300": {}
},
"f": "Off",
"p": "",
"s": "1"
},
"2": {
"c": "M1-485",
"b": "9600",
"addresses": {},
"f": "Off",
"p": "None",
"s": "1"
}
}
ind = 1
with open(csv_file, 'r') as inp_file:
reader = csv.DictReader(inp_file)
for row in reader:
modbusMap["1"]["addresses"]["300"]["2-{}".format(ind)] = make_modbusmap_channel(ind, row, device_type_name)
ind += 1
with open("modbusMap.p", 'wb') as mod_map_file:
pickle.dump(modbusMap, mod_map_file, protocol=0)
with open("modbusMap.json", 'w') as json_file:
json.dump(modbusMap, json_file, indent=4)
@click.command()
@click.option("-i", "--input-file", default="modbusMap.p", help="The modbus map pickle file to convert.")
@click.option("-o", "--output", default="modbusMap.json", help="The modbus map json file output filename.")
def pickle_to_json(input_file, output):
"""Convert a pickle file to a json file."""
if not Path(input_file).exists():
click.echo("Pickle file {} does not exist".format(input_file))
return
with open(input_file, 'rb') as picklefile:
input_contents = pickle.load(picklefile)
with open(output, 'w') as outfile:
json.dump(input_contents, outfile, indent=4)
click.echo("Wrote from {} to {}.".format(input_file, output))
@click.command()
@click.option("-i", "--input-file", default="modbusMap.json", help="The modbus map json file to convert.")
@click.option("-o", "--output", default="modbusMap.p", help="The modbus map pickle file output filename.")
def json_to_pickle(input_file, output):
"""Convert a pickle file to a json file."""
if not Path(input_file).exists():
click.echo("JSON file {} does not exist".format(input_file))
return
with open(input_file, 'rb') as json_file:
input_contents = json.load(json_file)
with open(output, 'wb') as outfile:
pickle.dump(input_contents, outfile, protocol=0)
click.echo("Wrote from {} to {}.".format(input_file, output))
cli.add_command(get_channel_csv)
cli.add_command(post_channel_csv)
cli.add_command(print_channel_options)
cli.add_command(create_modbusMap)
cli.add_command(pickle_to_json)
cli.add_command(json_to_pickle)
if __name__ == '__main__':
cli()

Binary file not shown.

View File

@@ -0,0 +1,103 @@
import csv
#Generate a tag/channel list to be used in the template
csv_file = bool(input("Do you have a CSV file yes(1)/no(0): "))
if not csv_file:
num_channels = int(input("Number of Channels in the Driver: "))
mesh_name = []
tags = []
data_type = []
change_thres = []
guaruntee_sec = []
register_num = []
unit_number = []
channel_size = []
if csv_file:
csv_path = str(input("Where is your CSV file: "))
plc_or_mbs = int(input("Is this for PLC(1) or Modbus(2) channels: "))
if csv_file:
with open(csv_path, 'r') as tagsFile:
reader = csv.reader(tagsFile)
row_count = sum(1 for row in reader)
with open(csv_path, 'r') as tagsFile:
reader = csv.reader(tagsFile)
try:
f = open("python-driver/Tags.py", "a")
print("writing tags")
f.write("tags = [ \n")
counter = 1
for row in reader:
print("{}".format(row[0]))
if plc_or_mbs == 1:
f.write("\tPLCChannel(PLC_IP_ADDRESS, \"{}\",\"{}\",\"{}\", {}, {}, plc_type=\"{}\")".format(row[0],row[1],row[2],row[3],row[4],"{{cookiecutter.device_type}}"))
else:
f.write("\tModbusChannel(\"{}\",{},\"{}\", {}, {},channel_size={}, unit_number={})".format(row[0],row[1],row[2],row[3],row[4], row[5], row[6]))
if counter < row_count:
f.write(",\n")
else:
f.write("\n]")
counter = counter + 1
f.close
print("Wrote {} Channels".format(row_count))
except:
print("Couldn't open file with tags")
else:
for x in range(num_channels):
mesh_name.append(input("Channel {} meshify name: ".format(x+1)))
if plc_or_mbs == 1:
tags.append(input("Channel {} tag name: ".format(x+1)))
data_type.append(input("Channel {} data type: ".format(x+1)))
change_thres.append(input("Channel {} change threshold: ".format(x+1)))
guaruntee_sec.append(input("Channel {} guarunteed seconds: ".format(x+1)))
if plc_or_mbs == 2:
register_num.append(input("Channel {} register number: ".format(x+1)))
unit_number.append(input("Channel {} unit number: ".format(x+1)))
channel_size.append(input("Channel {} channel size: ".format(x+1)))
#Generate a Tags.py file to hold a list of PLCChannels or ModbusChannels to be used in the driver to communicate
if(num_channels == 0):
try:
file = open("python-driver/Tags.py", "a")
file.write("tags = []")
file.close
print("Wrote no Channels")
except:
print("Couldn't open file without tags")
elif plc_or_mbs == 1:
try:
file = open("python-driver/Tags.py", "a")
file.write("tags = [ \n")
for x in range(num_channels):
file.write("\tPLCChannel(PLC_IP_ADDRESS, \"{}\",\"{}\",\"{}\", {}, {}, plc_type=\"{}\")".format(mesh_name[x],tags[x],data_type[x],change_thres[x],guaruntee_sec[x],"{{cookiecutter.device_type}}"))
if(x < num_channels-1):
file.write(",\n")
else:
file.write("\n]")
file.close
print("Wrote {} Channels".format(num_channels))
except:
print("Couldn't open file with tags")
else:
try:
file = open("python-driver/Tags.py", "a")
file.write("tags = [ \n")
for x in range(num_channels):
file.write("\tModbusChannel(\"{}\",{},\"{}\", {}, {},channel_size={}, unit_number={})".format(mesh_name[x],register_num[x],data_type[x],change_thres[x],guaruntee_sec[x],channel_size[x],unit_number[x]))
if(x < num_channels-1):
file.write(",\n")
else:
file.write("\n]")
file.close
print("Wrote {} Channels".format(num_channels))
except:
print("Couldn't open file with tags")

View File

@@ -0,0 +1,299 @@
"""Define Meshify channel class."""
import time
from pycomm.ab_comm.clx import Driver as ClxDriver
from pycomm.cip.cip_base import CommError, DataError
from file_logger import filelogger as log
import minimalmodbus
minimalmodbus.BAUDRATE = 9600
minimalmodbus.STOPBITS = 1
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()
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

View File

@@ -0,0 +1,3 @@
from Channel import PLCChannel, ModbusChannel
from {{cookiecutter.driver_name}} import PLC_IP_ADDRESS

View File

@@ -0,0 +1,14 @@
{
"files": {
"file3": "file_logger.py",
"file2": "Channel.py",
"file1": "{{cookiecutter.driver_name}}.py",
"file6": "persistence.py",
"file5": "utilities.py",
"file4": "Tags.py"
},
"deviceName": "{{cookiecutter.driver_name}}",
"releaseVersion": "1",
"driverFileName": "{{cookiecutter.driver_name}}.py",
"driverId": "0100"
}

View File

@@ -0,0 +1,360 @@
import types
import traceback
import binascii
import threading
import time
import thread
import os
import struct
import sys
import textwrap
import Queue
import json
class deviceBase():
def __init__(self, name=None, number=None, mac=None, Q=None, mcu=None, companyId=None, offset=None, mqtt=None, Nodes=None):
self.offset = offset
self.company = companyId
self.name = name
self.number = number
self.q = Q
self.deviceName = name + '_[' + mac + ':' + number[0:2] + ':' + number[2:] + ']!'
self.chName = "M1" + '_[' + mac + ':'
self.chName2 = '_[' + mac + ':'
print 'device name is:'
print self.deviceName
mac2 = mac.replace(":", "")
self.mac = mac2.upper()
self.address = 1
self.debug = True
self.mcu = mcu
self.firstRun = True
self.mqtt = mqtt
self.nodes = Nodes
#local dictionary of derived nodes ex: localNodes[tank_0199] = self
self.localNodes = {}
os.system("chmod 777 /root/reboot")
os.system("echo nameserver 8.8.8.8 > /etc/resolv.conf")
#Queue for imcoming sets
self.loraQ = Queue.Queue()
self.knownIDs = []
thread.start_new_thread(self.getSetsThread, ())
def getSetsThread(self):
while True:
try:
item = self.loraQ.get(block=True, timeout=600)
try:
print "here is the item from the sets q"
print item
if len(item) == 2:
techname = str(json.loads(item[1])[0]['payload']['name'].split(".")[0])
channel = str(json.loads(item[1])[0]['payload']['name'].split(".")[1])
name = techname.split("_")[0]
id = techname.split("_")[1][1:-2].replace(":","").upper()
value = json.loads(item[1])[0]['payload']['value']
msgId = json.loads(item[1])[0]['msgId']
print channel, value, id, name, msgId
success = self.specificSets(channel, value, id, name)
if success == True:
print "SUCCESS ON SET"
if int(msgId) == 0:
return
lc = self.getTime()
value = str(self.mac) + " Success Setting: " + channel + " To: " + value
msg = """[ { "value":"%s", "timestamp":"%s", "msgId":"%s" } ]""" % (value, str(lc), msgId)
print value
print msg
topic = "meshify/responses/" + str(msgId)
print topic
self.q.put([topic, str(msg), 2])
else:
lc = self.getTime()
if success == False:
reason = "(Internal Gateway/Device Error)"
else:
reason = success
value = str(self.mac) + " Failed Setting: " + channel + " To: " + value + " " + reason
msg = """[ { "value":"%s", "timestamp":"%s", "msgId":"%s" } ]""" % (value, str(lc), msgId)
topic = "meshify/responses/" + msgId
self.q.put([topic, str(msg), 2])
except:
if int(msgId) == 0:
return
lc = self.getTime()
value = str(self.mac) + " Failed Setting: " + channel + " To: " + value + " (No Callback Found)"
msg = """[ { "value":"%s", "timestamp":"%s", "msgId":"%s" } ]""" % (value, str(lc), msgId)
topic = "meshify/responses/" + msgId
self.q.put([topic, str(msg), 2])
print 'no Set callback found for channel: ' + funcName
except:
print "sets queue timeout, restarting..."
def sendtodbDevLora(self, id, channel, value, timestamp, deviceName):
mac = self.mac
if deviceName == "mainMeshify":
zigmac = "_[01:00:00:00:00:" + id[0:2] + ":" + id[2:4] + ":" + id[4:6] + "]!"
else:
zigmac = "_[00:00:00:00:00:" + id[0:2] + ":" + id[2:4] + ":" + id[4:6] + "]!"
dname = deviceName + zigmac
#define dname, make id into techname and mac
if id not in self.knownIDs:
self.knownIDs.append(id)
self.mcu.xbees[dname] = self.loraQ
#meshify/db/330/C493000354FB/ilora/c493000354fb2A6E/a1-v
#[ { "value":"0.5635", "timestamp":"1486039316" } ]
if int(timestamp) == 0:
timestamp = self.getTime()
topic = 'meshify/db/%s/%s/%s/%s' % (self.company, mac, dname, channel)
print topic
msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp))
print msg
self.q.put([topic, msg, 0])
def sendtodbLocLora(self, id, channel, value, timestamp, deviceName):
mac = id
while len(mac) < 12:
mac = "0" + mac
if deviceName == "mainMeshify":
zigmac = "_[01:00:00:00:00:" + id[0:2] + ":" + id[2:4] + ":" + id[4:6] + "]!"
else:
zigmac = "_[00:00:00:00:00:" + id[0:2] + ":" + id[2:4] + ":" + id[4:6] + "]!"
dname = deviceName + zigmac
#define dname, make id into techname and mac
if id not in self.knownIDs:
self.knownIDs.append(id)
topic = str(("meshify/sets/" + str(self.company) + "/" + mac + "/#"))
self.mqtt.subscribe(topic, 0)
topic = str(("meshify/sets/" + "1" + "/" + mac + "/#"))
self.mqtt.subscribe(topic, 0)
self.mcu.xbees[dname] = self.loraQ
#meshify/db/330/C493000354FB/ilora/c493000354fb2A6E/a1-v
#[ { "value":"0.5635", "timestamp":"1486039316" } ]
if int(timestamp) == 0:
timestamp = self.getTime()
topic = 'meshify/db/%s/%s/%s/%s' % (self.company, mac, dname, channel)
print topic
msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp))
print msg
self.q.put([topic, msg, 0])
def sendtodbLocLoraCom(self, id, channel, value, timestamp, deviceName):
mac = "1" + id
while len(mac) < 12:
mac = "0" + mac
if deviceName == "mainMeshify":
zigmac = "_[01:00:00:00:00:" + id[0:2] + ":" + id[2:4] + ":" + id[4:6] + "]!"
else:
zigmac = "_[00:00:00:00:01:" + id[0:2] + ":" + id[2:4] + ":" + id[4:6] + "]!"
dname = deviceName + zigmac
#define dname, make id into techname and mac
if id not in self.knownIDs:
self.knownIDs.append(id)
topic = str(("meshify/sets/" + str(self.company) + "/" + mac + "/#"))
self.mqtt.subscribe(topic, 0)
topic = str(("meshify/sets/" + "1" + "/" + mac + "/#"))
self.mqtt.subscribe(topic, 0)
self.mcu.xbees[dname] = self.loraQ
#meshify/db/330/C493000354FB/ilora/c493000354fb2A6E/a1-v
#[ { "value":"0.5635", "timestamp":"1486039316" } ]
if int(timestamp) == 0:
timestamp = self.getTime()
topic = 'meshify/db/%s/%s/%s/%s' % (self.company, mac, dname, channel)
print topic
msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp))
print msg
self.q.put([topic, msg, 0])
def sendtodbLoc(self, ch, channel, value, timestamp, deviceName, mac):
#this will add your derived nodes the master nodes list, allowing them to receive sets!!
localNodesName = deviceName + "_" + str(ch) + "99"
if not self.localNodes.has_key(localNodesName):
self.localNodes[localNodesName] = True
self.nodes[localNodesName] = self
#make the techname
lst = textwrap.wrap(str(mac), width=2)
tech = ""
for i in range(len(lst)):
tech += lst[i].lower() + ":"
chName2 = '_[' + tech
if int(ch) < 10:
ch = "0" + str(int(ch))
if len(ch) > 2:
ch = ch[:-2]
dname = deviceName + chName2 + str(ch) + ":98]!"
if int(timestamp) == 0:
timestamp = self.getTime()
topic = 'meshify/db/%s/%s/%s/%s' % (self.company, mac, dname, channel)
print topic
msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp))
print msg
self.q.put([topic, msg, 0])
def sendtodbDevJSON(self, ch, channel, value, timestamp, deviceName):
if int(ch) < 10:
ch = "0" + str(int(ch))
dname = deviceName + self.chName2 + str(ch) + ":99]!"
if int(timestamp) == 0:
timestamp = self.getTime()
topic = 'meshify/db/%s/%s/%s/%s' % (self.company, self.mac, dname, channel)
print topic
msg = """[ { "value":%s, "timestamp":"%s" } ]""" % (str(value), str(timestamp))
print msg
self.q.put([topic, msg, 0])
def sendtodbLora(self, ch, channel, value, timestamp, deviceName):
if ":" not in ch:
ch = ch[0:2] + ":" + ch[2:4]
#this will add your derived nodes the master nodes list, allowing them to receive sets!!
localNodesName = deviceName + "_" + str(ch).replace(':', "")
if not self.localNodes.has_key(localNodesName):
self.localNodes[localNodesName] = True
self.nodes[localNodesName] = self
dname = deviceName + self.chName2 + str(ch) + "]!"
if int(timestamp) == 0:
timestamp = self.getTime()
topic = 'meshify/db/%s/%s/%s/%s' % (self.company, self.mac, dname, channel)
print topic
msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp))
print msg
self.q.put([topic, msg, 0])
def sendtodbDev(self, ch, channel, value, timestamp, deviceName):
#this will add your derived nodes the master nodes list, allowing them to receive sets!!
localNodesName = deviceName + "_" + str(ch) + "99"
if not self.localNodes.has_key(localNodesName):
self.localNodes[localNodesName] = True
self.nodes[localNodesName] = self
if int(ch) < 10:
ch = "0" + str(int(ch))
dname = deviceName + self.chName2 + str(ch) + ":99]!"
if int(timestamp) == 0:
timestamp = self.getTime()
topic = 'meshify/db/%s/%s/%s/%s' % (self.company, self.mac, dname, channel)
print topic
msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp))
print msg
self.q.put([topic, msg, 0])
def sendtodbCH(self, ch, channel, value, timestamp):
if int(ch) < 10:
ch = "0" + str(ch)
dname = self.chName + str(ch) + ":99]!"
if int(timestamp) == 0:
timestamp = self.getTime()
topic = 'meshify/db/%s/%s/%s/%s' % (self.company, self.mac, dname, channel)
print topic
msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp))
print msg
self.q.put([topic, msg, 0])
def sendtodb(self, channel, value, timestamp):
if int(timestamp) == 0:
timestamp = self.getTime()
if timestamp < 1400499858:
return
else:
timestamp = str(int(timestamp) + int(self.offset))
topic = 'meshify/db/%s/%s/%s/%s' % (self.company, self.mac, self.deviceName, channel)
print topic
msg = """[ { "value":"%s", "timestamp":"%s" } ]""" % (str(value), str(timestamp))
print msg
self.q.put([topic, msg, 0])
def sendtodbJSON(self, channel, value, timestamp):
if int(timestamp) == 0:
timestamp = self.getTime()
if timestamp < 1400499858:
return
else:
timestamp = str(int(timestamp) + int(self.offset))
topic = 'meshify/db/%s/%s/%s/%s' % (self.company, self.mac, self.deviceName, channel)
print topic
msg = """[ { "value":%s, "timestamp":"%s" } ]""" % (str(value), str(timestamp))
print msg
self.q.put([topic, msg, 0])
def getTime(self):
return str(int(time.time() + int(self.offset)))

View File

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

View File

@@ -0,0 +1,21 @@
"""Data persistance functions."""
# if more advanced persistence is needed, use a sqlite database
import json
def load(filename="persist.json"):
"""Load persisted settings from the specified file."""
try:
with open(filename, 'r') as persist_file:
return json.load(persist_file)
except Exception:
return False
def store(persist_obj, filename="persist.json"):
"""Store the persisting settings into the specified file."""
try:
with open(filename, 'w') as persist_file:
return json.dump(persist_obj, persist_file, indent=4)
except Exception:
return False

View File

@@ -0,0 +1,52 @@
"""Utility functions for the driver."""
import socket
import struct
def get_public_ip_address():
"""Find the public IP Address of the host device."""
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(("8.8.8.8", 80))
ip_address = sock.getsockname()[0]
sock.close()
except Exception as e:
return e
return ip_address
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")
return float("NaN")
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_unpacked = struct.unpack('>f', mypack)
print("[{}, {}] >> {}".format(int1, int2, f_unpacked[0]))
return f_unpacked[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

View File

@@ -0,0 +1,133 @@
"""Driver for {{cookiecutter.driver_name}}"""
import threading
import json
import time
from random import randint
import os
from device_base import deviceBase
from Channel import PLCChannel, ModbusChannel,read_tag, write_tag, TAG_DATAERROR_SLEEPTIME
import persistence
from utilities import get_public_ip_address
from file_logger import filelogger as log
PLC_IP_ADDRESS = "{{cookiecutter.ip_address}}"
from Tags import tags
_ = None
log.info("{{cookiecutter.driver_name}} startup")
# GLOBAL VARIABLES
WAIT_FOR_CONNECTION_SECONDS = 20
IP_CHECK_PERIOD = 60
CHANNELS = tags
# PERSISTENCE FILE
PERSIST = persistence.load()
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 = "1"
self.finished = threading.Event()
self.force_send = False
self.public_ip_address = ""
self.public_ip_address_last_checked = 0
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."""
for i in range(0, WAIT_FOR_CONNECTION_SECONDS):
print("{{cookiecutter.driver_name}} driver will start in {} seconds".format(WAIT_FOR_CONNECTION_SECONDS - i))
time.sleep(1)
log.info("BOOM! Starting {{cookiecutter.driver_name}} driver...")
self._check_ip_address()
self.nodes["{{cookiecutter.driver_name}}_0199"] = self
send_loops = 0
while True:
now = time.time()
if self.force_send:
log.warning("FORCE SEND: TRUE")
for chan in CHANNELS:
val = chan.read()
if chan.check(val, self.force_send):
self.sendtodbDev(1, chan.mesh_name, chan.value, 0, '{{cookiecutter.driver_name}}')
time.sleep(TAG_DATAERROR_SLEEPTIME) # sleep to allow Micro800 to handle ENET requests
# print("{{cookiecutter.driver_name}} driver still alive...")
if self.force_send:
if send_loops > 2:
log.warning("Turning off force_send")
self.force_send = False
send_loops = 0
else:
send_loops += 1
if (now - self.public_ip_address_last_checked) > IP_CHECK_PERIOD:
self._check_ip_address()
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.sendtodbDev(1, 'public_ip_address', test_public_ip, 0, '{{cookiecutter.driver_name}}')
self.public_ip_address = test_public_ip
hostname = "google.com"
response = os.system("ping -c 1 " + hostname)
#and then check the response...
if response == 0:
print hostname, 'is up!'
self.ping_counter = 0
else:
print hostname, 'is down!'
self.ping_counter += 1
if self.ping_counter >= 3:
log.info("Rebooting because no internet detected")
os.system('reboot')
def {{cookiecutter.driver_name}}_sync(self, name, value):
"""Sync all data from the driver."""
self.force_send = True
# self.sendtodb("log", "synced", 0)
return True
def {{cookiecutter.driver_name}}_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']
write_res = write_tag(str(PLC_IP_ADDRESS), tag_n, val_n, plc_type="{{cookiecutter.device_type}}")
print("Result of {{cookiecutter.driver_name}}_writeplctag(self, {}, {}) = {}".format(name, value, write_res))
if write_res is None:
write_res = "Error writing to PLC..."
return write_res