|
| 1 | +import device |
| 2 | +import probe |
| 3 | +from register import * |
| 4 | + |
| 5 | +# This Python script adds support for Modbus enabled Shelly devices in dbus-modbus-client version 1.58 and later. |
| 6 | +# |
| 7 | + |
| 8 | +# This is just a helper class to make life a bit easier with the Modbus registers. |
| 9 | +# |
| 10 | +class Reg_shelly(Reg_f32l): |
| 11 | + def __init__(self, base, name=None, scale=1, text=None, write=False, invalid=[], **kwargs): |
| 12 | + super().__init__(base - 30000, name, scale, text, write, invalid, **kwargs) |
| 13 | + |
| 14 | +# Common base class for Shelly EMs but currently there's only one implementation. |
| 15 | +# |
| 16 | +class Shelly_Meter(device.CustomName, device.EnergyMeter): |
| 17 | + vendor_id = 'shelly' |
| 18 | + vendor_name = 'Shelly' |
| 19 | + min_timeout = 0.5 |
| 20 | + |
| 21 | + # Shelly uses input registers! |
| 22 | + default_access = 'input' |
| 23 | + |
| 24 | + def device_init(self): |
| 25 | + self.info_regs = [ |
| 26 | + Reg_text(0, 6, '/Serial', little=True), |
| 27 | + ] |
| 28 | + |
| 29 | + |
| 30 | +# Shelly Pro 3EM |
| 31 | +# https://shelly-api-docs.shelly.cloud/gen2/Devices/Gen2/ShellyPro3EM |
| 32 | +# |
| 33 | +# Triphase (defualt) |
| 34 | +# https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/EM/ |
| 35 | +# https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/EMData/ |
| 36 | +# |
| 37 | +# Monophase |
| 38 | +# https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/EM1/ |
| 39 | +# https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/EM1Data/ |
| 40 | +# |
| 41 | +# Modbus |
| 42 | +# https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/Modbus |
| 43 | +# |
| 44 | +class Shelly_Pro_3EM(Shelly_Meter): |
| 45 | + productname = 'Shelly Pro 3EM' |
| 46 | + productmodel = 'SPEM-003CEBEU' |
| 47 | + |
| 48 | + # Shelly doesn't have a purely numerical Product ID and it's unclear what could be a good placeholder for it. |
| 49 | + # Using the Base16 encoded value of 'PEM3'. |
| 50 | + productid = 0x50454D33 |
| 51 | + |
| 52 | + def device_init(self): |
| 53 | + super().device_init() |
| 54 | + |
| 55 | + if self.monophase(): |
| 56 | + # As far as I can tell there is no elegant way of mapping Shelly Pro 3EM's Monophase profile. Shelly |
| 57 | + # has modeled the Monophase profile as three virtual devices under the same IP:Port whereas |
| 58 | + # dbus-modbus-client makes the strong assumption that an IP:Port is an unique identifier for a single |
| 59 | + # device. Our only option is to go truly Monophase and disregard the Phase B and C meters. *shrug* |
| 60 | + # |
| 61 | + nr_phases = 1 |
| 62 | + |
| 63 | + self.data_regs += [ |
| 64 | + Reg_shelly(32302, '/Ac/Energy/Forward', 1000, '%.1f kWh'), |
| 65 | + Reg_shelly(32304, '/Ac/Energy/Reverse', 1000, '%.1f kWh'), |
| 66 | + Reg_shelly(32007, '/Ac/Power', 1, '%.1f W'), |
| 67 | + |
| 68 | + Reg_shelly(32003, '/Ac/L1/Voltage', 1, '%.1f V'), |
| 69 | + Reg_shelly(32005, '/Ac/L1/Current', 1, '%.1f A'), |
| 70 | + Reg_shelly(32310, '/Ac/L1/Energy/Forward', 1000, '%.1f kWh'), |
| 71 | + Reg_shelly(32312, '/Ac/L1/Energy/Reverse', 1000, '%.1f kWh'), |
| 72 | + Reg_shelly(32007, '/Ac/L1/Power', 1, '%.1f W'), |
| 73 | + ] |
| 74 | + |
| 75 | + else: |
| 76 | + # Triphase (default) |
| 77 | + # |
| 78 | + nr_phases = 3 |
| 79 | + |
| 80 | + self.data_regs += [ |
| 81 | + Reg_shelly(31162, '/Ac/Energy/Forward', 1000, '%.1f kWh'), |
| 82 | + Reg_shelly(31164, '/Ac/Energy/Reverse', 1000, '%.1f kWh'), |
| 83 | + Reg_shelly(31013, '/Ac/Power', 1, '%.1f W'), |
| 84 | + |
| 85 | + Reg_shelly(31020, '/Ac/L1/Voltage', 1, '%.1f V'), |
| 86 | + Reg_shelly(31022, '/Ac/L1/Current', 1, '%.1f A'), |
| 87 | + Reg_shelly(31182, '/Ac/L1/Energy/Forward', 1000, '%.1f kWh'), |
| 88 | + Reg_shelly(31184, '/Ac/L1/Energy/Reverse', 1000, '%.1f kWh'), |
| 89 | + Reg_shelly(31024, '/Ac/L1/Power', 1, '%.1f W'), |
| 90 | + |
| 91 | + Reg_shelly(31040, '/Ac/L2/Voltage', 1, '%.1f V'), |
| 92 | + Reg_shelly(31042, '/Ac/L2/Current', 1, '%.1f A'), |
| 93 | + Reg_shelly(31202, '/Ac/L2/Energy/Forward', 1000, '%.1f kWh'), |
| 94 | + Reg_shelly(31204, '/Ac/L2/Energy/Reverse', 1000, '%.1f kWh'), |
| 95 | + Reg_shelly(31044, '/Ac/L2/Power', 1, '%.1f W'), |
| 96 | + |
| 97 | + Reg_shelly(31060, '/Ac/L3/Voltage', 1, '%.1f V'), |
| 98 | + Reg_shelly(31062, '/Ac/L3/Current', 1, '%.1f A'), |
| 99 | + Reg_shelly(31222, '/Ac/L3/Energy/Forward', 1000, '%.1f kWh'), |
| 100 | + Reg_shelly(31224, '/Ac/L3/Energy/Reverse', 1000, '%.1f kWh'), |
| 101 | + Reg_shelly(31064, '/Ac/L3/Power', 1, '%.1f W'), |
| 102 | + ] |
| 103 | + |
| 104 | + # |
| 105 | + # Returns true if the Shelly Pro 3EM appears to run in the Monophase profile. |
| 106 | + # |
| 107 | + def monophase(self): |
| 108 | + try: |
| 109 | + self.read_register(Reg_shelly(32000)) |
| 110 | + return True |
| 111 | + except Exception as err: |
| 112 | + self.log.info("The device appears to be in Triphase (default) mode.", err) |
| 113 | + return False |
| 114 | + |
| 115 | +models = { |
| 116 | + Shelly_Pro_3EM.productmodel: { |
| 117 | + 'model': Shelly_Pro_3EM.productmodel, |
| 118 | + 'handler': Shelly_Pro_3EM, |
| 119 | + }, |
| 120 | +} |
| 121 | + |
| 122 | +probe.add_handler(probe.ModelRegister(Reg_text(6, 10, 'model', little=True), models, |
| 123 | + methods=['tcp'], |
| 124 | + units=[1])) |
0 commit comments