Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions devices/em24.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import logging
import sys
import sdm_modbus


def device(config):

# Configuration parameters:
#
# timeout seconds to wait for a response, default: 1
# retries number of retries, default: 3
# unit modbus address, default: 1
#
# For Modbus TCP:
# host ip or hostname
# port modbus tcp port
#
# For Modbus RTU:
# device serial device, e.g. /dev/ttyUSB0
# stopbits number of stop bits
# parity parity setting, N, E or O
# baud baud rate

timeout = config.getint("timeout", fallback=1)
retries = config.getint("retries", fallback=3)
unit = config.getint("src_address", fallback=1)

host = config.get("host", fallback=False)
port = config.getint("port", fallback=False)
device = config.get("device", fallback=False)

if device:
stopbits = config.getint("stopbits", fallback=1)
parity = config.get("parity", fallback="N")
baud = config.getint("baud", fallback=2400)

if (parity
and parity.upper() in ["N", "E", "O"]):
parity = parity.upper()
else:
parity = False

return sdm_modbus.EM24(
device=device,
stopbits=stopbits,
parity=parity,
baud=baud,
timeout=timeout,
retries=retries,
unit=unit
)
else:
return sdm_modbus.EM24(
host=host,
port=port,
timeout=timeout,
retries=retries,
unit=unit
)


def values(device):
if not device:
return {}

logger = logging.getLogger()
logger.debug(f"device: {device}")

values = device.read_all(scaling = True)

logger.debug(f"values: {values}")

return {
"energy_active": values.get("import_energy_active", 0) + values.get("export_energy_active", 0),
"power_active": values.get("power_active", 0),
"l1_power_active": values.get("l1_power_active", 0),
"l2_power_active": values.get("l2_power_active", 0),
"l3_power_active": values.get("l3_power_active", 0),
"voltage_ln": values.get("voltage_ln", 0),
"l1n_voltage": values.get("l1_voltage", 0),
"l2n_voltage": values.get("l2_voltage", 0),
"l3n_voltage": values.get("l3_voltage", 0),
"voltage_ll": values.get("voltage_ll", 0),
"l12_voltage": values.get("l12_voltage", 0),
"l23_voltage": values.get("l23_voltage", 0),
"l31_voltage": values.get("l31_voltage", 0),
"frequency": values.get("frequency", 0),
"l1_energy_active": values.get("l1_import_energy_active", 0) + values.get("export_energy_active", 0)/3,
"l2_energy_active": values.get("l2_import_energy_active", 0) + values.get("export_energy_active", 0)/3,
"l3_energy_active": values.get("l3_import_energy_active", 0) + values.get("export_energy_active", 0)/3,
"import_energy_active": values.get("import_energy_active", 0),
"l1_import_energy_active": values.get("l1_import_energy_active", 0),
"l2_import_energy_active": values.get("l2_import_energy_active", 0),
"l3_import_energy_active": values.get("l3_import_energy_active", 0),
"export_energy_active": values.get("export_energy_active", 0),
"l1_export_energy_active": values.get("export_energy_active", 0)/3,
"l2_export_energy_active": values.get("export_energy_active", 0)/3,
"l3_export_energy_active": values.get("export_energy_active", 0)/3,
"energy_reactive": values.get("import_energy_reactive", 0) + values.get("export_energy_reactive", 0),
#"l1_energy_reactive"
#"l2_energy_reactive"
#"l3_energy_reactive"
#"energy_apparent"
#"l1_energy_apparent"
#"l2_energy_apparent"
#"l3_energy_apparent"
"power_factor": values.get("total_pf", 0),
"l1_power_factor": values.get("l1_power_factor", 0),
"l2_power_factor": values.get("l2_power_factor", 0),
"l3_power_factor": values.get("l3_power_factor", 0),
"power_reactive": values.get("power_reactive", 0),
"l1_power_reactive": values.get("l1_power_reactive", 0),
"l2_power_reactive": values.get("l2_power_reactive", 0),
"l3_power_reactive": values.get("l3_power_reactive", 0),
"power_apparent": values.get("power_apparent", 0),
"l1_power_apparent": values.get("l1_power_apparent", 0),
"l2_power_apparent": values.get("l2_power_apparent", 0),
"l3_power_apparent": values.get("l3_power_apparent", 0),
"l1_current": values.get("l1_current", 0),
"l2_current": values.get("l2_current", 0),
"l3_current": values.get("l3_current", 0),
"demand_power_active": values.get("demand_power_active", 0),
# "minimum_demand_power_active"
"maximum_demand_power_active": values.get("maximum_demand_power_active", 0),
# "demand_power_apparent"
#"l1_demand_power_active"
#"l2_demand_power_active"
#"l3_demand_power_active"
}
21 changes: 11 additions & 10 deletions semp-rtu.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@
import sys
import threading
import time
import traceback

