105 lines
3.2 KiB
Python
Executable File
105 lines
3.2 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import argparse
|
|
import json
|
|
import struct
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
|
|
from pymodbus.client import ModbusSerialClient
|
|
|
|
|
|
def decode_float_abcd(registers):
|
|
if len(registers) != 2:
|
|
raise ValueError(f"Expected 2 registers, got {len(registers)}")
|
|
|
|
raw = registers[0].to_bytes(2, "big") + registers[1].to_bytes(2, "big")
|
|
return struct.unpack(">f", raw)[0]
|
|
|
|
|
|
def read_float(client, address, device_id):
|
|
rr = client.read_input_registers(
|
|
address=address,
|
|
count=2,
|
|
device_id=device_id,
|
|
)
|
|
|
|
if rr.isError():
|
|
raise RuntimeError(f"Modbus error at address {address}: {rr}")
|
|
|
|
return decode_float_abcd(rr.registers)
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--port", default="/dev/ttyRS485")
|
|
parser.add_argument("--slave", type=int, default=1)
|
|
parser.add_argument("--baud", type=int, default=9600)
|
|
parser.add_argument("--parity", default="N")
|
|
parser.add_argument("--stopbits", type=int, default=1)
|
|
parser.add_argument("--bytesize", type=int, default=8)
|
|
parser.add_argument("--timeout", type=float, default=1.5)
|
|
args = parser.parse_args()
|
|
|
|
client = ModbusSerialClient(
|
|
port=args.port,
|
|
baudrate=args.baud,
|
|
parity=args.parity,
|
|
stopbits=args.stopbits,
|
|
bytesize=args.bytesize,
|
|
timeout=args.timeout,
|
|
)
|
|
|
|
if not client.connect():
|
|
print("[ERROR] Cannot open Modbus serial port", file=sys.stderr)
|
|
return 2
|
|
|
|
try:
|
|
voltage_v = read_float(client, 0, args.slave)
|
|
current_a = read_float(client, 6, args.slave)
|
|
active_power_w = read_float(client, 12, args.slave)
|
|
frequency_hz = read_float(client, 70, args.slave)
|
|
import_kwh = read_float(client, 72, args.slave)
|
|
export_kwh = read_float(client, 74, args.slave)
|
|
|
|
payload = {
|
|
"ts": datetime.now(timezone.utc).isoformat(),
|
|
"source": "revpi_sdm120m",
|
|
"meter_model": "Eastron SDM120-M",
|
|
"meter_id": f"modbus_{args.slave}",
|
|
"port": args.port,
|
|
"slave": args.slave,
|
|
"baud": args.baud,
|
|
"voltage_l1_v": round(voltage_v, 3),
|
|
"current_l1_a": round(current_a, 3),
|
|
"power_total_kw": round(active_power_w / 1000.0, 6),
|
|
"frequency_hz": round(frequency_hz, 3),
|
|
"import_kwh": round(import_kwh, 3),
|
|
"export_kwh": round(export_kwh, 3),
|
|
"comm_ok": True,
|
|
}
|
|
|
|
print(json.dumps(payload, indent=2, ensure_ascii=False))
|
|
return 0
|
|
|
|
except Exception as exc:
|
|
error_payload = {
|
|
"ts": datetime.now(timezone.utc).isoformat(),
|
|
"source": "revpi_sdm120m",
|
|
"meter_model": "Eastron SDM120-M",
|
|
"meter_id": f"modbus_{args.slave}",
|
|
"port": args.port,
|
|
"slave": args.slave,
|
|
"baud": args.baud,
|
|
"comm_ok": False,
|
|
"error_text": f"{type(exc).__name__}: {exc}",
|
|
}
|
|
print(json.dumps(error_payload, indent=2, ensure_ascii=False))
|
|
return 1
|
|
|
|
finally:
|
|
client.close()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|