Copilot refactor
This commit is contained in:
parent
b971d095c5
commit
10d00c8fbb
|
|
@ -0,0 +1,23 @@
|
|||
# Python bytecode
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
|
||||
# Packaging / builds
|
||||
build/
|
||||
dist/
|
||||
*.egg-info/
|
||||
|
||||
# Test / coverage
|
||||
.pytest_cache/
|
||||
.coverage
|
||||
htmlcov/
|
||||
|
||||
# Editors / OS
|
||||
.vscode/
|
||||
.DS_Store
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import argparse
|
||||
|
||||
from hopi.data_handler import PowerMeterDataHandler
|
||||
from hopi.serial_client import ModbusRtuSerialClient, SerialConfig
|
||||
|
||||
|
||||
SLAVE_ID = 1
|
||||
START_REG = 0
|
||||
NUM_WORDS = 20
|
||||
READ_MAX_BYTES = 5 + (2 * NUM_WORDS)
|
||||
|
||||
|
||||
def build_arg_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Read and print power meter values over Modbus RTU.")
|
||||
parser.add_argument("--port", default="/dev/ttyUSB0")
|
||||
parser.add_argument("--baud", type=int, default=9600)
|
||||
parser.add_argument("--timeout", type=float, default=0.1)
|
||||
return parser
|
||||
|
||||
|
||||
def main() -> None:
|
||||
args = build_arg_parser().parse_args()
|
||||
|
||||
config = SerialConfig(port=args.port, baudrate=args.baud, timeout=args.timeout)
|
||||
client = ModbusRtuSerialClient(config)
|
||||
handler = PowerMeterDataHandler()
|
||||
|
||||
with client:
|
||||
response = client.read_holding_registers(
|
||||
slave_id=SLAVE_ID,
|
||||
start_register=START_REG,
|
||||
register_count=NUM_WORDS,
|
||||
read_max_bytes=READ_MAX_BYTES,
|
||||
exact_response_length=True,
|
||||
)
|
||||
|
||||
registers = handler.parse_read_holding_registers_response(response)
|
||||
readings = handler.decode_readings(registers)
|
||||
|
||||
handler.print_readings_json(readings)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
import json
|
||||
import math
|
||||
import struct
|
||||
from typing import Iterable, List
|
||||
|
||||
from .models import PowerMeterReadings
|
||||
|
||||
|
||||
class PowerMeterDataHandler:
|
||||
"""Parses Modbus RTU responses and computes derived power metrics."""
|
||||
|
||||
def parse_read_holding_registers_response(self, response: bytes) -> List[int]:
|
||||
# Response layout: [slave][func][byte_count][data...][crc_lo][crc_hi]
|
||||
# We keep validation intentionally lightweight to match prior script behavior.
|
||||
if len(response) < 5:
|
||||
raise RuntimeError("No valid response")
|
||||
|
||||
byte_count = response[2]
|
||||
data_start = 3
|
||||
data_end = data_start + byte_count
|
||||
if len(response) < data_end:
|
||||
raise RuntimeError("Truncated response")
|
||||
|
||||
data = response[data_start:data_end]
|
||||
if len(data) % 2 != 0:
|
||||
raise RuntimeError("Invalid byte_count (must be even)")
|
||||
|
||||
return self._bytes_to_registers_be(data)
|
||||
|
||||
def decode_readings(self, registers: List[int]) -> PowerMeterReadings:
|
||||
active_power = self.float_dcba(registers, 0)
|
||||
rms_current = self.float_dcba(registers, 2)
|
||||
voltage = self.float_dcba(registers, 4)
|
||||
frequency = self.float_dcba(registers, 6)
|
||||
power_factor = self.float_dcba(registers, 8)
|
||||
annual_power_consumption = self.float_dcba(registers, 10)
|
||||
active_consumption = self.float_dcba(registers, 12)
|
||||
reactive_consumption = self.float_dcba(registers, 14)
|
||||
load_time_hours = self.float_dcba(registers, 16) / 60.0
|
||||
work_hours_per_day = int(registers[18])
|
||||
device_address = int(registers[19])
|
||||
|
||||
s_from_vi = self.apparent_from_vi(voltage, rms_current)
|
||||
s_from_pf = self.apparent_from_pf(active_power, power_factor)
|
||||
q_calculated = self.reactive_from_p_s(active_power, s_from_pf)
|
||||
s_from_pq = self.apparent_from_p_q(active_consumption, reactive_consumption)
|
||||
|
||||
return PowerMeterReadings(
|
||||
active_power_w=active_power,
|
||||
rms_current_a=rms_current,
|
||||
voltage_v=voltage,
|
||||
frequency_hz=frequency,
|
||||
power_factor=power_factor,
|
||||
annual_power_consumption_kwh=annual_power_consumption,
|
||||
active_consumption_kwh=active_consumption,
|
||||
reactive_consumption_kwh=reactive_consumption,
|
||||
load_time_hours=load_time_hours,
|
||||
work_hours_per_day=work_hours_per_day,
|
||||
device_address=device_address,
|
||||
apparent_power_vi_va=s_from_vi,
|
||||
apparent_power_pf_va=s_from_pf,
|
||||
reactive_power_var=q_calculated,
|
||||
apparent_consumption_kvah=s_from_pq,
|
||||
)
|
||||
|
||||
def readings_to_flat_dict(self, readings: PowerMeterReadings) -> dict:
|
||||
# dataclasses.asdict() is fine, but we keep this local to the data layer
|
||||
# so the entrypoint stays minimal.
|
||||
return {
|
||||
"active_power_w": readings.active_power_w,
|
||||
"rms_current_a": readings.rms_current_a,
|
||||
"voltage_v": readings.voltage_v,
|
||||
"frequency_hz": readings.frequency_hz,
|
||||
"power_factor": readings.power_factor,
|
||||
"annual_power_consumption_kwh": readings.annual_power_consumption_kwh,
|
||||
"active_consumption_kwh": readings.active_consumption_kwh,
|
||||
"reactive_consumption_kwh": readings.reactive_consumption_kwh,
|
||||
"load_time_hours": readings.load_time_hours,
|
||||
"work_hours_per_day": readings.work_hours_per_day,
|
||||
"device_address": readings.device_address,
|
||||
"apparent_power_vi_va": readings.apparent_power_vi_va,
|
||||
"apparent_power_pf_va": readings.apparent_power_pf_va,
|
||||
"reactive_power_var": readings.reactive_power_var,
|
||||
"apparent_consumption_kvah": readings.apparent_consumption_kvah,
|
||||
}
|
||||
|
||||
def readings_to_json(self, readings: PowerMeterReadings, *, ensure_ascii: bool = False) -> str:
|
||||
return json.dumps(self.readings_to_flat_dict(readings), ensure_ascii=ensure_ascii)
|
||||
|
||||
def print_readings_json(self, readings: PowerMeterReadings) -> None:
|
||||
print(self.readings_to_json(readings, ensure_ascii=False))
|
||||
|
||||
@staticmethod
|
||||
def _bytes_to_registers_be(data: bytes) -> List[int]:
|
||||
registers: List[int] = []
|
||||
for i in range(0, len(data), 2):
|
||||
registers.append((data[i] << 8) | data[i + 1])
|
||||
return registers
|
||||
|
||||
@staticmethod
|
||||
def float_dcba(regs: List[int], index: int) -> float:
|
||||
w1 = regs[index]
|
||||
w2 = regs[index + 1]
|
||||
b = bytes(
|
||||
[
|
||||
(w2 & 0xFF),
|
||||
(w2 >> 8) & 0xFF,
|
||||
(w1 & 0xFF),
|
||||
(w1 >> 8) & 0xFF,
|
||||
]
|
||||
)
|
||||
return struct.unpack(">f", b)[0]
|
||||
|
||||
@staticmethod
|
||||
def apparent_from_vi(voltage_v: float, current_a: float) -> float:
|
||||
if voltage_v == 0 or current_a == 0:
|
||||
return 0.0
|
||||
return voltage_v * current_a
|
||||
|
||||
@staticmethod
|
||||
def apparent_from_pf(active_power_w: float, power_factor: float) -> float:
|
||||
if power_factor == 0:
|
||||
return 0.0
|
||||
return active_power_w / power_factor
|
||||
|
||||
@staticmethod
|
||||
def reactive_from_p_s(active_power_w: float, apparent_power_va: float) -> float:
|
||||
if apparent_power_va < active_power_w:
|
||||
return 0.0
|
||||
return math.sqrt(apparent_power_va**2 - active_power_w**2)
|
||||
|
||||
@staticmethod
|
||||
def apparent_from_p_q(active_kwh: float, reactive_kwh: float) -> float:
|
||||
if active_kwh == 0 and reactive_kwh == 0:
|
||||
return 0.0
|
||||
return math.sqrt(active_kwh**2 + reactive_kwh**2)
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
from dataclasses import dataclass
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PowerMeterReadings:
|
||||
active_power_w: float
|
||||
rms_current_a: float
|
||||
voltage_v: float
|
||||
frequency_hz: float
|
||||
power_factor: float
|
||||
annual_power_consumption_kwh: float
|
||||
active_consumption_kwh: float
|
||||
reactive_consumption_kwh: float
|
||||
load_time_hours: float
|
||||
work_hours_per_day: int
|
||||
device_address: int
|
||||
|
||||
apparent_power_vi_va: float
|
||||
apparent_power_pf_va: float
|
||||
reactive_power_var: float
|
||||
apparent_consumption_kvah: float
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
from dataclasses import dataclass
|
||||
from typing import Optional
|
||||
|
||||
import serial
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SerialConfig:
|
||||
port: str = "/dev/ttyUSB0"
|
||||
baudrate: int = 9600
|
||||
timeout: float = 0.1
|
||||
bytesize: int = serial.EIGHTBITS
|
||||
parity: str = serial.PARITY_NONE
|
||||
stopbits: int = serial.STOPBITS_ONE
|
||||
|
||||
|
||||
class ModbusRtuSerialClient:
|
||||
"""Minimal Modbus RTU client over a serial port.
|
||||
|
||||
Only implements function 0x03 (Read Holding Registers).
|
||||
"""
|
||||
|
||||
def __init__(self, config: SerialConfig):
|
||||
self._config = config
|
||||
self._ser: Optional[serial.Serial] = None
|
||||
|
||||
def __enter__(self) -> "ModbusRtuSerialClient":
|
||||
self.open()
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc, tb) -> None:
|
||||
self.close()
|
||||
|
||||
@property
|
||||
def is_open(self) -> bool:
|
||||
return self._ser is not None and self._ser.is_open
|
||||
|
||||
def open(self) -> None:
|
||||
if self.is_open:
|
||||
return
|
||||
self._ser = serial.Serial(
|
||||
port=self._config.port,
|
||||
baudrate=self._config.baudrate,
|
||||
bytesize=self._config.bytesize,
|
||||
parity=self._config.parity,
|
||||
stopbits=self._config.stopbits,
|
||||
timeout=self._config.timeout,
|
||||
)
|
||||
self._ser.reset_input_buffer()
|
||||
self._ser.reset_output_buffer()
|
||||
|
||||
def close(self) -> None:
|
||||
if self._ser is not None:
|
||||
try:
|
||||
self._ser.close()
|
||||
finally:
|
||||
self._ser = None
|
||||
|
||||
def read_holding_registers(
|
||||
self,
|
||||
slave_id: int,
|
||||
start_register: int,
|
||||
register_count: int,
|
||||
*,
|
||||
read_max_bytes: int = 64,
|
||||
exact_response_length: bool = True,
|
||||
) -> bytes:
|
||||
if not self.is_open:
|
||||
raise RuntimeError("Serial port is not open")
|
||||
if not (0 <= slave_id <= 247):
|
||||
raise ValueError("slave_id must be in range 0..247")
|
||||
if not (0 <= start_register <= 0xFFFF):
|
||||
raise ValueError("start_register must be in range 0..65535")
|
||||
if not (1 <= register_count <= 125):
|
||||
raise ValueError("register_count must be in range 1..125")
|
||||
|
||||
request_wo_crc = bytes(
|
||||
[
|
||||
slave_id,
|
||||
0x03,
|
||||
(start_register >> 8) & 0xFF,
|
||||
start_register & 0xFF,
|
||||
(register_count >> 8) & 0xFF,
|
||||
register_count & 0xFF,
|
||||
]
|
||||
)
|
||||
request = request_wo_crc + self._crc16_modbus_le(request_wo_crc)
|
||||
|
||||
ser = self._ser
|
||||
assert ser is not None
|
||||
ser.reset_input_buffer()
|
||||
ser.reset_output_buffer()
|
||||
ser.write(request)
|
||||
ser.flush()
|
||||
|
||||
if exact_response_length:
|
||||
# Expected RTU response length for 0x03:
|
||||
# slave(1) + func(1) + byte_count(1) + data(2*N) + crc(2)
|
||||
expected_len = 5 + (2 * register_count)
|
||||
return ser.read(expected_len)
|
||||
|
||||
return ser.read(read_max_bytes)
|
||||
|
||||
@staticmethod
|
||||
def _crc16_modbus_le(payload: bytes) -> bytes:
|
||||
"""Compute Modbus CRC16 and return as little-endian 2 bytes."""
|
||||
crc = 0xFFFF
|
||||
for byte in payload:
|
||||
crc ^= byte
|
||||
for _ in range(8):
|
||||
if crc & 0x0001:
|
||||
crc = (crc >> 1) ^ 0xA001
|
||||
else:
|
||||
crc >>= 1
|
||||
return bytes([crc & 0xFF, (crc >> 8) & 0xFF])
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
import time: self [us] | cumulative | imported package
|
||||
import time: 268 | 268 | _io
|
||||
import time: 54 | 54 | marshal
|
||||
import time: 407 | 407 | posix
|
||||
import time: 892 | 1619 | _frozen_importlib_external
|
||||
import time: 92 | 92 | time
|
||||
import time: 298 | 389 | zipimport
|
||||
import time: 51 | 51 | _codecs
|
||||
import time: 532 | 582 | codecs
|
||||
import time: 436 | 436 | encodings.aliases
|
||||
import time: 582 | 1599 | encodings
|
||||
import time: 199 | 199 | encodings.utf_8
|
||||
import time: 91 | 91 | _signal
|
||||
import time: 36 | 36 | _abc
|
||||
import time: 167 | 202 | abc
|
||||
import time: 163 | 365 | io
|
||||
import time: 44 | 44 | _stat
|
||||
import time: 107 | 150 | stat
|
||||
import time: 929 | 929 | _collections_abc
|
||||
import time: 60 | 60 | errno
|
||||
import time: 93 | 93 | genericpath
|
||||
import time: 159 | 311 | posixpath
|
||||
import time: 524 | 1912 | os
|
||||
import time: 109 | 109 | _sitebuiltins
|
||||
import time: 427 | 427 | encodings.utf_8_sig
|
||||
import time: 642 | 642 | _distutils_hack
|
||||
import time: 306 | 306 | types
|
||||
import time: 152 | 152 | importlib
|
||||
import time: 184 | 184 | importlib._abc
|
||||
import time: 199 | 534 | importlib.util
|
||||
import time: 62 | 62 | importlib.machinery
|
||||
import time: 60 | 60 | sitecustomize
|
||||
import time: 51 | 51 | usercustomize
|
||||
import time: 1437 | 5537 | site
|
||||
import time: 345 | 345 | itertools
|
||||
import time: 137 | 137 | keyword
|
||||
import time: 85 | 85 | _operator
|
||||
import time: 284 | 369 | operator
|
||||
import time: 227 | 227 | reprlib
|
||||
import time: 75 | 75 | _collections
|
||||
import time: 833 | 1984 | collections
|
||||
import time: 79 | 79 | _functools
|
||||
import time: 650 | 2712 | functools
|
||||
import time: 1875 | 4587 | enum
|
||||
import time: 113 | 113 | _sre
|
||||
import time: 309 | 309 | re._constants
|
||||
import time: 503 | 811 | re._parser
|
||||
import time: 123 | 123 | re._casefix
|
||||
import time: 960 | 2006 | re._compiler
|
||||
import time: 186 | 186 | copyreg
|
||||
import time: 632 | 7409 | re
|
||||
import time: 1521 | 1521 | gettext
|
||||
import time: 1146 | 10075 | argparse
|
||||
import time: 114 | 114 | hopi
|
||||
import time: 213 | 213 | _json
|
||||
import time: 355 | 567 | json.scanner
|
||||
import time: 467 | 1034 | json.decoder
|
||||
import time: 390 | 390 | json.encoder
|
||||
import time: 210 | 1633 | json
|
||||
import time: 208 | 208 | math
|
||||
import time: 169 | 169 | _struct
|
||||
import time: 144 | 313 | struct
|
||||
import time: 50 | 50 | _typing
|
||||
import time: 2816 | 2865 | typing
|
||||
import time: 235 | 235 | _weakrefset
|
||||
import time: 446 | 680 | weakref
|
||||
import time: 210 | 890 | copy
|
||||
import time: 1398 | 1398 | _ast
|
||||
import time: 609 | 609 | contextlib
|
||||
import time: 1228 | 3233 | ast
|
||||
import time: 211 | 211 | _opcode
|
||||
import time: 231 | 231 | _opcode_metadata
|
||||
import time: 309 | 750 | opcode
|
||||
import time: 908 | 1657 | dis
|
||||
import time: 167 | 167 | linecache
|
||||
import time: 180 | 180 | token
|
||||
import time: 42 | 42 | _tokenize
|
||||
import time: 815 | 1037 | tokenize
|
||||
import time: 2370 | 8462 | inspect
|
||||
import time: 716 | 10066 | dataclasses
|
||||
import time: 1404 | 11470 | hopi.models
|
||||
import time: 401 | 17002 | hopi.data_handler
|
||||
import time: 144 | 144 | __future__
|
||||
import time: 438 | 438 | serial.serialutil
|
||||
import time: 168 | 168 | fcntl
|
||||
import time: 165 | 165 | select
|
||||
import time: 216 | 216 | termios
|
||||
import time: 180 | 180 | array
|
||||
import time: 488 | 1215 | serial.serialposix
|
||||
import time: 215 | 2011 | serial
|
||||
import time: 951 | 2962 | hopi.serial_client
|
||||
import time: 81 | 81 | _locale
|
||||
import time: 909 | 990 | locale
|
||||
import time: 170 | 170 | fnmatch
|
||||
import time: 343 | 343 | zlib
|
||||
import time: 211 | 211 | _compression
|
||||
import time: 226 | 226 | _bz2
|
||||
import time: 271 | 708 | bz2
|
||||
import time: 265 | 265 | _lzma
|
||||
import time: 266 | 531 | lzma
|
||||
import time: 951 | 2700 | shutil
|
||||
|
|
@ -0,0 +1 @@
|
|||
pyserial>=3.5
|
||||
146
test.py
146
test.py
|
|
@ -1,146 +0,0 @@
|
|||
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")
|
||||
|
||||
Loading…
Reference in New Issue