Merge remote-tracking branch 'origin/main'

This commit is contained in:
atef 2024-08-08 10:18:31 +02:00
commit 45610f7b19
6 changed files with 90 additions and 108 deletions

View File

@ -60,11 +60,11 @@ public partial class Battery250UpRecord
public readonly struct AuxBitInformation_ public readonly struct AuxBitInformation_
{ {
public Boolean DischargeEnabled => ((Self._IoStates >> 0) & 1) == 1; public Boolean DischargeEnabled => ((Self._AuxBitInformation >> 0) & 1) == 1;
public Boolean ChargeEnabled => ((Self._IoStates >> 1) & 1) == 1; public Boolean ChargeEnabled => ((Self._AuxBitInformation >> 1) & 1) == 1;
public Boolean WarmUpActive => ((Self._IoStates >> 2) & 1) == 1; public Boolean WarmUpActive => ((Self._AuxBitInformation >> 2) & 1) == 1;
public Boolean TOCRequested => ((Self._IoStates >> 3) & 1) == 1; public Boolean TOCRequested => ((Self._AuxBitInformation >> 3) & 1) == 1;
public Boolean EOCReached => ((Self._IoStates >> 4) & 1) == 1; public Boolean EOCReached => ((Self._AuxBitInformation >> 4) & 1) == 1;
internal AuxBitInformation_(Battery250UpRecord self) => Self = self; internal AuxBitInformation_(Battery250UpRecord self) => Self = self;
private Battery250UpRecord Self { get; } private Battery250UpRecord Self { get; }
@ -134,9 +134,6 @@ public partial class Battery250UpRecord
if (HasBit(7) ) yield return "VBm2 : Bus voltage too low"; if (HasBit(7) ) yield return "VBm2 : Bus voltage too low";
if (HasBit(9) ) yield return "VBM2 : Bus voltage too high"; if (HasBit(9) ) yield return "VBM2 : Bus voltage too high";
if (HasBit(11)) yield return "IDM2 : Discharge current too high"; if (HasBit(11)) yield return "IDM2 : Discharge current too high";
if (HasBit(12)) yield return "ISOB : Electrical insulation failure";
if (HasBit(13)) yield return "MSWE : Main switch failure";
if (HasBit(14)) yield return "FUSE : Main fuse blown";
if (HasBit(15)) yield return "HTRE : Battery failed to warm up"; if (HasBit(15)) yield return "HTRE : Battery failed to warm up";
if (HasBit(16)) yield return "TCPE : Temperature sensor failure"; if (HasBit(16)) yield return "TCPE : Temperature sensor failure";
if (HasBit(17)) yield return "STRE : Voltage measurement circuit fails"; if (HasBit(17)) yield return "STRE : Voltage measurement circuit fails";
@ -149,11 +146,11 @@ public partial class Battery250UpRecord
if (HasBit(27)) yield return "iCM2 : Charge current too high"; if (HasBit(27)) yield return "iCM2 : Charge current too high";
if (HasBit(29)) yield return "iDM2 : Discharge current too high"; if (HasBit(29)) yield return "iDM2 : Discharge current too high";
if (HasBit(31)) yield return "MID2 : String voltage unbalance too high"; if (HasBit(31)) yield return "MID2 : String voltage unbalance too high";
if (HasBit(33)) yield return "CCBF : Charger Circuit not working";
if (HasBit(42)) yield return "HTFS : Heater Fuse Blown"; if (HasBit(42)) yield return "HTFS : Heater Fuse Blown";
if (HasBit(43)) yield return "DATA : Parameters out of range"; if (HasBit(43)) yield return "DATA : Parameters out of range";
if (HasBit(45)) yield return "LMPA : Unbalance string voltages"; if (HasBit(45)) yield return "LMPA : Unbalance string voltages";
if (HasBit(46)) yield return "HEBT : Loss of heartbeat"; if (HasBit(54)) yield return "ULAL : The safety microprocessor intervened";
if (HasBit(48)) yield return "CURM : Battery charge requested after EDCH";
} }
[SuppressMessage("ReSharper", "StringLiteralTypo")] [SuppressMessage("ReSharper", "StringLiteralTypo")]
@ -161,24 +158,12 @@ public partial class Battery250UpRecord
{ {
Boolean HasBit(Int16 bit) => (_WarningFlags & 1uL << bit) > 0; Boolean HasBit(Int16 bit) => (_WarningFlags & 1uL << bit) > 0;
if (HasBit(1) ) yield return "TaM1: BMS temperature high";
if (HasBit(4) ) yield return "TbM1: Battery temperature high"; if (HasBit(4) ) yield return "TbM1: Battery temperature high";
if (HasBit(6) ) yield return "VBm1: Bus voltage low";
if (HasBit(8) ) yield return "VBM1: Bus voltage high";
if (HasBit(10)) yield return "IDM1: Discharge current high";
if (HasBit(22)) yield return "vsm1: String voltage too low"; if (HasBit(22)) yield return "vsm1: String voltage too low";
if (HasBit(24)) yield return "vsM1: String voltage high"; if (HasBit(28)) yield return "iDM1: String discharge current high";
if (HasBit(26)) yield return "iCM1: Charge current high";
if (HasBit(28)) yield return "iDM1: Discharge current high";
if (HasBit(30)) yield return "MID1: String voltages unbalanced"; if (HasBit(30)) yield return "MID1: String voltages unbalanced";
if (HasBit(32)) yield return "BLPW: Not enough charging power on bus"; if (HasBit(32)) yield return "BLPW: Charging power is not available. Bus voltage low";
if (HasBit(33)) yield return "CCBF : Internal charger hardware failure"; if (HasBit(49)) yield return "TOCW : TOC is less than 100% from 14 days";
if (HasBit(35)) yield return "Ah_W: String SOC low";
if (HasBit(38)) yield return "MPMM: Midpoint wiring problem";
if (HasBit(40)) yield return "TCdi: Temperature difference between strings high";
if (HasBit(44)) yield return "LMPW : String voltages unbalance warning";
if (HasBit(47)) yield return "TOCW : Top of Charge requested";
if (HasBit(49)) yield return "BUSL : Bus lower than string";
} }