from pymodbus.server import StartSerialServer
from pymodbus.constants import Endian
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.transaction import ModbusRtuFramer
from pymodbus.datastore import ModbusSlaveContext
from pymodbus.datastore import ModbusServerContext
from pymodbus.payload import BinaryPayloadBuilder


# Protocol for WattNode register list: https://ctlsys.com/wp-content/uploads/2016/10/WNC-Modbus-Register-List-V18.xls
def t_update(ctx, stop, module, device, refresh):

this_t = threading.currentThread()
this_t = threading.current_thread()
logger = logging.getLogger()

while not stop.is_set():
Expand All @@ -30,7 +30,7 @@ def t_update(ctx, stop, module, device, refresh):
logger.debug(f"{this_t.name}: no new values")
continue

block_1001 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little)
block_1001 = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.LITTLE)
block_1001.add_32bit_float(values.get("energy_active", 0)) # total active energy
block_1001.add_32bit_float(values.get("import_energy_active", 0)) # imported active energy
block_1001.add_32bit_float(values.get("energy_active", 0)) # total active energy non-reset
Expand All @@ -50,7 +50,7 @@ def t_update(ctx, stop, module, device, refresh):
block_1001.add_32bit_float(values.get("frequency", 0)) # line frequency
ctx.setValues(3, 1000, block_1001.to_registers())

block_1101 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little)
block_1101 = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.LITTLE)
block_1101.add_32bit_float(values.get("l1_energy_active", 0)) # total active energy l1
block_1101.add_32bit_float(values.get("l2_energy_active", 0)) # total active energy l2
block_1101.add_32bit_float(values.get("l3_energy_active", 0)) # total active energy l3
Expand Down Expand Up @@ -95,6 +95,7 @@ def t_update(ctx, stop, module, device, refresh):
ctx.setValues(3, 1100, block_1101.to_registers())
except Exception as e:
logger.critical(f"{this_t.name}: {e}")
print(traceback.format_exc())
finally:
time.sleep(refresh)

Expand Down Expand Up @@ -157,7 +158,7 @@ def t_update(ctx, stop, module, device, refresh):

slave_ctx = ModbusSlaveContext()

block_1601 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little)
block_1601 = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.LITTLE)
block_1601.add_32bit_int(0) # config passcode
block_1601.add_16bit_int(confparser[meter].getint("ct_current", fallback=default_config["meters"]["ct_current"])) # ct rated current
block_1601.add_16bit_int(confparser[meter].getint("ct_current", fallback=default_config["meters"]["ct_current"])) # ct rated current l1
Expand All @@ -182,7 +183,7 @@ def t_update(ctx, stop, module, device, refresh):
block_1601.add_16bit_int(0) # io pin mode
slave_ctx.setValues(3, 1600, block_1601.to_registers())

block_1651 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little)
block_1651 = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.LITTLE)
block_1651.add_16bit_int(0) # apply config
block_1651.add_16bit_int(address) # modbus address
block_1651.add_16bit_int(4) # baud rate
Expand All @@ -191,7 +192,7 @@ def t_update(ctx, stop, module, device, refresh):
block_1651.add_16bit_int(5) # message delay
slave_ctx.setValues(3, 1650, block_1651.to_registers())

block_1701 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little)
block_1701 = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.LITTLE)
block_1701.add_32bit_int(confparser[meter].getint("serial_number", fallback=default_config["meters"]["serial_number"])) # serial number
block_1701.add_32bit_int(0) # uptime (s)
block_1701.add_32bit_int(0) # total uptime (s)
Expand Down Expand Up @@ -223,7 +224,7 @@ def t_update(ctx, stop, module, device, refresh):
update_t_stop,
meter_module,
meter_device,
confparser[meter].getint("refresh_rate", fallback=default_config["meters"]["refresh_rate"])
confparser[meter].getfloat("refresh_rate", fallback=default_config["meters"]["refresh_rate"])
)
)

Expand All @@ -247,7 +248,7 @@ def t_update(ctx, stop, module, device, refresh):

