135 lines
3.5 KiB
Python
Executable File
135 lines
3.5 KiB
Python
Executable File
import config as cfg
|
|
|
|
|
|
# trick the pycharm type-checker into thinking Callable is in scope, not used at runtime
|
|
# noinspection PyUnreachableCode
|
|
if False:
|
|
from typing import Callable, List, Optional, AnyStr, Union, Any
|
|
|
|
|
|
class LedState(object):
|
|
"""
|
|
from page 6 of the '48TLxxx ModBus Protocol doc'
|
|
"""
|
|
off = 0
|
|
on = 1
|
|
blinking_slow = 2
|
|
blinking_fast = 3
|
|
|
|
|
|
class LedColor(object):
|
|
green = 0
|
|
amber = 1
|
|
blue = 2
|
|
red = 3
|
|
|
|
|
|
class ServiceSignal(object):
|
|
|
|
def __init__(self, dbus_path, get_value_or_const, unit=''):
|
|
# type: (str, Union[Callable[[],Any],Any], Optional[AnyStr] )->None
|
|
|
|
self.get_value_or_const = get_value_or_const
|
|
self.dbus_path = dbus_path
|
|
self.unit = unit
|
|
|
|
@property
|
|
def value(self):
|
|
try:
|
|
return self.get_value_or_const() # callable
|
|
except:
|
|
return self.get_value_or_const # value
|
|
|
|
|
|
class BatterySignal(object):
|
|
|
|
def __init__(self, dbus_path, aggregate, get_value, unit=''):
|
|
# type: (str, Callable[[List[any]],any], Callable[[BatteryStatus],any], Optional[AnyStr] )->None
|
|
"""
|
|
A Signal holds all information necessary for the handling of a
|
|
certain datum (e.g. voltage) published by the battery.
|
|
|
|
:param dbus_path: str
|
|
object_path on DBus where the datum needs to be published
|
|
|
|
:param aggregate: Iterable[any] -> any
|
|
function that combines the values of multiple batteries into one.
|
|
e.g. sum for currents, or mean for voltages
|
|
|
|
:param get_value: (BatteryStatus) -> any
|
|
function to extract the datum from the modbus record,
|
|
"""
|
|
|
|
self.dbus_path = dbus_path
|
|
self.aggregate = aggregate
|
|
self.get_value = get_value
|
|
self.unit = unit
|
|
|
|
|
|
class Battery(object):
|
|
|
|
""" Data record to hold hardware and firmware specs of the battery """
|
|
|
|
def __init__(self, slave_address, hardware_version, firmware_version, bms_version, ampere_hours):
|
|
# type: (int, str, str, str, int) -> None
|
|
self.slave_address = slave_address
|
|
self.hardware_version = hardware_version
|
|
self.firmware_version = firmware_version
|
|
self.bms_version = bms_version
|
|
self.ampere_hours = ampere_hours
|
|
self.n_strings = int(ampere_hours/cfg.AH_PER_STRING)
|
|
self.i_max = self.n_strings * cfg.I_MAX_PER_STRING
|
|
self.v_min = cfg.V_MIN
|
|
self.v_max = cfg.V_MAX
|
|
self.r_int_min = cfg.R_STRING_MIN / self.n_strings
|
|
self.r_int_max = cfg.R_STRING_MAX / self.n_strings
|
|
|
|
def __str__(self):
|
|
return 'slave address = {0}\nhardware version = {1}\nfirmware version = {2}\nbms version = {3}\nampere hours = {4}'.format(
|
|
self.slave_address, self.hardware_version, self.firmware_version, self.bms_version, str(self.ampere_hours))
|
|
|
|
|
|
class BatteryStatus(object):
|
|
"""
|
|
record holding the current status of a battery
|
|
"""
|
|
def __init__(self, battery, modbus_data):
|
|
# type: (Battery, List[int]) -> None
|
|
|
|
self.battery = battery
|
|
self.modbus_data = modbus_data
|
|
|
|
def serialize(self):
|
|
# type: () -> str
|
|
|
|
b = self.battery
|
|
|
|
s = cfg.INNOVENERGY_PROTOCOL_VERSION + '\n'
|
|
s += cfg.INSTALLATION_NAME + '\n'
|
|
s += str(b.slave_address) + '\n'
|
|
s += b.hardware_version + '\n'
|
|
s += b.firmware_version + '\n'
|
|
s += b.bms_version + '\n'
|
|
s += str(b.ampere_hours) + '\n'
|
|
|
|
for d in self.modbus_data:
|
|
s += str(d) + '\n'
|
|
|
|
return s
|
|
|
|
|
|
def read_file_one_line(file_name):
|
|
|
|
with open(file_name, 'r') as file:
|
|
return file.read().replace('\n', '').replace('\r', '').strip()
|
|
|
|
|
|
class CsvSignal(object):
|
|
def __init__(self, name, get_value, get_text=None):
|
|
self.name = name
|
|
self.get_value = get_value if callable(get_value) else lambda _: get_value
|
|
self.get_text = get_text
|
|
|
|
if get_text is None:
|
|
self.get_text = ""
|