From 0acf88a1e9c6881633333081d0b586b6d03bf2d8 Mon Sep 17 00:00:00 2001 From: Nico Melone Date: Thu, 29 Apr 2021 19:22:16 -0400 Subject: [PATCH] initial --- Example Taglist.csv | 60 +++ POCloud-CookieCutter-master/cookiecutter.json | 6 + POCloud-CookieCutter-master/hooks/api_test.py | 19 + POCloud-CookieCutter-master/hooks/meshify.py | 404 ++++++++++++++++++ POCloud-CookieCutter-master/hooks/meshify.pyc | Bin 0 -> 14090 bytes .../hooks/post_gen_project.py | 103 +++++ .../{{cookiecutter.driver_name}}/Channel.py | 299 +++++++++++++ .../{{cookiecutter.driver_name}}/Tags.py | 3 + .../{{cookiecutter.driver_name}}/config.txt | 14 + .../device_base.py | 360 ++++++++++++++++ .../file_logger.py | 18 + .../persistence.py | 21 + .../{{cookiecutter.driver_name}}/utilities.py | 52 +++ .../{{cookiecutter.driver_name}}.py | 133 ++++++ 14 files changed, 1492 insertions(+) create mode 100644 Example Taglist.csv create mode 100644 POCloud-CookieCutter-master/cookiecutter.json create mode 100644 POCloud-CookieCutter-master/hooks/api_test.py create mode 100644 POCloud-CookieCutter-master/hooks/meshify.py create mode 100644 POCloud-CookieCutter-master/hooks/meshify.pyc create mode 100644 POCloud-CookieCutter-master/hooks/post_gen_project.py create mode 100644 POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/Channel.py create mode 100644 POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/Tags.py create mode 100644 POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/config.txt create mode 100644 POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/device_base.py create mode 100644 POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/file_logger.py create mode 100644 POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/persistence.py create mode 100644 POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/utilities.py create mode 100644 POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/{{cookiecutter.driver_name}}.py diff --git a/Example Taglist.csv b/Example Taglist.csv new file mode 100644 index 0000000..49383cf --- /dev/null +++ b/Example Taglist.csv @@ -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 diff --git a/POCloud-CookieCutter-master/cookiecutter.json b/POCloud-CookieCutter-master/cookiecutter.json new file mode 100644 index 0000000..20bd4b7 --- /dev/null +++ b/POCloud-CookieCutter-master/cookiecutter.json @@ -0,0 +1,6 @@ +{ + "driver_name": "pocloud_driver", + "ip_address": "192.168.1.10", + "device_type": ["CLX","Micro800", "None"] + +} diff --git a/POCloud-CookieCutter-master/hooks/api_test.py b/POCloud-CookieCutter-master/hooks/api_test.py new file mode 100644 index 0000000..be748f5 --- /dev/null +++ b/POCloud-CookieCutter-master/hooks/api_test.py @@ -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)) diff --git a/POCloud-CookieCutter-master/hooks/meshify.py b/POCloud-CookieCutter-master/hooks/meshify.py new file mode 100644 index 0000000..98add40 --- /dev/null +++ b/POCloud-CookieCutter-master/hooks/meshify.py @@ -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() diff --git a/POCloud-CookieCutter-master/hooks/meshify.pyc b/POCloud-CookieCutter-master/hooks/meshify.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8804f05e4bd5899a0f2da2421fb879bf468daecc GIT binary patch literal 14090 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 literal 0 HcmV?d00001 diff --git a/POCloud-CookieCutter-master/hooks/post_gen_project.py b/POCloud-CookieCutter-master/hooks/post_gen_project.py new file mode 100644 index 0000000..900873c --- /dev/null +++ b/POCloud-CookieCutter-master/hooks/post_gen_project.py @@ -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") + + + diff --git a/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/Channel.py b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/Channel.py new file mode 100644 index 0000000..353ead7 --- /dev/null +++ b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/Channel.py @@ -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 diff --git a/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/Tags.py b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/Tags.py new file mode 100644 index 0000000..d5f6065 --- /dev/null +++ b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/Tags.py @@ -0,0 +1,3 @@ +from Channel import PLCChannel, ModbusChannel +from {{cookiecutter.driver_name}} import PLC_IP_ADDRESS + diff --git a/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/config.txt b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/config.txt new file mode 100644 index 0000000..1b02758 --- /dev/null +++ b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/config.txt @@ -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" +} \ No newline at end of file diff --git a/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/device_base.py b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/device_base.py new file mode 100644 index 0000000..6e4a3e4 --- /dev/null +++ b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/device_base.py @@ -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))) + + + + diff --git a/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/file_logger.py b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/file_logger.py new file mode 100644 index 0000000..bf47f8e --- /dev/null +++ b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/file_logger.py @@ -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) diff --git a/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/persistence.py b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/persistence.py new file mode 100644 index 0000000..8c8703f --- /dev/null +++ b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/persistence.py @@ -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 diff --git a/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/utilities.py b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/utilities.py new file mode 100644 index 0000000..7e88d62 --- /dev/null +++ b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/utilities.py @@ -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 diff --git a/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/{{cookiecutter.driver_name}}.py b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/{{cookiecutter.driver_name}}.py new file mode 100644 index 0000000..277373b --- /dev/null +++ b/POCloud-CookieCutter-master/{{cookiecutter.driver_name}}/{{cookiecutter.driver_name}}.py @@ -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