StartSerialServer(
context=server_ctx,
framer=ModbusRtuFramer,
framer="rtu",
identity=identity,
port=confparser["server"].get("device", fallback=default_config["server"]["device"]),
baudrate=confparser["server"].get("baud", fallback=default_config["server"]["baud"]),
Expand Down
34 changes: 21 additions & 13 deletions semp-tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,21 @@
import sys
import threading
import time
import traceback

from pymodbus.server import StartTcpServer
from pymodbus.constants import Endian
from pymodbus.device import ModbusDeviceIdentification
from pymodbus.transaction import ModbusSocketFramer
from pymodbus.transaction import ModbusRtuFramer
from pymodbus.datastore import ModbusSlaveContext
from pymodbus.datastore import ModbusServerContext
from pymodbus.payload import BinaryPayloadBuilder


# Protocol for WattNode register list:
# https://ctlsys.com/wp-content/uploads/2016/10/WNC-Modbus-Register-List-V18.xls
# https://ctlsys.com/wp-content/uploads/2016/10/WNC-Modbus-Manual-V18c.pdf
def t_update(ctx, stop, module, device, refresh):

this_t = threading.currentThread()
this_t = threading.current_thread()
logger = logging.getLogger()

while not stop.is_set():
Expand All @@ -30,8 +31,8 @@ def t_update(ctx, stop, module, device, refresh):
if not values:
logger.debug(f"{this_t.name}: no new values")
continue

block_1001 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little)
block_1001 = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.LITTLE)
block_1001.add_32bit_float(values.get("energy_active", 0)) # total active energy
block_1001.add_32bit_float(values.get("import_energy_active", 0)) # imported active energy
block_1001.add_32bit_float(values.get("energy_active", 0)) # total active energy non-reset
Expand All @@ -51,7 +52,7 @@ def t_update(ctx, stop, module, device, refresh):
block_1001.add_32bit_float(values.get("frequency", 0)) # line frequency
ctx.setValues(3, 1000, block_1001.to_registers())

block_1101 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little)
block_1101 = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.LITTLE)
block_1101.add_32bit_float(values.get("l1_energy_active", 0)) # total active energy l1
block_1101.add_32bit_float(values.get("l2_energy_active", 0)) # total active energy l2
block_1101.add_32bit_float(values.get("l3_energy_active", 0)) # total active energy l3
Expand Down Expand Up @@ -96,6 +97,7 @@ def t_update(ctx, stop, module, device, refresh):
ctx.setValues(3, 1100, block_1101.to_registers())
except Exception as e:
logger.critical(f"{this_t.name}: {e}")
print(traceback.format_exc())
finally:
time.sleep(refresh)

Expand Down Expand Up @@ -157,7 +159,7 @@ def t_update(ctx, stop, module, device, refresh):

slave_ctx = ModbusSlaveContext()

block_1601 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little)
block_1601 = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.LITTLE)
block_1601.add_32bit_int(1234) # config passcode
block_1601.add_16bit_int(confparser[meter].getint("ct_current", fallback=default_config["meters"]["ct_current"])) # ct rated current
block_1601.add_16bit_int(confparser[meter].getint("ct_current", fallback=default_config["meters"]["ct_current"])) # ct rated current l1
Expand All @@ -182,7 +184,7 @@ def t_update(ctx, stop, module, device, refresh):
block_1601.add_16bit_int(0) # io pin mode
slave_ctx.setValues(3, 1600, block_1601.to_registers())

block_1651 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little)
block_1651 = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.LITTLE)
block_1651.add_16bit_int(0) # apply config
block_1651.add_16bit_int(address) # modbus address
block_1651.add_16bit_int(4) # baud rate
Expand All @@ -191,7 +193,7 @@ def t_update(ctx, stop, module, device, refresh):
block_1651.add_16bit_int(5) # message delay
slave_ctx.setValues(3, 1650, block_1651.to_registers())

block_1701 = BinaryPayloadBuilder(byteorder=Endian.Big, wordorder=Endian.Little)
block_1701 = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.LITTLE)
block_1701.add_32bit_int(confparser[meter].getint("serial_number", fallback=default_config["meters"]["serial_number"])) # serial number
block_1701.add_32bit_int(0) # uptime (s)
block_1701.add_32bit_int(0) # total uptime (s)
Expand All @@ -213,6 +215,12 @@ def t_update(ctx, stop, module, device, refresh):
block_1701.add_16bit_int(0) # error status 7
block_1701.add_16bit_int(0) # error status 8
slave_ctx.setValues(3, 1700, block_1701.to_registers())

block_2128 = BinaryPayloadBuilder(byteorder=Endian.BIG, wordorder=Endian.LITTLE)
block_2128.add_16bit_int(0) # SolarEdge requests the value for the register 2127
# If you don't supply it, it will keep asking
# if you supply it, it will only ask it once
slave_ctx.setValues(3, 2127, block_2128.to_registers())

update_t_stop = threading.Event()
update_t = threading.Thread(
Expand All @@ -223,7 +231,7 @@ def t_update(ctx, stop, module, device, refresh):
update_t_stop,
meter_module,
meter_device,
confparser[meter].getint("refresh_rate", fallback=default_config["meters"]["refresh_rate"])
confparser[meter].getfloat("refresh_rate", fallback=default_config["meters"]["refresh_rate"])
)
)

Expand All @@ -240,9 +248,9 @@ def t_update(ctx, stop, module, device, refresh):
framer = False

if config_framer == "socket":
framer = ModbusSocketFramer
framer = "socket"
elif config_framer == "rtu":
framer = ModbusRtuFramer
framer = "rtu"

identity = ModbusDeviceIdentification()
server_ctx = ModbusServerContext(slaves=slaves, single=False)
Expand Down