diff --git a/S3ExtractingTool/ExtractS3README.txt b/S3ExtractingTool/ExtractS3README.txt new file mode 100644 index 000000000..c4cd1cb04 --- /dev/null +++ b/S3ExtractingTool/ExtractS3README.txt @@ -0,0 +1,90 @@ +This README file provides a comprehensive guide to utilizing a Python script for interacting with S3 storage, +specifically designed for downloading and processing data files based on a specified time range and key parameters. +The script requires Python3 installed on your system and makes use of the s3cmd tool for accessing data in cloud storage. +It also illustrates the process of configuring s3cmd by creating a .s3cfg file with your access credentials. Nice + + +############ Create the .s3cfg file in home directory ################ + +nano .s3cfg + +Copy this lines inside the file. + +[default] +host_base = sos-ch-dk-2.exo.io +host_bucket = %(bucket)s.sos-ch-dk-2.exo.io +access_key = EXO4d838d1360ba9fb7d51648b0 +secret_key = _bmrp6ewWAvNwdAQoeJuC-9y02Lsx7NV6zD-WjljzCU +use_https = True + + +############ S3cmd instalation ################ + +Please install s3cmd for retrieving data from our Cloud storage. + +sudo apt install s3cmd + +############ Python3 instalation ################ + +To check if you have already have python3, run this command + + python3 --version + + +To install you can use this command: + +1) sudo apt update + +2) sudo apt install python3 + +3) python3 --version (to check if pyhton3 installed correctly) + + +############ Run extractS3data.py ################ + +usage: extractRange.py [-h] --key KEY --bucket-number BUCKET_NUMBER start_timestamp end_timestamp + +KEY: the key can be a one word or a path + + for example: /DcDc/Devices/2/Status/Dc/Battery/voltage ==> this will provide us a Dc battery Voltage of the DcDc device 2. + example : Dc/Battery/voltage ==> This will provide all DcDc Device voltage (including the avg voltage of all DcDc device) + example : voltage ==> This will provide all voltage of all devices in the Salimax + +BUCKET_NUMBER: This a number of bucket name for the instalation + +start_timestamp end_timestamp: this must be a correct timestamp of 10 digits. +The start_timestamp must be smaller than the end_timestamp. + +PS: The data will be downloaded to a folder named S3cmdData_{Bucket_Number}. If this folder does not exist, it will be created. +If the folder exist, it will try to download data between the requested timestamps if they files are not already existing. + +Example command: + +python3 extractS3data.py 1749062721 1749106001 --keys GridMeter/Ac/Power/Active --bucket-number 12 --product_name=SodistoreMax + + +################################ EXTENDED FEATURES FOR MORE ADVANCED USAGE ################################ + +1) Multiple Keys Support: + +The script supports the extraction of data using multiple keys. Users can specify one or multiple keys separated by commas with the --keys parameter. +This feature allows for more granular data extraction, catering to diverse data analysis requirements. For example, users can extract data for different +metrics or parameters from the same or different CSV files within the specified range. + +3) Dynamic Header Generation: + +The script dynamically generates headers for the output CSV file based on the keys provided. This ensures that the output file accurately reflects the +extracted data, providing a clear and understandable format for subsequent analysis. The headers correspond to the keys used for data extraction, making +it easy to identify and analyze the extracted data. + +4)Advanced Data Processing Capabilities: + +Booleans as Numbers: The --booleans_as_numbers flag allows users to convert boolean values (True/False) into numeric representations (1/0). This feature +is particularly useful for analytical tasks that require numerical data processing. + +Example Command: + +python3 extractS3data.py 1749062721 1749106001 --keys AcDc/SystemControl/ResetAlarmsAndWarnings,AcDc/Devices/1/Status/Ac/L1/Voltage --bucket-number 12 --product_name=SodistoreMax + +This command extracts data for AcDc/SystemControl/ResetAlarmsAndWarnings and AcDc/Devices/1/Status/Ac/L1/Voltage keys from bucket number 12, between the specified timestamps, with boolean values converted to numbers. + diff --git a/S3ExtractingTool/example_file.json b/S3ExtractingTool/example_file.json new file mode 100644 index 000000000..ee447e9f8 --- /dev/null +++ b/S3ExtractingTool/example_file.json @@ -0,0 +1,937 @@ +{ + "AcDc": { + "SystemControl": { + "Alarms": "", + "CommunicationTimeout": "00:00:20", + "SystemConfig": "AcDcAndDcDc", + "ResetAlarmsAndWarnings": "True", + "TargetSlave": 0.0, + "UseSlaveIdForAddressing": "True", + "ReferenceFrame": "Consumer", + "SlaveErrorHandling": "Relaxed", + "SubSlaveErrorHandling": "Off", + "PowerSetPointActivation": "Immediate", + "PowerSetPointTrigger": "Wait", + "DeviceState": "Operation", + "NumberOfConnectedSlaves": 2.0, + "NumberOfConnectedSubSlaves": 0.0, + "Warnings": "" + }, + "Devices": { + "1": { + "Status": { + "Ac": { + "L1": { + "Voltage": 238.8, + "Current": 1.45, + "Phi": 3.09, + "Power": { + "Active": -345.73, + "Reactive": 19.21, + "Apparent": 346.26 + } + }, + "L2": { + "Voltage": 239.2, + "Current": 1.53, + "Phi": -3.09, + "Power": { + "Active": -365.41, + "Reactive": -20.3, + "Apparent": 365.98 + } + }, + "L3": { + "Voltage": 239.9, + "Current": 1.31, + "Phi": 3.14, + "Power": { + "Active": -314.26, + "Reactive": 1.97, + "Apparent": 314.27 + } + }, + "Frequency": 50.0, + "Power": { + "Active": -1025.4, + "Reactive": 0.88, + "Apparent": 1025.4 + } + }, + "PowerLimitedBy": "DcLink", + "InverterState": { + "Current": "AcConnected", + "OnLastAlarm": "Alarm" + }, + "ActiveGridType": "GridTied400V50Hz", + "DcVoltages": { + "Intern": { + "DcUpperHalf": 389.0, + "DcLowerHalf": 390.0, + "NToPe": 6553.5 + }, + "Extern": { + "UpperHalf": 389.0, + "LowerHalf": 390.0, + "NToPe": 0.7 + }, + "Active": { + "ActiveUpperVoltage": 780.0, + "ActiveLowerVoltage": 720.0, + "ActiveRefVoltage": 750.0 + } + }, + "Temperature": { + "InletAir": 23.9, + "IgbtL1": 49.7, + "IgbtL2": 47.6, + "IgbtL3": 51.8, + "IgbtBalancer": 50.6 + }, + "OverloadCapacity": { + "L1": 100.0, + "L2": 100.0, + "L3": 100.0 + }, + "Nominal": { + "AcFrequency": 50.0, + "AcVoltage": 400.0, + "Power": 25000.0 + }, + "Alarms": "", + "Warnings": "" + }, + "Control": { + "Ac": { + "Power": { + "L1": { + "Active": 3.81, + "Reactive": 0.0, + "Apparent": 3.81 + }, + "L2": { + "Active": 3.81, + "Reactive": 0.0, + "Apparent": 3.81 + }, + "L3": { + "Active": 3.81, + "Reactive": 0.0, + "Apparent": 3.81 + } + }, + "PhaseControl": "Asymmetric", + "GridType": "GridTied400V50Hz", + "IslandMode": { + "FrequencyOffset": 0.0, + "VoltageAdjustmentFactor": 100.0 + } + }, + "Dc": { + "ReferenceVoltage": 750.0, + "MinVoltage": 720.0, + "MaxVoltage": 780.0, + "PrechargeConfig": "PrechargeDcWithInternal" + }, + "PowerStageEnable": "True", + "ResetAlarmsAndWarnings": "False" + } + }, + "2": { + "Status": { + "Ac": { + "L1": { + "Voltage": 239.0, + "Current": 0.74, + "Phi": -0.1, + "Power": { + "Active": 175.89, + "Reactive": -18.51, + "Apparent": 176.86 + } + }, + "L2": { + "Voltage": 239.5, + "Current": 0.62, + "Phi": -0.77, + "Power": { + "Active": 106.89, + "Reactive": -103.07, + "Apparent": 148.49 + } + }, + "L3": { + "Voltage": 239.8, + "Current": 0.47, + "Phi": -0.07, + "Power": { + "Active": 112.42, + "Reactive": -8.03, + "Apparent": 112.71 + } + }, + "Frequency": 50.01, + "Power": { + "Active": 395.2, + "Reactive": -129.62, + "Apparent": 415.91 + } + }, + "PowerLimitedBy": "Nothing", + "InverterState": { + "Current": "AcConnected", + "OnLastAlarm": "Idle" + }, + "ActiveGridType": "GridTied400V50Hz", + "DcVoltages": { + "Intern": { + "DcUpperHalf": 388.0, + "DcLowerHalf": 389.0, + "NToPe": 0.1 + }, + "Extern": { + "UpperHalf": 388.0, + "LowerHalf": 388.0, + "NToPe": 6553.4 + }, + "Active": { + "ActiveUpperVoltage": 780.0, + "ActiveLowerVoltage": 720.0, + "ActiveRefVoltage": 750.0 + } + }, + "Temperature": { + "InletAir": 26.0, + "IgbtL1": 49.0, + "IgbtL2": 47.0, + "IgbtL3": 50.0, + "IgbtBalancer": 48.0 + }, + "OverloadCapacity": { + "L1": 100.0, + "L2": 100.0, + "L3": 100.0 + }, + "Nominal": { + "AcFrequency": 50.0, + "AcVoltage": 400.0, + "Power": 25000.0 + }, + "Alarms": "", + "Warnings": "" + }, + "Control": { + "Ac": { + "Power": { + "L1": { + "Active": 3.81, + "Reactive": 0.0, + "Apparent": 3.81 + }, + "L2": { + "Active": 3.81, + "Reactive": 0.0, + "Apparent": 3.81 + }, + "L3": { + "Active": 3.81, + "Reactive": 0.0, + "Apparent": 3.81 + } + }, + "PhaseControl": "Asymmetric", + "GridType": "GridTied400V50Hz", + "IslandMode": { + "FrequencyOffset": 0.0, + "VoltageAdjustmentFactor": 100.0 + } + }, + "Dc": { + "ReferenceVoltage": 750.0, + "MinVoltage": 720.0, + "MaxVoltage": 780.0, + "PrechargeConfig": "PrechargeDcWithInternal" + }, + "PowerStageEnable": "True", + "ResetAlarmsAndWarnings": "False" + } + } + }, + "Alarms": "", + "Warnings": "", + "Ac": { + "L1": { + "Voltage": 238.9, + "Current": 2.19, + "Phi": 3.14, + "Power": { + "Active": -523.19, + "Reactive": 2.13, + "Apparent": 523.19 + } + }, + "L2": { + "Voltage": 239.35, + "Current": 2.15, + "Phi": -2.7, + "Power": { + "Active": -464.43, + "Reactive": -221.64, + "Apparent": 514.6 + } + }, + "L3": { + "Voltage": 239.85, + "Current": 1.78, + "Phi": -3.11, + "Power": { + "Active": -426.74, + "Reactive": -12.81, + "Apparent": 426.93 + } + }, + "Frequency": 50.0, + "Power": { + "Active": -1414.36, + "Reactive": -232.31, + "Apparent": 1433.31 + } + }, + "Dc": { + "Voltage": 777.5, + "Current": -1.82, + "Power": -1414.36 + } + }, + "DcDc": { + "Dc": { + "Link": { + "Voltage": 775.5, + "Current": 0.0, + "Power": 0.0 + }, + "Battery": { + "Voltage": 61.2, + "Current": 0.0, + "Power": 0.0 + } + }, + "SystemControl": { + "Alarms": "", + "CommunicationTimeout": "00:00:20", + "SystemConfig": "DcDcOnly", + "ResetAlarmsAndWarnings": "True", + "TargetSlave": 2.0, + "UseSlaveIdForAddressing": "True", + "ReferenceFrame": "Producer", + "SlaveErrorHandling": "Relaxed", + "SubSlaveErrorHandling": "Off", + "PowerSetPointActivation": "Immediate", + "PowerSetPointTrigger": "Wait", + "DeviceState": "Operation", + "NumberOfConnectedSlaves": 2.0, + "NumberOfConnectedSubSlaves": 0.0, + "Warnings": "" + }, + "Devices": { + "1": { + "Status": { + "Dc": { + "Link": { + "Voltage": 775.0, + "Current": -0.0, + "Power": -0.0 + }, + "Battery": { + "Voltage": 61.2, + "Current": -0.0, + "Power": -0.0 + } + }, + "OverloadCapacity": 0.0, + "Temperature": { + "InletAir": 25.0, + "HighVoltageModule": 30.0, + "LowVoltageModule": 28.0 + }, + "PowerLimitedBy": "MaxChargeCurrent", + "Alarms": "", + "Warnings": "" + }, + "Control": { + "Vcc": { + "EndPointCurrent": 50.0, + "EndPointVoltage": 50.0, + "StartPointCurrent": 5.0 + }, + "VoltageLimits": { + "MinBatteryVoltageAlarm": 0.0, + "MaxBatteryVoltageAlarm": 64.0, + "MinBatteryVoltage": 32.0, + "MaxBatteryVoltage": 63.0 + }, + "DroopControl": { + "ReferenceVoltage": 750.0, + "LowerVoltage": 20.0, + "UpperVoltage": 20.0, + "VoltageDeadband": 0.0 + }, + "CurrentControl": { + "CurrentSetpoint": 0.0, + "MaxCurrentChangePerMs": 100.0, + "MaxBatteryChargingCurrent": 0.0, + "MaxBatteryDischargingCurrent": 210.0 + }, + "MaxDcPower": 10000.0, + "ControlMode": "VoltageDroop", + "ResetAlarmsAndWarnings": "False", + "PowerStageEnable": "True" + } + }, + "2": { + "Status": { + "Dc": { + "Link": { + "Voltage": 776.0, + "Current": -0.0, + "Power": -0.0 + }, + "Battery": { + "Voltage": 61.2, + "Current": -0.0, + "Power": -0.0 + } + }, + "OverloadCapacity": 0.0, + "Temperature": { + "InletAir": 23.0, + "HighVoltageModule": 30.0, + "LowVoltageModule": 29.0 + }, + "PowerLimitedBy": "MaxChargeCurrent", + "Alarms": "", + "Warnings": "" + }, + "Control": { + "Vcc": { + "EndPointCurrent": 50.0, + "EndPointVoltage": 50.0, + "StartPointCurrent": 5.0 + }, + "VoltageLimits": { + "MinBatteryVoltageAlarm": 0.0, + "MaxBatteryVoltageAlarm": 64.0, + "MinBatteryVoltage": 32.0, + "MaxBatteryVoltage": 63.0 + }, + "DroopControl": { + "ReferenceVoltage": 750.0, + "LowerVoltage": 20.0, + "UpperVoltage": 20.0, + "VoltageDeadband": 0.0 + }, + "CurrentControl": { + "CurrentSetpoint": 0.0, + "MaxCurrentChangePerMs": 100.0, + "MaxBatteryChargingCurrent": 0.0, + "MaxBatteryDischargingCurrent": 210.0 + }, + "MaxDcPower": 10000.0, + "ControlMode": "VoltageDroop", + "ResetAlarmsAndWarnings": "False", + "PowerStageEnable": "True" + } + } + }, + "Alarms": "", + "Warnings": "" + }, + "Battery": { + "Current": -1.25, + "Voltage": 61.31, + "Soc": 99.85, + "Soh": 100.0, + "CurrentMinSoc": 99.3, + "TemperatureCell1": 23.35, + "Power": -76.62, + "LowestCellVoltage": 3.51, + "HighestCellVoltage": 3.89, + "MonomerHighVoltageAlarm": "True", + "MonomerLowVoltageAlarm": "False", + "ChargeSwitchState": "False", + "DischargeSwitchState": "True", + "AvailableDischBatteries": 6.0, + "AvailableChBatteries": 0.0, + "ChargeModeBatteries": 0.0, + "DischargeModeBatteries": 1.0, + "StandbyModeBatteries": 5.0, + "ShutDownModeBatteries": 0.0, + "Eoc": "False", + "Eod": "False", + "Devices": { + "1": { + "BatteryDeligreenDataRecord": { + "FwVersion": 2.0, + "BusVoltage": 61.33, + "BusCurrent": 0.0, + "Power": 0.0, + "TotalBatteryVoltage": 58.66, + "ResidualCapacity": 209.99, + "BatteryCapacity": 210.0, + "Soc": 99.9, + "RatedCapacity": 210.0, + "NumberOfCycles": 58.0, + "Soh": 100.0, + "CellVoltage": "3.742,3.564,3.887,3.644,3.599,3.574,3.601,3.555,3.528,3.58,3.823,3.753,3.693,3.771,3.569,3.778", + "TemperaturesList": { + "CellTemperature1": 22.2, + "CellTemperature2": 21.5, + "CellTemperature3": 21.5, + "CellTemperature4": 21.2, + "EnvironmentTemperature": 28.1, + "PowerTemperature": 23.0 + } + }, + "BatteryDeligreenAlarmRecord": { + "CellAlarmList": "Normal-no alarm,Normal-no alarm,Alarm that analog quantity reaches the upper limit,Normal-no alarm,Normal-no alarm,Normal-no alarm,Normal-no alarm,Normal-no alarm,Normal-no alarm,Normal-no alarm,Alarm that analog quantity reaches the upper limit,Normal-no alarm,Normal-no alarm,Alarm that analog quantity reaches the upper limit,Normal-no alarm,Alarm that analog quantity reaches the upper limit", + "CellTemperatureAlarm": "Normal-no alarm,Normal-no alarm,Normal-no alarm,Normal-no alarm", + "EnviTempAlarm": "Normal-no alarm", + "PowerTempAlarm": "Normal-no alarm", + "CurrentAlarm": "Normal-no alarm", + "TotalVoltageAlarm": "Normal-no alarm", + "AlarmEvent1": { + "VoltageSensorFault": "False", + "TemperatureSensorFault": "False", + "CurrentSensorFault": "False", + "KeySwitchFault": "False", + "CellVoltageDropoutFault": "False", + "ChargeSwitchFault": "False", + "DischargeSwitchFault": "False", + "CurrentLimitSwitchFault": "False" + }, + "AlarmEvent2": { + "MonomerHighVoltageAlarm": "False", + "MonomerOvervoltageProtection": "True", + "MonomerLowVoltageAlarm": "False", + "MonomerUnderVoltageProtection": "False", + "HighVoltageAlarmForTotalVoltage": "False", + "OvervoltageProtectionForTotalVoltage": "False", + "LowVoltageAlarmForTotalVoltage": "False", + "UnderVoltageProtectionForTotalVoltage": "False" + }, + "AlarmEvent3": { + "ChargeHighTemperatureAlarm": "False", + "ChargeOverTemperatureProtection": "False", + "ChargeLowTemperatureAlarm": "False", + "ChargeUnderTemperatureProtection": "False", + "DischargeHighTemperatureAlarm": "False", + "DischargeOverTemperatureProtection": "False", + "DischargeLowTemperatureAlarm": "False", + "DischargeUnderTemperatureProtection": "False" + }, + "AlarmEvent4": { + "EnvironmentHighTemperatureAlarm": "False", + "EnvironmentOverTemperatureProtection": "False", + "EnvironmentLowTemperatureAlarm": "False", + "EnvironmentUnderTemperatureProtection": "False", + "PowerOverTemperatureProtection": "False", + "PowerHighTemperatureAlarm": "False", + "CellLowTemperatureHeating": "False", + "ReservationBit": "False" + }, + "AlarmEvent5": { + "ChargeOverCurrentAlarm": "False", + "ChargeOverCurrentProtection": "False", + "DischargeOverCurrentAlarm": "False", + "DischargeOverCurrentProtection": "False", + "TransientOverCurrentProtection": "False", + "OutputShortCircuitProtection": "False", + "TransientOverCurrentLockout": "False", + "OutputShortCircuitLockout": "False" + }, + "AlarmEvent6": { + "ChargeHighVoltageProtection": "False", + "IntermittentRechargeWaiting": "True", + "ResidualCapacityAlarm": "False", + "ResidualCapacityProtection": "False", + "CellLowVoltageChargingProhibition": "False", + "OutputReversePolarityProtection": "False", + "OutputConnectionFault": "False", + "InsideBit": "False" + }, + "AlarmEvent7": { + "InsideBit1": "False", + "InsideBit2": "False", + "InsideBit3": "False", + "InsideBit4": "False", + "AutomaticChargingWaiting": "False", + "ManualChargingWaiting": "False", + "InsideBit5": "False", + "InsideBit6": "False" + }, + "AlarmEvent8": { + "EepStorageFault": "False", + "RtcError": "False", + "VoltageCalibrationNotPerformed": "False", + "CurrentCalibrationNotPerformed": "False", + "ZeroCalibrationNotPerformed": "False", + "InsideBit1": "False", + "InsideBit2": "False", + "InsideBit3": "False" + }, + "DisconnectionState1": { + "Cell01Disconnection": "False", + "Cell02Disconnection": "False", + "Cell03Disconnection": "False", + "Cell04Disconnection": "False", + "Cell05Disconnection": "False", + "Cell06Disconnection": "False", + "Cell07Disconnection": "False", + "Cell08Disconnection": "False" + }, + "DisconnectionState2": { + "Cell09Disconnection": "False", + "Cell10Disconnection": "False", + "Cell11Disconnection": "False", + "Cell12Disconnection": "False", + "Cell13Disconnection": "False", + "Cell14Disconnection": "False", + "Cell15Disconnection": "False", + "Cell16Disconnection": "False" + }, + "EquilibriumState1": { + "Cell01Equilibrium": "False", + "Cell02Equilibrium": "False", + "Cell03Equilibrium": "False", + "Cell04Equilibrium": "False", + "Cell05Equilibrium": "False", + "Cell06Equilibrium": "False", + "Cell07Equilibrium": "False", + "Cell08Equilibrium": "False" + }, + "EquilibriumState2": { + "Cell09Equilibrium": "False", + "Cell10Equilibrium": "False", + "Cell11Equilibrium": "False", + "Cell12Equilibrium": "False", + "Cell13Equilibrium": "False", + "Cell14Equilibrium": "False", + "Cell15Equilibrium": "False", + "Cell16Equilibrium": "False" + }, + "OnOffState": { + "DischargeSwitchState": "True", + "ChargeSwitchState": "False", + "CurrentLimitSwitchStat": "False", + "HeatingSwitchState": "False", + "ReservationBit1": "False", + "ReservationBit2": "False", + "ReservationBit3": "False", + "ReservationBit4": "False" + }, + "SystemState": { + "Discharge": "False", + "Charge": "False", + "FloatingCharge": "False", + "ReservationBit1": "False", + "Standby": "True", + "Shutdown": "False", + "ReservationBit2": "False", + "ReservationBit3": "False" + } + } + }, + "2" :{...} + } + }, + "GridMeter": { + "Ac": { + "L1": { + "Voltage": 239.0, + "Current": 0.36, + "Phi": -1.55, + "Power": { + "Active": 1.99, + "Reactive": -86.59, + "Apparent": 86.61 + } + }, + "L2": { + "Voltage": 239.3, + "Current": 0.32, + "Phi": -1.66, + "Power": { + "Active": -7.02, + "Reactive": -77.2, + "Apparent": 77.52 + } + }, + "L3": { + "Voltage": 239.5, + "Current": 1.14, + "Phi": -1.96, + "Power": { + "Active": -103.28, + "Reactive": -252.68, + "Apparent": 272.97 + } + }, + "Frequency": 50.0, + "Power": { + "Active": -108.3, + "Reactive": -416.47, + "Apparent": 430.32 + } + }, + "ActivePowerImportT1": 28349.0, + "ActivePowerExportT1": 61296.0, + "ActivePowerImportT2": 28349.0, + "ActivePowerExportT2": 61296.0, + "ActivePowerImportT3": 46886.0, + "ActivePowerExportT3": 21032.0, + "ActivePowerImportT4": 0.0, + "ActivePowerExportT4": 0.0 + }, + "LoadOnAcIsland": { + "Ac": { + "L1": { + "Voltage": 239.0, + "Current": 0.0, + "Phi": 0.0, + "Power": { + "Active": 0.0, + "Reactive": 0.0, + "Apparent": 0.0 + } + }, + "L2": { + "Voltage": 239.3, + "Current": 0.0, + "Phi": 0.0, + "Power": { + "Active": 0.0, + "Reactive": 0.0, + "Apparent": 0.0 + } + }, + "L3": { + "Voltage": 239.5, + "Current": 0.0, + "Phi": 0.0, + "Power": { + "Active": 0.0, + "Reactive": 0.0, + "Apparent": 0.0 + } + }, + "Frequency": 50.0, + "Power": { + "Active": 0.0, + "Reactive": 0.0, + "Apparent": 0.0 + } + }, + "ActivePowerImportT1": 58082.0, + "ActivePowerExportT1": 1569.0, + "ActivePowerImportT2": 58082.0, + "ActivePowerExportT2": 1569.0, + "ActivePowerImportT3": 123.0, + "ActivePowerExportT3": 1.0, + "ActivePowerImportT4": 0.0, + "ActivePowerExportT4": 0.0 + }, + "LoadOnAcGrid": { + "Power": { + "Active": 1306.05, + "Reactive": -184.15, + "Apparent": 1318.97 + } + }, + "AcGridToAcIsland": { + "Power": { + "Active": -1414.36, + "Reactive": -232.31, + "Apparent": 1433.31 + } + }, + "AcDcToDcLink": { + "Power": -1414.36 + }, + "LoadOnDc": { + "Power": -321.32 + }, + "Relays": { + "K1GridBusIsConnectedToGrid": "True", + "K2IslandBusIsConnectedToGridBus": "True", + "K3Inverter1IsConnectedToIslandBus": "True", + "K3Inverter2IsConnectedToIslandBus": "True", + "K3Inverter3IsConnectedToIslandBus": "True", + "K3Inverter4IsConnectedToIslandBus": "True", + "FiWarning": "False", + "FiError": "False", + "K2ConnectIslandBusToGridBus": "True" + }, + "PvOnDc": { + "Dc": { + "Voltage": 779.07, + "Current": 1.4, + "Power": 1093.03 + }, + "NbrOfStrings": 3.0, + "Strings": { + "1": { + "Voltage": 779.25, + "Current": 0.5, + "Power": 391.96 + }, + "2": { + "Voltage": 779.1, + "Current": 0.52, + "Power": 407.47 + }, + "3": { + "Voltage": 778.86, + "Current": 0.38, + "Power": 293.63 + } + }, + "DcWh": 137.11 + }, + "Config": { + "MinSoc": 10.0, + "CurtailP": 5.0, + "PvInstalledPower": 0.0, + "ForceCalibrationChargeState": "RepetitivelyEvery", + "DayAndTimeForRepetitiveCalibration": "05/29/2025 09:00:00", + "DayAndTimeForAdditionalCalibration": "05/23/2025 15:46:00", + "ForceCalibrationDischargeState": "RepetitivelyEvery", + "DownDayAndTimeForRepetitiveCalibration": "06/23/2025 19:00:00", + "DownDayAndTimeForAdditionalCalibration": "01/01/0001 00:00:00", + "DisplayIndividualBatteries": "False", + "PConstant": 0.5, + "GridSetPoint": 0.0, + "BatterySelfDischargePower": 200.0, + "HoldSocZone": 1.0, + "IslandMode": { + "AcDc": { + "MaxDcLinkVoltage": 930.0, + "MinDcLinkVoltage": 690.0, + "ReferenceDcLinkVoltage": 750.0 + }, + "DcDc": { + "LowerDcLinkVoltage": 50.0, + "ReferenceDcLinkVoltage": 750.0, + "UpperDcLinkVoltage": 50.0, + "MaxBatteryChargingCurrent": 210.0, + "MaxBatteryDischargingCurrent": 210.0, + "MaxDcPower": 10000.0, + "MaxChargeBatteryVoltage": 63.0, + "MinDischargeBatteryVoltage": 32.0 + } + }, + "GridTie": { + "AcDc": { + "MaxDcLinkVoltage": 780.0, + "MinDcLinkVoltage": 720.0, + "ReferenceDcLinkVoltage": 750.0 + }, + "DcDc": { + "LowerDcLinkVoltage": 20.0, + "ReferenceDcLinkVoltage": 750.0, + "UpperDcLinkVoltage": 20.0, + "MaxBatteryChargingCurrent": 210.0, + "MaxBatteryDischargingCurrent": 210.0, + "MaxDcPower": 10000.0, + "MaxChargeBatteryVoltage": 63.0, + "MinDischargeBatteryVoltage": 32.0 + } + }, + "Devices": { + "RelaysIp": { + "DeviceState": "Measured", + "Host": "10.0.1.1", + "Port": 502.0 + }, + "TsRelaysIp": { + "DeviceState": "Measured", + "Host": "10.0.1.2", + "Port": 502.0 + }, + "GridMeterIp": { + "DeviceState": "Measured", + "Host": "10.0.4.1", + "Port": 502.0 + }, + "PvOnAcGrid": { + "DeviceState": "Disabled", + "Host": "false", + "Port": 0.0 + }, + "LoadOnAcGrid": { + "DeviceState": "Measured", + "Host": "true", + "Port": 0.0 + }, + "PvOnAcIsland": { + "DeviceState": "Disabled", + "Host": "false", + "Port": 0.0 + }, + "IslandBusLoadMeterIp": { + "DeviceState": "Measured", + "Host": "10.0.4.2", + "Port": 502.0 + }, + "TruConvertAcIp": { + "DeviceState": "Measured", + "Host": "10.0.2.1", + "Port": 502.0 + }, + "PvOnDc": { + "DeviceState": "Measured", + "Host": "10.0.5.1", + "Port": 502.0 + }, + "LoadOnDc": { + "DeviceState": "Measured", + "Host": "false", + "Port": 0.0 + }, + "TruConvertDcIp": { + "DeviceState": "Measured", + "Host": "10.0.3.1", + "Port": 502.0 + }, + "BatteryIp": { + "DeviceState": "Measured", + "Host": "localhost", + "Port": 6855.0 + }, + "BatteryNodes": "0,1,2,3,4,5" + }, + "S3": { + "Bucket": "1-3e5b3069-214a-43ee-8d85-57d72000c19d", + "Region": "sos-ch-dk-2", + "Provider": "exo.io", + "Key": "EXO1752677fa3b7a5dc1b4efcb9", + "Secret": "fUTGbI-I29dPdS9KrJxpnrpBcWbcpAZmoYcECfVEiYU", + "ContentType": "text/plain", + "Host": "1-3e5b3069-214a-43ee-8d85-57d72000c19d.sos-ch-dk-2.exo.io", + "Url": "https://1-3e5b3069-214a-43ee-8d85-57d72000c19d.sos-ch-dk-2.exo.io" + } + }, + "Log": { + "SalimaxAlarmState": "Green" + }, + "EssControl": { + "Mode": "UpwardsCalibrationCharge", + "LimitedBy": "ChargeLimitedByMaxDcBusVoltage", + "PowerCorrection": -355.15, + "PowerSetpoint": 22.85 + }, + "StateMachine": { + "Message": "ESS", + "State": 23.0 + } +} + diff --git a/S3ExtractingTool/extractS3data.py b/S3ExtractingTool/extractS3data.py new file mode 100644 index 000000000..a40765351 --- /dev/null +++ b/S3ExtractingTool/extractS3data.py @@ -0,0 +1,256 @@ +import os +import csv +import subprocess +import argparse +import matplotlib.pyplot as plt +from collections import defaultdict +import zipfile +import base64 +import shutil +import json +import sys + +def extract_timestamp(filename): + timestamp_str = filename[:10] + try: + timestamp = int(timestamp_str) + return timestamp + except ValueError: + return 0 + + +def list_files_in_range(start_timestamp, end_timestamp, sampling_stepsize,product_type,bucket_number): + if product_type == "Salimax" or product_type=="SodistoreMax": + hash = "3e5b3069-214a-43ee-8d85-57d72000c19d" + elif product_type == "Salidomo": + hash = "c0436b6a-d276-4cd8-9c44-1eae86cf5d0e" + else: + raise ValueError("Invalid product type option. Use Salimax or Salidomo or SodistoreMax") + + # Find common prefix + common_prefix = "" + for s_char, e_char in zip(str(start_timestamp), str(end_timestamp)): + if s_char == e_char: + common_prefix += s_char + else: + break + + s3_path = f"s3://{bucket_number}-{hash}/{common_prefix}*" + s3cmd_command = f"s3cmd ls {s3_path}" + + print(f"Running: {s3cmd_command}") + try: + output = subprocess.check_output(s3cmd_command, shell=True, text=True) + files = [line.split()[-1] for line in output.strip().split("\n") if line.strip()] + filenames = [] + for f in files: + name = f.split("/")[-1] # e.g., 1748802020.json + timestamp_str = name.split(".")[0] # extract '1748802020' + if timestamp_str.isdigit() and int(timestamp_str) <= int(end_timestamp): + filenames.append(name) + else: + break + + print(filenames) + return filenames + except subprocess.CalledProcessError: + print(f"No files found for prefix {common_prefix}") + return [] + +def get_nested_value(data, key_path): + try: + for key in key_path: + data = data[key] + return data + except (KeyError, TypeError): + return None + +def process_json_files_to_csv(output_directory, json_files, keys, start_timestamp, end_timestamp, bucket_number, booleans_as_numbers): + # Generate output file name from all keys + keypath = '_'.join(get_last_component(k) for k in keys) + output_csv_filename = f"{keypath}_from_{start_timestamp}_to_{end_timestamp}_bucket_{bucket_number}.csv" + + with open(output_csv_filename, 'w', newline='') as csvfile: + csv_writer = csv.writer(csvfile) + + # Write header: 'time' + key names + header = ['time'] + [k.split('/')[-1] for k in keys] + csv_writer.writerow(header) + + for json_file in json_files: + file_path = os.path.join(output_directory, json_file) + with open(file_path, 'r') as f: + lines = f.readlines() + + i = 0 + while i < len(lines) - 1: + timestamp_line = lines[i].strip() + json_line = lines[i + 1].strip() + i += 2 + + if not timestamp_line.startswith("Timestamp;"): + continue + + try: + timestamp = int(timestamp_line.split(';')[1]) + except ValueError: + continue + + if not (start_timestamp <= timestamp <= end_timestamp): + continue + + try: + data = json.loads(json_line) + except json.JSONDecodeError: + print(f"❌ Failed to parse JSON in {json_file}, line {i}") + continue + + row = [timestamp] + + for key in keys: + value = get_nested_value(data, key.split('/')) + + if booleans_as_numbers and isinstance(value, str) and value.lower() in ["true", "false"]: + value = 1 if value.lower() == "true" else 0 + if value is None: + value = "No value provided" + + row.append(value) + + csv_writer.writerow(row) + + print(f"✅ Extracted data saved in '{output_csv_filename}'") + + +def download_files(bucket_number, filenames_to_download, product_type): + if product_type == "Salimax" or product_type=="SodistoreMax": + hash = "3e5b3069-214a-43ee-8d85-57d72000c19d" + elif product_type == "Salidomo": + hash = "c0436b6a-d276-4cd8-9c44-1eae86cf5d0e" + else: + raise ValueError("Invalid product type option. Use Salimax or Salidomo or SodistoreMax") + output_directory = f"S3cmdData_{bucket_number}" + + if not os.path.exists(output_directory): + os.makedirs(output_directory) + print(f"Directory '{output_directory}' created.") + + for filename in filenames_to_download: + print(filename) + + local_path = os.path.join(output_directory, filename) + if not os.path.exists(local_path): + s3cmd_command = f"s3cmd get s3://{bucket_number}-{hash}/{filename} {output_directory}/" + + try: + subprocess.run(s3cmd_command, shell=True, check=True) + downloaded_files = [file for file in os.listdir(output_directory) if file.startswith(filename)] + if not downloaded_files: + print(f"No matching files found for prefix '{filename}'.") + else: + print(f"Files with prefix '{filename}' downloaded successfully.") + decompress_file(os.path.join(output_directory, filename), output_directory) + except subprocess.CalledProcessError as e: + # print(f"Error downloading files: {e}") + continue + else: + print(f"File '{filename}.json' already exists locally. Skipping download.") + +def decompress_file(compressed_file, output_directory): + base_name = os.path.splitext(os.path.basename(compressed_file))[0] + + with open(compressed_file, 'rb') as file: + compressed_data = file.read() + + # Decode the base64 encoded content + decoded_data = base64.b64decode(compressed_data) + + zip_path = os.path.join(output_directory, 'temp.zip') + with open(zip_path, 'wb') as zip_file: + zip_file.write(decoded_data) + + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(output_directory) + + # Rename the extracted data.json file to the original timestamp-based name + extracted_csv_path = os.path.join(output_directory, 'data.json') + if os.path.exists(extracted_csv_path): + new_csv_path = os.path.join(output_directory, f"{base_name}.json") + + os.rename(extracted_csv_path, new_csv_path) + + os.remove(zip_path) + print(f"Decompressed and renamed '{compressed_file}' to '{new_csv_path}'.") + + +def get_last_component(path): + path_without_slashes = path.replace('/', '') + return path_without_slashes + +def download_and_process_files(bucket_number, start_timestamp, end_timestamp, sampling_stepsize, keys, booleans_as_numbers, exact_match, product_type): + output_directory = f"S3cmdData_{bucket_number}" + + #if os.path.exists(output_directory): + # shutil.rmtree(output_directory) + + if not os.path.exists(output_directory): + os.makedirs(output_directory) + print(f"Directory '{output_directory}' created.") + + filenames_to_check = list_files_in_range(start_timestamp, end_timestamp, sampling_stepsize,product_type,bucket_number) + existing_files = [filename for filename in filenames_to_check if os.path.exists(os.path.join(output_directory, f"{filename}.json"))] + files_to_download = set(filenames_to_check) - set(existing_files) + print(files_to_download) + + #if os.listdir(output_directory): + # print("Files already exist in the local folder. Skipping download.") + #else: + if files_to_download: + download_files(bucket_number, files_to_download, product_type) + + json_files = [file for file in os.listdir(output_directory) if file.endswith('.json')] + json_files.sort(key=extract_timestamp) + + process_json_files_to_csv( + output_directory=output_directory, + json_files=json_files, + keys=keys, + start_timestamp=start_timestamp, + end_timestamp=end_timestamp, + bucket_number=bucket_number, + booleans_as_numbers=booleans_as_numbers + ) + +def parse_keys(input_string): + keys = [key.strip() for key in input_string.split(',')] + return keys + +def main(): + parser = argparse.ArgumentParser(description='Download files from S3 using s3cmd and extract specific values from CSV files.') + parser.add_argument('start_timestamp', type=int, help='The start timestamp for the range (even number)') + parser.add_argument('end_timestamp', type=int, help='The end timestamp for the range (even number)') + parser.add_argument('--keys', type=parse_keys, required=True, help='The part to match from each CSV file, can be a single key or a comma-separated list of keys') + parser.add_argument('--bucket-number', type=int, required=True, help='The number of the bucket to download from') + parser.add_argument('--sampling_stepsize', type=int, required=False, default=1, help='The number of 2sec intervals, which define the length of the sampling interval in S3 file retrieval') + parser.add_argument('--booleans_as_numbers', action="store_true", required=False, help='If key used, then booleans are converted to numbers [0/1], if key not used, then booleans maintained as text [False/True]') + parser.add_argument('--exact_match', action="store_true", required=False, help='If key used, then key has to match exactly "=", else it is enough that key is found "in" text') + parser.add_argument('--product_name', required=True, help='Use Salimax, Salidomo or SodistoreMax') + + args = parser.parse_args() + start_timestamp = args.start_timestamp + end_timestamp = args.end_timestamp + keys = args.keys + bucket_number = args.bucket_number + sampling_stepsize = args.sampling_stepsize + booleans_as_numbers = args.booleans_as_numbers + exact_match = args.exact_match + # new arg for product type + product_type = args.product_name + + if start_timestamp >= end_timestamp: + print("Error: start_timestamp must be smaller than end_timestamp.") + return + download_and_process_files(bucket_number, start_timestamp, end_timestamp, sampling_stepsize, keys, booleans_as_numbers, exact_match, product_type) + +if __name__ == "__main__": + main() diff --git a/csharp/App/Backend/DeleteOldData/DeleteOldDataFromS3.cs b/csharp/App/Backend/DeleteOldData/DeleteOldDataFromS3.cs new file mode 100644 index 000000000..d4a6e3835 --- /dev/null +++ b/csharp/App/Backend/DeleteOldData/DeleteOldDataFromS3.cs @@ -0,0 +1,94 @@ +using System.Diagnostics; +using InnovEnergy.App.Backend.Database; +using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.Lib.Utils; + +namespace InnovEnergy.App.Backend.DeleteOldData; + +public class DeleteOldDataFromS3 +{ + + public static void DeleteFrom(Installation installation, int timestamps_to_delete) + { + + string configPath = "/home/ubuntu/.s3cfg"; + string bucketPath = installation.Product ==(int)ProductType.Salidomo ? $"s3://{installation.S3BucketId}-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e/{timestamps_to_delete}*" : $"s3://{installation.S3BucketId}-3e5b3069-214a-43ee-8d85-57d72000c19d/{timestamps_to_delete}*" ; + + //Console.WriteLine($"Deleting old data from {bucketPath}"); + + Console.WriteLine("Deleting data for timestamp prefix: " + timestamps_to_delete); + + try + { + ProcessStartInfo startInfo = new ProcessStartInfo + { + FileName = "s3cmd", + Arguments = $"--config {configPath} rm {bucketPath}", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }; + + using Process process = new Process { StartInfo = startInfo }; + + process.OutputDataReceived += (sender, e) => + { + if (!string.IsNullOrEmpty(e.Data)) + Console.WriteLine("[s3cmd] " + e.Data); + }; + + process.ErrorDataReceived += (sender, e) => + { + if (!string.IsNullOrEmpty(e.Data)) + Console.WriteLine("[s3cmd-ERR] " + e.Data); + }; + + process.Start(); + process.BeginOutputReadLine(); + process.BeginErrorReadLine(); + process.WaitForExit(); + } + catch (Exception ex) + { + Console.WriteLine("Exception occurred during deletion: " + ex.Message); + } + } + + public static async Task DeleteOldData() + { + while (true){ + var installations = Db.Installations.ToList(); + foreach (var installation in installations){ + Console.WriteLine("DELETE S3 DATA FOR INSTALLATION "+installation.Name); + long oneYearAgoTimestamp = DateTimeOffset.UtcNow.AddYears(-1).ToUnixTimeSeconds(); + + Console.WriteLine("delete data before "+oneYearAgoTimestamp); + for (int lastDigit=4;lastDigit>=0; lastDigit--) + { + int timestamps_to_delete = int.Parse(oneYearAgoTimestamp.ToString().Substring(0, lastDigit+1)); + timestamps_to_delete--; + Console.WriteLine(timestamps_to_delete); + + while (true) + { + if (timestamps_to_delete % 10 == 0) + { + Console.WriteLine("delete " + timestamps_to_delete + "*"); + DeleteFrom(installation,timestamps_to_delete); + break; + } + Console.WriteLine("delete " + timestamps_to_delete + "*"); + DeleteFrom(installation,timestamps_to_delete); + timestamps_to_delete--; + + } + } + } + Console.WriteLine("FINISHED DELETING S3 DATA FOR ALL INSTALLATIONS\n"); + + await Task.Delay(TimeSpan.FromDays(1)); + } + } + +} \ No newline at end of file diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index 6790e9211..545de13d5 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -3,6 +3,7 @@ using Flurl.Http; using Hellang.Middleware.ProblemDetails; using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.Websockets; +using InnovEnergy.App.Backend.DeleteOldData; using Microsoft.AspNetCore.HttpOverrides; using Microsoft.AspNetCore.Mvc; using Microsoft.OpenApi.Models; @@ -30,6 +31,9 @@ public static class Program WebsocketManager.MonitorSalimaxInstallationTable().SupressAwaitWarning(); WebsocketManager.MonitorSalidomoInstallationTable().SupressAwaitWarning(); WebsocketManager.MonitorSodistoreInstallationTable().SupressAwaitWarning(); + + + Task.Run(() => DeleteOldDataFromS3.DeleteOldData()); builder.Services.AddControllers(); builder.Services.AddProblemDetails(setup => diff --git a/csharp/App/Backend/Websockets/WebsockerManager.cs b/csharp/App/Backend/Websockets/WebsockerManager.cs index f3c51145c..07dacead3 100644 --- a/csharp/App/Backend/Websockets/WebsockerManager.cs +++ b/csharp/App/Backend/Websockets/WebsockerManager.cs @@ -19,7 +19,7 @@ public static class WebsocketManager { while (true){ lock (InstallationConnections){ - Console.WriteLine("MONITOR SALIMAX INSTALLATIONS\n"); + // Console.WriteLine("MONITOR SALIMAX INSTALLATIONS\n"); foreach (var installationConnection in InstallationConnections){ if (installationConnection.Value.Product==(int)ProductType.Salimax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)){ @@ -35,7 +35,7 @@ public static class WebsocketManager if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);} } } - Console.WriteLine("FINISHED MONITORING SALIMAX INSTALLATIONS\n"); + // Console.WriteLine("FINISHED MONITORING SALIMAX INSTALLATIONS\n"); } await Task.Delay(TimeSpan.FromMinutes(1)); @@ -47,15 +47,15 @@ public static class WebsocketManager public static async Task MonitorSalidomoInstallationTable() { while (true){ - Console.WriteLine("TRY TO LOCK FOR MONITOR SALIDOMO INSTALLATIONS\n"); + //Console.WriteLine("TRY TO LOCK FOR MONITOR SALIDOMO INSTALLATIONS\n"); lock (InstallationConnections){ - Console.WriteLine("MONITOR SALIDOMO INSTALLATIONS\n"); + //Console.WriteLine("MONITOR SALIDOMO INSTALLATIONS\n"); foreach (var installationConnection in InstallationConnections) { //Console.WriteLine("Installation ID is "+installationConnection.Key); - if (installationConnection.Value.Product == (int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) < TimeSpan.FromMinutes(60)){ - Console.WriteLine("Installation ID is "+installationConnection.Key + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp)); - } + // if (installationConnection.Value.Product == (int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) < TimeSpan.FromMinutes(60)){ + // Console.WriteLine("Installation ID is "+installationConnection.Key + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp)); + // } if (installationConnection.Value.Product==(int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(60)) { @@ -64,16 +64,16 @@ public static class WebsocketManager //Console.WriteLine("timestamp now is is "+(DateTime.Now)); Installation installation = Db.Installations.FirstOrDefault(f => f.Product == (int)ProductType.Salidomo && f.Id == installationConnection.Key); - Console.WriteLine("Installation ID is "+installation.Name + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp)); + //Console.WriteLine("Installation ID is "+installation.Name + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp)); installation.Status = (int)StatusType.Offline; installation.Apply(Db.Update); installationConnection.Value.Status = (int)StatusType.Offline; if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);} - else{Console.WriteLine("NONE IS CONNECTED TO THAT INSTALLATION-------------------------------------------------------------");} + //else{Console.WriteLine("NONE IS CONNECTED TO THAT INSTALLATION-------------------------------------------------------------");} } } - Console.WriteLine("FINISHED WITH UPDATING\n"); + //Console.WriteLine("FINISHED WITH UPDATING\n"); } await Task.Delay(TimeSpan.FromMinutes(1)); } @@ -84,9 +84,9 @@ public static class WebsocketManager public static async Task MonitorSodistoreInstallationTable() { while (true){ - Console.WriteLine("TRY TO LOCK FOR MONITOR SODISTORE INSTALLATIONS\n"); + //Console.WriteLine("TRY TO LOCK FOR MONITOR SODISTORE INSTALLATIONS\n"); lock (InstallationConnections){ - Console.WriteLine("MONITOR SODISTORE INSTALLATIONS\n"); + //Console.WriteLine("MONITOR SODISTORE INSTALLATIONS\n"); foreach (var installationConnection in InstallationConnections) { @@ -97,16 +97,16 @@ public static class WebsocketManager //Console.WriteLine("timestamp now is is "+(DateTime.Now)); Installation installation = Db.Installations.FirstOrDefault(f => f.Product == (int)ProductType.SodiStoreMax && f.Id == installationConnection.Key); - Console.WriteLine("Installation ID is "+installation.Name + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp)); + //Console.WriteLine("Installation ID is "+installation.Name + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp)); installation.Status = (int)StatusType.Offline; installation.Apply(Db.Update); installationConnection.Value.Status = (int)StatusType.Offline; if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);} - else{Console.WriteLine("NONE IS CONNECTED TO THAT INSTALLATION-------------------------------------------------------------");} + //else{Console.WriteLine("NONE IS CONNECTED TO THAT INSTALLATION-------------------------------------------------------------");} } } - Console.WriteLine("FINISHED WITH UPDATING\n"); + //Console.WriteLine("FINISHED WITH UPDATING\n"); } await Task.Delay(TimeSpan.FromMinutes(1)); } @@ -225,20 +225,20 @@ public static class WebsocketManager ); - Console.WriteLine("Printing installation connection list"); - Console.WriteLine("----------------------------------------------"); - foreach (var installationConnection in InstallationConnections) - { - Console.WriteLine("Installation ID: " + installationConnection.Key + " Number of Connections: " + installationConnection.Value.Connections.Count); - } - Console.WriteLine("----------------------------------------------"); + // Console.WriteLine("Printing installation connection list"); + // Console.WriteLine("----------------------------------------------"); + // foreach (var installationConnection in InstallationConnections) + // { + // Console.WriteLine("Installation ID: " + installationConnection.Key + " Number of Connections: " + installationConnection.Value.Connections.Count); + // } + // Console.WriteLine("----------------------------------------------"); } } lock (InstallationConnections) { //When the front-end terminates the connection, the following code will be executed - Console.WriteLine("The connection has been terminated"); + //Console.WriteLine("The connection has been terminated"); foreach (var installationConnection in InstallationConnections) { if (installationConnection.Value.Connections.Contains(currentWebSocket)) @@ -249,18 +249,18 @@ public static class WebsocketManager } await currentWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Connection closed by server", CancellationToken.None); - lock (InstallationConnections) - { - //Print the installationConnections dictionary after deleting a websocket - Console.WriteLine("Print the installation connections list after deleting a websocket"); - Console.WriteLine("----------------------------------------------"); - foreach (var installationConnection in InstallationConnections) - { - Console.WriteLine("Installation ID: " + installationConnection.Key + " Number of Connections: " + installationConnection.Value.Connections.Count); - } - - Console.WriteLine("----------------------------------------------"); - } + // lock (InstallationConnections) + // { + // //Print the installationConnections dictionary after deleting a websocket + // Console.WriteLine("Print the installation connections list after deleting a websocket"); + // Console.WriteLine("----------------------------------------------"); + // foreach (var installationConnection in InstallationConnections) + // { + // Console.WriteLine("Installation ID: " + installationConnection.Key + " Number of Connections: " + installationConnection.Value.Connections.Count); + // } + // + // Console.WriteLine("----------------------------------------------"); + // } } catch (Exception ex) { diff --git a/typescript/frontend-marios2/package-lock.json b/typescript/frontend-marios2/package-lock.json index f8632153f..8658486f9 100644 --- a/typescript/frontend-marios2/package-lock.json +++ b/typescript/frontend-marios2/package-lock.json @@ -37,6 +37,7 @@ "react-cytoscapejs": "^2.0.0", "react-dom": "17.0.2", "react-flow-renderer": "^10.3.17", + "react-gauge-chart": "^0.5.1", "react-helmet-async": "1.3.0", "react-icons": "^4.11.0", "react-icons-converter": "^1.1.4", @@ -7737,6 +7738,51 @@ "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", "integrity": "sha512-yFk/2idb8OHPKkbAL8QaOaqENNoMhIaSHZerk3oQsECwkObkCpJyjYwCe+OHiq6UEdhe1m8ZGARRRO3ljFjlKg==" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "dependencies": { + "d3-path": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", @@ -7745,6 +7791,28 @@ "node": ">=12" } }, + "node_modules/d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "dependencies": { + "d3-array": "^3.2.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "dependencies": { + "delaunator": "5" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-dispatch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", @@ -7765,6 +7833,38 @@ "node": ">=12" } }, + "node_modules/d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "dependencies": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "bin": { + "csv2json": "bin/dsv2json.js", + "csv2tsv": "bin/dsv2dsv.js", + "dsv2dsv": "bin/dsv2dsv.js", + "dsv2json": "bin/dsv2json.js", + "json2csv": "bin/json2dsv.js", + "json2dsv": "bin/json2dsv.js", + "json2tsv": "bin/json2dsv.js", + "tsv2csv": "bin/dsv2dsv.js", + "tsv2json": "bin/dsv2json.js" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-dsv/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "engines": { + "node": ">= 10" + } + }, "node_modules/d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", @@ -7773,6 +7873,57 @@ "node": ">=12" } }, + "node_modules/d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "dependencies": { + "d3-dsv": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "dependencies": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==", + "engines": { + "node": ">=12" + } + }, "node_modules/d3-interpolate": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", @@ -7784,6 +7935,65 @@ "node": ">=12" } }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "dependencies": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-selection": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", @@ -7792,6 +8002,39 @@ "node": ">=12" } }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/d3-timer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", @@ -8022,6 +8265,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "dependencies": { + "robust-predicates": "^3.0.2" + } + }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -11005,6 +11256,14 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "engines": { + "node": ">=12" + } + }, "node_modules/intl-messageformat": { "version": "10.5.14", "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz", @@ -17037,6 +17296,58 @@ "react-dom": "16 || 17 || 18" } }, + "node_modules/react-gauge-chart": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/react-gauge-chart/-/react-gauge-chart-0.5.1.tgz", + "integrity": "sha512-Kd0pLIfHWOVEWJCz+xAxFfrKzCdOkLHNKR13V+O3h06ocyGT7fWjz2oevMXsPt3bVn1Du2od7gPrjUhqXKdASg==", + "dependencies": { + "d3": "^7.6.1" + }, + "peerDependencies": { + "react": "^16.8.2 || ^17.0 || ^18.x", + "react-dom": "^16.8.2 || ^17.0 || ^18.x" + } + }, + "node_modules/react-gauge-chart/node_modules/d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "dependencies": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/react-helmet-async": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz", @@ -17652,6 +17963,11 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "node_modules/rollup": { "version": "2.79.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", @@ -17750,6 +18066,11 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -26418,11 +26739,60 @@ "resolved": "https://registry.npmjs.org/d3/-/d3-3.5.17.tgz", "integrity": "sha512-yFk/2idb8OHPKkbAL8QaOaqENNoMhIaSHZerk3oQsECwkObkCpJyjYwCe+OHiq6UEdhe1m8ZGARRRO3ljFjlKg==" }, + "d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "requires": { + "internmap": "1 - 2" + } + }, + "d3-axis": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-axis/-/d3-axis-3.0.0.tgz", + "integrity": "sha512-IH5tgjV4jE/GhHkRV0HiVYPDtvfjHQlQfJHs0usq7M30XcSBvOotpmH1IgkcXsO/5gEQZD43B//fc7SRT5S+xw==" + }, + "d3-brush": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-brush/-/d3-brush-3.0.0.tgz", + "integrity": "sha512-ALnjWlVYkXsVIGlOsuWH1+3udkYFI48Ljihfnh8FZPF2QS9o+PzGLBslO0PjzVoHLZ2KCVgAM8NVkXPJB2aNnQ==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-drag": "2 - 3", + "d3-interpolate": "1 - 3", + "d3-selection": "3", + "d3-transition": "3" + } + }, + "d3-chord": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-chord/-/d3-chord-3.0.1.tgz", + "integrity": "sha512-VE5S6TNa+j8msksl7HwjxMHDM2yNK3XCkusIlpX5kwauBfXuyLAtNg9jCp/iHH61tgI4sb6R/EIMWCqEIdjT/g==", + "requires": { + "d3-path": "1 - 3" + } + }, "d3-color": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==" }, + "d3-contour": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.2.tgz", + "integrity": "sha512-4EzFTRIikzs47RGmdxbeUvLWtGedDUNkTcmzoeyg4sP/dvCexO47AaQL7VKy/gul85TOxw+IBgA8US2xwbToNA==", + "requires": { + "d3-array": "^3.2.0" + } + }, + "d3-delaunay": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz", + "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==", + "requires": { + "delaunator": "5" + } + }, "d3-dispatch": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-dispatch/-/d3-dispatch-3.0.1.tgz", @@ -26437,11 +26807,64 @@ "d3-selection": "3" } }, + "d3-dsv": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-dsv/-/d3-dsv-3.0.1.tgz", + "integrity": "sha512-UG6OvdI5afDIFP9w4G0mNq50dSOsXHJaRE8arAS5o9ApWnIElp8GZw1Dun8vP8OyHOZ/QJUKUJwxiiCCnUwm+Q==", + "requires": { + "commander": "7", + "iconv-lite": "0.6", + "rw": "1" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + } + } + }, "d3-ease": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==" }, + "d3-fetch": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-fetch/-/d3-fetch-3.0.1.tgz", + "integrity": "sha512-kpkQIM20n3oLVBKGg6oHrUchHM3xODkTzjMoj7aWQFq5QEM+R6E4WkzT5+tojDY7yjez8KgCBRoj4aEr99Fdqw==", + "requires": { + "d3-dsv": "1 - 3" + } + }, + "d3-force": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/d3-force/-/d3-force-3.0.0.tgz", + "integrity": "sha512-zxV/SsA+U4yte8051P4ECydjD/S+qeYtnaIyAs9tgHCqfguma/aAQDjo85A9Z6EKhBirHRJHXIgJUlffT4wdLg==", + "requires": { + "d3-dispatch": "1 - 3", + "d3-quadtree": "1 - 3", + "d3-timer": "1 - 3" + } + }, + "d3-format": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz", + "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==" + }, + "d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-637ln3gXKXOwhalDzinUgY83KzNWZRKbYubaG+fGVuc/dxO64RRljtCTnf5ecMyE1RIdtqpkVcq0IbtU2S8j2Q==", + "requires": { + "d3-array": "2.5.0 - 3" + } + }, + "d3-hierarchy": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-hierarchy/-/d3-hierarchy-3.1.2.tgz", + "integrity": "sha512-FX/9frcub54beBdugHjDCdikxThEqjnR93Qt7PvQTOHxyiNCAlvMrHhclk3cD5VeAaq9fxmfRp+CnWw9rEMBuA==" + }, "d3-interpolate": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", @@ -26450,11 +26873,76 @@ "d3-color": "1 - 3" } }, + "d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==" + }, + "d3-polygon": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-polygon/-/d3-polygon-3.0.1.tgz", + "integrity": "sha512-3vbA7vXYwfe1SYhED++fPUQlWSYTTGmFmQiany/gdbiWgU/iEyQzyymwL9SkJjFFuCS4902BSzewVGsHHmHtXg==" + }, + "d3-quadtree": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-quadtree/-/d3-quadtree-3.0.1.tgz", + "integrity": "sha512-04xDrxQTDTCFwP5H6hRhsRcb9xxv2RzkcsygFzmkSIOJy3PeRJP7sNk3VRIbKXcog561P9oU0/rVH6vDROAgUw==" + }, + "d3-random": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-random/-/d3-random-3.0.1.tgz", + "integrity": "sha512-FXMe9GfxTxqd5D6jFsQ+DJ8BJS4E/fT5mqqdjovykEB2oFbTMDVdg1MGFxfQW+FBOGoB++k8swBrgwSHT1cUXQ==" + }, + "d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "requires": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + } + }, + "d3-scale-chromatic": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-scale-chromatic/-/d3-scale-chromatic-3.1.0.tgz", + "integrity": "sha512-A3s5PWiZ9YCXFye1o246KoscMWqf8BsD9eRiJ3He7C9OBaxKhAd5TFCdEx/7VbKtxxTsu//1mMJFrEt572cEyQ==", + "requires": { + "d3-color": "1 - 3", + "d3-interpolate": "1 - 3" + } + }, "d3-selection": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==" }, + "d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "requires": { + "d3-path": "^3.1.0" + } + }, + "d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "requires": { + "d3-array": "2 - 3" + } + }, + "d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "requires": { + "d3-time": "1 - 3" + } + }, "d3-timer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", @@ -26616,6 +27104,14 @@ "object-keys": "^1.1.1" } }, + "delaunator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz", + "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==", + "requires": { + "robust-predicates": "^3.0.2" + } + }, "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -28819,6 +29315,11 @@ "side-channel": "^1.0.4" } }, + "internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==" + }, "intl-messageformat": { "version": "10.5.14", "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-10.5.14.tgz", @@ -33141,6 +33642,53 @@ "zustand": "^3.7.2" } }, + "react-gauge-chart": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/react-gauge-chart/-/react-gauge-chart-0.5.1.tgz", + "integrity": "sha512-Kd0pLIfHWOVEWJCz+xAxFfrKzCdOkLHNKR13V+O3h06ocyGT7fWjz2oevMXsPt3bVn1Du2od7gPrjUhqXKdASg==", + "requires": { + "d3": "^7.6.1" + }, + "dependencies": { + "d3": { + "version": "7.9.0", + "resolved": "https://registry.npmjs.org/d3/-/d3-7.9.0.tgz", + "integrity": "sha512-e1U46jVP+w7Iut8Jt8ri1YsPOvFpg46k+K8TpCb0P+zjCkjkPnV7WzfDJzMHy1LnA+wj5pLT1wjO901gLXeEhA==", + "requires": { + "d3-array": "3", + "d3-axis": "3", + "d3-brush": "3", + "d3-chord": "3", + "d3-color": "3", + "d3-contour": "4", + "d3-delaunay": "6", + "d3-dispatch": "3", + "d3-drag": "3", + "d3-dsv": "3", + "d3-ease": "3", + "d3-fetch": "3", + "d3-force": "3", + "d3-format": "3", + "d3-geo": "3", + "d3-hierarchy": "3", + "d3-interpolate": "3", + "d3-path": "3", + "d3-polygon": "3", + "d3-quadtree": "3", + "d3-random": "3", + "d3-scale": "4", + "d3-scale-chromatic": "3", + "d3-selection": "3", + "d3-shape": "3", + "d3-time": "3", + "d3-time-format": "4", + "d3-timer": "3", + "d3-transition": "3", + "d3-zoom": "3" + } + } + } + }, "react-helmet-async": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/react-helmet-async/-/react-helmet-async-1.3.0.tgz", @@ -33586,6 +34134,11 @@ "glob": "^7.1.3" } }, + "robust-predicates": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz", + "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==" + }, "rollup": { "version": "2.79.1", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz", @@ -33653,6 +34206,11 @@ "queue-microtask": "^1.2.2" } }, + "rw": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/rw/-/rw-1.3.3.tgz", + "integrity": "sha512-PdhdWy89SiZogBLaw42zdeqtRJ//zFd2PgQavcICDUgJT5oW10QCRKbJ6bg4r0/UY2M6BWd5tkxuGFRvCkgfHQ==" + }, "rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", diff --git a/typescript/frontend-marios2/package.json b/typescript/frontend-marios2/package.json index fb96cf903..e5a5a5d2f 100644 --- a/typescript/frontend-marios2/package.json +++ b/typescript/frontend-marios2/package.json @@ -16,6 +16,7 @@ "axios": "^1.5.0", "chart.js": "^4.4.0", "clsx": "1.1.1", + "crypto-js": "^4.2.0", "cytoscape": "^3.26.0", "date-fns": "^2.28.0", "dayjs": "^1.11.10", @@ -32,6 +33,7 @@ "react-cytoscapejs": "^2.0.0", "react-dom": "17.0.2", "react-flow-renderer": "^10.3.17", + "react-gauge-chart": "^0.5.1", "react-helmet-async": "1.3.0", "react-icons": "^4.11.0", "react-icons-converter": "^1.1.4", diff --git a/typescript/frontend-marios2/src/App.css b/typescript/frontend-marios2/src/App.css index c385c7151..c31814a6e 100644 --- a/typescript/frontend-marios2/src/App.css +++ b/typescript/frontend-marios2/src/App.css @@ -6,4 +6,5 @@ .apexcharts-toolbar { display: none !important; -} \ No newline at end of file +} + diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx index efb2c1a12..6a2f0e44f 100644 --- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/BatteryView.tsx @@ -27,6 +27,7 @@ import CircularProgress from '@mui/material/CircularProgress'; import MainStats from './MainStats'; import DetailedBatteryView from './DetailedBatteryView'; import { ProductIdContext } from '../../../contexts/ProductIdContextProvider'; +import DetailedBatteryViewSodistore from './DetailedBatteryViewSodistore'; interface BatteryViewProps { values: JSONRecordData; @@ -175,23 +176,41 @@ function BatteryView(props: BatteryViewProps) { > } /> - {Object.entries(props.values.Battery.Devices).map( - ([BatteryId, battery]) => ( - - } - /> - ) - )} + {product === 0 + ? Object.entries(props.values.Battery.Devices).map( + ([BatteryId, battery]) => ( + + } + /> + ) + ) + : Object.entries(props.values.Battery.Devices).map( + ([BatteryId, battery]) => ( + + } + /> + ) + )} @@ -226,6 +245,9 @@ function BatteryView(props: BatteryViewProps) { ) : ( Max Cell Voltage )} + {product === 3 && ( + Voltage Difference + )} @@ -350,14 +372,21 @@ function BatteryView(props: BatteryViewProps) { : '#32CD32' }; } else { + const avgTemp = + (battery.BatteryDeligreenDataRecord.TemperaturesList + .CellTemperature1 + + battery.BatteryDeligreenDataRecord + .TemperaturesList.CellTemperature2 + + battery.BatteryDeligreenDataRecord + .TemperaturesList.CellTemperature3 + + battery.BatteryDeligreenDataRecord + .TemperaturesList.CellTemperature4) / + 4; return { width: '10%', textAlign: 'center', backgroundColor: - battery.BatteryDeligreenDataRecord - .TemperaturesList.EnvironmentTemperature > 50 || - battery.BatteryDeligreenDataRecord - .TemperaturesList.EnvironmentTemperature < 0 + avgTemp > 50 || avgTemp < 0 ? '#FF033E' : '#32CD32' }; @@ -459,13 +488,13 @@ function BatteryView(props: BatteryViewProps) { <> 3.94 + minVoltage < 2 ? '#FF033E' - : minVoltage < 2 || minVoltage > 3.8 + : minVoltage > 2 && minVoltage < 2.2 ? '#ffbf00' : '#32CD32', color: minVoltage != 0 ? 'black' : 'white' @@ -476,13 +505,13 @@ function BatteryView(props: BatteryViewProps) { 3.94 + maxVoltage > 3.9 ? '#FF033E' - : maxVoltage < 2 || maxVoltage > 3.8 + : maxVoltage < 3.9 && maxVoltage > 3.8 ? '#ffbf00' : '#32CD32', color: maxVoltage != 0 ? 'black' : 'white' @@ -495,6 +524,46 @@ function BatteryView(props: BatteryViewProps) { })()} )} + {product === 3 && ( + <> + {(() => { + const cellVoltagesString = + battery.BatteryDeligreenDataRecord.CellVoltage || + ''; + + const cellVoltagesArray: number[] = cellVoltagesString + .split(',') + .map((v) => parseFloat(v.trim())) + .filter((v) => !isNaN(v)); // Filter invalid numbers + + const minVoltage = Math.min(...cellVoltagesArray); + const maxVoltage = Math.max(...cellVoltagesArray); + + const voltage_diff = maxVoltage - minVoltage; + + return ( + <> + 0.2 + ? '#FF033E' + : voltage_diff > 0.1 && voltage_diff < 0.2 + ? '#ffbf00' + : '#32CD32', + color: voltage_diff != 0 ? 'black' : 'white' + }} + > + {voltage_diff.toFixed(3)} V{' '} + + + ); + })()} + + )} ))} diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryViewSodistore.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryViewSodistore.tsx new file mode 100644 index 000000000..bcbd57c8a --- /dev/null +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryViewSodistore.tsx @@ -0,0 +1,1169 @@ +import React, { useEffect, useRef, useState } from 'react'; +import { I_S3Credentials } from '../../../interfaces/S3Types'; +import { + Box, + Card, + Grid, + IconButton, + Paper, + Table, + TableBody, + TableCell, + TableContainer, + TableRow, + Typography +} from '@mui/material'; +import { Device } from '../Log/graph.util'; +import { useNavigate } from 'react-router-dom'; +import ArrowBackIcon from '@mui/icons-material/ArrowBack'; +import GaugeChart from 'react-gauge-chart'; + +interface DetailedBatteryViewSodistoreProps { + batteryId: number; + s3Credentials: I_S3Credentials; + batteryData: Device; + installationId: number; + productNum: number; +} + +function DetailedBatteryViewSodistore( + props: DetailedBatteryViewSodistoreProps +) { + if (props.batteryData === null) { + return null; + } + + const navigate = useNavigate(); + + const handleBatteryViewButton = () => { + navigate(location.pathname.split('/').slice(0, -2).join('/')); + }; + + const batteryStyle = { + borderRadius: '15px', + padding: '10px', + backgroundColor: 'lightgray', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + height: '150px' + }; + + const batteryStringStyle = { + flex: 1, + border: '1px solid #000', + height: '97%', + width: '30px', + borderRadius: '7px', + backgroundColor: '#bfbfbf' + }; + + const cellVoltagesArray: number[] = + props.batteryData.BatteryDeligreenDataRecord.CellVoltage.split(',') + .map((v) => parseFloat(v.trim())) + .filter((v) => !isNaN(v)); // Filter invalid numbers + + const minVoltage = Math.min(...cellVoltagesArray); + const maxVoltage = Math.max(...cellVoltagesArray); + + const SocGauge = ({ soc }: { soc: number }) => { + const [stableSoc, setStableSoc] = useState(soc); + const isFirstRender = useRef(true); + + useEffect(() => { + // On first render, don't animate — just set + if (isFirstRender.current) { + setStableSoc(soc); + isFirstRender.current = false; + } else { + // Only update if the change is significant enough to justify it + if (Math.abs(soc - stableSoc) > 0.1) { + setStableSoc(soc); + } + } + }, [soc]); + + return ( +
+ +
+
SOC: {stableSoc.toFixed(1)}%
+
+
+ ); + }; + + return ( + <> + + + + + + + + + + + Node {props.batteryId} + + + + + + Battery Information + + + + + + + + Total Battery Voltage + + + {props.batteryData.BatteryDeligreenDataRecord.BusVoltage + + ' V'} + + + + + Charge/Discharge Current + + + {props.batteryData.BatteryDeligreenDataRecord.BusCurrent + + ' A'} + + + + + Residual Capacity + + + {props.batteryData.BatteryDeligreenDataRecord + .ResidualCapacity + ' Ah'} + + + + + + Battery Capacity + + + {props.batteryData.BatteryDeligreenDataRecord + .BatteryCapacity + ' Ah'} + + + + + SoC + + + {props.batteryData.BatteryDeligreenDataRecord.Soc + ' %'} + + + + + Rated Capacity + + + {props.batteryData.BatteryDeligreenDataRecord + .RatedCapacity + ' Ah'} + + + + + + Number of Cycles + + + { + props.batteryData.BatteryDeligreenDataRecord + .NumberOfCycles + } + + + + + + SOH + + + {props.batteryData.BatteryDeligreenDataRecord.Soh + ' %'} + + + + + + Port Voltage + + + + +
+
+
+
+ {/*----------------------------------------------------------------------------------------------------------------------------------*/} + + {/* SOC Card */} + + + SOC + + + + + {/* On-Off State Card */} + {/*----------------------------------------------------------------------------------------------------------------------------------*/} + + + + On-Off State + + + + + + {[ + [ + 'Charge Switch State', + props.batteryData.BatteryDeligreenAlarmRecord.OnOffState + .ChargeSwitchState + ], + [ + 'Discharge Switch State', + props.batteryData.BatteryDeligreenAlarmRecord.OnOffState + .DischargeSwitchState + ], + [ + 'Current Limit Switch State', + props.batteryData.BatteryDeligreenAlarmRecord.OnOffState + .CurrentLimitSwitchStat + ], + [ + 'Heating Switch State', + props.batteryData.BatteryDeligreenAlarmRecord.OnOffState + .HeatingSwitchState + ] + ].map(([label, value]) => ( + + + {label} + + + {value} + + + ))} + +
+
+
+
+ {/*----------------------------------------------------------------------------------------------------------------------------------*/} + + + + + + + + Current + + + {props.batteryData.BatteryDeligreenDataRecord.BusCurrent.toFixed( + 3 + )}{' '} + A + + + + + + + + + 63 + ? '#FF033E' + : '#32CD32', + borderRadius: '10px 0 0 10px', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + textAlign: 'center', + marginBottom: '15px' + }} + > + + Voltage + + + {props.batteryData.BatteryDeligreenDataRecord.TotalBatteryVoltage.toFixed( + 3 + )}{' '} + V + + + + + + + {/*----------------------------------------------------------------------------------------------------------------------------------*/} + + + + + + + + System State + + + {Object.entries( + props.batteryData.BatteryDeligreenAlarmRecord.SystemState + ).find(([_, value]) => value === 'True')?.[0] ?? 'Unknown'} + + + +
+
+
+ {/*----------------------------------------------------------------------------------------------------------------------------------*/} + {/*Temperature List*/} + + + + Battery Temperatures + + + + + + {Object.entries( + props.batteryData.BatteryDeligreenDataRecord + .TemperaturesList || {} + ).map(([label, value]) => ( + + + {label} + + + {value + ' °C'} + + + ))} + +
+
+
+
+ + {/*----------------------------------------------------------------------------------------------------------------------------------*/} + + + + {[0, 1].map((col) => ( + + + {cellVoltagesArray + .slice(col * 8, (col + 1) * 8) + .map((voltage, index) => ( + + + 3.94 + ? '#FF033E' + : voltage < 2 || voltage > 3.8 + ? '#ffbf00' + : '#32CD32', + padding: '8px', + minWidth: '145px', + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + borderRadius: '8px 0 0 8px' + }} + > + + Cell {col * 8 + index + 1 < 10 ? '0' : ''} + {col * 8 + index + 1} + + + {voltage.toFixed(3)} V + + + {/* Battery Cap */} + + + + ))} + + + ))} + + + + {/*Alarm events 1-3 */} + {/*----------------------------------------------------------------------------------------------------------------------------------*/} + + {[1, 2, 3].map((eventNum) => { + const activeAlarms = Object.entries( + props.batteryData.BatteryDeligreenAlarmRecord[ + `AlarmEvent${eventNum}` + ] || {} + ).filter(([_, value]) => value === 'True'); + + return ( + + + + Alarm Event {eventNum} + + + 0 ? 'auto' : 'visible', + width: '100%', + px: 1, + flex: 1 + }} + > + + + + {activeAlarms.length === 0 ? ( + + + No active alarms + + + ) : ( + activeAlarms.map(([label, value]) => ( + + + {label} + + + {value} + + + )) + )} + +
+
+
+
+
+ ); + })} +
+ + {/*Alarm events 4-6 */} + {/*----------------------------------------------------------------------------------------------------------------------------------*/} + + {[4, 5, 6].map((eventNum) => { + const activeAlarms = Object.entries( + props.batteryData.BatteryDeligreenAlarmRecord[ + `AlarmEvent${eventNum}` + ] || {} + ).filter(([_, value]) => value === 'True'); + + return ( + + + + Alarm Event {eventNum} + + + 0 ? 'auto' : 'visible', + width: '100%', + px: 1, + flex: 1 + }} + > + + + + {activeAlarms.length === 0 ? ( + + + No active alarms + + + ) : ( + activeAlarms.map(([label, value]) => ( + + + {label} + + + {value} + + + )) + )} + +
+
+
+
+
+ ); + })} +
+ + {/*Alarm events 7,8, CellsAlarms */} + {/*----------------------------------------------------------------------------------------------------------------------------------*/} + + {[7, 8].map((eventNum) => { + const activeAlarms = Object.entries( + props.batteryData.BatteryDeligreenAlarmRecord[ + `AlarmEvent${eventNum}` + ] || {} + ).filter(([_, value]) => value === 'True'); + + return ( + + + + Alarm Event {eventNum} + + + 0 ? 'auto' : 'visible', + width: '100%', + px: 1, + flex: 1 + }} + > + + + + {activeAlarms.length === 0 ? ( + + + No active alarms + + + ) : ( + activeAlarms.map(([label, value]) => ( + + + {label} + + + {value} + + + )) + )} + +
+
+
+
+
+ ); + })} + + {/* Third Column: CellAlarmList */} + + {(() => { + const cellAlarms = ( + props.batteryData.BatteryDeligreenAlarmRecord.CellAlarmList || '' + ) + .split(',') + .map((item) => item.trim()) + .map((value, index) => ({ + label: `Cell ${index + 1 < 10 ? '0' : ''}${index + 1}`, + value + })) + .filter( + ({ value }) => + value !== 'Normal, no alarm' && value !== 'Normal-no alarm' + ); // handles both formats + + return ( + + + Cell Alarms + + + 0 ? 'auto' : 'visible', + width: '100%', + px: 1, + flex: 1 + }} + > + + + + {cellAlarms.length === 0 ? ( + + + No active alarms + + + ) : ( + cellAlarms.map(({ label, value }) => ( + + + {label} + + + {value} + + + )) + )} + +
+
+
+
+ ); + })()} +
+
+ + {/*CellTemperatureAlarms */} + {/*----------------------------------------------------------------------------------------------------------------------------------*/} + + {/* Third Column: CellAlarmList */} + + {(() => { + const cellTemperatureAlarms = ( + props.batteryData.BatteryDeligreenAlarmRecord + .CellTemperatureAlarm || '' + ) + .split(',') + .map((item) => item.trim()) + .map((value, index) => ({ + label: `Cell ${index + 1 < 10 ? '0' : ''}${index + 1}`, + value + })) + .filter( + ({ value }) => + value !== 'Normal, no alarm' && value !== 'Normal-no alarm' + ); // handles both formats + + return ( + + + Cell Alarms + + + 0 ? 'auto' : 'visible', + width: '100%', + px: 1, + flex: 1 + }} + > + + + + {cellTemperatureAlarms.length === 0 ? ( + + + No active alarms + + + ) : ( + cellTemperatureAlarms.map(({ label, value }) => ( + + + {label} + + + {value} + + + )) + )} + +
+
+
+
+ ); + })()} +
+
+ + ); +} + +export default DetailedBatteryViewSodistore; diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx index 62b0a3302..35be8866e 100644 --- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/MainStats.tsx @@ -135,6 +135,8 @@ function MainStats(props: MainStatsProps) { function generateSeries(chartData, category, color) { const series = []; const pathsToSearch = [ + 'Node0', + 'Node1', 'Node2', 'Node3', 'Node4', @@ -143,8 +145,7 @@ function MainStats(props: MainStatsProps) { 'Node7', 'Node8', 'Node9', - 'Node10', - 'Node11' + 'Node10' ]; let i = 0; diff --git a/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx b/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx index aa4b6f948..9b192b300 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Log/graph.util.tsx @@ -68,6 +68,113 @@ export interface Device { TotalBatteryVoltage: number; }; + BatteryDeligreenAlarmRecord: { + OnOffState: { + ChargeSwitchState: string; + CurrentLimitSwitchStat: string; + DischargeSwitchState: string; + HeatingSwitchState: string; + }; + + AlarmEvent1: { + CellVoltageDropoutFault: string; + ChargeSwitchFault: string; + CurrentLimitSwitchFault: string; + CurrentSensorFault: string; + DischargeSwitchFault: string; + KeySwitchFault: string; + TemperatureSensorFault: string; + VoltageSensorFault: string; + }; + + AlarmEvent2: { + HighVoltageAlarmForTotalVoltage: string; + LowVoltageAlarmForTotalVoltage: string; + MonomerHighVoltageAlarm: string; + MonomerLowVoltageAlarm: string; + MonomerOvervoltageProtection: string; + MonomerUnderVoltageProtection: string; + OvervoltageProtectionForTotalVoltage: string; + UnderVoltageProtectionForTotalVoltage: string; + }; + + AlarmEvent3: { + ChargeHighTemperatureAlarm: string; + ChargeLowTemperatureAlarm: string; + ChargeOverTemperatureProtection: string; + ChargeUnderTemperatureProtection: string; + DischargeHighTemperatureAlarm: string; + DischargeLowTemperatureAlarm: string; + DischargeOverTemperatureProtection: string; + DischargeUnderTemperatureProtection: string; + }; + + AlarmEvent4: { + CellLowTemperatureHeating: string; + EnvironmentHighTemperatureAlarm: string; + EnvironmentLowTemperatureAlarm: string; + EnvironmentOverTemperatureProtection: string; + EnvironmentUnderTemperatureProtection: string; + PowerHighTemperatureAlarm: string; + PowerOverTemperatureProtection: string; + }; + + AlarmEvent5: { + ChargeOverCurrentAlarm: string; + ChargeOverCurrentProtection: string; + DischargeOverCurrentAlarm: string; + DischargeOverCurrentProtection: string; + OutputShortCircuitLockout: string; + OutputShortCircuitProtection: string; + TransientOverCurrentLockout: string; + TransientOverCurrentProtection: string; + }; + + AlarmEvent6: { + CellLowVoltageChargingProhibition: string; + ChargeHighVoltageProtection: string; + InsideBit: string; + IntermittentRechargeWaiting: string; + OutputConnectionFault: string; + OutputReversePolarityProtection: string; + ResidualCapacityAlarm: string; + ResidualCapacityProtection: string; + }; + + AlarmEvent7: { + AutomaticChargingWaiting: string; + InsideBit1: string; + InsideBit2: string; + InsideBit3: string; + InsideBit4: string; + InsideBit5: string; + InsideBit6: string; + ManualChargingWaiting: string; + }; + + AlarmEvent8: { + CurrentCalibrationNotPerformed: string; + EepStorageFault: string; + InsideBit1: string; + InsideBit2: string; + InsideBit3: string; + RtcError: string; + VoltageCalibrationNotPerformed: string; + ZeroCalibrationNotPerformed: string; + }; + + CellAlarmList: string; + CellTemperatureAlarm: string; + + SystemState: { + Charge: string; + Discharge: string; + FloatingCharge: string; + Shutdown: string; + Standby: string; + }; + }; + Leds: Leds; Eoc: boolean; Soc: number; diff --git a/typescript/frontend-marios2/src/interfaces/Chart.tsx b/typescript/frontend-marios2/src/interfaces/Chart.tsx index cd3e519ed..b3ccf5013 100644 --- a/typescript/frontend-marios2/src/interfaces/Chart.tsx +++ b/typescript/frontend-marios2/src/interfaces/Chart.tsx @@ -159,7 +159,7 @@ export const transformInputToBatteryViewDataJson = async ( ); const adjustedTimestamp = - product == 0 + product == 0 || product == 3 ? new Date(timestampArray[i] * 1000) : new Date(timestampArray[i] * 100000); //Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset @@ -185,19 +185,21 @@ export const transformInputToBatteryViewDataJson = async ( const battery_nodes = result.Config.Devices.BatteryNodes.toString().split(','); - // console.log(battery_nodes); - //Initialize the chartData structure based on the node names extracted from the first result let old_length = pathsToSave.length; if (battery_nodes.length > old_length) { battery_nodes.forEach((node) => { - if (!pathsToSave.includes('Node' + node)) { - pathsToSave.push('Node' + node); + const node_number = + product == 3 ? Number(node) + 1 : Number(node) - 1; + if (!pathsToSave.includes('Node' + node_number)) { + pathsToSave.push('Node' + node_number); } }); } + // console.log(pathsToSave); + if (initialiation) { initialiation = false; categories.forEach((category) => {