import std/asyncdispatch import std/[httpclient, times] import std/[tables, math, json] import utils type Operator = enum div10, div100, none, read16BitSignedDiv10, read16BitSignedDiv100, read16BitSignedNone read32BitUnsignedDiv10, read32BitUnsignedDiv100, read32BitUnsignedNone read32BitSignedDiv10 , read32BitSignedDiv100, read32BitSignedNone var inverterModel = { 14: "X3-Hybrid G4", 15: "X1-Hybrid G4", 23: "X1-Hybrid G5" }.toTable var inverterMode = { 0: "Waiting", 1: "Checking", 2: "Normal", 3: "Off", 4: "Permanent Failure", 5: "Updating", 6: "EPS Check", 7: "EPS Mode", 8: "Self Test", 9: "Idle", 10: "Standby" }.toTable var batteryMode = { 0: "Self Use Mode", 1: "Force Time use", 2: "Back Up Mode", 3: "Feed-in Priority" }.toTable var batteryStatus = { 0: "Failure", 1: "OK" }.toTable proc convert (json: JsonNode, operator: Operator, a: int, b:int = 0): JsonNode = result = case operator of div10: newJFloat(utils.round(json["Data"][a].getInt / 10, 1)) of div100: newJFloat(utils.round(json["Data"][a].getInt / 100, 2)) of none: json["Data"][a] of read16BitSignedDiv10: newJFloat(utils.round( read16BitSigned( json["Data"][a].getInt ) / 10 , 1)) of read16BitSignedDiv100: newJFloat(utils.round( read16BitSigned( json["Data"][a].getInt ) / 100 , 2)) of read16BitSignedNone: newJInt( read16BitSigned( json["Data"][a].getInt ) ) of read32BitUnsignedDiv10: newJFloat(utils.round( read32BitUnsigned( json["Data"][a].getInt, json["Data"][b].getInt ).float64 / 10.float64 , 1)) of read32BitUnsignedDiv100: newJFloat(utils.round( read32BitUnsigned( json["Data"][a].getInt, json["Data"][b].getInt ).float64 / 100.float64 , 2)) of read32BitUnsignedNone: newJInt( read32BitUnsigned( json["Data"][a].getInt, json["Data"][b].getInt ) ) of read32BitSignedDiv10: newJFloat(utils.round( read32BitSigned( json["Data"][a].getInt, json["Data"][b].getInt ).float64 / 10.float64 , 1)) of read32BitSignedDiv100: newJFloat(utils.round( read32BitSigned( json["Data"][a].getInt, json["Data"][b].getInt ).float64 / 100.float64 , 2)) of read32BitSignedNone: newJInt( read32BitSigned( json["Data"][a].getInt, json["Data"][b].getInt ) ) proc parseInverterData* (json: JsonNode): JsonNode = result = newJObject() result["model"] = newJString(inverterModel[json["type"].getInt]) result["registration_number"] = json["sn"] case json["type"].getInt: # X3-Hybrid G4 of 14: result["serial_number"] = json["Information"][2] result["firmware_version"] = json["ver"] result["mode"] = newJString(inverterMode[json["Data"][19].getInt]) # volts result["l1_voltage"] = convert(json, div10, 0) result["l2_voltage"] = convert(json, div10, 1) result["l3_voltage"] = convert(json, div10, 2) result["eps_l1_voltage"] = convert(json, div10, 23) result["eps_l2_voltage"] = convert(json, div10, 23) result["eps_l3_voltage"] = convert(json, div10, 25) result["pv1_voltage"] = convert(json, div10, 10) result["pv2_voltage"] = convert(json, div10, 11) # amps result["l1_current"] = convert(json, div10, 3) result["l2_current"] = convert(json, div10, 4) result["l3_current"] = convert(json, div10, 5) result["eps_l1_current"] = convert(json, div10, 26) result["eps_l2_current"] = convert(json, div10, 27) result["eps_l3_current"] = convert(json, div10, 28) result["pv1_current"] = convert(json, div10, 12) result["pv2_current"] = convert(json, div10, 13) # watts result["l1_power"] = convert(json, none, 6) result["l2_power"] = convert(json, none, 7) result["l3_power"] = convert(json, none, 8) result["eps_l1_power"] = convert(json, none, 29) result["eps_l2_power"] = convert(json, none, 30) result["eps_l3_power"] = convert(json, none, 31) result["power"] = convert(json, none, 9) result["pv1_power"] = convert(json, none, 14) result["pv2_power"] = convert(json, none, 15) result["pv_power"] = newJInt(json["Data"][14].getInt + json["Data"][15].getInt) result["grid_in_power"] = convert(json, read32BitSignedNone, 34, 35) result["power_total"] = convert(json, read16BitSignedNone, 47) # hz result["l1_frequency"] = convert(json, div100, 16) result["l2_frequency"] = convert(json, div100, 17) result["l3_frequency"] = convert(json, div100, 18) # kwh result["yield_energy_today"] = convert(json, div10, 70) result["yield_energy_total"] = convert(json, read32BitUnsignedDiv10, 68, 69) result["grid_out_energy_today"] = convert(json, div100, 92) result["grid_in_energy_today"] = convert(json, div100, 90) result["grid_in_energy_total"] = convert(json, read32BitUnsignedDiv100, 86, 87) result["energy_total"] = convert(json, read32BitUnsignedDiv100, 88, 89) # X4-Hybrid G4 # X4-Hybrid G5 of 15, 23: result["serial_number"] = json["Information"][2] result["firmware_version"] = json["ver"] result["mode"] = newJString(inverterMode[json["Data"][10].getInt]) # volts result["l1_voltage"] = convert(json, div10, 0) result["eps_l1_voltage"] = convert(json, div10, 0) result["pv1_voltage"] = convert(json, div10, 4) result["pv2_voltage"] = convert(json, div10, 5) # amps result["l1_current"] = convert(json, read16BitSignedDiv10, 1) result["eps_l1_current"] = convert(json, read16BitSignedDiv10, 1) result["pv1_current"] = convert(json, div10, 6) result["pv2_current"] = convert(json, div10, 7) # watts result["l1_power"] = convert(json, none, 2) result["epsl1_power"] = convert(json, none, 2) result["total_power"] = convert(json, none, 2) result["pv1_power"] = convert(json, none, 8) result["pv2_power"] = convert(json, none, 9) result["pv_power"] = newJInt(json["Data"][8].getInt + json["Data"][9].getInt) result["grid_in_power"] = convert(json, read32BitSignedNone, 32, 33) # hz result["l1_frequency"] = convert(json, div100, 3) # kwh result["grid_in_energy"] = convert(json, read32BitUnsignedDiv100, 34, 35) result["yield_energy_today"] = convert(json, div10, 13) result["yield_energy_total"] = convert(json, read32BitUnsignedDiv10, 11, 12) result["energy_total"] = convert(json, read32BitUnsignedDiv100, 36, 37) else: result["error"] = newJString("Unknown model") proc parseBatteryData* (json: JsonNode): JsonNode = result = newJObject() case json["type"].getInt: # X3-Hybrid G4 of 14: result["mode"] = newJString(batteryMode[json["Data"][168].getInt]) result["status"] = newJString(batteryStatus[json["Data"][45].getInt]) result["temperature"] = convert(json, read16BitSignedNone, 105) # volts result["voltage"] = convert(json, div100, 39) # amps result["current"] = convert(json, read16BitSignedDiv100, 40) # watts result["power"] = convert(json, read16BitSignedNone, 41) # percent result["soc"] = convert(json, none, 103) # kwh result["remaining_capacity"] = convert(json, div10, 106) # kwh result["discharge_today"] = convert(json, div10, 78) result["charge_today"] = convert(json, div10, 79) result["discharge_total"] = convert(json, read32BitUnsignedDiv10, 74, 75) result["charge_total"] = convert(json, read32BitUnsignedDiv10, 76, 77) # X1-Hybrid G4 # X1-Hybrid G5 of 15, 23: result["temperature"] = convert(json, read16BitSignedNone, 17) # volts result["voltage"] = convert(json, div100, 14) # amps result["current"] = convert(json, div100, 15) # watts result["power"] = convert(json, none, 16) # percent result["soc"] = convert(json, none, 18) # kwh result["remaining_capacity"] = convert(json, div10, 106) else: result["error"] = newJString("Unknown model") proc getSolaxData* (ip: string, password: string): Future[JsonNode] {.async.} = result = newJObject() result["last_update"] = newJInt(now().toTime.toUnix) try: let client = newAsyncHttpClient() let body = "optType=ReadRealTimeData&pwd=" & password let response = await client.postContent("http://" & ip, body) let json = parseJson(response) client.close() for key, value in parseInverterData(json): result["inverter_" & key] = value for key, value in parseBatteryData(json): result["battery_" & key] = value except: echo "Error[getSolaxData]:\n" & getCurrentExceptionMsg() result["error"] = newJString(getCurrentExceptionMsg())