diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..e37503d Binary files /dev/null and b/.DS_Store differ diff --git a/debug.py b/debug.py new file mode 100644 index 0000000..1b37965 --- /dev/null +++ b/debug.py @@ -0,0 +1,114 @@ +from solaxx3.rs485 import SolaxX3 +solax_items=[ +"power_dc1", +"power_dc2", +"battery_capacity", +"radiator_temperature", +"grid_power", +"grid_voltage", +"grid_current", +"pv_voltage_1", +"pv_voltage_2", +"pv_current_1", +"pv_current_2", +"consumption_energy_today", +"feed_in_power", +"energy_to_grid_total", +"feed_in_energy_today", +"consumption_energy_today", +"solar_energy_today", +"battery_power_charge1", +"temperature_battery", +"output_energy_charge_today", +"grid_current_r", +"grid_current_s", +"grid_current_t", +"grid_power_r", +"grid_power_s", +"grid_power_t", +"grid_voltage_r", +"grid_voltage_s", +"grid_voltage_t" +] + + +openhab_items=[ +"Solax_PowerDC1", +"Solax_PowerDC2", +"Solax_BatterySOC", +"Solax_TempRadiator", +"Solax_ACPower", +"Solax_NetworkVoltage", +"Solax_OutputCurrent", +"Solax_PV1Voltage", +"Solax_PV2Voltage", +"Solax_PV1Current", +"Solax_PV2Current", +"Solax_ConsumeFromGridToday", +"Solax_FeedInPower", +"Solax_FeedInTotal", +"Solax_FeedinEnergyToday", +"Solax_HouseConsumption", +"Solax_DailyYield", +"Solax_BatteryPowerCharge", +"Solax_BatteryTemp", +"Solax_BatteryOutputEnergyToday", +"Solax_GridCurrent_1", +"Solax_GridCurrent_2", +"Solax_GridCurrent_3", +"Solax_GridPower_1", +"Solax_GridPower_2", +"Solax_GridPower_3", +"Solax_GridVoltage_1", +"Solax_GridVoltage_2", +"Solax_GridVoltage_3" +] + +# adjust the serial port and baud rate as necessary +s = SolaxX3(port="/dev/ttySOLAX", baudrate=19200) +data =[] +if s.connect(): + s.read_all_registers() + available_stats = s.list_register_names() + for stat in available_stats: + print(stat + " " + str(s.read(stat)[0])) + battery_capacity = s.read("battery_capacity")[0] + feed_in_today = s.read("feed_in_energy_today")[0] + consumtion_today = s.read("consumption_energy_today")[0] + battery_charging = s.read("battery_power_charge1")[0] + grid_voltage_r = s.read("grid_voltage_r")[0] + grid_voltage_s = s.read("grid_voltage_s")[0] + grid_voltage_t = s.read("grid_voltage_t")[0] + run_mode = s.read("run_mode")[0] + time_count_down = s.read("time_count_down")[0] + inverter_ac_power = s.read("grid_power")[0] + etoday_togrid = s.read("energy_to_grid_today")[0] + solar_energy_today = s.read("solar_energy_today")[0] + echarge_today = s.read("echarge_today")[0] + energy_from_grid = s.read("energy_from_grid_meter")[0] + energy_to_grid = s.read("energy_to_grid_meter")[0] + power_to_ev = s.read("power_to_ev")[0] + feed_in_power = s.read("feed_in_power")[0] + output_energy_charge = s.read("output_energy_charge")[0] + output_energy_today = s.read("output_energy_charge_today")[0] + input_energy_today = s.read("input_energy_charge_today")[0] + power_dc1 = s.read("power_dc1")[0] + power_dc2 = s.read("power_dc2")[0] + total_power = power_dc1 + power_dc2 + #battery_temperature = s.read("temperature_battery") + #print(f"\n\nBattery temperature: {s.read('temperature_battery')}") +# for i, item in enumerate(solax_items): +# print (item + " " + str(s.read(item)[0]) + str(s.read(item)[1])+ " " + openhab_items[i]) + + +# for i, item in enumerate(solax_items): +# try: +# act_item=items.get(openhab_items[i]) +# act_item.state=float(s.read(item)[0]) +# data += [openhab_items[i]+" value="+str(s.read(item)[0])] +# except: +# print("error in" + openhab_items[i] + " " + item ) + +else: + print("Cannot connect to the Modbus Server/Slave") + exit() diff --git a/solax.py b/solax.py index c53e1b6..1e2e706 100644 --- a/solax.py +++ b/solax.py @@ -127,6 +127,8 @@ if s.connect(): data += ["Solax_SUM_DC"+" value="+str(total_power)] data += ["Solax_eToGridToday"+" value="+str(etoday_togrid)] data += ["Solax_feed_in_today"+" value="+str(feed_in_today)] + data += ["Solax_runMode"+" value="+str(run_mode)] + data += ["Solax_time_count_down"+" value="+str(time_count_down)] write_api = client.write_api(write_options=SYNCHRONOUS) write_api.write(bucket, org, data) diff --git a/source/LICENSE b/source/LICENSE new file mode 100644 index 0000000..eb633aa --- /dev/null +++ b/source/LICENSE @@ -0,0 +1,8 @@ +The MIT License (MIT) +Copyright © 2022 + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/source/README.md b/source/README.md new file mode 100644 index 0000000..1732c15 --- /dev/null +++ b/source/README.md @@ -0,0 +1,50 @@ +# solax-x3 +#### Read in real-time all parameters provided by Solax X3 solar inverter via its Modbus S-485 serial interface. + +
+ +## Prerequisites + +* Solax X3 inverter +* Modbus RS-485 serial adapter/interface +* [Modbus cable](https://github.com/mkfam7/solaxx3/blob/main/diagrams/rs485_cable.png) +* python version >= 3.8 +* This python module + +## Installation + +``` +pip install solaxx3 +``` + +## Usage + + +``` +from solaxx3.rs485 import SolaxX3 + +# adjust the serial port and baud rate as necessary +s = SolaxX3(port="/dev/ttyUSB0", baudrate=115200) + +if s.connect(): + s.read_all_registers() + + available_stats = s.list_register_names() + for stat in available_stats: + print(stat) + + battery_temperature = s.read("temperature_battery") + print(f"\n\nBattery temperature: {s.read('temperature_battery')}") + + +else: + print("Cannot connect to the Modbus Server/Slave") + exit() + + +``` + +Project Link: [https://github.com/mkfam7/solaxx3](https://github.com/mkfam7/solaxx3) + + + diff --git a/source/database/collect_stats.py b/source/database/collect_stats.py new file mode 100644 index 0000000..40009e5 --- /dev/null +++ b/source/database/collect_stats.py @@ -0,0 +1,108 @@ +from datetime import datetime, timedelta +from pymodbus.client import ModbusSerialClient +from solaxx3.rs485 import SolaxX3 +import mysql.connector + +s = SolaxX3(port="/dev/ttyUSB0", baudrate=115200) + +database_ip = "172.17.7.77" + +if s.connect(): + s.read_all_registers() + + # read the stats from the inverter + battery_capacity = s.read("battery_capacity")[0] + feed_in_today = s.read("feed_in_energy_today")[0] + consumtion_today = s.read("consumption_energy_today")[0] + battery_charging = s.read("battery_power_charge1")[0] + grid_voltage_r = s.read("grid_voltage_r")[0] + grid_voltage_s = s.read("grid_voltage_s")[0] + grid_voltage_t = s.read("grid_voltage_t")[0] + run_mode = s.read("run_mode")[0] + time_count_down = s.read("time_count_down")[0] + inverter_ac_power = s.read("grid_power")[0] + etoday_togrid = s.read("energy_to_grid_today")[0] + solar_energy_today = s.read("solar_energy_today")[0] + echarge_today = s.read("echarge_today")[0] + energy_from_grid = s.read("energy_from_grid_meter")[0] + energy_to_grid = s.read("energy_to_grid_meter")[0] + power_to_ev = s.read("power_to_ev")[0] + feed_in_power = s.read("feed_in_power")[0] + output_energy_charge = s.read("output_energy_charge")[0] + output_energy_today = s.read("output_energy_charge_today")[0] + input_energy_today = s.read("input_energy_charge_today")[0] + power_dc1 = s.read("power_dc1")[0] + power_dc2 = s.read("power_dc2")[0] + total_power = power_dc1 + power_dc2 + uploadTime = s.read("rtc_datetime")[0] + uploadDate = uploadTime.date() + + timezone_difference_from_utc = 2 + uploadTime = uploadTime - timedelta(hours=timezone_difference_from_utc, minutes=0) + + # store the stats in the database + mydb = mysql.connector.connect( + host=database_ip, user="root", passwd="rootroot", database="solax" + ) + mycursor = mydb.cursor() + + try: + + # create the sql statement + sql = """REPLACE INTO solax_local ( + uploadTime, + inverter_status, + dc_solar_power, + grid_voltage_r, + grid_voltage_s, + grid_voltage_t, + battery_capacity, + battery_power, + feed_in_power, + time_count_down, + inverter_ac_power, + consumeenergy, + feedinenergy, + power_dc1, + power_dc2 + ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) + """ + + values = ( + uploadTime, + run_mode, + total_power, + grid_voltage_r, + grid_voltage_s, + grid_voltage_t, + battery_capacity, + battery_charging, + feed_in_power, + time_count_down, + inverter_ac_power, + energy_from_grid, + energy_to_grid, + power_dc1, + power_dc2, + ) + + mycursor.execute(sql, values) + mydb.commit() + + # update daily values + sql = """REPLACE INTO solax_daily ( + uploadDate, + feed_in, + total_yield + ) VALUES (%s, %s, %s) + """ + values = (uploadDate, feed_in_today, etoday_togrid) + mycursor.execute(sql, values) + mydb.commit() + + except mysql.connector.Error as error: + print("parameterized query failed {}".format(error)) + +else: + print("Cannot connect to the Modbus Server/Slave") + exit() diff --git a/source/database/schema.sql b/source/database/schema.sql new file mode 100644 index 0000000..27ec77d --- /dev/null +++ b/source/database/schema.sql @@ -0,0 +1,25 @@ +CREATE TABLE `solax_daily` ( + `uploadDate` date NOT NULL, + `feed_in` float(6,1) DEFAULT NULL, + `total_yield` float(6,1) DEFAULT NULL, + PRIMARY KEY (`uploadDate`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +CREATE TABLE `solax_local` ( + `uploadTime` datetime NOT NULL, + `inverter_status` tinyint(4) DEFAULT NULL, + `dc_solar_power` smallint(6) DEFAULT NULL, + `grid_voltage_r` smallint(6) DEFAULT NULL, + `grid_voltage_s` smallint(6) DEFAULT NULL, + `grid_voltage_t` smallint(6) DEFAULT NULL, + `battery_capacity` tinyint(4) DEFAULT NULL, + `battery_power` smallint(6) DEFAULT NULL, + `feed_in_power` smallint(6) DEFAULT NULL, + `time_count_down` smallint(6) DEFAULT NULL, + `inverter_ac_power` smallint(6) DEFAULT NULL, + `consumeenergy` float(7,1) DEFAULT NULL, + `feedinenergy` float(7,1) DEFAULT NULL, + `power_dc1` smallint(6) DEFAULT NULL, + `power_dc2` smallint(6) DEFAULT NULL, + PRIMARY KEY (`uploadTime`) +) ENGINE=InnoDB DEFAULT CHARSET=latin1; diff --git a/source/diagrams/rs485_cable.png b/source/diagrams/rs485_cable.png new file mode 100644 index 0000000..6737d81 Binary files /dev/null and b/source/diagrams/rs485_cable.png differ diff --git a/source/example.py b/source/example.py new file mode 100644 index 0000000..494ebf9 --- /dev/null +++ b/source/example.py @@ -0,0 +1,19 @@ +from solaxx3.rs485 import SolaxX3 + +# adjust the serial port and baud rate as necessary +s = SolaxX3(port="/dev/ttyUSB0", baudrate=115200) + +if s.connect(): + s.read_all_registers() + + available_stats = s.list_register_names() + for stat in available_stats: + print(stat) + + battery_temperature = s.read("temperature_battery") + print(f"\n\nBattery temperature: {s.read('temperature_battery')}") + + +else: + print("Cannot connect to the Modbus Server/Slave") + exit() diff --git a/source/grafana/solax_dashboard.json b/source/grafana/solax_dashboard.json new file mode 100644 index 0000000..3326c79 --- /dev/null +++ b/source/grafana/solax_dashboard.json @@ -0,0 +1,2445 @@ +{ + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": "-- Grafana --", + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "type": "dashboard" + } + ] + }, + "editable": true, + "gnetId": null, + "graphTooltip": 0, + "id": 8, + "iteration": 1665919520980, + "links": [], + "panels": [ + { + "cacheTimeout": null, + "colorBackground": true, + "colorValue": false, + "colors": [ + "rgb(145, 75, 12)", + "rgb(145, 75, 12)", + "rgb(145, 75, 12)" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "volt", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 0, + "y": 0 + }, + "id": 29, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "80%", + "prefix": "", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select grid_voltage_r, uploadTime as `time`\nfrom solax.solax_local\nwhere uploadTime > now(3) - INTERVAL 2 MINUTE\norder by uploadTime desc\nlimit 1\n;\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "0", + "timeFrom": null, + "timeShift": null, + "title": "R-Grid", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorValue": false, + "colors": [ + "rgb(59, 59, 59)", + "rgb(59, 59, 59)", + "rgb(59, 59, 59)" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "none", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 3, + "y": 0 + }, + "id": 36, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "80%", + "prefix": "", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select inverter_status, uploadTime as `time`\nfrom solax.solax_local\nwhere uploadTime > now(3) - INTERVAL 2 MINUTE\norder by uploadTime desc\nlimit 1\n;\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "0", + "timeFrom": null, + "timeShift": null, + "title": "Inverter Status", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "Waiting", + "value": "0" + }, + { + "op": "=", + "text": "Checking", + "value": "1" + }, + { + "op": "=", + "text": "Normal", + "value": "2" + }, + { + "op": "=", + "text": "Fault", + "value": "3" + }, + { + "op": "=", + "text": "Permanent Fault", + "value": "4" + }, + { + "op": "=", + "text": "Update", + "value": "5" + }, + { + "op": "=", + "text": "Off-grid waiting", + "value": "6" + }, + { + "op": "=", + "text": "Off-grid", + "value": "7" + }, + { + "op": "=", + "text": "Self testing", + "value": "8" + }, + { + "op": "=", + "text": "Idle", + "value": "9" + }, + { + "op": "=", + "text": "Standby", + "value": "10" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorPostfix": false, + "colorPrefix": false, + "colorValue": false, + "colors": [ + "#5794F2", + "#E0B400", + "#A352CC" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "kwatt", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 4, + "w": 2, + "x": 9, + "y": 0 + }, + "id": 24, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "80%", + "prefix": "", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select total_yield as self_use,uploadDate as `time`\nFROM solax.solax_daily\nWHERE $__timeFilter(uploadDate)\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "0,0", + "timeFrom": null, + "timeShift": null, + "title": "Total Daily Yield", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorPostfix": false, + "colorPrefix": false, + "colorValue": false, + "colors": [ + "#5794F2", + "#E0B400", + "#E0B400" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "kwatt", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 11, + "y": 0 + }, + "id": 23, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "80%", + "prefix": "", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select (total_yield-feed_in) as self_use,uploadDate as `time`\nFROM solax.solax_daily\nWHERE $__timeFilter(uploadDate)\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "0,0", + "timeFrom": null, + "timeShift": null, + "title": "Self Use", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "datasource": "MySQL", + "description": "", + "gridPos": { + "h": 6, + "w": 3, + "x": 15, + "y": 0 + }, + "id": 38, + "links": [], + "options": { + "fieldOptions": { + "calcs": [ + "lastNotNull" + ], + "defaults": { + "decimals": 1, + "mappings": [ + { + "id": 0, + "op": "=", + "text": "N/A", + "type": 1, + "value": "null" + } + ], + "max": 100, + "min": 0, + "nullValueMode": "connected", + "thresholds": [ + { + "color": "dark-red", + "value": null + }, + { + "color": "rgba(237, 129, 40, 0.89)", + "value": 41 + }, + { + "color": "#299c46", + "value": 75 + } + ], + "unit": "percent" + }, + "override": {}, + "values": false + }, + "orientation": "horizontal", + "showThresholdLabels": false, + "showThresholdMarkers": true + }, + "pluginVersion": "6.5.2", + "targets": [ + { + "datasource": "MySQL", + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select battery_capacity as \"Available\" , uploadTime as `time`\nfrom solax.solax_local \norder by uploadTime desc\nlimit 1\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "timeFrom": null, + "timeShift": null, + "title": "Battery", + "transparent": true, + "type": "gauge" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorValue": false, + "colors": [ + "#299c46", + "rgb(189, 152, 0)", + "#d44a3a" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "watt", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 2, + "x": 20, + "y": 0 + }, + "id": 34, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "100%", + "prefix": "", + "prefixFontSize": "100%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select dc_solar_power, uploadTime as `time`\nfrom solax.solax_local\nwhere uploadTime > now(3) - INTERVAL 2 MINUTE\norder by uploadTime desc\nlimit 1\n;\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "-1,15000", + "timeFrom": null, + "timeShift": null, + "title": "SOLAR PANELS", + "transparent": true, + "type": "singlestat", + "valueFontSize": "100%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorValue": false, + "colors": [ + "#C4162A", + "#C4162A", + "#C4162A" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "watt", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 2, + "x": 22, + "y": 0 + }, + "id": 33, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "100%", + "prefix": "", + "prefixFontSize": "100%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select feed_in_power, uploadTime as `time`\nfrom solax.solax_local\nwhere uploadTime > now(3) - INTERVAL 2 MINUTE\norder by uploadTime desc\nlimit 1\n;\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "0,1", + "timeFrom": null, + "timeShift": null, + "title": "GRID", + "transparent": true, + "type": "singlestat", + "valueFontSize": "100%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorValue": false, + "colors": [ + "#000000", + "#000000", + "#000000" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "volt", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 0, + "y": 2 + }, + "id": 30, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "80%", + "prefix": "", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select grid_voltage_s, uploadTime as `time`\nfrom solax.solax_local\nwhere uploadTime > now(3) - INTERVAL 2 MINUTE\norder by uploadTime desc\nlimit 1\n;\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "49,50", + "timeFrom": null, + "timeShift": null, + "title": "S-Grid", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorValue": false, + "colors": [ + "rgb(59, 59, 59)", + "rgb(59, 59, 59)", + "rgb(59, 59, 59)" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "s", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 3, + "y": 2 + }, + "id": 39, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "80%", + "prefix": "", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select time_count_down, uploadTime as `time`\nfrom solax.solax_local\nwhere uploadTime > now(3) - INTERVAL 2 MINUTE\norder by uploadTime desc\nlimit 1\n;\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "0", + "timeFrom": null, + "timeShift": null, + "title": "Count Down", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorPostfix": false, + "colorPrefix": false, + "colorValue": false, + "colors": [ + "#d44a3a", + "rgba(237, 129, 40, 0.89)", + "#37872D" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "kwatt", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 11, + "y": 2 + }, + "id": 21, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "80%", + "prefix": "", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT \tfeed_in as feedinenergy, uploadDate as `time`\nFROM solax.solax_daily\nWHERE $__timeFilter(uploadDate)\n;", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "0,0", + "timeFrom": null, + "timeShift": null, + "title": "Daily FeedIn", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorValue": false, + "colors": [ + "#FF780A", + "#FF780A", + "#FF780A" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "watt", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 2, + "x": 20, + "y": 3 + }, + "id": 35, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "100%", + "prefix": "", + "prefixFontSize": "100%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select (inverter_ac_power-feed_in_power) as Consumption, uploadTime as `time`\nfrom solax.solax_local\nwhere uploadTime > now(3) - INTERVAL 2 MINUTE\norder by uploadTime desc\nlimit 1\n;\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "-1,15000", + "timeFrom": null, + "timeShift": null, + "title": "Consumption", + "transparent": true, + "type": "singlestat", + "valueFontSize": "100%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorValue": false, + "colors": [ + "#1F60C4", + "#1F60C4", + "#1F60C4" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "watt", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 3, + "w": 2, + "x": 22, + "y": 3 + }, + "id": 32, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "100%", + "prefix": "", + "prefixFontSize": "100%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select battery_power, uploadTime as `time`\nfrom solax.solax_local\nwhere uploadTime > now(3) - INTERVAL 2 MINUTE\norder by uploadTime desc\nlimit 1\n;\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "0,15000", + "timeFrom": null, + "timeShift": null, + "title": "Battery", + "transparent": true, + "type": "singlestat", + "valueFontSize": "100%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorValue": false, + "colors": [ + "rgb(122, 122, 122)", + "rgb(122, 122, 122)", + "rgb(122, 122, 122)" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "volt", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 0, + "y": 4 + }, + "id": 31, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "80%", + "prefix": "", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select grid_voltage_t, uploadTime as `time`\nfrom solax.solax_local\nwhere uploadTime > now(3) - INTERVAL 2 MINUTE\norder by uploadTime desc\nlimit 1\n;\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "49,50", + "timeFrom": null, + "timeShift": null, + "title": "T-Grid", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "cacheTimeout": null, + "colorBackground": true, + "colorPostfix": false, + "colorPrefix": false, + "colorValue": false, + "colors": [ + "#5794F2", + "rgba(237, 129, 40, 0.89)", + "#d44a3a" + ], + "datasource": "MySQL", + "decimals": 1, + "description": "", + "format": "kwatt", + "gauge": { + "maxValue": 100, + "minValue": 0, + "show": false, + "thresholdLabels": false, + "thresholdMarkers": true + }, + "gridPos": { + "h": 2, + "w": 2, + "x": 11, + "y": 4 + }, + "id": 16, + "interval": null, + "links": [], + "mappingType": 1, + "mappingTypes": [ + { + "name": "value to text", + "value": 1 + }, + { + "name": "range to text", + "value": 2 + } + ], + "maxDataPoints": 100, + "nullPointMode": "connected", + "nullText": null, + "options": {}, + "postfix": "", + "postfixFontSize": "80%", + "prefix": "", + "prefixFontSize": "80%", + "rangeMaps": [ + { + "from": "null", + "text": "N/A", + "to": "null" + } + ], + "sparkline": { + "fillColor": "rgba(31, 118, 189, 0.18)", + "full": false, + "lineColor": "rgb(31, 120, 193)", + "show": false, + "ymax": null, + "ymin": null + }, + "tableColumn": "", + "targets": [ + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT \t(max(consumeenergy) - min(consumeenergy)) as consumeenergy, uploadTime as `time`\nFROM solax.solax_local\nWHERE $__timeFilter(uploadTime)\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": "0,0", + "timeFrom": null, + "timeShift": null, + "title": "Daily Grid Consumption", + "transparent": true, + "type": "singlestat", + "valueFontSize": "80%", + "valueMaps": [ + { + "op": "=", + "text": "N/A", + "value": "null" + } + ], + "valueName": "current" + }, + { + "aliasColors": { + "Consumption": "dark-blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "MySQL", + "decimals": 1, + "description": "", + "fill": 0, + "fillGradient": 8, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 6 + }, + "hiddenSeries": false, + "id": 37, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Grid", + "color": "#56A64B", + "fill": 10 + }, + { + "alias": "Solar", + "color": "#FADE2A", + "stack": false, + "zindex": 1 + }, + { + "alias": "Consumption", + "color": "#FF9830", + "fill": 8, + "zindex": -3 + }, + { + "alias": "Battery", + "color": "#3274D9", + "fill": 8, + "zindex": -2 + }, + { + "alias": "Battery Charge", + "color": "#3274D9", + "dashes": true, + "stack": false, + "yaxis": 2 + } + ], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select `battery_capacity` as \"Battery Charge\" , uploadTime as `time`\nfrom solax.solax_local\n\n", + "refId": "E", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select `feed_in_power` as \"Grid\" , uploadTime as `time`\nfrom solax.solax_local\n\n", + "refId": "B", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select `dc_solar_power` as \"Solar\", uploadTime as `time`\nfrom solax.solax_local\n\n", + "refId": "F", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select `battery_power` as \"Battery\", uploadTime as `time`\nfrom solax.solax_local\n\n", + "refId": "C", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select (inverter_ac_power-feed_in_power) as \"Consumption\", uploadTime as `time`\nfrom solax.solax_local", + "refId": "D", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "Solar Panels - LOCAL", + "tooltip": { + "shared": true, + "sort": 1, + "value_type": "individual" + }, + "transparent": true, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "watt", + "label": "Power Consumtion", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "decimals": 2, + "format": "percent", + "label": "Battery Charge (%)", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": 0 + } + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 15 + }, + "hiddenSeries": false, + "hideTimeOverride": true, + "id": 43, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": true, + "values": true + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pluginVersion": "6.5.2", + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Self_Use", + "color": "#73BF69", + "zindex": -2 + }, + { + "alias": "Feed_In", + "color": "#FADE2A" + }, + { + "alias": "From_Grid", + "color": "#F2495C", + "zindex": -1 + } + ], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select sum(total_yield-feed_in) as Self_Use, uploadDate as `time`\nfrom solax.solax_daily\ngroup by year(uploadDate), month(uploadDate)", + "refId": "B", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select sum(feed_in) as Feed_In, uploadDate as `time`\nfrom solax.solax_daily\ngroup by year(uploadDate), month(uploadDate)", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "SELECT \t(max(consumeenergy) - min(consumeenergy)) as From_Grid, DATE_SUB(LAST_DAY(uploadTime),INTERVAL DAY(LAST_DAY(uploadTime))-1 DAY) as `time`\nFROM solax.solax_local\ngroup by year(uploadTime), month(uploadTime)\n;\n", + "refId": "C", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": "0y/y", + "title": "Yearly Solar Production", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "format": "kwatt", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": false + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": {}, + "bars": true, + "dashLength": 10, + "dashes": false, + "datasource": null, + "fill": 1, + "fillGradient": 0, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 15 + }, + "hiddenSeries": false, + "hideTimeOverride": true, + "id": 42, + "legend": { + "avg": false, + "current": false, + "max": false, + "min": false, + "show": true, + "total": false, + "values": false + }, + "lines": false, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "Self Use", + "color": "#73BF69" + }, + { + "alias": "Feed In", + "color": "#FADE2A" + } + ], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select (total_yield-feed_in) as \"Self Use\", uploadDate as `time` \nfrom solax.solax_daily\n;", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select feed_in as \"Feed In\", uploadDate as `time` \nfrom solax.solax_daily\n;\n\t", + "refId": "B", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": "0M/M", + "title": "Monthly Solar Production", + "tooltip": { + "shared": true, + "sort": 0, + "value_type": "individual" + }, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": 1, + "format": "kwatt", + "label": "", + "logBase": 1, + "max": "120", + "min": null, + "show": true + }, + { + "format": "short", + "label": null, + "logBase": 1, + "max": null, + "min": null, + "show": true + } + ], + "yaxis": { + "align": false, + "alignLevel": null + } + }, + { + "aliasColors": { + "Consumption": "dark-blue" + }, + "bars": false, + "dashLength": 10, + "dashes": false, + "datasource": "MySQL", + "decimals": 1, + "description": "", + "fill": 0, + "fillGradient": 8, + "gridPos": { + "h": 9, + "w": 24, + "x": 0, + "y": 23 + }, + "hiddenSeries": false, + "id": 22, + "legend": { + "alignAsTable": false, + "avg": false, + "current": true, + "max": false, + "min": false, + "rightSide": false, + "show": true, + "total": false, + "values": true + }, + "lines": true, + "linewidth": 1, + "nullPointMode": "null", + "options": { + "dataLinks": [] + }, + "percentage": false, + "pointradius": 2, + "points": false, + "renderer": "flot", + "seriesOverrides": [ + { + "alias": "EPS 1", + "fill": 8 + }, + { + "alias": "EPS 2", + "fill": 8 + }, + { + "alias": "EPS 3", + "fill": 8 + }, + { + "alias": "EPS Total", + "stack": false + } + ], + "spaceLength": 10, + "stack": true, + "steppedLine": false, + "targets": [ + { + "format": "time_series", + "group": [], + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select `peps1` as \"EPS 1\" , uploadTime as `time`\nfrom solax.solax_stats\n\n", + "refId": "E", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select `peps2` as \"EPS 2\" , uploadTime as `time`\nfrom solax.solax_stats\n\n", + "refId": "B", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select (`peps3`) as \"EPS 3\", uploadTime as `time`\nfrom solax.solax_stats\n\n", + "refId": "F", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + }, + { + "format": "time_series", + "group": [], + "hide": false, + "metricColumn": "none", + "rawQuery": true, + "rawSql": "select (`peps3`+`peps2`+`peps1`) as \"EPS Total\", uploadTime as `time`\nfrom solax.solax_stats\n\n", + "refId": "A", + "select": [ + [ + { + "params": [ + "value" + ], + "type": "column" + } + ] + ], + "timeColumn": "time", + "where": [ + { + "name": "$__timeFilter", + "params": [], + "type": "macro" + } + ] + } + ], + "thresholds": [], + "timeFrom": null, + "timeRegions": [], + "timeShift": null, + "title": "EPS Power", + "tooltip": { + "shared": true, + "sort": 1, + "value_type": "individual" + }, + "transparent": true, + "type": "graph", + "xaxis": { + "buckets": null, + "mode": "time", + "name": null, + "show": true, + "values": [] + }, + "yaxes": [ + { + "decimals": null, + "format": "watt", + "label": "Power Consumtion", + "logBase": 1, + "max": null, + "min": null, + "show": true + }, + { + "decimals": 2, + "format": "percent", + "label": "Battery Charge (%)", + "logBase": 1, + "max": "100", + "min": "0", + "show": true + } + ], + "yaxis": { + "align": true, + "alignLevel": 0 + } + } + ], + "refresh": "1m", + "schemaVersion": 21, + "style": "dark", + "tags": [], + "templating": { + "list": [ + { + "current": { + "selected": false, + "text": "rec_datetime between date_sub(now(),INTERVAL 2 day) and now();", + "value": "rec_datetime between date_sub(now(),INTERVAL 2 day) and now();" + }, + "hide": 2, + "label": "limit_condition", + "name": "limit_condition", + "options": [ + { + "selected": true, + "text": "rec_datetime between date_sub(now(),INTERVAL 2 day) and now();", + "value": "rec_datetime between date_sub(now(),INTERVAL 2 day) and now();" + } + ], + "query": "rec_datetime between date_sub(now(),INTERVAL 2 day) and now();", + "skipUrlSync": false, + "type": "constant" + }, + { + "current": { + "selected": false, + "text": "from_unixtime(rec_datetime) between < $__from and rec_datetime < $__to", + "value": "from_unixtime(rec_datetime) between < $__from and rec_datetime < $__to" + }, + "hide": 2, + "label": "limit_condition2", + "name": "limit_condition2", + "options": [ + { + "selected": true, + "text": "from_unixtime(rec_datetime) between < $__from and rec_datetime < $__to", + "value": "from_unixtime(rec_datetime) between < $__from and rec_datetime < $__to" + } + ], + "query": "from_unixtime(rec_datetime) between < $__from and rec_datetime < $__to", + "skipUrlSync": false, + "type": "constant" + } + ] + }, + "time": { + "from": "now/d", + "to": "now/d" + }, + "timepicker": { + "refresh_intervals": [ + "5s", + "10s", + "30s", + "1m", + "5m", + "15m", + "30m", + "1h", + "2h", + "1d" + ] + }, + "timezone": "", + "title": "Solax", + "uid": "GoHNntS4k", + "version": 2 +} \ No newline at end of file diff --git a/source/images/solax_dashboard.png b/source/images/solax_dashboard.png new file mode 100644 index 0000000..c3e89a3 Binary files /dev/null and b/source/images/solax_dashboard.png differ diff --git a/source/pyproject.toml b/source/pyproject.toml new file mode 100644 index 0000000..7513639 --- /dev/null +++ b/source/pyproject.toml @@ -0,0 +1,26 @@ +# pyproject.toml + +[build-system] +requires = ["setuptools>=61.0.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "solaxx3" +version = "0.1.0" +description = "Read Solax X3 inverter registers via modbus interface (RS-485)" +readme = "README.md" +authors = [{ name = "Flavius Moldovan", email = "mkfam@protonmail.com" }] +license = { file = "LICENSE" } +classifiers = [ + "License :: OSI Approved :: MIT License", + "Programming Language :: Python", + "Programming Language :: Python :: 3", +] +keywords = ["Solax", "solax-x3", "solaxx3", "solar inverter", "RTU", "MODBUS"] +dependencies = [ + "pymodbus[serial] >= 3.0.0", +] +requires-python = ">=3.8" + +[project.urls] +Homepage = "https://github.com/mkfam7/solaxx3" diff --git a/source/setup.py b/source/setup.py new file mode 100644 index 0000000..6068493 --- /dev/null +++ b/source/setup.py @@ -0,0 +1,3 @@ +from setuptools import setup + +setup() diff --git a/src/solaxx3/__init__.py b/src/solaxx3/__init__.py new file mode 100644 index 0000000..5935f36 --- /dev/null +++ b/src/solaxx3/__init__.py @@ -0,0 +1,2 @@ +# Version of the Solax RTU package +__version__ = "0.0.6" diff --git a/src/solaxx3/registers.py b/src/solaxx3/registers.py new file mode 100644 index 0000000..2c036b5 --- /dev/null +++ b/src/solaxx3/registers.py @@ -0,0 +1,589 @@ +class SolaxRegistersInfo: + __registers = { + # input registers + "grid_voltage": { + "address": 0x0000, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid voltage", + }, + "grid_current": { + "address": 0x0001, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "A", + "data_length": 1, + "description": "Grid current", + }, + "grid_power": { + "address": 0x0002, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 1, + "description": "Grid power", + }, + "pv_voltage_1": { + "address": 0x0003, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "PV Voltage 1", + }, + "pv_voltage_2": { + "address": 0x0004, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "PV Voltage 2", + }, + "pv_current_1": { + "address": 0x0005, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "PV Current 1", + }, + "pv_current_2": { + "address": 0x0006, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "PV Current 2", + }, + "grid_frequency": { + "address": 0x0007, + "register_type": "input", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Grid frequency", + }, + "radiator_temperature": { + "address": 0x0008, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "C", + "data_length": 1, + "description": "Radiator temperature", + }, + "run_mode": { + "address": 0x0009, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "", + "data_length": 1, + "description": "Inverter status", + }, + "power_dc1": { + "address": 0x000A, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "W", + "data_length": 1, + "description": "DC Power String 1", + }, + "power_dc2": { + "address": 0x000B, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "W", + "data_length": 1, + "description": "DC Power String 2", + }, + "temperature_fault_value": { + "address": 0x000C, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "C", + "data_length": 1, + "description": "Temperature fault value", + }, + "pv1_volt_fault_value": { + "address": 0x000D, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "PV1 volt fault value", + }, + "pv2_volt_fault_value": { + "address": 0x000E, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "PV2 volt fault value", + }, + "gfci_fault_value": { + "address": 0x000F, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "GFCI fault value", + }, + "grid_fault_value": { + "address": 0x0010, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid fault value", + }, + "grid_freq__fault__value_t": { + "address": 0x0011, + "register_type": "input", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "Hz", + "data_length": 1, + "description": "Grid frequency fault value t", + }, + "dci_fault_value": { + "address": 0x0012, + "register_type": "input", + "data_format": "uint16", + "si_adj": 100, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "DCI fault value", + }, + "time_count_down": { + "address": 0x0013, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1000, + "signed": False, + "data_unit": "seconds", + "data_length": 1, + "description": "Time remaining until the current operation finishes (e.g. checking)", + }, + "battery_voltage_charge_1": { + "address": 0x0014, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "V", + "data_length": 1, + "description": "Battery voltage charge 1", + }, + "battery_current_charge_1": { + "address": 0x0015, + "register_type": "input", + "data_format": "int16", + "si_adj": 10, + "signed": True, + "data_unit": "V", + "data_length": 1, + "description": "Battery current charge 1", + }, + "battery_power_charge1": { + "address": 0x0016, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 1, + "description": "Battery power charge 1", + }, + "bms_connect_state": { + "address": 0x0017, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "", + "data_length": 1, + "description": "BMS connect state", + }, + "temperature_battery": { + "address": 0x0018, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": True, + "data_unit": "C", + "data_length": 1, + "description": "Temperature of the battery", + }, + "battery_capacity": { + "address": 0x001C, + "register_type": "input", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_unit": "%", + "data_length": 1, + "description": "Battery charge percentage", + }, + "output_energy_charge": { + "address": 0x001D, + "register_type": "input", + "data_format": "uint32", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Output energy charge", + }, + "output_energy_charge_today": { + "address": 0x0020, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 1, + "description": "Output energy charge today", + }, + "input_energy_charge": { + "address": 0x0021, + "register_type": "input", + "data_format": "uint32", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Input energy charge", + }, + "input_energy_charge_today": { + "address": 0x0023, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 1, + "description": "Input energy charge today", + }, + "bms_charge_max_current": { + "address": 0x0024, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "BMS charge max current", + }, + "bms_discharge_max_current": { + "address": 0x0025, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "A", + "data_length": 1, + "description": "BMS discharge max current", + }, + "power_to_ev": { + "address": 0x0026, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W", + "data_length": 2, + "description": "Power to EV", + }, + "ref_power_to_ev": { + "address": 0x0028, + "register_type": "input", + "data_format": "int16", + "si_adj": 1, + "signed": False, + "data_unit": "W", + "data_length": 1, + "description": "ref_power_to_ev", + }, + "feed_in_power": { + "address": 0x0046, + "register_type": "input", + "data_format": "int32", + "si_adj": 1, + "signed": True, + "data_unit": "W ", + "data_length": 2, + "description": "Feed-in power obtained from Meter or CT", + }, + "energy_to_grid_meter": { + "address": 0x0048, + "register_type": "input", + "data_format": "uint32", + "si_adj": 1, + "signed": False, + "data_unit": "KWh", + "si_adj": 100, + "data_length": 2, + "description": "Energy to grid meter value", + }, + "energy_from_grid_meter": { + "address": 0x004A, + "register_type": "input", + "data_format": "uint32", + "si_adj": 100, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Energy from grid meter value", + }, + "energy_to_grid_today": { + "address": 0x0050, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 1, + "description": "The quantity of energy sent to grid today", + }, + "energy_to_grid_total": { + "address": 0x0052, + "register_type": "input", + "data_format": "uint32", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Total energy sent to grid", + }, + "grid_voltage_r": { + "address": 0x006A, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid voltage for R phase", + }, + "grid_voltage_s": { + "address": 0x006E, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid voltage for S phase", + }, + "grid_voltage_t": { + "address": 0x0072, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "V", + "data_length": 1, + "description": "Grid voltage for T phase", + }, + "echarge_today": { + "address": 0x0091, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 1, + "description": "Echarge energy today", + }, + "solar_energy_total": { + "address": 0x0094, + "register_type": "input", + "data_format": "uint32", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Total solar energy", + }, + "solar_energy_today": { + "address": 0x0096, + "register_type": "input", + "data_format": "uint16", + "si_adj": 10, + "signed": False, + "data_unit": "KWh", + "data_length": 1, + "description": "Solar energy today", + }, + "feed_in_energy_today": { + "address": 0x0098, + "register_type": "input", + "data_format": "uint32", + "si_adj": 100, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Energy to grid meter for today", + }, + "consumption_energy_today": { + "address": 0x009A, + "register_type": "input", + "data_format": "uint32", + "si_adj": 100, + "signed": False, + "data_unit": "KWh", + "data_length": 2, + "description": "Energy from grid meter for today", + }, + # holding registers + "serial_number": { + "address": 0x0000, + "register_type": "holding", + "data_format": "varchar", + "signed": False, + "data_length": 7, + "description": "Inverter' serial number", + }, + "firmware_version_dsp": { + "address": 0x007D, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "description": "Firmware version DSP", + }, + "hardware_version_dsp": { + "address": 0x007E, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "description": "Hardware version DSP", + }, + "firmware_version_modbus_rtu": { + "address": 0x0082, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "description": "Firmware version MODBUS RTU", + }, + "firmware_version_arm": { + "address": 0x0083, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "description": "Firmware version ARM", + }, + "firmware_version_arm_bootloader": { + "address": 0x0084, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "description": "Firmware version ARM Bootloader", + }, + "rtc_datetime": { + "address": 0x0085, + "register_type": "holding", + "data_format": "datetime", + "si_adj": 1, + "signed": False, + "data_length": 6, + "description": "RTC datetime", + }, + "registration_code": { + "address": 0x00AA, + "register_type": "holding", + "data_format": "varchar", + "si_adj": 1, + "signed": False, + "data_length": 6, + "description": "Registration code for external module", + }, + "inverter_power_type": { + "address": 0x00BA, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "description": "Inverter power type", + }, + "user_password": { + "address": 0x00E0, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "description": "User password", + }, + "advanced_password": { + "address": 0x00E1, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "description": "Advanced password", + }, + "machine_type": { + "address": 0x0105, + "register_type": "holding", + "data_format": "uint16", + "si_adj": 1, + "signed": False, + "data_length": 1, + "description": "Machine type: 1:X1, 3:X3", + }, + } + + def get_register_info(self, name: str): + return self.__registers[name] + + def list_register_names(self): + return list(self.__registers.keys()) diff --git a/src/solaxx3/rs485.py b/src/solaxx3/rs485.py new file mode 100644 index 0000000..3d12da0 --- /dev/null +++ b/src/solaxx3/rs485.py @@ -0,0 +1,169 @@ +from typing import Any + +# from pymodbus.client.sync import ModbusSerialClient +from pymodbus.client import ModbusSerialClient +from datetime import date, datetime, timedelta +from struct import * +from solaxx3.registers import SolaxRegistersInfo +from time import sleep, perf_counter + + +class SolaxX3: + connected: bool = False + + def __init__( + self, + method="rtu", + port="/dev/ttyUSB0", + baudrate=115200, + timeout=3, + parity="N", + stopbits=1, + bytesize=8, + ) -> None: + self._input_registers_values_list = [] + self._holding_registers_values_list = [] + + self.client = ModbusSerialClient( + method=method, + port=port, + baudrate=baudrate, + timeout=timeout, + parity=parity, + stopbits=stopbits, + bytesize=bytesize, + ) + + def connect(self) -> bool: + self.connected = self.client.connect() + return self.connected + + def _join_msb_lsb(self, msb: int, lsb: int) -> int: + return (msb << 16) | lsb + + def _unsigned16(self, type: str, addr: int, count: int = 1, unit: int = 1) -> int: + + self._input_registers_values_list + if type == "input": + return self._input_registers_values_list[addr] + elif type == "holding": + return self._holding_registers_values_list[addr] + + def _readRegisterRange( + self, type: str, addr: int, count: int = 1, unit: int = 1 + ) -> list: + if type == "input": + return self._input_registers_values_list[addr : addr + count] + elif type == "holding": + return self._holding_registers_values_list[addr : addr + count] + + def _twos_complement(self, number: int, bits: int) -> int: + """ + Compute the 2's complement of the int value val + """ + + # if sign bit is set e.g., 8bit: 128-255 + if (number & (1 << (bits - 1))) != 0: + + # compute negative value + number = number - (1 << bits) + + return number + + def _read_register(self, register_type: str, register_info: dict) -> Any: + """Read the values from a register based on length and sign + + Parameters: + + register_info:dict - dictionary with register definition fields + """ + + if "int" in register_info["data_format"]: + if register_info["data_length"] == 1: + val = self._unsigned16(register_type, register_info["address"]) + + if register_info["data_length"] == 2: + val = self._join_msb_lsb( + self._unsigned16(register_type, register_info["address"] + 1), + self._unsigned16(register_type, register_info["address"]), + ) + + if register_info["signed"]: + val = self._twos_complement(val, register_info["data_length"] * 16) + + val = val / register_info["si_adj"] + + elif "varchar" in register_info["data_format"]: + block = self._readRegisterRange( + register_type, register_info["address"], register_info["data_length"] + ) + sn = [] + for i in range(register_info["data_length"]): + first_byte, second_byte = unpack( + "BB", int.to_bytes(block[i], 2, "little") + ) + if not second_byte == 0x0: + sn.append(chr(second_byte)) + if not first_byte == 0x0: + sn.append(chr(first_byte)) + val = "".join(sn) + + elif "datetime" in register_info["data_format"]: + sec, min, hr, day, mon, year = self._readRegisterRange( + register_type, register_info["address"], register_info["data_length"] + ) + + inverter_datetime = ( + f"{(year+2000):02}-{mon:02}-{day:02} {hr:02}:{min:02}:{sec:02}" + ) + val = datetime.strptime(inverter_datetime, "%Y-%m-%d %H:%M:%S") + + return val + + def read_register(self, register_info: dict) -> tuple: + """Read the values from a register based on length and sign + + Parameters: + + register_info:dict - dictionary with register definition fields + """ + + val = self._read_register(register_info["register_type"], register_info) + + if not "data_unit" in register_info: + return (val, "N/A") + + return (val, register_info["data_unit"]) + + def read(self, name: str): + """Retrieve the value for the register with the provided name""" + + r = SolaxRegistersInfo() + + register_info = r.get_register_info(name) + value = self.read_register(register_info) + + return value + + def list_register_names(self): + r = SolaxRegistersInfo() + return r.list_register_names() + + def read_all_registers(self) -> None: + self._input_registers_values_list = [] + self._holding_registers_values_list = [] + + read_block_length = 100 + for i in range(3): + address = i * read_block_length + values_list = self.client.read_input_registers( + address=address, count=read_block_length, slave=1 + ).registers + self._input_registers_values_list.extend(values_list) + + for i in range(3): + address = i * read_block_length + values_list = self.client.read_holding_registers( + address=address, count=read_block_length, slave=1 + ).registers + self._holding_registers_values_list.extend(values_list)