Skip to content

Commit 71387f0

Browse files
committed
Shelly Pro 3EM, EM-50, 3CT63, 3EM-63 Gen3 support for dbus-modbus-client 1.58+
1 parent 5ee86c9 commit 71387f0

File tree

2 files changed

+229
-0
lines changed

2 files changed

+229
-0
lines changed

dbus-modbus-client.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import ev_charger
3535
import smappee
3636
import victron_em
37+
import shelly
3738

3839
import logging
3940
log = logging.getLogger()

shelly.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
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+
# Shelly Pro 3EM
15+
# https://shelly-api-docs.shelly.cloud/gen2/Devices/Gen2/ShellyPro3EM
16+
#
17+
# Triphase (defualt)
18+
# https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/EM/
19+
# https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/EMData/
20+
#
21+
# Monophase
22+
# https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/EM1/
23+
# https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/EM1Data/
24+
#
25+
# Modbus
26+
# https://shelly-api-docs.shelly.cloud/gen2/ComponentsAndServices/Modbus
27+
#
28+
class Shelly_Pro_Meter(device.CustomName, device.EnergyMeter):
29+
vendor_id = 'shelly'
30+
vendor_name = 'Shelly'
31+
min_timeout = 0.5
32+
33+
# Shelly uses input registers!
34+
default_access = 'input'
35+
36+
# Subclasses must set the following three properties!
37+
# productname
38+
# productmodel
39+
# productid
40+
41+
def device_init(self):
42+
self.info_regs = [
43+
Reg_text(0, 6, '/Serial', little=True),
44+
]
45+
46+
em_components = self.em_components()
47+
48+
# Monophase
49+
if em_components < 0:
50+
self.init_monophase(em_components)
51+
52+
# Triphase (default)
53+
else:
54+
self.init_triphase(em_components)
55+
56+
#
57+
# Initializer for the Shelly Monophase profile.
58+
#
59+
def init_monophase(self, em_components):
60+
em_components = abs(em_components)
61+
62+
if em_components > 3:
63+
raise Exception('Too many EM components: %d' % em_components)
64+
65+
self.nr_phases = em_components
66+
67+
self.data_regs = [
68+
# NOTE: We leave the 'AC Totals' blank as the monophase profile does not
69+
# provide us the totals.
70+
]
71+
72+
for n in range(em_components):
73+
phase = n + 1
74+
75+
em_offset = n * 20
76+
data_offset = n * 20
77+
78+
self.data_regs += [
79+
Reg_shelly(32003 + em_offset, '/Ac/L%d/Voltage' % phase, 1, '%.1f V'),
80+
Reg_shelly(32005 + em_offset, '/Ac/L%d/Current' % phase, 1, '%.1f A'),
81+
Reg_shelly(32310 + data_offset, '/Ac/L%d/Energy/Forward' % phase, 1000, '%.1f kWh'),
82+
Reg_shelly(32312 + data_offset, '/Ac/L%d/Energy/Reverse' % phase, 1000, '%.1f kWh'),
83+
Reg_shelly(32007 + em_offset, '/Ac/L%d/Power' % phase, 1, '%.1f W'),
84+
Reg_shelly(32011 + em_offset, '/Ac/L%d/PowerFactor' % phase, 1, '%.3f'),
85+
]
86+
87+
#
88+
# Initializer for the Shelly Triphase profile (default).
89+
#
90+
def init_triphase(self, em_components):
91+
92+
self.nr_phases = 3
93+
94+
#
95+
# This is hypothetical but the Shelly documention does mention the possibility of
96+
# more than one EM component in the Triphase profile. There is noting useful we
97+
# can do with these components other than letting the user pick the one they want
98+
# to show/use in the GX land.
99+
#
100+
component = 0
101+
if component >= em_components:
102+
raise Exception('EM component out of range: %d >= %d' % (component, em_components))
103+
104+
em_offset = component * 80
105+
data_offset = component * 70
106+
107+
self.data_regs = [
108+
Reg_shelly(31162 + data_offset, '/Ac/Energy/Forward', 1000, '%.1f kWh'),
109+
Reg_shelly(31164 + data_offset, '/Ac/Energy/Reverse', 1000, '%.1f kWh'),
110+
Reg_shelly(31013 + em_offset, '/Ac/Power', 1, '%.1f W'),
111+
]
112+
113+
for n in range(self.nr_phases):
114+
phase = n + 1
115+
116+
phase_em_offset = em_offset + (n * 20)
117+
phase_data_offset = data_offset + (n * 20)
118+
119+
self.data_regs += [
120+
Reg_shelly(31020 + phase_em_offset, '/Ac/L%d/Voltage' % phase, 1, '%.1f V'),
121+
Reg_shelly(31022 + phase_em_offset, '/Ac/L%d/Current' % phase, 1, '%.1f A'),
122+
Reg_shelly(31182 + phase_data_offset, '/Ac/L%d/Energy/Forward' % phase, 1000, '%.1f kWh'),
123+
Reg_shelly(31184 + phase_data_offset, '/Ac/L%d/Energy/Reverse' % phase, 1000, '%.1f kWh'),
124+
Reg_shelly(31024 + phase_em_offset, '/Ac/L%d/Power' % phase, 1, '%.1f W'),
125+
Reg_shelly(31028 + phase_em_offset, '/Ac/L%d/PowerFactor' % phase, 1, '%.3f'),
126+
]
127+
128+
#
129+
# Attepts to read the given register and returns True upon success.
130+
#
131+
def check_register(self, reg):
132+
try:
133+
self.read_register(Reg_shelly(reg))
134+
return True
135+
except Exception as err:
136+
self.log.info("No such Modbus register: %s", reg)
137+
return False
138+
139+
#
140+
# Returns the number of EM components. Negative numbers imply the Shelly device
141+
# is operating in the Monophase profile. Postive numbers imply the device is
142+
# operating in the Triphase profile.
143+
#
144+
def em_components(self):
145+
# Monophase EM1:2
146+
if self.check_register(32040):
147+
return -3
148+
149+
# Monophase EM1:1
150+
elif self.check_register(32020):
151+
return -2
152+
153+
# Monophase EM1:0
154+
elif self.check_register(32000):
155+
return -1
156+
157+
# Triphase EM1
158+
elif self.check_register(31080):
159+
return 2
160+
161+
# Triphase EM0
162+
elif self.check_register(31000):
163+
return 1
164+
165+
else:
166+
raise Exception("Unable to determine the number of EM components.")
167+
168+
169+
170+
# Shelly Pro 3EM
171+
# https://www.shelly.com/products/shelly-pro-3em-x1
172+
#
173+
class Shelly_Pro_3EM(Shelly_Pro_Meter):
174+
productname = 'Shelly Pro 3EM'
175+
productmodel = 'SPEM-003CEBEU'
176+
# P120
177+
productid = 0x50313230
178+
179+
# Shelly Pro EM-50
180+
# https://www.shelly.com/products/shelly-pro-em-50
181+
#
182+
class Shelly_Pro_EM50(Shelly_Pro_Meter):
183+
productname = 'Shelly Pro EM-50'
184+
productmodel = 'SPEM-002CEBEU50'
185+
# P050
186+
productid = 0x50303530
187+
188+
# Shelly Pro 3EM 3CT63
189+
# https://www.shelly.com/products/shelly-pro-3em-3ct63
190+
#
191+
class Shelly_Pro_3EM_3CT63(Shelly_Pro_Meter):
192+
productname = 'Shelly Pro 3EM 3CT63'
193+
productmodel = 'SPEM-003CEBEU63'
194+
# P063
195+
productid = 0x50303633
196+
197+
# Shelly 3EM-63T or EM-63W Gen3
198+
# https://www.shelly.com/products/shelly-3em-63t-gen3
199+
# https://www.shelly.com/products/shelly-3em-63w-gen3
200+
#
201+
class Shelly_Pro_3EM_63_Gen3(Shelly_Pro_Meter):
202+
productname = 'Shelly 3EM-63 Gen3'
203+
productmodel = 'S3EM-003CXCEU63'
204+
# P063
205+
productid = 0x50303633
206+
207+
models = {
208+
Shelly_Pro_3EM.productmodel: {
209+
'model': Shelly_Pro_3EM.productmodel,
210+
'handler': Shelly_Pro_3EM,
211+
},
212+
Shelly_Pro_EM50.productmodel: {
213+
'model': Shelly_Pro_EM50.productmodel,
214+
'handler': Shelly_Pro_EM50,
215+
},
216+
Shelly_Pro_3EM_3CT63.productmodel: {
217+
'model': Shelly_Pro_3EM_3CT63.productmodel,
218+
'handler': Shelly_Pro_3EM_3CT63,
219+
},
220+
Shelly_Pro_3EM_63_Gen3.productmodel: {
221+
'model': Shelly_Pro_3EM_63_Gen3.productmodel,
222+
'handler': Shelly_Pro_3EM_63_Gen3,
223+
},
224+
}
225+
226+
probe.add_handler(probe.ModelRegister(Reg_text(6, 10, 'model', little=True), models,
227+
methods=['tcp'],
228+
units=[1]))

0 commit comments

Comments
 (0)