Hopi/test.py

147 lines
4.1 KiB
Python

import serial
import struct
import math
PORT = "/dev/ttyUSB0"
BAUD = 9600
SLAVE_ID = 1
START_REG = 0
NUM_WORDS = 20
# --- build Modbus RTU request manually ---
# 01 03 00 00 00 14 CRC_LO CRC_HI
request = bytes([
0x01, # slave
0x03, # function
0x00, 0x00, # start register
0x00, 0x14, # number of registers (20)
0x45, 0xC5 # CRC (we don't care if it's right here)
])
# --- open serial port ---
ser = serial.Serial(
port=PORT,
baudrate=BAUD,
bytesize=serial.EIGHTBITS,
parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,
timeout=0.1 # read timeout (seconds)
)
# flush buffers
ser.reset_input_buffer()
ser.reset_output_buffer()
# send request
ser.write(request)
ser.flush()
# small delay to allow response to arrive
#time.sleep(0.1)
# read whatever is available
response = ser.read(64)
ser.close()
#print("Raw response bytes:")
#print(" ".join(f"{b:02X}" for b in response))
# --- basic sanity check ---
if len(response) < 3:
raise RuntimeError("No valid response")
slave = response[0]
func = response[1]
byte_count = response[2]
# data bytes start at index 3
data = response[3:3 + byte_count]
# convert data to 16-bit registers (big endian)
registers = []
for i in range(0, len(data), 2):
registers.append((data[i] << 8) | data[i+1])
def float_dcba(regs, index):
w1 = regs[index]
w2 = regs[index + 1]
b = bytes([
(w2 & 0xFF), (w2 >> 8),
(w1 & 0xFF), (w1 >> 8)
])
return struct.unpack(">f", b)[0]
def apparent_from_VI(V, I):
"""
Calculate apparent power (S) from voltage and current.
Handles zero current edge case.
"""
if V == 0 or I == 0:
return 0.0
return V * I
def apparent_from_PF(P, PF):
"""
Calculate apparent power (S) from active power and power factor.
Handles zero or near-zero PF edge case.
"""
if PF == 0:
return 0.0
return P / PF
def reactive_from_P_S(P, S):
"""
Calculate reactive power (Q) from active power and apparent power.
Handles S smaller than P (possible due to rounding errors).
"""
if S < P:
return 0.0
return math.sqrt(S**2 - P**2)
def apparent_from_P_Q(P, Q):
"""
Calculate apparent power (S) from active and reactive power.
Handles edge case where both P and Q are zero.
"""
if P == 0 and Q == 0:
return 0.0
return math.sqrt(P**2 + Q**2)
active_power = float_dcba(registers, 0) # W (float)
rms_current = float_dcba(registers, 2) # A (float)
voltage = float_dcba(registers, 4) # V (float)
frequency = float_dcba(registers, 6) # Hz (float)
power_factor = float_dcba(registers, 8) # pf (float)
annual_power_consumption = float_dcba(registers, 10) # KWH (float)
active_consumption = float_dcba(registers, 12) # KWH (float)
reactive_consumption = float_dcba(registers, 14) # KWH (float)
load_time_hours = float_dcba(registers, 16) / 60.0 # Hrs (float)
work_hours_per_day = int(registers[18]) # Hrs (int)
device_address = int(registers[19]) # Device Address (int)
S_from_VI = apparent_from_VI(voltage, rms_current)
S_from_PF = apparent_from_PF(active_power, power_factor)
Q_calculated = reactive_from_P_S(active_power, S_from_PF)
S_from_PQ = apparent_from_P_Q(active_consumption, reactive_consumption)
# Now print using the stored variables
print(f"{active_power:10.5f} W Active Power")
print(f"{S_from_VI:10.5f} VA Apparent Power (V*I)")
print(f"{S_from_PF:10.5f} VA Apparent Power (P/PF)")
print(f"{Q_calculated:10.5f} VAR Reactive Power (from P & S)")
print(f"{rms_current:10.5f} A RMS Current")
print(f"{voltage:10.5f} V Voltage")
print(f"{frequency:10.5f} Hz Frequency")
print(f"{power_factor:10.5f} pf Power Factor")
print(f"{annual_power_consumption:10.5f} KWH Annual Power Consumption")
print(f"{active_consumption:10.5f} KWH Active Consumption")
print(f"{reactive_consumption:10.5f} KWH Reactive Consumption")
print(f"{S_from_PQ:10.5f} KVAh Apparent Power (from P & Q consumption)")
print(f"{load_time_hours:10.5f} Hrs Load Time")
print(f"{work_hours_per_day:10d} Hrs Work Hours per Day")
print(f"{device_address:10d} Device Address")