From 9590ee575b772f1d8f1942d46036b05002fe4690 Mon Sep 17 00:00:00 2001 From: Nico Melone Date: Wed, 9 Aug 2023 21:34:45 -0500 Subject: [PATCH] initi --- .DS_Store | Bin 0 -> 6148 bytes __pycache__/lattice.cpython-310.pyc | Bin 0 -> 10620 bytes __pycache__/lattice.cpython-311.pyc | Bin 0 -> 20648 bytes __pycache__/lattice.cpython-39.pyc | Bin 0 -> 10645 bytes billing.log | 0 hp-billing-report.ipynb | 19 ++ lattice.py | 419 ++++++++++++++++++++++++++++ 7 files changed, 438 insertions(+) create mode 100644 .DS_Store create mode 100644 __pycache__/lattice.cpython-310.pyc create mode 100644 __pycache__/lattice.cpython-311.pyc create mode 100644 __pycache__/lattice.cpython-39.pyc create mode 100644 billing.log create mode 100644 hp-billing-report.ipynb create mode 100644 lattice.py diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 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 LHvR(N`n~@Lvf(K3 literal 0 HcmV?d00001 diff --git a/__pycache__/lattice.cpython-311.pyc b/__pycache__/lattice.cpython-311.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6d0c86e44bfe43f64660659a6274fd10a608ba19 GIT binary patch literal 20648 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 G?EeM0c-~(C literal 0 HcmV?d00001 diff --git a/__pycache__/lattice.cpython-39.pyc b/__pycache__/lattice.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..da96c23ea0eea0d522f04bdf6b32a78528f894e3 GIT binary patch literal 10645 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 QXG4QYIz$#s@_{w;f3h$QiU0rr literal 0 HcmV?d00001 diff --git a/billing.log b/billing.log new file mode 100644 index 0000000..e69de29 diff --git a/hp-billing-report.ipynb b/hp-billing-report.ipynb new file mode 100644 index 0000000..a610084 --- /dev/null +++ b/hp-billing-report.ipynb @@ -0,0 +1,19 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "language_info": { + "name": "python" + }, + "orig_nbformat": 4 + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/lattice.py b/lattice.py new file mode 100644 index 0000000..bc901bd --- /dev/null +++ b/lattice.py @@ -0,0 +1,419 @@ +"""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://194.p2121.net/api/" #getenv("MESHIFY_BASE_URL") +MESHIFY_USERNAME = "reportuser@henrypump.com" #getenv("MESHIFY_USERNAME") +MESHIFY_PASSWORD = "Kk8kMU2cc6vqVy" #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 GET(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 getNodeTypes(): + return GET("nodetypes") + +def getNodes(): + return GET("nodes") + +def getFolders(): + return GET("folders") + +def getChannelValues(nodeId): + return GET("data/current?nodeId={}".format(nodeId)) + +def getUsers(): + return GET("users") + +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 = GET('devicetypes') + this_devicetype = find_by_name(device_type_name, devicetypes) + channels = GET('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 = GET('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()