View File

@ -54,7 +54,11 @@ public partial class Battery250UpRecord
[InputRegister(1052)] private UInt16 _TimeToTocRequest; [InputRegister(1052)] private UInt16 _TimeToTocRequest;
//[InputRegister(1063)] private UInt16 _TotalBatteryCycle; [InputRegister(1063)] private UInt16 _TotalBatteryCycle;
[InputRegister(1064, Scale = 1, Offset = -1000)] private UInt16 _DC_Discharge_Current_Limit;
[InputRegister(1065, Scale = 1, Offset = -1000)] private UInt16 _DC_Charge_Current_Limit;
[InputRegister(1066, Scale = 0.1)] private UInt16 _Voltage_Charge_Setpoint;
[InputRegister(1067, Scale = 0.1)] private UInt16 _DC_Min_Voltage_Limit;
[InputRegister(1068)] private UInt16 _AuxBitInformation; [InputRegister(1068)] private UInt16 _AuxBitInformation;
private LedState ParseLed(LedColor led) => (LedState)((_LedStates >> (Int32)led) & 3); private LedState ParseLed(LedColor led) => (LedState)((_LedStates >> (Int32)led) & 3);

View File

@ -704,26 +704,24 @@ def update_state_from_dictionaries(current_warnings, current_alarms, node_number
changed_alarms = current_alarms changed_alarms = current_alarms
is_first_update = False is_first_update = False
else: else:
changed_alarms={} changed_alarms = {}
changed_warnings={} changed_warnings = {}
# calculate the diff in warnings and alarms
prev_alarm_value_list=list(previous_alarms.values())
alarm_keys=list(previous_alarms.keys())
for i, alarm in enumerate(current_alarms.values()): for key in current_alarms:
if alarm!=prev_alarm_value_list[i]: current_value = current_alarms[key]
changed_alarms[alarm_keys[i]]=True prev_value = previous_alarms.get(key, False) # Use False if the key doesn't exist
if current_value != prev_value:
changed_alarms[key] = True
else: else:
changed_alarms[alarm_keys[i]]=False changed_alarms[key] = False
prev_warning_value_list=list(previous_warnings.values()) for key in current_warnings:
warning_keys=list(previous_warnings.keys()) current_value = current_warnings[key]
prev_value = previous_warnings.get(key, False)
for i, warning in enumerate(current_warnings.values()): if current_value != prev_value:
if warning!=prev_warning_value_list[i]: changed_warnings[key] = True
changed_warnings[warning_keys[i]]=True
else: else:
changed_warnings[warning_keys[i]]=False changed_warnings[key] = False
status_message = { status_message = {
"InstallationId": INSTALLATION_ID, "InstallationId": INSTALLATION_ID,
@ -915,8 +913,8 @@ def create_batch_of_csv_files():
# sort csv files by creation time # sort csv files by creation time
csv_files.sort(key=lambda x: os.path.getctime(os.path.join(CSV_DIR, x))) csv_files.sort(key=lambda x: os.path.getctime(os.path.join(CSV_DIR, x)))
# keep the 30 MOST RECENT FILES # keep the 600 MOST RECENT FILES
recent_csv_files = csv_files[-30:] if len(csv_files) > 30 else csv_files recent_csv_files = csv_files[-600:] if len(csv_files) > 600 else csv_files
# get the name of the first csv file # get the name of the first csv file
if not csv_files: if not csv_files:
@ -1025,7 +1023,7 @@ def create_update_task(modbus, dbus, batteries, signals, csv_signals, main_loop)
ALLOW = True ALLOW = True
alive = update(modbus, batteries, dbus, signals, csv_signals) alive = update(modbus, batteries, dbus, signals, csv_signals)
elapsed_time = time.time() - start_time elapsed_time = time.time() - start_time
if elapsed_time >= 60: if elapsed_time >= 1200:
create_batch_of_csv_files() create_batch_of_csv_files()
start_time = time.time() start_time = time.time()
#alive = update_for_testing(modbus, batteries, dbus, signals, csv_signals) #alive = update_for_testing(modbus, batteries, dbus, signals, csv_signals)
@ -1067,11 +1065,6 @@ def manage_csv_files(directory_path, max_files=20):
file_to_delete = os.path.join(directory_path, csv_files.pop(0)) file_to_delete = os.path.join(directory_path, csv_files.pop(0))
os.remove(file_to_delete) os.remove(file_to_delete)
def serialize_for_csv(value):
if isinstance(value, (dict, list, tuple)):
return json.dumps(value, ensure_ascii=False)
return str(value)
def insert_id(path, id_number): def insert_id(path, id_number):
parts = path.split("/") parts = path.split("/")
insert_position = parts.index("Devices") + 1 insert_position = parts.index("Devices") + 1
@ -1089,22 +1082,23 @@ def create_csv_files(signals, statuses, node_numbers, alarms_number_list, warnin
csv_filename = f"{timestamp}.csv" csv_filename = f"{timestamp}.csv"
csv_path = os.path.join(CSV_DIR, csv_filename) csv_path = os.path.join(CSV_DIR, csv_filename)
# Append values to the CSV file # Append values to the CSV file
with open(csv_path, 'a', newline='') as csvfile: if not os.path.exists(csv_path):
csv_writer = csv.writer(csvfile, delimiter=';') with open(csv_path, 'a', newline='') as csvfile:
# Add a special row for the nodes configuration csv_writer = csv.writer(csvfile, delimiter=';')
nodes_config_path = "/Config/Devices/BatteryNodes" # Add a special row for the nodes configuration
nodes_list = ",".join(str(node) for node in node_numbers) nodes_config_path = "/Config/Devices/BatteryNodes"
config_row = [nodes_config_path, nodes_list, ""] nodes_list = ",".join(str(node) for node in node_numbers)
csv_writer.writerow(config_row) config_row = [nodes_config_path, nodes_list, ""]
# Iterate over each node and signal to create rows in the new format csv_writer.writerow(config_row)
for i, node in enumerate(node_numbers): # Iterate over each node and signal to create rows in the new format
csv_writer.writerow([f"/Battery/Devices/{str(i+1)}/Alarms", alarms_number_list[i], ""]) for i, node in enumerate(node_numbers):
csv_writer.writerow([f"/Battery/Devices/{str(i+1)}/Warnings", warnings_number_list[i], ""]) csv_writer.writerow([f"/Battery/Devices/{str(i+1)}/Alarms", alarms_number_list[i], ""])
for s in signals: csv_writer.writerow([f"/Battery/Devices/{str(i+1)}/Warnings", warnings_number_list[i], ""])
signal_name = insert_id(s.name, i+1) for s in signals:
value = s.get_value(statuses[i]) signal_name = insert_id(s.name, i+1)
row_values = [signal_name, value, s.get_text] value = s.get_value(statuses[i])
csv_writer.writerow(row_values) row_values = [signal_name, value, s.get_text]
csv_writer.writerow(row_values)
def main(argv): def main(argv):
# type: (list[str]) -> () # type: (list[str]) -> ()

View File

@ -2234,7 +2234,7 @@
"repair": false, "repair": false,
"outputs": 2, "outputs": 2,
"x": 1630, "x": 1630,
"y": 420, "y": 440,
"wires": [ "wires": [
[ [
"9b93fb5a4717969a" "9b93fb5a4717969a"
@ -2332,7 +2332,7 @@
"type": "function", "type": "function",
"z": "58aeeaac02a3a4c7", "z": "58aeeaac02a3a4c7",
"name": "get_total_number_of_limb_strings", "name": "get_total_number_of_limb_strings",
"func": "let total_num_limb_string=0;\nlimb_string_list = msg.payload.limb_string_list;\nmain_switch_state_list = msg.payload.main_switch_state;\nbattery_health = [];\nif (limb_string_list == null){\n msg.payload.battery_health = \"BMS connection lost!\";\n msg.payload.num_limb_string = -1\n return msg;\n}\n\nfor (let i = 0; i < limb_string_list.length; i++) {\n num_limb_string = limb_string_list[i][1][0];\n \n if(num_limb_string>1){\n total_num_limb_string = -1;//if there are more than 1 limb string in a battery, should give alarm to stop use this battery\n battery_health.push(\"Battery \"+(i+2)+\" has more than 1 limb string!\");\n }\n \n if(num_limb_string<=1){\n total_num_limb_string+=num_limb_string;\n battery_health.push(\"Battery \"+(i+2)+\" has \" + num_limb_string+ \" limb strings.\");\n }\n}\nmsg.payload.num_limb_string = total_num_limb_string;\nmsg.payload.battery_health = battery_health;\n\nreturn msg;", "func": "let total_num_limb_string=0;\nlimb_string_list = msg.payload.limb_string_list;\nmain_switch_state_list = msg.payload.main_switch_state;\nbattery_health = [];\nflag = 1;\nif (limb_string_list == null){\n msg.payload.battery_health = \"BMS connection lost!\";\n msg.payload.num_limb_string = -1\n return msg;\n}\n\nfor (let i = 0; i < limb_string_list.length; i++) {\n num_limb_string = limb_string_list[i][1][0];\n \n if(num_limb_string>1){\n flag = -1;\n }\n \n if(num_limb_string<=1){\n total_num_limb_string+=num_limb_string;\n }\n \n battery_health.push(\"Battery \"+(i+2)+\" has \" + num_limb_string+ \" limb strings.\");\n}\n\nconst updateNumLimbString = (total, flag) => flag === 1 ? total : -1;\nmsg.payload.num_limb_string = updateNumLimbString(total_num_limb_string, flag);\nmsg.payload.battery_health = battery_health;\n\nreturn msg;",
"outputs": 1, "outputs": 1,
"timeout": 0, "timeout": 0,
"noerr": 0, "noerr": 0,
@ -2343,9 +2343,9 @@
"y": 420, "y": 420,
"wires": [ "wires": [
[ [
"fe54eaf69a7fe10f",
"933a8eed519beb7a", "933a8eed519beb7a",
"fcb98057e9e5a076" "fcb98057e9e5a076",
"9b93fb5a4717969a"
] ]
] ]
}, },
@ -2604,7 +2604,7 @@
"rules": [ "rules": [
{ {
"t": "set", "t": "set",
"p": "payload", "p": "payload.controller_info",
"pt": "msg", "pt": "msg",
"to": "Please replace battery first!", "to": "Please replace battery first!",
"tot": "str" "tot": "str"

View File

@ -54,6 +54,6 @@ INNOVENERGY_PROTOCOL_VERSION = '48TL200V3'
# S3 Credentials # S3 Credentials
S3BUCKET = "91-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e" S3BUCKET = "114-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"
S3KEY = "EXOe6dce12288f11a676c2025a1" S3KEY = "EXO5b9198dc11544f42b44e1180"
S3SECRET = "xpqM4Eh0Gg1HaYVkzlR9X6PwYa-QNb-mVk0XUkwW3cc" S3SECRET = "ga-mD3SYZMJUfjksmXPKAHQhVkxPZYv57jC1oD_mkC0"

View File

@ -151,7 +151,7 @@ prev_status = 0
def update_state_from_dictionaries(current_warnings, current_alarms, node_numbers): def update_state_from_dictionaries(current_warnings, current_alarms, node_numbers):
global previous_warnings, previous_alarms, INSTALLATION_ID, PRODUCT_ID, is_first_update, channel, prev_status global previous_warnings, previous_alarms, INSTALLATION_ID, PRODUCT_ID, is_first_update, channel, prev_status
if is_first_update: if is_first_update:
changed_warnings = current_warnings changed_warnings = current_warnings
changed_alarms = current_alarms changed_alarms = current_alarms
@ -159,25 +159,23 @@ def update_state_from_dictionaries(current_warnings, current_alarms, node_number
else: else:
changed_alarms = {} changed_alarms = {}
changed_warnings = {} changed_warnings = {}
# calculate the diff in warnings and alarms
prev_alarm_value_list = list(previous_alarms.values())
alarm_keys = list(previous_alarms.keys())
for i, alarm in enumerate(current_alarms.values()): for key in current_alarms:
if alarm != prev_alarm_value_list[i]: current_value = current_alarms[key]
changed_alarms[alarm_keys[i]] = True prev_value = previous_alarms.get(key, False) # Use False if the key doesn't exist
if current_value != prev_value:
changed_alarms[key] = True
else: else:
changed_alarms[alarm_keys[i]] = False changed_alarms[key] = False
prev_warning_value_list=list(previous_warnings.values()) for key in current_warnings:
warning_keys=list(previous_warnings.keys()) current_value = current_warnings[key]
prev_value = previous_warnings.get(key, False)
for i, warning in enumerate(current_warnings.values()): if current_value != prev_value:
if warning!=prev_warning_value_list[i]: changed_warnings[key] = True
changed_warnings[warning_keys[i]]=True
else: else:
changed_warnings[warning_keys[i]]=False changed_warnings[key] = False
status_message = { status_message = {
"InstallationId": INSTALLATION_ID, "InstallationId": INSTALLATION_ID,
"Product": PRODUCT_ID, "Product": PRODUCT_ID,
@ -552,7 +550,7 @@ def create_update_task(modbus, service, batteries):
create_csv_files(csv_signals, statuses, node_numbers, alarms_number_list, warnings_number_list) create_csv_files(csv_signals, statuses, node_numbers, alarms_number_list, warnings_number_list)
num_files_in_csv_dir = count_files_in_folder(CSV_DIR) num_files_in_csv_dir = count_files_in_folder(CSV_DIR)
if elapsed_time >= 60: if elapsed_time >= 1200:
create_batch_of_csv_files() create_batch_of_csv_files()
start_time = time.time() start_time = time.time()
@ -590,8 +588,8 @@ def create_batch_of_csv_files():
# sort csv files by creation time # sort csv files by creation time
csv_files.sort(key=lambda x: os.path.getctime(os.path.join(CSV_DIR, x))) csv_files.sort(key=lambda x: os.path.getctime(os.path.join(CSV_DIR, x)))
# keep the 30 MOST RECENT FILES # keep the 600 MOST RECENT FILES
recent_csv_files = csv_files[-30:] if len(csv_files) > 30 else csv_files recent_csv_files = csv_files[-600:] if len(csv_files) > 600 else csv_files
# get the name of the first csv file # get the name of the first csv file
if not csv_files: if not csv_files:
@ -685,26 +683,27 @@ def create_batch_of_csv_files():
def create_csv_files(signals, statuses, node_numbers, alarms_number_list, warnings_number_list): def create_csv_files(signals, statuses, node_numbers, alarms_number_list, warnings_number_list):
timestamp = int(time.time()) timestamp = int(time.time())
if timestamp % 2 != 0: if timestamp % 2 != 0:
timestamp-=1 timestamp -= 1
if not os.path.exists(CSV_DIR): if not os.path.exists(CSV_DIR):
os.makedirs(CSV_DIR) os.makedirs(CSV_DIR)
csv_filename = "{}.csv".format(timestamp) csv_filename = "{}.csv".format(timestamp)
csv_path = os.path.join(CSV_DIR, csv_filename) csv_path = os.path.join(CSV_DIR, csv_filename)
with open(csv_path, 'ab') as csvfile: if not os.path.exists(csv_path):
csv_writer = csv.writer(csvfile, delimiter=';') with open(csv_path, 'ab') as csvfile:
nodes_config_path = "/Config/Devices/BatteryNodes" csv_writer = csv.writer(csvfile, delimiter=';')
nodes_list = ",".join(str(node) for node in node_numbers) nodes_config_path = "/Config/Devices/BatteryNodes"
config_row = [nodes_config_path, nodes_list, ""] nodes_list = ",".join(str(node) for node in node_numbers)
csv_writer.writerow(config_row) config_row = [nodes_config_path, nodes_list, ""]
for i, node in enumerate(node_numbers): csv_writer.writerow(config_row)
csv_writer.writerow(["/Battery/Devices/{}/Alarms".format(str(i+1)), alarms_number_list[i], ""]) for i, node in enumerate(node_numbers):
csv_writer.writerow(["/Battery/Devices/{}/Warnings".format(str(i+1)), warnings_number_list[i], ""]) csv_writer.writerow(["/Battery/Devices/{}/Alarms".format(str(i+1)), alarms_number_list[i], ""])
for s in signals: csv_writer.writerow(["/Battery/Devices/{}/Warnings".format(str(i+1)), warnings_number_list[i], ""])
signal_name = insert_id(s.name, i+1) for s in signals:
value = s.get_value(statuses[i]) signal_name = insert_id(s.name, i+1)
row_values = [signal_name, value, s.get_text] value = s.get_value(statuses[i])
csv_writer.writerow(row_values) row_values = [signal_name, value, s.get_text]
csv_writer.writerow(row_values)
def create_watchdog_task(main_loop): def create_watchdog_task(main_loop):
# type: (DBusGMainLoop) -> Callable[[],bool] # type: (DBusGMainLoop) -> Callable[[],bool]