From 1c224c3698169cbb96ecb6e582892bf821b04a4c Mon Sep 17 00:00:00 2001 From: Jason Nance Date: Mon, 31 Jul 2023 00:11:29 -0500 Subject: [PATCH] Initial add --- .gitignore | 5 + README.md | 4 + envoy.py | 201 ++++++++++++++++++ panel-json-to-csv.py | 46 +++++ panel-json-to-yaml.py | 42 ++++ requirements.txt | 3 + sample-data/README.md | 37 ++++ sample-data/inverter_production_data.json | 240 ++++++++++++++++++++++ sample-data/load_consumption_data.json | 104 ++++++++++ sample-data/meter_details.json | 20 ++ sample-data/meter_live_data.json | 97 +++++++++ sample-data/meter_readings.json | 142 +++++++++++++ 12 files changed, 941 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100755 envoy.py create mode 100755 panel-json-to-csv.py create mode 100755 panel-json-to-yaml.py create mode 100644 requirements.txt create mode 100644 sample-data/README.md create mode 100644 sample-data/inverter_production_data.json create mode 100644 sample-data/load_consumption_data.json create mode 100644 sample-data/meter_details.json create mode 100644 sample-data/meter_live_data.json create mode 100644 sample-data/meter_readings.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..37a588a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +.venv/ +.vscode +credentials.json +envoy-logger-config.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..c2175cb --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +Miscellaneous scripts used to gather data from an Enphase Envoy/Gateway + +This stuff is meant for research. If you're looking for an organized project +that queries data from an Envoy to save and graph, check out amykyta3/envoy-logger. diff --git a/envoy.py b/envoy.py new file mode 100755 index 0000000..c93b7fa --- /dev/null +++ b/envoy.py @@ -0,0 +1,201 @@ +#!/usr/bin/env python3 + +""" envoy.py + +Read data from a local Enphase Envoy and write it to stdout (print it) + +Usage: + + envoy.py + +...where is one of: + + meter_details + meter_readings + inverter_production_data + meter_live_data + load_consumption_data + home_json + production_json + production_json_details + inventory_json + inventory_json_deleted + +Before running this script you must create a ".env" file in the same directory +as this script with: + + ENLIGHTEN_USERNAME="Enlighten username (email address)" + ENLIGHTEN_PASSWORD="Enlighten password" + ENVOY_SERIAL="serial number of your Envoy" + ENVOY_HOSTNAME="hostname or IP address of your Envoy/Gateway" + +Alternatively, you can use environment variables of the same names. + +!!! SECURITY WARNING !!! + +This script will create a "credentials.json" file in the same directory as this +script that includes an API token that can be used to access your Envoy/Gateway. +Additionally, the .env file has similar credentials. Be careful!! You may want +to create an additional Enlighten account and grant it read-only access to your +Envoy. + +""" + +import datetime +import json +import os +import sys +import traceback +from typing import Any + +import jwt +import requests +from dotenv import dotenv_values +from urllib3.exceptions import InsecureRequestWarning + +# Envoy uses self-signed SSL certificate +requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning) + +DATA_URIS = { + "meter_details": "/ivp/meters", + "meter_readings": "/ivp/meters/readings", + "inverter_production_data": "/api/v1/production/inverters", + "meter_live_data": "/ivp/livedata/status", + "load_consumption_data": "/ivp/meters/reports/consumption", + "home_json": "/home.json", + "production_json": "/production.json", + "production_json_details": "/production.jsoni?details=1", + "inventory_json": "/inventory.json", + "inventory_json_deleted": "/inventory.json?deleted=1", +} + +CREDENTIALS_FILE = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "credentials.json" +) + + +def get_token(config: dict = dotenv_values()) -> dict: + s = requests.Session() + + # Initiate a login session + # This is a form post, hence "data=" (not "json=") + # A JSON string is returned in the response body + r = s.post( + "https://enlighten.enphaseenergy.com/login/login.json?", + data={ + "user[email]": config["ENLIGHTEN_USERNAME"], + "user[password]": config["ENLIGHTEN_PASSWORD"], + }, + ) + + # Example Response JSON: + # + # { + # "message": "success", + # "session_id": "blahblahblahhexstring", + # "manager_token": "big.old_long_jwt_token_here", + # "is_consumer": True + # } + login_json = json.loads(r.text) + + # Retrieve a token + r = s.post( + "https://entrez.enphaseenergy.com/tokens", + json={ + "session_id": login_json["session_id"], + "serial_num": config["ENVOY_SERIAL"], + "username": config["ENLIGHTEN_USERNAME"], + }, + ) + + token = r.text + + # Extract the expiration + decoded = jwt.decode( + token, + options={"verify_signature": False}, + ) + + expiration = datetime.datetime.fromtimestamp(decoded["exp"]) + + # Write credentials file + with open(CREDENTIALS_FILE, "w", encoding="utf-8") as f: + json.dump( + { + "token": token, + "expires": str(expiration), + "expires_epoch": decoded["exp"], + }, + f, + ensure_ascii=False, + indent=4, + ) + + return { + "token": token, + "expires": str(expiration), + "expires_epoch": decoded["exp"], + } + + +def read_token(config: dict = dotenv_values()) -> dict: + with open(CREDENTIALS_FILE, "r", encoding="utf-8") as f: + credentials = json.load(f) + + expiration = datetime.datetime.fromtimestamp(credentials["expires_epoch"]) + + if expiration <= datetime.datetime.now(): + # Token is expired, attempt refresh + credentials = get_token(config) + + return credentials + + +def get_data(envoy_hostname: str, endpoint: str, token: str) -> Any: + r = requests.get( + f"https://{envoy_hostname}{DATA_URIS[endpoint]}", + headers={ + "Accept": "application/json", + "Authorization": f"Bearer {token}", + }, + verify=False, + ) + return r.json() + + +def main(endpoint: str, config: dict = dotenv_values()) -> None: + if endpoint not in DATA_URIS.keys(): + raise RuntimeError("Unknown endpoint") + + if os.path.isfile(CREDENTIALS_FILE): + credentials = read_token(config) + else: + credentials = get_token(config) + + print( + json.dumps( + get_data( + envoy_hostname=config["ENVOY_HOSTNAME"], + endpoint=endpoint, + token=credentials["token"], + ) + ) + ) + + +if __name__ == "__main__": + if len(sys.argv) != 2: + sys.stderr.write( + f""" +Usage: + {__file__} + +""" + ) + sys.exit(1) + + try: + main(endpoint=sys.argv[1]) + except: + sys.stderr.write(traceback.format_exc()) + sys.exit(1) diff --git a/panel-json-to-csv.py b/panel-json-to-csv.py new file mode 100755 index 0000000..ccd4c9a --- /dev/null +++ b/panel-json-to-csv.py @@ -0,0 +1,46 @@ +#!/usr/bin/env python3 + +""" panel-json-to-csv.py + +Read a Envoy panel.json file and write a CSV version of it. + +This can be helpful when assigning row/col labels for your panels such as what +is used by Envoy Logger. + +""" + +import csv +import json +from collections import OrderedDict + +with open("panel.json", "r") as f: + panel_json = json.load(f) + +config = {} + +for module in panel_json["arrays"][0]["modules"]: + config[module["inverter"]["serial_num"]] = { + "tags": { + "module_id": module["module_id"], + "x": module["x"], + "y": module["y"], + "inverter_id": module["inverter"]["inverter_id"], + }, + } + +# Sort by x then y +# This returns a list of tuples for some reason +sorted_config = sorted( + config.items(), key=lambda i: (i[1]["tags"]["y"], i[1]["tags"]["x"]) +) + +config = OrderedDict() + +for c in sorted_config: + config[c[0]] = c[1] + +with open("panel.csv", "w") as f: + c = csv.writer(f) + c.writerow(["Serial", "X", "Y"]) + for panel in config.items(): + c.writerow([f"'{panel[0]}", panel[1]["tags"]["x"], panel[1]["tags"]["y"]]) diff --git a/panel-json-to-yaml.py b/panel-json-to-yaml.py new file mode 100755 index 0000000..dc8aa59 --- /dev/null +++ b/panel-json-to-yaml.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 + +""" panel-json-to-yaml.py + +Read a Envoy panel.json file and write YAML version of it. + +This can be helpful when generating an Envoy Logger configuration file. + +""" + +import json +from collections import OrderedDict + +import yaml + +with open("panel.json", "r") as f: + panel_json = json.load(f) + +config = {} + +for module in panel_json["arrays"][0]["modules"]: + config[module["inverter"]["serial_num"]] = { + "tags": { + "module_id": module["module_id"], + "x": module["x"], + "y": module["y"], + "inverter_id": module["inverter"]["inverter_id"], + }, + } + +# Sort by x then y +# This returns a list of tuples for some reason +sorted_config = sorted( + config.items(), key=lambda i: (i[1]["tags"]["y"], i[1]["tags"]["x"]) +) + +config = OrderedDict() + +for c in sorted_config: + config[c[0]] = c[1] + +print(yaml.dump(config)) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..381b336 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +PyJWT +python-dotenv +requests diff --git a/sample-data/README.md b/sample-data/README.md new file mode 100644 index 0000000..a2278ee --- /dev/null +++ b/sample-data/README.md @@ -0,0 +1,37 @@ +Sample data gathered From an Enphase Envoy/Gateway using the `envoy.py` script +in the parent directory. This data is from a site in the US with: + +- Single phase service (split phase / standard US 120v/240v) +- Solar panels +- Batteries +- Generator +- Central US timezone + +Timestamps appear to be in Unix format (seconds since midnight Jan 1, 1970). + +This data was dumped at night, so much of the production information is 0. + +Some data has been redacted as I'm unsure what is considered "private" in the +Enphase world. Where it has, you will find "REDACTED-\". Some data +is tracked through its redactions. For example, a file with: + + [ + { + "some_secret_number": 12345, + "another_secret_number": 67890 + } + ] + +Would be updated to: + + [ + { + "some_secret_number": REDACTED-5-DIGIT-INTEGER-A, + "another_secret_number": REDACTED-5-DIGIT-INTEGER-B + } + ] + +Note the lack of quotes (rendering the JSON invalid) because it was an integer +and that the use of "A" and "B" in the name to indicate that they were +different values. Nested values may have "AA", "AB", "AC"... to indicate three +unique values nested under previously redacted value "A". diff --git a/sample-data/inverter_production_data.json b/sample-data/inverter_production_data.json new file mode 100644 index 0000000..9971a2e --- /dev/null +++ b/sample-data/inverter_production_data.json @@ -0,0 +1,240 @@ +[ + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593704, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 321 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593705, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 313 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593927, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 315 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593640, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 315 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593968, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 309 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593706, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 311 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593904, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 311 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593713, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 310 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690594067, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 308 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593971, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 312 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593657, + "devType": 1, + "lastReportWatts": 1, + "maxReportWatts": 308 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593985, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 314 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593987, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 306 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593988, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 310 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593989, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 310 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593789, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 311 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593871, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 308 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593568, + "devType": 1, + "lastReportWatts": 1, + "maxReportWatts": 308 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593708, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 310 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593827, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 310 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593754, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 307 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593831, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 310 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593761, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 314 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593840, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 309 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593991, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 311 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593910, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 304 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593757, + "devType": 1, + "lastReportWatts": 1, + "maxReportWatts": 309 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593571, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 308 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593994, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 305 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593648, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 311 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593649, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 305 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593895, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 309 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690594000, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 308 + }, + { + "serialNumber": "REDACTED-PANEL-SERIAL", + "lastReportDate": 1690593931, + "devType": 1, + "lastReportWatts": 0, + "maxReportWatts": 307 + } +] diff --git a/sample-data/load_consumption_data.json b/sample-data/load_consumption_data.json new file mode 100644 index 0000000..1c71c41 --- /dev/null +++ b/sample-data/load_consumption_data.json @@ -0,0 +1,104 @@ +[ + { + "createdAt": 1690599576, + "reportType": "total-consumption", + "cumulative": { + "currW": 6449.843, + "actPower": 6449.843, + "apprntPwr": 14588.359, + "reactPwr": -287.352, + "whDlvdCum": 2852687.878, + "whRcvdCum": 0.0, + "varhLagCum": 593553.235, + "varhLeadCum": 362998.218, + "vahCum": 2719292.009, + "rmsVoltage": 237.964, + "rmsCurrent": 61.305, + "pwrFactor": 0.44, + "freqHz": 60.0 + }, + "lines": [ + { + "currW": 3488.178, + "actPower": 3488.178, + "apprntPwr": 3894.08, + "reactPwr": -407.412, + "whDlvdCum": 1601470.541, + "whRcvdCum": 0.0, + "varhLagCum": 267816.532, + "varhLeadCum": 180039.569, + "vahCum": 1445024.591, + "rmsVoltage": 118.891, + "rmsCurrent": 32.753, + "pwrFactor": 0.9, + "freqHz": 60.0 + }, + { + "currW": 2961.665, + "actPower": 2961.665, + "apprntPwr": 3399.718, + "reactPwr": 120.059, + "whDlvdCum": 1251217.336, + "whRcvdCum": 0.0, + "varhLagCum": 325736.703, + "varhLeadCum": 182958.649, + "vahCum": 1274267.418, + "rmsVoltage": 119.073, + "rmsCurrent": 28.552, + "pwrFactor": 0.87, + "freqHz": 60.0 + } + ] + }, + { + "createdAt": 1690599576, + "reportType": "net-consumption", + "cumulative": { + "currW": 6449.843, + "actPower": 6449.843, + "apprntPwr": 6634.482, + "reactPwr": 369.555, + "whDlvdCum": 1556011.455, + "whRcvdCum": 225516.011, + "varhLagCum": 279595.563, + "varhLeadCum": 357523.999, + "vahCum": 2719292.009, + "rmsVoltage": 237.987, + "rmsCurrent": 55.742, + "pwrFactor": 0.97, + "freqHz": 60.0 + }, + "lines": [ + { + "currW": 3488.178, + "actPower": 3488.178, + "apprntPwr": 3562.889, + "reactPwr": -79.784, + "whDlvdCum": 923728.888, + "whRcvdCum": 81943.817, + "varhLagCum": 111653.472, + "varhLeadCum": 177070.986, + "vahCum": 1445024.591, + "rmsVoltage": 118.916, + "rmsCurrent": 29.976, + "pwrFactor": 0.98, + "freqHz": 60.0 + }, + { + "currW": 2961.665, + "actPower": 2961.665, + "apprntPwr": 3071.593, + "reactPwr": 449.339, + "whDlvdCum": 632282.567, + "whRcvdCum": 143572.194, + "varhLagCum": 167942.091, + "varhLeadCum": 180453.013, + "vahCum": 1274267.418, + "rmsVoltage": 119.072, + "rmsCurrent": 25.766, + "pwrFactor": 0.97, + "freqHz": 60.0 + } + ] + } +] diff --git a/sample-data/meter_details.json b/sample-data/meter_details.json new file mode 100644 index 0000000..0c0e563 --- /dev/null +++ b/sample-data/meter_details.json @@ -0,0 +1,20 @@ +[ + { + "eid": REDACTED-9-DIGIT-INTEGER-A, + "state": "enabled", + "measurementType": "production", + "phaseMode": "split", + "phaseCount": 2, + "meteringStatus": "normal", + "statusFlags": [] + }, + { + "eid": REDACTED-9-DIGIT-INTEGER-B, + "state": "enabled", + "measurementType": "net-consumption", + "phaseMode": "split", + "phaseCount": 2, + "meteringStatus": "normal", + "statusFlags": [] + } +] diff --git a/sample-data/meter_live_data.json b/sample-data/meter_live_data.json new file mode 100644 index 0000000..cbc18a6 --- /dev/null +++ b/sample-data/meter_live_data.json @@ -0,0 +1,97 @@ +{ + "connection": { + "mqtt_state": "connected", + "prov_state": "configured", + "auth_state": "ok", + "sc_stream": "disabled", + "sc_debug": "disabled" + }, + "meters": { + "last_update": 1690594185, + "soc": 100, + "main_relay_state": 1, + "gen_relay_state": 5, + "backup_bat_mode": 0, + "backup_soc": 100, + "is_split_phase": 1, + "phase_count": 0, + "enc_agg_soc": 100, + "enc_agg_energy": 40320, + "acb_agg_soc": 0, + "acb_agg_energy": 0, + "pv": { + "agg_p_mw": 0, + "agg_s_mva": 675087, + "agg_p_ph_a_mw": 0, + "agg_p_ph_b_mw": 0, + "agg_p_ph_c_mw": 0, + "agg_s_ph_a_mva": 675087, + "agg_s_ph_b_mva": 0, + "agg_s_ph_c_mva": 0 + }, + "storage": { + "agg_p_mw": -17000, + "agg_s_mva": -655291, + "agg_p_ph_a_mw": -17000, + "agg_p_ph_b_mw": 0, + "agg_p_ph_c_mw": 0, + "agg_s_ph_a_mva": -655291, + "agg_s_ph_b_mva": 0, + "agg_s_ph_c_mva": 0 + }, + "grid": { + "agg_p_mw": 1862778, + "agg_s_mva": 2818992, + "agg_p_ph_a_mw": 1862778, + "agg_p_ph_b_mw": 0, + "agg_p_ph_c_mw": 0, + "agg_s_ph_a_mva": 2818992, + "agg_s_ph_b_mva": 0, + "agg_s_ph_c_mva": 0 + }, + "load": { + "agg_p_mw": 1845778, + "agg_s_mva": 2838788, + "agg_p_ph_a_mw": 1845778, + "agg_p_ph_b_mw": 0, + "agg_p_ph_c_mw": 0, + "agg_s_ph_a_mva": 2838788, + "agg_s_ph_b_mva": 0, + "agg_s_ph_c_mva": 0 + }, + "generator": { + "agg_p_mw": 0, + "agg_s_mva": 0, + "agg_p_ph_a_mw": 0, + "agg_p_ph_b_mw": 0, + "agg_p_ph_c_mw": 0, + "agg_s_ph_a_mva": 0, + "agg_s_ph_b_mva": 0, + "agg_s_ph_c_mva": 0 + } + }, + "tasks": { + "task_id": -2024873250, + "timestamp": 1690367536 + }, + "counters": { + "main_CfgLoad": 1, + "main_CfgChanged": 1, + "main_CfgNotFound": 943, + "main_taskUpdate": 76, + "MqttClient_publish": 133426, + "MqttClient_respond": 1144, + "MqttClient_msgarrvd": 572, + "MqttClient_create": 221, + "MqttClient_setCallbacks": 221, + "MqttClient_connect": 221, + "MqttClient_connect_err": 133, + "MqttClient_connect_Err": 133, + "MqttClient_subscribe": 88, + "SSL_Keys_Create": 221, + "sc_hdlDataPub": 133137, + "sc_SendStreamCtrl": 611, + "sc_SendDemandRspCtrl": 1, + "rest_Status": 15684 + } +} diff --git a/sample-data/meter_readings.json b/sample-data/meter_readings.json new file mode 100644 index 0000000..ea407d9 --- /dev/null +++ b/sample-data/meter_readings.json @@ -0,0 +1,142 @@ +[ + { + "eid": REDACTED-9-DIGIT-INTEGER-A, + "timestamp": 1690594248, + "actEnergyDlvd": 1522390.271, + "actEnergyRcvd": 3.1, + "apparentEnergy": 1821068.249, + "reactEnergyLagg": 312992.506, + "reactEnergyLead": 5474.219, + "instantaneousDemand": 0.0, + "activePower": 0.0, + "apparentPower": 668.916, + "reactivePower": 655.926, + "pwrFactor": 0.0, + "voltage": 237.119, + "current": 5.639, + "freq": 60.0, + "channels": [ + { + "eid": REDACTED-9-DIGIT-INTEGER-AA, + "timestamp": 1690594248, + "actEnergyDlvd": 759784.347, + "actEnergyRcvd": 1.555, + "apparentEnergy": 908674.821, + "reactEnergyLagg": 155680.496, + "reactEnergyLead": 2968.583, + "instantaneousDemand": 0.0, + "activePower": 0.0, + "apparentPower": 333.665, + "reactivePower": 327.24, + "pwrFactor": 0.0, + "voltage": 118.404, + "current": 2.817, + "freq": 60.0 + }, + { + "eid": REDACTED-9-DIGIT-INTEGER-AB, + "timestamp": 1690594248, + "actEnergyDlvd": 762605.924, + "actEnergyRcvd": 1.545, + "apparentEnergy": 912393.427, + "reactEnergyLagg": 157312.01, + "reactEnergyLead": 2505.636, + "instantaneousDemand": 0.0, + "activePower": 0.0, + "apparentPower": 335.251, + "reactivePower": 328.686, + "pwrFactor": 0.0, + "voltage": 118.716, + "current": 2.823, + "freq": 60.0 + }, + { + "eid": REDACTED-9-DIGIT-INTEGER-AC, + "timestamp": 1690594248, + "actEnergyDlvd": 0.0, + "actEnergyRcvd": 0.0, + "apparentEnergy": 0.0, + "reactEnergyLagg": 0.0, + "reactEnergyLead": 0.0, + "instantaneousDemand": 0.0, + "activePower": 0.0, + "apparentPower": 0.0, + "reactivePower": 0.0, + "pwrFactor": 0.0, + "voltage": 0.0, + "current": 0.0, + "freq": 60.0 + } + ] + }, + { + "eid": REDACTED-9-DIGIT-INTEGER-B, + "timestamp": 1690594248, + "actEnergyDlvd": 1546337.423, + "actEnergyRcvd": 225516.011, + "apparentEnergy": 2709177.49, + "reactEnergyLagg": 279047.717, + "reactEnergyLead": 356997.001, + "instantaneousDemand": 1744.595, + "activePower": 1744.595, + "apparentPower": 2733.154, + "reactivePower": -1155.227, + "pwrFactor": 0.634, + "voltage": 237.062, + "current": 23.085, + "freq": 60.0, + "channels": [ + { + "eid": REDACTED-9-DIGIT-INTEGER-BA, + "timestamp": 1690594248, + "actEnergyDlvd": 918923.88, + "actEnergyRcvd": 81943.817, + "apparentEnergy": 1440010.371, + "reactEnergyLagg": 111648.737, + "reactEnergyLead": 176674.563, + "instantaneousDemand": 1034.941, + "activePower": 1034.941, + "apparentPower": 1529.27, + "reactivePower": -717.363, + "pwrFactor": 0.675, + "voltage": 118.389, + "current": 12.927, + "freq": 60.0 + }, + { + "eid": REDACTED-9-DIGIT-INTEGER-BB, + "timestamp": 1690594248, + "actEnergyDlvd": 627413.543, + "actEnergyRcvd": 143572.194, + "apparentEnergy": 1269167.119, + "reactEnergyLagg": 167398.981, + "reactEnergyLead": 180322.439, + "instantaneousDemand": 709.653, + "activePower": 709.653, + "apparentPower": 1203.884, + "reactivePower": -437.865, + "pwrFactor": 0.582, + "voltage": 118.673, + "current": 10.158, + "freq": 60.0 + }, + { + "eid": REDACTED-9-DIGIT-INTEGER-BC, + "timestamp": 1690594248, + "actEnergyDlvd": 0.0, + "actEnergyRcvd": 0.0, + "apparentEnergy": 0.0, + "reactEnergyLagg": 0.0, + "reactEnergyLead": 0.0, + "instantaneousDemand": 0.0, + "activePower": 0.0, + "apparentPower": 0.0, + "reactivePower": 0.0, + "pwrFactor": 0.0, + "voltage": 0.0, + "current": 0.0, + "freq": 60.0 + } + ] + } +]