# coding=utf-8 import config as cfg from convert import mean, read_float, read_led_state, read_bool, count_bits, comma_separated from data import BatterySignal, Battery, LedColor, ServiceSignal, BatteryStatus, LedState # noinspection PyUnreachableCode if False: from typing import List, Iterable def init_service_signals(batteries): # type: (List[Battery]) -> Iterable[ServiceSignal] n_batteries = len(batteries) product_name = cfg.PRODUCT_NAME + ' x' + str(n_batteries) return [ ServiceSignal('/NbOfBatteries', n_batteries), # TODO: nb of operational batteries ServiceSignal('/Mgmt/ProcessName', __file__), ServiceSignal('/Mgmt/ProcessVersion', cfg.SOFTWARE_VERSION), ServiceSignal('/Mgmt/Connection', cfg.CONNECTION), ServiceSignal('/DeviceInstance', cfg.DEVICE_INSTANCE), ServiceSignal('/ProductName', product_name), ServiceSignal('/ProductId', cfg.PRODUCT_ID), ServiceSignal('/Connected', 1) ] def init_battery_signals(): # type: () -> Iterable[BatterySignal] read_voltage = read_float(register=999, scale_factor=0.01, offset=0) read_current = read_float(register=1000, scale_factor=0.01, offset=-10000) read_led_amber = read_led_state(register=1004, led=LedColor.amber) read_led_green = read_led_state(register=1004, led=LedColor.green) read_led_blue = read_led_state(register=1004, led=LedColor.blue) read_led_red = read_led_state(register=1004, led=LedColor.red) def read_power(status): # type: (BatteryStatus) -> int return int(read_current(status) * read_voltage(status)) def calc_power_limit_imposed_by_voltage_limit(v, i, v_limit, r_int): # type: (float, float, float, float) -> float dv = v_limit - v di = dv / r_int p_limit = v_limit * (i + di) return p_limit def calc_power_limit_imposed_by_current_limit(v, i, i_limit, r_int): # type: (float, float, float, float) -> float di = i_limit - i dv = di * r_int p_limit = i_limit * (v + dv) return p_limit def calc_max_charge_power(bs): # type: (BatteryStatus) -> int b = bs.battery v = read_voltage(bs) i = read_current(bs) p_limits = [ calc_power_limit_imposed_by_voltage_limit(v, i, b.v_max, b.r_int_min), calc_power_limit_imposed_by_voltage_limit(v, i, b.v_max, b.r_int_max), calc_power_limit_imposed_by_current_limit(v, i, b.i_max, b.r_int_min), calc_power_limit_imposed_by_current_limit(v, i, b.i_max, b.r_int_max), ] p_limit = min(p_limits) # p_limit is normally positive here (signed) p_limit = max(p_limit, 0) # charge power must not become negative return int(p_limit) def calc_max_discharge_power(bs): # type: (BatteryStatus) -> float b = bs.battery v = read_voltage(bs) i = read_current(bs) p_limits = [ calc_power_limit_imposed_by_voltage_limit(v, i, b.v_min, b.r_int_min), calc_power_limit_imposed_by_voltage_limit(v, i, b.v_min, b.r_int_max), calc_power_limit_imposed_by_current_limit(v, i, -b.i_max, b.r_int_min), calc_power_limit_imposed_by_current_limit(v, i, -b.i_max, b.r_int_max), ] p_limit = max(p_limits) # p_limit is normally negative here (signed) p_limit = min(p_limit, 0) # discharge power must not become positive return int(-p_limit) # make unsigned! def read_battery_cold(status): return \ read_led_green(status) >= LedState.blinking_slow and \ read_led_blue(status) >= LedState.blinking_slow def read_soc(status): soc = read_float(register=1053, scale_factor=0.1, offset=0)(status) # if the SOC is 100 but EOC is not yet reached, report 99.9 instead of 100 if soc > 99.9 and not read_eoc_reached(status): return 99.9 if soc >= 99.9 and read_eoc_reached(status): return 100 return soc def read_eoc_reached(status): return \ read_led_green(status) == LedState.on and \ read_led_amber(status) == LedState.off and \ read_led_blue(status) == LedState.off return [ BatterySignal('/Dc/0/Voltage', mean, get_value=read_voltage, unit='V'), BatterySignal('/Dc/0/Current', sum, get_value=read_current, unit='A'), BatterySignal('/Dc/0/Power', sum, get_value=read_power, unit='W'), BatterySignal('/BussVoltage', mean, read_float(register=1001, scale_factor=0.01, offset=0), unit='V'), BatterySignal('/Soc', mean, read_soc, unit='%'), BatterySignal('/Dc/0/Temperature', mean, read_float(register=1003, scale_factor=0.1, offset=-400), unit='C'), BatterySignal('/NumberOfWarningFlags', sum, count_bits(base_register=1005, nb_of_registers=3, nb_of_bits=47)), BatterySignal('/WarningFlags/TaM1', any, read_bool(base_register=1005, bit=1)), BatterySignal('/WarningFlags/TbM1', any, read_bool(base_register=1005, bit=4)), BatterySignal('/WarningFlags/VBm1', any, read_bool(base_register=1005, bit=6)), BatterySignal('/WarningFlags/VBM1', any, read_bool(base_register=1005, bit=8)), BatterySignal('/WarningFlags/IDM1', any, read_bool(base_register=1005, bit=10)), BatterySignal('/WarningFlags/vsM1', any, read_bool(base_register=1005, bit=24)), BatterySignal('/WarningFlags/iCM1', any, read_bool(base_register=1005, bit=26)), BatterySignal('/WarningFlags/iDM1', any, read_bool(base_register=1005, bit=28)), BatterySignal('/WarningFlags/MID1', any, read_bool(base_register=1005, bit=30)), BatterySignal('/WarningFlags/BLPW', any, read_bool(base_register=1005, bit=32)), BatterySignal('/WarningFlags/Ah_W', any, read_bool(base_register=1005, bit=35)), BatterySignal('/WarningFlags/MPMM', any, read_bool(base_register=1005, bit=38)), BatterySignal('/WarningFlags/TCMM', any, read_bool(base_register=1005, bit=39)), BatterySignal('/WarningFlags/TCdi', any, read_bool(base_register=1005, bit=40)), BatterySignal('/WarningFlags/WMTO', any, read_bool(base_register=1005, bit=41)), BatterySignal('/WarningFlags/bit44', any, read_bool(base_register=1005, bit=44)), BatterySignal('/WarningFlags/CELL1', any, read_bool(base_register=1005, bit=46)), BatterySignal('/WarningFlags/bit47WarningDummy', any, read_bool(base_register=1005, bit=47)), BatterySignal('/NumberOfAlarmFlags', sum, count_bits(base_register=1009, nb_of_registers=3, nb_of_bits=47)), BatterySignal('/AlarmFlags/Tam', any, read_bool(base_register=1009, bit=0)), BatterySignal('/AlarmFlags/TaM2', any, read_bool(base_register=1009, bit=2)), BatterySignal('/AlarmFlags/Tbm', any, read_bool(base_register=1009, bit=3)), BatterySignal('/AlarmFlags/TbM2', any, read_bool(base_register=1009, bit=5)), BatterySignal('/AlarmFlags/VBm2', any, read_bool(base_register=1009, bit=7)), BatterySignal('/AlarmFlags/IDM2', any, read_bool(base_register=1009, bit=11)), BatterySignal('/AlarmFlags/ISOB', any, read_bool(base_register=1009, bit=12)), BatterySignal('/AlarmFlags/MSWE', any, read_bool(base_register=1009, bit=13)), BatterySignal('/AlarmFlags/FUSE', any, read_bool(base_register=1009, bit=14)), BatterySignal('/AlarmFlags/HTRE', any, read_bool(base_register=1009, bit=15)), BatterySignal('/AlarmFlags/TCPE', any, read_bool(base_register=1009, bit=16)), BatterySignal('/AlarmFlags/STRE', any, read_bool(base_register=1009, bit=17)), BatterySignal('/AlarmFlags/CME', any, read_bool(base_register=1009, bit=18)), BatterySignal('/AlarmFlags/HWFL', any, read_bool(base_register=1009, bit=19)), BatterySignal('/AlarmFlags/HWEM', any, read_bool(base_register=1009, bit=20)), BatterySignal('/AlarmFlags/ThM', any, read_bool(base_register=1009, bit=21)), BatterySignal('/AlarmFlags/vsm1', any, read_bool(base_register=1009, bit=22)), BatterySignal('/AlarmFlags/vsm2', any, read_bool(base_register=1009, bit=23)), BatterySignal('/AlarmFlags/vsM2', any, read_bool(base_register=1009, bit=25)), BatterySignal('/AlarmFlags/iCM2', any, read_bool(base_register=1009, bit=27)), BatterySignal('/AlarmFlags/iDM2', any, read_bool(base_register=1009, bit=29)), BatterySignal('/AlarmFlags/MID2', any, read_bool(base_register=1009, bit=31)), BatterySignal('/AlarmFlags/CCBF', any, read_bool(base_register=1009, bit=33)), BatterySignal('/AlarmFlags/AhFL', any, read_bool(base_register=1009, bit=34)), BatterySignal('/AlarmFlags/TbCM', any, read_bool(base_register=1009, bit=36)), BatterySignal('/AlarmFlags/BRNF', any, read_bool(base_register=1009, bit=37)), BatterySignal('/AlarmFlags/HTFS', any, read_bool(base_register=1009, bit=42)), BatterySignal('/AlarmFlags/DATA', any, read_bool(base_register=1009, bit=43)), BatterySignal('/AlarmFlags/CELL2', any, read_bool(base_register=1009, bit=45)), BatterySignal('/AlarmFlags/bit47AlarmDummy', any, read_bool(base_register=1009, bit=47)), BatterySignal('/LedStatus/Red', max, read_led_red), BatterySignal('/LedStatus/Blue', max, read_led_blue), BatterySignal('/LedStatus/Green', max, read_led_green), BatterySignal('/LedStatus/Amber', max, read_led_amber), BatterySignal('/IoStatus/MainSwitchClosed', any, read_bool(base_register=1013, bit=0)), BatterySignal('/IoStatus/AlarmOutActive', any, read_bool(base_register=1013, bit=1)), BatterySignal('/IoStatus/InternalFanActive', any, read_bool(base_register=1013, bit=2)), BatterySignal('/IoStatus/VoltMeasurementAllowed', any, read_bool(base_register=1013, bit=3)), BatterySignal('/IoStatus/AuxRelay', any, read_bool(base_register=1013, bit=4)), BatterySignal('/IoStatus/RemoteState', any, read_bool(base_register=1013, bit=5)), BatterySignal('/IoStatus/HeaterOn', any, read_bool(base_register=1013, bit=6)), BatterySignal('/IoStatus/EocReached', min, read_eoc_reached), BatterySignal('/IoStatus/BatteryCold', any, read_battery_cold), # see protocol doc page 7 BatterySignal('/Info/MaxDischargeCurrent', sum, lambda bs: bs.battery.i_max, unit='A'), BatterySignal('/Info/MaxChargeCurrent', sum, lambda bs: bs.battery.i_max, unit='A'), BatterySignal('/Info/MaxChargeVoltage', min, lambda bs: bs.battery.v_max, unit='V'), BatterySignal('/Info/MinDischargeVoltage', max, lambda bs: bs.battery.v_min, unit='V'), BatterySignal('/Info/BatteryLowVoltage' , max, lambda bs: bs.battery.v_min-2, unit='V'), BatterySignal('/Info/NumberOfStrings', sum, lambda bs: bs.battery.n_strings), BatterySignal('/Info/MaxChargePower', sum, calc_max_charge_power), BatterySignal('/Info/MaxDischargePower', sum, calc_max_discharge_power), BatterySignal('/FirmwareVersion', comma_separated, lambda bs: bs.battery.firmware_version), BatterySignal('/HardwareVersion', comma_separated, lambda bs: bs.battery.hardware_version), BatterySignal('/BmsVersion', comma_separated, lambda bs: bs.battery.bms_version) ]