Compare commits
No commits in common. "main" and "v1.02" have entirely different histories.
|
|
@ -1,94 +0,0 @@
|
|||
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:
|
||||
|
||||
i) 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.
|
||||
|
||||
ii) Sampling Stepsize: The --sampling_stepsize parameter enables users to define the granularity of the time range for data extraction. By specifying the number
|
||||
of 1 minute intervals, users can adjust the sampling interval, allowing for flexible data retrieval based on time.
|
||||
Example Command:
|
||||
|
||||
python3 extractS3data.py 1749062721 1749106001 --keys AcDc/SystemControl/ResetAlarmsAndWarnings,AcDc/Devices/1/Status/Ac/L1/Voltage --bucket-number 12 --product_name=SodistoreMax --sampling_stepsize 2 --booleans_as_numbers
|
||||
|
||||
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.
|
||||
The script will fetch data in 2 minutes intervals
|
||||
|
||||
|
|
@ -1,937 +0,0 @@
|
|||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,266 +0,0 @@
|
|||
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
|
||||
|
||||
import subprocess
|
||||
|
||||
def list_files_in_range(start_timestamp, end_timestamp, sampling_stepsize, product_type, bucket_number):
|
||||
if product_type in ["Salimax", "SodistoreMax"]:
|
||||
hash = "3e5b3069-214a-43ee-8d85-57d72000c19d"
|
||||
elif product_type == "Salidomo":
|
||||
hash = "c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"
|
||||
else:
|
||||
raise ValueError("Invalid product type option.")
|
||||
|
||||
# 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 = []
|
||||
count=0
|
||||
|
||||
for f in files:
|
||||
name = f.split("/")[-1]
|
||||
timestamp_str = name.split(".")[0]
|
||||
|
||||
if timestamp_str.isdigit():
|
||||
timestamp = int(timestamp_str)
|
||||
|
||||
|
||||
if start_timestamp <= timestamp <= end_timestamp :
|
||||
if count % sampling_stepsize == 0:
|
||||
filenames.append(name)
|
||||
count += 1
|
||||
|
||||
|
||||
|
||||
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, 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 1 minute 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('--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
|
||||
# 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, product_type)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -14,7 +14,7 @@ I'll reroute my emails to one of you for software updates.
|
|||
|
||||
|
||||
MARIOS: Please make sure to patch out the vulnerable npm packages in the frontend.
|
||||
Get started on React or Testcafe Integration tests ;)
|
||||
And in my opinion, get started on React or Testcafe Integration tests ;)
|
||||
You can add them into the Gitea Actions Pipeline, read the documentation on Github-actions and integration tests.
|
||||
|
||||
Runner:
|
||||
|
|
|
|||
|
|
@ -226,12 +226,6 @@
|
|||
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="DataTypes\CsvName.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +0,0 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 17
|
||||
VisualStudioVersion = 17.5.002.0
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Backend", "Backend.csproj", "{161624D7-33B9-48B8-BA05-303DCFAEB03E}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{161624D7-33B9-48B8-BA05-303DCFAEB03E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{161624D7-33B9-48B8-BA05-303DCFAEB03E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{161624D7-33B9-48B8-BA05-303DCFAEB03E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{161624D7-33B9-48B8-BA05-303DCFAEB03E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {A377D3C5-E56D-4A16-AC4B-F3B0BB4CFCCE}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using InnovEnergy.App.Backend.Database;
|
||||
using InnovEnergy.App.Backend.DataTypes;
|
||||
using InnovEnergy.App.Backend.DataTypes.Methods;
|
||||
|
|
@ -8,32 +9,20 @@ using InnovEnergy.App.Backend.Relations;
|
|||
using InnovEnergy.App.Backend.Websockets;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace InnovEnergy.App.Backend;
|
||||
|
||||
using Token = String;
|
||||
|
||||
// create JobStatus class to track download battery log job
|
||||
public class JobStatus
|
||||
{
|
||||
public string JobId { get; set; }
|
||||
public string Status { get; set; }
|
||||
public string FileName { get; set; }
|
||||
public DateTime StartTime { get; set; }
|
||||
}
|
||||
|
||||
[Controller]
|
||||
[Route("api/")]
|
||||
//All the http requests from the frontend that contain "/api" will be forwarded to this controller from the nginx reverse proxy.
|
||||
public class Controller : ControllerBase
|
||||
{
|
||||
|
||||
[HttpPost(nameof(Login))]
|
||||
public ActionResult<Session> Login(String username, String? password)
|
||||
{
|
||||
//Find the user to the database, verify its password and create a new session.
|
||||
//Store the new session to the database and return it to the frontend.
|
||||
//If the user log out, the session will be deleted. Each session is valid for 24 hours. The db deletes all invalid/expired sessions every 30 minutes.
|
||||
var user = Db.GetUserByEmail(username);
|
||||
|
||||
if (user is null)
|
||||
|
|
@ -41,11 +30,14 @@ public class Controller : ControllerBase
|
|||
|
||||
if (!(user.Password.IsNullOrEmpty() && user.MustResetPassword) && !user.VerifyPassword(password))
|
||||
{
|
||||
//return Unauthorized("No Password set");
|
||||
throw new Exceptions(401, "Wrong Password Exception", "Please try again.", Request.Path.Value!);
|
||||
}
|
||||
|
||||
var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user));
|
||||
|
||||
//TODO The Frontend should check for the MustResetPassword Flag
|
||||
|
||||
return Db.Create(session)
|
||||
? session
|
||||
: throw new Exceptions(401,"Session Creation Exception", "Not allowed to log in.", Request.Path.Value!);
|
||||
|
|
@ -55,7 +47,6 @@ public class Controller : ControllerBase
|
|||
[HttpPost(nameof(Logout))]
|
||||
public ActionResult Logout(Token authToken)
|
||||
{
|
||||
//Find the session and delete it from the database.
|
||||
var session = Db.GetSession(authToken);
|
||||
|
||||
return session.Logout()
|
||||
|
|
@ -68,7 +59,6 @@ public class Controller : ControllerBase
|
|||
[HttpGet(nameof(CreateWebSocket))]
|
||||
public async Task CreateWebSocket(Token authToken)
|
||||
{
|
||||
//Everytime a user logs in, this function is called
|
||||
var session = Db.GetSession(authToken)?.User;
|
||||
|
||||
if (session is null)
|
||||
|
|
@ -87,8 +77,6 @@ public class Controller : ControllerBase
|
|||
return;
|
||||
}
|
||||
|
||||
//Create a websocket and pass its descriptor to the HandleWebSocketConnection method.
|
||||
//This descriptor is returned to the frontend on the background
|
||||
var webSocketContext = await HttpContext.WebSockets.AcceptWebSocketAsync();
|
||||
var webSocket = webSocketContext;
|
||||
|
||||
|
|
@ -115,24 +103,6 @@ public class Controller : ControllerBase
|
|||
.ToList();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetHistoryForInstallation))]
|
||||
public ActionResult<IEnumerable<UserAction>> GetHistoryForInstallation(Int64 id, Token authToken)
|
||||
{
|
||||
var user = Db.GetSession(authToken)?.User;
|
||||
if (user == null)
|
||||
return Unauthorized();
|
||||
|
||||
var installation = Db.GetInstallationById(id);
|
||||
|
||||
if (installation is null || !user.HasAccessTo(installation))
|
||||
return Unauthorized();
|
||||
|
||||
return Db.UserActions
|
||||
.Where(action =>action.InstallationId == id)
|
||||
.OrderByDescending(action => action.Timestamp)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetAllWarningsForInstallation))]
|
||||
public ActionResult<IEnumerable<Warning>> GetAllWarningsForInstallation(Int64 id, Token authToken)
|
||||
{
|
||||
|
|
@ -152,145 +122,6 @@ public class Controller : ControllerBase
|
|||
.ToList();
|
||||
}
|
||||
|
||||
|
||||
|
||||
[HttpGet(nameof(GetCsvTimestampsForInstallation))]
|
||||
public ActionResult<IEnumerable<Int64>> GetCsvTimestampsForInstallation(Int64 id, Int32 start, Int32 end, Token authToken)
|
||||
{
|
||||
var user = Db.GetSession(authToken)?.User;
|
||||
if (user == null)
|
||||
return Unauthorized();
|
||||
|
||||
var installation = Db.GetInstallationById(id);
|
||||
|
||||
if (installation is null || !user.HasAccessTo(installation))
|
||||
return Unauthorized();
|
||||
|
||||
var sampleSize = 100;
|
||||
var allTimestamps = new List<Int64>();
|
||||
|
||||
static string FindCommonPrefix(string str1, string str2)
|
||||
{
|
||||
int minLength = Math.Min(str1.Length, str2.Length);
|
||||
int i = 0;
|
||||
while (i < minLength && str1[i] == str2[i])
|
||||
{
|
||||
i++;
|
||||
}
|
||||
return str1.Substring(0, i);
|
||||
}
|
||||
|
||||
Int64 startTimestamp = Int64.Parse(start.ToString().Substring(0,5));
|
||||
Int64 endTimestamp = Int64.Parse(end.ToString().Substring(0,5));
|
||||
|
||||
if (installation.Product == (int)ProductType.Salidomo)
|
||||
{
|
||||
|
||||
start = Int32.Parse(start.ToString().Substring(0, start.ToString().Length - 2));
|
||||
end = Int32.Parse(end.ToString().Substring(0, end.ToString().Length - 2));
|
||||
}
|
||||
|
||||
string configPath = "/home/ubuntu/.s3cfg";
|
||||
|
||||
while (startTimestamp <= endTimestamp)
|
||||
{
|
||||
string bucketPath = installation.Product==(int)ProductType.Salimax || installation.Product==(int)ProductType.SodiStoreMax?
|
||||
"s3://"+installation.S3BucketId + "-3e5b3069-214a-43ee-8d85-57d72000c19d/"+startTimestamp :
|
||||
"s3://"+installation.S3BucketId + "-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e/"+startTimestamp;
|
||||
Console.WriteLine("Fetching data for "+startTimestamp);
|
||||
|
||||
try
|
||||
{
|
||||
// Set up process start info
|
||||
ProcessStartInfo startInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "s3cmd",
|
||||
Arguments = $"--config {configPath} ls {bucketPath}",
|
||||
RedirectStandardOutput = true,
|
||||
RedirectStandardError = true,
|
||||
UseShellExecute = false,
|
||||
CreateNoWindow = true
|
||||
};
|
||||
|
||||
// Start the process
|
||||
Process process = new Process
|
||||
{
|
||||
StartInfo = startInfo
|
||||
};
|
||||
|
||||
process.Start();
|
||||
|
||||
// Read the output
|
||||
string output = process.StandardOutput.ReadToEnd();
|
||||
string error = process.StandardError.ReadToEnd();
|
||||
|
||||
process.WaitForExit();
|
||||
|
||||
// Check for errors
|
||||
if (process.ExitCode != 0)
|
||||
{
|
||||
Console.WriteLine("Error executing command:");
|
||||
Console.WriteLine(error);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Define a regex pattern to match the filenames without .csv extension
|
||||
|
||||
var pattern = @"/([^/]+)\.(csv|json)$";
|
||||
var regex = new Regex(pattern);
|
||||
|
||||
// Process each line of the output
|
||||
foreach (var line in output.Split('\n'))
|
||||
{
|
||||
var match = regex.Match(line);
|
||||
|
||||
if (match.Success && long.Parse(match.Groups[1].Value) >= start && long.Parse(match.Groups[1].Value) <= end)
|
||||
{
|
||||
allTimestamps.Add(long.Parse(match.Groups[1].Value));
|
||||
//Console.WriteLine(match.Groups[1].Value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Exception: {e.Message}");
|
||||
}
|
||||
|
||||
startTimestamp++;
|
||||
}
|
||||
|
||||
int totalRecords = allTimestamps.Count;
|
||||
if (totalRecords <= sampleSize)
|
||||
{
|
||||
// If the total records are less than or equal to the sample size, return all records
|
||||
Console.WriteLine("Start timestamp = "+start +" end timestamp = "+end);
|
||||
Console.WriteLine("SampledTimestamps = " + allTimestamps.Count);
|
||||
return allTimestamps;
|
||||
}
|
||||
|
||||
int interval = totalRecords / sampleSize;
|
||||
var sampledTimestamps = new List<Int64>();
|
||||
|
||||
for (int i = 0; i < totalRecords; i += interval)
|
||||
{
|
||||
sampledTimestamps.Add(allTimestamps[i]);
|
||||
}
|
||||
|
||||
// If we haven't picked enough records (due to rounding), add the latest record to ensure completeness
|
||||
if (sampledTimestamps.Count < sampleSize)
|
||||
{
|
||||
sampledTimestamps.Add(allTimestamps.Last());
|
||||
}
|
||||
|
||||
Console.WriteLine("Start timestamp = "+start +" end timestamp = "+end);
|
||||
Console.WriteLine("TotalRecords = "+totalRecords + " interval = "+ interval);
|
||||
Console.WriteLine("SampledTimestamps = " + sampledTimestamps.Count);
|
||||
|
||||
return sampledTimestamps;
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetUserById))]
|
||||
public ActionResult<User> GetUserById(Int64 id, Token authToken)
|
||||
{
|
||||
|
|
@ -324,7 +155,7 @@ public class Controller : ControllerBase
|
|||
return installation
|
||||
.FillOrderNumbers()
|
||||
.HideParentIfUserHasNoAccessToParent(user)
|
||||
.HideWriteKeyIfUserIsNotAdmin(user.UserType);
|
||||
.HideWriteKeyIfUserIsNotAdmin(user.HasWriteAccess);
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetUsersWithDirectAccessToInstallation))]
|
||||
|
|
@ -346,18 +177,6 @@ public class Controller : ControllerBase
|
|||
.ToList();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetInstallationsTheUserHasAccess))]
|
||||
public ActionResult<IEnumerable<Object>> GetInstallationsTheUserHasAccess(Int64 userId, Token authToken)
|
||||
{
|
||||
var user = Db.GetUserById(userId);
|
||||
if (user == null)
|
||||
return Unauthorized();
|
||||
|
||||
|
||||
|
||||
return user.AccessibleInstallations().ToList();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetUsersWithInheritedAccessToInstallation))]
|
||||
public ActionResult<IEnumerable<Object>> GetUsersWithInheritedAccessToInstallation(Int64 id, Token authToken)
|
||||
{
|
||||
|
|
@ -456,20 +275,6 @@ public class Controller : ControllerBase
|
|||
}
|
||||
|
||||
|
||||
[HttpGet(nameof(GetAllInstallationsFromProduct))]
|
||||
public ActionResult<IEnumerable<Installation>> GetAllInstallationsFromProduct(int product,Token authToken)
|
||||
{
|
||||
var user = Db.GetSession(authToken)?.User;
|
||||
|
||||
if (user is null)
|
||||
return Unauthorized();
|
||||
|
||||
return user
|
||||
.AccessibleInstallations(product)
|
||||
.Select(i => i.FillOrderNumbers().HideParentIfUserHasNoAccessToParent(user).HideWriteKeyIfUserIsNotAdmin(user.UserType))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetAllInstallations))]
|
||||
public ActionResult<IEnumerable<Installation>> GetAllInstallations(Token authToken)
|
||||
{
|
||||
|
|
@ -479,33 +284,8 @@ public class Controller : ControllerBase
|
|||
return Unauthorized();
|
||||
|
||||
return user
|
||||
.AccessibleInstallations(product:(int)ProductType.Salimax)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetAllSalidomoInstallations))]
|
||||
public ActionResult<IEnumerable<Installation>> GetAllSalidomoInstallations(Token authToken)
|
||||
{
|
||||
var user = Db.GetSession(authToken)?.User;
|
||||
|
||||
if (user is null)
|
||||
return Unauthorized();
|
||||
|
||||
return user
|
||||
.AccessibleInstallations(product:(int)ProductType.Salidomo)
|
||||
.ToList();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetAllSodioHomeInstallations))]
|
||||
public ActionResult<IEnumerable<Installation>> GetAllSodioHomeInstallations(Token authToken)
|
||||
{
|
||||
var user = Db.GetSession(authToken)?.User;
|
||||
|
||||
if (user is null)
|
||||
return Unauthorized();
|
||||
|
||||
return user
|
||||
.AccessibleInstallations(product:(int)ProductType.SodioHome)
|
||||
.AccessibleInstallations()
|
||||
.Select(i => i.FillOrderNumbers().HideParentIfUserHasNoAccessToParent(user).HideWriteKeyIfUserIsNotAdmin(user.HasWriteAccess))
|
||||
.ToList();
|
||||
}
|
||||
|
||||
|
|
@ -524,14 +304,13 @@ public class Controller : ControllerBase
|
|||
|
||||
|
||||
[HttpGet(nameof(GetAllFoldersAndInstallations))]
|
||||
public ActionResult<IEnumerable<Object>> GetAllFoldersAndInstallations(int productId, Token authToken)
|
||||
public ActionResult<IEnumerable<Object>> GetAllFoldersAndInstallations(Token authToken)
|
||||
{
|
||||
var user = Db.GetSession(authToken)?.User;
|
||||
|
||||
if (user is null)
|
||||
return Unauthorized();
|
||||
|
||||
|
||||
var foldersAndInstallations = user
|
||||
.AccessibleFoldersAndInstallations()
|
||||
.Do(o => o.FillOrderNumbers())
|
||||
|
|
@ -547,26 +326,16 @@ public class Controller : ControllerBase
|
|||
[HttpPost(nameof(CreateUser))]
|
||||
public async Task<ActionResult<User>> CreateUser([FromBody] User newUser, Token authToken)
|
||||
{
|
||||
|
||||
var create = Db.GetSession(authToken).Create(newUser);
|
||||
if (create)
|
||||
{
|
||||
var mail_success= await Db.SendNewUserEmail(newUser);
|
||||
if (!mail_success)
|
||||
{
|
||||
Db.GetSession(authToken).Delete(newUser);
|
||||
}
|
||||
|
||||
return mail_success ? newUser.HidePassword():Unauthorized();
|
||||
}
|
||||
|
||||
return Unauthorized() ;
|
||||
return create && await Db.SendNewUserEmail(newUser)
|
||||
? newUser.HidePassword()
|
||||
: Unauthorized() ;
|
||||
}
|
||||
|
||||
[HttpPost(nameof(CreateInstallation))]
|
||||
public async Task<ActionResult<Installation>> CreateInstallation([FromBody] Installation installation, Token authToken)
|
||||
{
|
||||
|
||||
var session = Db.GetSession(authToken);
|
||||
|
||||
if (! await session.Create(installation))
|
||||
|
|
@ -678,12 +447,7 @@ public class Controller : ControllerBase
|
|||
if (!session.Update(installation))
|
||||
return Unauthorized();
|
||||
|
||||
if (installation.Product == (int)ProductType.Salimax)
|
||||
{
|
||||
return installation.FillOrderNumbers().HideParentIfUserHasNoAccessToParent(session!.User).HideWriteKeyIfUserIsNotAdmin(session.User.UserType);
|
||||
}
|
||||
|
||||
return installation.HideParentIfUserHasNoAccessToParent(session!.User);
|
||||
return installation.FillOrderNumbers().HideParentIfUserHasNoAccessToParent(session!.User).HideWriteKeyIfUserIsNotAdmin(session.User.HasWriteAccess);
|
||||
}
|
||||
|
||||
[HttpPost(nameof(AcknowledgeError))]
|
||||
|
|
@ -743,240 +507,17 @@ public class Controller : ControllerBase
|
|||
: Unauthorized();
|
||||
}
|
||||
|
||||
[HttpPost(nameof(UpdateFirmware))]
|
||||
public async Task<ActionResult> UpdateFirmware(Int64 batteryNode, Int64 installationId,String version,Token authToken)
|
||||
{
|
||||
var session = Db.GetSession(authToken);
|
||||
var installationToUpdate = Db.GetInstallationById(installationId);
|
||||
|
||||
|
||||
if (installationToUpdate != null)
|
||||
{
|
||||
_ = session.RunScriptInBackground(installationToUpdate.VpnIp, batteryNode,version,installationToUpdate.Product);
|
||||
}
|
||||
|
||||
return Ok();
|
||||
}
|
||||
|
||||
private static Dictionary<string, JobStatus> JobStatuses = new Dictionary<string, JobStatus>();
|
||||
|
||||
[HttpPost("StartDownloadBatteryLog")]
|
||||
public async Task<ActionResult<string>> StartDownloadBatteryLog(long batteryNode, long installationId, Token authToken)
|
||||
{
|
||||
var session = Db.GetSession(authToken);
|
||||
var installationToDownload = Db.GetInstallationById(installationId);
|
||||
|
||||
if (installationToDownload != null)
|
||||
{
|
||||
string jobId = Guid.NewGuid().ToString();
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await session.RunDownloadLogScript(installationToDownload.VpnIp, batteryNode, installationToDownload.Product);
|
||||
string fileName = $"{installationToDownload.VpnIp}-node{batteryNode}-{DateTime.Now:dd-MM-yyyy}.bin";
|
||||
string filePath = $"/home/ubuntu/backend/downloadBatteryLog/{fileName}";
|
||||
|
||||
if (System.IO.File.Exists(filePath))
|
||||
{
|
||||
SaveJobStatus(jobId, "Completed", fileName:fileName);
|
||||
}
|
||||
else
|
||||
{
|
||||
SaveJobStatus(jobId, "Failed");
|
||||
}
|
||||
});
|
||||
|
||||
// Store initial job status in in-memory storage
|
||||
SaveJobStatus(jobId, "Processing");
|
||||
|
||||
return Ok(jobId);
|
||||
}
|
||||
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
[HttpGet("DownloadBatteryLog")]
|
||||
public async Task<ActionResult> DownloadBatteryLog(string jobId)
|
||||
|
||||
{
|
||||
Console.WriteLine("-----------------------------------Start uploading battery log-----------------------------------");
|
||||
var jobStatus = JobStatuses.TryGetValue(jobId, out var status) ? status : null;
|
||||
if (jobStatus == null || jobStatus.Status != "Completed" || string.IsNullOrEmpty(jobStatus.FileName))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
string fileName = jobStatus.FileName;
|
||||
string filePath = $"/home/ubuntu/backend/downloadBatteryLog/{fileName}";
|
||||
|
||||
if (!System.IO.File.Exists(filePath))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
string contentType = "application/octet-stream";
|
||||
var memory = new MemoryStream();
|
||||
await using (var stream = new FileStream(filePath, FileMode.Open))
|
||||
{
|
||||
await stream.CopyToAsync(memory);
|
||||
}
|
||||
memory.Position = 0;
|
||||
|
||||
var fileContentResult = new FileContentResult(memory.ToArray(), contentType)
|
||||
{
|
||||
//FileDownloadName = Path.GetFileName(filePath)
|
||||
FileDownloadName = fileName
|
||||
};
|
||||
|
||||
Console.WriteLine("-----------------------------------Stop uploading battery log-----------------------------------");
|
||||
|
||||
return fileContentResult;
|
||||
}
|
||||
|
||||
[HttpDelete("DeleteBatteryLog")]
|
||||
public IActionResult DeleteBatteryLog(string fileName)
|
||||
{
|
||||
Console.WriteLine("-----------------------------------Start deleting downloaded battery log-----------------------------------");
|
||||
string filePath = $"/home/ubuntu/backend/downloadBatteryLog/{fileName}";
|
||||
try
|
||||
{
|
||||
if (System.IO.File.Exists(filePath))
|
||||
{
|
||||
System.IO.File.Delete(filePath);
|
||||
Console.WriteLine("-----------------------------------Stop deleting downloaded battery log-----------------------------------");
|
||||
return Ok();
|
||||
}
|
||||
else
|
||||
{
|
||||
return NotFound("File not found.");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(500, $"Internal server error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveJobStatus(string jobId, string status, string fileName = null)
|
||||
{
|
||||
JobStatuses[jobId] = new JobStatus
|
||||
{
|
||||
JobId = jobId,
|
||||
Status = status,
|
||||
FileName = fileName,
|
||||
StartTime = DateTime.UtcNow // Initialize StartTime when saving
|
||||
};
|
||||
}
|
||||
|
||||
[HttpGet("GetJobResult")]
|
||||
public ActionResult GetJobResult(string jobId)
|
||||
{
|
||||
if (string.IsNullOrEmpty(jobId))
|
||||
{
|
||||
return BadRequest(new { status = "Error", message = "Job ID is required." });
|
||||
}
|
||||
|
||||
if (!JobStatuses.TryGetValue(jobId, out var jobStatus))
|
||||
{
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
if (jobStatus.Status == "Completed")
|
||||
{
|
||||
return Ok(new { status = "Completed", fileName = jobStatus.FileName });
|
||||
}
|
||||
else if (jobStatus.Status == "Failed")
|
||||
{
|
||||
return StatusCode(500, new { status = "Failed", message = "Job processing failed." });
|
||||
}
|
||||
else if (jobStatus.Status == "Processing")
|
||||
{
|
||||
// Check for timeout
|
||||
var startTime = jobStatus.StartTime;
|
||||
var currentTime = DateTime.UtcNow;
|
||||
|
||||
if ((currentTime - startTime).TotalMinutes > 60)//60 minutes as timeout => Running multiple tasks in parallel on a crowded backend server will increase the time each task takes to complete
|
||||
{
|
||||
return StatusCode(500, new { status = "Failed", message = "Job in back end timeout exceeded." });
|
||||
}
|
||||
|
||||
return Ok(new { status = "Processing" });
|
||||
}
|
||||
else
|
||||
{
|
||||
return BadRequest(new { status = "Unknown", message = "Unknown job status." });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[HttpPost(nameof(InsertNewAction))]
|
||||
public async Task<ActionResult<IEnumerable<Object>>> InsertNewAction([FromBody] UserAction action, Token authToken)
|
||||
{
|
||||
var session = Db.GetSession(authToken);
|
||||
var actionSuccess = await session.InsertUserAction(action);
|
||||
return actionSuccess ? Ok() : Unauthorized();
|
||||
|
||||
}
|
||||
|
||||
[HttpPost(nameof(UpdateAction))]
|
||||
public async Task<ActionResult<IEnumerable<Object>>> UpdateAction([FromBody] UserAction action, Token authToken)
|
||||
{
|
||||
var session = Db.GetSession(authToken);
|
||||
var actionSuccess = await session.UpdateUserAction(action);
|
||||
return actionSuccess ? Ok() : Unauthorized();
|
||||
}
|
||||
|
||||
|
||||
[HttpPost(nameof(DeleteAction))]
|
||||
public async Task<ActionResult<IEnumerable<Object>>> DeleteAction(Int64 actionId, Token authToken)
|
||||
{
|
||||
var session = Db.GetSession(authToken);
|
||||
var actionSuccess = await session.DeleteUserAction(actionId);
|
||||
return actionSuccess ? Ok() : Unauthorized();
|
||||
}
|
||||
|
||||
|
||||
[HttpPost(nameof(EditInstallationConfig))]
|
||||
public async Task<ActionResult<IEnumerable<Object>>> EditInstallationConfig([FromBody] Configuration config, Int64 installationId,int product,Token authToken)
|
||||
public async Task<ActionResult<IEnumerable<Object>>> EditInstallationConfig([FromBody] String config, Int64 installationId, Token authToken)
|
||||
{
|
||||
var session = Db.GetSession(authToken);
|
||||
|
||||
string configString = product switch
|
||||
{
|
||||
0 => config.GetConfigurationSalimax(), // Salimax
|
||||
3 => config.GetConfigurationSodistoreMax(), // SodiStoreMax
|
||||
2 => config.GetConfigurationSodistoreHome(), // SodiStoreHome
|
||||
_ => config.GetConfigurationString() // fallback
|
||||
};
|
||||
|
||||
Console.WriteLine("CONFIG IS " + configString);
|
||||
|
||||
//Send configuration changes
|
||||
var success = await session.SendInstallationConfig(installationId, config);
|
||||
|
||||
// Record configuration change
|
||||
if (success)
|
||||
{
|
||||
// // Update Configuration colum in Installation table
|
||||
// var installation = Db.GetInstallationById(installationId);
|
||||
//
|
||||
// installation.Configuration = JsonConvert.SerializeObject(config);
|
||||
//
|
||||
// if (!installation.Apply(Db.Update))
|
||||
// return StatusCode(500, "Failed to update installation configuration in database");
|
||||
|
||||
var action = new UserAction
|
||||
{
|
||||
InstallationId = installationId,
|
||||
Timestamp = DateTime.UtcNow,
|
||||
Description = configString
|
||||
};
|
||||
|
||||
var actionSuccess = await session.InsertUserAction(action);
|
||||
return actionSuccess ? Ok() : StatusCode(500, "Failed to record Configuration changes in History of Action");
|
||||
}
|
||||
|
||||
return Unauthorized();
|
||||
//var installationToUpdate = Db.GetInstallationById(installationId);
|
||||
|
||||
return await session.SendInstallationConfig(installationId, config)
|
||||
? Ok()
|
||||
: Unauthorized();
|
||||
}
|
||||
|
||||
[HttpPut(nameof(MoveFolder))]
|
||||
|
|
@ -1050,7 +591,7 @@ public class Controller : ControllerBase
|
|||
|
||||
Db.DeleteUserPassword(user);
|
||||
|
||||
return Redirect($"https://monitor.inesco.energy/?username={user.Email}&reset=true"); // TODO: move to settings file
|
||||
return Redirect($"https://monitor.innov.energy/?username={user.Email}&reset=true"); // TODO: move to settings file
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
namespace InnovEnergy.App.Backend.DataTypes;
|
||||
|
||||
public class Configuration
|
||||
{
|
||||
public double? MinimumSoC { get; set; }
|
||||
public double? GridSetPoint { get; set; }
|
||||
public CalibrationChargeType? CalibrationChargeState { get; set; }
|
||||
public DateTime? CalibrationChargeDate { get; set; }
|
||||
public CalibrationChargeType? CalibrationDischargeState { get; set; }
|
||||
public DateTime? CalibrationDischargeDate { get; set; }
|
||||
|
||||
public double? MaximumDischargingCurrent { get; set; }
|
||||
public double? MaximumChargingCurrent { get; set; }
|
||||
public double? OperatingPriority { get; set; }
|
||||
public double? BatteriesCount { get; set; }
|
||||
public double? ClusterNumber { get; set; }
|
||||
public double? PvNumber { get; set; }
|
||||
public bool ControlPermission { get; set; }
|
||||
|
||||
public String GetConfigurationString()
|
||||
{
|
||||
return $"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}, " +
|
||||
$"CalibrationDischargeState: {CalibrationDischargeState}, CalibrationDischargeDate: {CalibrationDischargeDate}, " +
|
||||
$"MaximumDischargingCurrent: {MaximumDischargingCurrent}, MaximumChargingCurrent: {MaximumChargingCurrent}, OperatingPriority: {OperatingPriority}, " +
|
||||
$"BatteriesCount: {BatteriesCount}, ClusterNumber: {ClusterNumber}, PvNumber: {PvNumber}, GrowattControlPermission:{ControlPermission}";
|
||||
|
||||
}
|
||||
|
||||
public string GetConfigurationSalimax()
|
||||
{
|
||||
return
|
||||
$"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}";
|
||||
}
|
||||
|
||||
public string GetConfigurationSodistoreMax()
|
||||
{
|
||||
return
|
||||
$"MinimumSoC: {MinimumSoC}, GridSetPoint: {GridSetPoint}, CalibrationChargeState: {CalibrationChargeState}, CalibrationChargeDate: {CalibrationChargeDate}";
|
||||
}
|
||||
|
||||
public string GetConfigurationSodistoreHome()
|
||||
{
|
||||
return $"MinimumSoC: {MinimumSoC}, MaximumDischargingCurrent: {MaximumDischargingCurrent}, MaximumChargingCurrent: {MaximumChargingCurrent}, OperatingPriority: {OperatingPriority}, " +
|
||||
$"BatteriesCount: {BatteriesCount}, ClusterNumber: {ClusterNumber}, PvNumber: {PvNumber}, GrowattControlPermission:{ControlPermission}";
|
||||
}
|
||||
}
|
||||
|
||||
public enum CalibrationChargeType
|
||||
{
|
||||
RepetitivelyEvery,
|
||||
AdditionallyOnce,
|
||||
ChargePermanently
|
||||
}
|
||||
|
|
@ -1,3 +1,3 @@
|
|||
namespace InnovEnergy.App.Backend.DataTypes;
|
||||
|
||||
public class Folder : TreeNode { }
|
||||
public class Folder : TreeNode {}
|
||||
|
|
@ -2,32 +2,20 @@ using SQLite;
|
|||
|
||||
namespace InnovEnergy.App.Backend.DataTypes;
|
||||
|
||||
public enum ProductType
|
||||
{
|
||||
Salimax = 0,
|
||||
Salidomo = 1,
|
||||
SodioHome =2,
|
||||
SodiStoreMax=3
|
||||
}
|
||||
|
||||
public enum StatusType
|
||||
{
|
||||
Offline = -1,
|
||||
Green = 0,
|
||||
Warning = 1,
|
||||
Alarm = 2
|
||||
}
|
||||
|
||||
public class Installation : TreeNode
|
||||
{
|
||||
//Each installation has 2 roles, a read role and a write role.
|
||||
//There are 2 keys per role a public key and a secret
|
||||
//Product can be 0 or 1, 0 for Salimax, 1 for Salidomo
|
||||
public String Location { get; set; } = "";
|
||||
public String Region { get; set; } = "";
|
||||
public String Country { get; set; } = "";
|
||||
public String VpnIp { get; set; } = "";
|
||||
public String InstallationName { get; set; } = "";
|
||||
public String VpnIp { get; set; } = "";
|
||||
|
||||
// TODO: make relation
|
||||
//public IReadOnlyList<String> OrderNumbers { get; set; } = Array.Empty<String>();
|
||||
// public String? OrderNumbers { get; set; } = "";
|
||||
|
||||
public Double Lat { get; set; }
|
||||
public Double Long { get; set; }
|
||||
|
||||
public String S3Region { get; set; } = "sos-ch-dk-2";
|
||||
public String S3Provider { get; set; } = "exo.io";
|
||||
|
|
@ -35,20 +23,12 @@ public class Installation : TreeNode
|
|||
public String S3Key { get; set; } = "";
|
||||
public String S3WriteSecret { get; set; } = "";
|
||||
public String S3Secret { get; set; } = "";
|
||||
public int S3BucketId { get; set; } = 0;
|
||||
public String ReadRoleId { get; set; } = "";
|
||||
public String WriteRoleId { get; set; } = "";
|
||||
public Boolean TestingMode { get; set; } = false;
|
||||
public int Status { get; set; } = -1;
|
||||
public int Product { get; set; } = (int)ProductType.Salimax;
|
||||
public int Device { get; set; } = 0;
|
||||
public string SerialNumber { get; set; } = "";
|
||||
public string InverterSN { get; set; } = "";
|
||||
public string DataloggerSN { get; set; } = "";
|
||||
public int BatteryClusterNumber { get; set; } = 0;
|
||||
|
||||
|
||||
[Ignore]
|
||||
public String OrderNumbers { get; set; }
|
||||
public String VrmLink { get; set; } = "";
|
||||
public string Configuration { get; set; } = "";
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -4,7 +4,6 @@ using InnovEnergy.Lib.S3Utils.DataTypes;
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.Json.Nodes;
|
||||
using InnovEnergy.App.Backend.Database;
|
||||
|
|
@ -54,34 +53,10 @@ public static class ExoCmd
|
|||
return BuildSignature(method, path, null, time);
|
||||
}
|
||||
|
||||
public static async Task<JsonArray> GetAccessKeys()
|
||||
{
|
||||
|
||||
var url = "https://api-ch-dk-2.exoscale.com/v2/api-key";
|
||||
var method = "api-key";
|
||||
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
|
||||
|
||||
var authheader = "credential="+S3Credentials.Key+",signed-query-args="+",expires="+unixtime+",signature="+BuildSignature("GET", method, unixtime);
|
||||
|
||||
var client = new HttpClient();
|
||||
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
|
||||
|
||||
var response = await client.GetAsync(url);
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var responseJson = JsonNode.Parse(responseString) ;
|
||||
Console.WriteLine(responseJson["api-keys"].AsArray().Count);
|
||||
|
||||
return responseJson["api-keys"].AsArray();
|
||||
}
|
||||
|
||||
public static async Task<(String,String)> CreateReadKey(this Installation installation)
|
||||
{
|
||||
var readRoleId = installation.ReadRoleId;
|
||||
|
||||
|
||||
if (String.IsNullOrEmpty(readRoleId)
|
||||
||! await CheckRoleExists(readRoleId))
|
||||
{
|
||||
|
|
@ -114,10 +89,11 @@ public static class ExoCmd
|
|||
{
|
||||
var url = "https://api-ch-dk-2.exoscale.com/v2/api-key";
|
||||
var method = "api-key";
|
||||
var contentString = $$"""{"role-id": "{{roleName}}", "name":"{{ installation.BucketName()}}"}""";
|
||||
var contentString = $$"""{"role-id": "{{roleName}}", "name":"{{installation.BucketName()}}"}""";
|
||||
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60;
|
||||
|
||||
|
||||
|
||||
var authheader = "credential=" + S3Credentials.Key + ",expires=" + unixtime + ",signature=" +
|
||||
BuildSignature("POST", method, contentString, unixtime);
|
||||
|
||||
|
|
@ -131,7 +107,7 @@ public static class ExoCmd
|
|||
if (response.StatusCode != HttpStatusCode.OK){
|
||||
Console.WriteLine("Fuck");
|
||||
}
|
||||
//Console.WriteLine($"Created Key for {installation.InstallationName}");
|
||||
//Console.WriteLine($"Created Key for {installation.Name}");
|
||||
var responseString = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var responseJson = JsonNode.Parse(responseString) ;
|
||||
|
|
@ -143,14 +119,10 @@ public static class ExoCmd
|
|||
{
|
||||
const String url = "https://api-ch-dk-2.exoscale.com/v2/iam-role";
|
||||
const String method = "iam-role";
|
||||
String rolename = installation.Product==(int)ProductType.Salimax?Db.Installations.Count(f => f.Product == (int)ProductType.Salimax) + installation.Name:
|
||||
installation.Product==(int)ProductType.SodiStoreMax?Db.Installations.Count(f => f.Product == (int)ProductType.SodiStoreMax) + installation.Name:
|
||||
Db.Installations.Count(f => f.Product == (int)ProductType.Salidomo) + installation.Name;
|
||||
|
||||
|
||||
var contentString = $$"""
|
||||
{
|
||||
"name" : "{{rolename}}",
|
||||
"name" : "{{installation.Id + installation.Name}}",
|
||||
"policy" : {
|
||||
"default-service-strategy": "deny",
|
||||
"services": {
|
||||
|
|
@ -169,6 +141,7 @@ public static class ExoCmd
|
|||
""";
|
||||
|
||||
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
|
||||
|
||||
var authheader = "credential="+S3Credentials.Key+",signed-query-args="+",expires="+unixtime+",signature="+BuildSignature("POST", method, contentString, unixtime);
|
||||
|
||||
var client = new HttpClient();
|
||||
|
|
@ -190,81 +163,8 @@ public static class ExoCmd
|
|||
return id;
|
||||
}
|
||||
|
||||
public static async Task<bool> RemoveReadRole(this Installation installation)
|
||||
{
|
||||
var roleId = installation.ReadRoleId;
|
||||
var url = $"https://api-ch-dk-2.exoscale.com/v2/iam-role/{roleId}";
|
||||
var method = $"iam-role/{roleId}";
|
||||
|
||||
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60;
|
||||
var authheader = "credential=" + S3Credentials.Key + ",expires=" + unixtime + ",signature=" + BuildSignature("DELETE", method, unixtime);
|
||||
|
||||
|
||||
var client = new HttpClient();
|
||||
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
|
||||
|
||||
try
|
||||
{
|
||||
var response = await client.DeleteAsync(url);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine($"Successfully deleted read role with ID {roleId}.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Failed to delete read role. HTTP Status: {response.StatusCode}. Response: {await response.Content.ReadAsStringAsync()}");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error occurred while deleting read role: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<bool> RemoveWriteRole(this Installation installation)
|
||||
{
|
||||
var roleId = installation.WriteRoleId;
|
||||
var url = $"https://api-ch-dk-2.exoscale.com/v2/iam-role/{roleId}";
|
||||
var method = $"iam-role/{roleId}";
|
||||
|
||||
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds() + 60;
|
||||
var authheader = "credential=" + S3Credentials.Key + ",expires=" + unixtime + ",signature=" + BuildSignature("DELETE", method, unixtime);
|
||||
|
||||
|
||||
var client = new HttpClient();
|
||||
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
|
||||
|
||||
try
|
||||
{
|
||||
var response = await client.DeleteAsync(url);
|
||||
|
||||
if (response.IsSuccessStatusCode)
|
||||
{
|
||||
Console.WriteLine($"Successfully deleted write role with ID {roleId}.");
|
||||
return true;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Failed to delete write role. HTTP Status: {response.StatusCode}. Response: {await response.Content.ReadAsStringAsync()}");
|
||||
return false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Error occurred while deleting write role: {ex.Message}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static async Task<Boolean> RevokeReadKey(this Installation installation)
|
||||
{
|
||||
//Check exoscale documentation https://openapi-v2.exoscale.com/topic/topic-api-request-signature
|
||||
|
||||
var url = $"https://api-ch-dk-2.exoscale.com/v2/access-key/{installation.S3Key}";
|
||||
var method = $"access-key/{installation.S3Key}";
|
||||
|
||||
|
|
@ -281,26 +181,6 @@ public static class ExoCmd
|
|||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
|
||||
public static async Task<Boolean> RevokeWriteKey(this Installation installation)
|
||||
{
|
||||
//Check exoscale documentation https://openapi-v2.exoscale.com/topic/topic-api-request-signature
|
||||
|
||||
var url = $"https://api-ch-dk-2.exoscale.com/v2/access-key/{installation.S3WriteKey}";
|
||||
var method = $"access-key/{installation.S3WriteKey}";
|
||||
|
||||
var unixtime = DateTimeOffset.UtcNow.ToUnixTimeSeconds()+60;
|
||||
|
||||
var authheader = "credential="+S3Credentials.Key+",expires="+unixtime+",signature="+BuildSignature("DELETE", method, unixtime);
|
||||
|
||||
var client = new HttpClient();
|
||||
|
||||
client.DefaultRequestHeaders.Authorization =
|
||||
new AuthenticationHeaderValue("EXO2-HMAC-SHA256", authheader);
|
||||
|
||||
var response = await client.DeleteAsync(url);
|
||||
return response.IsSuccessStatusCode;
|
||||
}
|
||||
|
||||
public static async Task<(String, String)> CreateWriteKey(this Installation installation)
|
||||
{
|
||||
var writeRoleId = installation.WriteRoleId;
|
||||
|
|
@ -318,13 +198,10 @@ public static class ExoCmd
|
|||
{
|
||||
const String url = "https://api-ch-dk-2.exoscale.com/v2/iam-role";
|
||||
const String method = "iam-role";
|
||||
String rolename = installation.Product==(int)ProductType.Salimax?Db.Installations.Count(f => f.Product == (int)ProductType.Salimax) + installation.Name:
|
||||
installation.Product==(int)ProductType.SodiStoreMax?Db.Installations.Count(f => f.Product == (int)ProductType.SodiStoreMax) + installation.Name:
|
||||
Db.Installations.Count(f => f.Product == (int)ProductType.Salidomo) + installation.Name;
|
||||
|
||||
var contentString = $$"""
|
||||
{
|
||||
"name" : "WRITE{{rolename}}",
|
||||
"name" : "WRITE{{installation.Id + installation.Name}}",
|
||||
"policy" : {
|
||||
"default-service-strategy": "deny",
|
||||
"services": {
|
||||
|
|
@ -371,53 +248,34 @@ public static class ExoCmd
|
|||
return await s3Region.PutBucket(installation.BucketName()) != null;
|
||||
}
|
||||
|
||||
public static async Task<Boolean> DeleteBucket(this Installation installation)
|
||||
public static async Task<Boolean> SendConfig(this Installation installation, String config)
|
||||
{
|
||||
|
||||
// This looks hacky but here we grab the vpn-Ip of the installation by its installation Name (e.g. Salimax0001)
|
||||
// From the vpn server (here salidomo, but we use the vpn home ip for future-proofing)
|
||||
// using var client = new HttpClient();
|
||||
// var webRequest = client.GetAsync("10.2.0.1/vpnstatus.txt");
|
||||
// var text = webRequest.ToString();
|
||||
// var lines = text!.Split(new [] { Environment.NewLine }, StringSplitOptions.None);
|
||||
// var vpnIp = lines.First(l => l.Contains(installation.InstallationName)).Split(",")[1];
|
||||
//
|
||||
// // Writing the config to a file and then sending that file with rsync sounds inefficient
|
||||
// // We should find a better solution...
|
||||
// // TODO The VPN server should do this not the backend!!!
|
||||
// await File.WriteAllTextAsync("./config.json", config);
|
||||
// var result = await Cli.Wrap("rsync")
|
||||
// .WithArguments("./config.json")
|
||||
// .AppendArgument($@"root@{vpnIp}:/salimax")
|
||||
// .ExecuteAsync();
|
||||
|
||||
// return result.ExitCode == 200;
|
||||
|
||||
|
||||
var s3Region = new S3Region($"https://{installation.S3Region}.{installation.S3Provider}", S3Credentials!);
|
||||
return await s3Region.DeleteBucket(installation.BucketName()) ;
|
||||
var url = s3Region.Bucket(installation.BucketName()).Path("config.json");
|
||||
return await url.PutObject(config);
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static async Task<Boolean> SendConfig(this Installation installation, Configuration config)
|
||||
{
|
||||
|
||||
var maxRetransmissions = 4;
|
||||
UdpClient udpClient = new UdpClient();
|
||||
udpClient.Client.ReceiveTimeout = 2000;
|
||||
int port = 9000;
|
||||
|
||||
Console.WriteLine("Trying to reach installation with IP: " + installation.VpnIp);
|
||||
//Try at most MAX_RETRANSMISSIONS times to reach an installation.
|
||||
for (int j = 0; j < maxRetransmissions; j++)
|
||||
{
|
||||
//string message = "This is a message from RabbitMQ server, you can subscribe to the RabbitMQ queue";
|
||||
byte[] data = Encoding.UTF8.GetBytes(JsonSerializer.Serialize<Configuration>(config));
|
||||
udpClient.Send(data, data.Length, installation.VpnIp, port);
|
||||
|
||||
Console.WriteLine(config.GetConfigurationString());
|
||||
|
||||
Console.WriteLine($"Sent UDP message to {installation.VpnIp}:{port}: {config}");
|
||||
//Console.WriteLine($"Sent UDP message to {installation.VpnIp}:{port}"+" GridSetPoint is "+config.GridSetPoint +" and MinimumSoC is "+config.MinimumSoC);
|
||||
|
||||
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse(installation.VpnIp), port);
|
||||
|
||||
try
|
||||
{
|
||||
byte[] replyData = udpClient.Receive(ref remoteEndPoint);
|
||||
string replyMessage = Encoding.UTF8.GetString(replyData);
|
||||
Console.WriteLine("Received " + replyMessage + " from installation " + installation.VpnIp);
|
||||
return true;
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
if (ex.SocketErrorCode == SocketError.TimedOut){Console.WriteLine("Timed out waiting for a response. Retry...");}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Error: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -41,9 +41,9 @@ public static class FolderMethods
|
|||
public static IEnumerable<Folder> UniqueChildFolders(this Folder parent)
|
||||
{
|
||||
|
||||
//var set = new HashSet<Folder>(Db.Folders, EqualityComparer<Folder>.Default);
|
||||
var set = new HashSet<Folder>(Db.Folders, EqualityComparer<Folder>.Default);
|
||||
|
||||
return ChildFolders(parent);
|
||||
return ChildFolders(parent).Where(set.Add);
|
||||
}
|
||||
|
||||
public static IEnumerable<Installation> ChildInstallations(this Folder parent)
|
||||
|
|
@ -53,11 +53,8 @@ public static class FolderMethods
|
|||
.Where(f => f.ParentId == parent.Id);
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<Folder> DescendantFolders(this Folder parent)
|
||||
{
|
||||
|
||||
Console.WriteLine("Parent is "+parent.Id+" looking for descendant folders");
|
||||
return parent
|
||||
.TraverseDepthFirstPreOrder(UniqueChildFolders)
|
||||
.Skip(1); // skip self
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
using System.Net;
|
||||
using System.Text.Json.Nodes;
|
||||
using CliWrap;
|
||||
using InnovEnergy.App.Backend.Database;
|
||||
using InnovEnergy.App.Backend.Relations;
|
||||
using InnovEnergy.Lib.S3Utils;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.App.Backend.DataTypes.Methods;
|
||||
|
|
@ -7,24 +11,14 @@ namespace InnovEnergy.App.Backend.DataTypes.Methods;
|
|||
|
||||
public static class InstallationMethods
|
||||
{
|
||||
private static readonly String BucketNameSalt = "3e5b3069-214a-43ee-8d85-57d72000c19d";
|
||||
private static readonly String SalidomoBucketNameSalt = "c0436b6a-d276-4cd8-9c44-1eae86cf5d0e";
|
||||
private static readonly String SodioHomeBucketNameSalt = "e7b9a240-3c5d-4d2e-a019-6d8b1f7b73fa";
|
||||
private static readonly String BucketNameSalt =
|
||||
Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") == ""
|
||||
? "stage"
|
||||
:"3e5b3069-214a-43ee-8d85-57d72000c19d";
|
||||
|
||||
public static String BucketName(this Installation installation)
|
||||
{
|
||||
if (installation.Product == (int)ProductType.Salimax || installation.Product == (int)ProductType.SodiStoreMax)
|
||||
{
|
||||
return $"{installation.S3BucketId}-{BucketNameSalt}";
|
||||
}
|
||||
|
||||
if (installation.Product == (int)ProductType.SodioHome)
|
||||
{
|
||||
return $"{installation.S3BucketId}-{SodioHomeBucketNameSalt}";
|
||||
}
|
||||
|
||||
return $"{installation.S3BucketId}-{SalidomoBucketNameSalt}";
|
||||
|
||||
return $"{installation.Id}-{BucketNameSalt}";
|
||||
}
|
||||
|
||||
public static async Task<Boolean> RenewS3Credentials(this Installation installation)
|
||||
|
|
@ -32,7 +26,6 @@ public static class InstallationMethods
|
|||
if(!installation.S3Key.IsNullOrEmpty())
|
||||
await installation.RevokeReadKey();
|
||||
|
||||
|
||||
var (key,secret) = await installation.CreateReadKey();
|
||||
|
||||
installation.S3Key = key;
|
||||
|
|
@ -49,6 +42,12 @@ public static class InstallationMethods
|
|||
}
|
||||
|
||||
|
||||
public static async Task<Boolean> DeleteBucket(this Installation installation)
|
||||
{
|
||||
// TODO We dont do this here
|
||||
return true;
|
||||
}
|
||||
|
||||
public static IEnumerable<User> UsersWithAccess(this Installation installation)
|
||||
{
|
||||
return installation
|
||||
|
|
@ -99,9 +98,9 @@ public static class InstallationMethods
|
|||
return Db.GetFolderById(installation.ParentId);
|
||||
}
|
||||
|
||||
public static Installation HideWriteKeyIfUserIsNotAdmin(this Installation installation, int userIsAdmin)
|
||||
public static Installation HideWriteKeyIfUserIsNotAdmin(this Installation installation, Boolean userIsAdmin)
|
||||
{
|
||||
if(userIsAdmin==2)
|
||||
if(userIsAdmin)
|
||||
return installation;
|
||||
|
||||
installation.S3WriteKey = "";
|
||||
|
|
|
|||
|
|
@ -1,9 +1,6 @@
|
|||
using System.Diagnostics;
|
||||
using InnovEnergy.App.Backend.Database;
|
||||
using InnovEnergy.App.Backend.Relations;
|
||||
using InnovEnergy.App.Backend.Websockets;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using Org.BouncyCastle.Asn1.X509;
|
||||
|
||||
namespace InnovEnergy.App.Backend.DataTypes.Methods;
|
||||
|
||||
|
|
@ -15,7 +12,7 @@ public static class SessionMethods
|
|||
|
||||
return user is not null
|
||||
&& folder is not null
|
||||
&& user.UserType!=0
|
||||
&& user.HasWriteAccess
|
||||
&& user.HasAccessTo(folder.Parent())
|
||||
&& Db.Create(folder) // TODO: these two in a transaction
|
||||
&& Db.Create(new FolderAccess { UserId = user.Id, FolderId = folder.Id });
|
||||
|
|
@ -29,7 +26,7 @@ public static class SessionMethods
|
|||
return user is not null
|
||||
&& folder is not null
|
||||
&& original is not null
|
||||
&& user.UserType !=0
|
||||
&& user.HasWriteAccess
|
||||
&& user.HasAccessTo(folder)
|
||||
&& folder
|
||||
.WithParentOf(original) // prevent moving
|
||||
|
|
@ -44,7 +41,7 @@ public static class SessionMethods
|
|||
|
||||
return user is not null
|
||||
&& folder is not null
|
||||
&& user.UserType !=0
|
||||
&& user.HasWriteAccess
|
||||
&& user.HasAccessTo(folder)
|
||||
&& user.HasAccessTo(parent)
|
||||
&& folder
|
||||
|
|
@ -61,7 +58,7 @@ public static class SessionMethods
|
|||
if(installation == null || installation.ParentId == parentId) return false;
|
||||
|
||||
return user is not null
|
||||
&& user.UserType !=0
|
||||
&& user.HasWriteAccess
|
||||
&& user.HasAccessTo(installation)
|
||||
&& user.HasAccessTo(parent)
|
||||
&& installation
|
||||
|
|
@ -69,135 +66,25 @@ public static class SessionMethods
|
|||
.Apply(Db.Update);
|
||||
}
|
||||
|
||||
public static async Task RunScriptInBackground(this Session? session, String vpnIp, Int64 batteryNode,String version,Int64 product)
|
||||
{
|
||||
Console.WriteLine("-----------------------------------Start updating firmware-----------------------------------");
|
||||
string scriptPath = (product == (int)ProductType.Salimax)
|
||||
? "/home/ubuntu/backend/uploadBatteryFw/update_firmware_Salimax.sh"
|
||||
: "/home/ubuntu/backend/uploadBatteryFw/update_firmware_Salidomo.sh";
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
var process = new Process();
|
||||
process.StartInfo.FileName = "/bin/bash";
|
||||
process.StartInfo.Arguments = $"{scriptPath} {vpnIp} {batteryNode} {version}";
|
||||
process.StartInfo.UseShellExecute = false;
|
||||
process.StartInfo.RedirectStandardOutput = true;
|
||||
|
||||
process.Start();
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
Console.WriteLine(output);
|
||||
});
|
||||
Console.WriteLine("-----------------------------------Stop updating firmware-----------------------------------");
|
||||
}
|
||||
|
||||
public static async Task RunDownloadLogScript(this Session? session, String vpnIp, Int64 batteryNode,Int64 product)
|
||||
{
|
||||
Console.WriteLine("-----------------------------------Start downloading battery log-----------------------------------");
|
||||
string scriptPath = (product == (int)ProductType.Salimax)
|
||||
? "/home/ubuntu/backend/downloadBatteryLog/download_bms_log_Salimax.sh"
|
||||
: "/home/ubuntu/backend/downloadBatteryLog/download_bms_log_Salidomo.sh";
|
||||
|
||||
await Task.Run(() =>
|
||||
{
|
||||
var process = new Process
|
||||
{
|
||||
StartInfo = new ProcessStartInfo
|
||||
{
|
||||
FileName = "/bin/bash",
|
||||
Arguments = $"{scriptPath} {vpnIp} {batteryNode}",
|
||||
UseShellExecute = false,
|
||||
RedirectStandardOutput = true
|
||||
}
|
||||
};
|
||||
|
||||
process.Start();
|
||||
var output = process.StandardOutput.ReadToEnd();
|
||||
process.WaitForExit();
|
||||
Console.WriteLine(output);
|
||||
});
|
||||
Console.WriteLine("-----------------------------------Stop downloading battery log-----------------------------------");
|
||||
}
|
||||
|
||||
public static async Task<Boolean> SendInstallationConfig(this Session? session, Int64 installationId, Configuration configuration)
|
||||
public static async Task<Boolean> SendInstallationConfig(this Session? session, Int64 installationId, String configuration)
|
||||
{
|
||||
var user = session?.User;
|
||||
var installation = Db.GetInstallationById(installationId);
|
||||
|
||||
return user is not null
|
||||
&& installation is not null
|
||||
&& user.UserType !=0
|
||||
&& user.HasWriteAccess
|
||||
&& user.HasAccessTo(installation)
|
||||
&& await installation.SendConfig(configuration);
|
||||
}
|
||||
|
||||
public static async Task<Boolean> InsertUserAction(this Session? session, UserAction action)
|
||||
{
|
||||
var user = session?.User;
|
||||
|
||||
if (user is null || user.UserType == 0)
|
||||
return false;
|
||||
|
||||
action.UserName = user.Name;
|
||||
|
||||
var installation = Db.GetInstallationById(action.InstallationId);
|
||||
installation.TestingMode = action.TestingMode;
|
||||
installation.Apply(Db.Update);
|
||||
WebsocketManager.InformWebsocketsForInstallation(action.InstallationId);
|
||||
|
||||
// Save the configuration change to the database
|
||||
Db.HandleAction(action);
|
||||
return true;
|
||||
}
|
||||
|
||||
public static async Task<Boolean> UpdateUserAction(this Session? session, UserAction action)
|
||||
{
|
||||
var user = session?.User;
|
||||
|
||||
if (user is null || user.UserType == 0)
|
||||
return false;
|
||||
|
||||
var installation = Db.GetInstallationById(action.InstallationId);
|
||||
if (installation.TestingMode != action.TestingMode)
|
||||
{
|
||||
installation.TestingMode = action.TestingMode;
|
||||
installation.Apply(Db.Update);
|
||||
WebsocketManager.InformWebsocketsForInstallation(action.InstallationId);
|
||||
}
|
||||
|
||||
Db.UpdateAction(action);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static async Task<Boolean> DeleteUserAction(this Session? session, Int64 actionId)
|
||||
{
|
||||
var user = session?.User;
|
||||
|
||||
if (user is null || user.UserType == 0)
|
||||
return false;
|
||||
var action = Db.GetActionById(actionId);
|
||||
if (action.TestingMode)
|
||||
{
|
||||
var installation = Db.GetInstallationById(action.InstallationId);
|
||||
installation.TestingMode = false;
|
||||
installation.Apply(Db.Update);
|
||||
WebsocketManager.InformWebsocketsForInstallation(action.InstallationId);
|
||||
}
|
||||
|
||||
Db.Delete(action);
|
||||
Console.WriteLine("---------------Deleted the Action in the database-----------------");
|
||||
return true;
|
||||
}
|
||||
|
||||
public static Boolean Delete(this Session? session, Folder? folder)
|
||||
{
|
||||
var user = session?.User;
|
||||
|
||||
return user is not null
|
||||
&& folder is not null
|
||||
&& user.UserType !=0
|
||||
&& user.HasWriteAccess
|
||||
&& user.HasAccessTo(folder)
|
||||
&& Db.Delete(folder);
|
||||
}
|
||||
|
|
@ -207,71 +94,30 @@ public static class SessionMethods
|
|||
{
|
||||
var user = session?.User;
|
||||
|
||||
|
||||
|
||||
//Salimax installation
|
||||
if (installation.Product == (int)ProductType.Salimax)
|
||||
{
|
||||
return user is not null
|
||||
&& user.UserType != 0
|
||||
&& installation is not null
|
||||
&& user.HasWriteAccess
|
||||
&& user.HasAccessToParentOf(installation)
|
||||
&& Db.Create(installation) // TODO: these two in a transaction
|
||||
&& installation.SetOrderNumbers()
|
||||
&& Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id })
|
||||
&& await installation.CreateBucket()
|
||||
&& await installation.RenewS3Credentials();
|
||||
|
||||
&& await installation.RenewS3Credentials(); // generation of access _after_ generation of
|
||||
// bucket to prevent "zombie" access-rights.
|
||||
// This might ** us over if the creation of access rights fails,
|
||||
// as bucket-names are unique and bound to the installation id... -K
|
||||
}
|
||||
|
||||
if (installation.Product == (int)ProductType.SodiStoreMax || installation.Product == (int)ProductType.SodioHome)
|
||||
{
|
||||
return user is not null
|
||||
&& user.UserType != 0
|
||||
&& user.HasAccessToParentOf(installation)
|
||||
&& Db.Create(installation) // TODO: these two in a transaction
|
||||
&& Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id })
|
||||
&& await installation.CreateBucket()
|
||||
&& await installation.RenewS3Credentials();
|
||||
|
||||
}
|
||||
|
||||
if (installation.Product == (int)ProductType.Salidomo)
|
||||
{
|
||||
return user is not null
|
||||
&& user.UserType != 0
|
||||
&& user.HasAccessToParentOf(installation)
|
||||
&& Db.Create(installation)
|
||||
&& await installation.CreateBucket()
|
||||
&& await installation.RenewS3Credentials();
|
||||
}
|
||||
//
|
||||
// if (installation.Product == (int)ProductType.SodioHome)
|
||||
// {
|
||||
// return user is not null
|
||||
// && user.UserType != 0
|
||||
// && user.HasAccessToParentOf(installation)
|
||||
// && Db.Create(installation);
|
||||
// }
|
||||
//
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static Boolean Update(this Session? session, Installation? installation)
|
||||
{
|
||||
var user = session?.User;
|
||||
|
||||
var original = Db.GetInstallationById(installation?.Id);
|
||||
//Salimax installation
|
||||
if (installation.Product == (int)ProductType.Salimax)
|
||||
{
|
||||
|
||||
return user is not null
|
||||
&& installation is not null
|
||||
&& original is not null
|
||||
&& user.UserType !=0
|
||||
&& user.HasWriteAccess
|
||||
&& user.HasAccessTo(installation)
|
||||
&& installation.SetOrderNumbers()
|
||||
&& installation
|
||||
|
|
@ -279,52 +125,16 @@ public static class SessionMethods
|
|||
.Apply(Db.Update);
|
||||
}
|
||||
|
||||
if (installation.Product == (int)ProductType.SodiStoreMax)
|
||||
{
|
||||
|
||||
return user is not null
|
||||
&& installation is not null
|
||||
&& original is not null
|
||||
&& user.UserType !=0
|
||||
&& user.HasAccessTo(installation)
|
||||
&& installation
|
||||
.WithParentOf(original) // prevent moving
|
||||
.Apply(Db.Update);
|
||||
}
|
||||
|
||||
|
||||
return user is not null
|
||||
&& installation is not null
|
||||
&& original is not null
|
||||
&& user.UserType !=0
|
||||
&& user.HasAccessToParentOf(installation)
|
||||
&& installation
|
||||
.Apply(Db.Update);
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static async Task<Boolean> Delete(this Session? session, Installation? installation)
|
||||
{
|
||||
var user = session?.User;
|
||||
|
||||
if (user is not null
|
||||
return user is not null
|
||||
&& installation is not null
|
||||
&& user.UserType != 0)
|
||||
{
|
||||
|
||||
return
|
||||
Db.Delete(installation)
|
||||
&& await installation.RevokeReadKey()
|
||||
&& await installation.RevokeWriteKey()
|
||||
&& await installation.RemoveReadRole()
|
||||
&& await installation.RemoveWriteRole()
|
||||
&& user.HasWriteAccess
|
||||
&& user.HasAccessTo(installation)
|
||||
&& Db.Delete(installation)
|
||||
&& await installation.DeleteBucket();
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
public static Boolean Create(this Session? session, User newUser)
|
||||
|
|
@ -334,7 +144,7 @@ public static class SessionMethods
|
|||
|
||||
return sessionUser is not null
|
||||
&& userAlreadyExists is null
|
||||
&& sessionUser.UserType !=0
|
||||
&& sessionUser.HasWriteAccess
|
||||
&& newUser
|
||||
.WithParent(sessionUser)
|
||||
.Do(() => newUser.MustResetPassword = true)
|
||||
|
|
@ -356,7 +166,7 @@ public static class SessionMethods
|
|||
return editedUser is not null
|
||||
&& sessionUser is not null
|
||||
&& originalUser is not null
|
||||
&& sessionUser.UserType !=0
|
||||
&& sessionUser.HasWriteAccess
|
||||
&& sessionUser.HasAccessTo(originalUser)
|
||||
&& editedUser
|
||||
.WithParentOf(originalUser) // prevent moving
|
||||
|
|
@ -382,7 +192,7 @@ public static class SessionMethods
|
|||
|
||||
return sessionUser is not null
|
||||
&& userToDelete is not null
|
||||
&& sessionUser.UserType !=0
|
||||
&& sessionUser.HasWriteAccess
|
||||
&& sessionUser.HasAccessTo(userToDelete)
|
||||
&& Db.Delete(userToDelete);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,18 +12,6 @@ namespace InnovEnergy.App.Backend.DataTypes.Methods;
|
|||
|
||||
public static class UserMethods
|
||||
{
|
||||
public static IEnumerable<Installation> AccessibleInstallations(this User user,int product)
|
||||
{
|
||||
var direct = user.DirectlyAccessibleInstallations().ToList().Where(f=>f.Product==product);
|
||||
var fromFolders = user
|
||||
.AccessibleFolders()
|
||||
.SelectMany(u => u.ChildInstallations()).ToList().Where(f=>f.Product==product);
|
||||
|
||||
return direct
|
||||
.Concat(fromFolders)
|
||||
.Distinct();
|
||||
}
|
||||
|
||||
public static IEnumerable<Installation> AccessibleInstallations(this User user)
|
||||
{
|
||||
var direct = user.DirectlyAccessibleInstallations().ToList();
|
||||
|
|
@ -36,7 +24,6 @@ public static class UserMethods
|
|||
.Distinct();
|
||||
}
|
||||
|
||||
|
||||
public static IEnumerable<Folder> AccessibleFolders(this User user)
|
||||
{
|
||||
return user
|
||||
|
|
@ -45,16 +32,6 @@ public static class UserMethods
|
|||
.Distinct();
|
||||
}
|
||||
|
||||
public static IEnumerable<TreeNode> AccessibleFoldersAndInstallations(this User user,int product)
|
||||
{
|
||||
var folders = user.AccessibleFolders() as IEnumerable<TreeNode>;
|
||||
|
||||
user.AccessibleInstallations(product).ForEach(i => i.FillOrderNumbers());
|
||||
var installations = user.AccessibleInstallations(product);
|
||||
|
||||
return folders.Concat(installations);
|
||||
}
|
||||
|
||||
public static IEnumerable<TreeNode> AccessibleFoldersAndInstallations(this User user)
|
||||
{
|
||||
var folders = user.AccessibleFolders() as IEnumerable<TreeNode>;
|
||||
|
|
@ -78,7 +55,6 @@ public static class UserMethods
|
|||
|
||||
public static IEnumerable<Folder> DirectlyAccessibleFolders(this User user)
|
||||
{
|
||||
|
||||
return Db
|
||||
.FolderAccess
|
||||
.Where(r => r.UserId == user.Id)
|
||||
|
|
@ -182,7 +158,6 @@ public static class UserMethods
|
|||
.Any(user.HasDirectAccessTo);
|
||||
}
|
||||
|
||||
|
||||
public static Boolean HasAccessTo(this User user, User? other)
|
||||
{
|
||||
if (other is null)
|
||||
|
|
@ -194,10 +169,19 @@ public static class UserMethods
|
|||
.Contains(user);
|
||||
}
|
||||
|
||||
public static Boolean HasAccessTo(this User user, TreeNode? other)
|
||||
{
|
||||
return other?.Type switch
|
||||
{
|
||||
"installation" => user.HasAccessTo((Installation)other),
|
||||
"user" => user.HasAccessTo((User)other),
|
||||
"folder" => user.HasAccessTo((Folder)other),
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
public static Boolean HasAccessToParentOf(this User user, TreeNode? other)
|
||||
{
|
||||
|
||||
return other?.Type switch
|
||||
{
|
||||
"Installation" => user.HasAccessTo(Db.GetFolderById(other.ParentId)),
|
||||
|
|
@ -229,16 +213,13 @@ public static class UserMethods
|
|||
|
||||
public static Task SendEmail(this User user, String subject, String body)
|
||||
{
|
||||
|
||||
Console.WriteLine(user.Name);
|
||||
Console.WriteLine(subject);
|
||||
return Mailer.Send(user.Name, user.Email, subject, body);
|
||||
}
|
||||
|
||||
public static Task SendPasswordResetEmail(this User user, String token)
|
||||
{
|
||||
const String subject = "Reset the password of your Inesco Energy Account";
|
||||
const String resetLink = "https://monitor.inesco.energy/api/ResetPassword"; // TODO: move to settings file
|
||||
const String subject = "Reset the password of your InnovEnergy-Account";
|
||||
const String resetLink = "https://monitor.innov.energy/api/ResetPassword"; // TODO: move to settings file
|
||||
var encodedToken = HttpUtility.UrlEncode(token);
|
||||
|
||||
var body = $"Dear {user.Name}\n" +
|
||||
|
|
@ -250,13 +231,13 @@ public static class UserMethods
|
|||
|
||||
public static Task SendNewUserWelcomeMessage(this User user)
|
||||
{
|
||||
const String subject = "Your new Inesco Energy Account";
|
||||
const String subject = "Your new InnovEnergy-Account";
|
||||
|
||||
var resetLink = $"https://monitor.inesco.energy/?username={user.Email}"; // TODO: move to settings file
|
||||
var resetLink = $"https://monitor.innov.energy/?username={user.Email}"; // TODO: move to settings file
|
||||
|
||||
var body = $"Dear {user.Name}\n" +
|
||||
$"To set your password and log in to your " +
|
||||
$"Inesco Energy Account open this link:{resetLink}";
|
||||
$"Innovenergy-Account open this link:{resetLink}";
|
||||
|
||||
return user.SendEmail(subject, body);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
using SQLite;
|
||||
|
||||
namespace InnovEnergy.App.Backend.DataTypes;
|
||||
|
||||
public abstract partial class TreeNode
|
||||
{
|
||||
//This is the parent class of each relation. It has an autoincrement Id, name, information, parent Id and Type.
|
||||
//Ignore means: "Do not map this property to a database column."
|
||||
[PrimaryKey, AutoIncrement]
|
||||
public Int64 Id { get; set; }
|
||||
public virtual String Name { get; set; } = ""; // overridden by User (unique)
|
||||
public String Information { get; set; } = ""; // unstructured random info
|
||||
|
||||
[Indexed]
|
||||
[Indexed] // parent/child relation
|
||||
public Int64 ParentId { get; set; }
|
||||
|
||||
[Ignore]
|
||||
|
|
|
|||
|
|
@ -7,8 +7,9 @@ public class User : TreeNode
|
|||
|
||||
[Unique]
|
||||
public String Email { get; set; } = null!;
|
||||
public int UserType { get; set; } = 0;
|
||||
public Boolean HasWriteAccess { get; set; } = false;
|
||||
public Boolean MustResetPassword { get; set; } = false;
|
||||
public String Language { get; set; } = null!;
|
||||
public String? Password { get; set; } = null!;
|
||||
|
||||
[Unique]
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
using SQLite;
|
||||
|
||||
namespace InnovEnergy.App.Backend.DataTypes;
|
||||
|
||||
public class UserAction
|
||||
{
|
||||
[PrimaryKey, AutoIncrement]
|
||||
public Int64 Id { get; set; } // Primary key for the table, auto-incremented
|
||||
|
||||
[Indexed]
|
||||
public String UserName { get; set; } = null!;// User Name who made the configuration change
|
||||
|
||||
public Int64 InstallationId { get; set; } // Installation ID where the configuration change is made
|
||||
|
||||
public Boolean TestingMode { get; set; }
|
||||
|
||||
public DateTime Timestamp { get; set; } // Timestamp of the configuration change
|
||||
|
||||
public String Description { get; set; } = null!;// Serialized string representing the new configuration
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
namespace InnovEnergy.App.Backend.DataTypes;
|
||||
|
||||
public class WebsocketMessage
|
||||
{
|
||||
|
||||
public int id { get; set; }
|
||||
public int status { get; set; }
|
||||
public Boolean testingMode { get; set; }
|
||||
|
||||
}
|
||||
|
|
@ -7,19 +7,16 @@ namespace InnovEnergy.App.Backend.Database;
|
|||
|
||||
public static partial class Db
|
||||
{
|
||||
|
||||
private static Boolean Insert(Object obj)
|
||||
{
|
||||
var success = Connection.Insert(obj) > 0;
|
||||
if (success) Backup();
|
||||
if(success) BackupDatabase();
|
||||
return success;
|
||||
}
|
||||
|
||||
public static Boolean Create(Installation installation)
|
||||
{
|
||||
// The bucket Id it calculated as follows: It is 1 + the maximum bucket id of all the existing installations of the same product
|
||||
// SQLite wrapper is smart and *modifies* t's Id to the one generated (autoincrement) by the insertion
|
||||
installation.S3BucketId = Installations.Where(inst => inst.Product == installation.Product).Max(inst => (int?)inst.S3BucketId)+1 ?? 0;
|
||||
return Insert(installation);
|
||||
}
|
||||
|
||||
|
|
@ -63,40 +60,6 @@ public static partial class Db
|
|||
return Insert(o2i);
|
||||
}
|
||||
|
||||
public static Boolean Create(UserAction action)
|
||||
{
|
||||
return Insert(action);
|
||||
}
|
||||
|
||||
public static void HandleAction(UserAction newAction)
|
||||
{
|
||||
//Find the total number of actions for this installation
|
||||
var totalActions = UserActions.Count(action => action.InstallationId == newAction.InstallationId);
|
||||
|
||||
//If there are 100 actions, remove the one with the oldest timestamp
|
||||
if (totalActions == 100)
|
||||
{
|
||||
var oldestAction =
|
||||
UserActions.Where(action => action.InstallationId == newAction.InstallationId)
|
||||
.OrderBy(action => action.Timestamp)
|
||||
.FirstOrDefault();
|
||||
|
||||
//Remove the old action
|
||||
Delete(oldestAction);
|
||||
|
||||
//Add the new action
|
||||
Create(newAction);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("---------------Added the new Action to the database-----------------");
|
||||
Create(newAction);
|
||||
}
|
||||
}
|
||||
|
||||
//This function is called from the RabbitMQ manager when a new error arrives to the database.
|
||||
//We keep only the last 100 errors for each installation. If we already have stored 100 errors, we delete the older one and we insert the new one.
|
||||
|
||||
public static void HandleError(Error newError,int installationId)
|
||||
{
|
||||
//Find the total number of errors for this installation
|
||||
|
|
@ -118,7 +81,7 @@ public static partial class Db
|
|||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("---------------Added the new Alarm to the database-----------------");
|
||||
Console.WriteLine("---------------Added the new Error to the database-----------------");
|
||||
Create(newError);
|
||||
}
|
||||
}
|
||||
|
|
@ -136,17 +99,16 @@ public static partial class Db
|
|||
.OrderBy(warning => warning.Date)
|
||||
.FirstOrDefault();
|
||||
|
||||
//Remove the old warning
|
||||
//Remove the old error
|
||||
Delete(oldestWarning);
|
||||
|
||||
//Add the new warning
|
||||
//Add the new error
|
||||
Create(newWarning);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("---------------Added the new Warning to the database-----------------");
|
||||
Console.WriteLine("---------------Added the new Error to the database-----------------");
|
||||
Create(newWarning);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,3 +1,5 @@
|
|||
using System.Reactive.Concurrency;
|
||||
using System.Reactive.Linq;
|
||||
using InnovEnergy.App.Backend.DataTypes;
|
||||
using InnovEnergy.App.Backend.DataTypes.Methods;
|
||||
using InnovEnergy.App.Backend.Relations;
|
||||
|
|
@ -7,14 +9,72 @@ using InnovEnergy.Lib.Utils;
|
|||
using SQLite;
|
||||
using SQLiteConnection = SQLite.SQLiteConnection;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Database;
|
||||
|
||||
//The methods of the Db class are located in multiple files (Create.cs, Read,cs, Delete.cs, Update.cs)
|
||||
//That's why the class definition is partial
|
||||
namespace InnovEnergy.App.Backend.Database;
|
||||
|
||||
public static partial class Db
|
||||
{
|
||||
private static SQLiteConnection Connection { get; } = InitConnection();
|
||||
|
||||
private static SQLiteConnection InitConnection()
|
||||
{
|
||||
var latestDb = new DirectoryInfo("DbBackups")
|
||||
.GetFiles()
|
||||
.OrderBy(f => f.LastWriteTime)
|
||||
.Last().Name;
|
||||
|
||||
//This is the file connection from the DbBackups folder
|
||||
var fileConnection = new SQLiteConnection("DbBackups/" + latestDb);
|
||||
|
||||
//Create a table if it does not exist
|
||||
fileConnection.CreateTable<User>();
|
||||
fileConnection.CreateTable<Installation>();
|
||||
fileConnection.CreateTable<Folder>();
|
||||
fileConnection.CreateTable<FolderAccess>();
|
||||
fileConnection.CreateTable<InstallationAccess>();
|
||||
fileConnection.CreateTable<Session>();
|
||||
fileConnection.CreateTable<OrderNumber2Installation>();
|
||||
fileConnection.CreateTable<Error>();
|
||||
fileConnection.CreateTable<Warning>();
|
||||
|
||||
return CopyDbToMemory(fileConnection);
|
||||
}
|
||||
|
||||
private static SQLiteConnection CopyDbToMemory(SQLiteConnection fileConnection)
|
||||
{
|
||||
var memoryConnection = new SQLiteConnection(":memory:");
|
||||
|
||||
//Create a table if it does not exist in main memory
|
||||
memoryConnection.CreateTable<User>();
|
||||
memoryConnection.CreateTable<Installation>();
|
||||
memoryConnection.CreateTable<Folder>();
|
||||
memoryConnection.CreateTable<FolderAccess>();
|
||||
memoryConnection.CreateTable<InstallationAccess>();
|
||||
memoryConnection.CreateTable<Session>();
|
||||
memoryConnection.CreateTable<OrderNumber2Installation>();
|
||||
memoryConnection.CreateTable<Error>();
|
||||
memoryConnection.CreateTable<Warning>();
|
||||
|
||||
//Copy all the existing tables from the disk to main memory
|
||||
fileConnection.Table<Session>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<Folder>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<Installation>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<User>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<FolderAccess>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<InstallationAccess>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<OrderNumber2Installation>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<Error>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<Warning>().ForEach(memoryConnection.Insert);
|
||||
|
||||
return memoryConnection;
|
||||
}
|
||||
|
||||
public static void BackupDatabase()
|
||||
{
|
||||
var filename = "db-" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + ".sqlite";
|
||||
Connection.Backup("DbBackups/" + filename);
|
||||
}
|
||||
|
||||
public static TableQuery<Session> Sessions => Connection.Table<Session>();
|
||||
public static TableQuery<Folder> Folders => Connection.Table<Folder>();
|
||||
public static TableQuery<Installation> Installations => Connection.Table<Installation>();
|
||||
|
|
@ -24,16 +84,11 @@ public static partial class Db
|
|||
public static TableQuery<OrderNumber2Installation> OrderNumber2Installation => Connection.Table<OrderNumber2Installation>();
|
||||
public static TableQuery<Error> Errors => Connection.Table<Error>();
|
||||
public static TableQuery<Warning> Warnings => Connection.Table<Warning>();
|
||||
public static TableQuery<UserAction> UserActions => Connection.Table<UserAction>();
|
||||
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
//Used to force static constructor
|
||||
// used to force static constructor
|
||||
//Since this class is static, we call Init method from the Program.cs to initialize all the fields of the class
|
||||
//When a class is loaded, the fields are initialized before the constructor's code is executed.
|
||||
//The TableQuery fields are lazy meaning that they will be initialized when they get accessed
|
||||
//The connection searches for the latest backup and binds all the tables to it.
|
||||
}
|
||||
|
||||
//This is the constructor of the class
|
||||
|
|
@ -50,96 +105,19 @@ public static partial class Db
|
|||
Connection.CreateTable<OrderNumber2Installation>();
|
||||
Connection.CreateTable<Error>();
|
||||
Connection.CreateTable<Warning>();
|
||||
Connection.CreateTable<UserAction>();
|
||||
});
|
||||
|
||||
//UpdateKeys();
|
||||
UpdateKeys().SupressAwaitWarning();
|
||||
CleanupSessions().SupressAwaitWarning();
|
||||
DeleteSnapshots().SupressAwaitWarning();
|
||||
}
|
||||
|
||||
|
||||
private static SQLiteConnection InitConnection()
|
||||
{
|
||||
var latestDb = new DirectoryInfo("DbBackups")
|
||||
.GetFiles()
|
||||
.OrderBy(f => f.LastWriteTime)
|
||||
.Last().Name;
|
||||
|
||||
Console.WriteLine("latestdb is "+latestDb);
|
||||
|
||||
//This is the file connection from the DbBackups folder
|
||||
var fileConnection = new SQLiteConnection("DbBackups/" + latestDb);
|
||||
|
||||
//Create a table if it does not exist
|
||||
fileConnection.CreateTable<User>();
|
||||
fileConnection.CreateTable<Installation>();
|
||||
fileConnection.CreateTable<Folder>();
|
||||
fileConnection.CreateTable<FolderAccess>();
|
||||
fileConnection.CreateTable<InstallationAccess>();
|
||||
fileConnection.CreateTable<Session>();
|
||||
fileConnection.CreateTable<OrderNumber2Installation>();
|
||||
fileConnection.CreateTable<Error>();
|
||||
fileConnection.CreateTable<Warning>();
|
||||
fileConnection.CreateTable<UserAction>();
|
||||
|
||||
return fileConnection;
|
||||
//return CopyDbToMemory(fileConnection);
|
||||
}
|
||||
|
||||
public static void BackupDatabase()
|
||||
{
|
||||
var filename = "db-" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + ".sqlite";
|
||||
Connection.Backup("DbBackups/" + filename);
|
||||
}
|
||||
|
||||
//Delete all except 10 snapshots every 24 hours.
|
||||
private static async Task DeleteSnapshots()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var files = new DirectoryInfo("DbBackups")
|
||||
.GetFiles()
|
||||
.OrderByDescending(f => f.LastWriteTime);
|
||||
|
||||
var filesToDelete = files.Skip(10);
|
||||
|
||||
foreach (var file in filesToDelete)
|
||||
{
|
||||
Console.WriteLine("File to delete is " + file.Name);
|
||||
file.Delete();
|
||||
}
|
||||
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Console.WriteLine("An error has occured when cleaning database snapshots, exception is:\n"+e);
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromHours(24));
|
||||
}
|
||||
}
|
||||
|
||||
//Delete all expired sessions every half an hour. An expired session is a session remained for more than 1 day.
|
||||
private static async Task CleanupSessions()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var deadline = DateTime.Now.AddDays(-Session.MaxAge.Days);
|
||||
foreach (var session in Sessions)
|
||||
{
|
||||
if (session.LastSeen < deadline)
|
||||
{
|
||||
Console.WriteLine("Need to remove session of user id " + session.User.Name + "last time is "+session.LastSeen);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Sessions.Delete(s => s.LastSeen < deadline);
|
||||
DeleteStaleSessions();
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
|
|
@ -150,61 +128,6 @@ public static partial class Db
|
|||
}
|
||||
}
|
||||
|
||||
private static async Task RemoveNonExistingKeys()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var validReadKeys = Installations
|
||||
.Select(i => i.S3Key)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
|
||||
|
||||
var validWriteKeys = Installations
|
||||
.Select(i => i.S3WriteKey)
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
Console.WriteLine("VALID READ KEYS");
|
||||
for (int i = 0; i < validReadKeys.Count; i++)
|
||||
{
|
||||
Console.WriteLine(validReadKeys[i]);
|
||||
}
|
||||
|
||||
Console.WriteLine("VALID WRITE KEYS");
|
||||
|
||||
for (int i = 0; i < validReadKeys.Count; i++)
|
||||
{
|
||||
Console.WriteLine(validWriteKeys[i]);
|
||||
}
|
||||
|
||||
|
||||
const String provider = "exo.io";
|
||||
var S3keys = await ExoCmd.GetAccessKeys();
|
||||
|
||||
foreach (var keyMetadata in S3keys)
|
||||
{
|
||||
if (keyMetadata["key"].ToString()!="EXOa0b53cf10517307cec1bf00e" && !validReadKeys.Contains(keyMetadata["key"].ToString()) && !validWriteKeys.Contains(keyMetadata["key"].ToString()))
|
||||
{
|
||||
//await ExoCmd.RevokeReadKey(keyMetadata["key"].ToString());
|
||||
Console.WriteLine("Deleted key "+keyMetadata["key"]);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
catch(Exception e)
|
||||
{
|
||||
Console.WriteLine("An error has occured when updating S3 keys, exception is:\n"+e);
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromHours(24));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static async Task UpdateKeys()
|
||||
{
|
||||
while (true)
|
||||
|
|
@ -218,14 +141,11 @@ public static partial class Db
|
|||
Console.WriteLine("An error has occured when updating S3 keys, exception is:\n"+e);
|
||||
}
|
||||
|
||||
await RemoveNonExistingKeys();
|
||||
|
||||
await Task.Delay(TimeSpan.FromHours(24));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static Boolean RunTransaction(Func<Boolean> func)
|
||||
{
|
||||
var savepoint = Connection.SaveTransactionPoint();
|
||||
|
|
@ -247,6 +167,12 @@ public static partial class Db
|
|||
}
|
||||
|
||||
|
||||
private static void DeleteStaleSessions()
|
||||
{
|
||||
var deadline = DateTime.Now.AddDays((-1) * Session.MaxAge.Days);
|
||||
Sessions.Delete(s => s.LastSeen < deadline);
|
||||
}
|
||||
|
||||
private static async Task UpdateS3Urls()
|
||||
{
|
||||
var regions = Installations
|
||||
|
|
@ -255,7 +181,6 @@ public static partial class Db
|
|||
.ToList();
|
||||
|
||||
const String provider = "exo.io";
|
||||
Console.WriteLine("-----------------------UPDATED READ KEYS-------------------------------------------------------------------");
|
||||
|
||||
foreach (var region in regions)
|
||||
{
|
||||
|
|
@ -271,7 +196,6 @@ public static partial class Db
|
|||
{
|
||||
await installation.RenewS3Credentials();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -284,7 +208,6 @@ public static partial class Db
|
|||
}
|
||||
catch
|
||||
{
|
||||
Console.WriteLine("return false");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,27 +1,19 @@
|
|||
using InnovEnergy.App.Backend.DataTypes;
|
||||
using InnovEnergy.App.Backend.DataTypes.Methods;
|
||||
using InnovEnergy.App.Backend.Relations;
|
||||
|
||||
|
||||
namespace InnovEnergy.App.Backend.Database;
|
||||
|
||||
|
||||
public static partial class Db
|
||||
{
|
||||
//Since we do not want to stress the memory in the VM a lot, we make a snapshot of the database every 100 transactions.
|
||||
private static int _backupCounter = 0;
|
||||
private static void Backup()
|
||||
{
|
||||
_backupCounter++;
|
||||
if (_backupCounter > 100)
|
||||
{
|
||||
_backupCounter = 0;
|
||||
BackupDatabase();
|
||||
}
|
||||
}
|
||||
public static Boolean Delete(Folder folder)
|
||||
{
|
||||
var deleteSuccess= RunTransaction(DeleteFolderAndAllItsDependencies);
|
||||
if (deleteSuccess)
|
||||
{
|
||||
Backup();
|
||||
BackupDatabase();
|
||||
}
|
||||
|
||||
return deleteSuccess;
|
||||
|
|
@ -46,9 +38,8 @@ public static partial class Db
|
|||
public static Boolean Delete(Error errorToDelete)
|
||||
{
|
||||
var deleteSuccess = RunTransaction(DeleteError);
|
||||
|
||||
if (deleteSuccess)
|
||||
Backup();
|
||||
BackupDatabase();
|
||||
return deleteSuccess;
|
||||
|
||||
|
||||
|
|
@ -58,34 +49,17 @@ public static partial class Db
|
|||
}
|
||||
}
|
||||
|
||||
public static Boolean Delete(UserAction actionToDelete)
|
||||
{
|
||||
var deleteSuccess = RunTransaction(DeleteAction);
|
||||
|
||||
|
||||
if (deleteSuccess)
|
||||
Backup();
|
||||
return deleteSuccess;
|
||||
|
||||
|
||||
Boolean DeleteAction()
|
||||
{
|
||||
return UserActions.Delete(action => action.Id == actionToDelete.Id) >0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean Delete(Warning warningToDelete)
|
||||
{
|
||||
var deleteSuccess = RunTransaction(DeleteWarning);
|
||||
if (deleteSuccess)
|
||||
Backup();
|
||||
BackupDatabase();
|
||||
return deleteSuccess;
|
||||
|
||||
|
||||
Boolean DeleteWarning()
|
||||
{
|
||||
return Warnings.Delete(warning => warning.Id == warningToDelete.Id) >0;
|
||||
return Warnings.Delete(error => error.Id == warningToDelete.Id) >0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -93,19 +67,14 @@ public static partial class Db
|
|||
{
|
||||
var deleteSuccess = RunTransaction(DeleteInstallationAndItsDependencies);
|
||||
if (deleteSuccess)
|
||||
Backup();
|
||||
BackupDatabase();
|
||||
return deleteSuccess;
|
||||
|
||||
|
||||
Boolean DeleteInstallationAndItsDependencies()
|
||||
{
|
||||
InstallationAccess.Delete(i => i.InstallationId == installation.Id);
|
||||
if (installation.Product == (int)ProductType.Salimax)
|
||||
{
|
||||
//For Salimax, delete the OrderNumber2Installation entries associated with this installation id.
|
||||
OrderNumber2Installation.Delete(i => i.InstallationId == installation.Id);
|
||||
}
|
||||
|
||||
return Installations.Delete(i => i.Id == installation.Id) > 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -114,13 +83,14 @@ public static partial class Db
|
|||
{
|
||||
var deleteSuccess = RunTransaction(DeleteUserAndHisDependencies);
|
||||
if (deleteSuccess)
|
||||
Backup();
|
||||
BackupDatabase();
|
||||
return deleteSuccess;
|
||||
|
||||
Boolean DeleteUserAndHisDependencies()
|
||||
{
|
||||
FolderAccess .Delete(u => u.UserId == user.Id);
|
||||
InstallationAccess.Delete(u => u.UserId == user.Id);
|
||||
|
||||
return Users.Delete(u => u.Id == user.Id) > 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -133,7 +103,7 @@ public static partial class Db
|
|||
{
|
||||
var delete = Sessions.Delete(s => s.Id == session.Id) > 0;
|
||||
if (delete)
|
||||
Backup();
|
||||
BackupDatabase();
|
||||
return delete;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
using InnovEnergy.App.Backend.Relations;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Database;
|
||||
|
||||
public static partial class Db
|
||||
{
|
||||
public static void CreateFakeRelations()
|
||||
{
|
||||
Connection.RunInTransaction(() =>
|
||||
{
|
||||
CreateFakeUserTree();
|
||||
CreateFakeFolderTree();
|
||||
LinkFakeInstallationsToFolders();
|
||||
GiveFakeUsersAccessToFolders();
|
||||
GiveFakeUsersAccessToInstallations();
|
||||
});
|
||||
}
|
||||
|
||||
private static void CreateFakeUserTree()
|
||||
{
|
||||
foreach (var userId in Enumerable.Range(1, Users.Count()))
|
||||
{
|
||||
var user = GetUserById(userId);
|
||||
if (user is null)
|
||||
continue;
|
||||
|
||||
user.ParentId = userId > 1
|
||||
? Random.Shared.Next(userId - 1) + 1
|
||||
: 0; // root has parentId 0
|
||||
|
||||
Update(user);
|
||||
}
|
||||
}
|
||||
|
||||
private static void CreateFakeFolderTree()
|
||||
{
|
||||
foreach (var folderId in Enumerable.Range(1, Folders.Count()))
|
||||
{
|
||||
var folder = GetFolderById(folderId);
|
||||
if (folder is null)
|
||||
continue;
|
||||
|
||||
folder.ParentId = folderId > 1
|
||||
? Random.Shared.Next(folderId - 1) + 1
|
||||
: 0; // root has parentId 0
|
||||
|
||||
Update(folder);
|
||||
}
|
||||
}
|
||||
|
||||
private static void LinkFakeInstallationsToFolders()
|
||||
{
|
||||
var nFolders = Folders.Count();
|
||||
|
||||
foreach (var installation in Installations)
|
||||
{
|
||||
installation.ParentId = Random.Shared.Next(nFolders) + 1;
|
||||
Update(installation);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GiveFakeUsersAccessToFolders()
|
||||
{
|
||||
foreach (var uf in FolderAccess) // remove existing relations
|
||||
Connection.Delete(uf);
|
||||
|
||||
var nFolders = Folders.Count();
|
||||
var nUsers = Users.Count();
|
||||
|
||||
foreach (var user in Users)
|
||||
while (Random.Shared.Next((Int32)(nUsers - user.Id + 1)) != 0)
|
||||
{
|
||||
var relation = new FolderAccess
|
||||
{
|
||||
UserId = user.Id,
|
||||
FolderId = Random.Shared.Next(nFolders) + 1
|
||||
};
|
||||
Connection.Insert(relation);
|
||||
}
|
||||
}
|
||||
|
||||
private static void GiveFakeUsersAccessToInstallations()
|
||||
{
|
||||
foreach (var ui in InstallationAccess) // remove existing relations
|
||||
Connection.Delete(ui);
|
||||
|
||||
var nbInstallations = Installations.Count();
|
||||
|
||||
foreach (var user in Users)
|
||||
while (Random.Shared.Next(5) != 0)
|
||||
{
|
||||
var relation = new InstallationAccess
|
||||
{
|
||||
UserId = user.Id,
|
||||
InstallationId = Random.Shared.Next(nbInstallations) + 1
|
||||
};
|
||||
Connection.Insert(relation);
|
||||
}
|
||||
}
|
||||
//TODO fake OrderNumbers
|
||||
}
|
||||
|
|
@ -1,11 +1,12 @@
|
|||
using InnovEnergy.App.Backend.DataTypes;
|
||||
using InnovEnergy.App.Backend.Relations;
|
||||
|
||||
|
||||
namespace InnovEnergy.App.Backend.Database;
|
||||
|
||||
|
||||
public static partial class Db
|
||||
{
|
||||
//In this file, we provide all the methods that can be used in order to retrieve information from the database (read)
|
||||
public static Folder? GetFolderById(Int64? id)
|
||||
{
|
||||
return Folders
|
||||
|
|
@ -18,12 +19,6 @@ public static partial class Db
|
|||
.FirstOrDefault(i => i.Id == id);
|
||||
}
|
||||
|
||||
public static UserAction? GetActionById(Int64? id)
|
||||
{
|
||||
return UserActions
|
||||
.FirstOrDefault(i => i.Id == id);
|
||||
}
|
||||
|
||||
public static User? GetUserById(Int64? id)
|
||||
{
|
||||
return Users
|
||||
|
|
@ -38,15 +33,14 @@ public static partial class Db
|
|||
|
||||
public static Session? GetSession(String token)
|
||||
{
|
||||
//This method is called in almost every controller function.
|
||||
//After logging in, the frontend receives a session object which contains a token. For all the future REST API calls, this token is used for session authentication.
|
||||
var session = Sessions
|
||||
.FirstOrDefault(s => s.Token == token);
|
||||
|
||||
// cannot use session.Valid in the DB query above.
|
||||
// It does not exist in the db (IgnoreAttribute)
|
||||
|
||||
if (session is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!session.Valid)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -1,17 +1,14 @@
|
|||
using InnovEnergy.App.Backend.DataTypes;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Database;
|
||||
|
||||
|
||||
public static partial class Db
|
||||
{
|
||||
//We can execute the updates manually for each table, but we prefer the abstract way using Connection.Update method
|
||||
//We pass an object as an argument and the Connection will connect this object with the corresponding table.
|
||||
//The update is being done based on the primary id of the object.
|
||||
|
||||
private static Boolean Update(Object obj)
|
||||
{
|
||||
var success = Connection.Update(obj) > 0;
|
||||
if(success) Backup();
|
||||
if(success) BackupDatabase();
|
||||
return success;
|
||||
}
|
||||
|
||||
|
|
@ -25,6 +22,7 @@ public static partial class Db
|
|||
return Update(obj: error);
|
||||
}
|
||||
|
||||
|
||||
public static Boolean Update(Warning warning)
|
||||
{
|
||||
return Update(obj: warning);
|
||||
|
|
@ -46,15 +44,4 @@ public static partial class Db
|
|||
|
||||
return Update(obj: user);
|
||||
}
|
||||
|
||||
public static void UpdateAction(UserAction updatedAction)
|
||||
{
|
||||
var existingAction = UserActions.FirstOrDefault(action => action.Id == updatedAction.Id);
|
||||
|
||||
if (existingAction != null)
|
||||
{
|
||||
Update(updatedAction);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
id,Information,Name,ParentId
|
||||
1,8561 Eliot Point,Roob-Kuphal,860
|
||||
2,58323 Petterle Place,"Jacobi, Cassin and Legros",582
|
||||
3,13 Dapin Circle,Heaney and Sons,462
|
||||
4,623 Sycamore Drive,Runolfsdottir-Stoltenberg,817
|
||||
5,5 Stephen Pass,Emard-Sawayn,633
|
||||
6,892 Porter Plaza,"Hettinger, Macejkovic and Stoltenberg",415
|
||||
7,1077 Melrose Place,Hansen Group,756
|
||||
8,94 Dixon Trail,"Doyle, Torp and Boyer",346
|
||||
9,0679 Oak Hill,"Spinka, Breitenberg and Bednar",782
|
||||
10,21 Sheridan Park,Adams Inc,705
|
||||
11,7025 Elka Way,Ankunding Inc,949
|
||||
12,7 Redwing Way,Padberg Inc,278
|
||||
13,4311 Londonderry Center,"Gulgowski, Macejkovic and Carroll",756
|
||||
14,879 Mesta Center,Turner Group,576
|
||||
15,82 Barby Way,"Brekke, Frami and Ullrich",65
|
||||
16,78 Twin Pines Crossing,Ward-Gislason,676
|
||||
17,645 Kedzie Center,Lubowitz-Abshire,211
|
||||
18,69979 Debs Terrace,Schuppe-Gottlieb,793
|
||||
19,329 Bayside Circle,Heller-Altenwerth,908
|
||||
20,9823 Judy Alley,"Luettgen, Price and Larkin",969
|
||||
21,936 Harbort Way,Bartoletti Inc,476
|
||||
22,327 Clemons Alley,Keeling and Sons,207
|
||||
23,63305 Monument Street,Wolf-Witting,799
|
||||
24,9127 Wayridge Trail,"Rogahn, Pfannerstill and Wisozk",483
|
||||
25,1208 Rieder Parkway,"Gleason, Rowe and Sporer",171
|
||||
26,8 Comanche Hill,"Bruen, Weber and Cronin",816
|
||||
27,1658 Springs Avenue,Ledner Group,680
|
||||
28,0 Manley Avenue,Jaskolski Group,55
|
||||
29,7 Eggendart Lane,Leuschke and Sons,459
|
||||
30,29728 Daystar Way,Wiegand LLC,481
|
||||
31,88847 Hoard Way,Quitzon-Wiegand,523
|
||||
32,12785 Monterey Lane,Hammes-Gerhold,804
|
||||
33,712 4th Junction,"Schmitt, Schneider and Weber",932
|
||||
34,6143 Onsgard Alley,Stehr LLC,929
|
||||
35,906 Ridge Oak Center,"Ullrich, Reilly and Hegmann",621
|
||||
36,23067 Center Center,"Barrows, Mraz and Morissette",980
|
||||
37,81566 Ridge Oak Circle,"Torp, Lehner and Ruecker",63
|
||||
38,55 Thierer Plaza,"Oberbrunner, Legros and Quitzon",311
|
||||
39,60502 Dryden Parkway,Wisozk-Rutherford,996
|
||||
40,957 Londonderry Junction,"Quitzon, Corkery and Leffler",610
|
||||
41,314 Longview Junction,"Brekke, Sanford and Kautzer",573
|
||||
42,1 Holy Cross Park,Thompson Inc,770
|
||||
43,6016 Emmet Circle,Maggio Group,827
|
||||
44,07493 Clarendon Park,Rau-Howe,452
|
||||
45,9578 Novick Alley,Ebert-King,855
|
||||
46,20688 Prairieview Drive,Steuber Inc,205
|
||||
47,02 Sheridan Plaza,"McClure, Reinger and Kuhic",595
|
||||
48,3974 Scofield Road,Ratke-Bailey,250
|
||||
49,8271 Mcguire Crossing,Kihn-Hansen,835
|
||||
50,5375 Jenna Court,Tillman-Jones,221
|
||||
51,17 Warner Terrace,"Kihn, Stark and Padberg",146
|
||||
52,28779 Heffernan Place,"Friesen, Haley and Hickle",189
|
||||
53,52 Dovetail Junction,"Doyle, Heidenreich and Kessler",41
|
||||
54,6 Rowland Lane,Kulas LLC,322
|
||||
55,8 Eggendart Plaza,Braun and Sons,303
|
||||
56,2 Starling Avenue,"Hoppe, Berge and Towne",510
|
||||
57,02 7th Lane,Armstrong Inc,536
|
||||
58,7 Drewry Place,Feil Inc,359
|
||||
59,341 Shoshone Lane,"Wolf, Batz and Hegmann",702
|
||||
60,33536 Dorton Trail,Weissnat-Hamill,422
|
||||
61,72759 Mesta Place,Bahringer Inc,412
|
||||
62,93532 Coolidge Park,Blanda Group,928
|
||||
63,09 Vahlen Trail,Mayer LLC,434
|
||||
64,37251 Corry Center,"Carter, Bartoletti and Stehr",958
|
||||
65,60837 Green Ridge Avenue,Koch-Mann,437
|
||||
66,69689 Waxwing Drive,White Group,88
|
||||
67,1 Heffernan Hill,Ullrich-Hermann,411
|
||||
68,66785 Ramsey Junction,Schmitt-Schamberger,335
|
||||
69,16 Everett Way,Rosenbaum Inc,451
|
||||
70,2 4th Plaza,"Larson, Stanton and Dach",459
|
||||
71,503 Chive Street,Yost LLC,612
|
||||
72,99796 Red Cloud Alley,Klocko-Crist,473
|
||||
73,41578 Bultman Circle,"Nitzsche, Parker and Collins",669
|
||||
74,6 Stang Court,Bruen Group,152
|
||||
75,6 Oxford Way,Sipes Inc,204
|
||||
76,44 Rieder Drive,Moen Group,194
|
||||
77,402 Hintze Hill,Beahan and Sons,385
|
||||
78,0489 Eastwood Pass,"Ledner, Willms and Casper",561
|
||||
79,7 Milwaukee Pass,Bogisich Group,142
|
||||
80,364 Glacier Hill Plaza,"McLaughlin, Mante and Sawayn",350
|
||||
81,5717 Michigan Avenue,"Weber, Moore and Halvorson",803
|
||||
82,4 Heffernan Hill,Herzog-Weissnat,865
|
||||
83,4182 4th Drive,Smitham Inc,437
|
||||
84,05 Armistice Street,Schultz Group,501
|
||||
85,7194 Dottie Plaza,"Hermann, Marks and Bogan",720
|
||||
86,7 Maywood Crossing,Lubowitz-Kuhic,956
|
||||
87,320 Hollow Ridge Drive,Mills-Torphy,311
|
||||
88,28309 Mallard Parkway,Nienow LLC,73
|
||||
89,08 Springview Way,"Simonis, Goodwin and Steuber",216
|
||||
90,9415 Village Terrace,Kulas-King,319
|
||||
91,144 Truax Place,Beatty LLC,842
|
||||
92,351 Twin Pines Park,Emard-Emmerich,400
|
||||
93,5 Declaration Avenue,Nikolaus and Sons,108
|
||||
94,630 Brown Avenue,"Murazik, Hettinger and Turcotte",227
|
||||
95,58 Westerfield Trail,Schroeder LLC,639
|
||||
96,01 Springview Point,Bernhard-Stamm,62
|
||||
97,89 Melvin Parkway,"Olson, Rutherford and Terry",853
|
||||
98,48465 Waywood Pass,Torp-Beer,192
|
||||
99,366 Calypso Hill,"Ernser, Schuster and Altenwerth",140
|
||||
100,197 Independence Center,Green LLC,752
|
||||
|
|
|
@ -0,0 +1,801 @@
|
|||
Id,Name,Location,Region,Country,Lat,Long,S3Bucket,Information,ParentId,OrderNumbers
|
||||
1,Esme Aslen,Perez,,PH,14.689167,120.9849216,1HufyG2b4aypPMRBtEbxSagKbx42qpqG1X,566 Blackbird Way,270,11TRpqjVx7qF96bMd1FCUxc2SLG9TdrZA
|
||||
2,Felice Took,Thị Trấn Quán Lào,,VN,19.9699898,105.6515167,1GThU33nBVDp9AUuP9GEBNBZ7ZL13PGfiF,246 Shelley Pass,96,1KirLqb2D9pxAMZBK9azNZpbPkpSKH6e8m
|
||||
3,Lidia Fines,Ala-Buka,,KG,41.411881,71.4833652,17cSziKXmDZRKAizeR8C69Bn9KR2CKDi35,84 Eastwood Avenue,179,1F1Ft4bA8JFJ5ope4bZFmqCCSyGqwrC3GP
|
||||
4,Carlotta McAvey,Guantang,,CN,24.304673,113.136459,1BwCkPsMx1N7VR7QVgV8n42G79WuFwA8xt,56 Farmco Center,988,1LPLPAsYrZuRXGu5xqTUWFrM2a9kkcErjh
|
||||
5,Bride Whitcher,Wielgie,,PL,52.7489266,19.2533342,16XoybSpiMAEuY9uhSqRc5ztjTH6P1St2T,242 Evergreen Lane,288,1JD3gJ7BWAYbqJnChmAT1SGatAmDYmfNAq
|
||||
6,Jed Loyd,Pedro II,,BR,-4.4338427,-41.4537623,18RsoZSV2ezwJUzPd1nYcAkBhW5eh4C7eN,4 Burning Wood Avenue,114,1ADsuovtzJb6AZxCXVzXbaeaL3XnErYdSw
|
||||
7,Dulcea Avramovic,Arras,B4,FR,50.2837307,2.7425682,14Cv1hiVKBLJTR4uRe7YDAP8macPgKbdyk,253 Saint Paul Lane,731,1Q1qCuKFJxGBVHLpKgdq4dnGDzgT2cxfFH
|
||||
8,Claudelle Sandon,Feitoria,13,PT,38.6871727,-9.3264629,18h8JiXLcBZ2vN8CWzKrV8gvyG6GzKnAyP,4032 Laurel Hill,276,1BymrXePuwjaTnMsHZnk6KX2AjmLxVLm1B
|
||||
9,Lexy Flory,Lukavec,,CZ,49.7517708,17.919916,14JYxi2JJDRFdh27awSiqecQFH9hX74Ezf,71837 Park Meadow Trail,932,12MMWjLSwvxCUk8b42RfiHanQqTthjd5fy
|
||||
10,Kendrick Yeabsley,Al Qurayshīyah,,YE,14.5122,44.85358,1MiupVAoeznpfKqyqEdpssWY9zv9Q98iBX,0 Sommers Road,948,1KBWwGzyTjC4RhHfDguMpLHSPoNLh4AKwR
|
||||
11,Ibrahim Ulrik,Kabinda,,CD,-6.1373578,24.4886232,18z2thTKs69QjP4ygs14adTdCPnc8RU4kF,7 Debra Lane,384,1HZuqDz7KJuQmwA1ywDSDo1d9QSsJcEnEb
|
||||
12,Scarlett Culkin,Cacocum,,CU,20.7365652,-76.331889,1PvTPvvjF3mTQrfUUG2s2Q3p55NvdN2Bdh,865 Pepper Wood Hill,877,1MXSqqWT1yP73HCm9ih2DopsJwc76ywWBB
|
||||
13,Thedric Bottomer,Pisz,,PL,53.8044834,21.7303948,1HGPZSTA6CsBeeUU6A8aLB4SKojrFu3Wqx,0014 Pankratz Junction,318,1HG9mhcuEWnYuYYCCEhX9a8eXxaHqxosJi
|
||||
14,Ardenia Bertrand,Liuzhi,,CN,26.201227,105.480028,1MBUNTaM5UgzuDQSo6yetRNJcCdMSJxCqd,5901 Kinsman Park,980,17gFjcwx2AcPKqTbJRraxScMhQZFWyQsM4
|
||||
15,Jeffie Eadmead,Nuing,,PH,13.1424635,123.7244858,1PV39kAuV4yn9aQXheUUW7PPHbfZa9Pnrm,8532 7th Avenue,43,12bULkkcnpPpHezAGuExGDRV3HwhgsuEgM
|
||||
16,Nealson Blanchet,Prestea,,GH,5.4373199,-2.1401139,1LqtgActqHD8M8coDCpj3MAhS7BNF1EWLt,1 Forest Run Pass,434,1Dio3AmR4ackcUs1AynZqE4rWZBpiF7DZu
|
||||
17,Hubert Demare,Dazu,,CN,29.7444645,105.7948814,12JSBRAgg6AEP56wTYsXCqJYZaVnsUc2kH,07035 Sunbrook Terrace,601,1LG4RM4RsfmyZCmdVjAumcv15h1xMismZP
|
||||
18,Janna McKnish,Minh Lương,,VN,9.9044207,105.1668326,13AgjHQPCkHY1dMpp1vkNtze6c45pa6jqd,05 Miller Street,709,1BS5NGUZciR3r2NuCfvfQ5WeTxwS3BssQq
|
||||
19,Oates Castilla,Herval,,BR,-32.0288506,-53.3936083,1JnVPrUSZbkVPmpqyreH5ehoRcVzfL9SC8,46304 Parkside Plaza,740,1P6y1KitwGRTKYzHhbnqq5k1GKRq98ffB2
|
||||
20,Paxton Trunchion,Sukapura,,ID,-7.9293213,113.013113,1KkYjNxFujheKaQnzLJV9F8UZMWe4muwj8,9889 Clyde Gallagher Drive,250,1ExZMr2iCe7ue7BtpHGKeqW91hNTFczb13
|
||||
21,Denis Millyard,Baikouquan,,CN,45.895779,85.349613,15KGGBfcQoHG2edZuy1Z6rPDXrxE3uPyow,758 Mitchell Point,251,1PZbKUUwUJMCrGUDVF9oknCtSV2Yr8WQrW
|
||||
22,Adeline Suddaby,Oslo,03,NO,59.9522733,10.6776776,1Fobdjc78bX4YBonCEqpD5zMP4X4UWVjCW,280 Monument Lane,127,1DWXZCyub18kqaa566CmphS5oA19zvpxGE
|
||||
23,La verne Strank,Kang-neung,,KR,37.751853,128.8760574,1E54xwUo7DMSSKK83mTgGe63y2997wwUfs,0 Clyde Gallagher Crossing,257,1ApfaTKdCm3ARdbT7kHTfKv2pvZ3yqUNko
|
||||
24,Elana Couchman,Surcubamba,,PE,-12.1230818,-74.6341875,1H1YZViAgqg5U9xt1pybWQSM9JHm2dbSSc,7 Dakota Plaza,353,155L8sVgdFQWmd5Rh2VwruvZ9P4FkzZSen
|
||||
25,Sharona Gretton,Lenīnskīy,,KZ,52.2550199,76.7807566,1PXMguqae2saGzJAnqfRkr35V7kp4shKAm,3 Hoffman Plaza,305,1JB1X9hPBHTdmdHEgeppq3voyvt2pFisv4
|
||||
26,Yvon Vreiberg,Kasangulu,,CD,-4.5887697,15.1668326,1Ewo37pCKPeLkDDgSdjp1L5Ti41jmxJ3Jj,0824 Bellgrove Terrace,46,18MEeWNNHtMEoA3ZWxccBKfgkARs9HuwPF
|
||||
27,Marsha Ellwood,Razgrad,,BG,43.5336719,26.5411165,1CPB6sbFXUnrDhwXLo8Tie16Ag2xUiCF2o,4 Granby Drive,946,14egHvnZTKyGB9SGdoLXdzW3nCW8j4veD1
|
||||
28,Coral MacKibbon,Gornyy,,RU,55.111901,83.8869856,1LSBhMrC4pW4rjAVHh6YXnEdP8HENXgZcX,9170 Autumn Leaf Way,451,1B6srrKHQCz81X43mMJs7iD6yiUr55bi7Y
|
||||
29,Mendy Woodwin,Buenlag,,PH,15.9793744,120.1912435,12pWbzjUvTVZE49FQgzpRUVBPCoNgGzUvt,651 Hanson Trail,850,18641NwCLxdn8n1Z36h59zptfgrwDmWkaK
|
||||
30,Stafford Bartoloma,Ash Shaykh ‘Uthmān,,YE,12.87678,44.99309,1ESyJ7XnkjfVoRLm3F7BLGshXSHLFv1EaJ,322 Golden Leaf Court,390,1NjnBgE9mPXnG4DbTLvKDwnCuTzH9GVB3C
|
||||
31,Edmund Rowat,Sittwe,,MM,20.1527657,92.8676861,1Nb9PxazfvCC6qWn9XMzB75k3wW18DmA37,216 Starling Lane,748,1AtNyADaeVS5U4X1axSPVzqnAACHpVajRf
|
||||
32,Spenser Barthorpe,Caleufú,,AR,-40.1382194,-71.2883704,1353ZuYdbGXUtFcbvBWhM19ysyUhWKEQxL,359 Portage Crossing,324,1MuYCoKnHUoJpqpyjjPqZdgkpXhAeYaCYu
|
||||
33,Gardner Leon,Nanzhang Chengguanzhen,,CN,31.775967,111.846264,1NxT8yfqZErr75DmzVB2463PwN4vmQUC7Q,537 Merry Plaza,577,1999xP3hpTCaoyQT3GDtN5UAJVi9FJJRoB
|
||||
34,Mortimer Dorset,Lyon,B9,FR,43.0429124,1.9038837,1AJisRTkys56mCwnbAiyDd5372fcoNadtj,17362 Toban Parkway,981,1QAqZDt1TZqzULy6vmzcgmvw9KiQvpjoji
|
||||
35,Curtis Yackiminie,Sumberjo,,ID,-7.2030729,112.0237903,1848zsJDaAdNwsUqCPqgujkVUHFnmBWfuj,9 Golf View Point,459,196ctrJ11KnqK1FQpRTLf12ewuv9AE3M7N
|
||||
36,Torrie Fifield,Jonquière,QC,CA,48.3705292,-71.2653479,17aUhQ3qDyAAo2g9nBDATLX9STuE18x8bS,92 Randy Park,251,1KdkvMu4A8XVcrizJSurPdLxB3DAUs7vbp
|
||||
37,Klarika Barus,Soreang,,ID,-7.025202,107.5259078,1JzaqskB8sUKymxpWX5i7WRvMUvmCQkQu7,167 Chinook Center,957,1Nxvs7wMsvwmN65JEGBoRtXmF8iDt3Q3AK
|
||||
38,Lula Smissen,Gunungangka,,ID,-7.34307,108.16434,14np5Mv6wSKroh2YFcSDGZb8qxmB3F1v6u,89704 Mitchell Lane,264,16AbKQrJBWoNsQJhgfLJi3ZTEs25nJSs9B
|
||||
39,Antoine Leppard,Ilagan,,PH,17.0977975,121.8540162,1FbhDAwaxRimKJntMZRs5dp3VzMttDQzpK,4 Nobel Alley,608,1D6o6qxdHBS9xEKXuek1aWZZ5eowPiqBsJ
|
||||
40,Coralie Dekeyser,Carcassonne,A9,FR,44.346784,5.8855505,12XBbUrP3xuHKxwyNbGz2ss9GM7H4MwTCy,9 Sommers Court,564,1J1HHTYfb2nrT3NkFsTvz5QnxiWP7yTpS
|
||||
41,Beverley Strathe,Jaleswar,,NP,26.6523865,85.7974141,14qmfHzxueiibbqyRP6qzCJcPXCuzEJiUb,8 Hoffman Point,393,1HRD1VPkzSAALrDrZGDupcFYXvPAWqhR1T
|
||||
42,Rozina Grimes,Runjin,,CN,47.949239,126.276226,19mha7LgNwiLJABc2qwUtqYFUVnooCn5vb,5581 Thackeray Lane,283,1CwknBidpD1YhN979hswJ71Gig2URoTova
|
||||
43,Janean Kemm,Pañgobilian,,PH,8.7963805,117.8314494,1AF3bvBTNMcx7SM7YPnwTXGiyZaT2h3nZR,069 Anniversary Hill,756,1Bzr2WB4Wirn9mdprjNATJia92rBMQh9MD
|
||||
44,Imogen Ireland,Vitim,,RU,53.3519942,83.7638288,154hhyvBJiZWXA62UQomC3nRTUhF6bptWi,180 Troy Place,99,15do5G3NqxAuGLYKV4eZWx8VyuMzs7ohFE
|
||||
45,Warner Dunridge,Franca,,BR,-20.5393288,-47.4013437,1KiMZF4dCDb4gJXVWBCKvpkgMUoEVoHA5u,92835 Cordelia Drive,778,1HCpPW2vZTSR9ATpF5icbqXgngojBSxKnS
|
||||
46,Illa Adelman,Xigaoshan,,CN,31.556273,115.004962,1A6Ca9eEpBAmhuMvErQLbZhPQmJpKxhu1M,01893 Mosinee Street,122,14RYh43iPBMmY8355iGvTEKwqykCNrGQjc
|
||||
47,Carlo Pahl,Przyborów,,PL,50.0248149,20.6621099,1GkTMPm1TeMcJAmBXnEikzkeYWABhUWjL8,8 Crownhardt Pass,853,1HZDUSSpKBwSKp5NRTbywNfcZHa3txLZun
|
||||
48,Rosabella Winger,Princeville,QC,CA,46.171607,-71.875654,1NBq4v8FANFF6zGN5mPbj5g7QVQK6BfYZu,99 Hermina Park,244,12nGgmuf2SswKoxDWTjkCynoNhnjxCvVE9
|
||||
49,Kerstin Kyles,Ponoka,AB,CA,52.675914,-113.5832367,1DaxggKTZkpNCLjf2Yi5ubSPyWayyPfMcZ,24032 Merrick Circle,916,1JRayxpmx7Vs7MBtijLiVbCHoKC1Yx9jsT
|
||||
50,Ines Bumford,Mülheim an der Ruhr,NW,DE,51.441874,6.886548,14bRQ6nNKCYJ8wnV9bCuFGZU7hhbDWogtw,64982 Petterle Hill,794,1BKxj7rgr6dvKDftGP7bzXc5iqGVr7XQwM
|
||||
51,Jorgan Oppery,Edson,AB,CA,53.5855087,-116.4428615,1LWaYMRw1bisDMYCEotuc16bG5tH1UZxKv,6 Florence Circle,95,1F2Qw4WBRrcVp3qhbqUSVfCon7S1w3QivV
|
||||
52,Hy Secrett,Guamal,,CO,9.1669471,-74.1474846,1FS6tyGcCfyidnemiiLKreVDhtpHbXck6r,726 Darwin Court,602,1566XFMEYzLHLVh19wZoFbbiv1bddPdHuc
|
||||
53,Alfred Membry,Song’ao,,CN,29.60095,121.685484,135cy5WE9ppyiZfsK4GSTYjDDZiRhEBE7T,8 Thierer Crossing,612,19J1yzRq5DThzxN7pD5YHqk3U6RTg9DMeJ
|
||||
54,Kipp Mangeney,Unquillo,,AR,-31.372506,-64.182416,1DDtSa3td147KDTKBH58aNVvGqo8Fpqs7u,89 Melvin Crossing,717,18KXH6hc8cNsr4e6UnRzhrnfaKNyBNcXxk
|
||||
55,Marje Cupitt,Kraton,,ID,-7.8090256,110.363649,16Zka9Rsxh5zkysuvwDSUEUnx9yAJfTwX6,818 Pawling Crossing,890,1B7jATu1Bm7nXXHnz3aSYzYFsTXCKaxL2q
|
||||
56,Melodie Terren,Youfang,,CN,32.291013,109.07515,1BfG5cMz9be52vbaDHhrn9Yxfm2PLfZwbF,1 Texas Crossing,869,1Jjj1kSaBwxfbyF8tMPTWKhq53CDtQfNR3
|
||||
57,Chick McGuinness,Baños,,EC,-1.3928344,-78.4268758,1Pba3uomng6Ai3wAtvs5igMJT46fiiJkz8,52 Dahle Street,705,1K2T26numkhJ6NLbmnLTA2CT8NTaStarsu
|
||||
58,Ansley Haet,Mojokerto,,ID,-7.4704747,112.4401329,1KSQPisER2pycyFSN5y6Gwt7yuK9ENQyqb,86 Northland Street,697,1KN3qBqF8kPrp93H3q52VPyuFFKVfpVUD8
|
||||
59,Pen Parrington,Jatimulyo,,ID,-7.9431343,112.6183751,1JhCytgdeYx4SfYMpGW3Z2RT7KcHKMpq8W,01292 Comanche Terrace,320,1HGHc19nTfLGvoneeBAhqFFnm6JmpWTySX
|
||||
60,Myriam Prandy,Camp Flora,,PH,14.6387213,121.0505978,1CNA1Kw6wocXgRFogNXRvUGQYnmustMktk,6777 Montana Court,667,1LgSjtFKW8UFoPecTeY9reCRo85VhRkCCg
|
||||
61,Risa O'Glessane,Rāiwind,,PK,31.2470602,74.2308449,1DF8ALPSV1oirhGPspm1BoBEM7WBkfnpoP,210 Mallard Plaza,378,1FY41heaNJri5VNDSS3AY2GgskkzSyeQHP
|
||||
62,Hubert Plues,Wudangshan,,CN,23.1639193,113.3423334,1Cq6vetSZAXb1Pq9mzARnr2n3CNJW7RUbN,83 Lunder Park,691,1CcTRtZZz4veHGTxGUnWFvMXaW3H9MpBMS
|
||||
63,Redford Zotto,Nila Dua,,ID,-6.2185725,106.9723202,16QqkW3BB7NDcwHjrRb3X26VyCJQ7oytcw,53537 Oak Way,327,1DD6h7pFhUwcfTZpWC1nw9FqkQwr5ac5mU
|
||||
64,Letizia Byard,Harbin,,CN,45.803775,126.534967,1LZ9Hah2kHzvUDgfR3Dz3pSzNm7HEchMPk,72134 Troy Drive,918,17BeCSyg7zsyHCChRqjEQcLWEtg2TLhsrW
|
||||
65,Deanne Petr,Ringin Kembar,,ID,-8.3083083,112.7211714,1Gwd8XvHQkA7FwuHW5EouyKXz58UdJsGGf,93298 Meadow Vale Avenue,692,12gtwFQjcLBDKfrsNvBWixYk5FGT8QRL3E
|
||||
66,Ellsworth Dorcey,Henglu,,CN,31.627447,121.022581,1JWsqpdvt9pTyRMj21RyD8vNM4dMmqtHa5,79193 Erie Road,155,1P5M468S4G4cLCuEEK8uuotDj1K7gG8qg5
|
||||
67,Adoree Humburton,Horta,47,PT,38.5328,-28.626058,1LfAjBSKfhopZ1tQkjoHRS64ndPjkBHdvR,4963 Welch Lane,691,12zQ7eQ27R1PvUyyfY7qqLXHCgdsi1oS4x
|
||||
68,Purcell Kirlin,Independencia,MEX,MX,19.3004022,-98.9341937,12UdTikZM17Hy1fdt8m3rxK6ukH5jcqbHL,6042 Mayfield Court,219,12ws9n2Fhkf9qP2WLjKfHzNQexvmKccoxs
|
||||
69,Lock Rawling,Wissembourg,C1,FR,49.031645,7.950014,15vaCoN9M4B3ix32SgNbkKF4YJV6mAYWQc,570 Bunting Plaza,278,13fweWgwRKAFF6tKXHr94b77PSLa5tM7Hm
|
||||
70,Rianon Simek,Fanyang,,CN,31.096749,118.19426,17bUCYFpXPovCB7jqfmFqndF46xeV5Epsc,778 Gulseth Crossing,974,1GmKz74wVZ9CXtXSVDFQSSXxULuxWLBf5w
|
||||
71,Ximenez Haslewood,Lleida,CT,ES,41.6543565,0.6251541,1FzyzAfjxTV4T1XNhzmggq2Cws42sEjx4J,47550 Golf Course Point,231,1LpHk7Ct7kKQhCqcNLZXGhWm7ZWYtnEHLB
|
||||
72,Ted Stoade,Langensari,,ID,-6.8008183,107.1781769,18WPJS1c4BNqzKWscMowDKcLzquTLz9y3s,873 Corscot Crossing,567,1NaY741u42XAGTRgTCnBUvogseSSwfwt4z
|
||||
73,Arlette Clendennen,Vrýses,,GR,35.3750852,24.2012676,1QBqR2GXKzeYvw96xWt94sqcMYpn1rANYr,54175 Arapahoe Crossing,75,15C7VyBKd4kQAuhgNdtSo4hMit83LEDpzZ
|
||||
74,Jennine Checklin,Irvine,CA,US,33.6682421,-117.7659237,14hy1irSCzMQtRgBrMJmw79bqzXBEsFJes,24406 Eggendart Center,700,14rZawCFyyWrH8QQn9FqS1AwcqSaBr8VHu
|
||||
75,Alis Adamov,Tafí del Valle,,AR,-26.850856,-65.71065,1NRhbW74WHpB21yYU8wpgAuJtiEqfLvoNr,84624 Forest Park,777,1JXNmvFmMWiZq3FMPsaENpDuZ89US9B7hB
|
||||
76,Janet Hickin,Kasturi,,ID,-7.0148687,108.3534306,16zQsguwdnHGU1PPteAAYsJEcwYydoiFMC,0288 Sachs Crossing,865,1Mt3FKBzsfJbmpvsEe74ayvrM1aUkQeg3X
|
||||
77,Moses Kingzett,Salamwates,,ID,-8.2310152,111.5766407,177LHwtJ4ELFFXxLFPHKa5g5YQJQPYJFJS,030 Farragut Avenue,30,19v6CokNc4mjDtJa4xZs5T3Bt9U56eAQPV
|
||||
78,Bank Gregoli,Mamuša,,XK,42.3273306,20.7226764,1NhskrKXqMRF7YwL7vpW2EjCvtjekpmrTJ,7171 Sheridan Road,663,1GZAcVqTY3nxc3aMbframHCMHLJ2Pj2fRr
|
||||
79,Ralph MacCrackan,Hāgere Hiywet,,ET,8.9721547,37.8618422,1FMAxtBgDhbY9eyBP2JCJH4YbA82C7GCGB,095 Namekagon Pass,736,1JQCQmvNd4KqXEuSLAdmHgkV9HauHXTn3B
|
||||
80,Chiquia Riccardi,Stockton,CA,US,38.0434058,-121.2969674,1F9Ac1isLP82UjYbQUby7dqQQEdJPqfVzB,179 Corscot Drive,919,1Bxx8WDSq4wpbz2JsQN9y2Dwzhxwmgjbpa
|
||||
81,Teddy Grainge,Panguna,,PG,-6.308009,155.477221,1JM5GoQNZqL6ERzHKde44ebWSFKnXJpUrr,43 Ridgeview Place,827,1ua2cb174qRFMrGy2HUnFK5AUUjKwZtUY
|
||||
82,Arri Hailey,Guadalupe,,CR,9.9477873,-84.0592667,1G53ngEkrMj3QoJBLLW7meKB7L2NmVsYAu,924 Jackson Hill,462,1Lvhrfmcvo4cELaqQaUouL24JGdWmgNrHn
|
||||
83,Deni Caldicot,Santa Fe,,PH,11.1856085,124.9202302,13veFMhakbKLhocwEoZGiGq6zLxNsTwmP7,1 Forster Point,108,13t79pJX9G8q7iEgbdtDiAg2h9sg42b189
|
||||
84,Lilian Lobley,Castro Daire,18,PT,40.8979064,-7.9345589,16biQP8cfTNF424LETbmpSne1Ki9JEiETt,2101 Lawn Circle,979,17ufLzg4XQuWodc8bkeNff4sWWED4F6EKh
|
||||
85,Beverlie Kettles,Fradelos,03,PT,41.5047902,-8.4776032,1H6JWnPHKmQXdAEpXqTzaLyypQHTsdK9Nc,56611 Dwight Street,707,1FDWUPPQZpoBPmBpWCibi33AxPe3YhrmVL
|
||||
86,Natal De Domenici,Bago City,,PH,10.5712156,122.944471,18Hp67WruR2mXfvS8FxVeQAorUBmcw9z55,3138 Dakota Place,875,1DED9KbRcTS3HtrYfw9VdM9A6yiL8ojzG8
|
||||
87,Tomasine Streeten,Tunggulsari,,ID,-6.5489311,111.0683954,1KGPcYcG8nDgwEYC4WAeCVPqP4gXtFUq5m,33 Coolidge Circle,843,1JqBpsBh9dtcthWKzy6PiZZExLEUenMCQp
|
||||
88,Dorey Drever,Filimonovo,,RU,56.2086482,95.4511119,1BJhQzhzbuFPjfaSNj5t1YKs2oaLvbcE8P,209 Kingsford Crossing,407,15YYk1ziqYTDpJPn2QzjndspbjqWbRHXeZ
|
||||
89,Toni MacCafferky,Zagazig,,EG,30.5765383,31.5040656,1HxPTJgnqpKqvPN2SuTjjw6DQhPheJobVf,585 Rusk Trail,221,12KJABp3kN6VwwzaDRy7WWMVuPT4pHHfm8
|
||||
90,Daffie Allenby,Salvaleón de Higüey,,DO,18.6205659,-68.6978423,1DS8FKK3yr7AHAYgCpefMBmMkmtYrgPzdX,322 Warner Park,490,1K96jTdZaXeNDYaNH1NJits7RouTME7ENw
|
||||
91,Donni Sustin,Zhaocun,,CN,30.97213,119.310773,1FryTXnffLwRcgbFA9geXHhLkehsbvuN3C,55 Jana Parkway,226,1KGtWqtaJ8Dsi47ahXHv5cBFjuddyaL6gf
|
||||
92,Humbert Daniells,Tam Bình,,VN,10.0679356,105.9464874,1Njimycu53C3BjhGgJEizfSoCYw1Hf6s1F,81895 Westport Park,73,16hQKaEyxrHSiXxZM7T67Z1Z2um51viZ9z
|
||||
93,Nerty Crookston,Asia,,PE,-12.8010669,-76.5257007,1NpBBEAQ5HaiocVTv6Ce9ZLBKcYt7ykkY,69809 Walton Drive,854,1L1T2ECVvmui9ZTEqiBsqcPXo27tbQc5QY
|
||||
94,Marlee Taft,Yongning,,CN,22.75839,108.487368,1Dww9dTT44SQDe2kCCyWdUXjUf7Z5HMPSp,4365 Farmco Circle,160,14YCpwu5qyD2YMXR6G5F9bjiP1CpCDMvaF
|
||||
95,Leonid Lewens,Oeleu,,ID,-9.9678,124.536223,1DhtCAoabcY96y39vewCTEnh6cKo8jSHK5,84087 Daystar Place,612,15tfTmJc9JSPpcmgGabGh5DEfyrXiA6EmV
|
||||
96,Mabel Gilbard,Yalkhoy-Mokhk,,RU,43.10528,46.19045,1MuHRtRArno1FRWSej421ibaqvfsPNEFww,515 Superior Road,175,1DxqQt5LFa7Zae46fqTybUFRzEwNDtA9UE
|
||||
97,Marleah Gerriessen,Aygavan,,AM,39.8752693,44.6683679,1EdmCoADgV15R9gTgWuboKyuJh7bnYiQeG,595 Welch Junction,57,1MWWNCGwGBrSRsh39thAWNXqQZ8FkBTk1y
|
||||
98,Stormie Andriesse,Bakungan,,ID,-8.2,114.5,17Hn46mWkPkEU7CENjFhiHzQXQG6tPhwnA,8720 Lerdahl Center,751,1cDkvtTwHaQ77Ed35RhytkErjY56QMYps
|
||||
99,Margarita Abernethy,Liutang,,CN,28.162833,113.643076,1Kbz8UpURvm1Sge75rhDVcWnkNFbVS2M1E,3072 Dawn Trail,776,1h6fVVYbSN93FgMLx5bYBJ1hZVT6YQGSW
|
||||
100,Veronike Olliffe,Gorodishche,,RU,48.816388,44.4777899,1LwDW2r4DZtWMXB4CQtyTEZsF4Ldm6v3p5,351 Cody Pass,755,1LLegitL6emqexAbo1g4wng56n9Lbb885G
|
||||
101,Alistair Fowler,Georgīevka,,KZ,52.7572749,78.2041704,12Hgardf4xcSYGBikUYVyrQoccLUnsYjQx,854 Leroy Parkway,797,14Hrdg5VkQzj9wuRGz369hvkbVdydd7iHY
|
||||
102,Mureil Ander,Xinyan,,CN,32.147679,114.091192,16qtEUNhCUah8oVE16sLEkVMocrgbzWg74,30240 Lukken Plaza,637,1AwKXUXTVBHU3YogmbVCZufQJHbJ7qWBy7
|
||||
103,Borg Zelake,Linfen,,CN,36.088005,111.518975,18zJQCM7TuL1z5A2TeywYsD4GYUS5RZNCN,751 Columbus Center,733,1HhjK84mSQc42KMpWMWBkocbMvh6mr4pqf
|
||||
104,Mikel Coulman,Funes,,CO,1.001167,-77.448926,1DhNvfcHWGFJTcUEcPb437uJbA1JZxaN6w,00 Haas Parkway,858,18KK3RLM1VvG9rnfNLmjNYJdBQSuiopSEY
|
||||
105,Carmine Boissier,Temizhbekskaya,,RU,45.3106092,41.0495422,15a6ZXUqaDwVyN3ur12DpCbF3MdH2QogUB,00285 Goodland Terrace,585,17b9vkE8zDRinrAH8MFCRWr5fp7r9bFqVU
|
||||
106,Brittney Pendell,Nizhnepavlovka,,RU,52.1080018,35.7847305,1AZqDRpTJcBp2q7hgHJahs955ZnqMJE8Yt,248 Delaware Street,604,14PUzcFoDr9hzZFqWB27FeFnGPC7yCcMQU
|
||||
107,Katlin Mannin,Rukem,,ID,-6.8013457,111.4126837,18b8eWzgXZSC2hexPS4zLudHYu3EaSLUw4,3 Everett Crossing,762,1C2uJ2sFNDRtiLd2TP9wHDYsSH7EDxKBza
|
||||
108,Dominga Jeffry,Columbus,GA,US,32.56,-85,1tvQWB67f65VNZqPEkPuzXFZqSzKqCyQJ,1 Commercial Trail,624,12X5U1aAud3fEg3x2UBn6y3DuG2x9rNeV1
|
||||
109,Feodora Delgado,Radoboj,,HR,46.1629456,15.9296045,1Cqp7K2ur6fJsNW8xHNo1eiSTRJktjm9gd,75921 Division Crossing,369,12apzddqZApFDsQCHrkt3aaFEiMemNwwh7
|
||||
110,Gabi Blaisdell,Senovo,,SI,46.019373,15.4759099,1LLRx5dfPdhohMDwoP6wzy2uLJEhbriX7u,1027 Everett Avenue,212,1MocUn8f4m1zH4VA6hzfwnKf3nMS2fzkPS
|
||||
111,Melisse Tempest,Shilong,,CN,23.105394,113.874335,12hUF9pgHy2nBJSTecTLHYqtYNiEp6qezn,2 Sauthoff Junction,782,12JGVxa2XtmTXpbdcJFxPjK3UNnruWgS1P
|
||||
112,Idaline Skaif,Jejkowice,,PL,50.1076329,18.4682284,1Pg76JZBBM1ccMgNSBCzKmnmBWm2vYZDuD,6 Pine View Avenue,936,1Prhv45VJSdDNZFdZSc3DiniTb6U1ypLMZ
|
||||
113,Ashby Sweeten,Zhaizigou,,CN,33.881885,105.538243,1QGvHjfmbNJ1juTgTat9eMeLeaxcwY2DLX,93 Macpherson Trail,266,1JPtF9xQSWmKpcDWbVFxyDKnyssbc5bL3c
|
||||
114,Ainsley Edgerton,Antioquia,,CO,6.6169319,-75.7720328,16ndLyoL7J4gwBkjmbmJ2a5MBn5d5h69Dt,910 Red Cloud Road,442,1LZBJo97jr3eSh2uHEGNr23hdLKMY99bb4
|
||||
115,Kitty Scyner,Gniezno,,PL,52.5299344,17.6034134,1DoxtMkEKvXTEsfS95eDPzswPT1gxWQBcX,29 Redwing Point,380,1GnjGP2pXsDo6L5XtMjjmsMhAAy4yWxw7X
|
||||
116,Sindee D'orsay,Asen,,BG,42.6700312,23.3695036,16vdUTepjzmbGX21d3JutXg5c2Nd3vi8bj,27185 Grim Pass,722,15XbFgkfjQcpGSnPqSVPnts1g9AXdtPCMz
|
||||
117,Corinna Erangy,Březová nad Svitavou,,CZ,49.6542555,16.5117461,1FZybsJYQJUfahMyVmFYxYed9LucvNGFmo,8291 Utah Court,529,15dAsM4XEQT23TusKUNSgkDoJjbJVnuxY7
|
||||
118,Bron McVie,Kabul,,AF,34.5553494,69.207486,1JtwvjeWDiNTsdZLP9MKF3UhAjHDyWQ6VL,586 Esch Way,836,19DEcsbWzoqLBXBwMrtXcBVmCSsJ42vXWx
|
||||
119,Emily O'Kelly,Marumori,,JP,37.8611669,140.8154202,1NKj6MT8oPuJJ7UJbhaLjZXV1VKKti6rfT,472 Fairview Drive,986,19XfznqVcL9gM2FDJetwwkSnYdzcb2ygpZ
|
||||
120,Alexine Dudgeon,Nubma,,CN,41.3212232,-72.8647203,1Kjx9do1tRto7xGsdxj6ZS9jCDc2Xs79oB,809 Garrison Way,233,197iqiZQzTTLWwQ2S9gnsGG5SfDsoypYgh
|
||||
121,Lizabeth Van Der Walt,Bykivka,,UA,50.2923198,27.9814782,1Er5CJAzfo2kHq9ZsKhwVFc1DJcpKCFYVX,93 Bowman Avenue,252,17fafLkbDLRQk5r8tuKwPoCq4xs2AkVoNr
|
||||
122,Shelby Guidotti,Upper San Mateo,,PH,14.5700525,121.021201,1GCkKgxWs2FdSpedvUoRHgBgyPaiv7QhVn,76 Menomonie Hill,780,1K88Xm2RQHS6tc7Up3gmDAoUBctBbHbaGS
|
||||
123,Dominick Szymonowicz,New Brunswick,NJ,US,40.496654,-74.446092,1GstevdyhQHFNRGeku1vRP9vQWtDLArcue,60 Cardinal Crossing,485,13YKd9QDCuF5Y6c45EApkR7ZFc5W3z5D6v
|
||||
124,August Bowcher,As Suwaydā’,,SY,32.7128968,36.5662858,148rx9kaLQmyQNVmEzPwwSiadPoiZm4Pvk,74479 Towne Parkway,319,1CJXts8XWktapY6VTSi6fMiBzHNBMMEkub
|
||||
125,Devin Edelheid,Ban Tak,,TH,17.0405373,99.0571089,19F1dVafqRkeCA2GZyC31AcAr71DLBDTME,85194 Lunder Terrace,139,19tBCjYCB2oGfzmSfQXPs9HQqqJEamRLQf
|
||||
126,Peggy Simo,Aozai,,CN,23.791187,112.299103,13pNfKCJ4wNB1ttUUbsgtYAeEXSSr8f4dm,02545 Coolidge Street,587,1Frpz91PvjmWPBkGBDnM2kaj6YhPJUyi4G
|
||||
127,Kele Oven,Avranches,99,FR,48.690168,-1.3699309,15Pdu5mP3gfZDZq8J2h7t6gTRMFjczVAQb,606 Welch Street,924,1CigkG8CexSahqwS4iczCMcPbkP3HsuEAS
|
||||
128,Riobard Kochl,Krajan Jamprong,,ID,-6.9604,111.6157,1P2hx4ncV6SFYUcuWfBZifN4N1YooHD4bi,930 Artisan Hill,537,1N3u8sWHCHWCDkSv3qj7dgkcs1CGwjqu1o
|
||||
129,Sybyl Newlyn,Heliconia,,CO,6.208781,-75.732944,13hjUKzDou4omRXjkvpaRsHdYbWCSCVnn4,917 Lake View Court,487,1JvKMMS1ZKJNooj7X4UAdiRjkWLkaHpQs
|
||||
130,Katharina Pinsent,Luokeng,,CN,30.383617,109.702514,1DpgoEsgLMSBtcvJtgahnbJfJ6YvJ2ytKq,3890 Lake View Crossing,310,18aD3uNYKrDeT9jEqQZPFRdy4DAjiyh679
|
||||
131,Johanna Foynes,Louis Trichardt,,ZA,-23.1846822,30.0723009,1GuJgJLAW1NgbwFtqbX7KLkDeq7W7Pb9dA,3 Marcy Center,559,1LaQCnsJcCaZFHtk9DLQnqs7ue5C9wSyja
|
||||
132,Kelly Winpenny,Vienna,VA,US,33.5177039,-117.693138,1KYBgCvcyrvFpkdQZkNTz6nQtEMqDC8MCG,2576 Arrowood Street,234,1D7Jb4qtfTAHLC6QmznskPEvUVmKGAXu2r
|
||||
133,Georg Bart,Ketawang,,ID,-7.4104934,110.3279026,1JyswLw4DPN9o1paftH9E3eWwkUenwx2Vy,47884 Marquette Parkway,806,133uMh2UsG2rybD5YyFLnSHroqXJuM8mga
|
||||
134,Bogey Coyte,Sztum,,PL,53.9208518,19.0295929,1KmbxkfbqKUCq5eF6k1srEDcHrgFZQHkf6,76725 Bellgrove Pass,334,1B9gsyrPeAFzppjiDeLJ9d36tMiFMG8uh7
|
||||
135,Avery Frift,Särö,N,SE,57.5113755,11.9586978,1He1vBAHkn16VJJq9fEjvJNHWy7T6n4BuU,92 Schlimgen Trail,346,1GS93v5UqJ58mxYiQSjZZcKdUpFUvRvBaP
|
||||
136,Phil Sutor,Matagalpa,,NI,12.9290069,-85.9151211,138yrE68FWF55mN1Xo8zNTksbfLssBm3Hd,2 Fairfield Alley,819,1KsjcDwK3aNpQ7gcEUJvRgX8yTYJrTh2y6
|
||||
137,Nellie Ancliff,La Ensenada,,CL,-41.2070875,-72.5387781,1Bqxkws3HaAmgvvxUuK1uDk7wyxc8eDvGu,50583 Coolidge Center,688,1EdgsYbS7TAYPsAQBKy6JHP7wU6B9wUj7T
|
||||
138,Davy Hoggan,Kakata,,LR,6.5301553,-10.3441758,14FtRsA7n1w7wpmdayLinAsKQf7LNdEnTH,6216 Granby Alley,70,1P7YBznRKxCUtTwYLw1koXmVPKAkN9cA3Q
|
||||
139,Skippie Desesquelle,Rasi Salai,,TH,15.3445135,104.1560983,1PDFGhArFAxoiHc2ZPzjfLm9pHzqTYo2X7,6 Jenna Place,115,17X5Lik5jYYnup7xJUeD2yzrsHXB3HHKXs
|
||||
140,Caralie Haresign,Brikama,,GM,13.2747943,-16.6409198,1CFYrAB2RURzD1m3ZR7oToeCFkAZRKWga9,4 Tennessee Avenue,562,1CytZsj822imvsqmn5gbvRB3FAkndhQBFb
|
||||
141,Cindelyn Stihl,Anjihai,,CN,44.362833,85.35698,1Gw2n1tqiLFJHGiiBYinnfNR5vnnKSvLgS,9521 Oneill Terrace,187,1HVpVpMysTvZCV3yhhR6ZVY2eSVkPtAA5D
|
||||
142,Mel Leither,Cosne-Cours-sur-Loire,A1,FR,47.3675629,2.901637,178UAyPsMXpXHfFRiHNCQePnaqbA3tdVFW,1677 Manley Way,660,1HmXQB3y8CBaYaL2Pe2zCm5JXS3djLQ1Hq
|
||||
143,Adele Kirkwood,Saint-Pierre-Montlimart,B5,FR,47.2662587,-1.0263278,1AgZuu8XPqV68viatznaRbk64M4nhk37j6,480 Brown Point,906,1HN79oXNyxC4KZbAWoNEbP2ALTh4kdPFhU
|
||||
144,Tammy Ubsdell,Aş Şafaqayn,,YE,15.366667,43.433333,1DVkYwLD2bezdW1JUbZpBZEoCzvNtbMJNe,40893 1st Drive,212,1BySvgCJL19VxZUXreiSvVTM3nHrYEvwCG
|
||||
145,Jacquelin Schoroder,Belén de Umbría,,CO,5.2351338,-75.8346203,14Ejde4R7gUTx6eRrQKVas1ahXruNDA2Mz,750 Manley Way,462,1DjEet7F7H4qdTLzVsYUPdfSUSw8Qh7yJQ
|
||||
146,Alika Oleksiak,Lins,,BR,-21.6733002,-49.7471388,1MY3NS2YwrjGNft1rcsWs323AkS7hntgvB,785 Lindbergh Pass,256,17pYdzzcKGUqtudHbVeDFBQrGvk1zkcCrV
|
||||
147,Andeee Batte,Batulenger Barat,,ID,-6.8893639,113.3914263,1N9tpqH1eBfaW22N16qY6ZtibDXsHbR7fe,24054 Loftsgordon Terrace,712,12JjLkgxYqgVgBE7rjG7WEwW9jPnfDtGFz
|
||||
148,Layton Kearney,San Rafael,,CR,9.9436507,-84.0166951,16RQPMRio7pxJUHa5w8swCChBDw6Q9xWU8,5845 Sutherland Hill,328,16b1KBBDdyAVFsGeMQ6kD6Dyskye938XNd
|
||||
149,Tori Hartil,Foumbot,,CM,5.5028937,10.6365483,1Q8DWSYjwnv55aqjPEvSftZxZDJphfVb9S,7036 Bultman Circle,124,16aj6c7TkNfrgVrqMzwEDZYG5edjgeoZQi
|
||||
150,Tanney Cranefield,Songbo,,CN,29.859258,98.007143,19SJzqv85Vaa3GQiDnfdyek6SZ5q3asRTS,70161 Schlimgen Circle,337,1rWtbSztBuJ7kwCj7fiAoa6kSNVbF1McL
|
||||
151,Eadith Fearick,Kanlagay,,PH,5.87568,121.29146,1FEDALUQNW9MSLDP1x2T6fAgQXSmovZMp6,69013 Crownhardt Plaza,145,13p8rxtWG16e3LrYhVSeAdXW8BsUnotnsf
|
||||
152,Jaime Miettinen,Quisao,,PH,14.4392961,121.3345633,14BnjjBsav2U8mnJB6zS52DSwmfJFvcCgR,9213 Mccormick Way,994,1L3XoYWtL585zPjYinMNA9NV15BK8wyj1z
|
||||
153,Grier Bernhardsson,Maranura,,PE,-12.94844,-72.667999,14spt7aVHjX5RDczABZ41XKs7cS3mwPp6x,78 3rd Plaza,89,1EuPjzsEUnoRrnJLa8UXtNTpkmAA3Qz3qq
|
||||
154,Ben De Laci,Tōgane,,JP,35.5599466,140.3605429,1N266VxL1LK7H1e6hZsdVmEJ7fU6pPmCog,30073 Moland Court,813,1LBQbr7EAkebCh7gWhSPn7zDk2Ls5WY8nB
|
||||
155,Gabrielle Spellward,Xiantang,,CN,23.296786,113.823135,1MjkmmYnct3YzfGix77V1pvZ2hm4qAo4JG,1 Starling Junction,390,13nKMjGjJ3Z6eCrMnc6dE937VjSoz7X5tj
|
||||
156,Delmar Oneal,Ambanja,,MG,-13.6635336,48.4537424,13195gXQeKSnVfsrdspvS28Bi9PdxUhqHB,11363 Ridgeview Point,132,1Fi8xnTkLi6YonQRA8cZkbgUEG3QJXw3BD
|
||||
157,Dionisio Verrillo,Lac-Mégantic,QC,CA,45.588882,-70.903207,1Bx5oUvnqeCemnEKG8ctPEBEnhXPZhXinb,978 Brickson Park Street,385,1JExEL8aikNRo14xLJBXepkixQKdzaqr4T
|
||||
158,Garvey Jacquet,Neuilly-sur-Marne,A8,FR,48.8537451,2.53823,1PN8oVQ4a1Ka21hFrU1HzUwo1dVTrVoqJ3,30031 Hazelcrest Terrace,50,16g1wyBrkSGkqsLs9dCuR68MuM6FD1u4CK
|
||||
159,Rabi Waye,Bothaville,,ZA,-27.36667,26.63056,1DANFkqiaThgVBjetjykXhakSRRxQKb7AU,5 Gerald Terrace,191,1JZTRMUC5decCqupSWgfL2QBCuxszhisD8
|
||||
160,Randi Burner,Dongjiao,,CN,27.756339,112.555087,1J2XFCJ9whj2pSqmJ85XEFikDwFRvctUC1,0 Lawn Circle,446,1DucMPQRSQcFwZVp4AQaj6iBqCCXWnFJP7
|
||||
161,Forester Wardhough,Desē,,ET,11.1270398,39.6363314,1K1DoX1SvuRgzz6S7XyDQedZpWEx8c9nmU,6 Toban Drive,928,12AYRyhzAftTiEjLxfxUmSxmmEQjDWRhoJ
|
||||
162,Quinn Jeffress,Qal‘ah-ye Fārsī,,AF,33.78529,63.24735,1PzQ92zPn82ten8D7MNZY7BMKfqW6asmhj,2 Paget Drive,867,1A2oVPg9jUwn1drnpZmqbLMvqTueAqpNJE
|
||||
163,Zola Bigby,Tupsan,,PH,9.2478,124.7215982,16KtPXptNahPaoWNa3583QuxgSwgtwjajy,24 Melody Circle,450,1PsZfeUgVYKtkHsLMynYmemhh1nR4BfRja
|
||||
164,Alley Mackilpatrick,Bečmen,,RS,44.7811062,20.1963089,1PdNEsjRe7Wrf3L2mhEJurbhJDB6bewKz5,4 David Parkway,349,12kGivNxZKDbJWV4pywuP8ngLNad4Swqxg
|
||||
165,Ingunna Humpherston,Jinjiang,,CN,24.781681,118.552365,1PWNbg1MNdHW3j7bP2fK3cHENVfy3S3sjS,64 Chive Parkway,873,1BgzWAeqcATNm7LH7CanxVHvVZ8yL3Mx62
|
||||
166,Xever Dennerly,Az Ziyārah,,SY,35.6907846,36.3376199,1EMDd99rBa4hLR7B2LUGyzSadiJiH9NV8M,61 Talisman Avenue,787,1PsEfurZnoHTYAqvw2giAq3n1wowHbDfjx
|
||||
167,Ertha Steptoe,La Dorada,,CO,5.472709,-74.667984,17qepqY5UhZp7Dmt1j9UT6dGKkKQLe3PPe,2255 Forest Run Crossing,215,16mgXtLch5D49mVGdtTLTLXG22j5BofXg5
|
||||
168,Sloan Adshede,Villaguay,,AR,-38.9467974,-68.1042424,1NqPDq1XtvYpmMoVwUUaLkVSqgg6rf4f3N,76051 Cherokee Trail,598,1GMTrrPVWGUsBEeqGe5RpQFunn3JRKz84W
|
||||
169,Zebadiah O'Tierney,Ressano Garcia,,MZ,-25.4562038,31.9925717,13Ex6LHewify4yXqnMLcDyEQHgiorwGDAE,02 Heath Circle,624,1EhJ8He21bUUvfJV5rpKmziP5Hk3fHSCur
|
||||
170,Jillana MacCartair,Vitina,,XK,42.3221655,21.3589808,1Q3casxZyfYdTrRjKhHRmGvhttmwWAbjdN,944 Corscot Alley,562,1BKmpb3uqUDTqEKVkQJHZhX2Pja2bt8Py9
|
||||
171,Ara Klimentov,Fusagasuga,,CO,4.345152,-74.361823,1cJ1UEQMtt4PXXzxkWpYMyAsxYg9seJXn,51 Spohn Avenue,933,1JfQtoFj2DbjVUb7hb7m6LorxDchna9cCo
|
||||
172,Sollie Tenwick,Växjö,G,SE,56.8521006,14.8183366,1T2dfupouau9jdtSKw5Wnis4zNu5AqG1r,74 Ridge Oak Way,878,1Gv7xZzRPuDRsqervU8qcfqmmbPQvMrxJM
|
||||
173,Jammie Waterstone,Paris 12,A8,FR,43.4945737,5.8978018,1HekDA6wQ3XoqMmbBAy3Pw1RMYfCPYiZfw,66 Arrowood Crossing,325,1MmMifYF2r9x1W78i7nYdmhDcWBDSun7SU
|
||||
174,Pepita Starcks,Frederiksberg,1084,DK,55.6840317,12.5532982,1M8uebU4jqxi2JmvXqvMFbFKD9jJUodixR,2 Pierstorff Parkway,251,1ADAHn5T8J2ZnyhZSPhYadmt14P1Ex1TEQ
|
||||
175,Aguste Pappi,Tata,,MA,29.750877,-7.9756343,1LNNtj9o8GFwQAHwjENGqoc9jHXMXS6r1V,215 Homewood Place,245,12EmmNCAUtQJKFcHatZGz7xGd7mo6115sv
|
||||
176,Dawn Challender,Leonárisso,,CY,35.4623398,34.1253751,1E5TSeSwVk3bZWHiujnvQo9nuY1MikNkjA,049 Monument Trail,104,1ADF129VqnMUqMpkzBPy7tHjvWNr4aBU94
|
||||
177,Lisette Clutheram,Kruševica,,HR,45.0827061,18.5064033,1ppsYaUgPQ4e6xNns4SdPgJ4GYGw7G87V,91 Algoma Avenue,907,17iftqriUJuuzXy6CfSAuHwxwyhR2VWjXe
|
||||
178,Arri Pinar,Moneague,,JM,18.2760947,-77.1168753,1BuWowXqbUmT5kRGAvg372wVS6WE7RMctu,9 Valley Edge Hill,945,1NVNfufbDCJeofpaErmaZEhP49p4R6L4iq
|
||||
179,Malena Blick,Guayabal de Síquima,,CO,4.878323,-74.467283,16ruXKWGu6TmFNR5FjJDUwg4V6CBURjVVh,878 Meadow Ridge Street,43,1CqX6CX2hAu8UBRdixecM8hgHVjcVg56kE
|
||||
180,Wendie Fowley,Córdoba,,AR,-31.3221103,-64.2679738,1CSJftBmtst2zWt1kdQmYEpdBbeZ8z1m5J,598 Forest Dale Hill,133,15fKFx7pB5utEKMh3ps73obY5aS5m9ZZ7F
|
||||
181,Emmalee Winterbottom,Sukmoilang,,ID,-8.2631,113.8575,1PKQhun67Jhu8JzrpHo4goCdZSSKSgAKnQ,9 Doe Crossing Avenue,191,12uSY9DQZn2xNhdDpk4ycNCsCWQ6uCLpYy
|
||||
182,Ellswerth Basnett,Borbon,,PH,10.8379491,124.0251538,19bsQY3GdqdfiqnvuN4LYM3VhErBKFtygJ,07379 Hayes Point,157,1GX8pzyJDwruEJRrFizXmv7P2fQen9Ak7Z
|
||||
183,Amalee Yerbury,Battung,,PH,16.7551121,121.7483586,1NxuWMRuweSNB1WmEp8eUiGvvWBmTuLH5p,77598 Homewood Road,824,1PyiGKqY5f5hbfMeJgxuFa6yxSgDJMJFgP
|
||||
184,Boycey Spinetti,Gifu-shi,,JP,35.3985584,136.8331549,13BeutLAYJr9Fx2nmbxTdekAD2SRoR8ZA1,61 Loeprich Street,822,12F4cXYaxWNyAd7CPAji1bjovQERSToyhF
|
||||
185,Ennis Bedford,Västerås,U,SE,59.5138669,16.0069361,1M34V78wtXsxaJCk2g1Pdt9WitFsn5YoFN,4 Lien Alley,186,1P9U6yyGYuSr117q58JxuT7BA98PRB3oh9
|
||||
186,Carolee Fruchter,Nice,B8,FR,44.0678168,6.9855112,17Dx8Sg8HpSsPC83kD7UcUU2DETiKq7D9j,4493 Anhalt Drive,645,1AQWZRJdmxewQo7sE2Qh3M9EWx8U1vX9HQ
|
||||
187,Lisa Guiraud,Fuwen,,CN,4.6690872,-74.1386906,159FVB1PS7WGChkYWZuNMQ9fLZF6qzvPgX,48 Summerview Street,764,1NfhsZ4KxPhSkiL1WWfi1xdnxUnHazZJ5g
|
||||
188,Carmon Flexman,Anchang,,CN,28.682892,115.858197,1QBAkwg8EPehirwpWRhaRpezMkH6YTuRtk,9 Acker Lane,518,16YeEWzzUGduT71QMK6KwT78dfGpYTwAHx
|
||||
189,Jemmie Wroe,Prakhon Chai,,TH,14.6014909,103.0933171,1NpFrtEBkNSyhxt9aNgonkEuW7pPYWxCbq,58579 Sunfield Park,902,1LXgtrm3bLjATEhNrRqgLk4FxUth7dJPHw
|
||||
190,Dan Stuther,Kinna,O,SE,57.5002589,12.7091837,1BnjmTB8PM9NkTBakJ1yYcpuCVyYtuxXDK,2933 Eliot Trail,194,1QCUp9bVwDUYCUawzw5ARk7TGFTPMynKTQ
|
||||
191,Gage Codman,Sabanalarga,,CO,4.8413815,-72.9899505,1B6UrYgJwz4AxBjJavgJsqNNeDyXP6L382,22408 Spenser Pass,199,1KNo2kXhW36ZUqTpfp4gWbB7uShv7p3w2y
|
||||
192,Cullan Simmig,Hongwŏn,,KP,40.024391,127.9682453,1NpBjUyxS6x7KgJWrrFPEfpRhgN4cckEgD,59106 Debra Center,550,1Q8cAGqanHsGKNz2Y3Z976mSZkzwnXUcmy
|
||||
193,Ezechiel Baelde,Aurillac,98,FR,44.9263486,2.4396467,1LNnfYEGEmnaKNCNhaYE5vN1dkcJrAbHgo,0308 Elka Place,184,1CiA2yDhxSk3Htw62JVxiT7oTqQPGV5ZVW
|
||||
194,Bo Snead,Serednye Vodyane,,UA,47.9848822,23.9117575,15aBspzcmh7pmib2ubLmUj1CwWoGnN6Zj2,34580 Fair Oaks Crossing,824,143EBYestirkpvyMps5ANG5x3dn6riPb1r
|
||||
195,Serena McFeat,Wang Sam Mo,,TH,16.9551818,103.4418676,18QPKYBzGZH262vG3e8SN45bjUREYpE5rC,0412 Waxwing Terrace,575,1DpDWRpAuy35SyYLeaRAtioovQxfKp9EaL
|
||||
196,Dory Helliar,Néa Karváli,,GR,40.9624179,24.5082696,13ZX4qvRbhXPUV2Z3KaeLss8b7h4Vafax8,84 La Follette Park,947,1E98VYxyc8toENbwoP6HhMLorZDvBWRoJq
|
||||
197,Darwin Larkworthy,Mīr Bachah Kōṯ,,AF,34.6982972,69.2758336,1LED5WZXbw1WyXoDF6Tom4w5EuZLq44Uj2,530 Mallory Alley,74,1LmHUKNSbxQx5bFnibc3wP6pkVjzrLRRbM
|
||||
198,Sydel Cherryman,Comapa,,GT,14.113818,-89.917591,1HmJ97HiGmZe3jgKQKmdF5k7w6va8AoMSV,0503 Village Parkway,827,1FRvuFA6ym8xaDT4HfpFSzVTzXWUoS13bs
|
||||
199,Dody Corrin,Verin Dvin,,AM,40.0231325,44.5918627,1NYL12GKHgFpsY7DmBEmyvUnUn9dJcA55s,531 Oneill Center,322,13y7bzA6dgudm1KAqdRjywztBiUtc7PT2y
|
||||
200,Aarika Golagley,Chapecó,,BR,-27.1009343,-52.615699,1CVYog98nBbSgVdF7bJdw4vqJcaUCR8YTc,385 Sycamore Pass,888,1TrAoRXiwfvGZkHL4PNXPY6oY2hfkYuft
|
||||
201,Coleman Jobes,Erdaobaihe,,CN,42.3223159,128.1256078,12EUSsRerY93CfprxYnwUTDupc9HFyYsKK,0546 Iowa Drive,332,19tkoEzmKREPr9GLsoYLhL4BSxRJr9rYW2
|
||||
202,Teador Gallop,Pentéli,,GR,38.0476296,23.8696276,1Az2oL5TGuFRywS8jSa21xPgvQ5ntTsAXR,31015 Northfield Park,356,1LxpAcQQ7aPoXKFTGDz9bZmMcP715NMPza
|
||||
203,Fredelia Dirr,Pasirsongket Dua,,ID,-7.3106,106.5946,1KadbQRNqeo9nciTAvVSyBudrrEctx8QYR,2120 Marquette Plaza,724,1MPFYawj3jYuXwkQBW4uZChsk2DiB7jaRa
|
||||
204,Aksel Byk,Cijangkar,,ID,-6.9888872,106.9535691,1MHCjrjwMqf7LJJKqAnp9NNnK1ktvG4LQC,3 Cherokee Park,305,17yva8SpKe4hx9EKTCm891EZ81JzuQM6Ym
|
||||
205,Birk Upston,Shanban,,CN,29.237137,91.773134,19YuCwFKJBiFbBGZf27FpeiEyQP87NX9Ed,108 Daystar Plaza,405,1ETHfiPpdvxYB9YMgQd3JjEkgasR537yEZ
|
||||
206,Alley Dunster,Velké Opatovice,,CZ,49.6085752,16.6797892,179J6PKfpMFd3mR6xJVkEBS7RypXjCr2HC,389 Sugar Crossing,273,1GBu1n3LNwRCKbH68LDZTHL7umdupVqMFk
|
||||
207,Guglielmo Albasiny,Camplong,,ID,-7.1846561,113.34663,1NfP4spBPPEhxpiJ1p4cjsdauFc7tGH6u7,3 Rockefeller Terrace,811,1A6GFVvJmckHGQuLgEdVhfGL4Myz35nvfF
|
||||
208,Poppy Brewse,Duyên Hải,,VN,9.6311842,106.4877072,1AHfbHjesMZYGPEjADf1f9moMyCTLnccGd,750 Farragut Park,348,1DirYTNXWWf6XH7owZ12z3BPQtge1HFQDk
|
||||
209,Marilin Itzhak,Ranisoba,,ID,-8.8231,121.0194,1KWSbjQDXw2kbWToY9kETMtrvwUunXpZbN,0 Badeau Street,688,1FpCHWD3V5XMYRxHzbUN4c7N2Bv3j4hXP5
|
||||
210,Tannie Berends,Tanahmerah,,ID,-6.080007,140.276147,1N8uTZLaUQgqVRdmo3fPSPq68SAZmb6KPh,1 Lighthouse Bay Junction,483,1FPmc9wEH475yik2s8neRz8VqHiFuNUzE5
|
||||
211,Gleda Noore,Santa Isabel,,BR,-15.2192781,-49.4059435,1pJe2YYb4y7TQRdXLfiYaQwQicKcFodcq,38828 Coleman Point,686,12CbSqDKMMgmeFa6g6m9onLFoSksVwwUGo
|
||||
212,Cash Macconaghy,Spas’ke,,UA,51.5542766,32.6130851,157FVdh7e23c9RmvbF6pACvg8GJbVtGSxL,1 Brickson Park Junction,433,1LVj7SuSewGv72cUDjU93HZ6cfW2nF8Ac3
|
||||
213,Gradey Broggio,Kajansi,,UG,0.2070254,32.5401025,1LngD4VyDR7M6rNn1VKQSNCm4X9Uq4kNFc,9618 Summerview Terrace,195,19HPciqSfwi9sWbCXasDwb4PBgFPQGpi5L
|
||||
214,Damaris Cayser,Saint-Constant,QC,CA,45.3770527,-73.579509,1L2K3vJKerxbSMwSVCjgrd96XnqnCZcr8C,30 Dovetail Place,630,1J23BtwDa9zhbprbVBerW26h9um3Y2sNvQ
|
||||
215,Onfre Etteridge,Carson City,NV,US,39.1645065,-119.7634857,1763N7xZr3MZvBKJkPLCHmw6E5uXKJWx9c,393 Burrows Road,712,1NPijqywM5dDzgCpi4W89jAt1vD6fNtF1D
|
||||
216,John Siggery,Ixopo,,ZA,-26.1633679,27.797184,1CJqNpFWd67zcW7nqKnQoQF1oyMNcavmtj,08214 Hintze Pass,349,1LphtwhL78Ed1seivapSMnXGwinhdZjFhr
|
||||
217,Abbie Gawthrop,Memória,10,PT,39.7847567,-8.6496676,1EMFjxiHhXpWDPpcToHreSoY1AQGUMZBGb,788 Dayton Park,355,183jpx12xuLCpxgnvNCdWywwBSaBzXjzoU
|
||||
218,Link Songest,Dolsk,,PL,51.9817834,17.0627666,13MQewysiS89X9YR79J9VorXh5YjYR7KnV,9 Mariners Cove Street,129,15ZS8NRCaT9pbuNoZJCeoVF9TM4j2GXrZz
|
||||
219,Auroora Mora,Baltimore,MD,US,39.2817753,-76.6932438,192x6A2oRj9rjkAtWwkZ73tNrcErszY9ft,52436 Veith Lane,82,19hF5ZgxAJxNeDoCcgjMoCt25yzrF8YK7z
|
||||
220,Saudra Bayfield,Buenos Aires,,HN,-34.5890239,-58.4297385,1Lg5qt4LFg8HGQ9RbAviRzk2AndSVeBYd4,58315 Springview Drive,59,1P8596xg5kEmMzhEVLAYALjNeoUKoXecFQ
|
||||
221,Pat O' Quirk,Maumbawa,,ID,-8.738072,118.1171082,1NNjijsUUcgpBFBwitsCAKdLV38X64Um6e,361 John Wall Street,263,14Wb3csxMNnQndkxjUA7oTGwFGPE3e576U
|
||||
222,Mose Molohan,Oklahoma City,OK,US,35.4674683,-97.4996835,1Kx3X1Juh1ip9rz5gpbmuQ8A7iU6Jew8yc,6 Badeau Way,528,1QJ8swLDajnk3C7Ys1icepHDK5jKLs8QYX
|
||||
223,Ulberto Minty,Yasugichō,,JP,35.4159603,133.2439826,1NWTYB2gSD1B5Jfz2e4p4YqaJALhnxPUip,130 Butternut Pass,176,1Pu5pkP59sysoHei9fkA5jeTgu7MjAQSBr
|
||||
224,Elvyn Golling,El Tambo,,CO,1.4081109,-77.391527,12sMwAduhkwrVTF1yxKixUZkMxxD1Roiwp,4228 Algoma Point,809,12YRZukWpzbwPddEVhRf4hQa6vfJGtVEp8
|
||||
225,Rock Creggan,Denver,CO,US,39.7380371,-105.0265195,1M8THsFbyKjWVkg3stQoDfEuPqCJcbP3iZ,39 Emmet Lane,331,1PCTKDAKh6HZNPDRdzKeEt9B6Hjbw11f7B
|
||||
226,Rosamund Gurney,Bilajer,,AZ,40.5811666,50.0415363,14V9NbWb3uCzyobRHVsj9scfcvG2eRUkmi,4 Farragut Trail,403,1GZN7d6Eek7aX2m1rjZL3gSJYt431UWbkV
|
||||
227,Zebadiah Symson,Savski Venac,,RS,44.7821727,20.4514425,1MJZtW3vV9s8wyGLwMgMe9ZdMUdhT9J14W,80994 Merry Hill,6,1PvdeqNtLRGd7JtDA1LXi7JBdxbMaqtP7b
|
||||
228,Benedick Ganforth,Benito Juarez,OAX,MX,16.3047672,-95.244405,16fdbn4Wdf7zydE87cugpJ4XJks2Q6gvxv,5 Thompson Parkway,247,1M8PAxqLfsZdJ91sEj4gtaQfPDkHyvVcPX
|
||||
229,Ingar Gladding,Kurikka,,FI,64.9099021,25.5080144,1EwTziv1BFbqXURARYsukpn662CHNLwdAM,10 Heath Court,237,1CJMD3YejLsqzYmTgda8wc8Fq375qm19JK
|
||||
230,Palm Doorey,Sijiqing,,CN,30.900941,115.610077,1FxzKB7kgr17BY9qZRLtHsXJeNww8Giqqf,84 Lunder Lane,199,1Q145XdQ35CeVtVdmKcGvVUQ8PxP1ejyHL
|
||||
231,Charil Wallen,Esfarāyen,,IR,37.0666772,57.496673,1BM89RihuEXHC2qXT4ZRZS6MthJWcSwrkC,9503 Erie Circle,343,14121aKoFti5egao1F6rhjAXvosoohmXSt
|
||||
232,Milzie Donavan,Aeka,,ID,-6.2311496,106.8274069,1FMmUK6RY6VG1oFVAy1jjeLWVTPabM6zuB,1090 Shasta Street,514,1GtU6qNSLNX8wXVnqwBUAMH4KEpeS3Pbqb
|
||||
233,Sheryl Schmidt,Jiangdong,,CN,29.866818,121.570383,1BWdkWhspWhcJ6W2MpU5CHCNboYsMaN2eV,3 Graedel Crossing,366,1CZADQnnXS68fCABfsFFwnmmqCKDgKcXbV
|
||||
234,Wood Manclark,Piskivka,,UA,50.6960614,29.5987334,1GNEnkxCMq6ZeF4Uto1GhPHDSMNoVrWjpz,98876 Maple Center,212,1ESWk1akjXW3MaPNzPn79YzjB51EEReZhQ
|
||||
235,Briana McCroft,Little Baguio,,PH,14.6760848,120.9873093,1LsDZyeiDcefppGNMdBYJqrv51SHJ4A6dp,77802 Towne Court,722,1Bs2JhUxGGp89nR1taiS5QMVkBxzsZ7Gvz
|
||||
236,Valera Arp,Louis Trichardt,,ZA,-23.1846822,30.0723009,1MN12UAwcgucrPbEeyfg3Go9awaHcydDih,647 Barby Park,726,19QHhg3Yi9RLV5mzaiJqrQzTe9JEz8397c
|
||||
237,Rudd Jessett,Montinho,13,PT,41.1241415,-8.2082378,19kJDgwFJt7Ht1pTAGqivoouGitpDB5Ph1,8 Meadow Valley Parkway,341,1L4mfAgoJZ2aEeKXD4p1i5TDsdscQN8Cnf
|
||||
238,Christie Cristol,Piuí,,BR,-23.611227,-46.668872,141XZZj2RojyTzmC4rx9zyg8iWZhZ9GAHp,774 Eagan Road,741,1BCKpWR7wzyFMMpVQUofdnwqg3jMPznjZC
|
||||
239,Elsworth Sabathier,Nong Ruea,,TH,16.5154623,102.4151335,19vAuwxzc6S15vim7aPUhaGCKz8crdJ3at,51667 Mandrake Alley,487,1GkADhpTm5WzpKA3fA4b7rCramFebNuPKS
|
||||
240,Maegan Buckston,Kunmi Erdi,,CN,48.8602,124.1228,1KWQDJ7mxPV9brfp93nw2MUmLybKW1cKDf,6791 Merrick Way,577,14z9CZuFJNri5meR42vmZPMKyLmbPRz9mr
|
||||
241,Travus Saintpierre,Chigorodó,,CO,7.665099,-76.67749,1Pxw8xjgRoyY9fDrbN5MkDbMzEGk6UhfmJ,39 Warbler Pass,139,1E2YRkhB2Z5DRiv1YxrNzktDcop9b2tKmJ
|
||||
242,Merrill Loweth,Pexiligais,11,PT,38.8066531,-9.3199202,12GmQJqZSiq8SmN3rq5mJjUDkihE42ud6N,4 Nelson Drive,325,1HeDCyZrn1Lea358zKqi6KDmk6CvAiH55q
|
||||
243,Merrile Kenton,Pingtan,,CN,25.49872,119.790168,1NXHxtebyDeYrW4qc2nwo4vddwJVTjQrNs,46691 Farmco Avenue,209,13p8i8XCUPtya7ePr9b1HH8991gAWN3aPc
|
||||
244,Quillan MacAlees,San Antonio,,HN,14.8670094,-88.7722444,1DVciKjsbFNAzPXgm85HAAdjzFE832AEWz,01 Banding Center,642,1d6NftkXpKngdW5YVxfLUuVEKWGgMDshD
|
||||
245,Jerri Tunnock,Enschede,15,NL,52.211713,6.8954592,13J4fq9szF8QhFsy8HSjTzMCnueu5v11QL,347 Cottonwood Avenue,30,17yHtDtsdTdaUYsie6chNNu4gobcLM4evb
|
||||
246,Dorelia Baglin,Bera,,BD,24.076563,89.61457,1LVmua47ZvN447R1Sj47BtVuBHVP2MZ4CZ,23 Loomis Park,202,1JuPPgQtU76s3edc9oNb34nLBtn3Aiom7H
|
||||
247,Janey Filipczynski,Bellavista,,PE,-12.0582305,-77.1053673,1FfxbY1Z3zzdiEuRUjAXTetfMV41aXNxnX,4 Knutson Street,409,1JD6ztQZf11ZscRv7fLrE8inAExi8Gqm2M
|
||||
248,Judye Goldson,Krasnoye,,RU,44.8737926,39.4564844,1CTC5cmT1NoQkB5HcAuS2vM4nCC3y2Hqk3,50 Veith Avenue,941,1M4Rw8toMSJn3d8ULouDd1gYKzANFXeMWh
|
||||
249,Kingsley Longmuir,Kingscourt,,IE,53.3500473,-6.2755912,18YVRpxRy44y3hbYmmDLKR4wkJrbc1AsYH,3 Sauthoff Drive,205,1DqicM3zp5jSZ8Vk1eKiQ9AtfQ6zYsGB9k
|
||||
250,Granthem Clamp,Lévis,QC,CA,46.7530428,-71.2196569,1Pt94yCXHFWQvKoNDvDQ9VbMKx2gDrfjmb,2460 Hooker Trail,927,16z4tBnh11Syhhzk74g4QxgTzz9tL4qbLG
|
||||
251,Harmonia Grube,Shawnee Mission,KS,US,39.02,-94.72,196XcF8vma4Cf8FwhELkxYW95i8XrbriuL,83220 Nelson Parkway,907,17GehvXZg9HjcDQkHHnSh5ABqd83hPi7HH
|
||||
252,Phillida Bentley,Ete,,NG,7.061611,7.449732,1Po34mXAeB1zapMqhgKqjpkS8VsqTWpuGh,14877 Anhalt Drive,814,189SEFM7m3MjezyS1v5z62gyi3VVWh6X2F
|
||||
253,Joelle Drinkeld,Jardín,,CO,5.596767,-75.819361,1Mo63tWDp4MZY6WQvvCKnRosgNg7hiPdZ5,01936 Lyons Crossing,779,1GaGBN5o9DaLpNTYsuyzJmD495QzY172dh
|
||||
254,Jerrilee Hanford,Avignon,B8,FR,43.8317876,4.3686291,17C4qu7ci66xYYM1wqKucfmqRYdCUMnAfh,694 Sunbrook Center,589,1A8gEdVDSpjgvoVyusR5asEhkrPi2Jtx3U
|
||||
255,Merci Barson,Jiangjiazui,,CN,28.822415,112.206429,17k8DdyMP4DJbm7ENPDWBGtX2maWW7wJ3t,2548 Longview Avenue,510,19gS7wwmcK7TYwkigBM46ZkynJz5qFQdDe
|
||||
256,Dino Mingey,Deán Funes,,AR,-31.3927967,-64.2545133,1BmX8NkxWCuf5AbV6mUQcJhgCe7dFe57te,2 Mockingbird Avenue,595,12gP8bkGbzRkuVChxndsi2r1QTZ6XjRWgE
|
||||
257,Bibi Lillie,Hallsberg,T,SE,59.0475746,15.002836,12vPxHATPkAfRZ7pDYN9hri2xqUbJ2cJo6,3860 Leroy Point,768,157Ht5VVsDgM2qjo8F2qgg5qCFmo1nZgB9
|
||||
258,Shepperd De'Vere - Hunt,Biograd na Moru,,HR,43.9373047,15.4435586,1PbwNp6jYaskLNyxxPkxDw9p5YaAuMqehA,00 Commercial Lane,724,18Vq2oQpTeB2DU4KKE51r54Po5uT7oVMd8
|
||||
259,Brewster Santos,Alor Star,KDH,MY,6.1324209,100.310501,1HPLv56ZXAuqwUbrjxo2mMF5UZnEuoxqkg,9143 Union Parkway,39,1FBHttHCpmpJqSDr1xeNcrgkwCXZSjjL4W
|
||||
260,Giovanni Belhome,Marulanda,,CO,5.2843859,-75.259705,1NFgWYJG9yqcdAAhHrrM6MgBnNmMZUdxxm,7 Stoughton Trail,747,1Bqk6H15n2xBxDwD2Hcihetx2girYVHBNe
|
||||
261,Florri Dore,Villarrica,,CL,-39.2820124,-72.2307798,1DKeB5oZv4jRGjW6uGnDUYTTgc4kApT6hW,065 Swallow Court,397,19UodzGQU6VuMgqj5qhgG6KmaofGEg5p1h
|
||||
262,Betti Murkitt,Fatukanutu,,ID,-10.1749074,123.9136675,1HmbFb46vYxLSUQSGumxws5eUPs5j2Ldbc,5 Badeau Plaza,91,18V84khemG1wYh6HnXintx85HwR6WLpFhf
|
||||
263,Zorina Behagg,Tisul’,,RU,56.0764809,88.6780253,13Qq2chAxviTsQEeXegTWWNZAUTNmZrkog,7386 Pine View Hill,816,1J1Hdpz9GacJamtt61CZrh2KQHhp4EGUw7
|
||||
264,Gavra Swallow,Wasquehal,B4,FR,50.6809584,3.1233128,18F8L5c1QBn2MnoyxeCBtPy8gwYFaayfcF,368 Straubel Avenue,375,1DJTcD4Mx5jTE7RiNDQSjxY2gJWD42Kb1Y
|
||||
265,Glenn Waren,Krasnyy Yar,,RU,59.8452242,60.7235532,1LJLtjt28R6swz1EqSniiwosPPHDBGSjSv,8 Eagle Crest Center,126,1N5cb6K4soWnS6RY239iiM5kC5nTsnspFx
|
||||
266,Jasper Davitti,Nghĩa Hành,,VN,15.0026852,108.7838572,12dxk8C3SWcMFfQtVpb8vCBH9NFwmVsEcU,4306 Hintze Junction,938,18N1CoDkCcUqWqXBNBimRqZTgJ7xg5VEtU
|
||||
267,Helsa O'Loinn,Jianjun,,CN,36.4406171,119.2131226,1B9vxpK6vwttUg3A9nsRfK6BNw26aZpmYX,8726 Division Hill,21,1A3yHrfwm7vzHiNrzzZByFnkEY2aJasNhf
|
||||
268,Shelton MacAlees,Tanahgrogot,,ID,-1.8703308,116.3013267,15MR8Tj6Y3bNqKh2MYjQXq5y9tFuLvZr7y,23065 Raven Plaza,61,1NLoC63DcCNm9w7x4i36BvVhw3EXD83oM3
|
||||
269,Ettore Caswell,Radā‘,,YE,14.41225,44.836578,12voaSjioAAsRD6hGsZKZx5qgFiybTvKfA,245 Kinsman Park,683,1L2jAuoxjFEWjfPQArEbVw8B7qgf5hJCzE
|
||||
270,Falkner Mandrier,Socorro,,PH,14.4409724,120.9950071,1FbtxyQUa6gYFV9ZVuJtq4zf7Aee47TkFT,16321 Mariners Cove Hill,334,1JmyiHtPNwdFRVbS56BS3hjsM5FZAjkNhn
|
||||
271,Alison Farlham,Serra de Santa Marinha,06,PT,40.1941227,-8.8629472,176r1X3rLt7FBhaALgVPbtdHcPoutaHq92,61 Cottonwood Way,77,1PpLXFyBb3xUKRq7EZgJpfMavKDZe1iobL
|
||||
272,Pall Petyanin,Tetebatu,,ID,-8.4691756,116.4134873,1PY1EUR3iK6qB3zprjwY1aemvoFC1Rw2Xo,8423 Oneill Road,202,13EFuNASSVCF1dVwYyATZi8nzxeJucifPJ
|
||||
273,Julissa Dewi,Banjar Jambe Baleran,,ID,-8.5324,115.1246,1GETY1wucPEHgznEebMSdwDfymhyakUvzB,019 Quincy Park,4,1Lu9iiMYN2FADU3HgkHPfBZRjrNfQT77uG
|
||||
274,Wynn Elflain,Wahang Dua,,ID,-10.0836633,120.0200631,1KJAzqEZYC8wYo1vEo795V6CSE1DVdvE9f,1 Daystar Point,477,1KAq3x4mqHw2A7cNaLiN6xgXXSm1YmYYf3
|
||||
275,Ashlie Kaplan,Maoping,,CN,30.823054,110.973252,1NDr3yCwBm9iM4bq1n4pQUDoHLZHBWMAkm,055 Delaware Pass,546,1q25wgNx4MSHnVcNAKVLqVmcjgCDfTgq1
|
||||
276,Lucinda Heel,San Miguel,,PH,8.955271,126.009711,1NyXCR3tQNzAsMC2mrM8XUogmP6tpKATZj,4 Michigan Plaza,755,1GC6FzAF9uKSUzzQenohGJ9X66oDZ6UDgL
|
||||
277,Darbee Barrick,Strasbourg,C1,FR,48.5851876,7.7342943,1NEheDcXBXPHPmPRNjMsTUFBs27tz8ngwu,8329 Village Green Point,57,1HX1KBJYJC9BCYJYRDA6KR8D9Jz7ywvtEj
|
||||
278,Elsbeth Kegan,Czarna Dąbrówka,,PL,54.3562129,17.5647322,1NfQUoiReaSjegkSVW18D8DXVbUgXCM2w1,358 Lerdahl Terrace,468,1JSJ6WCw5KWJAd2kjbKwQCj3Y6KpbpVfA6
|
||||
279,Darrell Biasetti,Marystown,NL,CA,47.16663,-55.14829,14sV2d3jrj6zbT4SDZSoZ2uzAeg2CxrLMS,8333 Upham Junction,210,1Htq6p5KFcCpdimgpzTexW7sJER9f1UmJi
|
||||
280,Tristan Denis,Tsubata,,JP,36.6753224,136.7483601,1NWQdBLLHCM6xbuzmJRR29iikrqwnn7zv5,20320 Haas Pass,186,1B5LSUHFTfUQmHE1rvgpGPuyxPszxEyhCH
|
||||
281,Jenifer McGuckin,Concepción,,GT,14.7673116,-91.133737,18JYNza36epALgXAEJLjPKqTTMsJXE2cbh,15069 Roth Lane,844,19pU7EbyFVZ8uy9iXXiUrWfDx43JMPVfT2
|
||||
282,Reggie Syalvester,Pervomayskaya,,RU,43.4009223,45.5278152,1HnykAABFMGakNDEZkJPYXSr66xW3YE6Yy,694 Alpine Parkway,527,18xXcpY11uin94tvWTUr7kFaUGu6HojqTZ
|
||||
283,Adriane Pantlin,Minglanilla,,PH,10.2511369,123.7810708,1BNWk4YyZttEbGcyNVeDzXMfsMAmD3yy7i,88627 Arizona Crossing,70,1MUzgJUZXsZUtwZsKwFeXL8SXRN54KaTJr
|
||||
284,Porty Cancellor,Rasshevatskaya,,RU,45.5695819,41.030903,1PoMqsX2qkMUobsf2gXGKXBjkirSy1J2Us,33092 Elgar Point,817,1J3EHySyUxVVnnMXyNonsdVxmPScmX7w1r
|
||||
285,Conny Krienke,Skała,,PL,50.2304098,19.8541516,1d2JCf7NcX8KwVea49mNFKmZ86LaQabXo,8 Mayfield Road,287,1E2wby8yFKWmGNT6WDbC3mLnwqt5qBYmyz
|
||||
286,Jareb Arkle,Cikadu,,ID,-7.2929068,107.2666396,1E5uajTuKtUYRVdJCXrFPmSJF2FDjctogU,398 1st Lane,238,14mX34vHBUzyH27xsBZacCWTerUHuug4hW
|
||||
287,Marya Dumper,Santiago de María,,SV,13.4841015,-88.4670322,1JW9UjkXfi6XGubtTcXo9i5xvLBPa6yvwE,675 Porter Road,741,1L4qdzGZMcuYFp8uML2KJSY5fSRsZ692CG
|
||||
288,Berty Rennebach,Tubuhue,,ID,-9.8867238,124.2477171,17fHC5C5RQ1nxhdo9cErNGmjYZWkbVrfSg,290 Sunbrook Circle,277,1CQjWEuTBRbVFiLEAHR3me7UyymsPACGmd
|
||||
289,Dyan McGibbon,Oksa,,PL,50.7392866,20.1476897,19PrYy4iWHtiRSHaG84cerPdF5h3YKewLu,452 Browning Way,445,1AsavazFbq4JtvPRmPKd7Lf7nSTh4XqeXn
|
||||
290,Skippie Pendre,Melfi,,TD,11.0630379,17.9324442,1Lm58BDp2dZmtGyqCe4W3W7XSXsRvEsjt7,6 Kenwood Crossing,222,1GjCLGYK1Zmp5NRzEN67CKy683FTJs7bnw
|
||||
291,Kippie Dutteridge,Xilin Hot,,CN,43.933411,116.086032,1JdbMruxGW3KLVMGexMVzmbZfzAwJVJuZU,790 Barnett Circle,223,12cSxYGg8zgZrNjPhHuN1GZ4HiGYsfLMS8
|
||||
292,Glennis Coutthart,Pitumarca,,PE,-13.9792358,-71.4165779,14Np7E7twAjTHmpzHuLZszTzRNeyzHdrtL,778 Saint Paul Avenue,745,1FKR5qFEg1Krm4GjNMpLqQYg3tWmDpqDgT
|
||||
293,Betty Errett,Bicas,14,PT,39.38316,-8.2407873,1CeDe6FjiBJMFfkpcVxVyy14PyYgyjUYbU,84 Golf Course Hill,838,1LiXM8EPY26LFBqgKNYbJYtYLzdzL4UiRg
|
||||
294,Alayne Bartolomeo,Gorskaya,,RU,60.0409397,29.9879347,1C8qgDjaifKYqC6g5HkisPbetq3vpP2twp,08 Mesta Avenue,238,1FySAVoQx7MYtaBJiBNERpgtFhH5XTtqpG
|
||||
295,Wynn MacTrustey,Bai’e,,CN,46.4574669,121.3449344,15dFbgZFNdNfm2mq4NC83PPdejWdLE6ayc,31434 Spenser Terrace,3,1K7kX8ZWTjrnrF4E6T26ScaoQqp9qjnApE
|
||||
296,Dorry Harrington,Pontang Tengah,,ID,-8.3464,113.6376,1QEvaD6cFPyhEEfuq2MKk6Bx7f6znQRdtv,1697 Delaware Alley,471,1BLaF7mqK4MaoVQkuz7QzsxZkQ6XqfXqXU
|
||||
297,Keelby Stitch,Cigenca,,ID,-6.8264662,106.6400362,1LiRuZ57pqv2FbhgUie9xQFikmfqGJiN5n,25 Oneill Road,243,12JKPye1yRxwt1s1B8xfGhGiDM7rq5TyH4
|
||||
298,Marys Weatherdon,Beidu,,CN,26.235575,100.874336,1HerETe4fCjJsjJoxyx8rXGvABLPEH9ZG4,6359 Blue Bill Park Alley,227,15zYt4G5PfBhW7NPGAtY7mo5HAfKUmRznC
|
||||
299,Damian Marland,Chiquimulilla,,GT,14.0804623,-90.3793599,19PiKYDNGZihxgXJdwy5DUMJzwQthJtZFd,9 Maple Trail,799,1FXsokxRPh74onouC7rvTwSMpe6oNopVac
|
||||
300,Jeanette Veldman,Aughrim,,IE,53.2987724,-6.366762,1NpT62YSLQLGzsAhNEArxcU8eZAt1xDzqY,84803 Superior Plaza,915,18CHUTX2TdK3Bn6psAgoHYP2q5ctE52mdH
|
||||
301,Tatum Bezley,Żarnów,,PL,51.2253204,20.1634178,16Aw8yBqN2sPDbc2zks3AqELRYvtbwSVZK,45 Springview Place,627,1AXkS5gz932hU3WGwSWQMHbi3RmBasP7au
|
||||
302,Vincents Poundsford,Shakhun’ya,,RU,57.6734511,46.6107277,13LZAYQ4499msi6WeV2NuMYAPE4UXyq9ms,7 Calypso Parkway,440,13yn18RHxrtjPw6cTBxpvPqB9DrtqfsEpD
|
||||
303,Ibby OIlier,Karlskoga,T,SE,59.3216049,14.5216278,14nMSaoSvvZsrh4UCLDSpzp2Xzb65s2gKy,66371 Roth Court,934,1169X33vVnoovqQRXDZ95Smn341vZ2TQuw
|
||||
304,Nikolaus Stanman,Hunkuyi,,NG,11.2658847,7.6480296,1Fid5PKtrgJ6o9M4w4DxCkqjyiq2kMoyAb,479 Cherokee Avenue,53,1PDVeJLR4dv4dkjFHxkRNusuvpmTJXYy7J
|
||||
305,Karlen Mahmood,Shouchang,,CN,29.358966,119.222105,1EfpH1RCobtNNmVKE9ZG7y4TbeXe2Kvgn4,480 Maple Wood Street,255,14ojaecKLe7HhC2RDSxQCLUCr5SJgs6AxU
|
||||
306,Emili Coushe,Bihoro,,JP,42.2531168,143.3092266,15xj2eBRJqQFHnHwiLcZm3a5GqMYtAYrFg,73 International Drive,192,19G5He3xgzty1K8p69Yg4FeLaEtyTWxW9J
|
||||
307,Silvia Brucker,Nesterov,,RU,54.5979325,22.6027073,15ZqaaNFc96Q6Fo4yD2byXmqTrkfP7efwJ,0726 Arkansas Avenue,173,15Cui9eW6m1zKsiChjeT2UeWAAa9DzVwq2
|
||||
308,Lotty Gravell,Zamość,,PL,51.610281,19.708599,13sLGAuqhgnG4uneuNfctheoCau5n9EaoH,421 Armistice Place,997,14dr4JDHvp98P1HGrn7VXMTYeLw1f2Fxv2
|
||||
309,Emalee Dominichetti,Manabo,,PH,17.3911787,120.7295465,1G1rYiQTLQ5N8gBCx1dPj7pKu7iGRmuPdX,5802 Talisman Way,799,13nqbEvyUBcLUzy9GRae9rrdTGFiSsWHkD
|
||||
310,Rafael Caldaro,Zábřeh,,CZ,49.8825995,16.8722323,1MK3jBvB6FM9UusPi1WVuSyAhFi3MyWvjX,47 Moland Point,950,12FtxVUKBBAAZANMD26ikKcfQrEfwT8EEP
|
||||
311,Shaw Mallon,Doña Remedios Trinidad,,PH,14.9619281,120.8947635,14iviPBNwvZCG8yUgH3VjfYbN5cbVCwPrf,5 Carpenter Way,932,18HSLQSZrzUPnZK62oGhQSYC3WkYztirFC
|
||||
312,Carroll Tottem,Sovetsk,,RU,57.5780197,48.9602971,1KtHCL9oAWVhuZc5bjgtLzRxGzSwGHoF7n,8 Roth Parkway,682,1HTsJywj4NhKstJN8VbmxUDmzc8X4LtjNz
|
||||
313,Ericka Dod,Kimberley,BC,CA,49.6885534,-115.9220734,13p1uuMKFsfqktYBK1ULgM5vH3MBmpxoqa,9657 Lotheville Plaza,1,18dqqGk1itAw38aztqJBAnLhedLq4qTTVJ
|
||||
314,Gardner Sodory,Tumen,,CN,42.968044,129.84371,1BKSHyjnEFuEDrxmDpR9gY9sTKRJpih8RT,2 Hazelcrest Park,895,1EJNzokvF8iWuzuYDMD4JHJMxHLBjvXPSd
|
||||
315,Kristofor Parks,Neyagawa,,JP,34.771214,135.606399,17jiLDTWDqmte3f3zFbc3kkqnMsgFnfkza,20 Lyons Center,261,128yZLYa2eSjc2G5reCKAHgfdk3HgLFjF6
|
||||
316,Drusi Cheetham,Itumbiara,,BR,-18.4097245,-49.2162908,12p3YLMDEpKEiuwxKd3LPZCsbpP1gCChJL,855 Fairfield Junction,724,1PCKHEvJjRPpRfroNe9A7vEEDkGuavGNGH
|
||||
317,Kristal Naish,Dongjiahe,,CN,35.585575,119.761775,1M88K9puLdvL9sGQLy4ymYqvrkyjDKaEE,707 Bartillon Plaza,800,1HUgvjFSJAAUBkfdWTw8ghZtBKLjAybBVL
|
||||
318,Valenka Cleve,Jintan,,CN,31.723247,119.597896,1FgZRm8CoG3Kb4UwN1g2PehKUjpvHXdyb5,37 Eagan Terrace,739,1Dyuqn7TbaWhUB1DAzspQECCwxSHgLZ2tg
|
||||
319,Eben Tregidgo,Annecy,B9,FR,45.925854,6.1250598,1ExoGE89QHxdoGHpDvJcmMCRELxQFMUxgz,9243 Nelson Avenue,808,1EbLMRaMdDqQkAG9m1sVNzqYDjs1SGcUYB
|
||||
320,Isadora McGurk,Pantian,,CN,23.917576,116.325389,1Ma3E6nkD4X4KXrTc3wfeeaFsFiGEPmYBj,7786 Pepper Wood Center,795,1DRHdmE2yGS43go6qD4t865FWrYk9zmLNi
|
||||
321,Lilllie Bernardini,Clearwater,FL,US,41.7396715,-85.9262162,1FmZbWxkVvFrXke71PKZ9JSvC9tS8QU69s,58 Jay Avenue,921,1NUUbaXn1mZJmqVh3yrJ373zSsc9o6eqMY
|
||||
322,Idalina Fiander,Sol’-Iletsk,,RU,51.1625615,54.9809527,1FzxQRRBsPW3CGWeqeFSKCSZUNN3Gmf3GE,9 Banding Way,569,17drJLdXrnenD74p5oBeM8S7J7nLsmmMek
|
||||
323,Elsie Wort,Pukë,,AL,42.0469772,19.8960968,1LQ66E4Aaq8Noqu9Ap6JTrj93ULKmsBdWP,9283 Nevada Lane,909,18RRb4JpcE1fPqWa4tC1KX1Gzcvjj82qPs
|
||||
324,Allison Tibbs,Correntina,,BR,-13.4996213,-45.4498065,1J8rF47s8m7FbXsXR37Xr2smAJNAGtgZyB,90 Bartillon Road,347,1LVTZT6hBSaBTB66QMs6XzMMTaQAeLQqpr
|
||||
325,Gilbert Inderwick,Ceres,,ZA,-33.2825097,19.3247866,1EA93FCXvPozGRcgyjCqUgjW6FKoFRGZ4y,423 Waubesa Lane,475,12tdwAnD8buEdWPPYpiM4DTGrvoRQ6TNBE
|
||||
326,Nata Dutteridge,Dasanlian Lauk,,ID,-8.5514,116.5189,1Dj2G772PsdpMBnGrC9Yh4nt5zoV7pttGX,03750 Veith Terrace,516,1cEsBJpp7YA3s2TvUgphoXQK4Ux4cquzw
|
||||
327,Jessa Fraczkiewicz,Canaries,,LC,13.9047039,-61.0667869,1Pdt3xbqNN6DqAeYuiES7XMQXJgNHPHX9V,4475 Rieder Pass,93,17tRbhdonavvRx42i9zwqoeanPyxZYQ2jZ
|
||||
328,Rea Piff,Čerčany,,CZ,49.8529322,14.7029932,169qC66PQMxyKdRyHRKzYZw1B2VL6mZsfH,6 Paget Lane,878,1CpfDJDt4zJb95UMAnYtEsrrnGXnM4uvVh
|
||||
329,Stefanie Locket,Kedungtulup,,ID,-6.8180048,111.2695981,13t8S7tZAjbpnjUiNznkVMvHGJHgZj8okT,028 Anniversary Street,742,1GHL9KPDpHAuC2XauL53uTuJTmmho3YUfL
|
||||
330,Del Serginson,Shatrovo,,RU,56.5155642,64.631671,16C68CnTYiEZd2NAXbZ1KihhnwSURAdkVT,40 Muir Junction,647,1QCRVTWN1dUhwkLSMm7gMDTpp5bGMSRwCi
|
||||
331,Maximilianus Gofforth,Driefontein,,ZA,-26.1317252,28.1909907,11PWqF7KWf5akRantM6jZbH5eGxpJcd85,98658 Hanover Circle,438,14xhN2dp4dVqSFLMkFHE3QTSxBKynwPBZc
|
||||
332,Margi Cousens,Pallasca,,PE,-8.2531767,-77.9990449,139vxnNG3f5ZpXJpMJXiXJsEowL3V3Qi3C,7 Blackbird Terrace,269,1DRbkY753XABj2YUsuejvAxcxn5cTaBYow
|
||||
333,Michal Calvard,Salaberry-de-Valleyfield,QC,CA,45.5159664,-73.4049684,15yPawruXovUrzunP2RcKh7vXZko8GRsAe,22480 Glendale Junction,596,1rJ162UefMAqyviMt7HfvqersLy3EPiqn
|
||||
334,Tallia Brugman,Zábřeh,,CZ,49.8825995,16.8722323,1G19akVhgw4Q2DkbUQ5Q2kTxKeYbTbmFrE,29042 Pepper Wood Road,536,17k4PVv6EqHo1cmJfXXP5GD327qpMEEFJt
|
||||
335,Arturo McManamon,Cangqian,,CN,30.291711,120.006412,19j8KQd1iRj7ZkqLMLH4USdFyytquYvn3v,21985 Packers Alley,863,12rVMzsyNFTvRbwfWLpUq3xgGDNqKGBxMC
|
||||
336,Antin Warret,Troyes,A4,FR,48.294118,4.0675057,1K5x1BbjUwue6RfDHCpio5HYVfHRNTQLDH,79310 Oak Valley Park,653,1PZFfEXEHnMtbuJ8ZNJWiDcjw5EHJHGxVE
|
||||
337,Dare Harring,Gunung Timur,,ID,3.5775446,98.6684374,1Cm4dh7ZqbPPVz3PfBaEychskP3cKEVLkB,4794 Walton Road,11,1GGKQT41QcKZPDQHjs8PmytJXexMsBMqv
|
||||
338,Rolando Ganford,Tanjung Palas,,ID,2.8426922,117.2911248,16pWL3QWtVSHa2qTUxDPEU5rx6YtJA7hxA,50668 Fallview Junction,231,14xkr5VwhKYyERGYNpQr5CBNyPoT6wEKjP
|
||||
339,Sisile Sefton,Chengnan,,CN,29.270311,88.880492,1MMoY4s4VpoFnGhqwcnBKW3nxfqmWWSsKS,19 Debs Point,66,1KAQ1oWZNF4PFy7Fjm8iFrfZ2bCNLXeaEZ
|
||||
340,Thorin O' Lone,Montes Velhos,02,PT,37.9380466,-8.184571,1B5rLNrj2VfonXgUXEBxyvMTf8rBh3VbWd,22063 Warrior Alley,940,1JJ77qhGFHKUbQr17SW4wRcfp1MH1MfpRR
|
||||
341,Arlette Felder,Barrunchal,11,PT,38.7409837,-9.3605586,1FVt1m8Gg71wrguC9ifvWtXotoRvf5iPzS,8 Onsgard Parkway,212,1GYakyrmoqLKjzXbVk5SeoE1rTtDdGvWSm
|
||||
342,Ewell Lamping,Ponta Delgada,42,PT,37.751046,-25.670123,1QDSJKtcSxQihZGUj7vJr14NgyijUjKNZ9,7 North Park,283,169RTaRTAjEvUWgWzSqzwGD5uXDMztXonr
|
||||
343,Geno Staig,Liangshan,,CN,27.88157,102.267712,1JuJKKV5bu13QrzEQw77NfXvjHLMkzYBs3,8 Bartelt Way,239,1JrxXktG9gu9bFpv8TdzdancivTZ6a9BsR
|
||||
344,Reggie Charley,Phú Mỹ,,VN,14.2157645,109.1166566,18BcF8fpqJ31dpmpRu3ucxGectc3MZGXyi,25624 Mesta Crossing,527,1PNgH7XxbN4TEgck1ZNaKbWeHihXKtbE9S
|
||||
345,Bambie Snalham,Dadoupu,,CN,44.395234,125.697103,14M4np9Krf1juTUzktLBtd6cf7kf12HgNN,948 Graedel Trail,532,1BkaG5hbKtiL3aKseDxK5e6k6eg9H1Kgrf
|
||||
346,Caril Arthan,Kumalarang,,PH,7.7521794,123.1436468,17ZpaBQg4Q148fXDq8gsubBSm2dWACb91z,8434 Novick Junction,278,1LbPevJvtDfYnMcdYTnR9TkNTvF8rQFf39
|
||||
347,Lacy Meenan,Zhongcheng,,CN,22.356083,112.562369,1PhT2tf4ok32HRccBCWqdWj7xtV32pVCJd,74 Petterle Street,560,18j6MBjEbmXZFzwGKHgptkjgqyinowEdaV
|
||||
348,Coraline Rickets,Krebetkrajan,,ID,-7.9110809,111.4253892,12jXFSgNH6mX7PeG7yRLj73XRUbNSKrWLK,272 Eagan Court,706,1LNw32Y5MepMvBNgWxUR1TfErMrL3ky8X9
|
||||
349,Saundra Brindley,Gurra e Vogël,,AL,41.2860336,19.8905149,1PFd1KXpsw1meNz2EbAURgAbe5gdE3bN7t,96 Del Mar Parkway,61,1Q2fwpRVAJ1dyBvoChQbHCimw8hdywhipk
|
||||
350,Donna Rosenthaler,Gachancipá,,CO,4.9918699,-73.87076,15PJMNRSbheNEdcBV6uG9K9rwsLqr6zMAi,6 Marcy Trail,304,1BdQELmaSo6BmTft8YVdEkijDSan99hCYx
|
||||
351,Andonis Chitty,Xiaopingba,,CN,23.212853,103.952615,1ESGuiCd39vxkjMj9azAtYJ8cqZBPr7XGQ,2 Tennyson Parkway,999,1ANFscDwsKjn88cfZziJ5DfF5oZAzZZT81
|
||||
352,Lilyan Branigan,Galle,,LK,6.0481918,80.2153998,1BKVF8sPnC7ZMTSJGt7wcovWjaGYy2qsxD,9 Commercial Hill,785,18BFVzrnhfdBcn6YHJUYZGm3tp8PJvsV9B
|
||||
353,Mufinella Helstrip,Grenoble,B9,FR,45.1721845,5.7078052,1inDjgUZTsRtKX1V1DwJHnvknjAjoSAcR,36 Everett Point,879,1KqQBPShoq6BbFQ6DtQr8iSzFZXGb3ZMTr
|
||||
354,Bobbi Macrow,Pitangueiras,,BR,-21.0106742,-48.2176007,1E1X9fzmBoMBS7cA2zVsVdYPNHhRZmCfyS,540 Everett Alley,497,1N7LtaREt2a4Xnhj6DwqWe1N7DQsXXXApm
|
||||
355,Kaela Hazelden,Retorta,13,PT,41.3602921,-8.7192157,1CGfXku4T3BL9pvvVBBnQK4qYGS4Kdh17b,08 Holmberg Place,598,19zooNYQjbxMwmVbNej4So3C2cjTGRmRYz
|
||||
356,Bibi Franchi,Pnikut,,UA,49.7115204,23.1314414,1PC5u5VgyLDDa6VKmKkJkWDxBWwLAKk3Qt,5 Kingsford Crossing,272,14cnsuxDgbyPF2CEHJQCVjc7ctbivoq6qb
|
||||
357,Anette Pate,Dianbu,,CN,21.514163,111.013556,16ws1rBV93JdcAyoQfvZLLXisNYPdDoiwQ,65 Rieder Center,77,1NztPSZ1EaaaoQChi587gCGsvd2wpC8dLs
|
||||
358,Hayden Neljes,Tampocon,,PH,9.8566537,123.1426191,18br3vFE7G2FrDjwuHVyza1HsqupWXXot9,052 Eastwood Terrace,712,14XW8UdnVDg4qUyNJhpABGCBsWnr9PLRRG
|
||||
359,Cosme Van Schafflaer,Brest,A2,FR,48.3909721,-4.4854059,1K9iquCiJWg61ZFrZbmJJLzZXeeiPBBPpU,90 Express Way,246,1A2KEhWJ9FQvM4V2UimHenor8E122PY3wa
|
||||
360,Laure Plinck,Qiucun,,CN,39.927987,116.337624,12hyMAjvZ8nWzkRmKMNH9SPAtAvyxjwpyi,7484 Morningstar Point,708,1BVuCvWY5a63EHxVyRQm4WThmgZxc9rrud
|
||||
361,Sara-ann Gidney,Skópelos,,GR,39.1251493,23.6799766,17fqRSLqJL8ABEghbeUEqTxYijV18E6i8u,6643 High Crossing Terrace,234,19WM7gr6wcuaFjdzSQVVK4aBvz1Y83frJX
|
||||
362,Toinette Garnar,Airmolek,,ID,-0.3915604,102.2862694,1DZzg2oJdf3vNT4CKqJPyyZKGQL9qdbMiq,3 Buhler Terrace,119,1LXJWSm49Wy3Bjp3SWN3zcDkGdWL85XWxw
|
||||
363,Mathe Elsmore,Opechenskiy Posad,,RU,58.2760382,34.1125526,19sNPW2epNa5CucJRzf4Nneu7J84y9siCM,32024 Kinsman Place,895,1DX7ZVzDLDYMa2okCvk9b8KCT3ruwW5dKa
|
||||
364,Angelia Letherbury,Mandaon,,PH,12.2270347,123.2823247,1Geg5MdymnQAbms77pJudhLh6uihRNCfHX,5610 Fisk Lane,917,14vsNwPXvP1mSdbtxDYCGmm3JCjQXdQEva
|
||||
365,Randee Ramme,Bintang,,ID,4.5475154,97.1574784,13afmjZUDpXgs4JVuaXvdPuvHmpMttTC7H,26448 Heath Circle,951,1Nq31CnNmB76ksAASw6XHoJLQSup3aJUq
|
||||
366,Yvor Jendrassik,La Rochelle,B7,FR,47.9318074,5.2893597,1HpYDbM9L59oUTwsb8hdgAvoiQNTUP5WAt,3598 Muir Drive,34,1Pgvt3aHWRGRSTQNQAcGcYzkyM3kLabXd1
|
||||
367,Casie Liptrot,Gewanē,,ET,10.1641809,40.6499135,1A55ZdoEzVLhfds9ih4G6uQxPbxyVVYeNa,02 Heath Avenue,563,1ANc8GXJFrwW5uTvjPKvRvqZUyjk58hS5o
|
||||
368,Deni Purple,Anshan,,CN,41.108647,122.994329,1946CJgLmTVkMeis1soXNWeG7DFbXPuq1j,21988 Oak Alley,860,15VViRbDnoDEnCZinwUZx2HcqEoTE54u3w
|
||||
369,Dacy Davidsohn,Sumberagung,,ID,-8.557437,114.0369075,1MAQo5Heg6hbkkVM3hyutUvFSHkLBoQX3B,10 Heffernan Hill,200,15X6auvSutMYvUEGc8CeGRmG96vLCGpQ8R
|
||||
370,Carma Fahy,Datong,,CN,40.076762,113.300129,19drc9SRp8Te3gwMD5ACqqzzcPwkYXPgCa,9923 Shelley Road,46,1GVHpSoiqJjn2jpJ1RJjiDsmV5dToEgSV8
|
||||
371,Shayna Delph,Moya,,KM,-12.3090253,44.4364706,14cCiC6YzAPcQ2EyhgQHqjH2gKm8eemFAW,7 Pine View Circle,966,1LMA2wCTFxQQHvywzeb4bNxRGEsapJq4y3
|
||||
372,Ben Ashlee,Leon,CL,ES,42.660251,-5.5722297,1HP9RXvKRPbkXv2KbExNLCVgK9tKe4msJn,260 Menomonie Trail,58,1BpjSMvPtpZyeyEE5JzPdY9UPphDxpxzGU
|
||||
373,Emlyn Pullen,Zhujiaqiao,,CN,31.103759,121.046456,1JPzjzgVEyh6wj1RWYZFmwrVnGAroPetE8,17 Cordelia Trail,611,18qSXSBhh4Y6QbeKg1myvmgjPnSoPJbqgZ
|
||||
374,Liana Twamley,Hînceşti,,MD,46.8282022,28.589271,1EdE9K2vYArtKTarNo7ejbeSEiFDETssXU,38 Sutteridge Park,649,15yvwnc9N2B7smmqCqXYHAz2PeVxaURqet
|
||||
375,Jermaine Bezant,San Isidro,,PH,9.939624,126.065213,1HSD16WCV3jseFwrmuLPhanwADfy9DjMih,8 Huxley Place,34,1Ak3H4ti9QuAch521NBT1ptJBBwUGhbs9N
|
||||
376,Maje Rockall,Malian,,CN,39.9391612,116.4578203,1NZpJKUq7uzCsiqL5omFPJqFkwHYkpoZsG,65 Saint Paul Circle,60,1NUiXodLtrXBfTZLkBg3XPDskn8cFuwPc4
|
||||
377,Cathryn Gerasch,Ussuriysk,,RU,44.3553292,131.5243532,1EonzNDC8NSeNQ2tDLk4gGqDHjg9Ke2SgB,09018 Morrow Court,759,1CPNcgzEKdpZiM6JFJziB1ECdX4MKEdUAy
|
||||
378,Clerissa Eminson,Darkton,,SZ,-26.2333329,31.033333,1ckzzQY9t7VjXBC2bfKKuyvLAdDN1ib61,79018 Crowley Park,255,15tSAPLMNFJzxw6hc7TtWo3S1A51zmebCV
|
||||
379,Hildy Stolworthy,Gualmatán,,CO,0.919645,-77.567138,1GZFchfuqohTra1RQecSQMLfk8kvB4Gj7f,1 Montana Parkway,409,1G9dSNr46LkAkm4BAqXf1k18HVK9pXkzmx
|
||||
380,Denver Hugill,Maubeuge,B4,FR,50.2748915,3.9727365,1QL5XV6wj3LqsKtaF2jkKASDsz55T3NwvR,02 John Wall Lane,738,15UHyT2reopP7cL3rmXd6FQGo6xWheooxo
|
||||
381,Isa Middleditch,Cihideung Satu,,ID,-6.9028171,106.3483779,129s2dETkfEW3d6UPKFawvqm6NAG2VZtjZ,4960 Union Lane,660,1Pg2EKEMq4nJdSM4yxdZhfrAQzczAvfkQV
|
||||
382,Dania Juppe,Pantian,,CN,23.917576,116.325389,1LbC64Dfqo7qSghQ4RV6su76rr35qPJF4t,6496 Northland Point,71,1MHjFbojDo8wTHatvMEPK5QG69p3ZPpBbh
|
||||
383,Aloise Wantling,Bambanglipuro,,ID,-7.9481229,110.3130087,1NJSyDyxExgYpb5tMnUuAPi4Qanzc2bn78,823 Gulseth Circle,980,13XvafKUk9rJ9Yqn3qqexAFVkT1QWgMBny
|
||||
384,Brett Gatecliffe,Yallahs,,JM,17.8768385,-76.5517132,17cyBhWj8rxapKrkwFmnadpePMN4KeisX1,0535 Continental Terrace,394,18Fac5cvArvehunr7jG5M2euir9LB32cn6
|
||||
385,Hamlin Dolley,Templeogue,,IE,53.2976886,-6.3219759,1LCs9ERJs2eZCwA9Ck2UVxHgMnEzPCZhsv,5594 Portage Hill,117,1FRSBXq9Q6UEy6SWHuVVWJkemTsdbVNC1h
|
||||
386,Jessamyn O'Hear,Dubu,,CN,42.4947806,-90.7164246,18ZVMTMgwvS2uwUahZUsZVFVoyUZXnRZ9c,791 7th Junction,492,1MgS4LaMaRx5aHPzC3o3Q8qZVPgKX8qc16
|
||||
387,Kelsy Donnel,Vel’sk,,RU,61.063264,42.0892601,1D6f8crVcT2mezkNSuq1p5Mc9KJx3wEeJK,341 Petterle Center,821,1MgzgifpzjBVGGLAu7VeNhrfSwaoLRT6GH
|
||||
388,Tracie Walsh,Brochów,,PL,52.339987,20.3286222,14S45K4ZtPrLwdpkgyY9NaJm6vtggdTKst,90324 Russell Place,95,1ExFoVckAtLyWEx6kQ8cWaUoUfjdN98QiL
|
||||
389,Katrine Warboy,Shuanghekou,,CN,30.975286,105.190522,1CaytdF7MqBarfj5ocXL5YiSj4xaEv4yzE,0070 Farmco Pass,595,1K8YWW6ZfmCECVU3UAMZ2MD8hixFuwZnLx
|
||||
390,Moshe Cawthry,Wanquan,,CN,40.766965,114.740557,1P8SWRCUcC4fLaWQgJi2rc7Ku1XkZjzRXb,6609 Kingsford Parkway,83,1NfFMe5Ka9Qgj6mSd6dxngXzdABx2zkuVC
|
||||
391,Court Arsnell,Gunziying,,CN,33.6669335,113.1115294,1LfLyaKv1Bhv63CEHS978KTCdMhiVVgYo5,49367 Fulton Hill,931,1Em85WhrQttvtuarWAr7BrtUwifsk9sBCN
|
||||
392,Annamaria Ramel,Long Hồ,,VN,10.1980682,105.9464874,18H4aZTH2HzjL9vfnYohUp3hx6EHBjXExv,03 Jana Road,4,14rQyXuR47aagae5NgHSvfM2uxZv6wjX86
|
||||
393,Llewellyn Minter,Zhujiachang,,CN,38.9421248,121.4849128,1DfkVpVbogavXZethuLJTKeW38GYN8fKVC,6 Mcguire Circle,60,1A9g7p9EfSEZFujHmKpJh7ee68GKryqHXN
|
||||
394,Rheba Hegel,Liancheng,,CN,25.710538,116.754472,1zJ2ZjfuDd7p8bkMDCtMVnpwDD5TQMVbq,19 Mifflin Crossing,387,13Kmbg4snhP56nkAepWbqVS4ZZFxVmpECR
|
||||
395,Tabbitha Varvara,Qingyang,,CN,35.709077,107.643631,13wemieayWPQbaE6T7Y9GCJJzJMZorFbfk,69 Schmedeman Parkway,791,1BnN7tzeWZdVdXxNBwQQ3pfmbSQx91AJ9r
|
||||
396,Filbert Cleynman,El Capulin,MEX,MX,21.037786,-100.3245306,19vh9pXWAgL7qSC5qVfrCxF2Fgg32XNzUn,20 John Wall Avenue,614,12wET6Nrho8hhYjfvHrbUhZWCYYPLz4oeV
|
||||
397,Bogart Jaze,Kyenjojo,,UG,0.6092923,30.6401231,12wjvKkX559QvpA2d7NgGaRF12CTjFKxaZ,06295 Summerview Way,853,1H536eyM39sMV7uigbtio8MjGcis4rF8bN
|
||||
398,Cecilio Jorio,Yongledian,,CN,39.708782,116.796531,1Fzhgp9sSWx1W9GLa8ZmFpAwCGC2pMEAx7,04 Shopko Junction,314,1EuTa71YCNRDruDoa5ppUYXNWYoMUBPtaE
|
||||
399,Sharline Blanket,Dagsar,,CN,31.270415,92.290535,18B3GQDsyVXGKbdKgobWQ6Y12sRksZJt2y,3 Ryan Road,594,1PCrBU82mKMAsPcEPcbpwxKAeQEAdBNJqe
|
||||
400,Huberto Fransinelli,Ystad,M,SE,55.583006,13.0105903,15enGxNKY3Y8t5bWTGKTTkCjVJDQL7CihL,9484 Thackeray Parkway,60,1GQvQtkVvxNRPqqTLxAeGr8h5Xy9oiVWgb
|
||||
401,Nicol Lode,Pasadena,CA,US,34.1672881,-118.153228,1Hj1sbbgjgzB9PQj6Sym7vHmCK2kNAwRze,02419 Doe Crossing Hill,502,1Kdk2t25XSxdxVa6NoHRH9k9XyQEA9tzuN
|
||||
402,Sondra Code,Xiang Ngeun,,LA,19.7600399,102.1832854,1ECYoNcnZJ4sR4NMYMgWy6uATxSwA69aZj,806 Arkansas Street,365,1ELFp5uZjjw6EzdpXYnmxZVRCW3N4SqriR
|
||||
403,Erv Outlaw,"Brgy. Nalook, kalibo",,PH,11.6818641,122.360249,17TPDqcRpwQ4wag8y6C4o2bJH9iDYafjon,72831 Linden Parkway,286,1FvZWUogZaX8VaNxxY5YcRBE1QLhx2ugKj
|
||||
404,Christi Cranch,Ngandangan,,ID,-6.695,111.464,1Fu4gX4NwVtJawbWXfBeG6W2Wka6vznorV,175 Marquette Center,334,19Yu1WTkMa7h88aRhQncdPq5UDxyi5iBEk
|
||||
405,Carney Titchard,Sanjia,,CN,19.241239,108.758458,17NW3b3z8nxn8aMbmTyHXWx2VFc46pzf3S,259 Scoville Pass,946,1ERWJLhQKUcMhfpGnfsHbAJNekwL6EbjfY
|
||||
406,Chrisy Fentem,Uchiza,,PE,-8.4592507,-76.4619911,1PDsNPwo7EGxLHi6xgBNECV68rRNVjDmjc,556 Calypso Way,996,1Pf3G4HXwNz9XBCwQiZvKK2y1W9MaMMNmd
|
||||
407,Maryanna Odegaard,Tutem,,ID,-9.6551739,124.2699432,1NPetYrekHwyAaCk3obfp2bfy6vLNYYVxn,6480 Golf Alley,801,1LEqghuWWjsTZUQoN5N7GjCSc7yusv8EkQ
|
||||
408,Jada Hamby,La Cruz,,CR,10.0445164,-84.4971588,1AJPZpK25BmyV3UNRezbAri87EKo4h27Bc,54 Park Meadow Junction,599,1HzEbGnQtUsvjLJSAHKe7Hjj8PHhTFgXqm
|
||||
409,Hyacintha Ive,Baiyang,,CN,23.028301,115.84573,1MH5oBRpmzpeQskYk2ZN1eH6FbZxL6rCvY,7 Iowa Hill,485,1DcmDYuVxVKTV1nTdiJYGgxjHXDNXvE8vJ
|
||||
410,Leontine Casarino,Ameixoeira,10,PT,39.8352158,-8.8446771,12JZ5peLszv9Gzp4FtiyXkgJf3AYayDfie,43073 Reinke Place,63,1BS9svQ8D6AAqHRzbmwpWXvL59d4vdopwp
|
||||
411,Janaye Aubrey,Trzemeszno,,PL,52.56166,17.82264,1HTP5EwvYfKi8drKhFnHatdfYubr329Uau,432 Arrowood Street,531,1AngkEShWNdf5f8h81BjwyJ425ueqCYH32
|
||||
412,Neala Placide,Ḥurfeish,,IL,33.016688,35.347106,1B25Tv2u1vZiE3DXZGdTLmPpmHivu29tiF,2 Cardinal Park,194,1KjMoMHyK6cYEJhHzhcMANbQ7c59okFkms
|
||||
413,Erhard Balaizot,Topolná,,CZ,49.1226502,17.5377636,1EzBbs5344DVW8sYzwEsqYEou8ksg7vrmV,11 Tomscot Avenue,66,19mMakB2Xqg6UnbmxCP7TAAhfQXQmSyWeV
|
||||
414,Yance Faudrie,Dapdap,,PH,14.5014709,121.0421391,1HdxLvGytWb1JE3rf7cNWj6jNrDLoGAYcT,4 Oak Valley Circle,385,15mPzXZhY7RDN37rBtEXL2dUfpUwN6CmHe
|
||||
415,Loralyn Message,Gelap,,ID,-6.9654201,112.2443628,1LSeXRhxdRPTGBA7mKQooyYpg3u2uxyW3x,541 Center Plaza,941,18Lsz1MEX9nw1c3MtY6hRPRSHJVNhaf1cW
|
||||
416,Jeromy Bakes,Barbacoas,,VE,9.4811641,-66.9719947,19ha3s9fyetubQUZJx8TNo2BuZGaeagUmU,7335 Lawn Street,331,18dV7gUMW5auYDvjyu56nfoSfJp5qKqmSE
|
||||
417,Lillis Royden,Ciudad Bolívar,,CO,5.8294025,-75.9928971,19NShoAPjdrJc7wheW5FjbocL9vdijfE7V,51894 Burrows Court,177,19s2umu9Ku8dYcmGUUEZ9ZYP9GW9W94cZi
|
||||
418,Norbert Loding,Chunxi,,CN,30.6569282,104.0779484,1CD4ei4ngLbN4EaqZGFApDzQt6kAjnaPvu,5180 Barnett Trail,969,1CxFmgBjKJELqkaAo2DG9HjGk3mLrbMBMk
|
||||
419,Babita Collingwood,Jand,,PK,33.4206358,72.0017338,17Dy7XuFWyCJZY4WWUnaQK3jyUad3WS6Dy,76514 Fieldstone Place,804,1BfjZ2yFavb3WEW3JgqjR1S8wdQjMXgRyX
|
||||
420,Lindy Turvie,Shouxihu,,CN,32.407849,119.4213786,1Br9FvavcFVCN4mndfjXbFzNcp8QG5VcRw,7337 Oak Circle,552,1JhL663XJhfSiJo5Rh1voGWxfeK3PrRj3b
|
||||
421,Shea Piscopello,Tulle,B1,FR,44.7016194,4.5805038,14xGUT1wvKbMtbjLUZJjtKEDPfXQpWm7Vv,793 Sullivan Street,263,1GrFBP7q8wYbt1VJA3GoD29jShuUTcZcng
|
||||
422,Ronnica Vatcher,Sacanche,,PE,-7.081475,-76.702454,1HkuhiYfEEHpd4sUtar7wDgKDiiLoZeTAi,74 Straubel Circle,643,17Khi2A6V5SrmvBXBK9kkYosEvK6GSmTLh
|
||||
423,Tobe Rochford,Finspång,E,SE,58.6842321,15.7861546,18mCHpc1ZhuYo1KhRSNzHqD5zdS5eUozaB,1747 Buell Center,572,19WHo9YRANgfdVmKyvMdqz7cRaouzbR1gN
|
||||
424,Benita Murton,Kyrenia,,CY,35.3237104,33.3149413,1KnqqSw2JiE8AQVC85T6vkLQHrLKeYuRvH,29 Weeping Birch Avenue,100,1F1qcy4w7EX66oz3wGG9DD1qfBvwNZysxV
|
||||
425,Quinn Raiker,Yangxunqiao,,CN,30.146551,120.369051,1BYgUMgX7qH5BvbcG67gWzKsoxW8MJnhmm,50414 Porter Court,515,15supjAhtgXsBeQM5pgm9obogieBWmZNDF
|
||||
426,Lina Boshers,Pura,,PH,15.590487,120.6380367,172xN1f9Uw8QGUxmxsxBXgF5z9WHSTy3Xy,750 Mariners Cove Point,731,19UzVZsF167H2wcdvJnMocK2wHKehPcAxE
|
||||
427,Jedd Stook,Novozavidovskiy,,RU,56.5514786,36.4347173,1J5G9VaLNndUd7pByLMz1sFYdUJtpDJYvU,1264 Bartelt Crossing,372,18SAJ6bQRTsBYWRyRU2oCtymiGZpQveUt9
|
||||
428,Robbi Soughton,Lipno,,PL,50.781501,20.1074449,1B7ntvRVVm8ip6oi3fmprwnMAuNvPkz2UE,284 Trailsway Center,269,1Gcm44j6kuY3tNvXdifRf8jcDhinkdXBx
|
||||
429,Renie Shulver,Kubang,,ID,-0.1423977,100.4902451,14WGmTCAcsVGuXbCw3Sp4XGhkvF6KmaegT,87203 Jana Parkway,646,17sF2sq1tU92L9rpWB3XxjYvERgoCM3Wkw
|
||||
430,Kendrick Belfitt,Zhaobaoshan,,CN,29.960349,121.724208,1FtiKrPo3fXZsfHGgbpyBnhA7YougmQjpj,3 Autumn Leaf Drive,77,127tXWMN3QbiUw24fXm7khXnF6FBt4VaLJ
|
||||
431,Larisa Quirk,Malveira,11,PT,38.9374004,-9.252733,1w8b4ZCzQvZwcVH9eTvSm8jwfd75FG7xm,9 Claremont Alley,451,123Uu8U3Mrm1L1HJTS6fZSTN1dR2ndRTEo
|
||||
432,Welch Pridie,Usogorsk,,RU,63.4258542,48.67174,1DAjn7bM68gN4XgADCoEumyWDeU6w3pWqx,296 Mccormick Street,5,1LBChuFbevoaUcTehJTbiomt3M9e1FHJ12
|
||||
433,Orrin Nancekivell,Juupajoki,,FI,61.7927812,24.5525054,1AbF78qNT5DSXn1sFc5Pq649rNZoRuoDS1,2 Union Center,964,14MTRTANj6nMapqhvoTot1uyWN9Yqt5daU
|
||||
434,Sherye Macilhench,Silvares,05,PT,40.1422779,-7.6602365,1CYiFrLQVZBpgmXxqkzYezAeU8sJ97pwXQ,1 Starling Avenue,927,1KFqAtfSWms2VkNSSuNPe1NnH9u7v7HVrA
|
||||
435,Monro Ciementini,Xianxi,,CN,35.3939908,109.1880047,1F9UngYCRuAjKWaWkNVhNqKQkKqTUsBVoi,7 Sugar Park,492,1N1APTpbhNH3KcawnfgGHLnpXfXwYfF32h
|
||||
436,Gayleen Goburn,Emiliano Zapata,COA,MX,18.8478608,-99.1843676,1BB48hXbqyMFpdLMMfJ7d5au2VbSfH5TKd,76993 Onsgard Center,170,199ha94iH4yueoVnavxocwW3p1aZzFrDgm
|
||||
437,Jerrie Dayley,Borås,O,SE,57.7298725,13.1015839,1BcETAWXsMjPa4PorVd4T1baEa8Mmfi4Lj,6 Victoria Street,409,17DqAs97oSdKb6KJ49rEKW3Kehu4PqKygQ
|
||||
438,Arlen McIllrick,Varaždin,,HR,46.305746,16.3366066,1CHXD4pGBd1cMRFj2oCsrN1hz2aPx1oCWv,6002 Katie Drive,714,1AzHSTRgREtQcEqmTbYYVEDL2XCgcd3rgj
|
||||
439,Jodee Dodell,Kitaibaraki,,JP,36.7818033,140.7299857,14Lz57UUs8Ai3nEVZpmDBk5XiADYqGJuWg,0 Nancy Hill,158,1r8AfPhc3DRx6B8x1hddSQfhxmY8AhUw1
|
||||
440,Clemens Gudyer,Brunflo,Z,SE,63.0725838,14.823991,1LXFCpXocxWYMqXj3MHqkMNi8xknhgd54z,9 7th Road,611,1Fnu8PAZULLQCWkGdjB9JeYrgVzHCSL3gg
|
||||
441,Klarika Brandreth,Douane,,TN,36.795091,10.184999,1vdVNWUh6tKe1jx9M1UcBjSaGVJhtDmbp,85 Lighthouse Bay Hill,264,1Hh2LyxcUj9V9Wt5FSuwU6kb7cMfcDdY7p
|
||||
442,Hobard Branchett,Dahuaishu,,CN,39.925359,116.410316,14fbqLDT4wyKaRusPsqtNwWmG7fj8Z82za,6 Pierstorff Circle,713,1GWDaU7TGcwmSYz8mTR9YAGjcnptGofhf7
|
||||
443,Marrilee Munro,Villa Bustos,,AR,-31.2657738,-64.460387,1J8PfEFjAMzK6AkCmre7qfBp8bMVhqBxaD,24767 Havey Road,720,1QDewtBbTA5d2uc7TCMmf1Psf5zvSKuReB
|
||||
444,Ira Spawell,Quxi,,CN,31.491169,120.31191,1E4WPAepBbQpT9tCsQjT79mfWXfZKCZqET,62145 Hagan Lane,631,1GiZapdKEtLKWjCnZZi1ZAWNzetg5jCmJq
|
||||
445,Binni Orring,Zaragoza,,CO,7.48688,-74.8672519,1N5UjUmBy21W36r28BEM6KC2EhQrCXpqsC,5 8th Hill,160,1J117NZ5tMozcGMJgFZZMcGe3fjRkYR1zL
|
||||
446,Rhys Corrigan,Castries,,LC,14.0101094,-60.9874687,1KNZNVHoBsRf7Ujzoph5RYsKWMCzcaP9FB,2699 Little Fleur Way,317,19pqjyuo2n9xums5TrgUExvZhhCGS3c8uE
|
||||
447,Camilla Ennals,San Pedro,,PH,14.3679657,121.0504535,1PFTjcKJzZVhiyevUNyC7aux4EPER1yuY4,862 Duke Terrace,436,16BLoLB2mwrBpB9sJyCt8DDHckd1wWLN6q
|
||||
448,Jobye Boutton,Vidče,,CZ,49.441512,18.0947362,12rZf3pLixn1rKgyzSrLKYWPTmVS71Ge6Q,887 Kipling Parkway,375,16R6fTgA12sHr7ZA71mKvRAzkX2eS7nQPK
|
||||
449,Zach Addis,Babakanbungur,,ID,-6.5554802,106.7246239,14ZCMNrnyA7dVrQ4ftrujP4h5tP2HrZpFM,69495 Blackbird Alley,949,1MLgpU8uvFargh6VyH7Qfp7ZJMktt8atpe
|
||||
450,Yasmeen MacEllen,La Ceja,,CO,6.027632,-75.431596,14XstZxUugWrddVwKkoCbRaJt9AwNsZXjN,6473 Surrey Avenue,563,1DcXrThmmLsP1AR7JnbK8swG75Upvzg3gc
|
||||
451,Katlin Ewbanke,Palca,,PE,-11.3442564,-75.5655147,12bLxuwUajEdTCDXax3jUath2X8hsMqSMq,28 Basil Drive,62,1AtmjeFFJWEskm1PBUmQFG74mVNc5twaHD
|
||||
452,Caitlin Ambroziak,Sambir,,UA,49.5207147,23.2065501,1Q4xZr23srnSaW1BqabvYg6dzE8mBTRXkc,35 Hermina Place,670,1GNuv1s3ZbwByYqBKytE3xT4QKR1SNWBu2
|
||||
453,Toddie Blackburn,L'Île-Perrot,QC,CA,45.4235609,-73.9319061,15gBnXv284FzSPxQ2TyJw6LenoEZRgwdgs,2 Hollow Ridge Place,185,1ADctpcxezXDfj6MKg9faZNYxr6vBAx4pY
|
||||
454,Arly Tackett,Donglu,,CN,23.0861808,113.4943199,1JbKuY1H9mUBxLcWKmCrDabZ4Saknfstnu,54737 Schlimgen Terrace,633,17YbRJyNQRMYoz9r7x8LaessMwcKHFrUgh
|
||||
455,Hildegarde Segot,Tuusula,,FI,60.4609132,25.0250415,1BA9igHodsegmf4dCmaGVytgUHX4PTgP9F,3 Maywood Crossing,607,15LycPZVidBX8k1cgtu9xNiMxh553AiVLd
|
||||
456,Bay Gravie,Trondheim,16,NO,63.4400274,10.4024274,1H2Pi725xhdpnn95TkzUhjeDSWU26gMXb,2 Merry Road,482,18RF7tWfvxKbiic3hZSysMeQLjPeJirD2k
|
||||
457,Justinn Swindell,El Marañón,,HN,15.3954121,-88.0534236,1KC6yMzMv7i1TMXteShL4oZdj5MfBGLBvX,0 Bashford Park,90,12NqGXFHa3o8UGLtZdgUE81DLHe75uahTX
|
||||
458,Seth Fettes,Kimovsk,,RU,53.9910079,38.5367317,1A5DgzXc7gvTuvra77CqA4cUWhEH49zWSi,30475 Harbort Lane,324,1DaL6DdgHpkztijEKNUUthWZbdsERnEiGD
|
||||
459,Walther Nesfield,Souto da Costa,01,PT,40.9121933,-8.4162288,1HextcQK6wMEXucZLBmY26HZ4AhBKHjqc,9336 Bartillon Drive,602,1KHVqAtF2icgmH6wsQxBVWo28eDNn5DygT
|
||||
460,Susi Padell,Limbaan,,PH,7.5823695,125.8470159,1V8riRQjHAikUgq2svRBsV9ZrHqL4atN8,37 Milwaukee Road,68,1PhmXyceobX8kdeUA4CnT5suqtT55Tzky2
|
||||
461,Aurora Oldnall,Purwojoyo,,ID,3.5262275,98.5708754,1KukLZivsHCDiyKRtSM6Hat4H1go9mZogo,119 Village Green Place,206,16SwAuBV6cF49foPZsSM1SKX6APqrRAnaT
|
||||
462,Jacquenette Aylwin,Madrid,MD,ES,40.500471,-3.6673942,1GYrv6PYXyE4VzQ1cpKFr7okBhJW6fCTrT,32724 Kensington Junction,231,1LoGiArmGsWHGcCvBN9xifHbaCTvmG4S8
|
||||
463,Mikaela Ovesen,Binabaan,,PH,11.0358967,122.5157547,1LJXEwQ2Mwn6kas3HHXsaYfAdQzkxTKzR,2 Fisk Road,53,1Ae2zu1HEMu1yDZFMdhGkNFG4C3Xd76QfB
|
||||
464,Constancia Damrell,Töreboda,O,SE,58.703557,14.1362521,1M72SJRe2iZ2hUc4nZmu8tbtehPciqMuLK,23 Killdeer Lane,934,1Dedo8hK2hRfNSprUCTJqbVctAJ8HoYZX7
|
||||
465,Kinna Laurant,Onsala,N,SE,57.4395298,12.0487391,1HP97QwSY8E1xC9SrRtrGhkM1hedSJ5VTq,0438 Summer Ridge Place,630,1DzEEqByRB9BWBvX9Y4ARb6szmbxUYeAJs
|
||||
466,Fabiano Priddy,Aguas Corrientes,,UY,-34.5244257,-56.3908165,1FFZsdp7Mq2sZf5Qop5uSwp4otemgWcah8,72 Welch Terrace,207,141UvXwPhLNcjXtegEhoP4wW8awid2jjJA
|
||||
467,Grange Pley,Narimanov,,RU,46.3176956,48.0025271,1J5qAJjnDmMxgMyCzWCAfV4TXgGG8DUTFv,0225 Maywood Way,275,18eUhtjA4EsTebTr1eYRx1GvD4jijYomP5
|
||||
468,Jenny Blaasch,Shuishi,,CN,33.5719791,-84.3396421,1Jign7fprmNq3DCsjAxUfCL2CXWtBXwQS4,70030 Springview Plaza,684,16UmpnhNPxhv8EfEmcn6jrwM3PeEzSgG5N
|
||||
469,Aleen Trynor,Balindong,,PH,7.916433,124.200182,1NZhTaStVVziCfzbY2QUaqW6wyEBqK6tuS,99 Pond Point,241,1Uaf25aUfEby2QgENrq96E7ue2MHCSVab
|
||||
470,Yvor Thorpe,Dan Khun Thot,,TH,15.1786657,101.7637775,18Tajyj3QYeCNdTVdu94zwqdiJBgpwtiAm,89482 Lunder Parkway,174,14dwPxwx2Vdb3bADBT7oQAj7aChN36vPWZ
|
||||
471,Otis Gremain,Marsada,,PH,5.976337,121.2234268,15ubam9Fx8gMAp4aohSVn9WJSuqnzk2VM5,14 Sauthoff Point,985,14W87Euvhg9RBqi4XQ6v7BUWKmEfAwRJjr
|
||||
472,Lenna McCandie,São Bento,,BR,-6.4525359,-37.4894776,1MC2QErmiBi1BXgravsooxnSnxDXtka8b,06655 Maryland Pass,354,1KxAB2g4LcT8vxN3wRUvo7FUok3tKrawBV
|
||||
473,Mack Chinge,Yushu,,CN,32.993106,97.008784,1CyWo54TAgofoihUbUUWdVfQ2PgU3xo3ce,41 Melby Road,333,1FX1ubwwNdJXzQkN8uJexa78sU4VkcEBbH
|
||||
474,Flossie Dewdeny,Solânea,,BR,-6.7378881,-35.6860723,1F2LFkopdmCXmcgEmzMmpUMxcDgzuDq9CJ,598 Mccormick Road,419,17EgNE8bLLAZjv9zeeZstp4bYRTSCHsTwk
|
||||
475,Bianca Wigfall,Poitiers,B7,FR,46.5838069,0.3350126,1MCZS6B52s14AMivcSCXeLgG5g9y5pPG51,46698 Jackson Park,523,1LWA3ptQFirdx9LfApwg2i6RurmwdUjGpj
|
||||
476,Christan Imlock,Banjar Mambalkajanan,,ID,-8.5497299,115.2199829,168R3MhDoFumbLQxdWn7e62ApXqWonuTpj,0 Homewood Drive,391,1FUyBpJB9Pb5R7SxbQPRBtas1SWLg7xtuJ
|
||||
477,Joye Ramstead,Santa Rosa,,PE,-11.806679,-77.1657716,1KWkHgJ8Z7aUq49Dxj4R3vFmDQ29wU9wkH,1587 Shoshone Center,537,15TadpZok5h3Eh2rJ23nqUcZ9A1YUpQLND
|
||||
478,Skylar Kears,Bol’shaya Setun’,,RU,55.71667,37.41667,17ouH92Nxr7XbLfciwbYSeHGsn8aT4LGtB,20891 Fieldstone Trail,372,13tx6KZFRSNhhpDysEpS7RkeZNpjsGFkz5
|
||||
479,Lindsay Smalles,Louisville,KY,US,38.2214782,-85.6794434,1DChqAnNotWC5PzDr8YHFTk29WarEw9DUc,5 Jackson Terrace,526,1BuLTQMpiosQkMJWv8639PduGoWnLcsZ8f
|
||||
480,Iris Cakebread,Hagonoy,,PH,14.7464846,121.0764017,115135tBujrVUfnGbPCYdNoRtDJJKa1aBF,5894 Claremont Circle,748,1PdFtxhJcccprzb7ZGrkvSey5HSUbAVch5
|
||||
481,De Dolman,Kaabong,,UG,3.5126215,33.9750018,1P3r1Fc9nkHYxCJG5cJsaVpaXmhm98Vpqv,7 Mifflin Drive,229,1GVJvQS1Up4nEVzp48QLh1GFcPZrwuxeBP
|
||||
482,Tatiana Bruyns,Budzów,,PL,49.788203,19.70598,1CJpnGAT2eF2Gxzuexg3BucLrbKRMhvDAZ,1628 Pankratz Avenue,300,14XorZLj262Ru1Pr28TaCFFbrj6SA7dw8v
|
||||
483,Marcy Bengal,Indaiatuba,,BR,-23.0886496,-47.2089525,16zmMuHpcFgDRAufSLSfJfBzyRdFyu1tfw,4567 Helena Place,23,1NCBGjTWehSw8usNg4m49ZdXcPwN3wrrVS
|
||||
484,Clarita Wetton,Magadan,,RU,59.5611525,150.8301413,14BNzfdw14w9ojqQkEPhYLG2WRWBT78Y3N,8 Golf Course Circle,748,1vwXdMY2Ksb2GrcBBP4GeiVgzisvD45fa
|
||||
485,Mab Kruszelnicki,Houbai,,CN,31.805855,119.188203,1LSYokthBF8YD8oPSeErD8EAstpzdFXtre,13 Cody Crossing,992,1Me6yqK7DpTRy4g4wJVJ7hSqnHGCS31HzG
|
||||
486,Yurik Marmion,Laḩij,,YE,13.0578415,44.8832833,1PmPMniyaT7RS1hVVViQZA36ywateQbz2Z,4 Fuller Point,109,18Zywq3usgJ5jRTEgSjeDKgsUxtTAaCW6i
|
||||
487,Rollie Goodayle,Kovdor,,RU,67.6043051,30.4421656,1LqeF2vdwtxztqLVNXLEtkpvkjB3PySayf,0 Maple Wood Crossing,15,1GkgXirXdtnhdme2zVnrKJUSXRyvwtgywK
|
||||
488,Tersina Fabb,Washington,DC,US,38.8973837,-77.0395416,17sfVs34eGZTBtnhH2zHUcSDpWMaPVtUd4,58 Del Sol Junction,182,19KaWXzzpo4QRE2zSKzjLEgopER4Wm3VaL
|
||||
489,Walther Blyth,Krebet,,ID,-8.1061859,112.6407229,1D2GTCaRZ23UjRMTyS6Sarky6YhA4cz9hC,7064 Petterle Center,49,17QGZqguSAGpRHHE5ALYX3QGkBJDw3ZPND
|
||||
490,Devinne Teale,Guankou,,CN,28.168791,113.641688,1MLzkVFnUiGiDAhNUdtzhMcWaKM7SPr2au,167 Bartillon Junction,811,17rE3nMb1ksEdREhkUAP1ZpwVNyheBWWAj
|
||||
491,Sandy Kroger,Sarāvān,,IR,27.3526033,62.3447815,1DDfdNW8znBxmbB2ijbdyXoaqPAhvnZjkE,6 Porter Point,394,1F1GeFv4DD45KK3bTAxRRuPhDyau6Zcq1E
|
||||
492,Egan Crippell,Fatuhilik,,ID,-9.805626,124.6031704,14ZDEGdBXPatU2jWnu6hBpcushCiwoY5kw,739 Calypso Point,521,1saKK9gun7V6MPpYepvqQFUzxeBfixiS2
|
||||
493,Ciro Bogges,Ongi,,MN,45.3401583,104.0061889,13ncmh7bc6jk5k7NZkq9nj43eAjCueNdTP,61114 Cascade Terrace,394,1FdXb2w6CMNhN8oXFczMjq1uP3RxJH7Ags
|
||||
494,Johna Rabbage,Halle,ST,DE,51.4790619,11.9027428,14uRjdTeEzGLoHh8Sd5FKF6eabBRhuok2T,9653 Anzinger Terrace,864,1DyiVQugCWLPNFBXwnhTgaq2pkKKBpZgcP
|
||||
495,Brett Severs,Chenqing,,CN,29.4315861,106.912251,1FEy4SiM845XQku6JtmMCc2mBaCvwdiw55,85297 Raven Parkway,853,1PaZLFhxhhcNR9x4Dk1wrqHRWGeTRYvQJm
|
||||
496,Mia Senton,Heishan,,CN,41.666028,122.1234429,149ZL6rvLz9TpzCQZCBrSkDXhVZp36J3n8,37677 Sage Center,255,1PYGwDuc7Z7Q1buCWR4pYB43jnLfps5Yqu
|
||||
497,Dimitri Poxton,Gevgelija,,MK,41.139883,22.5397872,1FhQ4Q43VCKCveqYat3SkYq1FYKN3UcCqi,08535 Carberry Court,363,153J2PbGTrCoSWbpaMTZz6y8A6xdHj1na6
|
||||
498,Louise Broomer,Qujiang,,CN,24.682501,113.604535,178n16hLTRmYxdNzUqNYSJpoC7SS9HgKfF,20 Washington Lane,355,1GT5bG7b6FzS7DjV6AWGyJQoMFDoHiaPty
|
||||
499,Joelie Railton,Mosteiró,13,PT,41.2791209,-8.6487108,18FKpZhxmLW5F7Fngqvdx7dndbGp6vmEb9,229 Hanson Trail,841,1P12iKCYjkLpMEDhjL9QwRMFdGFJ6Grith
|
||||
500,Hyacinthie Corhard,Zainsk,,RU,55.3605576,52.0111855,12Hz1eGEtULJFn9PkCd88roryL85z3PuSp,51 Springs Trail,717,18gnPJmyL4KcH247zoRJ6ThPjeZssxHJgP
|
||||
501,Mathilda Postill,Skalat,,UA,49.4285053,25.9712877,1LT8pwtXNhn3zEiqMsvM2BAjoEaUFAopvY,14 Annamark Street,696,18v7GWdp15zRvncn85REw38q2H2HshQKwR
|
||||
502,Alexio Castletine,Visaginas,,LT,55.5945618,26.4507193,1PZXvebacJN8xw5BsYbJq86B2EF2nvSVy9,33807 Northwestern Drive,510,1MQFSQgwwKpuvzrprGS6AupeSxtwuGxGei
|
||||
503,Allyson Thwaites,Muzayri‘,,AE,23.14355,53.7881,1P3269xTtGofzPyDa9s78LU8Sdmzru1Mn9,85 Briar Crest Junction,67,1K1xK3TLMF6MsXHbFp57EvCPb9WT7uXjur
|
||||
504,Erl Piatkowski,Biała Piska,,PL,53.6115,22.06311,1D9w7FkRfaB8SibEBptPsm5kbiQvY5bJXH,39 Blaine Park,737,14AGLks3CGc7Gf8AJ3w3gYtNxH5S9RkCxH
|
||||
505,Haven Lumber,Bayan Tuohai,,CN,48.433225,123.220598,1MvRZP6zhX5U9UA7jjGuXr53LgcUHpxxGt,2 Cherokee Lane,264,1Cxuzwdqah49dQyjH4oeBkMDZhQZ6Zzi2T
|
||||
506,Michaela Curnnok,Idanha-a-Nova,05,PT,39.9226488,-7.2372538,17UV627ZkwRZ6UPu3gVdBThgGYKSnmfXuW,7 Anhalt Junction,395,1Ewv5GDjj8rSnJ88zUDLQv3T5LS8udpygH
|
||||
507,Glyn Batsford,Kota Kinabalu,SBH,MY,5.9840985,116.0761121,1LJRTCP8Hys17Eo5NkqnT9QpWFcPXTFSNv,127 Summerview Circle,739,1KQTKiP6ZvWzXokS9Vr3E11CNg6DgkgeoQ
|
||||
508,Olivie Main,Kebonagung,,ID,-8.1611036,113.6770012,1FBAZhXooekXsB99kDPzZ83rdwLpLs3iAp,10059 Everett Circle,882,1BSu9Um8wHo4HerZxNpt3zTTVVWhJyBbqi
|
||||
509,Ernesta Eads,Ala-Buka,,KG,41.411881,71.4833652,13Q5kCPzRiTDBYrqeoqnbjuQgmGXySGxCX,73107 Cody Center,463,1rJ8cQV9dLmz2pxv25MPnYUs3W81ofVVC
|
||||
510,Mychal Gravy,Shengtang,,CN,23.343496,112.708676,1MK6ydNEM3dGTM9u7bSP19JtEi6GSrQW8Z,5 Southridge Way,858,1JgsMAhohYdX3RVJhfbwi1zmBims6TF4gv
|
||||
511,Itch Danick,Siikajoki,,FI,64.8155446,24.7665727,13KPaEg2rzDRMZcwSrozrcxHakuYCdVuyX,61645 Sullivan Pass,487,1pXvUto8eCZarywp8aRMJtYCYTji3Sv8D
|
||||
512,Caren Liddel,Kota Kinabalu,SBH,MY,5.976474,116.115777,16QZPYSUwGmpyz3Du9rFNgYCoSKaMqc7KB,6 Sheridan Street,595,18zoXP3s1iYfUEh1d74ShygNubPLD898nZ
|
||||
513,Mercie East,Linzi,,CN,36.826981,118.309118,1N8Y6Js3bd1rpPoYfxCbZDcaDEAA7hRtBQ,9824 Golf Course Center,649,1HKMbLQbRn49BiCmeLCaKSWAHwwwP5F99Z
|
||||
514,Virgina Knyvett,Tuusula,,FI,60.4609132,25.0250415,13ChitmDjyo2p3m4Vo1v8nF7KHudzaPCEL,57 Drewry Street,12,1NYXL61x4HW7TEJEDnuppQos6m2Gncf8Xt
|
||||
515,Brunhilde Beceril,Đoan Hùng,,VN,21.6071636,105.1492869,12YiRuZ5ZgUGwxM5T6onbzKKU1YbCoSKCH,0000 Ridgeway Junction,542,14JifG5iq3Wyv58xdi4cCEczcwJdCMao7o
|
||||
516,Drake Winspur,Unidad,,PH,14.556369,121.1375395,17Rzg9AXGmDqUNio4ttGk1L9bcJtiyN9GL,78617 Quincy Lane,585,1CxSczYE5CRPcEoyG1iAM6L9L38AqDXh4Z
|
||||
517,Papageno Currom,Ińsko,,PL,53.43606,15.55039,18RW9Nc1hkqo1ziZY4P4BLeHjiTW7HjcsB,53554 Fuller Way,784,1GYJAiznnkygV9dJvqnuKfTm3XZsGbKVYw
|
||||
518,Caryn Noye,Panaytayon,,PH,9.9296843,123.9327724,1HPt8EarHryaPztnFrkmoko1xwNUuvi7vQ,7026 Esker Terrace,113,1AcgnsbqxjYbXtqBT1xCKoNk2z1T1nToaW
|
||||
519,Willie Ashingden,Huakoulongtan,,CN,24.49705,103.418167,1MdJhhL9BUe1R184SweMQDu1GEt3yntJk9,15816 Fisk Avenue,636,1H4DYgh2QAUhaCEFx4oKHePcXER1FDEicn
|
||||
520,Myrtle McConnell,Linjiang,,CN,41.811979,126.918087,1KFpeetHmezQvrQ2MkbCyz1RHjkV5SnMHR,386 Hanson Point,394,1AwCeXA6zghWVNRZNKiwFD6bDRTUvuDsrp
|
||||
521,Hoyt Snassell,Carlsbad,CA,US,33.1,-117.28,1H17g2VYKX8jUHrVC8PhJgkWSNxeo3ktQY,50 Dottie Court,967,18uyh1LWrfuSbGx35gUV67czrDKuev6nc8
|
||||
522,Lin Hogsden,North Perth,ON,CA,43.7251,-80.96723,1Q1D6vHqEFXHHY7v8gvvooUoZBN19R1sX9,7 Hoffman Court,155,1BWQbQQ9iTHCbW6ettZ6JoZuPhF3rPEZSu
|
||||
523,Dana Gregson,Karlstad,S,SE,59.3720912,13.5143586,193kvjFr5WH94Lbcvsmd8XuQGMQXhUaoP,821 Dapin Street,346,1Eruy3jp2Jh1mYtK2ZKmLUvy32hv5FAmXp
|
||||
524,Horatius La Padula,Jiadingzhen,,CN,36.951766,102.494696,14vAukyfRU9fhgzA5cNUfkVhhGtFGs5wH2,3 Tony Alley,328,1Mc7meww2ZG3ZnnUVEFnsa2rSYNPqrkw6C
|
||||
525,Tudor Cutmare,Fengyang Fuchengzhen,,CN,32.871671,117.56024,1MZrgaNdf9FajXPwQVB33paD5DywVydP7v,7296 Lighthouse Bay Place,407,1HuYVNeH2Jv2CZdtQfSVyhLLwwKw6aLuyC
|
||||
526,Phil Shipcott,Wuluo,,CN,28.0896014,108.7986202,1KgwfyYXuwTCWGuMC5D8WV5HohswLPPGkx,5 Lakewood Gardens Junction,573,16byrZ1q73TZmrjiBR7Ffk2G1rb6nWEboR
|
||||
527,Adan Negro,Hecun,,CN,28.663694,118.534841,1Hn1iyqczrb1ws5jaAfsXQQTp5V1vqCk2H,3798 Carpenter Circle,329,16abvbVqWHb1Y1G4VQoBTqfJobGveYTAfx
|
||||
528,Gizela Cherryman,Landi Kotal,,PK,34.1205182,71.1485258,16qRKjQBLsYHVh88Vxe1jdFAcE1Uc78uTN,22 Milwaukee Park,556,1A6vBBZaYsEu2stY4mW8LLF3pNF7ykyf96
|
||||
529,Robinet Smalridge,Balzers,,LI,47.0655826,9.5074526,1PDx3uBkqoQT8Uw46rtN9qkSLQGrq1kHDB,21 Center Terrace,812,1999anGfvnCgqd4328GvagQCNJVRpbqxjp
|
||||
530,Barry Vasnev,Pácora,,CO,5.527995,-75.459881,1HwspJQ2NJFMrYVA6k1MdcRY4Ui5oN7vra,92 Warrior Court,328,1HciAF8AP9YdM5yvwagamaJxCuYAu49r8Q
|
||||
531,Delilah Arkwright,Hövsan,,AZ,40.3802589,50.0918078,17N6CmzKpSjTWUoxFwEXTMTAsprFKvjHmJ,636 Cambridge Court,419,1HxM1DZM2fKYX5MvqiXRH3AyVenLhAk8nH
|
||||
532,Lefty Bucky,União dos Palmares,,BR,-9.1113278,-35.958777,17rngctoGSn26vuW3sHkHxRNBxjBvADdRB,974 Tennyson Court,676,1PbreSXxWuR4D1mN7LJhYEAm88zBbZ5RYB
|
||||
533,Kincaid McFadyen,Solikamsk,,RU,59.6670169,56.6698125,1JckhhJx8KP9k1ZuoAD9wwAjfiCQ3psLyD,5 Boyd Crossing,810,1D2J6MyieMGpwey2CM1vw2EAVUKGL4cmCS
|
||||
534,Lief Mosby,Calarcá,,CO,4.4796434,-75.5975296,1ECRKPNSCGx99BYrj7afQxgBVsA6uZD9bw,088 Bowman Street,22,1NQt8oaSJT7NU9KPzHWrcFxpTwPCrp1B9g
|
||||
535,Glenine Pfeuffer,Shibīn al Kawm,,EG,30.5604564,31.0079484,17oEvy7putERmZtuDZzfYFVEwtNuhBoeiY,91364 Grayhawk Plaza,1,1EEy6uan8H4L6shTa6injKWxWYGGqLZBv9
|
||||
536,Grange Lammas,Rafael Perazza,,UY,-34.5256988,-56.794578,18RzqRHXfVnPcESGggA5gmw5XxQGL7peGm,62 Bonner Plaza,470,1PgYgcp3TMnAKYDA9ev8aTCKdSZKLSEubC
|
||||
537,Pietrek Bedrosian,Ban Talat Nua,,TH,7.9122744,98.3459726,1KzSGX9X8bCfMqryFm1iykdMhX8ndYzM8s,8 Eastwood Point,739,1BbreStcm5PYB6222p1foVQkuRR57Tzmxc
|
||||
538,Laure Summerbell,Pinheiro Machado,,BR,-31.575874,-53.3806553,1MB6qtwjazrPUJrRp6HaGv1kApXZGA8GmH,72 Blackbird Plaza,577,1KkWvfxreuP3k8xuTDompDgfxsguYngwk9
|
||||
539,Brannon Ilyinski,Xinjiebu,,CN,28.22335,115.062214,13VxHUZtDXCEPkEnu6jMTyxjCxr79dG4aK,4 Gerald Street,708,1ZugNFw7XGxUZp2ZjKotYKTat55dp9HpF
|
||||
540,Gloriana Mocher,Farah,,AF,32.4464635,62.1454133,1AkV4r2vXRRoPa3Du7PUbnaDhYkhF3Sf7c,23 Manitowish Drive,150,1L9o4gESG2HDMf3GjNxFPUwRLX4ThN1r5s
|
||||
541,Elane Goodred,Rezé,B5,FR,46.349041,3.556485,1MCmAWWAj2VTLZ5ePuScmuDfEUug5pN6vi,84 Redwing Way,458,1Cq3SHPzgPW9PVKqZnfvrNV2uqyqi1gRT2
|
||||
542,Hebert Hemmingway,Ipauçu,,BR,-23.0552782,-49.623678,1JtCqKuRAcMNvQF7S9Go3V5asfUihFEAj9,1057 Northwestern Lane,766,1LRyCSQhaNqhG78eEnxKMsKqoHpcjgAHWL
|
||||
543,Nolana Dewane,Bom Jesus dos Perdões,,BR,-23.1770273,-46.4970116,13P6xdRvZTea5eNa5xiX87xUjkECexB6jq,2319 Glacier Hill Junction,753,1GvoVRiNjUyDQeZJKMxAhfZ7XervzC3DPq
|
||||
544,Helge Allett,Matai,,TZ,-8.2981893,31.5227266,1GbJAq8MBqpuExYBtjV8wHG18ieTDquoDS,849 Mayer Pass,961,18GUmPU1ULScDNqDQWeUSYyXnRg8i2En9A
|
||||
545,Giuditta Robarts,San Rafael,,PH,14.5697429,121.0190818,1CsqNkfT4z4CfqS4jG5giN3oWcgNW6g1RK,7 Huxley Park,333,1LxE4eDiJbvd4ESWw864ymAi1u2QwWZyfi
|
||||
546,Nero Durnin,Novovoronezh,,RU,51.3531567,39.3015305,1CzbhAVJ4mEBmQcwuGeq8uMbdnKvRLYz9r,5 Monterey Plaza,665,142j2AtY8VJXePz3hknpiuPmXwuH5Wn841
|
||||
547,Constanta MacKessock,Kotauneng,,ID,-8.6192014,122.2058206,1CKrRPoX2v9doYF3ADCri9ob3PyLHASo8T,1 Northview Junction,397,1E3ndVrqF5aFywD1Bae4sjP54VodM3qEFs
|
||||
548,Joby Lugden,Grenoble,B9,FR,45.1934857,5.7218985,1Ht5yzAQQNZZUk2Xkid2JJeYbwxLiCPDgK,35 Browning Junction,164,14nfdB5PYvW7PrtNhLa3gDPQjC6PsBNKwb
|
||||
549,Cecilla Poundford,Salinas,,BR,-15.9554018,-42.2930656,1AYYFZPHjTXFa4G1TBiCKo2nJ1h34K7i8G,9229 Tomscot Pass,799,1FMdrkcy4MZcVLTYuoUj4dNXy3o77poY4S
|
||||
550,Gennifer Curr,Niba,,CN,31.16384,98.558093,1DYqqaHuSc6hrjuCp1vcpZkrSJakCBLtku,3855 Crest Line Street,917,18K9b3TcTqAVZdcaQ1CPrqv4aTiF8djAVq
|
||||
551,Delila Binham,Port-Gentil,,GA,-0.7351026,8.7591311,1H58SDWNXc7Dvp7qYCGm4vvy4sryy65J2E,36221 Garrison Avenue,522,1DkNPX8YuDuen2dddV7m2LpRWsg3iT9WzH
|
||||
552,Clem Cassley,Krajan Tanjungrejo,,ID,-7.7308816,109.9081382,154TUqBCQ527m6FUwF9bF1eBXPzjQCskLi,422 Mayfield Drive,793,15fMbnAvZYqgJaBfF7pimxqYSf3kcgEsk5
|
||||
553,Ole Caroli,Desa Kertasari,,ID,-7.2760371,107.6925674,13kiiHactneetupi2ARuRTZUTZUv4sqPMC,55 Hoffman Road,912,1GTt863FeMdPZ3pyTvNRuLcyXtRBFsULKg
|
||||
554,Tait Frane,Xiaohekou,,CN,40.210071,119.710719,1EYznkzwEoJbNUBo9737L7dNj4gw3c7Q9g,27 Browning Court,542,1F311pMYEFq9vWWZAPx9xbtC5HaJPGL3Fp
|
||||
555,Hube Pugh,Xiwanzi,,CN,40.974829,115.279249,1NvaxYt17Eqw4W4dRkfyTsfnjFtKFJnhKU,12 Buell Place,926,1CLXGU7sy57okn3voiLt2gUk9QwbdBGuuK
|
||||
556,Jessey Didball,Changjiangbu,,CN,30.855065,113.734707,15XDuL3xNzs3FfHq678B6fnhzdg7C5eAN3,04108 Crescent Oaks Park,795,1BauMtCScp3r5rKrvVsMMwg7nMbPWLYNu1
|
||||
557,Dunstan Aitcheson,Dimitrov,,AM,40.0072152,44.4880651,13khKNNq52jZDEW6iegR88nMrA7VCnnqTL,261 Thierer Pass,65,1Q2EaJueR7pdQ5CxCF5ptseDgPFE3MZtn
|
||||
558,Melvyn Renney,Areado,,BR,-21.357789,-46.1496185,1LTooydgC5skfwyn3d4FFoKcRSyeGrkP4d,9 Leroy Way,14,1Pm9pSphW17qs8xXA4hn6nwnB44qDTzu4K
|
||||
559,Kimbell Marchment,Olleros,,PE,-12.2179111,-76.5148607,1K99P66NPzMtatgN8u3TDdGgbjs5VoYCJF,581 Victoria Parkway,31,1KFDgPk5f1zgzYGW2WBtH5XcL9FMZGzp8v
|
||||
560,Dwain Bootyman,Severnoye,,RU,55.7977131,38.439524,1C4mhdetsQKRYLMN3x8uMkYBwo6omnBHP9,15 Crescent Oaks Road,154,1PtWySZ5uQpNCMKwWwwEu8thD9MiiZATD8
|
||||
561,Harley Belfield,Atafu Village,,TK,-8.5335451,-172.5165543,1QDJaaNJy82uPaW6okfhj9zqU3bxBzfjxc,5 Summerview Alley,717,18ywNyFkX2F9cdcjcswhL3TTsqJUZzgnMC
|
||||
562,Bree Grigoryov,Marabahan,,ID,-2.9062039,114.6905436,1M4xkxB72h9cy2FVwhjrpz2KSgoZpHKvXC,276 Carey Center,146,1JDNQSzWXvg3r1bfRjsXdbM81XpuH8yrD1
|
||||
563,Burlie Murkus,Carcassonne,A9,FR,44.346784,5.8855505,13o8r4vjhYwoDhrugqjZyT5akmCf6CdECS,47274 Quincy Way,201,1544ygbMGcsoEHhW3jwaCbx5BvL21WGVCH
|
||||
564,Chick Richardon,Yên Thành,,VN,19.0511285,105.4536718,1BK3vUJYng4MEXN3matZUS1Snr4bDpFABr,70673 International Trail,200,14AV3P8k7qMeRdVMLR2qCuHEqts4tczxWf
|
||||
565,Courtnay Dudbridge,San Buenaventura,,HN,13.9013242,-87.1996084,1A4gY2zMD3M73XKJfQMEGL7YJT6WMeKLkg,3212 Crescent Oaks Alley,437,189gc7H4fjRE6meqFW91MY8XJJw4TUCp5X
|
||||
566,Juana Pernell,Pagak Kulon,,ID,-8.3020216,112.4827889,1Bqro5THB79ZmwX6VS3gYxHid1JkM9URtW,35232 Scofield Crossing,947,1J2KAUdrkdF7RLj4bVLoajBd7WnTBeFjmv
|
||||
567,Loralie Clemendet,Luotaping,,CN,29.181201,110.138102,18mrso3Ss3g8D2jMgaaU8tMboh5L7sNCGH,5755 Sheridan Terrace,439,15Fg5KVJ6nLBLRp4muPANsq8YHnkeZA4kQ
|
||||
568,Vin Rawlyns,Cidadap,,ID,-6.8639965,107.6067354,1BSKDqpV6GYhHsQV8vZres7nMRCxbjcnCz,03512 Gulseth Pass,305,15YEpf34x9ZxJxfDb1ybqffHnjbdY7rfPv
|
||||
569,Shelley Mouse,Oujiangcha,,CN,28.491356,112.615076,1BpaRjhWAbtk6PCjVSiecEHSatTVNg2qR2,6 Susan Point,74,1KzNu3tjqF4Y7Y5DNycUEyAfbsQC6BmoQ7
|
||||
570,Shay Ewles,Borgo Maggiore,,SM,43.9574882,12.4552546,1p1VK5vXiLuTwvXTYEiu9UafPfX1JXKyd,7715 Oak Valley Drive,148,16KwCyFFsRj4CKLoiQKKjDrnXqFKQzKYnj
|
||||
571,Karina Izakoff,Mont-Royal,QC,CA,45.5080397,-73.564543,1DN2Wiq4D75X3stAQZ567YgnDTz1K9Cd57,2351 Norway Maple Park,284,18JkyUgNzgdPf7RbgCdpNZ1FMY3nvvxE62
|
||||
572,Norah Labeuil,Baing,,ID,-10.22485,120.548897,1Azep3LtTjbqKPDkLKAjWwX2Yscic4BE3B,935 Ridgeway Trail,598,1MQ4rTbEFBmGrn43QGrmbBopxxVZJvRTys
|
||||
573,Jesse Biasini,Rūjayb,,PS,32.19061,35.29332,12aEeezHmzexGmj7Cp7sAsimhDG8hudkqm,41460 Armistice Way,883,1JeetGjg27DFZGXbdqzmR7UVzd6MtgfJXY
|
||||
574,Dietrich Cottee,Tanjungbatu,,ID,0.663325,103.4569571,1G6ZyFUhhT9DKjTpQSrojzDohvtqeGa1eH,2 Jenna Terrace,224,1EXE9Fjqyhqxmh8Strzu2J7hnwxEuh4uFk
|
||||
575,Alison Domaschke,Casal da Anja,10,PT,39.876135,-8.9189073,16w1sDY1puN6H8zMnvcZ98KXdXkdCy6sRt,18766 Lerdahl Way,980,178tYxX5ej3QpT9W1yw3g4w8KgX4jHyYiE
|
||||
576,Novelia Wicher,Kresek,,ID,-6.143977,106.3993658,1Ac7y3upGxz9qugNkQmz1iFrHN9WNeinxV,61141 Clarendon Plaza,694,14D7rQhyJ5iudeRBYKF53DN62eDeYJDVgC
|
||||
577,Gerhard Lukes,Túquerres,,CO,1.2255938,-77.6611917,1675zTCGYgfhQdFErk7ZHUYqqK7MfJscer,14462 Annamark Road,972,1FymNRErtiPqhbVXuqGXfBRPFckkFCCgvq
|
||||
578,Isa Cresser,Moriki,,NG,12.8776754,6.4903141,163AzeatSseVxaFkWxchgiJmYzMbzmZ4zH,731 Barby Crossing,294,1JKw7sCoWvKJNc2PGr11sYWyvwu5F7xQKa
|
||||
579,Wiley Beynke,Luyang,,CN,31.878735,117.26467,17ahMBCjyM4GoTbxLWWwnW88why48soQSi,643 Logan Alley,385,1JuJTJfZpwD7oGCmwth6HxsRBXGim6vCc5
|
||||
580,Pam Gierck,Shixi,,CN,28.438851,120.564983,1HzikhRAnygX78RvW5bzQJd1fN5FrPByPg,30 Melby Lane,440,1Fx4Wj2RgYTNkNj9cgp9YXCvRAeyt3RTJA
|
||||
581,Greggory Order,Västerhaninge,AB,SE,59.1575081,18.1350248,1AhjtNr3T7X5GKvByZeyoXEN41xPz7SpPq,6742 Sloan Drive,923,1F7K1x5jq5d8nkBRt1P7AZYrFuaDMX8exA
|
||||
582,Fee Malecky,Zarechnyy,,RU,53.2169534,45.1560169,19kJvGfX2yWCNFJGQjUBVxKHKpzkXadgfH,70132 North Court,175,1JmkscruKk6hJohgdNVXBkYZKMBY3bBFHK
|
||||
583,Tracey Falck,Puubheto,,ID,-8.7918,121.5904,1G8qtAKEeuKrodKqxZ4CD87m5XcKmHkeE7,851 Melody Park,27,1HvkT6NbxaCEfjryhf5GTvSkGGAswgL9L8
|
||||
584,Adey Izod,Zhouling,,CN,35.119385,109.167435,1N2yrL3ENka57oeorbQWNuVzLX74QUeFjP,4 Beilfuss Terrace,353,18f72jiPtfxxDnJXAB3pEYMWuntJPMzRmE
|
||||
585,Darnall Dietmar,Baitu,,CN,34.675197,103.498609,1BKH38Yx1PmJEB6ywoG12BSTexQuv7P2YH,18 Forest Road,444,1KpRN4EBjvTLAmfxdwsk4PGesvPQJvyvak
|
||||
586,Damaris Patridge,Taung,,ZA,-27.6326162,24.861199,1kT2WRfkexWPajH1k16NgE27D5ZcEy2Zu,35708 Eagan Pass,583,12mvSttqLij4yS2R5obt4kQqzzzFPSax23
|
||||
587,Calla Steffens,Al Fūlah,,SD,11.7315405,28.3578828,1AfqFeHLP6PyyBgW4Nk4ArjPSQat1XRAiu,700 Bartelt Center,704,1NKK9eDuYg6o2BjQboP2p8dv1hk5dZytSy
|
||||
588,Cris Luckin,Shënmëri,,AL,42.1023509,20.2325862,1Bk3cwMyRxYhCoYQWHF9a6psMGzHcTzYDb,54 Rowland Hill,760,1PA2oPn6JauU7ztBC68dVE53ZNKuZviHRS
|
||||
589,Betteanne Finnie,Ýpsonas,,CY,34.6882288,32.9550349,1MQDKADKCFUrfbRmLroC5jD1uG8kYGUBx,73998 Lakeland Pass,130,1BzXC4cYKb7n1Z9U3LHnPji3PNVWVKV1DP
|
||||
590,Bradney Gimbrett,Nizhnyaya Salda,,RU,58.0916417,60.7098352,1NocZWqSDvSiasc33XLnZiV8CsQqPChVpV,25314 Randy Place,48,1Fv5keoAuk9YEbhMt9chZSwbCRibf1wsXx
|
||||
591,Maury Elders,Ipoh,PRK,MY,4.6096768,101.1064003,12vTGaz13M8tfZD1vE1yNZDetPgv5RxHi6,6 Kensington Court,978,1EYtXbgjwXVF78SQAgp42T7CdsQfvy7msx
|
||||
592,Cosmo Hagstone,Togu,,ID,1.1704451,99.2037508,1N245TZFxVd3NUZsV4ejuMda4E1YEoUSTv,9533 Summit Point,681,19aLdVyNygsUKtCKLqwMnRKEo1NFuWcy2v
|
||||
593,Sonny Raleston,Hwangju-ŭp,,KP,38.67028,125.77611,1C3Nqmq3F5qV1qRaApBvAQ4wFXWYau2UyU,59453 Farmco Point,603,1E6qAvTmCS1wg2xSYKHj1SY2QtotgPfCH4
|
||||
594,Mariellen Prime,Shuidun,,CN,23.534915,115.420204,1K4FpJNcXQvZ6jsh4NTHdR5T3aFJCqBFwC,08093 Leroy Way,70,1AAAS3uePExXC4WwutEZsnf5AWMAJZmKzd
|
||||
595,Sammie Werndly,Pilot Butte,SK,CA,50.46678,-104.41778,19tUBxWLyb3UjxuWdfi6eHCe8AdAaCcEJ6,2 Hanover Lane,543,12PRVPwpAJ6awwwiPU3hJfXoB72Gb8nzyc
|
||||
596,Jordanna Blunn,Laiguangying,,CN,40.0158487,116.4551375,1JiGdfJdQQv1iThTs1fkhezZQe4dTN5JmH,5 Portage Terrace,557,1F7zyvU7FHrnmasHtS1uCiNEd9pRMHMBpm
|
||||
597,Tabb Serris,Sop Moei,,TH,17.9341079,97.9157099,1F7Je4HX9mE4eagLP17zFXfnKn96KJW9gG,4491 Sloan Parkway,285,14mpxaNwd3Tena5hGyg87mq73839Vmk1gt
|
||||
598,Haskel Orsman,Catbalogan,,PH,11.7760619,124.8803623,1CmuZ3nA7DUJB1e4MdMnkkmGMJKfFGnKJX,881 North Crossing,815,1Pykkiji51qPoaf1NYAe6fTmpLpp4U6yNN
|
||||
599,Cassie Prangle,Langnga,,ID,-3.8506762,119.5281714,1dapeKuwBN4D9CUopYf9q7W2SSqLvhSNG,486 Rigney Trail,278,17tMjpNrkmuzq8TrjYbDgpK7rqN6ijk8Kw
|
||||
600,Gipsy Libby,Quezalguaque,,NI,12.5077895,-86.9035511,1397s9k6gQ2BNoN79GufQLUuBnoAvTA5g4,50 Autumn Leaf Road,882,16b2PxWRnAhik71WYjYBwquTdD51gESEd3
|
||||
601,Dorrie Sings,Adar,,MA,30.145,-8.369444,15BYoYjvokndfW282kyqVPSwLQToZt4h8x,5 Westport Place,314,18ARQVfrNPXfy4vrxHZk91c8EokrDPHqjP
|
||||
602,Ajay Mollatt,Aguiar da Beira,09,PT,40.816453,-7.5454104,19tREYHXyJiF43pJZ2iATf3JomQJkbkj4m,25275 Charing Cross Avenue,638,17SboD9V8vwHHifAfNDhhvn3QPk5KYYtvH
|
||||
603,Tiffi Archley,Kotingnatagete,,ID,-8.6816,122.193,1Fjp15SfgkMbjtwAw89JYx42iK8Cc6K11d,726 Northland Lane,94,19gypixRPnumAzQRkr9Wh7KL8FwySZ6zj8
|
||||
604,Saloma Curwood,Lac du Bonnet,MB,CA,50.2536,-96.06116,141S9Y1U4q1WjLwQpDKkf3XnjxZz9Rv8iw,297 Melvin Drive,820,1DxZqdFdH7N92YspUxRfqZwswhP1zhVjrQ
|
||||
605,Gwenny Crunden,Sápes,,GR,41.0243406,25.6957932,18g1AvW9GtpFaeSPVoQHkpVSVa9hG1Dhob,1 Roth Junction,780,16Ahs1nsZJv2pQKTCPew7GenkLxfbhy9Aq
|
||||
606,Aurore Ayers,Jadranovo,,HR,45.2220415,14.6312221,16ScA7yXY1v1jn9RxSNqKVxAFUXJJewa12,56 Charing Cross Drive,387,1CFebTKGkWJ3QfgmqRjY7G6dRvUd4BcjoH
|
||||
607,Austin Droogan,Dalheim,,LU,49.540933,6.2596019,12tUEmtiJixBgHYoxexvhWh5Fdt3Pk9sNw,41 Wayridge Street,258,1A3vSderLUTdHnijHtbxp5VwaRE45U7voR
|
||||
608,Lucia Fearon,Carpiquet,99,FR,48.804517,-0.7467014,158VeBWTyBAmuNzTmq93HJUyjRkujQVdqe,0 Pond Avenue,383,1Lu1upetQx1U2Kmv9Q8aYARmHfXT6mPE8j
|
||||
609,Shela Morrow,Weizheng,,CN,28.6909216,113.5978392,17hP6bAaSjYZpvLUwWiLA1xBsDTudQmUwo,7893 Banding Point,876,12KUFG49ry6cLHK4nv9pBkute273RAy1Da
|
||||
610,Hyman Stenett,Eldoret,,KE,0.5142775,35.2697802,1Hr1m7AGCeJad2W4tQ8NMCmsPtJuCJDsMY,110 Surrey Center,978,19Srk5RoTynwwnrf1kXRPXQyccfiX6JHis
|
||||
611,Northrup Waszczyk,Duobao,,CN,23.116886,113.237353,1CZ3Xo6Dubhu5sdX1K5kj8c2b4cJuE3823,9 Lake View Park,528,1Jg9MCKrdTFcGbnCcRQrB5B1hkYVYBRRyS
|
||||
612,Melisse Ewdale,Jianfeng,,CN,37.515114,103.575676,12ZEsYCcAFAxt4ggcPiKTqwnfqnEscgDS9,410 Badeau Crossing,156,19Eo1AWmnc6owtN2qyW2giDi7BSNFMUujL
|
||||
613,Kristos Berni,Kon Dơng,,VN,14.0452728,108.255546,1DESEdvgzGBVvo6oK17stXBq1rFhgzDF7y,79865 Bonner Park,143,18e4QhwmFhbDAAXViYeP4Rh1RqWn9LxGQB
|
||||
614,Jamal Liff,Fonte Boa,,BR,-2.4681009,-66.1415263,1QHVaNqghpY2n4MEagTPXXhFm5GHexZbNm,41525 Kipling Lane,802,178V8QodBdtEvNqEH8tjVLmz2DkArQdfea
|
||||
615,Loutitia Dan,Mtwango,,TZ,-9.016667,34.8,1ECkbu1oWqMghWSdnDJmwqJpqLknDJYhWo,2 Stoughton Drive,507,1762LJfgmozxCkQXc25F6ubXGX2JhSoVMc
|
||||
616,Stillmann Hambidge,Voskhod,,UA,44.5132212,34.2156313,15aBS5iGRPvfjDP4HiFgNfuh9iaM5kNbKp,95738 Golden Leaf Pass,920,1F5GJce9TCdRSa85UhkJfy78qp164FruHW
|
||||
617,Yvonne Kowal,Aţ Ţīrah,,PS,31.87022,35.12682,1Ma53rBZMPzASuSvBKGpoQYv1rVWDfcnAe,70 Northwestern Plaza,893,14TTs2iBy4C753epXTqNXNUndHyzhzVQ6m
|
||||
618,Barbara Conachy,Lipci,,ME,42.4999931,18.6517064,1QA28bTb6PuUEVk4mJemirvznTK6wvuu5v,875 Melrose Avenue,290,1AfX41ytHXPbR2h6ebpEM4vuve6rufBvuT
|
||||
619,Nanny Philpots,Congonhas,,BR,-20.4993469,-43.8613566,15s7zr5YZNthEJcnz3DfqiQ2Qv4Q1DuHZL,393 Mayfield Plaza,424,1APRyzSdVuEK27enef3wXsVKxTMxA5ppXE
|
||||
620,Freemon Eager,Gesing,,ID,-8.3004413,115.0881146,1Di7KscEBV8FzyTffKurNtK3Vd5Lh7X9ir,01 Ridgeview Drive,520,13zWtmVmP3XSRzho4T4Sjc4STSV1nqvbjP
|
||||
621,Cam Smart,Kilafors,X,SE,61.2427595,16.5504575,1Jjm6Y7nCoJ4GmLRFYqoBGsVdrbxbuZNba,30535 Barby Trail,322,1Cz3CJNFuxBsNgVdB29eV3XoQy13STHr9d
|
||||
622,Rourke Loude,Rzhavki,,RU,56.0127743,37.2229806,18Eooh5rPifQxtSe9cjjyseWvL8ptZMNEw,2895 Maple Wood Center,757,12SrrcfQac6Za2JTMYPfJXxT2uyncY9nDq
|
||||
623,Axel Butler,Villa Constitución,,AR,-34.6269535,-58.4058228,1FCUoLtiL41A8o2odDSVNHs7aLoU8mQ2LD,95 Linden Drive,987,1D7CHjCbyMvJsS7ijSZaukAQaYmD4LSEVe
|
||||
624,Kira Paschek,Quinta,16,PT,39.5481333,-118.5748667,1NxVQgCEegWmcH3w9LuYdnS5rQffVVi8Z4,53073 Butterfield Plaza,653,1Ji7XbcFLiNThZW7F4CNi7wf7nUo88HW87
|
||||
625,Paulette Nucci,Lindavista,COA,MX,19.5037694,-99.1324282,159Fvuc46m2cC4or96yi1UKQnRs9T3dccm,698 Paget Court,758,1Dfhx6q8zihVj95nppyRuGLRY2L5jx1SLn
|
||||
626,Leo Maso,Uyskoye,,RU,54.3749589,60.0069111,16Uzg6iq9VQaXoTTEA6eSEyNwnRz9NE9cm,0959 Fisk Lane,731,1HkJwHH6HEz4Ad8fjELTvDQzi5ZehDjLuy
|
||||
627,Vera Dinse,Cuauhtemoc,MOR,MX,18.8086564,-99.2037508,1FR2bea9G4Zb3UZyw4G5tCZtsGR5QiYGha,54 Kedzie Park,859,1Pr4RNim33mVAMeuNdFaa9icbmxSQYeWNb
|
||||
628,Leanora Camus,Boleiros,14,PT,39.5805123,-8.6401184,1Latw97RngaHYY9HZQeMiECoifUibjyRDr,42 Mcbride Place,371,17P7TJq6iCH78A7PFdcChwTf3nCix8VAPr
|
||||
629,Any Dominichelli,Palcamayo,,PE,-11.2957096,-75.7727595,1NnSLFdu9AS4iQzVi4coSRTWmetXoT6C6,94876 Utah Point,533,1LcvWfUtAucAieh41YsuWYGnsNSoksu1Mh
|
||||
630,Shanta Mountlow,Sambir,,UA,49.5207147,23.2065501,1D5AumHbyrmwiz5mU1gRkDNx8qE1VAsnYf,7011 Oriole Junction,70,12jFe517wNQ9n2pzWewjvz3PJm563pbVK4
|
||||
631,Renie Jans,Shalkar,,KZ,47.8313394,59.6188673,1Hf6nckn4JKETcf6U75yUxgsuDth2U6Thc,873 Weeping Birch Terrace,819,16UXTRSBFQmJz4VuJXisjYoo1C9U1R29N8
|
||||
632,Nikolas Scholz,Säffle,S,SE,59.132661,12.930107,17m1gf66nRdZeFgraRpWfXum8KY5pWitfR,041 Mosinee Drive,543,1LWDGmdXCLmCRFJHTKwcmpoP7bcNeaWJuj
|
||||
633,Joyann Hargess,Uluarang,,ID,-6.1473,106.0452,1Jz9NMtBntXZbRAiRcGHdYXeanFzuz8XSV,23 Ruskin Point,131,1BWwD4sFA5LUm5Co59TcrEJJNvPRT9hQ6T
|
||||
634,Ted Yurkiewicz,Nîmes,A9,FR,43.8324929,4.3662321,1P1yi9tdJxm6aBDuzDb6byWyURrPDbuz7f,0910 Hazelcrest Plaza,845,1ModKBsAysNoPRQdAXCtPsxySReXcgbkU2
|
||||
635,Oliver Peascod,Ostravice,,CZ,49.5255788,18.3904377,1JUFDYCwHjpbPfKXLMKzkdksuDwW6TMXZj,80755 Graceland Parkway,560,1MXhPmsp7auueSTSitogdADWbmGjtAZ7k8
|
||||
636,Colby Dalgarnocht,Yelizavetinskoye,,RU,45.00722,43.34944,14qh1TfP2kyePJyykGKUeWQ339C7uNwHLB,28838 Towne Center,271,14adkzfWLz8YvQJSi1GpkKphRM56Dic6Bi
|
||||
637,Tate Venart,Bastos,,BR,-21.9221397,-50.7317774,172qrr9KMLTu8THFvvYMwBfnFSjehQtJPQ,00 Waywood Place,529,15BWkEedupeJys2hAYfG1x4D8RRzFUq6hH
|
||||
638,Chrissie Phillip,Baru,,ID,-2.8534379,108.2941031,12mtnDiqevwJARQ3ckBzaa9c15nX8ZuymM,0428 Spohn Place,857,1CPmUcLnQxYptirtRCf8rvazKBSguB3ckg
|
||||
639,Port Hounson,Fareydūnshahr,,IR,32.9401173,50.124088,139B6kv4XjRHyhrJwb4V84zi89CnNyTitu,4 Manufacturers Avenue,476,1Pc6iHjqZxcGqspRPyjHSojj1sQLiVX7BN
|
||||
640,William Hazlewood,Liuheng,,CN,29.743828,122.108265,1KA11Lsoq5mSm1uMHMmTQsVtAchH4PmDom,485 Anhalt Point,508,1FMVdyxQX7N4qd5ZCWc9HTTG6zTVu1vo7q
|
||||
641,Madelene Caraher,Goléré,,SN,16.2488504,-14.108845,1EMBxfCAwF5x9WVeW2NNwhDSVHAWnQxEp8,67 Banding Point,96,1Gmvg1L2mq4UQSjunc2xE8Y4ki9bZrF44C
|
||||
642,Margaretta Gethouse,Chuanbu,,CN,33.011529,114.022298,1KjqDMXJNrwjwvuzqaG8g1c1nuVRchwUbA,901 Coleman Parkway,224,1FvAqUTK4bLRHbcqW2Evx66vzBxpzqyW5r
|
||||
643,Cliff McDougald,Alderetes,,AR,-26.8193949,-65.1435685,1LUtwJvvR1DznStTp6YDGqf3tGmZ2oRTxx,3 Grasskamp Street,400,12YmooWHEQAXaxorFyEBmxvkCvz7K7VqxM
|
||||
644,Daisi Rubinowitch,Spring,TX,US,30.1185635,-95.3730176,12NsiKpVNuq7WKm768RmEvHHGrK37s4tHC,69432 Summerview Center,667,1MzEQfj58f8PvAQL5jQtiMPTzdr53rnSme
|
||||
645,Charis Crevagh,Castleblayney,,IE,54.10269,-6.64937,1MC5ttfywzMb2mwHD9DYRedyye7sTgMpMv,527 Goodland Park,704,12QY2LjCZed7rLoE9YFVs3Q5HDTX6uyh4r
|
||||
646,Rex D'orsay,Kavála,,GR,40.937607,24.412866,1NGcZY5jVDpbYwBvNzZR5fDhWB9onvGL8y,15 Dunning Place,89,1JNGPaDaT93CAYFdv95VnLdvkkjoT1w2bu
|
||||
647,Jeremie Cunradi,Tentúgal,06,PT,40.2252364,-8.5846629,1GkEdDvuv83dTHN78JZCwMpFf7NpNdxY7j,78148 Harper Junction,884,1K3vDukm3AfDXZxTrXBNqzTGt5sTrQefEY
|
||||
648,Cindy McGraw,Silca,,HN,14.8108901,-86.5425921,1GcgApqnESidrAfrXFLc9wTPJ4RyEW1uEC,2643 Columbus Alley,749,17RLpiZzYahnwmaLminrUNYZuZMszxax6z
|
||||
649,Samson Dominichetti,Umeå,AC,SE,63.8105554,20.3121225,1C177p4tqESntg32igpbvsiGxLni58WQCt,6 Sunnyside Way,884,19PY4DAmcYbFHYsPoA6T6CmgLfjYj2u4VB
|
||||
650,Adolph Player,Gushui,,CN,30.518048,117.134282,19CfK9WR9rcv5ia3ZujPLZeuMpdcbHpnnv,1 Westerfield Lane,558,19dMo2tYUuvCbUQSK6AKC4jWsRsWekp4j9
|
||||
651,Coretta Donavan,Sibulan,,PH,9.338682,123.291063,1B6NRUuFKMFwmTQhNfcQxeZK7EyjAuKSSx,3 Superior Road,698,12Q39Kwhdsht2JaDMQM4U8yf8uDr7gJ59y
|
||||
652,Anastasie Varnham,Kurashiki,,JP,26.3841358,127.804829,1GinHXgwSHs7jLN2bVZjxssZWX5KLGQQaw,74373 Lakeland Trail,465,1DFxMeAVq9ELQpNbxG1h8boXuabeYuMxmc
|
||||
653,Truda Tough,Rondonópolis,,BR,-16.4622905,-54.6210664,1A8LybZsUJSyAhxdE1dkcNw9SR4Zj9vEwv,71750 Russell Parkway,550,1EG9BzyokGu9qfm55LHVn4GW1KxQGnLZhL
|
||||
654,Cozmo Fost,Ervedosa do Douro,18,PT,41.1661451,-7.4737393,12txxVN5mVwsxxJMiX6GsKECwnqQXXBSQc,30962 Brentwood Junction,553,1PCvNfwfYHVjB4LNB3mrRUuynr57SMGYyp
|
||||
655,Gustavo Checcuzzi,Maddela,,PH,16.3407314,121.7646503,17zG2wLFSiCpnrfMiNGy5hz8mwsZgAGPP6,003 Morningstar Court,45,12iiQc1xWr3sPfizmd88ALs8FzqTUcjMdN
|
||||
656,Octavius Jowsey,Balky,,UA,47.3845072,34.9321089,1KheyPy7Wg5sb4bnzkLA6WiRsfgeSBVHpS,08405 Red Cloud Drive,136,18aeLsLfNWRBPWv3CDB8uEfsww1FJC2LYL
|
||||
657,Berti Mailey,Kuleqi,,CN,39.246621,107.666176,1584nqDVG2xcDQ3SCbBT7wAdSbBAFat5nr,525 Corben Parkway,302,1P7Dgz5aCyBGiTMLfxYzYYAHyoVdZoRK1N
|
||||
658,Hadley Reynolds,West End,ENG,GB,53.46464,0.04134,12yVVd5TDvVKGhCzL5TiD2u8esB8B7LsoD,41 Gerald Alley,719,1Jw12E8TYWfdqrYFUf44HJpZqFxdXzPUAy
|
||||
659,Ardith Garrison,Sinfra,,CI,6.6233371,-5.9145568,1FcA9xD87ropZtNsXMfqfmaDTXaMgaEhf6,80 4th Parkway,914,14mreHtmTCcXq9W24Sk7WjDi735Yq2AyUA
|
||||
660,Frannie Brikner,Mompós,,CO,9.2209606,-74.4028287,1QES2hwVZ1QKUMYKzGnHUqv3hij232pvS4,9 Susan Junction,461,1KfKpj4AAzt4r4p9beUfJCpsut33LfBmWX
|
||||
661,Pru Bapty,Sūq Şirwāḩ,,YE,15.45417,45.01667,1FciG2j9WXsHV1L6X1dT949giKE6c8X9Yw,3 Forster Way,259,1MMUXp3vXP1mhyufq6nEJiKmWZcXG7RgZb
|
||||
662,Ardine Agdahl,Maji,,CN,34.34866,106.009915,19jbqME7q2BnY7uyHdW55jeoPKRYGR7K4C,4888 Chive Road,65,15Md8mxfPitGKSoHruDRCbJduw2HPQAhKz
|
||||
663,Nicholle Owain,Jingling,,CN,30.96187,113.378132,14QkuVsVM6sf6ZvL1PaQ3kD44L33Rv3h3F,713 Parkside Point,511,1HsQVuLYCVgNx8DGY1qcNrgEcDifhhZvN4
|
||||
664,Dominick Moppett,Murcia,MC,ES,37.970285,-1.1424989,1Ex7hEnF4atgNq1SGF6mPab4BLg1h4u7n8,81 Westerfield Place,745,14kepnEjnc2STXyhKKduA1XYyTc2kCpcHb
|
||||
665,Nanon Daton,Shihuang,,CN,39.781583,114.632886,19vVthZBdM4HUMyPm42oy9YZzHbkM6MrrZ,4706 Beilfuss Place,93,15xAMNRAXcns6gKiTNrcHC1iZquuQJnTCK
|
||||
666,Perrine Sidebotton,Tsagaanchuluut,,MN,47.1117538,96.6716809,1M2RrhSRFJydsuky68gVocmGzA9cq3547X,1282 Almo Trail,176,1FmBbNmDV3qVEKTzSDymsroZLbW9PPfHti
|
||||
667,Kilian Ixor,Changjiang,,CN,30.5211502,112.8915932,1MuSU52pdhJDx1dbwCxfuKGBmJnvVLz1wp,5 Clarendon Street,125,1BNowkL3sXxhX4ie2wgva4tKUiVt7vAbsb
|
||||
668,Lilly Alvis,Le Teil,B9,FR,43.8139627,1.9593782,1JL8rGqGW1wu9X5i3kKPuanuGyYDzX2Hdv,60 Hanson Trail,380,13wVyUGAoijVTfhwZxYU1HNgwv8UNWjfbF
|
||||
669,Corbie Ellingsworth,Qaffīn,,PS,32.433378,35.0843,1PuXiTREMu1LbGoUBrHUznEU8xDETKmW1d,3221 Nova Street,75,17c2c5s1V4AXMuWetae4xfATEx4btcUTdE
|
||||
670,Wells Viggars,Barra do Bugres,,BR,-15.0705872,-57.1882847,12Lk6W6uh8Dd51z5bzhsXcwyAvwpaxvHdk,69 Onsgard Place,213,1CdA9igeF4ivqPtZ1Gxnun2xgSpcCGyprW
|
||||
671,Caleb Gundry,Okakarara,,NA,-20.5907049,17.4588131,1DHukSXTDT5hSuVraGkWPRcmSarLfdwdQ7,61927 Rigney Alley,982,1F7UL7HpLD3mwxhDvT7914H2CWU2mPw9GQ
|
||||
672,Clarke Tombleson,La Estancia,,HN,13.9142931,-87.921223,175MwSEj783zoqbKukzM68SBXivy57dW6i,49988 Ridgeview Lane,554,1dQwCwdVbiZw65exftDNnVjp2STNNdGkA
|
||||
673,Shelden Murcutt,Lampihung,,ID,-4.5585849,105.4068079,1NRK9PY7jqMEsVMf5R8H1tcem5xkCD34bu,01795 Washington Junction,978,17XbsBfXX9gyMGPPuUMTtK14EH1xgPRBPN
|
||||
674,Tobias Searchfield,Falun,W,SE,60.6221986,15.6073973,1Maspi97MHGeTY4d6AFHtXFJ66QyonBVj4,35935 Kinsman Drive,224,1M1nQ45iBbSpdBCHFRcWKpSWdYzkzpZirq
|
||||
675,Langston Dilleston,Barr,C1,FR,48.403405,7.456782,1CbXjdd5Xvgn5adbkyXWeL9Bdp95uGMbNh,085 Fisk Lane,327,121uSdVqm6etcSSgHY1KMtehmuauJbgrtx
|
||||
676,Illa Gofton,Pathum Thani,,TH,13.9906389,100.6184588,1bXWUrZGqw2UcQnmo34Q615nUh1aoWraE,6189 Schmedeman Trail,261,15Q9WmnuFS5vAN2SNFMLFuD1RTrfAgCNxo
|
||||
677,Lauralee Archell,Lai Lai Bisi Kopan,,ID,-10.1628,123.5781,1McBtuCrE3MQThMXwzF6t2FyNdY7m3EvV1,76862 Nelson Plaza,566,147nMAb5VLthEhEqZRTpRCjumyf5j7cAZA
|
||||
678,Andeee Gloy,Karlskrona,K,SE,56.2620262,15.6049677,1FLz85vCGR6EtF1a3sGuALbeEKaAJzZYwi,79 Vidon Circle,15,1JPYstXVkHSoaqhKY272ZWF5fhUdqTcG8D
|
||||
679,Robinet Broughton,Beddeng,,PH,17.5645481,120.3913529,1EzSkGw91gjRrTWx2PoWR8LcCngByAoYQK,0 Hallows Crossing,690,1C8kfxuVrvvaB3m7VqrcBHiFVgH3JSk817
|
||||
680,Cally Pauli,Tanabi,,BR,-20.6286479,-49.6546435,18ihmgUuJtTK7B3XCAiVxSySCid1tPq8hF,1 Florence Circle,615,19ENVBBbW9MKYyASAF8SodqpuKDc8eihgE
|
||||
681,Xena Kurton,Tanahedang,,ID,-8.5534,122.9028,1H3C3KnoUDCahtmett2o7YbXHXXjvSgUh9,2 American Way,59,1PawFaxc6ecUPoQ16jdEpUx1LiVGZStTev
|
||||
682,Inge Peer,Prosperidad,,PH,14.6640555,120.9712983,1GSYgaRvkqN5Lj9pePb7TNsfkvQhw4Cyz5,97412 Lakewood Gardens Center,321,1rvpsDuNivfA4KHJyQu3Fk4p7myqmRdDC
|
||||
683,Lida Ashpole,Capitán Bermúdez,,AR,-34.4765431,-58.7226377,1BBJfBotNHTnGSi6HB9NZix22K9K7zAbzC,03435 Butterfield Center,739,1L8FviqnSeio8tPzSCZepmg1fR37M7p16g
|
||||
684,Cristionna Ney,Lucan,,IE,52.6396679,-9.4904891,13LrNkHAUwHp4UHEiA2VcW4uFLabJfjYsK,9502 Cody Place,22,1MChEkSX53oF3qe4Jk5BVdDPf4YiEKK5w3
|
||||
685,Ulrick Hawkslee,Amboasary,,MG,-24.333333,45.95,1JZ1tje4NexsSpmpdi46fi6UHdGKM7v15H,110 Dryden Court,241,1KeDFy4Hbi9nHAW6pUDCFokcFB7VAM7dsU
|
||||
686,Luci Delhanty,Norrköping,E,SE,58.5920069,16.1867248,16vkogVsnq26DLih1XhcnXFDKuhW7Dp3Ko,18923 Dunning Crossing,153,14vNwvNXKgvpveRdFAKeMUtAVgn6rYsyEb
|
||||
687,Odetta Rewbottom,Cox’s Bāzār,,BD,21.4394636,92.0077316,16u6VrdMcRQPPhsmY2NviFjR1zRLJxf8Kw,9 Hooker Avenue,363,1KjKU9PAbhUHTDzSQmxCFV6zXdK42ydinU
|
||||
688,Barn Farlam,Kedungbanteng Krajan,,ID,-7.0448067,109.1474718,1GGNSerDRqPYAE1dTuUwsG8bkHuLr8e794,909 Service Center,714,1Mhdqu2yPrqZYSWMmPHfHVam8iE1RxBfg3
|
||||
689,Job Skillen,Zolochiv,,UA,49.80941,24.901371,1KBKnp7NPtrtXCC6r6QzDmPXPno7BaZcTW,5645 Eastlawn Junction,912,14HkQj56pMcraN2G5b1LdGCGAdUpFsLh3P
|
||||
690,Veriee Besque,Gävle,X,SE,60.6815788,17.1350993,1AYvDFveJWDra1V5oSnVB7BVJgiJeAuPFn,3 West Pass,901,1GvggB7wcfx2t4eq8WsQS2gt5xGw4DXvg8
|
||||
691,Bone Nern,Zaytā Jammā‘īn,,PS,32.13698,35.18595,1EVsrwn2zw5T9TfEe6Jvoj7XJq4siQnMxS,5 Bobwhite Street,238,1NHbXaosVsTKNPvhsqe684CPLT5vhyYaMM
|
||||
692,Bernadine Kynson,Hunkuyi,,NG,11.2658847,7.6480296,1AF1Y9g2jGdy85BvHppxGKFA3wBofbY9Xk,4 Lien Parkway,684,13Ayg7VvEh5UndST3da9jNQtB9EPtYLejo
|
||||
693,Margret Bucknill,Bang Racham,,TH,14.9192781,100.2905604,1PYKx4qUQasAGxqZAXbbY92KtsMy9f7DTW,1 Maywood Trail,217,1LGS6jTyFJqkRibz3Y9EzUGUWsCXscXir4
|
||||
694,Tedman Emilien,Oefau,,ID,-9.906,124.5313,1RNtFZr3BYZxsxFPDP6owvLq7rFPBLN7S,788 Oak Valley Place,967,12EEGgaHUhrWc6adARun9iubBSmWSF3Swb
|
||||
695,Lucy Tett,Pangapisan,,PH,16.0264893,120.2128474,1MhSyDpkaPEcSabbW4hCrwsYiRn8RkpcJe,6 Mcbride Court,15,19SeJE242MPH6ZWfscuTVdMmsXWvNrjPe3
|
||||
696,Madelyn Habbeshaw,Gosë e Madhe,,AL,41.0955895,19.6133429,1HY5iyu12ip4ZpGTPGg1V4RTeJL6DjCXQ3,1016 Nancy Trail,896,1KmnJhksvFSM14ZPa6pfnVhRA41QLWnYbG
|
||||
697,Rupert Yantsev,Svenljunga,O,SE,57.4715317,12.7264154,15ShgcywjwTE4W6B5jLsGVGx4LTNL5L5An,612 Coleman Hill,979,17H5LAdxsbcjLtQKQm8RKTQ7y7WBeapPGP
|
||||
698,Carter Essex,Senovo,,SI,46.019373,15.4759099,1769FHiq71hVjFEMiUnT2aa9M5Ge2MvyUb,55 Walton Road,822,18KwbN8mpraeZGmgGqsWmDtcR15XHXr2TY
|
||||
699,Bartel Cartan,Linmansangan,,PH,16.0684136,119.9874642,1A61UBbCThviGVzi1GHUYfwLJqYxvya8at,2 Packers Trail,252,1JT8jLqxYiVPbmray8BZfqpufS5SnZ2rVD
|
||||
700,Gian Wibrew,Casa Nova,13,PT,41.274605,-8.0974429,12xjhv5pvGGvYQTd1jdi78XFW1tuGykkPx,6 Iowa Street,966,1QFSKZhaET37R7JCFNnwAqkKihp3d3tW8E
|
||||
701,Goraud Bindley,Oxford,NS,CA,45.73345,-63.86542,12o8xU42Mn6kq7g4JAt1J6xbBPLx6FMjfY,4 Monument Parkway,112,1BSnUSMX8VDDRE14V4uRQ4iKiRGPRxB7Po
|
||||
702,Freemon Ruppel,Ad Dīs ash Sharqīyah,,YE,14.90806,49.94778,1PCrC9onTodQAQpcAzSz4upWxi9TRKRULM,2757 Lien Junction,477,1FvPQ4Ma6odX3Goav5nXD6MaA3xzwNSJt9
|
||||
703,Anthea Lashley,Qingguang,,CN,39.197212,117.059698,1BZBSNeBDRHRn3of7qr1wh3stBhGqpexNd,0229 Sycamore Crossing,267,192nTs41tsDBN9VFyfNgxwm7NEpwSUMPFM
|
||||
704,Stafford Blague,Sáchica,,CO,5.583059,-73.542575,1C35mRf2AdrRM3oScK3RHrvcjS5FLAaMdk,7 Russell Plaza,762,13Go4r4G9b91tSMrwrxCiKHa5hdvcvJr68
|
||||
705,Fay Salman,Huancapallac,,PE,-9.916667,-75.783333,1Pq6F2nG3ctxX9JAnPxkYeTLaEE6DMEkAc,15638 Dunning Court,267,1FKRky7vNujPe8QfDk5xg7HihSrxmJfv4J
|
||||
706,Jenn Pillman,Iogach,,RU,51.783333,87.253,1AF6mkTUD8qQGDHZusg31pYrSDXuUqPhuo,8365 Autumn Leaf Pass,464,1G53WLxRbLCx4VwF6XiRnPQytqnRu8FUWb
|
||||
707,Nikola Velasquez,Daweishan,,CN,28.454511,114.014752,1MuzdikBEhKqDiFBNr5VPZ7wTWHQV4iM7J,6 Moland Court,923,1Pc84PwzAhtUvaHxh6tKT8yKrFANZXb3Dc
|
||||
708,Morley McGrale,Bizana,,ZA,-30.85775,29.85391,1DQbat17mnFX9VMCiNEuwTzF5vqSGZmTFK,6707 Karstens Parkway,168,13xzvM6fdit5yiWp7WDWPsMxyR1Hi6sexE
|
||||
709,Elfrida Cosser,Sunjia Buzi,,CN,43.617445,124.139465,12u9qwTN9eri2jBTGeDpqiqS5knUY3Uo9L,85 Hallows Hill,953,1KWGJnmpzExoev3CY3bRQiTNLXxFrDTFTk
|
||||
710,Marlee Emmert,Middelburg,,ZA,-31.4951912,25.0080443,1MuXtU4y7NDdHhAyMjr4q5cVZ4uBAGJPY3,5 Emmet Avenue,383,1BsHXjgZPoSi1PtKhJiKxUTMFwcK17TaDH
|
||||
711,Jerald Harry,Carlton,ENG,GB,54.256793,-1.90546,1G4WWjRcZRecspWdVTY1X4wc9SacwofJ2b,9 Swallow Park,781,19wqzQQw8koWYBVZkpb7L3XvoANZBPJ1gm
|
||||
712,Danice Congdon,Gutalac,,PH,7.9414261,122.3862362,1DdhRvgtATCEsAUm1cdTwncU9mta5ukqk1,294 Briar Crest Point,799,18vftu2Pu1HN1LLosxnK35SSaKteszdDro
|
||||
713,Chandler Shurrocks,Bohou,,CN,19.88374,109.745545,1PFvLoe3u8SELxKCDwW7guE9mUWi8qjFE1,2 1st Center,870,1LxddRocR64B2Zgr7apHDGsE92KeygHVbd
|
||||
714,Tamarah Mallon,Ansheng,,CN,39.0493793,121.7791025,12aKJj8tAUEuLgXXHVdAtCpDrzjdK8ugc2,7 Arapahoe Place,635,16Du6VMTDoDg1ok26gammBXfYGLPKKZWsJ
|
||||
715,Ira Wickendon,Gornje Živinice,,BA,44.4280906,18.6066275,1KjZQzq3mv7LB3NgfyJaiCQN2e3U6aY7TG,441 Killdeer Lane,268,1D6hSGe7NYCG1zRnNrEer1QnW2yrJptEDB
|
||||
716,Cathrine Wasbey,Apopa,,SV,13.8086742,-89.1801225,15DTQdX7VkKx9xUSptb93egr9xa9A4irCk,92 Steensland Trail,92,1Fixg4CpjVKjjvnW8waer4j1C4iYPCrvka
|
||||
717,Doro Theml,Abdurahmoni Jomí,,TJ,38.5527205,69.0138738,1HezMymy8FeFh1VPS1EAercW5tkHWrZb9z,2075 Fieldstone Lane,208,1M1wxQ5oTihU1KtgNKYPJTqZtUUs6zSrRK
|
||||
718,Igor Simoes,Chengzhong,,CN,24.315499,109.410683,1J17pFWdeKDgkD48V4pz9cVXaarMR6dSD2,6 Montana Street,27,1FCcVrBiyCgh978zSjRwBL85hJEro9Pgqx
|
||||
719,Aluino Gidney,Yongjiu,,CN,24.6152794,118.1011155,19y6YcRf7FdHU1fmpvTMtyiFixAucNvLcD,9 Alpine Street,949,1BiDh8dgQ1FxNgVHJHCtdiX41fBPqSFV9L
|
||||
720,Giustino Chadderton,Jianyi,,CN,31.2457401,121.4824439,1DiW2U1CQDvjwQj2ikgqSL6LhhBrzh6FKL,8 Troy Park,665,1EbvtJkr9tMtKeSL5thxg6ysVdoYkmsg1r
|
||||
721,Diandra Chilley,Dashiju,,CN,29.444529,121.04024,1HsoMn1ju2gWC2WPWBH57ZZDUr5RLQAvca,17 Sauthoff Circle,345,1HH91J8RHck2envQW4t2ZDsf9o1n5qdFqh
|
||||
722,Garv Iozefovich,Rio das Pedras,,BR,-22.8481591,-47.6063666,1KtXMMMxiGTvSooT8RK2vkxD2S2TKFYHJi,636 Porter Parkway,584,1DZ28aihiSU7DdUdfNpptdXu2K84psEkAj
|
||||
723,Christy Machin,Velké Meziříčí,,CZ,49.3560885,16.0130577,14S4SAAeGoWgHpomaoxf8KoytkhvpsSzm7,95 Forster Alley,783,1Ps9XM4gRoZSfg4RZg6JcCtVUrbD6m7MLp
|
||||
724,Minor Brotherhead,Parakou,,BJ,9.3466822,2.6090043,1Q5jPcXoWbL7Jq98r28djJFVn4Xs2A5oSB,508 Sullivan Way,276,193VLe5emX7Gj1y2fxJcFXdbnpu24QnGPn
|
||||
725,Emeline Errowe,Halle,ST,DE,51.4787653,12.0365785,1LmcR43SPFJ7gj2gcjXimVNZLJwRoZ9DZg,909 Arapahoe Junction,329,1CFA5A9eAMwypqgURZhKVjKvnETwcksyng
|
||||
726,Kaspar Lomasna,Qingjiangqiao,,CN,26.602423,110.994507,1MVuyQPfUNjSQzaeYZRt7fU9cxaVyH3WLF,817 Mandrake Point,754,1HoMhC6rzCbWPve5paRCjK6sqof7yxb1az
|
||||
727,Jeanne Girdwood,Aku,,NG,6.7001345,7.3246534,1C83VYzU5qxBunf6SV1MCqGFFc4AbUC6bt,4383 Melrose Plaza,994,18mZXmcawiqQMjEEGhmio23yJGVQVPEb9h
|
||||
728,Mufinella Farquharson,Weton,,ID,-6.7290837,111.3694594,151Wr6VfHe8HGf9XANKfoHrp9Fgj8akuee,04 Eggendart Terrace,856,12XjbV7C5Rmp7QHyWT6iGG2h6BsbFzfAGD
|
||||
729,Judas Rotherforth,Sonsorol Village,,PW,5.3268119,132.2239117,1AWCkn6iBi3PGzvU1UAdrChHuDV4XEQiyb,52813 Summit Court,290,13Azx8gy1YrTtEmtvQ3F1uxYcyjWxxk9Zt
|
||||
730,Aeriell Eades,Yur’yev-Pol’skiy,,RU,56.5005833,39.6794042,1HxA2tMKfhg5zCFcXFvwPSAhSqTqoqQiit,4 Haas Crossing,147,1N8Hxgap2NrfQ6rawgmAae59KMVrfrhhM1
|
||||
731,Kendell Lutwidge,Gaobu,,CN,23.090823,113.74595,1FS5rEFNnr8zGJzRrdRj8V7q7FdipDCgns,8900 Lakeland Pass,894,1Ubf1Mr3vp2HRhS85NApJMWhbRC3Cvkie
|
||||
732,Gualterio Ollander,Avignon,B8,FR,43.8317876,4.3686291,12aqgmdErdDhPhFRBE8cSKG2UwvfXQj5FB,81189 Banding Road,890,1GJbedfcQmCqHv8ezK5YyaLhBrThwxx1vC
|
||||
733,Bret Kirkhouse,Akron,OH,US,41.0285407,-81.4633516,1GZ2sZGCHBRzjAQiaz92RU7h8BFvTNSx4P,874 Vahlen Way,678,1QJBpAWmo1NiAfEmyrY6niWTeRwrzfhiur
|
||||
734,Marcello Sebrook,Rejoagung,,ID,-5.1734862,105.1990045,1GNmPCg1LdmVoBwoStPpSPG6G2buw1sutj,65 2nd Parkway,683,1BhHeKQzzkS7t4BaqZgf8nDBFHWhZDQmkQ
|
||||
735,Even Pitherick,Babirik,,ID,-2.5063808,115.1296249,1BdtdYX6h6pjLPJZap9Fhb3V7utoAuv9EU,92 Towne Park,306,16uRW31A5nkpjPyEve37nNGRbaSaPFE87W
|
||||
736,Aliza Brodie,Babug,,PH,14.8744012,120.8130073,19Asxq1C8LDETixtyp15KbVrzURwBQSrub,0 Hollow Ridge Trail,431,14J7kriJVHNEUuGnVqZ7RoqnFKMsvcuY7V
|
||||
737,Angela Wigg,Caluya,,PH,18.1965639,120.5989577,1LgoqpZKg3MC9TRsb3AEjZhpTRtFewT8N5,6 Mesta Street,922,1FTuvsdZH6q71ofvJuSUUDyW6SzKKu9ctk
|
||||
738,Marsha Shearsby,Wufeng,,CN,30.199688,110.674706,139X7X1C3yNNVn241SqDeGyoxDvhou5g9o,408 Forest Dale Plaza,100,1EqgLrpGiqR18ubLKDrHXymmxvL2HkyEmU
|
||||
739,Chrissy Slocum,Doumen,,CN,22.2092,113.296467,1fp4GG5V419kErMVAGxSmT1y79ceeb4W2,59 Graedel Plaza,829,15mvXqSdeNWcrPj9vFPdyM3LJNtn4RLt78
|
||||
740,Francklyn Chessel,Lancai,,CN,35.6643,101.818401,18Ue4GHxPgfast8oXaQx97SLN2rAxk2GoU,62593 Washington Way,857,1tKh7S6MqYJUPzRtssZ3ERkcakH8oRexk
|
||||
741,Meade Hentzer,Siborong-borong,,ID,2.2155425,99.0349977,176oWo8HoXNqcF6VrbjXLLWaStKQrePYa8,83798 Melrose Park,501,1QJ8upUpg51LKZe2YuQuTagZXij6MbwVUb
|
||||
742,Isador Bradnam,Heyu,,CN,31.866274,108.958759,14aVz51cYEPkuEuwt3CRUg45rqVNRNLFL3,30 Burrows Avenue,608,1CBifx622FdZUNeEsjucoPfzC3dg3rvbcn
|
||||
743,Lorrin Americi,Kuandian,,CN,40.731316,124.783659,1GJ4QjcKBsEjLyg5oehi3QpyeH5DKCagRh,0 Sugar Parkway,3,1FR5cUN37Qezu3uhkXKGfKSnXcx8gXZ4zd
|
||||
744,Lauryn Greenrde,Pruzhany,,BY,52.5557638,24.4554644,12FKo7nwiELqhDvdvvYTGSYLQ6yWEn1vME,9 Ludington Hill,593,16x14Sm8ckRjxDiJDxHnRNLYsnB3drt3YG
|
||||
745,Lyon Fulger,Skrunda,,LV,56.674534,22.0049709,1NrNxupDKyUUB1RVTbsYDUjYkDv4xvnCnk,7 Tennyson Center,788,1MaT1DdyVhg2YZxqaF2iTtDcfEQSc9Ktjt
|
||||
746,Guillemette Garratty,Lazaro Cardenas,GRO,MX,17.9567646,-102.1943485,1C3GjB9WMthDHY6ZEveYJJyTXx54uecM9H,20664 Roth Road,776,1JCux2qrNe3U95XjtAzGPpsYmJ3ZFP8r4H
|
||||
747,Cristin Thamelt,El Cardo,,PE,-5.85,-79.8833329,1EXHsJuGCdAGKonmnXQYPW3UsWRWGPNhHs,8 Acker Pass,509,1L91NszTD3FREp7XZSnwF89yZGKx8A5hsW
|
||||
748,Rees Greenard,Karangranjong,,ID,-5.0768453,105.0177539,1PDJ4LHixoEsfFhyWgp4ggAQcYWp4oH574,5532 Haas Plaza,428,1EQjJTi2McrFdiYkuVgu6gJ2VKJxGXY5Cf
|
||||
749,Pebrook Lillywhite,Lantera,,ID,-6.2455587,106.7817741,1LbBvqdm6zCZfFKed3Phz37rdp7veUZHs7,7 Northfield Lane,888,1LjvZ842EwibZ76LTeojffsMAPaka9swPu
|
||||
750,Tera Cleary,Sukonolo Krajan,,ID,-8.1031,112.6167,17G8QCobU4ZSCbWLtRZMTYg11SZzYzvLZu,817 Banding Way,63,1AtGTpjhraCTuZ6wojuNPhKydvrp8Nduo8
|
||||
751,Merrie De Bell,Zalewo,,PL,53.8458953,19.6034448,17BFJsm8KzuTUzsq1ncTMmsWBiEWRRzKpp,19212 Colorado Crossing,219,1CZprNKtJdU4jcCSaf4rjUvmTWtCbKU9DB
|
||||
752,Fania Steers,Rahama,,NG,10.4141663,8.6903172,1BwtMsMEjdcsBcnEUTCRHVJ7rrYPihgQ3y,6 Gerald Street,593,1PAuLtnbk9ZrCKK5yLHunn7qEqUboC8DX8
|
||||
753,Colene Claessens,Tarrafal,,CV,15.2760578,-23.7484077,1Hikp8WnjJLLh54Yit8LhMD8MZAoJRJ9cp,8132 Fulton Crossing,708,1pPAbHY133funbo887iBR77EBHnCqzCcp
|
||||
754,Patton Samber,Digos,,PH,14.6843598,121.0928711,159zaFtgfesoZQqV2GWG8GzxfuD5av8zam,59 Claremont Plaza,298,1N8TYLU9Z4cpL6fbghGCdfb9XN338CRgaz
|
||||
755,Laryssa Stove,Yushan,,CN,28.682309,118.244769,1DZtRyeN176Cg3MConKUw2r4qYN9r7N8yT,75618 Green Street,25,1KGHAUzhEMnzk2Zxy4Nmw6tnW9DLoXde8u
|
||||
756,Rosalynd Brower,Oświęcim,,PL,50.0413118,19.2000652,1B7CSJv8o7YhCyDrAm9og57poV7FX5GZyx,02 Fairview Park,801,13TXefqWmeRWJT5vM3U7Mxu9UcFPya8ygk
|
||||
757,Annadiana Wilbor,Tāklisah,,TN,36.792339,10.6295091,1CVA7RwQ5w798zKtBPnHzFhx67Nyn7x96J,0909 8th Alley,262,1JjCdibqNfmLev8bbHR9Rzwyxrg8iYK8Yn
|
||||
758,Elisabetta Jakucewicz,Medan,,ID,3.5951956,98.6722227,15CmnsLaPXasrVvxKgkHxiq2bVfe5zk9Dr,57389 Cascade Place,374,1AfCWEzZDy22vFc5yPXzFnXT7sk3Pmfxkg
|
||||
759,Camella Michell,Brest,A2,FR,48.4100734,-4.4706255,1KhvrCZhk27PN77GMMYakK3Rn81kwkjXvT,801 Anderson Drive,823,1JUpixgfRe9RR6tcmuqpzjKMrVKTDmo7Gd
|
||||
760,Pamela Kramer,Baishan,,CN,41.933917,126.422806,15ZkaYznFnhFbrtVTrdVWYcakp2taJ3qDt,307 Hanover Plaza,293,1NMFiiMEDFpnMGYtCqvemv6sfpEBQbgiJf
|
||||
761,Laurice Float,Sunbu,,CN,30.898495,118.891482,19kGgpXV2s8KrgHko3s4MqzBtJyT9YLT8y,1 Jenna Road,63,1ErtUZXG9rzJP7KqbX5ry3TyZcEeTLChvc
|
||||
762,Cameron Dearman,Huangcun,,CN,23.131797,113.407143,1F2CsyyCCxa21mgN5yvdvM1TGUgvRQfDta,434 Tomscot Road,920,19joJuqpH1BYYVkjRQAsAvQv2Nr1b5mEzb
|
||||
763,Marcella Bordone,Alta,20,NO,69.9717043,23.287731,1MrHUFHTXPgepgtC9fqAtD4scDar7JQqTC,963 Banding Place,705,1CVRsJac8mJzBdBxnqF6jWtnx45uMYj1Dn
|
||||
764,Sunny Mews,Staraya Kupavna,,RU,55.8065475,38.1784959,1EPVS62itkTr4B1E9m4smn4nAFTeAEC9HF,2 Pearson Plaza,547,17oqfi4C7CJkCWfpvTZs3i3fdjasvV6NHF
|
||||
765,Gardy Elsegood,Daejeon,,KR,36.3504119,127.3845475,1MkbtBBypfM7WcE6Zz5UVuPKtgiYy9UbAm,3 Wayridge Point,535,16L8CmKEJ7zw9zhGN6Zj9N9fVZD4jUqhzu
|
||||
766,Georgeanne Milksop,Chivor,,CO,4.888109,-73.366926,1JYxtDez32ymdEBsRtzJhVP43mAYDXTWdQ,8201 Vera Alley,54,19BhqSZ4BBkHnJTVY4ejoAPZuRez496pYF
|
||||
767,Brina Enser,Las Animas,GRO,MX,19.2558715,-99.0165468,1HKpZiVJEwDiAVa9cdidvkCQ5iwWdA3Mvu,970 Esker Trail,848,1P5ZSvePTu6AmXvMCUzLqdrxeyzpP5J81j
|
||||
768,Cathi Treagust,Medveđa,,RS,42.8414484,21.5766407,1LZ4pYHySWSC1sPYWvuTKhQmknE2gzmKiZ,40004 Ohio Street,378,1FiTCYTvvYZ4ZKa3e9Eo7SyZdNJcpqugFR
|
||||
769,Megen Bonnett,Jamao al Norte,,DO,19.6419585,-70.449236,1LGsUzpHS8FKsi2UiYas7kezuqnRqaZvFP,8 Oakridge Park,464,1HXCQcx5NpEXnrMvXXpjPsxyrUcNzrvcRu
|
||||
770,Morty Russam,Linan,,PH,11.4297007,122.5987302,1Ag1t4RLxVoNiZq2TmEeEYVhavZ2MCAnLo,57 Pawling Center,942,14NeVkTsUXJR7tF8j8a1rEa1FMSsYwr1sP
|
||||
771,Vivia Tumilson,Bisée,,LC,14.0242809,-60.9758292,1HxpQB4oxT8sbvaSzWjJGxjDdJmxEbbqfV,95 Sauthoff Junction,319,15gC2wzVFHKRf5xptcJ4QpMNA9KYqw8Pvd
|
||||
772,Ettie Cettell,Longueuil,QC,CA,45.4693392,-73.4848038,19HXovCwwJ8UbjGpDnSeivdqB4d5wR31Hb,183 Dexter Trail,264,1SmuU3b6sqNqMNsGvpADNM2W1JMNaAtCk
|
||||
773,Gav Blamphin,Ipil,,PH,14.4706312,121.0053859,1CfCAbPVqaCUiWLtbdDBvFVwNkcEbXptFp,612 Hoffman Plaza,394,1oRiZMZfc2hTfTYrqg7xi2eZDZCDyRqcY
|
||||
774,Florella Sweetsur,Huangbao,,CN,35.872664,120.04619,1FbgasU3zwG7vmsRaZ3GyjD7oZixegJ7a9,31 Starling Parkway,482,16dTefLLSoVudodagPhjh1p28ENgPAEDpz
|
||||
775,Jeff Saltman,Guadalupe,GUA,MX,20.7288886,-100.7612288,1GLXNDiBvd4L3NwWJ238FwpqLbQip2MPNo,8085 Corben Street,564,187Dakx4doM7dw5jyX2kHWH9b4NtQCpcaa
|
||||
776,Hubert Frarey,Amiens,B6,FR,49.8905368,2.3081396,1EqM9kk5dReEqjzPjEA7qZWpSbDN1GnB4x,650 Namekagon Plaza,730,1GXrQnEZ6Y9jqJKS8t8vAPSAmt3tf8AcPn
|
||||
777,Briant Adshede,Kaeng Khoi,,TH,14.5801157,101.003522,1DXz7N8hJ1qkwcTHU5y7bh4zEDxYuFkbMr,63 Nelson Way,916,12LuXAcRxZZthx3gwRVy9j6iF53zY3du3W
|
||||
778,Belia Belle,Yongdeng Chengguanzhen,,CN,36.733165,103.262152,121xEXN7ohmfZhDjo3imF7WQRAHvFws41K,90620 Rigney Avenue,750,1NuszYF64Pfg61HRWkVwUWMxcdfEhRzU7b
|
||||
779,Elinore Simmonite,Bailu,,CN,30.2733296,120.1672735,1GtQ4BBdaAiwSDfYcRha84XV8WrCqNjA4a,80 Basil Hill,749,1DckH7NmxdvpGsdTW2CtQvip5216k4dyqS
|
||||
780,Jennilee De la Yglesia,Nirasaki,,JP,35.7441454,138.4737282,1G2MsZbVjY5iHJCSDMhRnAGUUZT1v6wWsV,2495 Union Crossing,962,1NgxytZfK2UUxUTFmZXAxAKUCHp4YCTVEq
|
||||
781,Cari Stubbes,Taipalsaari,,FI,61.1521338,28.0739477,1PR33x8PMUTt2e62hCcpk3d48V4vZ65nzu,4567 Maryland Lane,673,13UzE4RCcCCJ1K3jnDTYwLANQ1F6VrUHv7
|
||||
782,Rodolfo Coopman,Moss,01,NO,59.4634763,10.694967,19csAeyeyQU4ArnkcPmD98zBqTtakxqA5G,5966 Corry Alley,313,13i5ju9MU43JFPoeRAfFswYYNDnFgJ3X66
|
||||
783,Warren Bunney,Göteborg,O,SE,57.7069317,11.9727803,12g539JCARiP8MB4dxgYNtQH3xnBb3ejQV,71 Kings Circle,422,19AeGxuhax1aGfg75XooYQ6E5TZj9RzbGJ
|
||||
784,Evey Brookshaw,San Silvestre,,VE,8.2796966,-70.109113,1HkVmJzraxBUZArSGG3X8ogqWBXUWjFB9c,8 Bowman Plaza,828,16zrhg3gEb3pUrU3e6EozEbo22paV5o9m2
|
||||
785,Tiertza Cranna,Vurğun,,AZ,41.096515,45.4741645,1KvThyzXg1d1Kpw4JwJtyjEQNm2nzqYZut,3383 Green Center,431,1D1YchnnTqqDbNuN8G6hJKYHe9QKw8LjW4
|
||||
786,Marris Trumble,Beya,,RU,53.0475258,90.944621,1MnT877Ct6H4UhBVjtgtbSQKa13gLXJMKa,3719 Kensington Way,971,1N5DBKMuxdApZuBT6mHSsjgHVShTQQ5CdQ
|
||||
787,Paulita Rubenfeld,El Pao,,VE,9.6389796,-68.129727,1FQvAPi2Jwf6ycnwP2CGpquQnYFdFZiuGR,6559 Moland Road,144,1GY2ohoCGPd26DgX8RiQnvLsPrwbXT3Gga
|
||||
788,Arlyn Gallahar,Arranhó,11,PT,38.9550335,-9.1331143,16yzAyLd5g8ziVvq55TfMU185pbEgy9Xqe,39 John Wall Trail,591,1FSX8bZzBHpuXDS7fg3dd2EAAipNKrgw19
|
||||
789,Babs Cobb,Castleblayney,,IE,54.10269,-6.64937,14kkAspGiJMfHoQP2t1tFir35BDkvbGgDq,8259 Gateway Avenue,796,18whmUx1SPXwQtzr1abtRPzhayUWepBeMJ
|
||||
790,Tobit Esselen,Vidzy,,BY,55.3948269,26.6327297,15Ut8Ns2fhsEABo342J5ngg1CnrsLZU96z,62703 Carpenter Parkway,454,1EJ6bKK6LGdv1uNWXJnQYaNPBYKDCyqFfN
|
||||
791,Nikolai Routhorn,Floridablanca,,CO,7.0427956,-73.1054544,15Sy1NLp72YkG4722P9SxwzX1Jx9712cGX,61 Bluejay Avenue,768,1JtmkHtGfVjzQZPjVTRFbPN4bKsxEpKHkU
|
||||
792,Lamont Hayball,Wang Thonglang,,TH,13.7838134,100.6150564,1Evwid8y9DiNP61HpicFhwjB29y3NtiTMC,52951 Waywood Junction,402,18mFXiZXrCCVbD3z4vsHvvaqfcu7jF9JDH
|
||||
793,Harriette Reuven,Itaitinga,,BR,-3.9715688,-38.5276456,1JhBFtETwxnsHn9aiPUfgwFnEWbTtTBMCU,8647 Steensland Junction,599,1MyXbG295d6E2rHU9QHhexwf6oK2q9P7Ce
|
||||
794,Ame Mitchenson,Kiryū,,JP,36.4045915,139.2613339,1CpD6b4Dnp6cJ9ojEHQgRRLp7amfReizka,7649 Carey Hill,634,1N3YRuLKAt7rVxfqmV7nSQsLaf42QYHN4s
|
||||
795,Sonya Windibank,San Juan,72,PR,18.4,-66.05,17rEW4QXpLxkEapdDNnkdyfbKjzHtR8Ezm,6677 Dorton Terrace,47,1DoTGtCPs8Y5d6X9Yue3N8DU1D1yMiAjHF
|
||||
796,Merilee Warwicker,Quillabamba,,PE,-12.868355,-72.6919814,13w4jtZZEANw2hAytJhk8dkmmWe156trdG,4422 Fisk Point,146,1CYJd5P9azkoB9udUxppmwDbJgikQTP5nJ
|
||||
797,Gael Cupitt,Springfield,MA,US,42.1144473,-72.4909026,1DijAp6sNBN5VGAQrRDPf6djyL3pm91abt,909 Fairview Avenue,728,1NR5PyNrVjaeGpe6M4XUbuGyH79WGG6pqv
|
||||
798,Abbe Kmieciak,Pacora,,PA,9.0835262,-79.2906747,1P82Un2amYTo2ui8qUaoAoPsTa1Syw1B4j,60447 South Alley,381,1BGNuZmbEv14FqKYmq5fz5QMYTLRtiWi9c
|
||||
799,Silvie Edgars,Albuquerque,NM,US,35.0820413,-106.6478031,12EQAU3tFj1EsCYs9aeJvH95yXvRPCvFCP,1 Pawling Way,440,1P4MSAS6RnEnyCionLWFocAVsPyyMtGFw5
|
||||
800,Lisbeth Guess,Plakhtiyivka,,UA,46.1030135,29.7204853,1LDcxbqavA1MnSYeX56C5sZcSKGnkebFmv,5745 Judy Center,787,1NUSa1v7VCSGRVATogC2ZJJHBEmUbUHQzy
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,8 +1,8 @@
|
|||
{
|
||||
"SmtpServerUrl" : "smtp.gmail.com",
|
||||
"SmtpUsername" : "angelis@inesco.energy",
|
||||
"SmtpPassword" : "huvu pkqd kakz hqtm ",
|
||||
"SmtpServerUrl" : "mail.agenturserver.de",
|
||||
"SmtpUsername" : "p518526p69",
|
||||
"SmtpPassword" : "i;b*xqm4iB5uhl",
|
||||
"SmtpPort" : 587,
|
||||
"SenderName" : "Inesco Energy",
|
||||
"SenderAddress" : "noreply@inesco.energy"
|
||||
"SenderName" : "InnovEnergy",
|
||||
"SenderAddress" : "noreply@innov.energy"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,38 +1,40 @@
|
|||
using System.Diagnostics;
|
||||
using Flurl.Http;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Channels;
|
||||
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;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using RabbitMQ.Client;
|
||||
|
||||
namespace InnovEnergy.App.Backend;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
public static async Task Main(String[] args)
|
||||
public static void Main(String[] args)
|
||||
{
|
||||
//First, we initialize the database. This is an empty constructor of the Db class that will be called.
|
||||
//In addition, we initialize WatchDog in order to restart the backend service in case of failure.
|
||||
//Finally, we start all the backend services. We call the InitializeEnvironment function of RabbitMqManager to create the queue (factory/connection)
|
||||
//Then, we generate a consumer that binds to the queue. This is a separate async Task so it must not be awaited (it acts as a separate thread).
|
||||
//Finally, we call the MonitorSalimaxInstallationTable and MonitorSalidomoInstallationTable from the WebsocketManager class.
|
||||
//Those methods will build in-memory data structures to track the connected frontends and update them regarding the offline installations.
|
||||
|
||||
//Db.CreateFakeRelations();
|
||||
Watchdog.NotifyReady();
|
||||
Db.Init();
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
RabbitMqManager.InitializeEnvironment();
|
||||
RabbitMqManager.StartRabbitMqConsumer().SupressAwaitWarning();
|
||||
|
||||
WebsocketManager.MonitorInstallationTable().SupressAwaitWarning();
|
||||
string vpnServerIp = "194.182.190.208";
|
||||
//string vpnServerIp = "127.0.0.1";
|
||||
WebsocketManager.Factory = new ConnectionFactory { HostName = vpnServerIp};
|
||||
WebsocketManager.Connection = WebsocketManager.Factory.CreateConnection();
|
||||
WebsocketManager.Channel = WebsocketManager.Connection.CreateModel();
|
||||
Console.WriteLine("Middleware subscribed to RabbitMQ queue, ready for receiving messages");
|
||||
WebsocketManager.Channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
|
||||
|
||||
|
||||
// Task.Run(() => DeleteOldDataFromS3.DeleteOldData());
|
||||
WebsocketManager.StartRabbitMqConsumer();
|
||||
Console.WriteLine("Queue declared");
|
||||
WebsocketManager.InformInstallationsToSubscribeToRabbitMq();
|
||||
WebsocketManager.MonitorInstallationTable();
|
||||
|
||||
builder.Services.AddControllers();
|
||||
builder.Services.AddProblemDetails(setup =>
|
||||
|
|
@ -89,7 +91,7 @@ public static class Program
|
|||
|
||||
private static OpenApiInfo OpenApiInfo { get; } = new OpenApiInfo
|
||||
{
|
||||
Title = "Inesco Backend API",
|
||||
Title = "InnovEnergy Backend API",
|
||||
Version = "v1"
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,62 +1,42 @@
|
|||
using InnovEnergy.App.Backend.Database;
|
||||
using InnovEnergy.App.Backend.DataTypes;
|
||||
using InnovEnergy.App.Backend.DataTypes.Methods;
|
||||
using SQLite;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Relations;
|
||||
|
||||
public class Session : Relation<String, Int64>
|
||||
{
|
||||
public static TimeSpan MaxAge { get; } = TimeSpan.FromDays(1);
|
||||
public static TimeSpan MaxAge { get; } = TimeSpan.FromDays(7);
|
||||
|
||||
[Unique ] public String Token { get => Left ; init => Left = value;}
|
||||
[Indexed] public Int64 UserId { get => Right; init => Right = value;}
|
||||
[Indexed] public DateTime LastSeen { get; set; }
|
||||
public Boolean AccessToSalimax { get; set; } = false;
|
||||
public Boolean AccessToSalidomo { get; set; } = false;
|
||||
public Boolean AccessToSodistoreMax { get; set; } = false;
|
||||
public Boolean AccessToSodioHome { get; set; } = false;
|
||||
[Ignore] public Boolean Valid => DateTime.Now - LastSeen <=MaxAge ;
|
||||
|
||||
// Private backing field
|
||||
[Ignore] public Boolean Valid => DateTime.Now - LastSeen < MaxAge
|
||||
&& (User) is not null;
|
||||
|
||||
[Ignore] public User User => _User ??= Db.GetUserById(UserId)!;
|
||||
|
||||
|
||||
private User? _User;
|
||||
|
||||
[Ignore] public User User
|
||||
{
|
||||
get => _User ??= Db.GetUserById(UserId)!;
|
||||
set => _User =value;
|
||||
}
|
||||
|
||||
|
||||
[Obsolete("To be used only by deserializer")]
|
||||
public Session()
|
||||
{}
|
||||
|
||||
//We need to return a session object to the frontend. Only the public fields can be included.
|
||||
//For this reason, we use the public User User. It is a public field but ignored, so it can be included to the object returned
|
||||
//to the frontend but it will not get inserted to the database.
|
||||
//When we initialize it like that: User = Db.GetUserById(user.Id)!, the set will be called and the private member will be initialized as well.
|
||||
//What if the getSession method is called from another function of the controller?
|
||||
//GetSession will retrieve a session object from the database, but this does not have the metadata included (the private fields and the ignored public fields)
|
||||
//Thus, the get will be called and the private field _User will be initialized on the fly.
|
||||
public Session(User user)
|
||||
{
|
||||
User = Db.GetUserById(user.Id)!;
|
||||
_User = user;
|
||||
Token = CreateToken();
|
||||
UserId = user.Id;
|
||||
LastSeen = DateTime.Now;
|
||||
AccessToSalimax = user.AccessibleInstallations(product: (int)ProductType.Salimax).ToList().Count > 0;
|
||||
AccessToSalidomo = user.AccessibleInstallations(product: (int)ProductType.Salidomo).ToList().Count > 0;
|
||||
AccessToSodistoreMax = user.AccessibleInstallations(product: (int)ProductType.SodiStoreMax).ToList().Count > 0;
|
||||
AccessToSodioHome = user.AccessibleInstallations(product: (int)ProductType.SodioHome).ToList().Count > 0;
|
||||
|
||||
Console.WriteLine("salimax" +user.AccessibleInstallations(product: (int)ProductType.Salimax).ToList().Count);
|
||||
Console.WriteLine("AccessToSodistoreMax" +user.AccessibleInstallations(product: (int)ProductType.SodiStoreMax).ToList().Count);
|
||||
Console.WriteLine("sodio" + user.AccessibleInstallations(product: (int)ProductType.SodioHome).ToList().Count);
|
||||
}
|
||||
|
||||
private static String CreateToken()
|
||||
{
|
||||
//var token = new Byte[24];
|
||||
//Random.Shared.NextBytes(token);
|
||||
//return Convert.ToBase64String(token).Replace("/","");
|
||||
return Guid.NewGuid().ToString("N");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
{
|
||||
"Key": "EXOa0b53cf10517307cec1bf00e",
|
||||
"Secret": "7MbZBQbHDJ1-eRsZH47BEbRvPaTT7io8H7OGqFujdQ4"
|
||||
"Key": "EXO4d838d1360ba9fb7d51648b0",
|
||||
"Secret": "_bmrp6ewWAvNwdAQoeJuC-9y02Lsx7NV6zD-WjljzCU"
|
||||
}
|
||||
|
|
@ -5,6 +5,5 @@ public class InstallationInfo
|
|||
{
|
||||
public int Status { get; set; }
|
||||
public DateTime Timestamp { get; set; }
|
||||
public int Product { get; set; }
|
||||
public List<WebSocket> Connections { get; } = new List<WebSocket>();
|
||||
}
|
||||
|
|
@ -1,191 +0,0 @@
|
|||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using InnovEnergy.App.Backend.Database;
|
||||
using InnovEnergy.App.Backend.DataTypes;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Websockets;
|
||||
|
||||
public static class RabbitMqManager
|
||||
{
|
||||
public static ConnectionFactory Factory = null!;
|
||||
public static IConnection Connection = null!;
|
||||
public static IModel Channel = null!;
|
||||
|
||||
//This function will be called from the Backend/Program.cs
|
||||
public static void InitializeEnvironment()
|
||||
{
|
||||
string vpnServerIp = "10.2.0.11";
|
||||
|
||||
//Subscribe to RabbitMq queue as a consumer
|
||||
Factory = new ConnectionFactory
|
||||
{
|
||||
HostName = vpnServerIp,
|
||||
Port = 5672,
|
||||
VirtualHost = "/",
|
||||
UserName = "consumer",
|
||||
Password = "faceaddb5005815199f8366d3d15ff8a",
|
||||
|
||||
};
|
||||
|
||||
Connection = Factory.CreateConnection();
|
||||
Channel = Connection.CreateModel();
|
||||
Console.WriteLine("Middleware subscribed to RabbitMQ queue, ready for receiving messages");
|
||||
Channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
|
||||
}
|
||||
|
||||
public static async Task StartRabbitMqConsumer()
|
||||
{
|
||||
//Wait to receive a message from an installation
|
||||
var consumer = new EventingBasicConsumer(Channel);
|
||||
consumer.Received += (_, ea) =>
|
||||
{
|
||||
var body = ea.Body.ToArray();
|
||||
var message = Encoding.UTF8.GetString(body);
|
||||
//A message can be an alarm, a warning or a heartbit
|
||||
StatusMessage? receivedStatusMessage = JsonSerializer.Deserialize<StatusMessage>(message);
|
||||
|
||||
lock (WebsocketManager.InstallationConnections)
|
||||
{
|
||||
//Consumer received a message
|
||||
if (receivedStatusMessage != null)
|
||||
{
|
||||
Installation installation = Db.Installations.FirstOrDefault(f => f.Product == receivedStatusMessage.Product && f.S3BucketId == receivedStatusMessage.InstallationId);
|
||||
int installationId = (int)installation.Id;
|
||||
|
||||
//This is a heartbit message, just update the timestamp for this installation.
|
||||
//There is no need to notify the corresponding front-ends.
|
||||
//Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue.
|
||||
if (receivedStatusMessage.Type == MessageType.Heartbit)
|
||||
{
|
||||
//Do not do anything here, just for debugging purposes.
|
||||
}
|
||||
else
|
||||
{
|
||||
//Traverse the Warnings list, and store each of them to the database
|
||||
if (receivedStatusMessage.Warnings != null)
|
||||
{
|
||||
foreach (var warning in receivedStatusMessage.Warnings)
|
||||
{
|
||||
Warning newWarning = new Warning
|
||||
{
|
||||
InstallationId = installationId,
|
||||
Description = warning.Description,
|
||||
Date = warning.Date,
|
||||
Time = warning.Time,
|
||||
DeviceCreatedTheMessage = warning.CreatedBy,
|
||||
Seen = false
|
||||
};
|
||||
//Create a new warning and add it to the database
|
||||
//Console.WriteLine("Add a warning for installation "+installationId);
|
||||
Db.HandleWarning(newWarning, installationId);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//Traverse the Alarm list, and store each of them to the database
|
||||
if (receivedStatusMessage.Alarms != null)
|
||||
{
|
||||
|
||||
string monitorLink;
|
||||
if (installation.Product == (int)ProductType.Salimax)
|
||||
{
|
||||
monitorLink =
|
||||
$"https://monitor.inesco.energy/installations/list/installation/{installation.S3BucketId}/batteryview";
|
||||
}
|
||||
else if (installation.Product == (int)ProductType.SodiStoreMax)
|
||||
{
|
||||
monitorLink =
|
||||
$"https://monitor.inesco.energy/sodistore_installations/list/installation/{installation.S3BucketId}/batteryview";
|
||||
}
|
||||
else
|
||||
{
|
||||
monitorLink =
|
||||
$"https://monitor.inesco.energy/salidomo_installations/list/installation/{installation.S3BucketId}/batteryview";
|
||||
}
|
||||
|
||||
foreach (var alarm in receivedStatusMessage.Alarms)
|
||||
{
|
||||
Error newError = new Error
|
||||
{
|
||||
InstallationId = installation.Id,
|
||||
Description = alarm.Description,
|
||||
Date = alarm.Date,
|
||||
Time = alarm.Time,
|
||||
DeviceCreatedTheMessage = alarm.CreatedBy,
|
||||
Seen = false
|
||||
};
|
||||
|
||||
//Console.WriteLine("Add an alarm for installation "+installationId);
|
||||
|
||||
// Send replace battery email to support team if this alarm is "NeedToReplaceBattery"
|
||||
if (alarm.Description == "2 or more string are disabled")
|
||||
{
|
||||
Console.WriteLine("Send replace battery email to the support team for installation "+installationId);
|
||||
string recipient = "support@innov.energy";
|
||||
string subject = $"Battery Alarm from {installation.InstallationName}: 2 or more strings broken";
|
||||
string text = $"Dear InnovEnergy Support Team,\n" +
|
||||
$"\n"+
|
||||
$"Installation Name: {installation.InstallationName}\n"+
|
||||
$"\n"+
|
||||
$"Installation Monitor Link: {monitorLink}\n"+
|
||||
$"\n"+
|
||||
$"Please exchange: {alarm.CreatedBy}\n"+
|
||||
$"\n"+
|
||||
$"Error created date and time: {alarm.Date} {alarm.Time}\n"+
|
||||
$"\n"+
|
||||
$"Thank you for your great support:)";
|
||||
//Disable this function now
|
||||
//Mailer.Send("InnovEnergy Support Team", recipient, subject, text);
|
||||
}
|
||||
//Create a new error and add it to the database
|
||||
Db.HandleError(newError, installationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Int32 prevStatus;
|
||||
|
||||
|
||||
|
||||
//This installation id does not exist in our in-memory data structure, add it.
|
||||
if (!WebsocketManager.InstallationConnections.ContainsKey(installationId))
|
||||
{
|
||||
prevStatus = -2;
|
||||
//Console.WriteLine("Create new empty list for installation: " + installationId);
|
||||
WebsocketManager.InstallationConnections[installationId] = new InstallationInfo
|
||||
{
|
||||
Status = receivedStatusMessage.Status,
|
||||
Timestamp = DateTime.Now,
|
||||
Product = installation.Product
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
prevStatus = WebsocketManager.InstallationConnections[installationId].Status;
|
||||
WebsocketManager.InstallationConnections[installationId].Status = receivedStatusMessage.Status;
|
||||
WebsocketManager.InstallationConnections[installationId].Timestamp = DateTime.Now;
|
||||
}
|
||||
|
||||
if (installationId == 795)
|
||||
{
|
||||
Console.WriteLine("RECEIVED A HEARTBIT FROM prototype, time is "+ WebsocketManager.InstallationConnections[installationId].Timestamp);
|
||||
}
|
||||
|
||||
installation.Status = receivedStatusMessage.Status;
|
||||
installation.Apply(Db.Update);
|
||||
|
||||
//Console.WriteLine("----------------------------------------------");
|
||||
//If the status has changed, update all the connected front-ends regarding this installation
|
||||
if(prevStatus != receivedStatusMessage.Status && WebsocketManager.InstallationConnections[installationId].Connections.Count > 0)
|
||||
{
|
||||
WebsocketManager.InformWebsocketsForInstallation(installationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer);
|
||||
}
|
||||
}
|
||||
|
|
@ -4,8 +4,7 @@ namespace InnovEnergy.App.Backend.Websockets;
|
|||
public class StatusMessage
|
||||
{
|
||||
public required Int32 InstallationId { get; set; }
|
||||
public required Int32 Product { get; set; }
|
||||
public required Int32 Status { get; set; }
|
||||
public required int Status { get; set; }
|
||||
public required MessageType Type { get; set; }
|
||||
public List<AlarmOrWarning>? Warnings { get; set; }
|
||||
public List<AlarmOrWarning>? Alarms { get; set; }
|
||||
|
|
|
|||
|
|
@ -5,65 +5,187 @@ using System.Text;
|
|||
using System.Text.Json;
|
||||
using InnovEnergy.App.Backend.Database;
|
||||
using InnovEnergy.App.Backend.DataTypes;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
|
||||
namespace InnovEnergy.App.Backend.Websockets;
|
||||
|
||||
public static class WebsocketManager
|
||||
{
|
||||
public static Dictionary<Int64, InstallationInfo> InstallationConnections = new Dictionary<Int64, InstallationInfo>();
|
||||
public static Dictionary<int, InstallationInfo> InstallationConnections = new Dictionary<int, InstallationInfo>();
|
||||
public static ConnectionFactory Factory = null!;
|
||||
public static IConnection Connection = null!;
|
||||
public static IModel Channel = null!;
|
||||
|
||||
public static void InformInstallationsToSubscribeToRabbitMq()
|
||||
{
|
||||
var installationIps = Db.Installations.Select(inst => inst.VpnIp).ToList();
|
||||
Console.WriteLine("Count is "+installationIps.Count);
|
||||
var maxRetransmissions = 2;
|
||||
|
||||
UdpClient udpClient = new UdpClient();
|
||||
udpClient.Client.ReceiveTimeout = 2000;
|
||||
int port = 9000;
|
||||
//Send a message to each installation and tell it to subscribe to the queue
|
||||
using (udpClient)
|
||||
{
|
||||
for (int i = 0; i < installationIps.Count; i++)
|
||||
{
|
||||
if(installationIps[i]==""){continue;}
|
||||
Console.WriteLine("-----------------------------------------------------------");
|
||||
Console.WriteLine("Trying to reach installation with IP: " + installationIps[i]);
|
||||
//Try at most MAX_RETRANSMISSIONS times to reach an installation.
|
||||
for (int j = 0; j < maxRetransmissions; j++)
|
||||
{
|
||||
string message = "This is a message from RabbitMQ server, you can subscribe to the RabbitMQ queue";
|
||||
byte[] data = Encoding.UTF8.GetBytes(message);
|
||||
udpClient.Send(data, data.Length, installationIps[i], port);
|
||||
|
||||
Console.WriteLine($"Sent UDP message to {installationIps[i]}:{port}: {message}");
|
||||
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse(installationIps[i]), port);
|
||||
|
||||
try
|
||||
{
|
||||
byte[] replyData = udpClient.Receive(ref remoteEndPoint);
|
||||
string replyMessage = Encoding.UTF8.GetString(replyData);
|
||||
Console.WriteLine("Received " + replyMessage + " from installation " + installationIps[i]);
|
||||
break;
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
if (ex.SocketErrorCode == SocketError.TimedOut){Console.WriteLine("Timed out waiting for a response. Retry...");}
|
||||
else{Console.WriteLine("Error: " + ex.Message);}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("Start RabbitMQ Consumer");
|
||||
|
||||
}
|
||||
|
||||
//Every 1 minute, check the timestamp of the latest received message for every installation.
|
||||
//If the difference between the two timestamps is more than one minute, we consider this installation unavailable.
|
||||
public static async Task MonitorInstallationTable()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
lock (InstallationConnections)
|
||||
{
|
||||
Console.WriteLine("Monitoring installation table...");
|
||||
foreach (var installationConnection in InstallationConnections)
|
||||
{
|
||||
Console.WriteLine("installationConnection ID is " + installationConnection.Key + ", latest timestamp is" +installationConnection.Value.Timestamp + ", product is "+ installationConnection.Value.Product
|
||||
+ ", and time diff is "+ (DateTime.Now - installationConnection.Value.Timestamp));
|
||||
|
||||
if ((installationConnection.Value.Product == (int)ProductType.Salimax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2)) ||
|
||||
(installationConnection.Value.Product == (int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(60)) ||
|
||||
(installationConnection.Value.Product == (int)ProductType.SodioHome && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(4)) ||
|
||||
(installationConnection.Value.Product == (int)ProductType.SodiStoreMax && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(2))
|
||||
)
|
||||
{
|
||||
|
||||
Console.WriteLine("Installation ID is " + installationConnection.Key);
|
||||
Console.WriteLine("installationConnection.Value.Timestamp is " + installationConnection.Value.Timestamp);
|
||||
// Console.WriteLine("diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
|
||||
|
||||
installationConnection.Value.Status = (int)StatusType.Offline;
|
||||
Installation installation = Db.Installations.FirstOrDefault(f => f.Product == installationConnection.Value.Product && f.Id == installationConnection.Key);
|
||||
installation.Status = (int)StatusType.Offline;
|
||||
installation.Apply(Db.Update);
|
||||
if (installationConnection.Value.Connections.Count > 0)
|
||||
{
|
||||
InformWebsocketsForInstallation(installationConnection.Key);
|
||||
while (true){
|
||||
lock (InstallationConnections){
|
||||
foreach (var installationConnection in InstallationConnections){
|
||||
if ((DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(1)){
|
||||
Console.WriteLine("Installation "+installationConnection.Key+" is offline, latest timestamp was "+installationConnection.Value.Timestamp);
|
||||
installationConnection.Value.Status = -1;
|
||||
if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await Task.Delay(TimeSpan.FromMinutes(1));
|
||||
}
|
||||
}
|
||||
|
||||
//Inform all the connected websockets regarding installation "installationId"
|
||||
public static void InformWebsocketsForInstallation(Int64 installationId)
|
||||
public static async Task StartRabbitMqConsumer()
|
||||
{
|
||||
var consumer = new EventingBasicConsumer(Channel);
|
||||
consumer.Received += (_, ea) =>
|
||||
{
|
||||
var body = ea.Body.ToArray();
|
||||
var message = Encoding.UTF8.GetString(body);
|
||||
StatusMessage? receivedStatusMessage = JsonSerializer.Deserialize<StatusMessage>(message);
|
||||
|
||||
lock (InstallationConnections)
|
||||
{
|
||||
//Consumer received a message
|
||||
if (receivedStatusMessage != null)
|
||||
{
|
||||
Console.WriteLine("Received a message from installation: " + receivedStatusMessage.InstallationId + " and status is: " + receivedStatusMessage.Status);
|
||||
Console.WriteLine("----------------------------------------------");
|
||||
Console.WriteLine("Update installation connection table");
|
||||
var installationId = receivedStatusMessage.InstallationId;
|
||||
|
||||
//This is a heartbit message, just update the timestamp for this installation.
|
||||
//There is no need to notify the corresponding front-ends.
|
||||
if (receivedStatusMessage.Type == MessageType.Heartbit)
|
||||
{
|
||||
InstallationConnections[installationId].Timestamp = DateTime.Now;
|
||||
}
|
||||
else
|
||||
{
|
||||
//Traverse the Warnings list, and store each of them to the database
|
||||
if (receivedStatusMessage.Warnings != null)
|
||||
{
|
||||
foreach (var warning in receivedStatusMessage.Warnings)
|
||||
{
|
||||
Warning newWarning = new Warning
|
||||
{
|
||||
InstallationId = receivedStatusMessage.InstallationId,
|
||||
Description = warning.Description,
|
||||
Date = warning.Date,
|
||||
Time = warning.Time,
|
||||
DeviceCreatedTheMessage = warning.CreatedBy,
|
||||
Seen = false
|
||||
};
|
||||
//Create a new warning and add it to the database
|
||||
Db.HandleWarning(newWarning, receivedStatusMessage.InstallationId);
|
||||
}
|
||||
}
|
||||
|
||||
//Traverse the Alarm list, and store each of them to the database
|
||||
if (receivedStatusMessage.Alarms != null)
|
||||
{
|
||||
foreach (var alarm in receivedStatusMessage.Alarms)
|
||||
{
|
||||
Error newError = new Error
|
||||
{
|
||||
InstallationId = receivedStatusMessage.InstallationId,
|
||||
Description = alarm.Description,
|
||||
Date = alarm.Date,
|
||||
Time = alarm.Time,
|
||||
DeviceCreatedTheMessage = alarm.CreatedBy,
|
||||
Seen = false
|
||||
};
|
||||
//Create a new error and add it to the database
|
||||
Db.HandleError(newError, receivedStatusMessage.InstallationId);
|
||||
}
|
||||
}
|
||||
|
||||
//This installation id does not exist in our data structure, add it.
|
||||
if (!InstallationConnections.ContainsKey(installationId))
|
||||
{
|
||||
Console.WriteLine("Create new empty list for installation: " + installationId);
|
||||
InstallationConnections[installationId] = new InstallationInfo
|
||||
{
|
||||
Status = receivedStatusMessage.Status,
|
||||
Timestamp = DateTime.Now
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
InstallationConnections[installationId].Status = receivedStatusMessage.Status;
|
||||
InstallationConnections[installationId].Timestamp = DateTime.Now;
|
||||
}
|
||||
|
||||
Console.WriteLine("----------------------------------------------");
|
||||
//Update all the connected front-ends regarding this installation
|
||||
if(InstallationConnections[installationId].Connections.Count > 0)
|
||||
{
|
||||
InformWebsocketsForInstallation(installationId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
Channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer);
|
||||
}
|
||||
|
||||
//Inform all the connected websockets regarding installation "installationId"
|
||||
public static void InformWebsocketsForInstallation(int installationId)
|
||||
{
|
||||
var installation = Db.GetInstallationById(installationId);
|
||||
var installationConnection = InstallationConnections[installationId];
|
||||
Console.WriteLine("Update all the connected websockets for installation " + installation.Name);
|
||||
Console.WriteLine("Update all the connected websockets for installation " + installationId);
|
||||
|
||||
var jsonObject = new
|
||||
{
|
||||
id = installationId,
|
||||
status = installationConnection.Status,
|
||||
testingMode = installation.TestingMode
|
||||
status = installationConnection.Status
|
||||
};
|
||||
|
||||
string jsonString = JsonSerializer.Serialize(jsonObject);
|
||||
|
|
@ -80,7 +202,6 @@ public static class WebsocketManager
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public static async Task HandleWebSocketConnection(WebSocket currentWebSocket)
|
||||
{
|
||||
var buffer = new byte[4096];
|
||||
|
|
@ -96,7 +217,6 @@ public static class WebsocketManager
|
|||
|
||||
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
||||
var installationIds = JsonSerializer.Deserialize<int[]>(message);
|
||||
Console.WriteLine("Received Websocket message: " + message);
|
||||
|
||||
//This is a ping message to keep the connection alive, reply with a pong
|
||||
if (installationIds[0] == -1)
|
||||
|
|
@ -118,69 +238,56 @@ public static class WebsocketManager
|
|||
continue;
|
||||
}
|
||||
|
||||
//Received a new message from this websocket.
|
||||
//We have a HandleWebSocketConnection per connected frontend
|
||||
Console.WriteLine("Received a new message from websocket");
|
||||
lock (InstallationConnections)
|
||||
{
|
||||
List<WebsocketMessage> dataToSend = new List<WebsocketMessage>();
|
||||
|
||||
//Each front-end will send the list of the installations it wants to access
|
||||
//If this is a new key (installation id), initialize the list for this key and then add the websocket object for this client
|
||||
//Then, report the status of each requested installation to the front-end that created the websocket connection
|
||||
foreach (var installationId in installationIds)
|
||||
{
|
||||
var installation = Db.GetInstallationById(installationId);
|
||||
if (!InstallationConnections.ContainsKey(installationId))
|
||||
{
|
||||
//Since we keep all the changes to the database, in case that the backend reboots, we need to update the in-memory data structure.
|
||||
//Thus, if the status is -1, we put an old timestamp, otherwise, we put the most recent timestamp.
|
||||
//We store everything to the database, because when the backend reboots, we do not want to wait until all the installations send the heartbit messages.
|
||||
//We want the in memory data structure to be up to date immediately.
|
||||
Console.WriteLine("Create new empty list for installation id " + installationId);
|
||||
InstallationConnections[installationId] = new InstallationInfo
|
||||
{
|
||||
Status = installation.Status,
|
||||
Timestamp = installation.Status==(int)StatusType.Offline ? DateTime.Now.AddDays(-1) : DateTime.Now,
|
||||
Product = installation.Product
|
||||
Status = -1
|
||||
};
|
||||
}
|
||||
|
||||
InstallationConnections[installationId].Connections.Add(currentWebSocket);
|
||||
|
||||
var jsonObject = new WebsocketMessage
|
||||
var jsonObject = new
|
||||
{
|
||||
id = installationId,
|
||||
status = InstallationConnections[installationId].Status,
|
||||
testingMode = installation.TestingMode
|
||||
status = InstallationConnections[installationId].Status
|
||||
};
|
||||
|
||||
dataToSend.Add(jsonObject);
|
||||
|
||||
}
|
||||
var jsonString = JsonSerializer.Serialize(dataToSend);
|
||||
var encodedDataToSend = Encoding.UTF8.GetBytes(jsonString);
|
||||
var jsonString = JsonSerializer.Serialize(jsonObject);
|
||||
var dataToSend = Encoding.UTF8.GetBytes(jsonString);
|
||||
|
||||
|
||||
currentWebSocket.SendAsync(encodedDataToSend,
|
||||
currentWebSocket.SendAsync(dataToSend,
|
||||
WebSocketMessageType.Text,
|
||||
true, // Indicates that this is the end of the message
|
||||
CancellationToken.None
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
// 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))
|
||||
|
|
@ -191,18 +298,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)
|
||||
{
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,5 +1 @@
|
|||
#To deploy to the monitor server, uncomment the following line
|
||||
dotnet publish Backend.csproj -c Release -r linux-x64 --self-contained true -p:PublishTrimmed=false && rsync -av bin/Release/net6.0/linux-x64/publish/ ubuntu@194.182.190.208:~/backend && ssh ubuntu@194.182.190.208 'sudo systemctl restart backend'
|
||||
|
||||
#To deploy to the stage server, uncomment the following line
|
||||
#dotnet publish Backend.csproj -c Release -r linux-x64 --self-contained true -p:PublishTrimmed=false && rsync -av bin/Release/net6.0/linux-x64/publish/ ubuntu@91.92.154.141:~/backend && ssh ubuntu@91.92.154.141 'sudo systemctl restart backend'
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
dotnet publish Backend.csproj -c Release -r linux-x64 --self-contained true -p:PublishTrimmed=false && rsync -av bin/Release/net6.0/linux-x64/publish/ ubuntu@91.92.154.141:~/backend && ssh ubuntu@91.92.154.141 'sudo systemctl restart backend'
|
||||
Binary file not shown.
|
|
@ -1,22 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>InnovEnergy.App.DeligreenBatteryCommunication</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<Import Project="../InnovEnergy.App.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="../../Lib/Devices/BatteryDeligreen/BatteryDeligreen.csproj" />
|
||||
<ProjectReference Include="..\..\Lib\Devices\Amax5070\Amax5070.csproj" />
|
||||
<ProjectReference Include="..\..\Lib\Protocols\Modbus\Modbus.csproj" />
|
||||
<ProjectReference Include="..\..\Lib\Units\Units.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.1.39" />
|
||||
<PackageReference Include="System.IO.Ports" Version="7.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
namespace InnovEnergy.App.DeligreenBatteryCommunication;
|
||||
|
||||
public enum DeviceState
|
||||
{
|
||||
Disabled,
|
||||
Measured,
|
||||
Computed
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
using System.Net;
|
||||
using InnovEnergy.Lib.Devices.BatteryDeligreen;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using InnovEnergy.Lib.Utils.Net;
|
||||
|
||||
namespace InnovEnergy.App.DeligreenBatteryCommunication;
|
||||
|
||||
internal static class Program
|
||||
{
|
||||
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(2);
|
||||
|
||||
private static Channel _relaysChannel;
|
||||
|
||||
private const String Port = "/dev/ttyUSB0";
|
||||
|
||||
|
||||
static Program()
|
||||
{
|
||||
Console.WriteLine("Hello, Deligreen World!");
|
||||
|
||||
// BatteriesChannel = new SerialPortChannel(Port, BaudRate, Parity, DataBits, StopBits);
|
||||
|
||||
}
|
||||
|
||||
public static async Task Main(String[] args)
|
||||
{
|
||||
/*var device1 = new SalimaxDevice { Host = "10.0.1.3" , Port = 502, DeviceState = DeviceState.Measured };
|
||||
_relaysChannel = CreateChannel(device1);
|
||||
var saliMaxRelaysDevice = new RelaysDeviceAmax(_relaysChannel);*/
|
||||
|
||||
var listOfBatteries = new List<BatteryDeligreenDevice>
|
||||
{
|
||||
new BatteryDeligreenDevice(Port, 0),
|
||||
new BatteryDeligreenDevice(Port, 1)
|
||||
};
|
||||
|
||||
var batteryDevices = new BatteryDeligreenDevices(listOfBatteries);
|
||||
|
||||
Console.WriteLine("Starting Battery Communication");
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var startTime = DateTime.Now;
|
||||
Console.WriteLine("***************************** Reading Battery Data *********************************************");
|
||||
Console.WriteLine($"Start Reading all Batteries: {startTime}");
|
||||
var batteriesRecord = batteryDevices.Read();
|
||||
var stopTime = DateTime.Now;
|
||||
Console.WriteLine($"Finish Reading all Batteries: {stopTime}");
|
||||
|
||||
|
||||
|
||||
Console.WriteLine("Time used for reading all batteries:" + (stopTime - startTime));
|
||||
|
||||
Console.WriteLine("Average SOC " + batteriesRecord?.Soc);
|
||||
/* Console.WriteLine("Cell Alarm 1 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellAlarmList[0]);
|
||||
Console.WriteLine("Cell Alarm 2 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellAlarmList[1]);
|
||||
|
||||
Console.WriteLine("Cell Temperature Alarm 1 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellTemperatureAlarm[0]);
|
||||
Console.WriteLine("Cell Temperature Alarm 2 : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CellTemperatureAlarm[1]);
|
||||
|
||||
Console.WriteLine("Battery 1 EnviTemp Alarm: " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.EnviTempAlarm);
|
||||
Console.WriteLine("Battery 1 Current Alarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.CurrentAlarm);
|
||||
|
||||
Console.WriteLine("Battery 2 EnviTemp Alarm: " + batteriesRecord?.Devices[1].BatteryDeligreenAlarmRecord.EnviTempAlarm);
|
||||
Console.WriteLine("Battery 2 Current Alarm : " + batteriesRecord?.Devices[1].BatteryDeligreenAlarmRecord.CurrentAlarm);
|
||||
|
||||
Console.WriteLine("TotalVoltage Alarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.TotalVoltageAlarm);
|
||||
Console.WriteLine("PowerTemp Alarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.PowerTempAlarm);*/
|
||||
|
||||
Console.WriteLine("CellVoltageDropoutFault : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.AlarmEvent1.CellVoltageDropoutFault);
|
||||
Console.WriteLine("HighVoltageAlarmForTotalVoltage : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.AlarmEvent2.HighVoltageAlarmForTotalVoltage);
|
||||
Console.WriteLine("ChargeHighTemperatureAlarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.AlarmEvent3.ChargeHighTemperatureAlarm);
|
||||
Console.WriteLine("CellLowTemperatureHeating : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.AlarmEvent4.CellLowTemperatureHeating);
|
||||
Console.WriteLine("ChargeOverCurrentAlarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.AlarmEvent5.ChargeOverCurrentAlarm);
|
||||
Console.WriteLine("ResidualCapacityAlarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.AlarmEvent6.ResidualCapacityAlarm);
|
||||
Console.WriteLine("ManualChargingWaiting : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.AlarmEvent7.ManualChargingWaiting);
|
||||
Console.WriteLine("CurrentCalibrationNotPerformed : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.AlarmEvent8.CurrentCalibrationNotPerformed);
|
||||
Console.WriteLine("ChargeHighTemperatureAlarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.DisconnectionState1.Cell01Disconnection);
|
||||
Console.WriteLine("CellLowTemperatureHeating : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.DisconnectionState2.Cell10Disconnection);
|
||||
Console.WriteLine("ChargeOverCurrentAlarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.EquilibriumState1.Cell01Equilibrium);
|
||||
Console.WriteLine("ResidualCapacityAlarm : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.EquilibriumState2.Cell09Equilibrium);
|
||||
Console.WriteLine("ManualChargingWaiting : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.OnOffState.ChargeSwitchState);
|
||||
Console.WriteLine("CurrentCalibrationNotPerformed : " + batteriesRecord?.Devices[0].BatteryDeligreenAlarmRecord.SystemState.Charge);
|
||||
|
||||
// Wait for 2 seconds before the next reading
|
||||
await Task.Delay(2000); // Delay in milliseconds (2000ms = 2 seconds)
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Handle exception and print the error
|
||||
Console.WriteLine(e + " This the first try loop ");
|
||||
await Task.Delay(2000); // Delay in milliseconds (2000ms = 2 seconds)
|
||||
}
|
||||
}
|
||||
|
||||
Channel CreateChannel(SalimaxDevice device) => new TcpChannel(device);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
using InnovEnergy.Lib.Devices.Amax5070;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
namespace InnovEnergy.App.DeligreenBatteryCommunication;
|
||||
|
||||
public class RelaysDeviceAmax
|
||||
{
|
||||
private Amax5070Device AmaxDevice { get; }
|
||||
|
||||
public RelaysDeviceAmax(Channel channel) => AmaxDevice = new Amax5070Device(channel);
|
||||
|
||||
public RelaysRecordAmax? Read()
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
return AmaxDevice.Read();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
$"Failed to read from {nameof(RelaysDeviceAmax)}\n{e}".WriteLine();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Write(RelaysRecordAmax r)
|
||||
{
|
||||
try
|
||||
{
|
||||
AmaxDevice.Write(r);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
$"Failed to write to {nameof(RelaysDeviceAmax)}\n{e}".WriteLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,98 +0,0 @@
|
|||
using InnovEnergy.Lib.Devices.Amax5070;
|
||||
|
||||
namespace InnovEnergy.App.DeligreenBatteryCommunication;
|
||||
|
||||
public class RelaysRecordAmax
|
||||
{
|
||||
private readonly Amax5070Registers _regs;
|
||||
|
||||
private RelaysRecordAmax(Amax5070Registers regs) => _regs = regs;
|
||||
|
||||
//public UInt16 K0Input
|
||||
//{
|
||||
// get => _regs.DigitalInput;
|
||||
//}
|
||||
/*
|
||||
|
||||
public Boolean K0
|
||||
{
|
||||
get => _regs.DigitalOutput0;
|
||||
set => _regs.DigitalOutput0 = value;
|
||||
}
|
||||
|
||||
public Boolean K1
|
||||
{
|
||||
get => _regs.DigitalOutput1;
|
||||
set => _regs.DigitalOutput1 = value;
|
||||
}
|
||||
|
||||
public Boolean K2
|
||||
{
|
||||
get => _regs.DigitalOutput2;
|
||||
set => _regs.DigitalOutput2 = value;
|
||||
}
|
||||
|
||||
public Boolean K3
|
||||
{
|
||||
get => _regs.DigitalOutput3;
|
||||
set => _regs.DigitalOutput3 = value;
|
||||
}
|
||||
|
||||
|
||||
public Boolean R0
|
||||
{
|
||||
get => _regs.Relay12;
|
||||
set => _regs.Relay12 = value;
|
||||
}
|
||||
|
||||
public Boolean R1
|
||||
{
|
||||
get => _regs.Relay22;
|
||||
set => _regs.Relay22 = value;
|
||||
}
|
||||
|
||||
public Boolean R2
|
||||
{
|
||||
get => _regs.Relay32;
|
||||
set => _regs.Relay32 = value;
|
||||
}
|
||||
|
||||
public Boolean R3
|
||||
{
|
||||
get => _regs.Relay42;
|
||||
set => _regs.Relay42 = value;
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
public Boolean K1GridBusIsConnectedToGrid => _regs.DigitalInput22;
|
||||
public Boolean K2IslandBusIsConnectedToGridBus => !_regs.DigitalInput20;
|
||||
public IEnumerable<Boolean> K3InverterIsConnectedToIslandBus
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return K3Inverter1IsConnectedToIslandBus;
|
||||
yield return K3Inverter2IsConnectedToIslandBus;
|
||||
yield return K3Inverter3IsConnectedToIslandBus;
|
||||
yield return K3Inverter4IsConnectedToIslandBus;
|
||||
}
|
||||
}
|
||||
|
||||
private Boolean K3Inverter1IsConnectedToIslandBus => !_regs.DigitalInput16;
|
||||
private Boolean K3Inverter2IsConnectedToIslandBus => !_regs.DigitalInput17;
|
||||
private Boolean K3Inverter3IsConnectedToIslandBus => !_regs.DigitalInput18;
|
||||
private Boolean K3Inverter4IsConnectedToIslandBus => !_regs.DigitalInput19;
|
||||
|
||||
public Boolean FiWarning => !_regs.DigitalInput21;
|
||||
public Boolean FiError => !_regs.DigitalInput23;*/
|
||||
|
||||
//public Boolean K2ConnectIslandBusToGridBus
|
||||
//{
|
||||
// get => _regs.Relay22;
|
||||
// set => _regs.Relay22 = value;
|
||||
//}
|
||||
|
||||
|
||||
public static implicit operator Amax5070Registers(RelaysRecordAmax d) => d._regs;
|
||||
public static implicit operator RelaysRecordAmax(Amax5070Registers d) => new RelaysRecordAmax(d);
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
using InnovEnergy.Lib.Utils.Net;
|
||||
|
||||
namespace InnovEnergy.App.DeligreenBatteryCommunication;
|
||||
|
||||
public class SalimaxDevice : Ip4Address
|
||||
{
|
||||
public required DeviceState DeviceState { get; init; }
|
||||
|
||||
public override String ToString() => $"{base.ToString()} ({DeviceState})";
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), DeviceState);
|
||||
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
dotnet_version='net6.0'
|
||||
salimax_ip="$1"
|
||||
username='ie-entwicklung'
|
||||
root_password='Salimax4x25'
|
||||
set -e
|
||||
|
||||
echo -e "\n============================ Build ============================\n"
|
||||
|
||||
dotnet publish \
|
||||
./DeligreenBatteryCommunication.csproj \
|
||||
-p:PublishTrimmed=false \
|
||||
-c Release \
|
||||
-r linux-x64
|
||||
|
||||
echo -e "\n============================ Deploy ============================\n"
|
||||
|
||||
rsync -v \
|
||||
--exclude '*.pdb' \
|
||||
./bin/Release/$dotnet_version/linux-x64/publish/* \
|
||||
$username@"$salimax_ip":~/salimax
|
||||
|
|
@ -28,12 +28,12 @@ public static class Config
|
|||
new(s => s.Ac.L1.Voltage, "/Ac/L1/Voltage", "0.0 A"),
|
||||
new(s => s.Ac.L2.Voltage, "/Ac/L2/Voltage", "0.0 A"),
|
||||
new(s => s.Ac.L3.Voltage, "/Ac/L3/Voltage", "0.0 A"),
|
||||
new(s => (s.Ac.L1.Voltage + s.Ac.L2.Voltage + s.Ac.L3.Voltage) / 3, "/Ac/Voltage", "0.0 A"),
|
||||
new(s => (s.Ac.L1.Voltage + s.Ac.L2.Voltage + s.Ac.L3.Voltage) / 3.0m, "/Ac/Voltage", "0.0 A"),
|
||||
|
||||
new(s => s.Ac.L1.Power.Active, "/Ac/L1/Power", "0 W"),
|
||||
new(s => s.Ac.L2.Power.Active, "/Ac/L2/Power", "0 W"),
|
||||
new(s => s.Ac.L3.Power.Active, "/Ac/L3/Power", "0 W"),
|
||||
new(s => s.Ac.Power.Active, "/Ac/Power", "0 W"),
|
||||
new(s => s.Ac.L1.ActivePower, "/Ac/L1/Power", "0 W"),
|
||||
new(s => s.Ac.L2.ActivePower, "/Ac/L2/Power", "0 W"),
|
||||
new(s => s.Ac.L3.ActivePower, "/Ac/L3/Power", "0 W"),
|
||||
new(s => s.Ac.ActivePower, "/Ac/Power", "0 W"),
|
||||
|
||||
// new(s => s.EnergyImportL123, "Ac/Energy/Forward", "0.00 kWh"),
|
||||
// new(s => s.EnergyExportL123, "Ac/Energy/Reverse", "0.00 kWh"),
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ public static class EmuMeterDriver
|
|||
|
||||
var meterStatus = Observable
|
||||
.Interval(Config.UpdatePeriod)
|
||||
.Select(_ => emuMeter.Read())
|
||||
.Select(_ => emuMeter.ReadStatus())
|
||||
.Publish();
|
||||
|
||||
var poller = meterStatus.Connect();
|
||||
|
|
|
|||
|
|
@ -6,9 +6,9 @@ namespace InnovEnergy.App.EmuMeterDriver;
|
|||
|
||||
|
||||
// TODO: Does not compile
|
||||
public record Signal(Func<EmuMeterRegisters, Object> Source, ObjectPath Path, String Format = "")
|
||||
public record Signal(Func<EmuMeterStatus, Object> Source, ObjectPath Path, String Format = "")
|
||||
{
|
||||
public VeProperty ToVeProperty(EmuMeterRegisters status)
|
||||
public VeProperty ToVeProperty(EmuMeterStatus status)
|
||||
{
|
||||
var value = Source(status);
|
||||
return new VeProperty(Path, value, String.Format($"{{0:{Format}}}", value));
|
||||
|
|
|
|||
|
|
@ -1,61 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
|
||||
csproj="SchneiderMeterDriver.csproj"
|
||||
exe="SchneiderMeterDriver"
|
||||
remote="10.2.4.114"
|
||||
platform="linux-arm"
|
||||
netVersion="net6.0"
|
||||
config="Release"
|
||||
host="root@$remote"
|
||||
dir="/opt/innovenergy/$exe"
|
||||
log_dir="/var/log/SchneiderMeterDriver"
|
||||
|
||||
set -e
|
||||
|
||||
# Publish the project locally
|
||||
dotnet publish "$csproj" -c $config -r $platform -p:SuppressTrimmAnalysisWarnings=true -p:PublishSingleFile=true -p:PublishTrimmed=true -p:DebugType=None -p:DebugSymbols=false --self-contained true
|
||||
|
||||
# Sync the published files to the remote server
|
||||
rsync -av "bin/$config/$netVersion/$platform/publish/" "$host:$dir"
|
||||
|
||||
# Execute commands on the remote server
|
||||
ssh "$host" << 'EOF'
|
||||
set -e
|
||||
|
||||
# Remount the root filesystem with read and write permissions
|
||||
mount -o remount,rw /
|
||||
|
||||
# Create service and log directories
|
||||
mkdir -p /opt/innovenergy/SchneiderMeterDriver/service
|
||||
mkdir -p /opt/innovenergy/SchneiderMeterDriver/service/log
|
||||
mkdir -p /var/log/SchneiderMeterDriver
|
||||
|
||||
# Create the service run script
|
||||
cat << 'EOL' > /opt/innovenergy/SchneiderMeterDriver/service/run
|
||||
#!/bin/sh
|
||||
exec 2>&1
|
||||
exec softlimit -d 200000000 -s 2000000 -a 200000000 /opt/innovenergy/SchneiderMeterDriver/SchniederDriver
|
||||
EOL
|
||||
chmod +x /opt/innovenergy/SchneiderMeterDriver/service/run
|
||||
|
||||
# Create the log run script
|
||||
cat << 'EOL' > /opt/innovenergy/SchneiderMeterDriver/service/log/run
|
||||
#!/bin/sh
|
||||
exec 2>&1
|
||||
exec multilog t s25000 n4 /var/log/SchneiderMeterDriver
|
||||
EOL
|
||||
chmod +x /opt/innovenergy/SchneiderMeterDriver/service/log/run
|
||||
|
||||
# Create the symbolic link for the service
|
||||
ln -sf /opt/innovenergy/SchneiderMeterDriver/service /service/SchneiderMeterDriver
|
||||
|
||||
# Wait a bit for the symlink to be recognized
|
||||
|
||||
sleep 2
|
||||
|
||||
# Start the service
|
||||
start SchneiderMeterDriver
|
||||
EOF
|
||||
|
||||
echo "Deployment and service setup completed successfully."
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
using System.Text;
|
||||
|
||||
namespace InnovEnergy.App.GrowattCommunication.DataLogging;
|
||||
|
||||
public class LogFileConcatenator
|
||||
{
|
||||
private readonly String _LogDirectory;
|
||||
|
||||
public LogFileConcatenator(String logDirectory = "JsonLogDirectory/")
|
||||
{
|
||||
_LogDirectory = logDirectory;
|
||||
}
|
||||
|
||||
public String ConcatenateFiles(int numberOfFiles)
|
||||
{
|
||||
var logFiles = Directory
|
||||
.GetFiles(_LogDirectory, "log_*.json")
|
||||
.OrderByDescending(file => file)
|
||||
.Take(numberOfFiles)
|
||||
.OrderBy(file => file)
|
||||
.ToList();
|
||||
|
||||
var concatenatedContent = new StringBuilder();
|
||||
|
||||
foreach (var fileContent in logFiles.Select(File.ReadAllText))
|
||||
{
|
||||
concatenatedContent.AppendLine(fileContent);
|
||||
//concatenatedContent.AppendLine(); // Append an empty line to separate the files // maybe we don't need this
|
||||
}
|
||||
|
||||
return concatenatedContent.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
using InnovEnergy.Lib.Utils;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace InnovEnergy.App.GrowattCommunication.DataLogging;
|
||||
|
||||
public class CustomLogger : ILogger
|
||||
{
|
||||
private readonly String _LogFilePath;
|
||||
//private readonly Int64 _maxFileSizeBytes;
|
||||
private readonly Int32 _MaxLogFileCount;
|
||||
private Int64 _CurrentFileSizeBytes;
|
||||
|
||||
public CustomLogger(String logFilePath, Int32 maxLogFileCount)
|
||||
{
|
||||
_LogFilePath = logFilePath;
|
||||
_MaxLogFileCount = maxLogFileCount;
|
||||
_CurrentFileSizeBytes = File.Exists(logFilePath) ? new FileInfo(logFilePath).Length : 0;
|
||||
}
|
||||
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => throw new NotImplementedException();
|
||||
|
||||
public Boolean IsEnabled(LogLevel logLevel) => true; // Enable logging for all levels
|
||||
|
||||
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception, String> formatter)
|
||||
{
|
||||
var logMessage = formatter(state, exception!);
|
||||
|
||||
// Check the log file count and delete the oldest file if necessary
|
||||
var logFileDir = Path.GetDirectoryName(_LogFilePath)!;
|
||||
var logFileExt = Path.GetExtension(_LogFilePath);
|
||||
var logFileBaseName = Path.GetFileNameWithoutExtension(_LogFilePath);
|
||||
|
||||
var logFiles = Directory
|
||||
.GetFiles(logFileDir, $"{logFileBaseName}_*{logFileExt}")
|
||||
.OrderBy(file => file)
|
||||
.ToList();
|
||||
|
||||
if (logFiles.Count >= _MaxLogFileCount)
|
||||
{
|
||||
File.Delete(logFiles.First());
|
||||
}
|
||||
|
||||
var roundedUnixTimestamp = DateTime.Now.ToUnixTime() % 2 == 0 ? DateTime.Now.ToUnixTime() : DateTime.Now.ToUnixTime() + 1;
|
||||
var timestamp = "Timestamp;" + roundedUnixTimestamp + Environment.NewLine;
|
||||
|
||||
var logFileBackupPath = Path.Combine(logFileDir, $"{logFileBaseName}_{DateTime.Now.ToUnixTime()}{logFileExt}");
|
||||
File.AppendAllText(logFileBackupPath, timestamp + logMessage + Environment.NewLine);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace InnovEnergy.App.GrowattCommunication.DataLogging;
|
||||
|
||||
public static class Logger
|
||||
{
|
||||
// Specify the maximum log file size in bytes (e.g., 1 MB)
|
||||
|
||||
//private const Int32 MaxFileSizeBytes = 2024 * 30; // TODO: move to settings
|
||||
private const Int32 MaxLogFileCount = 5000; // TODO: move to settings
|
||||
private const String LogFilePath = "JsonLogDirectory/log.json"; // TODO: move to settings
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static readonly ILogger _logger = new CustomLogger(LogFilePath, MaxLogFileCount);
|
||||
|
||||
public static T LogInfo<T>(this T t) where T : notnull
|
||||
{
|
||||
_logger.LogInformation(t.ToString()); // TODO: check warning
|
||||
return t;
|
||||
}
|
||||
|
||||
public static T LogDebug<T>(this T t) where T : notnull
|
||||
{
|
||||
// _logger.LogDebug(t.ToString()); // TODO: check warning
|
||||
return t;
|
||||
}
|
||||
|
||||
public static T LogError<T>(this T t) where T : notnull
|
||||
{
|
||||
// _logger.LogError(t.ToString()); // TODO: check warning
|
||||
return t;
|
||||
}
|
||||
|
||||
public static T LogWarning<T>(this T t) where T : notnull
|
||||
{
|
||||
// _logger.LogWarning(t.ToString()); // TODO: check warning
|
||||
return t;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
namespace InnovEnergy.App.GrowattCommunication.DataTypes;
|
||||
|
||||
public class AlarmOrWarning
|
||||
{
|
||||
public String? Date { get; set; }
|
||||
public String? Time { get; set; }
|
||||
public String? Description { get; set; }
|
||||
public String? CreatedBy { get; set; }
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
|
||||
using InnovEnergy.App.GrowattCommunication.ESS;
|
||||
|
||||
namespace InnovEnergy.App.GrowattCommunication.DataTypes;
|
||||
|
||||
public class Configuration
|
||||
{
|
||||
public Double MinimumSoC { get; set; }
|
||||
public Double GridSetPoint { get; set; }
|
||||
public Double MaximumDischargingCurrent { get; set; }
|
||||
public Double MaximumChargingCurrent { get; set; }
|
||||
public EssMode OperatingPriority { get; set; }
|
||||
public required Int16 BatteriesCount { get; set; }
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
namespace InnovEnergy.App.GrowattCommunication.DataTypes;
|
||||
|
||||
public enum SodistoreAlarmState
|
||||
{
|
||||
Green,
|
||||
Orange,
|
||||
Red
|
||||
}
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
namespace InnovEnergy.App.GrowattCommunication.DataTypes;
|
||||
|
||||
public class StatusMessage
|
||||
{
|
||||
public required Int32 InstallationId { get; set; }
|
||||
public required Int32 Product { get; set; }
|
||||
public required SodistoreAlarmState Status { get; set; }
|
||||
public required MessageType Type { get; set; }
|
||||
public List<AlarmOrWarning>? Warnings { get; set; }
|
||||
public List<AlarmOrWarning>? Alarms { get; set; }
|
||||
}
|
||||
|
||||
public enum MessageType
|
||||
{
|
||||
AlarmOrWarning,
|
||||
Heartbit
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
namespace InnovEnergy.App.GrowattCommunication.ESS;
|
||||
|
||||
public enum EssMode
|
||||
{
|
||||
LoadPriority,
|
||||
BatteryPriority,
|
||||
GridPriority,
|
||||
Off
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
using InnovEnergy.App.GrowattCommunication.SystemConfig;
|
||||
using InnovEnergy.Lib.Devices.WITGrowatt4_15K;
|
||||
|
||||
namespace InnovEnergy.App.GrowattCommunication.ESS;
|
||||
|
||||
public record StatusRecord
|
||||
{
|
||||
public required WITGrowatRecord AcDcGrowatt { get; set; }
|
||||
public required Config Config { get; set; }
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>InnovEnergy.App.GrowattCommunication</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="../InnovEnergy.App.props" />
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Lib\Devices\WITGrowatt4-15K\WITGrowatt4-15K.csproj" />
|
||||
<ProjectReference Include="..\..\Lib\Protocols\Modbus\Modbus.csproj" />
|
||||
<ProjectReference Include="..\..\Lib\Units\Units.csproj" />
|
||||
<ProjectReference Include="..\..\Lib\Utils\Utils.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Flurl.Http" Version="4.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="7.0.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.2" />
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,88 +0,0 @@
|
|||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using InnovEnergy.App.GrowattCommunication.DataTypes;
|
||||
|
||||
namespace InnovEnergy.App.GrowattCommunication.MiddlewareClasses;
|
||||
|
||||
public static class MiddlewareAgent
|
||||
{
|
||||
private static UdpClient _udpListener = null!;
|
||||
private static IPAddress? _controllerIpAddress;
|
||||
private static EndPoint? _endPoint;
|
||||
|
||||
public static void InitializeCommunicationToMiddleware()
|
||||
{
|
||||
_controllerIpAddress = FindVpnIp();
|
||||
if (Equals(IPAddress.None, _controllerIpAddress))
|
||||
{
|
||||
Console.WriteLine("There is no VPN interface, exiting...");
|
||||
}
|
||||
|
||||
const Int32 udpPort = 9000;
|
||||
_endPoint = new IPEndPoint(_controllerIpAddress, udpPort);
|
||||
|
||||
_udpListener = new UdpClient();
|
||||
_udpListener.Client.Blocking = false;
|
||||
_udpListener.Client.Bind(_endPoint);
|
||||
}
|
||||
|
||||
private static IPAddress FindVpnIp()
|
||||
{
|
||||
const String interfaceName = "innovenergy";
|
||||
|
||||
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||
|
||||
foreach (var networkInterface in networkInterfaces)
|
||||
{
|
||||
if (networkInterface.Name == interfaceName)
|
||||
{
|
||||
var ipProps = networkInterface.GetIPProperties();
|
||||
var uniCastIPs = ipProps.UnicastAddresses;
|
||||
var controllerIpAddress = uniCastIPs[0].Address;
|
||||
|
||||
Console.WriteLine("VPN IP is: "+ uniCastIPs[0].Address);
|
||||
return controllerIpAddress;
|
||||
}
|
||||
}
|
||||
|
||||
return IPAddress.None;
|
||||
}
|
||||
|
||||
public static Configuration? SetConfigurationFile()
|
||||
{
|
||||
if (_udpListener.Available > 0)
|
||||
{
|
||||
IPEndPoint? serverEndpoint = null;
|
||||
|
||||
var replyMessage = "ACK";
|
||||
var replyData = Encoding.UTF8.GetBytes(replyMessage);
|
||||
|
||||
var udpMessage = _udpListener.Receive(ref serverEndpoint);
|
||||
var message = Encoding.UTF8.GetString(udpMessage);
|
||||
|
||||
var config = JsonSerializer.Deserialize<Configuration>(message);
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
Console.WriteLine($"Received a configuration message: GridSetPoint is " + config.GridSetPoint +
|
||||
", MinimumSoC is " + config.MinimumSoC + " and operating priorty is " +config.OperatingPriority + "Number of batteries is " + config.BatteriesCount );
|
||||
|
||||
// Send the reply to the sender's endpoint
|
||||
_udpListener.Send(replyData, replyData.Length, serverEndpoint);
|
||||
Console.WriteLine($"Replied to {serverEndpoint}: {replyMessage}");
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
if (_endPoint != null && !_endPoint.Equals(_udpListener.Client.LocalEndPoint as IPEndPoint))
|
||||
{
|
||||
Console.WriteLine("UDP address has changed, rebinding...");
|
||||
InitializeCommunicationToMiddleware();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using InnovEnergy.App.GrowattCommunication.DataTypes;
|
||||
using RabbitMQ.Client;
|
||||
|
||||
namespace InnovEnergy.App.GrowattCommunication.MiddlewareClasses;
|
||||
|
||||
public static class RabbitMqManager
|
||||
{
|
||||
private static ConnectionFactory? _factory ;
|
||||
private static IConnection ? _connection;
|
||||
private static IModel? _channel;
|
||||
|
||||
public static Boolean SubscribeToQueue(StatusMessage currentSalimaxState, String? s3Bucket,String VpnServerIp)
|
||||
{
|
||||
try
|
||||
{
|
||||
//_factory = new ConnectionFactory { HostName = VpnServerIp };
|
||||
|
||||
_factory = new ConnectionFactory
|
||||
{
|
||||
HostName = VpnServerIp,
|
||||
Port = 5672,
|
||||
VirtualHost = "/",
|
||||
UserName = "producer",
|
||||
Password = "b187ceaddb54d5485063ddc1d41af66f",
|
||||
|
||||
};
|
||||
|
||||
_connection = _factory.CreateConnection();
|
||||
_channel = _connection.CreateModel();
|
||||
_channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
|
||||
|
||||
Console.WriteLine("The controller sends its status to the middleware for the first time");
|
||||
if (s3Bucket != null) InformMiddleware(currentSalimaxState);
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("An error occurred while connecting to the RabbitMQ queue: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void InformMiddleware(StatusMessage status)
|
||||
{
|
||||
var message = JsonSerializer.Serialize(status);
|
||||
var body = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
_channel.BasicPublish(exchange: string.Empty,
|
||||
routingKey: "statusQueue",
|
||||
basicProperties: null,
|
||||
body: body);
|
||||
|
||||
Console.WriteLine($"Producer sent message: {message}");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,708 +0,0 @@
|
|||
using System.Diagnostics;
|
||||
using System.IO.Compression;
|
||||
using System.IO.Ports;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
using Flurl.Http;
|
||||
|
||||
using System.Reactive.Linq;
|
||||
using System.Reactive.Threading.Tasks;
|
||||
|
||||
using InnovEnergy.Lib.Devices.WITGrowatt4_15K;
|
||||
using InnovEnergy.App.GrowattCommunication.DataLogging;
|
||||
using InnovEnergy.App.GrowattCommunication.ESS;
|
||||
using InnovEnergy.App.GrowattCommunication.MiddlewareClasses;
|
||||
using InnovEnergy.App.GrowattCommunication.SystemConfig;
|
||||
using InnovEnergy.Lib.Protocols.Modbus.Channels;
|
||||
using InnovEnergy.App.GrowattCommunication.DataTypes;
|
||||
|
||||
using InnovEnergy.Lib.Units;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
using Newtonsoft.Json;
|
||||
using Formatting = Newtonsoft.Json.Formatting;
|
||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
||||
using InnovEnergy.Lib.Devices.WITGrowatt4_15K.DataType;
|
||||
using static InnovEnergy.App.GrowattCommunication.MiddlewareClasses.MiddlewareAgent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
|
||||
|
||||
namespace InnovEnergy.App.GrowattCommunication;
|
||||
|
||||
public static class Program
|
||||
{
|
||||
private static readonly TimeSpan UpdateInterval = TimeSpan.FromSeconds(5);
|
||||
private const UInt16 NbrOfFileToConcatenate = 15; // add this to config file
|
||||
private static UInt16 _fileCounter = 0;
|
||||
//
|
||||
private static Channel _growattChannel;
|
||||
|
||||
private const String SwVersionNumber =" V1.00.310725 beta";
|
||||
private const String VpnServerIp = "10.2.0.11";
|
||||
private static Boolean _subscribedToQueue = false;
|
||||
private static Boolean _subscribeToQueueForTheFirstTime = false;
|
||||
private static Int32 _failsCounter = 0; // move to a config file
|
||||
private static SodistoreAlarmState _prevSodiohomeAlarmState = SodistoreAlarmState.Green;
|
||||
private static SodistoreAlarmState _sodiohomeAlarmState = SodistoreAlarmState.Green;
|
||||
|
||||
|
||||
// move all this to config file
|
||||
private const String Port = "/dev/ttyUSB0";
|
||||
private const Byte SlaveId = 1;
|
||||
private const Parity Parity = 0; //none
|
||||
private const Int32 StopBits = 1;
|
||||
private const Int32 BaudRate = 9600;
|
||||
private const Int32 DataBits = 8;
|
||||
|
||||
[UnconditionalSuppressMessage("Trimming",
|
||||
"IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code",
|
||||
Justification = "<Pending>")]
|
||||
public static async Task Main(String[] args)
|
||||
{
|
||||
_growattChannel = new SerialPortChannel(Port, BaudRate, Parity, DataBits, StopBits);
|
||||
|
||||
InitializeCommunicationToMiddleware();
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
await Run();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
e.LogError();
|
||||
}
|
||||
}
|
||||
// ReSharper disable once FunctionNeverReturns
|
||||
}
|
||||
|
||||
[RequiresUnreferencedCode("Calls InnovEnergy.App.GrowattCommunication.Program.SaveModbusTcpFile(StatusRecord)")]
|
||||
private static async Task Run()
|
||||
{
|
||||
Watchdog.NotifyReady();
|
||||
|
||||
Console.WriteLine("Starting Growatt Communication");
|
||||
|
||||
var growattDeviceT415K = new WITGrowatDevice(_growattChannel, SlaveId);
|
||||
// var growattDevices = new WITGrowattDevices(new List<WITGrowatDevice> { growattDeviceT415K });
|
||||
|
||||
|
||||
StatusRecord? ReadStatus()
|
||||
{
|
||||
var config = Config.Load();
|
||||
var growattRecord = growattDeviceT415K.Read();
|
||||
|
||||
return new StatusRecord
|
||||
{
|
||||
AcDcGrowatt = growattRecord,
|
||||
Config = config // load from disk every iteration, so config can be changed while running
|
||||
};
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
await Observable
|
||||
.Interval(UpdateInterval)
|
||||
.Select(_ => RunIteration())
|
||||
.SelectMany(status =>
|
||||
DataLogging(status, DateTime.Now.Round(UpdateInterval))
|
||||
.ContinueWith(_ => status)) // back to StatusRecord
|
||||
.SelectMany(SaveModbusTcpFile)
|
||||
.SelectError()
|
||||
.ToTask();
|
||||
}
|
||||
|
||||
StatusRecord? RunIteration()
|
||||
{
|
||||
try
|
||||
{
|
||||
Watchdog.NotifyAlive();
|
||||
|
||||
var startTime = DateTime.Now;
|
||||
Console.WriteLine("***************************** Reading Battery Data *********************************************");
|
||||
Console.WriteLine(startTime.ToString("HH:mm:ss.fff"));
|
||||
|
||||
// the order matter of the next three lines
|
||||
var statusrecord = ReadStatus();
|
||||
|
||||
SendSalimaxStateAlarm(GetSodiHomeStateAlarm(statusrecord),statusrecord);
|
||||
|
||||
//await DataLogging(statusrecord, timestamp); // save a csv file locally
|
||||
//await SaveModbusTcpFile(statusrecord); // save the JSON file for modbuscTCP
|
||||
|
||||
Debug.Assert(statusrecord != null, nameof(statusrecord) + " != null");
|
||||
statusrecord.AcDcGrowatt.RemotePowerControl.WriteLine(" = RemotePowerControl");
|
||||
statusrecord.AcDcGrowatt.EnableEmsCommunicationFailureTime.WriteLine(" = EnableEmsCommunicationFailureTime");
|
||||
|
||||
statusrecord.AcDcGrowatt.EnableCommand.WriteLine(" = EnableCommand");
|
||||
statusrecord.AcDcGrowatt.ControlPermession.WriteLine(" = ControlPermession");
|
||||
statusrecord.AcDcGrowatt.MeterPower.WriteLine(" MeterPower");
|
||||
statusrecord.AcDcGrowatt.ConsumptionPower.WriteLine(" ConsumptionPower");
|
||||
statusrecord.AcDcGrowatt.ExportedPowerToGridMeter.WriteLine(" ExportedPowerToGrid");
|
||||
statusrecord.AcDcGrowatt.ImportedPowerFromGrid.WriteLine(" ImportedPowerFromGrid");
|
||||
statusrecord.AcDcGrowatt.InverterActivePower.WriteLine(" InverterActivePower");
|
||||
statusrecord.AcDcGrowatt.BatteryChargeCutoffVoltage.WriteLine(" BatteryChargeCutoffVoltage "); //30409 we set power here
|
||||
statusrecord.AcDcGrowatt.BatteryDischargeCutoffVoltage.WriteLine(" BatteryDishargeCutoffVoltage "); //30409 we set power here
|
||||
statusrecord.AcDcGrowatt.PowerFactor.WriteLine(" = PowerFactor");
|
||||
statusrecord.AcDcGrowatt.Batteries[0].Soc.WriteLine(" SOC");
|
||||
statusrecord.AcDcGrowatt.Batteries[0].Power.WriteLine(" Battery Power");
|
||||
statusrecord.AcDcGrowatt.Batteries[0].Current.WriteLine(" Battery Current");
|
||||
statusrecord.AcDcGrowatt.Batteries[0].Voltage.WriteLine(" Battery Voltage");
|
||||
statusrecord.AcDcGrowatt.BatteryMaxChargeCurrent.WriteLine(" BatteryMaxChargeCurrent "); //30409 we set power here
|
||||
statusrecord.AcDcGrowatt.BatteryMaxdischargeCurrent.WriteLine(" BatteryMaxDischargeCurrent "); //30409 we set power here
|
||||
statusrecord.AcDcGrowatt.DischargeCutoffSoc.WriteLine(" DischargeCutoffSoc "); //30409 we set power here
|
||||
statusrecord.AcDcGrowatt.ChargeCutoffSoc.WriteLine(" ChargeCutoffSoc "); //30409 we set power here
|
||||
|
||||
statusrecord.AcDcGrowatt.TotalPvPower.WriteLine(" Pv Power "); //30409 we set power here
|
||||
|
||||
statusrecord.AcDcGrowatt.SystemOperatingMode.WriteLine(" = SystemOperatingMode");
|
||||
statusrecord.AcDcGrowatt.BatteryOperatingMode.WriteLine(" = BatteryOperatingMode");
|
||||
statusrecord.AcDcGrowatt.OperatingPriority.WriteLine(" = OperatingPriority"); // 30408 this the duration
|
||||
|
||||
statusrecord.AcDcGrowatt.FaultMainCode.WriteLine(" = FaultMainCode"); // 30408 this the duration
|
||||
statusrecord.AcDcGrowatt.FaultSubCode.WriteLine(" = FaultSubCode"); // 30408 this the duration
|
||||
statusrecord.AcDcGrowatt.WarningMainCode.WriteLine(" = WarningMainCode"); // 30408 this the duration
|
||||
statusrecord.AcDcGrowatt.WarningSubCode.WriteLine(" = WarningSubCode"); // 30408 this the duration
|
||||
|
||||
|
||||
|
||||
EssModeControl(statusrecord);
|
||||
|
||||
statusrecord.ApplyDefaultSettings();
|
||||
|
||||
Console.WriteLine( " ************************************ We are writing ************************************");
|
||||
statusrecord.Config.Save(); // save the config file
|
||||
growattDeviceT415K.Write(statusrecord.AcDcGrowatt);
|
||||
var stopTime = DateTime.Now;
|
||||
Console.WriteLine(stopTime.ToString("HH:mm:ss.fff"));
|
||||
return statusrecord;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// Handle exception and print the error
|
||||
Console.WriteLine(e );
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyConfigFile(this StatusRecord? status, Configuration? config)
|
||||
{
|
||||
if (config == null) return;
|
||||
if (status == null) return;
|
||||
|
||||
status.Config.MinSoc = config.MinimumSoC;
|
||||
status.Config.GridSetPoint = config.GridSetPoint * 1000; // converted from kW to W
|
||||
status.Config.MaximumChargingCurrent = config.MaximumChargingCurrent;
|
||||
status.Config.MaximumDischargingCurrent = config.MaximumDischargingCurrent;
|
||||
status.Config.OperatingPriority = config.OperatingPriority;
|
||||
status.Config.BatteriesCount = config.BatteriesCount;
|
||||
}
|
||||
|
||||
private static String EssModeControl(StatusRecord? statusrecord)
|
||||
{
|
||||
var mode = statusrecord?.Config.OperatingPriority;
|
||||
|
||||
switch (mode)
|
||||
{
|
||||
case EssMode.Off:
|
||||
return "no mode";
|
||||
case EssMode.GridPriority:
|
||||
statusrecord.AcDcGrowatt.RemotePowerControl = true;
|
||||
statusrecord.AcDcGrowatt.RemotePowerControlChargeDuration = 0; // 30408 this the duration
|
||||
//statusrecord.AcDcGrowatt.ActivePowerPercent = 50; // 30408 this the duration
|
||||
//statusrecord.AcDcGrowatt.ActivePowerPercentDerating = 50; // 30408 this the duration
|
||||
|
||||
statusrecord.AcDcGrowatt.RemoteChargDischargePower = - 100; //30409 we set power here // for grid priority from 0 to -100
|
||||
statusrecord.AcDcGrowatt.ActualChargeDischargePowerControlValue.WriteLine(" register 30474");; // this to check what was set
|
||||
return "Grid priority mode active";
|
||||
case EssMode.BatteryPriority:
|
||||
statusrecord.AcDcGrowatt.RemotePowerControl = true;
|
||||
statusrecord.AcDcGrowatt.RemotePowerControlChargeDuration = 0; // 30408 this the duration
|
||||
|
||||
statusrecord.AcDcGrowatt.RemoteChargDischargePower = 100; //30409 we set power here // for battery priority from 0 to 100
|
||||
statusrecord.AcDcGrowatt.ActualChargeDischargePowerControlValue.WriteLine(" register 30474");; // this to check what was set
|
||||
return "Battery priority mode active";
|
||||
case EssMode.LoadPriority:
|
||||
statusrecord.AcDcGrowatt.RemotePowerControl = false;
|
||||
return "Load priority mode active";
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(mode), mode, null);
|
||||
}
|
||||
}
|
||||
|
||||
private static StatusMessage GetSodiHomeStateAlarm(StatusRecord? record)
|
||||
{
|
||||
var s3Bucket = Config.Load().S3?.Bucket;
|
||||
|
||||
var alarmList = new List<AlarmOrWarning>();
|
||||
var warningList = new List<AlarmOrWarning>();
|
||||
|
||||
if (record.AcDcGrowatt.SystemOperatingMode == GrowattSystemStatus.Fault)
|
||||
{
|
||||
if (record.AcDcGrowatt.FaultMainCode != 0)
|
||||
{
|
||||
alarmList.Add(new AlarmOrWarning
|
||||
{
|
||||
Date = DateTime.Now.ToString("yyyy-MM-dd"),
|
||||
Time = DateTime.Now.ToString("HH:mm:ss"),
|
||||
CreatedBy = "Growatt Inverter",
|
||||
Description = record.AcDcGrowatt.WarningMainCode.ToString(), // to add the sub code
|
||||
});
|
||||
}
|
||||
|
||||
if (record.AcDcGrowatt.WarningMainCode != 0)
|
||||
{
|
||||
warningList.Add(new AlarmOrWarning
|
||||
{
|
||||
Date = DateTime.Now.ToString("yyyy-MM-dd"),
|
||||
Time = DateTime.Now.ToString("HH:mm:ss"),
|
||||
CreatedBy = "Growatt inverter",
|
||||
Description = record.AcDcGrowatt.FaultMainCode.ToString(), //to add the sub code
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
_sodiohomeAlarmState = warningList.Any()
|
||||
? SodistoreAlarmState.Orange
|
||||
: SodistoreAlarmState.Green; // this will be replaced by LedState
|
||||
|
||||
_sodiohomeAlarmState = alarmList.Any()
|
||||
? SodistoreAlarmState.Red
|
||||
: _sodiohomeAlarmState; // this will be replaced by LedState
|
||||
|
||||
var installationId = GetInstallationId(s3Bucket ?? string.Empty);
|
||||
|
||||
var returnedStatus = new StatusMessage
|
||||
{
|
||||
InstallationId = installationId,
|
||||
Product = 2,
|
||||
Status = _sodiohomeAlarmState,
|
||||
Type = MessageType.AlarmOrWarning,
|
||||
Alarms = alarmList,
|
||||
Warnings = warningList
|
||||
};
|
||||
|
||||
return returnedStatus;
|
||||
}
|
||||
|
||||
private static Int32 GetInstallationId(String s3Bucket)
|
||||
{
|
||||
var part = s3Bucket.Split('-').FirstOrDefault();
|
||||
return int.TryParse(part, out var id) ? id : 0; // is 0 a default safe value? check with Marios
|
||||
}
|
||||
|
||||
private static void SendSalimaxStateAlarm(StatusMessage currentSalimaxState, StatusRecord? record)
|
||||
{
|
||||
var s3Bucket = Config.Load().S3?.Bucket;
|
||||
var subscribedNow = false;
|
||||
|
||||
//When the controller boots, it tries to subscribe to the queue
|
||||
if (_subscribeToQueueForTheFirstTime == false)
|
||||
{
|
||||
subscribedNow = true;
|
||||
_subscribeToQueueForTheFirstTime = true;
|
||||
_prevSodiohomeAlarmState = currentSalimaxState.Status;
|
||||
_subscribedToQueue = RabbitMqManager.SubscribeToQueue(currentSalimaxState, s3Bucket, VpnServerIp);
|
||||
}
|
||||
|
||||
//If already subscribed to the queue and the status has been changed, update the queue
|
||||
if (!subscribedNow && _subscribedToQueue && currentSalimaxState.Status != _prevSodiohomeAlarmState)
|
||||
{
|
||||
_prevSodiohomeAlarmState = currentSalimaxState.Status;
|
||||
if (s3Bucket != null)
|
||||
RabbitMqManager.InformMiddleware(currentSalimaxState);
|
||||
}
|
||||
|
||||
//If there is an available message from the RabbitMQ Broker, apply the configuration file
|
||||
Configuration? config = SetConfigurationFile();
|
||||
if (config != null)
|
||||
{
|
||||
record.ApplyConfigFile(config);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyDefaultSettings(this StatusRecord? st)
|
||||
{
|
||||
if (st is null)
|
||||
return;
|
||||
|
||||
st.AcDcGrowatt.BatteryMaxChargeCurrent = st.Config.MaximumChargingCurrent;
|
||||
st.AcDcGrowatt.BatteryMaxdischargeCurrent = st.Config.MaximumDischargingCurrent ;
|
||||
st.AcDcGrowatt.DischargeCutoffSoc = st.Config.MinSoc;
|
||||
st.AcDcGrowatt.EmsCommunicationFailureTime = 20; // 20 sec
|
||||
st.AcDcGrowatt.EnableEmsCommunicationFailureTime = false;
|
||||
st.AcDcGrowatt.EnableCommand = true;
|
||||
st.AcDcGrowatt.ControlPermession = true;
|
||||
st.AcDcGrowatt.BatteryChargeCutoffVoltage = 60; //st.Config.BatteryChargeCutoffVoltage;
|
||||
st.AcDcGrowatt.BatteryDischargeCutoffVoltage = 25; //st.Config.BatteryDischargeCutoffVoltage;
|
||||
}
|
||||
|
||||
private static Dictionary<String, UInt16> ConvertToModbusRegisters(Object value, String outputType, Int32 startingAddress)
|
||||
{
|
||||
var registers = new Dictionary<String, UInt16>();
|
||||
|
||||
switch (outputType)
|
||||
{
|
||||
case "UInt16":
|
||||
registers[startingAddress.ToString()] = Convert.ToUInt16(value);
|
||||
break;
|
||||
|
||||
case "Int16":
|
||||
var int16Val = Convert.ToInt16(value);
|
||||
registers[startingAddress.ToString()] = (UInt16)int16Val; // reinterpret signed as ushort
|
||||
break;
|
||||
|
||||
case "UInt32":
|
||||
var uint32Val = Convert.ToUInt32(value);
|
||||
registers[startingAddress.ToString()] = (UInt16)(uint32Val & 0xFFFF); // Low word
|
||||
registers[(startingAddress + 1).ToString()] = (UInt16)(uint32Val >> 16); // High word
|
||||
break;
|
||||
|
||||
case "Int32":
|
||||
var int32Val = Convert.ToInt32(value);
|
||||
var raw = unchecked((UInt32)int32Val); // reinterprets signed int as unsigned
|
||||
registers[startingAddress.ToString()] = (UInt16)(raw & 0xFFFF);
|
||||
registers[(startingAddress + 1).ToString()] = (UInt16)(raw >> 16);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentException("Unsupported output type: " + outputType);
|
||||
}
|
||||
return registers;
|
||||
}
|
||||
|
||||
[RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.Serialize<System.Collections.Generic.Dictionary<string, ushort>>(System.Collections.Generic.Dictionary<string, ushort>, System.Text.Json.JsonSerializerOptions?)")]
|
||||
private static async Task<Boolean> SaveModbusTcpFile(StatusRecord status)
|
||||
{
|
||||
var modbusData = new Dictionary<String, UInt16>();
|
||||
|
||||
// SYSTEM DATA
|
||||
var result1 = ConvertToModbusRegisters((status.Config.ModbusProtcolNumber * 10), "UInt16", 30001); // this to be updated to modbusTCP version
|
||||
var result2 = ConvertToModbusRegisters(status.AcDcGrowatt.SystemDateTime.ToUnixTime(), "UInt32", 30002);
|
||||
var result3 = ConvertToModbusRegisters(status.AcDcGrowatt.SystemOperatingMode, "UInt16", 30004);
|
||||
var result17 = ConvertToModbusRegisters(status.AcDcGrowatt.OperatingPriority, "UInt16", 30005);
|
||||
|
||||
// BATTERY SUMMARY (assuming single battery [0]) // this to be improved
|
||||
var battery = status.AcDcGrowatt.BatteriesRecords!.Batteries[0];
|
||||
|
||||
var result4 = ConvertToModbusRegisters((status.Config.BatteriesCount ), "UInt16", 31000);
|
||||
var result8 = ConvertToModbusRegisters((status.AcDcGrowatt.BatteryOperatingMode), "UInt16", 31001);
|
||||
var result12 = ConvertToModbusRegisters((battery.Voltage.Value * 10), "Int16", 31002);
|
||||
var result13 = ConvertToModbusRegisters((battery.Current.Value * 10), "Int32", 31003);
|
||||
var result14 = ConvertToModbusRegisters((battery.Soc.Value * 100), "UInt16", 31005);
|
||||
var result5 = ConvertToModbusRegisters((battery.Power.Value * 10), "Int32", 31006);
|
||||
|
||||
var result7 = ConvertToModbusRegisters((status.AcDcGrowatt.DischargeCutoffSoc * 100), "UInt16", 31008);
|
||||
var result20 = ConvertToModbusRegisters((status.AcDcGrowatt.ChargeCutoffSoc * 100), "UInt16", 31009);
|
||||
var result15 = ConvertToModbusRegisters((status.AcDcGrowatt.BatteriesRecords!.AverageSoh * 100), "UInt16", 310010);
|
||||
var result16 = ConvertToModbusRegisters((battery.BatteryAmbientTemperature.Value * 100), "UInt16", 31011);
|
||||
var result21 = ConvertToModbusRegisters((status.AcDcGrowatt.BatteryMaxChargeCurrent * 10), "UInt16", 31012);
|
||||
var result22 = ConvertToModbusRegisters((status.AcDcGrowatt.BatteryMaxdischargeCurrent * 10), "UInt16", 31013);
|
||||
var result23 = ConvertToModbusRegisters((status.AcDcGrowatt.BatteryChargeCutoffVoltage * 10), "UInt16", 31014);
|
||||
|
||||
var result18 = ConvertToModbusRegisters((status.AcDcGrowatt.TotalPvPower.Value * 10), "UInt32", 32000);
|
||||
var result19 = ConvertToModbusRegisters((status.AcDcGrowatt.MeterPower * 10), "Int32", 33000);
|
||||
|
||||
|
||||
// Merge all results into one dictionary
|
||||
var allResults = new[]
|
||||
{
|
||||
result1,result2, result3, result17, result4, result5, result7, result8,
|
||||
result12, result13, result14, result15, result16, result18, result19,result20,
|
||||
result21, result22, result23
|
||||
};
|
||||
|
||||
foreach (var result in allResults)
|
||||
{
|
||||
foreach (var entry in result)
|
||||
{
|
||||
modbusData[entry.Key] = entry.Value;
|
||||
}
|
||||
}
|
||||
// Write to JSON
|
||||
var json = JsonSerializer.Serialize(modbusData, new JsonSerializerOptions { WriteIndented = true });
|
||||
await File.WriteAllTextAsync("/home/inesco/SodiStoreHome/ModbusTCP/modbus_tcp_data.json", json);
|
||||
|
||||
//Console.WriteLine("JSON file written successfully.");
|
||||
//Console.WriteLine(json);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async Task<Boolean> DataLogging(StatusRecord status, DateTime timeStamp)
|
||||
{
|
||||
var csv = status.ToCsv();
|
||||
|
||||
// for debug, only to be deleted.
|
||||
//foreach (var item in csv.SplitLines())
|
||||
//{
|
||||
// Console.WriteLine(item + "");
|
||||
//}
|
||||
|
||||
await SavingLocalCsvFile(timeStamp.ToUnixTime(), csv);
|
||||
|
||||
var jsonData = new Dictionary<String, Object>();
|
||||
|
||||
ConvertToJson(csv, jsonData).LogInfo();
|
||||
|
||||
var s3Config = status.Config.S3;
|
||||
|
||||
if (s3Config is null)
|
||||
return false;
|
||||
|
||||
//Concatenating 15 files in one file
|
||||
return await ConcatinatingAndCompressingFiles(timeStamp.ToUnixTime(), s3Config);
|
||||
}
|
||||
|
||||
private static String ConvertToJson(String csv, Dictionary<String, Object> jsonData)
|
||||
{
|
||||
foreach (var line in csv.Split('\n'))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(line)) continue;
|
||||
|
||||
var parts = line.Split(';');
|
||||
var keyPath = parts[0];
|
||||
var value = parts[1];
|
||||
var unit = parts.Length > 2 ? parts[2].Trim() : "";
|
||||
InsertIntoJson(jsonData, keyPath.Split('/'), value);
|
||||
}
|
||||
|
||||
var jsonOutput = JsonConvert.SerializeObject(jsonData, Formatting.None);
|
||||
return jsonOutput;
|
||||
}
|
||||
|
||||
private static void InsertIntoJson(Dictionary<String, Object> jsonDict, String[] keys, String value)
|
||||
{
|
||||
var currentDict = jsonDict;
|
||||
for (Int16 i = 1; i < keys.Length; i++) // Start at 1 to skip empty root
|
||||
{
|
||||
var key = keys[i];
|
||||
if (!currentDict.ContainsKey(key))
|
||||
{
|
||||
currentDict[key] = new Dictionary<String, Object>();
|
||||
}
|
||||
|
||||
if (i == keys.Length - 1) // Last key, store the value
|
||||
{
|
||||
|
||||
if (!value.Contains(",") && double.TryParse(value, out Double doubleValue)) // Try to parse value as a number
|
||||
{
|
||||
currentDict[key] = Math.Round(doubleValue, 2); // Round to 2 decimal places
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
currentDict[key] = value; // Store as string if not a number
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
currentDict = (Dictionary<String, Object>)currentDict[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SavingLocalCsvFile(Int64 timestamp, String csv)
|
||||
{
|
||||
const String directoryPath = "/home/inesco/SodiStoreHome/csvFile";
|
||||
|
||||
// Ensure directory exists
|
||||
if (!Directory.Exists(directoryPath))
|
||||
{
|
||||
Directory.CreateDirectory(directoryPath);
|
||||
}
|
||||
|
||||
// Get all .csv files ordered by creation time (oldest first)
|
||||
var csvFiles = new DirectoryInfo(directoryPath)
|
||||
.GetFiles("*.csv")
|
||||
.OrderBy(f => f.CreationTimeUtc)
|
||||
.ToList();
|
||||
|
||||
// If more than 5000 files, delete the oldest
|
||||
if (csvFiles.Count >= 5000)
|
||||
{
|
||||
var oldestFile = csvFiles.First();
|
||||
try
|
||||
{
|
||||
oldestFile.Delete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Failed to delete file: {oldestFile.FullName}, Error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the filtered CSV content
|
||||
var filteredCsv = csv
|
||||
.SplitLines()
|
||||
.Where(l => !l.Contains("Secret"))
|
||||
.JoinLines();
|
||||
|
||||
// Save the new CSV file
|
||||
var filePath = Path.Combine(directoryPath, timestamp + ".csv");
|
||||
await File.WriteAllTextAsync(filePath, filteredCsv);
|
||||
}
|
||||
|
||||
private static async Task<Boolean> ConcatinatingAndCompressingFiles(Int64 timeStamp, S3Config s3Config)
|
||||
{
|
||||
if (_fileCounter >= NbrOfFileToConcatenate)
|
||||
{
|
||||
_fileCounter = 0;
|
||||
|
||||
var logFileConcatenator = new LogFileConcatenator();
|
||||
var jsontoSend = logFileConcatenator.ConcatenateFiles(NbrOfFileToConcatenate);
|
||||
|
||||
var fileNameWithoutExtension = timeStamp.ToString(); // used for both S3 and local
|
||||
var s3Path = fileNameWithoutExtension + ".json";
|
||||
|
||||
var request = s3Config.CreatePutRequest(s3Path);
|
||||
|
||||
var compressedBytes = CompresseBytes(jsontoSend);
|
||||
var base64String = Convert.ToBase64String(compressedBytes);
|
||||
var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64");
|
||||
|
||||
var uploadSucceeded = false;
|
||||
|
||||
try
|
||||
{
|
||||
var response = await request.PutAsync(stringContent);
|
||||
|
||||
if (response.StatusCode != 200)
|
||||
{
|
||||
Console.WriteLine("ERROR: PUT");
|
||||
var error = await response.GetStringAsync();
|
||||
Console.WriteLine(error);
|
||||
|
||||
await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension);
|
||||
Heartbit();
|
||||
return false;
|
||||
}
|
||||
|
||||
uploadSucceeded = true;
|
||||
Console.WriteLine("✅ File uploaded to S3 successfully.");
|
||||
|
||||
Console.WriteLine("---------------------------------------- Resending FailedUploadedFiles----------------------------------------");
|
||||
Heartbit();
|
||||
|
||||
await ResendLocalFailedFilesAsync(s3Config); // retry any pending failed files
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Upload exception: " + ex.Message);
|
||||
|
||||
if (!uploadSucceeded)
|
||||
{
|
||||
await SaveToLocalCompressedFallback(compressedBytes, fileNameWithoutExtension);
|
||||
}
|
||||
|
||||
Heartbit();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
_fileCounter++;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void Heartbit()
|
||||
{
|
||||
var s3Bucket = Config.Load().S3?.Bucket;
|
||||
var tryParse = int.TryParse(s3Bucket?.Split("-")[0], out var installationId);
|
||||
|
||||
if (tryParse)
|
||||
{
|
||||
var returnedStatus = new StatusMessage
|
||||
{
|
||||
InstallationId = installationId,
|
||||
Product = 2,
|
||||
Status = _sodiohomeAlarmState,
|
||||
Type = MessageType.Heartbit,
|
||||
};
|
||||
if (s3Bucket != null)
|
||||
RabbitMqManager.InformMiddleware(returnedStatus);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task SaveToLocalCompressedFallback(Byte[] compressedData, String fileNameWithoutExtension)
|
||||
{
|
||||
try
|
||||
{
|
||||
var fallbackDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FailedUploads");
|
||||
Directory.CreateDirectory(fallbackDir);
|
||||
|
||||
var fileName = fileNameWithoutExtension + ".json"; // Save as .json, but still compressed
|
||||
var fullPath = Path.Combine(fallbackDir, fileName);
|
||||
|
||||
await File.WriteAllBytesAsync(fullPath, compressedData); // Compressed data
|
||||
Console.WriteLine($"Saved compressed failed upload to: {fullPath}");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("Failed to save compressed file locally: " + ex.Message);
|
||||
}
|
||||
}
|
||||
|
||||
private static async Task ResendLocalFailedFilesAsync(S3Config s3Config)
|
||||
{
|
||||
var fallbackDir = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "FailedUploads");
|
||||
|
||||
if (!Directory.Exists(fallbackDir))
|
||||
return;
|
||||
|
||||
var files = Directory.GetFiles(fallbackDir, "*.json");
|
||||
files.Length.WriteLine(" Number of failed files, to upload");
|
||||
|
||||
foreach (var filePath in files)
|
||||
{
|
||||
var fileName = Path.GetFileName(filePath); // e.g., "1720023600.json"
|
||||
|
||||
try
|
||||
{
|
||||
byte[] compressedBytes = await File.ReadAllBytesAsync(filePath);
|
||||
var base64String = Convert.ToBase64String(compressedBytes);
|
||||
var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64");
|
||||
|
||||
var request = s3Config.CreatePutRequest(fileName);
|
||||
var response = await request.PutAsync(stringContent);
|
||||
|
||||
if (response.StatusCode == 200)
|
||||
{
|
||||
File.Delete(filePath);
|
||||
Console.WriteLine($"✅ Successfully resent and deleted: {fileName}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine($"❌ Failed to resend {fileName}, status: {response.StatusCode}");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"⚠️ Exception while resending {fileName}: {ex.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Byte[] CompresseBytes(String jsonToSend)
|
||||
{
|
||||
//Compress JSON data to a byte array
|
||||
using var memoryStream = new MemoryStream();
|
||||
//Create a zip directory and put the compressed file inside
|
||||
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
|
||||
{
|
||||
var entry = archive.CreateEntry("data.json", CompressionLevel.SmallestSize); // Add JSON data to the ZIP archive
|
||||
using (var entryStream = entry.Open())
|
||||
using (var writer = new StreamWriter(entryStream))
|
||||
{
|
||||
writer.Write(jsonToSend);
|
||||
}
|
||||
}
|
||||
|
||||
var compressedBytes = memoryStream.ToArray();
|
||||
|
||||
return compressedBytes;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,102 +0,0 @@
|
|||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Text.Json;
|
||||
using InnovEnergy.App.GrowattCommunication.ESS;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using static System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace InnovEnergy.App.GrowattCommunication.SystemConfig;
|
||||
|
||||
[SuppressMessage("Trimming", "IL2026:Members annotated with \'RequiresUnreferencedCodeAttribute\' require dynamic access otherwise can break functionality when trimming application code")]
|
||||
public class Config //TODO: let IE choose from config files (Json) and connect to GUI
|
||||
{
|
||||
private static String DefaultConfigFilePath => Path.Combine(Environment.CurrentDirectory, "config.json");
|
||||
private static DateTime DefaultDatetime => new(2024, 03, 11, 09, 00, 00);
|
||||
|
||||
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
|
||||
|
||||
public required Double MinSoc { get; set; }
|
||||
public required Double GridSetPoint { get; set; }
|
||||
public required Double MaximumDischargingCurrent { get; set; }
|
||||
public required Double MaximumChargingCurrent { get; set; }
|
||||
public required EssMode OperatingPriority { get; set; }
|
||||
public required Int16 BatteriesCount { get; set; }
|
||||
public required Double ModbusProtcolNumber { get; set; }
|
||||
|
||||
public required S3Config? S3 { get; set; }
|
||||
|
||||
|
||||
private static String? LastSavedData { get; set; }
|
||||
|
||||
public static Config Default => new()
|
||||
{
|
||||
MinSoc = 20,
|
||||
GridSetPoint = 0,
|
||||
MaximumChargingCurrent = 180,
|
||||
MaximumDischargingCurrent = 180,
|
||||
OperatingPriority = EssMode.LoadPriority,
|
||||
BatteriesCount = 0,
|
||||
ModbusProtcolNumber = 1.2,
|
||||
S3 = new()
|
||||
{
|
||||
Bucket = "1-3e5b3069-214a-43ee-8d85-57d72000c19d",
|
||||
Region = "sos-ch-dk-2",
|
||||
Provider = "exo.io",
|
||||
Key = "EXObb5a49acb1061781761895e7",
|
||||
Secret = "sKhln0w8ii3ezZ1SJFF33yeDo8NWR1V4w2H0D4-350I",
|
||||
ContentType = "text/plain; charset=utf-8"
|
||||
}
|
||||
};
|
||||
|
||||
public void Save(String? path = null)
|
||||
{
|
||||
var configFilePath = path ?? DefaultConfigFilePath;
|
||||
|
||||
try
|
||||
{
|
||||
var jsonString = Serialize(this, JsonOptions);
|
||||
|
||||
if (LastSavedData == jsonString)
|
||||
return;
|
||||
|
||||
LastSavedData = jsonString;
|
||||
|
||||
File.WriteAllText(configFilePath, jsonString);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
$"Failed to write config file {configFilePath}\n{e}".WriteLine();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static Config Load(String? path = null)
|
||||
{
|
||||
var configFilePath = path ?? DefaultConfigFilePath;
|
||||
try
|
||||
{
|
||||
var jsonString = File.ReadAllText(configFilePath);
|
||||
return Deserialize<Config>(jsonString)!;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
$"Failed to read config file {configFilePath}, using default config\n{e}".WriteLine();
|
||||
return Default;
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task<Config> LoadAsync(String? path = null)
|
||||
{
|
||||
var configFilePath = path ?? DefaultConfigFilePath;
|
||||
try
|
||||
{
|
||||
var jsonString = await File.ReadAllTextAsync(configFilePath);
|
||||
return Deserialize<Config>(jsonString)!;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Couldn't read config file {configFilePath}, using default config");
|
||||
e.Message.WriteLine();
|
||||
return Default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
using System.Security.Cryptography;
|
||||
using Flurl;
|
||||
using Flurl.Http;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using static System.Text.Encoding;
|
||||
using Convert = System.Convert;
|
||||
|
||||
namespace InnovEnergy.App.GrowattCommunication.SystemConfig;
|
||||
|
||||
public record S3Config
|
||||
{
|
||||
public required String Bucket { get; init; }
|
||||
public required String Region { get; init; }
|
||||
public required String Provider { get; init; }
|
||||
public required String Key { get; init; }
|
||||
public required String Secret { get; init; }
|
||||
public required String ContentType { get; init; }
|
||||
|
||||
private String Host => $"{Bucket}.{Region}.{Provider}";
|
||||
private String Url => $"https://{Host}";
|
||||
|
||||
public IFlurlRequest CreatePutRequest(String s3Path) => CreateRequest("PUT", s3Path);
|
||||
public IFlurlRequest CreateGetRequest(String s3Path) => CreateRequest("GET", s3Path);
|
||||
|
||||
private IFlurlRequest CreateRequest(String method, String s3Path)
|
||||
{
|
||||
var date = DateTime.UtcNow.ToString("r");
|
||||
var auth = CreateAuthorization(method, s3Path, date);
|
||||
|
||||
return Url
|
||||
.AppendPathSegment(s3Path)
|
||||
.WithHeader("Host", Host)
|
||||
.WithHeader("Date", date)
|
||||
.WithHeader("Authorization", auth)
|
||||
.AllowAnyHttpStatus();
|
||||
}
|
||||
|
||||
private String CreateAuthorization(String method,
|
||||
String s3Path,
|
||||
String date)
|
||||
{
|
||||
return CreateAuthorization
|
||||
(
|
||||
method : method,
|
||||
bucket : Bucket,
|
||||
s3Path : s3Path,
|
||||
date : date,
|
||||
s3Key : Key,
|
||||
s3Secret : Secret,
|
||||
contentType: ContentType
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static String CreateAuthorization(String method,
|
||||
String bucket,
|
||||
String s3Path,
|
||||
String date,
|
||||
String s3Key,
|
||||
String s3Secret,
|
||||
String contentType = "application/base64",
|
||||
String md5Hash = "")
|
||||
{
|
||||
|
||||
contentType = "application/base64; charset=utf-8";
|
||||
//contentType = "text/plain; charset=utf-8"; //this to use when sending plain csv to S3
|
||||
|
||||
var payload = $"{method}\n{md5Hash}\n{contentType}\n{date}\n/{bucket.Trim('/')}/{s3Path.Trim('/')}";
|
||||
using var hmacSha1 = new HMACSHA1(UTF8.GetBytes(s3Secret));
|
||||
|
||||
var signature = UTF8
|
||||
.GetBytes(payload)
|
||||
.Apply(hmacSha1.ComputeHash)
|
||||
.Apply(Convert.ToBase64String);
|
||||
|
||||
return $"AWS {s3Key}:{signature}";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
#!/bin/bash
|
||||
dotnet_version='net6.0'
|
||||
salimax_ip="$1"
|
||||
is_release="$2" # Pass --release if this is a real release
|
||||
username='inesco'
|
||||
root_password='Sodistore0918425'
|
||||
|
||||
release_flag_file="./bin/Release/$dotnet_version/linux-arm64/publish/.release.flag"
|
||||
|
||||
set -e
|
||||
|
||||
echo -e "\n============================ Build ============================\n"
|
||||
|
||||
dotnet publish \
|
||||
./GrowattCommunication.csproj \
|
||||
-p:PublishTrimmed=false \
|
||||
-c Release \
|
||||
-r linux-arm64
|
||||
|
||||
echo -e "\n============================ Deploy ============================\n"
|
||||
|
||||
rsync -v \
|
||||
--exclude '*.pdb' \
|
||||
./bin/Release/$dotnet_version/linux-arm64/publish/* \
|
||||
$username@"$salimax_ip":~/SodiStoreHome
|
||||
|
||||
if [[ "$is_release" == "--release" ]]; then
|
||||
echo -e "\n✅ Real release. Triggering sync to server..."
|
||||
touch "$release_flag_file"
|
||||
else
|
||||
echo -e "\n🚫 Test build. Not syncing to main release server."
|
||||
fi
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
WATCHDIR="$HOME/sync/work/Code/CSharp/git_trunk/csharp/App/GrowattCommunication/bin/Release/net6.0/linux-arm64/publish"
|
||||
DEST="ubuntu@91.92.155.224:/home/ubuntu/Releases"
|
||||
|
||||
echo "👀 Watching for real releases in $WATCHDIR..."
|
||||
|
||||
inotifywait -m -e close_write --format '%w%f' "$WATCHDIR" | while read file; do
|
||||
filename="$(basename "$file")"
|
||||
|
||||
if [[ "$filename" == ".release.flag" ]]; then
|
||||
echo "🚀 Release flag detected. Syncing full release to $DEST..."
|
||||
|
||||
rm "$file"
|
||||
rsync -avz \
|
||||
--exclude '*.pdb' \
|
||||
"$WATCHDIR/" "$DEST/"
|
||||
|
||||
echo "✅ Sync completed and flag cleared."
|
||||
fi
|
||||
done
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
using System.Net.WebSockets;
|
||||
|
||||
namespace InnovEnergy.App.Middleware;
|
||||
|
||||
public class InstallationInfo
|
||||
{
|
||||
public int Status { get; set; }
|
||||
public List<WebSocket> Connections { get; } = new List<WebSocket>();
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<Import Project="../InnovEnergy.App.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>InnovEnergy.App.Middleware</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="RabbitMQ.Client" Version="6.6.0" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Lib\Utils\Utils.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
</Project>
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
using InnovEnergy.App.Middleware;
|
||||
using System;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
|
||||
internal class Program
|
||||
{
|
||||
public static readonly object SharedDataLock = new object();
|
||||
|
||||
public static async Task Main(string[] args)
|
||||
{
|
||||
//For each installation id, we maintain a list of the connected clients
|
||||
var installationConnections = new Dictionary<int, InstallationInfo>();
|
||||
var installationsIds = new List<int> {1};
|
||||
var installationIps = new List<string> {"10.2.3.115"};
|
||||
var MAX_RETRANSMISSIONS = 2;
|
||||
|
||||
RabbitMqConsumer.StartRabbitMqConsumer(installationConnections,SharedDataLock);
|
||||
|
||||
UdpClient udpClient = new UdpClient();
|
||||
udpClient.Client.ReceiveTimeout = 2000;
|
||||
int port = 9000;
|
||||
//Send a message to each installation and tell it to subscribe to the queue
|
||||
for (int i = 0; i < installationsIds.Count; i++)
|
||||
{
|
||||
using (udpClient)
|
||||
{
|
||||
//Try at most MAX_RETRANSMISSIONS times to reach an installation.
|
||||
for (int j = 0; j < MAX_RETRANSMISSIONS; j++)
|
||||
{
|
||||
string message = "This is a message from RabbitMQ server, you can subscribe to the RabbitMQ queue";
|
||||
byte[] data = Encoding.UTF8.GetBytes(message);
|
||||
udpClient.Send(data, data.Length, installationIps[i], port);
|
||||
|
||||
Console.WriteLine($"Sent UDP message to {installationIps[i]}:{port}: {message}");
|
||||
IPEndPoint remoteEndPoint = new IPEndPoint(IPAddress.Parse(installationIps[i]), port);
|
||||
|
||||
try
|
||||
{
|
||||
byte[] replyData = udpClient.Receive(ref remoteEndPoint);
|
||||
string replyMessage = Encoding.UTF8.GetString(replyData);
|
||||
Console.WriteLine("Received message from installation " + installationsIds[i]);
|
||||
break;
|
||||
}
|
||||
catch (SocketException ex)
|
||||
{
|
||||
if (ex.SocketErrorCode == SocketError.TimedOut)
|
||||
{
|
||||
Console.WriteLine("Timed out waiting for a response. Retry...");
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("Error: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Console.WriteLine("WebSocket server is running. Press Enter to exit.");
|
||||
await WebSocketListener.StartServerAsync(installationConnections,SharedDataLock);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
using System.Net.WebSockets;
|
||||
using System.Text.Json;
|
||||
namespace InnovEnergy.App.Middleware;
|
||||
using System.Text;
|
||||
using RabbitMQ.Client;
|
||||
using RabbitMQ.Client.Events;
|
||||
|
||||
public static class RabbitMqConsumer
|
||||
{
|
||||
|
||||
private static ConnectionFactory _factory = null!;
|
||||
private static IConnection _connection = null!;
|
||||
private static IModel _channel= null!;
|
||||
|
||||
public static void StartRabbitMqConsumer(Dictionary<Int32, InstallationInfo> installationConnections, Object sharedDataLock)
|
||||
{
|
||||
string vpnServerIp = "194.182.190.208";
|
||||
_factory = new ConnectionFactory { HostName = "localhost" };
|
||||
_connection = _factory.CreateConnection();
|
||||
_channel = _connection.CreateModel();
|
||||
Console.WriteLine("Middleware subscribed to RabbitMQ queue, ready for receiving messages");
|
||||
_channel.QueueDeclare(queue: "statusQueue", durable: false, exclusive: false, autoDelete: false, arguments: null);
|
||||
|
||||
var consumer = new EventingBasicConsumer(_channel);
|
||||
consumer.Received += (_, ea) => Callback(installationConnections, sharedDataLock, ea);
|
||||
|
||||
_channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer);
|
||||
|
||||
}
|
||||
|
||||
private static void Callback(Dictionary<Int32, InstallationInfo> installationConnections, Object sharedDataLock, BasicDeliverEventArgs ea)
|
||||
{
|
||||
var body = ea.Body.ToArray();
|
||||
var message = Encoding.UTF8.GetString(body);
|
||||
StatusMessage? receivedStatusMessage = JsonSerializer.Deserialize<StatusMessage>(message);
|
||||
|
||||
lock (sharedDataLock)
|
||||
{
|
||||
// Process the received message
|
||||
if (receivedStatusMessage != null)
|
||||
{
|
||||
Console.WriteLine("Received a message from installation: " + receivedStatusMessage.InstallationId + " and status is: " + receivedStatusMessage.Status);
|
||||
Console.WriteLine("----------------------------------------------");
|
||||
Console.WriteLine("Update installation connection table");
|
||||
var installationId = receivedStatusMessage.InstallationId;
|
||||
|
||||
if (!installationConnections.ContainsKey(installationId))
|
||||
{
|
||||
Console.WriteLine("Create new empty list for installation: " + installationId);
|
||||
installationConnections[installationId] = new InstallationInfo
|
||||
{
|
||||
Status = receivedStatusMessage.Status
|
||||
};
|
||||
}
|
||||
|
||||
Console.WriteLine("----------------------------------------------");
|
||||
|
||||
foreach (var installationConnection in installationConnections)
|
||||
{
|
||||
if (installationConnection.Key == installationId && installationConnection.Value.Connections.Count > 0)
|
||||
{
|
||||
Console.WriteLine("Update all the connected websockets for installation " + installationId);
|
||||
installationConnection.Value.Status = receivedStatusMessage.Status;
|
||||
|
||||
var jsonObject = new
|
||||
{
|
||||
id = installationId,
|
||||
status = receivedStatusMessage.Status
|
||||
};
|
||||
|
||||
string jsonString = JsonSerializer.Serialize(jsonObject);
|
||||
byte[] dataToSend = Encoding.UTF8.GetBytes(jsonString);
|
||||
|
||||
foreach (var connection in installationConnection.Value.Connections)
|
||||
{
|
||||
connection.SendAsync(
|
||||
new ArraySegment<byte>(dataToSend, 0, dataToSend.Length),
|
||||
WebSocketMessageType.Text,
|
||||
true, // Indicates that this is the end of the message
|
||||
CancellationToken.None
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
|
||||
namespace InnovEnergy.App.Middleware;
|
||||
|
||||
public class StatusMessage
|
||||
{
|
||||
public required int InstallationId { get; init; }
|
||||
public required int Status { get; init; }
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
using System.Net;
|
||||
using System.Net.WebSockets;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace InnovEnergy.App.Middleware;
|
||||
|
||||
public static class WebSocketListener
|
||||
{
|
||||
|
||||
public static async Task StartServerAsync(Dictionary<Int32, InstallationInfo> installationConnections, Object sharedDataLock)
|
||||
{
|
||||
var listener = new HttpListener();
|
||||
listener.Prefixes.Add("http://127.0.0.1:8080/");
|
||||
|
||||
listener.Start();
|
||||
|
||||
//Http listener listens for connections. When it accepts a new connection, it creates a new Task to handle this connection
|
||||
while (true)
|
||||
{
|
||||
var context = await listener.GetContextAsync();
|
||||
if (context.Request.IsWebSocketRequest)
|
||||
{
|
||||
var webSocketContext = await context.AcceptWebSocketAsync(null);
|
||||
var webSocket = webSocketContext.WebSocket;
|
||||
|
||||
// Add the connected WebSocket to the collection
|
||||
Console.WriteLine("Accepted a new websocket connection");
|
||||
HandleWebSocketConnection(webSocket, installationConnections);
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
context.Response.Close();
|
||||
}
|
||||
}
|
||||
|
||||
//We have a task per websocket connection
|
||||
async Task HandleWebSocketConnection(WebSocket currentWebSocket, Dictionary<Int32, InstallationInfo> installationConnections)
|
||||
{
|
||||
|
||||
var buffer = new byte[4096];
|
||||
|
||||
try
|
||||
{
|
||||
while (currentWebSocket.State == WebSocketState.Open)
|
||||
{
|
||||
//Listen for incoming messages on this WebSocket
|
||||
var result = await currentWebSocket.ReceiveAsync(buffer, CancellationToken.None);
|
||||
Console.WriteLine("Received a new message from websocket");
|
||||
if (result.MessageType != WebSocketMessageType.Text)
|
||||
continue;
|
||||
|
||||
var message = Encoding.UTF8.GetString(buffer, 0, result.Count);
|
||||
var installationIds = JsonSerializer.Deserialize<int[]>(message);
|
||||
|
||||
lock (sharedDataLock)
|
||||
{
|
||||
//Each front-end will send the list of the installations it wants to access
|
||||
//If this is a new key (installation id), initialize the list for this key and then add the websocket object for this client
|
||||
//Then, report the status of each requested installation to the front-end that created the websocket connection
|
||||
foreach (var installationId in installationIds)
|
||||
{
|
||||
if (!installationConnections.ContainsKey(installationId))
|
||||
{
|
||||
Console.WriteLine("Create new empty list for installation id "+installationId);
|
||||
installationConnections[installationId] = new InstallationInfo
|
||||
{
|
||||
Status = -2
|
||||
};
|
||||
}
|
||||
|
||||
installationConnections[installationId].Connections.Add(currentWebSocket);
|
||||
|
||||
var jsonObject = new
|
||||
{
|
||||
id = installationId,
|
||||
status = installationConnections[installationId].Status
|
||||
};
|
||||
|
||||
var jsonString = JsonSerializer.Serialize(jsonObject);
|
||||
var dataToSend = Encoding.UTF8.GetBytes(jsonString);
|
||||
|
||||
|
||||
currentWebSocket.SendAsync(dataToSend,
|
||||
WebSocketMessageType.Text,
|
||||
true, // Indicates that this is the end of the message
|
||||
CancellationToken.None
|
||||
);
|
||||
}
|
||||
|
||||
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("----------------------------------------------");
|
||||
}
|
||||
}
|
||||
|
||||
//When the front-end terminates the connection, the following code will be executed
|
||||
Console.WriteLine("The connection has been terminated");
|
||||
foreach (var installationConnection in installationConnections)
|
||||
{
|
||||
if (installationConnection.Value.Connections.Contains(currentWebSocket))
|
||||
{
|
||||
installationConnection.Value.Connections.Remove(currentWebSocket);
|
||||
}
|
||||
}
|
||||
|
||||
await currentWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Connection closed by server", CancellationToken.None);
|
||||
//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)
|
||||
{
|
||||
Console.WriteLine("WebSocket error: " + ex.Message);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1 @@
|
|||
dotnet publish Middleware.csproj -c Release -r linux-x64 --self-contained true -p:PublishTrimmed=false && rsync -av bin/Release/net6.0/linux-x64/publish/ ubuntu@194.182.190.208:~/middleware
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../InnovEnergy.App.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
<RootNamespace>InnovEnergy.App.ResetBms</RootNamespace>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Lib\Protocols\Modbus\Modbus.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
|
||||
</Project>
|
||||
|
|
@ -1,110 +0,0 @@
|
|||
"MinSoc": Number, 0 - 100 this is the minimum State of Charge that the batteries must not go below,
|
||||
"ForceCalibrationCharge": Boolean (true or false), A flag to force a calibration charge,
|
||||
"DisplayIndividualBatteries": Boolean (true or false), To display the indvidual batteries
|
||||
"PConstant": Number 0 - 1, P value of our controller.
|
||||
"GridSetPoint": Number in Watts, The set point of our controller.
|
||||
"BatterySelfDischargePower": Number, 200, this a physical measurement of the self discharging power.
|
||||
"HoldSocZone": Number, 1, This is magic number for the soft landing factor.
|
||||
"IslandMode": { // Dc Link Voltage in Island mode
|
||||
"AcDc": {
|
||||
"MaxDcLinkVoltage": Number, 810, Max Dc Link Voltage,
|
||||
"MinDcLinkVoltage": Number, 690, Min Dc Link Voltage,
|
||||
"ReferenceDcLinkVoltage": Number, 750, Reference Dc Link
|
||||
},
|
||||
"DcDc": {
|
||||
"LowerDcLinkVoltage": Number, 50, Lower Dc Link Window ,
|
||||
"ReferenceDcLinkVoltage": 750, reference Dc Link
|
||||
"UpperDcLinkVoltage": Number, 50, Upper Dc Link Window ,
|
||||
}
|
||||
},
|
||||
"GridTie": {// Dc Link Voltage in GrieTie mode
|
||||
"AcDc": {
|
||||
"MaxDcLinkVoltage":Number, 780, Max Dc Link Voltage,
|
||||
"MinDcLinkVoltage": Number, 690, Min Dc Link Voltage,
|
||||
"ReferenceDcLinkVoltage": Number, 750, Reference Dc Link
|
||||
},
|
||||
"DcDc": {
|
||||
"LowerDcLinkVoltage": Number, 20, Lower Dc Link Window ,
|
||||
"ReferenceDcLinkVoltage": 750, reference Dc Link
|
||||
"UpperDcLinkVoltage": Number, 20, Upper Dc Link Window ,
|
||||
}
|
||||
},
|
||||
"MaxBatteryChargingCurrent":Number, 0 - 210, Max Charging current by DcDc
|
||||
"MaxBatteryDischargingCurrent":Number, 0 - 210, Max Discharging current by DcDc
|
||||
"MaxDcPower": Number, 0 - 10000, Max Power exported/imported by DcDc (10000 is the maximum)
|
||||
"MaxChargeBatteryVoltage": Number, 57, Max Charging battery Voltage
|
||||
"MinDischargeBatteryVoltage": Number, 0, Min Charging Battery Voltage
|
||||
"Devices": { This is All Salimax devices (including offline ones)
|
||||
"RelaysIp": {
|
||||
"DeviceState": 1, // 0: is not present, 1: Present and Can be mesured, 2: Present but must be computed/calculted
|
||||
"Host": "10.0.1.1", // Ip @ of the device in the local network
|
||||
"Port": 502 // port
|
||||
},
|
||||
"GridMeterIp": {
|
||||
"DeviceState": 1,
|
||||
"Host": "10.0.4.1",
|
||||
"Port": 502
|
||||
},
|
||||
"PvOnAcGrid": {
|
||||
"DeviceState": 0, // If a device is not present
|
||||
"Host": "false", // this is not important
|
||||
"Port": 0 // this is not important
|
||||
},
|
||||
"LoadOnAcGrid": {
|
||||
"DeviceState": 2, // this is a computed device
|
||||
"Host": "true",
|
||||
"Port": 0
|
||||
},
|
||||
"PvOnAcIsland": {
|
||||
"DeviceState": 0,
|
||||
"Host": "false",
|
||||
"Port": 0
|
||||
},
|
||||
"IslandBusLoadMeterIp": {
|
||||
"DeviceState": 1,
|
||||
"Host": "10.0.4.2",
|
||||
"Port": 502
|
||||
},
|
||||
"TruConvertAcIp": {
|
||||
"DeviceState": 1,
|
||||
"Host": "10.0.2.1",
|
||||
"Port": 502
|
||||
},
|
||||
"PvOnDc": {
|
||||
"DeviceState": 1,
|
||||
"Host": "10.0.5.1",
|
||||
"Port": 502
|
||||
},
|
||||
"LoadOnDc": {
|
||||
"DeviceState": 0,
|
||||
"Host": "false",
|
||||
"Port": 0
|
||||
},
|
||||
"TruConvertDcIp": {
|
||||
"DeviceState": 1,
|
||||
"Host": "10.0.3.1",
|
||||
"Port": 502
|
||||
},
|
||||
"BatteryIp": {
|
||||
"DeviceState": 1,
|
||||
"Host": "localhost",
|
||||
"Port": 6855
|
||||
},
|
||||
"BatteryNodes": [ // this is a list of nodes
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6
|
||||
]
|
||||
},
|
||||
"S3": { // this is parameters of S3 Buckets and co
|
||||
"Bucket": "8-3e5b3069-214a-43ee-8d85-57d72000c19d",
|
||||
"Region": "sos-ch-dk-2",
|
||||
"Provider": "exo.io",
|
||||
"Key": "EXO502627299197f83e8b090f63",
|
||||
"Secret": "jUNYJL6B23WjndJnJlgJj4rc1i7uh981u5Aba5xdA5s",
|
||||
"ContentType": "text/plain; charset=utf-8",
|
||||
"Host": "8-3e5b3069-214a-43ee-8d85-57d72000c19d.sos-ch-dk-2.exo.io",
|
||||
"Url": "https://8-3e5b3069-214a-43ee-8d85-57d72000c19d.sos-ch-dk-2.exo.io"
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
Prototype ie-entwicklung@10.2.3.115 Prototype
|
||||
Salimax0001 ie-entwicklung@10.2.3.104 Marti Technik (Bern)
|
||||
Salimax0002 ie-entwicklung@10.2.4.29 Weidmann d (ZG)
|
||||
Salimax0002 ie-entwicklung@10.2.4.29 Weidmann Oberwil (ZG)
|
||||
Salimax0003 ie-entwicklung@10.2.4.33 Elektrotechnik Stefan GmbH
|
||||
Salimax0004 ie-entwicklung@10.2.4.32 Biohof Gubelmann (Walde)
|
||||
Salimax0004A ie-entwicklung@10.2.4.153
|
||||
|
|
@ -9,6 +9,4 @@ Salimax0005 ie-entwicklung@10.2.4.36 Schreinerei Schönthal (Thu
|
|||
Salimax0006 ie-entwicklung@10.2.4.35 Steakhouse Mettmenstetten
|
||||
Salimax0007 ie-entwicklung@10.2.4.154 LerchenhofHerr Twannberg
|
||||
Salimax0008 ie-entwicklung@10.2.4.113 Wittmann Kottingbrunn
|
||||
Salimax0010 ie-entwicklung@10.2.4.211 Mohatech 1 (Beat Moser)
|
||||
Salimax0011 ie-entwicklung@10.2.4.239 Thomas Tschirren (Enggistein)
|
||||
SalidomoServer ig@134.209.238.170
|
||||
|
|
@ -21,24 +21,10 @@
|
|||
<ProjectReference Include="../../Lib/Devices/Trumpf/TruConvertDc/TruConvertDc.csproj" />
|
||||
<ProjectReference Include="../../Lib/Units/Units.csproj" />
|
||||
<ProjectReference Include="../../Lib/Utils/Utils.csproj" />
|
||||
<ProjectReference Include="../../Lib/Devices/Amax5070/Amax5070.csproj" />
|
||||
<ProjectReference Include="../../Lib/Devices/Adam6060/Adam6060.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="resources\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Remove="src\temp\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<EmbeddedResource Remove="src\temp\**" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="src\temp\**" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -3,71 +3,21 @@
|
|||
dotnet_version='net6.0'
|
||||
salimax_ip="$1"
|
||||
username='ie-entwicklung'
|
||||
root_password='Salimax4x25'
|
||||
set -e
|
||||
|
||||
# Define directories and configurations
|
||||
DEST_DIR="/home/$username/salimax/SaliMax" # Path to the specific file
|
||||
BACKUP_ROOT="/home/$username/salimax/salimax_backup" # Base backup directory
|
||||
TIMESTAMP=$(date +'%Y%m%d_%H%M%S')
|
||||
BACKUP_DIR="${BACKUP_ROOT}/script_backup_${TIMESTAMP}"
|
||||
# Backup directory with unique name
|
||||
MAX_BACKUPS=5 # Limit to 5 backups
|
||||
set -e
|
||||
|
||||
echo -e "\n============================ Build ============================\n"
|
||||
|
||||
# Build the project
|
||||
dotnet publish \
|
||||
./SaliMax.csproj \
|
||||
-p:PublishTrimmed=false \
|
||||
-c Release \
|
||||
-r linux-x64
|
||||
|
||||
echo -e "\n============================ Backup Old SaliMax File ============================\n"
|
||||
|
||||
# SSH into the remote machine and create the backup directory
|
||||
ssh $username@$salimax_ip "mkdir -p $BACKUP_DIR"
|
||||
|
||||
# Move the 'SaliMax' file to the backup folder on the remote machine
|
||||
ssh $username@$salimax_ip "rsync -av --exclude='${BACKUP_ROOT}/script_backup_*' --exclude='salimax_backup/*' $DEST_DIR $BACKUP_DIR/"
|
||||
|
||||
echo -e "\n============================ Cleanup Old Backups ============================\n"
|
||||
|
||||
# Cleanup old backups if the total number exceeds the maximum allowed
|
||||
echo "Existing backups:"
|
||||
ssh $username@$salimax_ip "ls -1d ${BACKUP_ROOT}/script_backup_* 2>/dev/null"
|
||||
|
||||
# Count the backups created by this script
|
||||
BACKUP_COUNT=$(ssh $username@$salimax_ip "ls -1d ${BACKUP_ROOT}/script_backup_* 2>/dev/null | wc -l")
|
||||
echo "Current number of backups: $BACKUP_COUNT"
|
||||
|
||||
if [ "$BACKUP_COUNT" -gt "$MAX_BACKUPS" ]; then
|
||||
# Find and delete the oldest backup created by the script
|
||||
OLD_BACKUP=$(ssh $username@$salimax_ip "find ${BACKUP_ROOT} -type d -name 'script_backup_*' | sort | head -n 1")
|
||||
echo "Old backup to delete: $OLD_BACKUP"
|
||||
|
||||
if [ -n "$OLD_BACKUP" ]; then
|
||||
echo "Backup limit reached. Deleting old backup: $OLD_BACKUP"
|
||||
ssh $username@$salimax_ip "rm -rf $OLD_BACKUP" && echo "Deleted: $OLD_BACKUP" || echo "Failed to delete: $OLD_BACKUP"
|
||||
else
|
||||
echo "No valid backups to delete."
|
||||
fi
|
||||
else
|
||||
echo "Backup limit not reached. Current number of backups: $BACKUP_COUNT"
|
||||
fi
|
||||
|
||||
|
||||
echo -e "\n============================ Deploy ============================\n"
|
||||
|
||||
# Deploy new files to the remote destination
|
||||
rsync -v \
|
||||
--exclude '*.pdb' \
|
||||
./bin/Release/$dotnet_version/linux-x64/publish/* \
|
||||
$username@"$salimax_ip":~/salimax
|
||||
|
||||
echo -e "\nDeployment complete!\n"
|
||||
|
||||
|
||||
#echo -e "\n============================ Execute ============================\n"
|
||||
|
||||
#sshpass -p "$root_password" ssh -o StrictHostKeyChecking=no -t "$username"@"$salimax_ip" "echo '$root_password' | sudo -S sh -c 'cd salimax && ./restart'" 2>/dev/null
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
dotnet_version='net6.0'
|
||||
salimax_ip="$1"
|
||||
username='ie-entwicklung'
|
||||
root_password='Salimax4x25'
|
||||
|
||||
set -e
|
||||
|
||||
echo -e "\n============================ Build ============================\n"
|
||||
|
||||
dotnet publish \
|
||||
./SaliMax.csproj \
|
||||
-p:PublishTrimmed=false \
|
||||
-c Release \
|
||||
-r linux-x64
|
||||
|
||||
echo -e "\n============================ Deploy ============================\n"
|
||||
#ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.29" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.211")
|
||||
#ip_addresses=("10.2.4.154" "10.2.4.29")
|
||||
ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.29" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" )
|
||||
|
||||
|
||||
|
||||
for ip_address in "${ip_addresses[@]}"; do
|
||||
rsync -v \
|
||||
--exclude '*.pdb' \
|
||||
./bin/Release/$dotnet_version/linux-x64/publish/* \
|
||||
$username@"$ip_address":~/salimax
|
||||
|
||||
ssh "$username"@"$ip_address" "cd salimax && echo '$root_password' | sudo -S ./restart"
|
||||
|
||||
echo "Deployed and ran commands on $ip_address"
|
||||
done
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,284 +0,0 @@
|
|||
#!/usr/bin/python2 -u
|
||||
# coding=utf-8
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import serial
|
||||
import logging
|
||||
from sys import argv, exit
|
||||
from datetime import datetime
|
||||
from pymodbus.pdu import ModbusRequest, ModbusResponse, ExceptionResponse
|
||||
from pymodbus.other_message import ReportSlaveIdRequest
|
||||
from pymodbus.exceptions import ModbusException
|
||||
from pymodbus.pdu import ExceptionResponse
|
||||
from pymodbus.factory import ClientDecoder
|
||||
from pymodbus.client import ModbusSerialClient as Modbus
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
|
||||
|
||||
# trick the pycharm type-checker into thinking Callable is in scope, not used at runtime
|
||||
# noinspection PyUnreachableCode
|
||||
if False:
|
||||
from typing import List, Optional, NoReturn
|
||||
|
||||
RESET_REGISTER = 0x2087
|
||||
FIRMWARE_VERSION_REGISTER = 1054
|
||||
SERIAL_STARTER_DIR = '/opt/victronenergy/serial-starter/'
|
||||
INSTALLATION_NAME_FILE = '/data/innovenergy/openvpn/installation-name'
|
||||
OUTPUT_DIR = '/data/innovenergy'
|
||||
|
||||
|
||||
class ReadLogRequest(ModbusRequest):
|
||||
|
||||
function_code = 0x42
|
||||
_rtu_frame_size = 5 # not used
|
||||
|
||||
def __init__(self, address = None, **kwargs):
|
||||
|
||||
ModbusRequest.__init__(self, **kwargs)
|
||||
self.sub_function = 0 if address is None else 1
|
||||
self.address = address
|
||||
|
||||
# FUGLY as hell, but necessary bcs PyModbus cannot deal
|
||||
# with responses that have lengths depending on the sub_function.
|
||||
# it goes without saying that this isn't thread-safe
|
||||
ReadLogResponse._rtu_frame_size = 9 if self.sub_function == 0 else 9+128
|
||||
|
||||
def encode(self):
|
||||
|
||||
if self.sub_function == 0:
|
||||
return struct.pack('>B', self.sub_function)
|
||||
else:
|
||||
return struct.pack('>BI', self.sub_function, self.address)
|
||||
|
||||
def decode(self, data):
|
||||
self.sub_function = struct.unpack('>B', data)
|
||||
|
||||
def execute(self, context):
|
||||
print("EXECUTE1")
|
||||
|
||||
def get_response_pdu_size(self):
|
||||
return ReadLogResponse._rtu_frame_size - 3
|
||||
|
||||
def __str__(self):
|
||||
return "ReadLogAddressRequest"
|
||||
|
||||
|
||||
class ReadLogResponse(ModbusResponse):
|
||||
|
||||
function_code = 0x42
|
||||
_rtu_frame_size = 9 # the WHOLE frame incl crc
|
||||
|
||||
def __init__(self, sub_function=0, address=b'\x00', data=None, **kwargs):
|
||||
ModbusResponse.__init__(self, **kwargs)
|
||||
self.sub_function = sub_function
|
||||
self.address = address
|
||||
self.data = data
|
||||
|
||||
def encode(self):
|
||||
pass
|
||||
|
||||
def decode(self, data):
|
||||
self.address, self.address = struct.unpack_from(">BI", data)
|
||||
self.data = data[5:]
|
||||
|
||||
def __str__(self):
|
||||
arguments = (self.function_code, self.address)
|
||||
return "ReadLogAddressResponse(%s, %s)" % arguments
|
||||
|
||||
# unfortunately we have to monkey-patch this global table because
|
||||
# the current (victron) version of PyModbus does not have a
|
||||
# way to "register" new function-codes yet
|
||||
ClientDecoder.function_table.append(ReadLogResponse)
|
||||
|
||||
|
||||
class LockTTY(object):
|
||||
|
||||
def __init__(self, tty):
|
||||
# type: (str) -> None
|
||||
self.tty = tty
|
||||
|
||||
def __enter__(self):
|
||||
os.system(SERIAL_STARTER_DIR + 'stop-tty.sh ' + self.tty)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
os.system(SERIAL_STARTER_DIR + 'start-tty.sh ' + self.tty)
|
||||
|
||||
|
||||
def wrap_try_except(error_msg):
|
||||
def decorate(f):
|
||||
def applicator(*args, **kwargs):
|
||||
try:
|
||||
return f(*args, **kwargs)
|
||||
except:
|
||||
print(error_msg)
|
||||
exit(1)
|
||||
return applicator
|
||||
return decorate
|
||||
|
||||
|
||||
def init_modbus(tty):
|
||||
# type: (str) -> Modbus
|
||||
|
||||
return Modbus(
|
||||
port='/dev/' + tty,
|
||||
method='rtu',
|
||||
baudrate=115200,
|
||||
stopbits=1,
|
||||
bytesize=8,
|
||||
timeout=0.5, # seconds
|
||||
parity=serial.PARITY_ODD)
|
||||
|
||||
|
||||
@wrap_try_except("Failed to download BMS log!")
|
||||
def download_log(modbus, node_id, battery_id):
|
||||
# type: (Modbus, int, str) -> NoReturn
|
||||
|
||||
# Get address of latest log entry
|
||||
# request = ReadLogRequest(unit=slave_id)
|
||||
|
||||
print ('downloading BMS log from node ' + str(node_id) + ' ...')
|
||||
|
||||
progress = -1
|
||||
log_file = battery_id + "-node" + str(node_id) + "-" + datetime.now().strftime('%d-%m-%Y') + ".bin"
|
||||
print(log_file)
|
||||
|
||||
with open(log_file, 'w') as f:
|
||||
|
||||
eof = 0x200000
|
||||
record = 0x40
|
||||
for address in range(0, eof, 2*record):
|
||||
|
||||
percent = int(100*address/eof)
|
||||
|
||||
if percent != progress:
|
||||
progress = percent
|
||||
print('\r{}% '.format(progress),end='')
|
||||
|
||||
request = ReadLogRequest(address, slave=node_id)
|
||||
result = modbus.execute(request) # type: ReadLogResponse
|
||||
|
||||
address1 = "{:06X}".format(address)
|
||||
address2 = "{:06X}".format(address+record)
|
||||
|
||||
data1 = result.data[:record]
|
||||
data2 = result.data[record:]
|
||||
|
||||
line1 = address1 + ":" + ''.join('{:02X}'.format(byte) for byte in data1)
|
||||
line2 = address2 + ":" + ''.join('{:02X}'.format(byte) for byte in data2)
|
||||
|
||||
lines = line1 + "\n" + line2 + "\n"
|
||||
f.write(lines)
|
||||
|
||||
print("\r100%")
|
||||
print("done")
|
||||
print("wrote log to " + log_file)
|
||||
|
||||
return True
|
||||
|
||||
|
||||
@wrap_try_except("Failed to contact battery!")
|
||||
def identify_battery(modbus, node_id):
|
||||
# type: (Modbus, int) -> str
|
||||
|
||||
target = 'battery #' + str(node_id)
|
||||
print('contacting ' + target + ' ...')
|
||||
|
||||
request = ReportSlaveIdRequest(slave=node_id)
|
||||
response = modbus.execute(request)
|
||||
|
||||
index_of_ff = response.identifier.find(b'\xff')
|
||||
sid_response = response.identifier[index_of_ff + 1:].decode('utf-8').split(' ')
|
||||
|
||||
response = modbus.read_input_registers(address=FIRMWARE_VERSION_REGISTER, count=1, slave=node_id)
|
||||
|
||||
fw = '{0:0>4X}'.format(response.registers[0])
|
||||
print("log string is",sid_response[0]+"-"+sid_response[1]+"-"+fw)
|
||||
|
||||
#return re.sub(" +", "-", sid + " " + fw)
|
||||
return sid_response[0]+"-"+sid_response[1]+"-"+fw
|
||||
|
||||
|
||||
def is_int(value):
|
||||
# type: (str) -> bool
|
||||
try:
|
||||
_ = int(value)
|
||||
return True
|
||||
except ValueError:
|
||||
return False
|
||||
|
||||
|
||||
def print_usage():
|
||||
print ('Usage: ' + __file__ + ' <slave id> <serial device>')
|
||||
print ('Example: ' + __file__ + ' 2 ttyUSB0')
|
||||
print ('')
|
||||
print ('You can omit the "ttyUSB" prefix of the serial device:')
|
||||
print (' ' + __file__ + ' 2 0')
|
||||
print ('')
|
||||
print ('You can omit the serial device entirely when the "com.victronenergy.battery.<serial device>" service is running:')
|
||||
print (' ' + __file__ + ' 2')
|
||||
print ('')
|
||||
|
||||
|
||||
def get_tty_from_battery_service_name():
|
||||
# type: () -> Optional[str]
|
||||
|
||||
import dbus
|
||||
bus = dbus.SystemBus()
|
||||
|
||||
tty = (
|
||||
name.split('.')[-1]
|
||||
for name in bus.list_names()
|
||||
if name.startswith('com.victronenergy.battery.')
|
||||
)
|
||||
|
||||
return next(tty, None)
|
||||
|
||||
|
||||
def parse_tty(tty):
|
||||
# type: (Optional[str]) -> str
|
||||
|
||||
if tty is None:
|
||||
return get_tty_from_battery_service_name()
|
||||
|
||||
if is_int(tty):
|
||||
return 'ttyUSB' + argv[1]
|
||||
else:
|
||||
return tty
|
||||
|
||||
|
||||
def parse_cmdline_args(argv):
|
||||
# type: (List[str]) -> (str, int)
|
||||
|
||||
slave_id = element_at_or_none(argv, 0)
|
||||
tty = parse_tty(element_at_or_none(argv, 1))
|
||||
|
||||
if slave_id is None or tty is None:
|
||||
print_usage()
|
||||
exit(2)
|
||||
|
||||
print("tty=",tty)
|
||||
print("slave id= ",slave_id)
|
||||
|
||||
return tty, int(slave_id)
|
||||
|
||||
|
||||
def element_at_or_none(lst, index):
|
||||
return next(iter(lst[index:]), None)
|
||||
|
||||
|
||||
def main(argv):
|
||||
# type: (List[str]) -> ()
|
||||
|
||||
tty, node_id = parse_cmdline_args(argv)
|
||||
|
||||
with init_modbus(tty) as modbus:
|
||||
battery_id = identify_battery(modbus, node_id)
|
||||
download_log(modbus, node_id, battery_id)
|
||||
|
||||
exit(0)
|
||||
|
||||
|
||||
main(argv[1:])
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
#Prototype 10.2.3.115 Prototype
|
||||
#Salimax0001 10.2.3.104 Marti Technik (Bern)
|
||||
#Salimax0002 10.2.4.29 Weidmann d (ZG)
|
||||
#Salimax0003 10.2.4.33 Elektrotechnik Stefan GmbH
|
||||
#Salimax0004 10.2.4.32 Biohof Gubelmann (Walde)
|
||||
#Salimax0005 10.2.4.36 Schreinerei Schönthal (Thun)
|
||||
#Salimax0006 10.2.4.35 Steakhouse Mettmenstetten
|
||||
#Salimax0007 10.2.4.154 LerchenhofHerr Twannberg
|
||||
#Salimax0008 10.2.4.113 Wittmann Kottingbrunn
|
||||
|
||||
dotnet_version='net6.0'
|
||||
ip_address="$1"
|
||||
battery_ids="$2"
|
||||
username='ie-entwicklung'
|
||||
root_password='Salimax4x25'
|
||||
|
||||
if [ "$#" -lt 2 ]; then
|
||||
echo "Error: Insufficient arguments. Usage: $0 <ip_address> <battery_ids>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Function to expand battery ids from a range
|
||||
expand_battery_ids() {
|
||||
local range="$1"
|
||||
local expanded_ids=()
|
||||
|
||||
IFS='-' read -r start end <<< "$range"
|
||||
for ((i = start; i <= end; i++)); do
|
||||
expanded_ids+=("$i")
|
||||
done
|
||||
|
||||
echo "${expanded_ids[@]}"
|
||||
}
|
||||
|
||||
# Check if battery_ids_arg contains a hyphen indicating a range
|
||||
if [[ "$battery_ids" == *-* ]]; then
|
||||
# Expand battery ids from the range
|
||||
battery_ids=$(expand_battery_ids "$battery_ids")
|
||||
else
|
||||
# Use the provided battery ids
|
||||
battery_ids=("$battery_ids")
|
||||
fi
|
||||
|
||||
echo "ip_address: $ip_address"
|
||||
echo "Battery_ids: ${battery_ids[@]}"
|
||||
|
||||
#ip_addresses=("10.2.3.115" "10.2.3.104" "10.2.4.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "10.2.4.154" "10.2.4.113" "10.2.4.29")
|
||||
#battery_ids=("2" "3" "4" "5" "6" "7" "8" "9" "10" "11")
|
||||
|
||||
set -e
|
||||
|
||||
scp download-bms-log "$username"@"$ip_address":/home/"$username"
|
||||
ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S systemctl stop battery.service"
|
||||
ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S apt install python3-pip -y"
|
||||
ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S pip3 install pymodbus"
|
||||
|
||||
for battery in "${battery_ids[@]}"; do
|
||||
ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S python3 download-bms-log " "$battery" " ttyUSB0"
|
||||
done
|
||||
ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S systemctl start battery.service"
|
||||
ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S rm download-bms-log"
|
||||
scp "$username"@"$ip_address":/home/"$username/*.bin" .
|
||||
ssh "$username"@"$ip_address" "echo '$root_password' | sudo -S rm *.bin"
|
||||
|
||||
echo "Deployed and ran commands on $ip_address"
|
||||
done
|
||||
|
||||
|
||||
|
|
@ -1,355 +0,0 @@
|
|||
using InnovEnergy.App.SaliMax.Ess;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Linq;
|
||||
using static System.Double;
|
||||
|
||||
namespace InnovEnergy.App.SaliMax.AggregationService;
|
||||
|
||||
public static class Aggregator
|
||||
{
|
||||
|
||||
public static async Task HourlyDataAggregationManager()
|
||||
{
|
||||
var currentDateTime = DateTime.Now;
|
||||
var nextRoundedHour = currentDateTime.AddHours(1).AddMinutes(-currentDateTime.Minute).AddSeconds(-currentDateTime.Second);
|
||||
|
||||
// Calculate the time until the next rounded hour
|
||||
var timeUntilNextHour = nextRoundedHour - currentDateTime;
|
||||
|
||||
// Output the current and next rounded hour times
|
||||
Console.WriteLine("------------------------------------------HourlyDataAggregationManager-------------------------------------------");
|
||||
Console.WriteLine("Current Date and Time: " + currentDateTime);
|
||||
Console.WriteLine("Next Rounded Hour: " + nextRoundedHour);
|
||||
// Output the time until the next rounded hour
|
||||
Console.WriteLine("Waiting for " + timeUntilNextHour.TotalMinutes + " minutes...");
|
||||
Console.WriteLine("-----------------------------------------------------------------------------------------------------------------");
|
||||
|
||||
// Wait until the next rounded hour
|
||||
await Task.Delay(timeUntilNextHour);
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
AggregatedData hourlyAggregatedData = CreateHourlyData("JsonLogDirectory",DateTime.Now.AddHours(-1).ToUnixTime(),DateTime.Now.ToUnixTime());
|
||||
hourlyAggregatedData.Save("HourlyData");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("An error has occured when calculating hourly aggregated data, exception is:\n" + e);
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromHours(1));
|
||||
}
|
||||
}
|
||||
|
||||
public static async Task DailyDataAggregationManager()
|
||||
{
|
||||
var currentDateTime = DateTime.Now;
|
||||
var nextRoundedHour = currentDateTime.AddDays(1).AddHours(-currentDateTime.Hour).AddMinutes(-currentDateTime.Minute).AddSeconds(-currentDateTime.Second);
|
||||
|
||||
// Calculate the time until the next rounded hour
|
||||
var timeUntilNextDay = nextRoundedHour - currentDateTime;
|
||||
Console.WriteLine("------------------------------------------DailyDataAggregationManager-------------------------------------------");
|
||||
// Output the current and next rounded hour times
|
||||
Console.WriteLine("Current Date and Time: " + currentDateTime);
|
||||
Console.WriteLine("Next Rounded Hour: " + nextRoundedHour);
|
||||
// Output the time until the next rounded hour
|
||||
Console.WriteLine("Waiting for " + timeUntilNextDay.TotalHours + " hours...");
|
||||
Console.WriteLine("-----------------------------------------------------------------------------------------------------------------");
|
||||
|
||||
// Wait until the next rounded hour
|
||||
await Task.Delay(timeUntilNextDay);
|
||||
|
||||
while (true)
|
||||
{
|
||||
try
|
||||
{
|
||||
var currentTime = DateTime.Now;
|
||||
AggregatedData dailyAggregatedData = CreateDailyData("HourlyData",currentTime.AddDays(-1).ToUnixTime(),currentTime.ToUnixTime());
|
||||
dailyAggregatedData.Save("DailyData");
|
||||
if (await dailyAggregatedData.PushToS3())
|
||||
{
|
||||
//DeleteHourlyData("HourlyData",currentTime.ToUnixTime());
|
||||
//AggregatedData.DeleteDailyData("DailyData");
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine("An error has occured when calculating daily aggregated data, exception is:\n" + e);
|
||||
}
|
||||
await Task.Delay(TimeSpan.FromDays(1));
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeleteHourlyData(String myDirectory, Int64 beforeTimestamp)
|
||||
{
|
||||
var jsonFiles = Directory.GetFiles(myDirectory, "*.json");
|
||||
Console.WriteLine("Delete data before"+beforeTimestamp);
|
||||
foreach (var jsonFile in jsonFiles)
|
||||
{
|
||||
if (IsFileWithinTimeRange(jsonFile, 0, beforeTimestamp))
|
||||
{
|
||||
File.Delete(jsonFile);
|
||||
Console.WriteLine($"Deleted hourly data file: {jsonFile}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// this for test
|
||||
private static AggregatedData CreateHourlyData(String myDirectory, Int64 afterTimestamp, Int64 beforeTimestamp)
|
||||
{
|
||||
// Get all CSV files in the specified directory
|
||||
var jsonFiles = Directory.GetFiles(myDirectory, "*.json");
|
||||
var batterySoc = new List<Double>();
|
||||
var pvPowerSum = new List<Double>();
|
||||
var heatingPower = new List<Double>();
|
||||
var gridPowerImport = new List<Double>();
|
||||
var gridPowerExport = new List<Double>();
|
||||
var batteryDischargePower = new List<Double>();
|
||||
var batteryChargePower = new List<Double>();
|
||||
|
||||
|
||||
Console.WriteLine("File timestamp should start after "+ afterTimestamp);
|
||||
|
||||
foreach (var jsonFile in jsonFiles)
|
||||
{
|
||||
if (jsonFile == "LogDirectory/log.json")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsFileWithinTimeRange(jsonFile, afterTimestamp, beforeTimestamp))
|
||||
{
|
||||
try
|
||||
{
|
||||
// Read and parse JSON
|
||||
|
||||
var jsonData = File.ReadAllText(jsonFile);
|
||||
|
||||
// Step 2: Find the first '{' character and trim everything before it
|
||||
int startIndex = jsonData.IndexOf('{');
|
||||
if (startIndex != -1)
|
||||
{
|
||||
jsonData = jsonData.Substring(startIndex); // Trim everything before '{'
|
||||
}
|
||||
|
||||
var jsonObject = JObject.Parse(jsonData);
|
||||
|
||||
|
||||
if (jsonObject["Battery"] != null && jsonObject["Battery"]["Soc"] != null)
|
||||
{
|
||||
batterySoc.Add((double)jsonObject["Battery"]["Soc"]);
|
||||
}
|
||||
if (jsonObject["PvOnDc"] != null && jsonObject["PvOnDc"]["DcWh"] != null)
|
||||
{
|
||||
pvPowerSum.Add((double)jsonObject["PvOnDc"]["DcWh"]);
|
||||
}
|
||||
if (jsonObject["Battery"] != null && jsonObject["Battery"]["Dc"]["Power"] != null)
|
||||
{
|
||||
double batteryPower = (double)jsonObject["Battery"]["Dc"]["Power"];
|
||||
if (batteryPower < 0)
|
||||
batteryDischargePower.Add(batteryPower);
|
||||
else
|
||||
batteryChargePower.Add(batteryPower);
|
||||
}
|
||||
if (jsonObject["GridMeter"] != null && jsonObject["GridMeter"]["ActivePowerExportT3"] != null)
|
||||
{
|
||||
gridPowerExport.Add((double)jsonObject["GridMeter"]["ActivePowerExportT3"]);
|
||||
}
|
||||
if (jsonObject["GridMeter"] != null && jsonObject["GridMeter"]["ActivePowerImportT3"] != null)
|
||||
{
|
||||
gridPowerImport.Add((double)jsonObject["GridMeter"]["ActivePowerImportT3"]);
|
||||
}
|
||||
if (jsonObject["Battery"] != null && jsonObject["Battery"]["HeatingPower"] != null)
|
||||
{
|
||||
heatingPower.Add((double)jsonObject["Battery"]["HeatingPower"]);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Failed to parse JSON file {jsonFile}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//Average Power (Watts)= Sum of Power Readings/Number of Readings
|
||||
|
||||
//Then, you can use the average power in the energy formula:
|
||||
//
|
||||
//Energy (kWh)= (Average Power / 3600) × Time (1 seconds)
|
||||
//
|
||||
// Dividing the Average power readings by 3600 converts the result from watt-seconds to kilowatt-hours.
|
||||
|
||||
var dischargingEnergy = (batteryDischargePower.Any() ? batteryDischargePower.Average() : 0.0) / 3600;
|
||||
var chargingEnergy = (batteryChargePower.Any() ? batteryChargePower.Average() : 0.0) / 3600;
|
||||
var heatingPowerAvg = (heatingPower.Any() ? heatingPower.Average() : 0.0) / 3600;
|
||||
|
||||
var dMaxSoc = batterySoc.Any() ? batterySoc.Max() : 0.0;
|
||||
var dMinSoc = batterySoc.Any() ? batterySoc.Min() : 0.0;
|
||||
var dSumGridExportPower = gridPowerExport.Any() ? gridPowerExport.Max() - gridPowerExport.Min(): 0.0;
|
||||
var dSumGridImportPower = gridPowerImport.Any() ? gridPowerImport.Max() - gridPowerImport.Min(): 0.0;
|
||||
var dSumPvPower = pvPowerSum.Any() ? pvPowerSum.Max() : 0.0;
|
||||
|
||||
|
||||
AggregatedData aggregatedData = new AggregatedData
|
||||
{
|
||||
MaxSoc = Math.Round(dMaxSoc, 2),
|
||||
MinSoc = Math.Round(dMinSoc, 2) ,
|
||||
DischargingBatteryPower = Math.Round(dischargingEnergy, 2) ,
|
||||
ChargingBatteryPower = Math.Round(chargingEnergy, 2) ,
|
||||
GridExportPower = Math.Round(dSumGridExportPower, 2) ,
|
||||
GridImportPower = Math.Round(dSumGridImportPower, 2) ,
|
||||
PvPower = Math.Round(dSumPvPower, 2) ,
|
||||
HeatingPower = Math.Round(heatingPowerAvg, 2)
|
||||
};
|
||||
|
||||
// Print the stored CSV data for verification
|
||||
Console.WriteLine($"Max SOC: {aggregatedData.MaxSoc}");
|
||||
Console.WriteLine($"Min SOC: {aggregatedData.MinSoc}");
|
||||
|
||||
Console.WriteLine($"DischargingBatteryBattery: {aggregatedData.DischargingBatteryPower}");
|
||||
Console.WriteLine($"ChargingBatteryPower: {aggregatedData.ChargingBatteryPower}");
|
||||
|
||||
Console.WriteLine($"SumGridExportPower: {aggregatedData.GridExportPower}");
|
||||
Console.WriteLine($"SumGridImportPower: {aggregatedData.GridImportPower}");
|
||||
|
||||
Console.WriteLine($"Min SOC: {aggregatedData.MinSoc}");
|
||||
|
||||
|
||||
Console.WriteLine("CSV data reading and storage completed.");
|
||||
Console.WriteLine("-----------------------------------------------------------------------------------------------------------------");
|
||||
|
||||
return aggregatedData;
|
||||
}
|
||||
|
||||
private static AggregatedData CreateDailyData(String myDirectory, Int64 afterTimestamp, Int64 beforeTimestamp)
|
||||
{
|
||||
// Get all CSV files in the specified directory
|
||||
var jsonFiles = Directory.GetFiles(myDirectory, "*.json");
|
||||
var batterySoc = new List<Double>();
|
||||
var pvPower = new List<Double>();
|
||||
var gridPowerImport = new List<Double>();
|
||||
var gridPowerExport = new List<Double>();
|
||||
var batteryDischargePower = new List<Double>();
|
||||
var batteryChargePower = new List<Double>();
|
||||
var heatingPowerAvg = new List<Double>();
|
||||
|
||||
|
||||
|
||||
Console.WriteLine("File timestamp should start after "+ afterTimestamp);
|
||||
|
||||
foreach (var jsonFile in jsonFiles)
|
||||
{
|
||||
if (jsonFile == "JsonLogDirectory/log.json")
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (IsFileWithinTimeRange(jsonFile, afterTimestamp, beforeTimestamp))
|
||||
{
|
||||
|
||||
try
|
||||
{
|
||||
var jsonData = File.ReadAllText(jsonFile);
|
||||
//Console.WriteLine("Parse file "+jsonFile);
|
||||
|
||||
// Parse JSON into a Dictionary
|
||||
var jsonDict = JsonConvert.DeserializeObject<Dictionary<string, Double>>(jsonData);
|
||||
|
||||
// Process values
|
||||
foreach (var (variableName, value) in jsonDict)
|
||||
{
|
||||
switch (variableName)
|
||||
{
|
||||
case "MinSoc":
|
||||
case "MaxSoc":
|
||||
batterySoc.Add(value);
|
||||
break;
|
||||
|
||||
case "PvPower":
|
||||
pvPower.Add(value);
|
||||
break;
|
||||
|
||||
case "DischargingBatteryPower":
|
||||
batteryDischargePower.Add(value);
|
||||
break;
|
||||
|
||||
case "ChargingBatteryPower":
|
||||
batteryChargePower.Add(value);
|
||||
break;
|
||||
|
||||
case "GridExportPower":
|
||||
gridPowerExport.Add(value);
|
||||
break;
|
||||
|
||||
case "GridImportPower":
|
||||
gridPowerImport.Add(value);
|
||||
break;
|
||||
|
||||
case "HeatingPower":
|
||||
heatingPowerAvg.Add(value);
|
||||
break;
|
||||
|
||||
default:
|
||||
// Ignore unknown variables
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine($"Failed to parse JSON file {jsonFile}: {e.Message}");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
AggregatedData aggregatedData = new AggregatedData
|
||||
{
|
||||
MaxSoc = batterySoc.Any() ? batterySoc.Max() : 0.0,
|
||||
MinSoc = batterySoc.Any() ? batterySoc.Min() : 0.0,
|
||||
DischargingBatteryPower = batteryDischargePower.Any() ? batteryDischargePower.Average(): 0.0,
|
||||
ChargingBatteryPower = batteryChargePower.Any() ? batteryChargePower.Average() : 0.0,
|
||||
GridExportPower = gridPowerExport.Any() ? gridPowerExport.Sum() : 0.0,
|
||||
GridImportPower = gridPowerImport.Any() ? gridPowerImport.Sum() : 0.0,
|
||||
PvPower = pvPower.Any() ? pvPower.Last() : 0.0,
|
||||
HeatingPower = heatingPowerAvg.Any() ? heatingPowerAvg.Average() : 0.0,
|
||||
};
|
||||
|
||||
// Print the stored CSV data for verification
|
||||
Console.WriteLine($"Pv Power: {aggregatedData.PvPower}");
|
||||
Console.WriteLine($"Heating Power: {aggregatedData.HeatingPower}");
|
||||
Console.WriteLine($"Max SOC: {aggregatedData.MaxSoc}");
|
||||
Console.WriteLine($"Min SOC: {aggregatedData.MinSoc}");
|
||||
|
||||
Console.WriteLine($"ChargingBatteryPower: {aggregatedData.DischargingBatteryPower}");
|
||||
Console.WriteLine($"DischargingBatteryBattery: {aggregatedData.ChargingBatteryPower}");
|
||||
|
||||
Console.WriteLine($"SumGridExportPower: {aggregatedData.GridExportPower}");
|
||||
Console.WriteLine($"SumGridImportPower: {aggregatedData.GridImportPower}");
|
||||
|
||||
|
||||
|
||||
Console.WriteLine("CSV data reading and storage completed.");
|
||||
Console.WriteLine("-----------------------------------------------------------------------------------------------------------------");
|
||||
|
||||
return aggregatedData;
|
||||
}
|
||||
|
||||
// Custom method to check if a string is numeric
|
||||
private static Boolean GetVariable(String value, String path)
|
||||
{
|
||||
return value == path;
|
||||
}
|
||||
|
||||
private static Boolean IsFileWithinTimeRange(string filePath, long startTime, long endTime)
|
||||
{
|
||||
var fileTimestamp = long.TryParse(Path.GetFileNameWithoutExtension(filePath).Replace("log_", ""), out var fileTimestamp1) ? fileTimestamp1 : -1;
|
||||
|
||||
return fileTimestamp >= startTime && fileTimestamp < endTime;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,118 +0,0 @@
|
|||
using System.IO.Compression;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using Flurl.Http;
|
||||
using InnovEnergy.App.SaliMax.Devices;
|
||||
using InnovEnergy.App.SaliMax.SystemConfig;
|
||||
using InnovEnergy.Lib.Units;
|
||||
using InnovEnergy.Lib.Utils;
|
||||
using Newtonsoft.Json;
|
||||
using static System.Text.Json.JsonSerializer;
|
||||
|
||||
namespace InnovEnergy.App.SaliMax.AggregationService;
|
||||
// shut up trim warnings
|
||||
#pragma warning disable IL2026
|
||||
|
||||
public class AggregatedData
|
||||
{
|
||||
public required Double MinSoc { get; set; }
|
||||
public required Double MaxSoc { get; set; }
|
||||
public required Double PvPower { get; set; }
|
||||
public required Double DischargingBatteryPower { get; set; }
|
||||
public required Double ChargingBatteryPower { get; set; }
|
||||
public required Double GridExportPower { get; set; }
|
||||
public required Double GridImportPower { get; set; }
|
||||
public required Double HeatingPower { get; set; }
|
||||
|
||||
|
||||
private readonly S3Config? _S3Config = Config.Load().S3;
|
||||
|
||||
public void Save(String directory)
|
||||
{
|
||||
var date = DateTime.Now.ToUnixTime();
|
||||
var defaultHDataPath = Environment.CurrentDirectory + "/" + directory + "/";
|
||||
var dataFilePath = defaultHDataPath + date + ".json";
|
||||
|
||||
if (!Directory.Exists(defaultHDataPath))
|
||||
{
|
||||
Directory.CreateDirectory(defaultHDataPath);
|
||||
Console.WriteLine("Directory created successfully.");
|
||||
}
|
||||
Console.WriteLine("data file path is " + dataFilePath);
|
||||
|
||||
try
|
||||
{
|
||||
// Convert the object to a JSON string
|
||||
var jsonString = JsonConvert.SerializeObject(this, Formatting.None);
|
||||
|
||||
// Write JSON to file
|
||||
File.WriteAllText(dataFilePath, jsonString);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
$"Failed to write config file {dataFilePath}\n{e}".WriteLine();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public static void DeleteDailyData(String directory)
|
||||
{
|
||||
|
||||
var jsonFiles = Directory.GetFiles(directory, "*.json");
|
||||
foreach (var jsonFile in jsonFiles)
|
||||
{
|
||||
File.Delete(jsonFile);
|
||||
Console.WriteLine($"Deleted daily data file: {jsonFile}");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Boolean> PushToS3()
|
||||
{
|
||||
var jsonString = JsonConvert.SerializeObject(this, Formatting.None);
|
||||
if (_S3Config is null)
|
||||
return false;
|
||||
|
||||
var s3Path = DateTime.Now.AddDays(-1).ToString("yyyy-MM-dd") + ".json";
|
||||
var request = _S3Config.CreatePutRequest(s3Path);
|
||||
|
||||
// Compress CSV data to a byte array
|
||||
byte[] compressedBytes;
|
||||
using (var memoryStream = new MemoryStream())
|
||||
{
|
||||
//Create a zip directory and put the compressed file inside
|
||||
using (var archive = new ZipArchive(memoryStream, ZipArchiveMode.Create, true))
|
||||
{
|
||||
var entry = archive.CreateEntry("data.json", CompressionLevel.SmallestSize); // Add CSV data to the ZIP archive
|
||||
using (var entryStream = entry.Open())
|
||||
using (var writer = new StreamWriter(entryStream))
|
||||
{
|
||||
writer.Write(jsonString);
|
||||
}
|
||||
}
|
||||
|
||||
compressedBytes = memoryStream.ToArray();
|
||||
}
|
||||
|
||||
// Encode the compressed byte array as a Base64 string
|
||||
string base64String = Convert.ToBase64String(compressedBytes);
|
||||
|
||||
// Create StringContent from Base64 string
|
||||
var stringContent = new StringContent(base64String, Encoding.UTF8, "application/base64");
|
||||
|
||||
// Upload the compressed data (ZIP archive) to S3
|
||||
var response = await request.PutAsync(stringContent);
|
||||
|
||||
|
||||
if (response.StatusCode != 200)
|
||||
{
|
||||
Console.WriteLine("ERROR: PUT");
|
||||
var error = await response.GetStringAsync();
|
||||
Console.WriteLine(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
namespace InnovEnergy.App.SaliMax.DataTypes;
|
||||
|
||||
public class AlarmOrWarning
|
||||
{
|
||||
public String? Date { get; set; }
|
||||
public String? Time { get; set; }
|
||||
public String? Description { get; set; }
|
||||
public String? CreatedBy { get; set; }
|
||||
}
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
using InnovEnergy.App.SaliMax.SystemConfig;
|
||||
|
||||
namespace InnovEnergy.App.SaliMax.DataTypes;
|
||||
|
||||
public class Configuration
|
||||
{
|
||||
public Double MinimumSoC { get; set; }
|
||||
public Double GridSetPoint { get; set; }
|
||||
public CalibrationChargeType CalibrationChargeState { get; set; }
|
||||
public DateTime CalibrationChargeDate { get; set; }
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// using InnovEnergy.App.SaliMax.SaliMaxRelays;
|
||||
// using InnovEnergy.App.SaliMax.SystemConfig;
|
||||
//
|
||||
// namespace InnovEnergy.App.SaliMax.Controller;
|
||||
//
|
||||
// public class ControlRecord
|
||||
// {
|
||||
// public TruConvertAcControl? AcControlRecord { get; init; }
|
||||
// public TruConvertDcControl? DcControlRecord { get; init; }
|
||||
// public Config? SalimaxConfig { get; init; } // we may have to create record of each
|
||||
// public SaliMaxRelayControl? SalimaxRelays { get; init; } // we may have to create record of each
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
|
|
@ -10,10 +10,8 @@ namespace InnovEnergy.App.SaliMax.Ess;
|
|||
public static class Controller
|
||||
{
|
||||
private static readonly Double BatteryHeatingPower = 200.0; // TODO: move to config
|
||||
private static Boolean _hasRepetitiveCalibrationChargeChecked = false;
|
||||
private static DateTime _nextDayAt10Am = DateTime.Now;
|
||||
|
||||
private static EssMode SelectControlMode(this StatusRecord s)
|
||||
public static EssMode SelectControlMode(this StatusRecord s)
|
||||
{
|
||||
//return EssMode.OptimizeSelfConsumption;
|
||||
|
||||
|
|
@ -30,31 +28,8 @@ public static class Controller
|
|||
{
|
||||
var mode = s.SelectControlMode().WriteLine();
|
||||
|
||||
if (mode is EssMode.Off) // to test on prototype
|
||||
{
|
||||
if (s.StateMachine.State == 28 )
|
||||
{
|
||||
return new EssControl
|
||||
{
|
||||
LimitedBy = EssLimit.NoLimit,
|
||||
Mode = EssMode.OffGrid,
|
||||
PowerCorrection = 0,
|
||||
PowerSetpoint = 0
|
||||
};
|
||||
}
|
||||
if (mode is EssMode.Off or EssMode.NoGridMeter)
|
||||
return EssControl.Default;
|
||||
}
|
||||
|
||||
// if we have no reading from the Grid meter, but we have a grid power (K1 is close),
|
||||
// then we do only heat the battery to avoid discharging the battery and the oscillation between reach min soc and off mode
|
||||
if (mode is EssMode.NoGridMeter)
|
||||
return new EssControl
|
||||
{
|
||||
LimitedBy = EssLimit.NoLimit,
|
||||
Mode = EssMode.NoGridMeter,
|
||||
PowerCorrection = 0,
|
||||
PowerSetpoint = s.Battery == null ? 1000 : s.Battery.Devices.Count * s.Config.BatterySelfDischargePower // 1000 default value for heating the battery
|
||||
};
|
||||
|
||||
var essDelta = s.ComputePowerDelta(mode);
|
||||
|
||||
|
|
@ -120,7 +95,6 @@ public static class Controller
|
|||
|
||||
//var maxInverterChargePower = s.ControlInverterPower(s.Config.MaxInverterPower);
|
||||
var maxBatteryChargePower = s.MaxBatteryChargePower();
|
||||
maxBatteryChargePower.WriteLine("MaxBattery Charge Power");
|
||||
|
||||
return control
|
||||
//.LimitChargePower(, EssLimit.ChargeLimitedByInverterPower)
|
||||
|
|
@ -130,7 +104,7 @@ public static class Controller
|
|||
|
||||
private static EssControl LimitDischargePower(this EssControl control, StatusRecord s)
|
||||
{
|
||||
var maxBatteryDischargeDelta = s.Battery?.Devices.Where(b => b.IoStatus.ConnectedToDcBus).Sum(b => b.MaxDischargePower) ?? 0;
|
||||
var maxBatteryDischargeDelta = s.Battery?.Devices.Where(b => b.ConnectedToDcBus).Sum(b => b.MaxDischargePower) ?? 0;
|
||||
var keepMinSocLimitDelta = s.ControlBatteryPower(s.HoldMinSocPower());
|
||||
|
||||
return control
|
||||
|
|
@ -138,6 +112,10 @@ public static class Controller
|
|||
.LimitDischargePower(keepMinSocLimitDelta , EssLimit.DischargeLimitedByMinSoc);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
private static Double ComputePowerDelta(this StatusRecord s, EssMode mode)
|
||||
{
|
||||
var chargePower = s.AcDc.Devices.Sum(d => d.Status.Nominal.Power.Value);
|
||||
|
|
@ -166,7 +144,7 @@ public static class Controller
|
|||
|
||||
private static Double MaxBatteryChargePower(this StatusRecord s)
|
||||
{
|
||||
// This introduces a limit when we don't have communication with batteries
|
||||
// This introduce a limit when we don't have communication with batteries
|
||||
// Otherwise the limit will be 0 and the batteries will be not heated
|
||||
|
||||
var batteries = s.GetBatteries();
|
||||
|
|
@ -175,6 +153,8 @@ public static class Controller
|
|||
? s.Config.Devices.BatteryNodes.Length * BatteryHeatingPower
|
||||
: batteries.Sum(b => b.MaxChargePower);
|
||||
|
||||
maxChargePower.W().ToDisplayString().WriteLine(" Max Charge Power");
|
||||
|
||||
return maxChargePower;
|
||||
}
|
||||
|
||||
|
|
@ -209,51 +189,21 @@ public static class Controller
|
|||
|
||||
private static Boolean MustDoCalibrationCharge(this StatusRecord statusRecord)
|
||||
{
|
||||
var calibrationChargeForced = statusRecord.Config.ForceCalibrationChargeState;
|
||||
var additionalCalibrationRequired = AdditionalCalibrationDateHasBeenPassed(statusRecord.Config.DayAndTimeForAdditionalCalibration);
|
||||
var repetitiveCalibrationRequired = RepetitiveCalibrationDateHasBeenPassed(statusRecord.Config.DayAndTimeForRepetitiveCalibration);
|
||||
var calibrationChargeForced = statusRecord.Config.ForceCalibrationCharge;
|
||||
var batteryCalibrationChargeRequested = statusRecord.Battery?.CalibrationChargeRequested?? false ;
|
||||
|
||||
var mustDoCalibrationCharge = calibrationChargeForced == CalibrationChargeType.ChargePermanently ||
|
||||
(calibrationChargeForced == CalibrationChargeType.AdditionallyOnce && additionalCalibrationRequired) ||
|
||||
(calibrationChargeForced == CalibrationChargeType.RepetitivelyEvery && repetitiveCalibrationRequired);
|
||||
|
||||
Console.WriteLine("Next Repetitive calibration charge date is "+ statusRecord.Config.DayAndTimeForRepetitiveCalibration);
|
||||
Console.WriteLine("Next Additional calibration charge date is "+ statusRecord.Config.DayAndTimeForAdditionalCalibration);
|
||||
var mustDoCalibrationCharge = batteryCalibrationChargeRequested || calibrationChargeForced == CalibrationChargeType.Yes || calibrationChargeForced == CalibrationChargeType.UntilEoc ;
|
||||
|
||||
if (statusRecord.Battery is not null)
|
||||
{
|
||||
if (calibrationChargeForced == CalibrationChargeType.AdditionallyOnce && statusRecord.Battery.Eoc )
|
||||
if (calibrationChargeForced == CalibrationChargeType.UntilEoc && statusRecord.Battery.Eoc )
|
||||
{
|
||||
statusRecord.Config.ForceCalibrationChargeState = CalibrationChargeType.RepetitivelyEvery;
|
||||
|
||||
}
|
||||
else if (calibrationChargeForced == CalibrationChargeType.RepetitivelyEvery && statusRecord.Battery.Eoc && _hasRepetitiveCalibrationChargeChecked)
|
||||
{
|
||||
statusRecord.Config.DayAndTimeForRepetitiveCalibration = statusRecord.Config.DayAndTimeForRepetitiveCalibration.AddDays(7);
|
||||
_hasRepetitiveCalibrationChargeChecked = false;
|
||||
statusRecord.Config.ForceCalibrationCharge = CalibrationChargeType.No;
|
||||
}
|
||||
}
|
||||
|
||||
return mustDoCalibrationCharge;
|
||||
}
|
||||
|
||||
private static Boolean RepetitiveCalibrationDateHasBeenPassed(DateTime calibrationChargeDate)
|
||||
{
|
||||
if (DateTime.Now >= calibrationChargeDate )
|
||||
{
|
||||
_hasRepetitiveCalibrationChargeChecked = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
private static Boolean AdditionalCalibrationDateHasBeenPassed(DateTime calibrationChargeDate)
|
||||
{
|
||||
if (DateTime.Now >= calibrationChargeDate )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static Double ControlGridPower(this StatusRecord status, Double targetPower)
|
||||
{
|
||||
|
|
@ -265,7 +215,7 @@ public static class Controller
|
|||
);
|
||||
}
|
||||
|
||||
private static Double ControlInverterPower(this StatusRecord status, Double targetInverterPower)
|
||||
public static Double ControlInverterPower(this StatusRecord status, Double targetInverterPower)
|
||||
{
|
||||
return ControlPower
|
||||
(
|
||||
|
|
@ -275,7 +225,7 @@ public static class Controller
|
|||
);
|
||||
}
|
||||
|
||||
private static Double ControlBatteryPower(this StatusRecord status, Double targetBatteryPower)
|
||||
public static Double ControlBatteryPower(this StatusRecord status, Double targetBatteryPower)
|
||||
{
|
||||
return ControlPower
|
||||
(
|
||||
|
|
@ -315,4 +265,5 @@ public static class Controller
|
|||
var ki = i * errorSum;
|
||||
return ki + kp;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ namespace InnovEnergy.App.SaliMax.Ess;
|
|||
public enum EssMode
|
||||
{
|
||||
Off,
|
||||
OffGrid,
|
||||
HeatBatteries,
|
||||
CalibrationCharge,
|
||||
ReachMinSoc,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
using System.Text.Json;
|
||||
using InnovEnergy.App.SaliMax.Devices;
|
||||
using InnovEnergy.App.SaliMax.SaliMaxRelays;
|
||||
using InnovEnergy.App.SaliMax.System;
|
||||
|
|
@ -24,32 +23,11 @@ public record StatusRecord
|
|||
public required AcPowerDevice? AcGridToAcIsland { get; init; }
|
||||
public required DcPowerDevice? AcDcToDcLink { get; init; }
|
||||
public required DcPowerDevice? LoadOnDc { get; init; }
|
||||
public required IRelaysRecord? Relays { get; init; }
|
||||
public required IReadOnlyList<AmptStatus?> PvOnDc { get; init; }
|
||||
public required RelaysRecord? Relays { get; init; }
|
||||
public required AmptStatus? PvOnDc { get; init; }
|
||||
public required Config Config { get; set; }
|
||||
public required SystemLog Log { get; init; } // TODO: init only
|
||||
public required SystemLog Log { get; set; } // TODO: init only
|
||||
|
||||
public required EssControl EssControl { get; set; } // TODO: init only
|
||||
public required StateMachine StateMachine { get; init; }
|
||||
|
||||
|
||||
public string ToJson()
|
||||
{
|
||||
// Try to get the "Battery" property via reflection
|
||||
// var batteryProperty = thing.GetType().GetProperty("Battery");
|
||||
// if (batteryProperty == null)
|
||||
// throw new InvalidOperationException("The object does not have a 'Battery' property.");
|
||||
//
|
||||
// // Retrieve the value of the Battery property
|
||||
// var batteryValue = Battery.GetValue(thing);
|
||||
var jsonOptions = new JsonSerializerOptions { WriteIndented = true };
|
||||
|
||||
// Serialize the Battery property
|
||||
Console.WriteLine("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee");
|
||||
string json = JsonSerializer.Serialize(this.Battery, jsonOptions);
|
||||
Console.WriteLine(json);
|
||||
|
||||
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,8 @@
|
|||
using InnovEnergy.App.SaliMax.DataTypes;
|
||||
|
||||
namespace InnovEnergy.App.SaliMax.Ess;
|
||||
|
||||
public record SystemLog
|
||||
{
|
||||
public required String? Message { get; init; }
|
||||
public required SalimaxAlarmState SalimaxAlarmState { get; init; }
|
||||
public required List<AlarmOrWarning>? SalimaxAlarms { get; set; }
|
||||
public required List<AlarmOrWarning>? SalimaxWarnings { get; set; }
|
||||
|
||||
}
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
using System.Text;
|
||||
|
||||
namespace InnovEnergy.App.SaliMax;
|
||||
|
||||
public class LogFileConcatenator
|
||||
{
|
||||
private readonly string _logDirectory;
|
||||
|
||||
public LogFileConcatenator(String logDirectory = "JsonLogDirectory/")
|
||||
{
|
||||
_logDirectory = logDirectory;
|
||||
}
|
||||
|
||||
public String ConcatenateFiles(int numberOfFiles)
|
||||
{
|
||||
var logFiles = Directory
|
||||
.GetFiles(_logDirectory, "log_*.json")
|
||||
.OrderByDescending(file => file)
|
||||
.Take(numberOfFiles)
|
||||
.OrderBy(file => file)
|
||||
.ToList();
|
||||
|
||||
var concatenatedContent = new StringBuilder();
|
||||
|
||||
foreach (var fileContent in logFiles.Select(File.ReadAllText))
|
||||
{
|
||||
concatenatedContent.AppendLine(fileContent);
|
||||
//concatenatedContent.AppendLine(); // Append an empty line to separate the files // maybe we don't need this
|
||||
}
|
||||
|
||||
return concatenatedContent.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -5,16 +5,17 @@ namespace InnovEnergy.App.SaliMax;
|
|||
|
||||
public class CustomLogger : ILogger
|
||||
{
|
||||
private readonly String _logFilePath;
|
||||
//private readonly Int64 _maxFileSizeBytes;
|
||||
private readonly Int32 _maxLogFileCount;
|
||||
private Int64 _currentFileSizeBytes;
|
||||
private readonly String _LogFilePath;
|
||||
private readonly Int64 _MaxFileSizeBytes;
|
||||
private readonly Int32 _MaxLogFileCount;
|
||||
private Int64 _CurrentFileSizeBytes;
|
||||
|
||||
public CustomLogger(String logFilePath, Int32 maxLogFileCount)
|
||||
public CustomLogger(String logFilePath, Int64 maxFileSizeBytes, Int32 maxLogFileCount)
|
||||
{
|
||||
_logFilePath = logFilePath;
|
||||
_maxLogFileCount = maxLogFileCount;
|
||||
_currentFileSizeBytes = File.Exists(logFilePath) ? new FileInfo(logFilePath).Length : 0;
|
||||
_LogFilePath = logFilePath;
|
||||
_MaxFileSizeBytes = maxFileSizeBytes;
|
||||
_MaxLogFileCount = maxLogFileCount;
|
||||
_CurrentFileSizeBytes = File.Exists(logFilePath) ? new FileInfo(logFilePath).Length : 0;
|
||||
}
|
||||
|
||||
public IDisposable? BeginScope<TState>(TState state) where TState : notnull => throw new NotImplementedException();
|
||||
|
|
@ -25,25 +26,37 @@ public class CustomLogger : ILogger
|
|||
{
|
||||
var logMessage = formatter(state, exception!);
|
||||
|
||||
// Check the file size and rotate the log file if necessary
|
||||
if (_CurrentFileSizeBytes + logMessage.Length >= _MaxFileSizeBytes)
|
||||
{
|
||||
RotateLogFile();
|
||||
_CurrentFileSizeBytes = 0;
|
||||
}
|
||||
|
||||
// Write the log message to the file
|
||||
File.AppendAllText(_LogFilePath, logMessage + Environment.NewLine);
|
||||
_CurrentFileSizeBytes += logMessage.Length;
|
||||
|
||||
//Console.WriteLine(logMessage);
|
||||
}
|
||||
|
||||
private void RotateLogFile()
|
||||
{
|
||||
// Check the log file count and delete the oldest file if necessary
|
||||
var logFileDir = Path.GetDirectoryName(_logFilePath)!;
|
||||
var logFileExt = Path.GetExtension(_logFilePath);
|
||||
var logFileBaseName = Path.GetFileNameWithoutExtension(_logFilePath);
|
||||
var logFileDir = Path.GetDirectoryName(_LogFilePath)!;
|
||||
var logFileExt = Path.GetExtension(_LogFilePath);
|
||||
var logFileBaseName = Path.GetFileNameWithoutExtension(_LogFilePath);
|
||||
|
||||
var logFiles = Directory
|
||||
.GetFiles(logFileDir, $"{logFileBaseName}_*{logFileExt}")
|
||||
.OrderBy(file => file)
|
||||
.ToList();
|
||||
|
||||
if (logFiles.Count >= _maxLogFileCount)
|
||||
{
|
||||
if (logFiles.Count >= _MaxLogFileCount)
|
||||
File.Delete(logFiles.First());
|
||||
}
|
||||
|
||||
var roundedUnixTimestamp = DateTime.Now.ToUnixTime() % 2 == 0 ? DateTime.Now.ToUnixTime() : DateTime.Now.ToUnixTime() + 1;
|
||||
var timestamp = "Timestamp;" + roundedUnixTimestamp + Environment.NewLine;
|
||||
|
||||
// Rename the current log file with a timestamp
|
||||
var logFileBackupPath = Path.Combine(logFileDir, $"{logFileBaseName}_{DateTime.Now.ToUnixTime()}{logFileExt}");
|
||||
File.AppendAllText(logFileBackupPath, timestamp + logMessage + Environment.NewLine);
|
||||
File.Move(_LogFilePath, logFileBackupPath);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ public static class Logger
|
|||
{
|
||||
// Specify the maximum log file size in bytes (e.g., 1 MB)
|
||||
|
||||
//private const Int32 MaxFileSizeBytes = 2024 * 30; // TODO: move to settings
|
||||
private const Int32 MaxFileSizeBytes = 1024 * 30; // TODO: move to settings
|
||||
private const Int32 MaxLogFileCount = 5000; // TODO: move to settings
|
||||
private const String LogFilePath = "JsonLogDirectory/log.json"; // TODO: move to settings
|
||||
private const String LogFilePath = "LogDirectory/log.csv"; // TODO: move to settings
|
||||
|
||||
// ReSharper disable once InconsistentNaming
|
||||
private static readonly ILogger _logger = new CustomLogger(LogFilePath, MaxLogFileCount);
|
||||
private static readonly ILogger _logger = new CustomLogger(LogFilePath, MaxFileSizeBytes, MaxLogFileCount);
|
||||
|
||||
public static T LogInfo<T>(this T t) where T : notnull
|
||||
{
|
||||
|
|
@ -27,7 +27,7 @@ public static class Logger
|
|||
|
||||
public static T LogError<T>(this T t) where T : notnull
|
||||
{
|
||||
//_logger.LogError(t.ToString()); // TODO: check warning
|
||||
_logger.LogError(t.ToString()); // TODO: check warning
|
||||
return t;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
namespace InnovEnergy.App.SaliMax.MiddlewareClasses;
|
||||
|
||||
public class AlarmOrWarning
|
||||
{
|
||||
public String Date { get; set; }
|
||||
public String Time { get; set; }
|
||||
public String Description { get; set; }
|
||||
public String CreatedBy { get; set; }
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
using System.Net;
|
||||
using System.Net.NetworkInformation;
|
||||
using System.Net.Sockets;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using InnovEnergy.App.SaliMax.DataTypes;
|
||||
|
||||
namespace InnovEnergy.App.SaliMax.MiddlewareClasses;
|
||||
|
||||
public static class MiddlewareAgent
|
||||
{
|
||||
private static UdpClient _udpListener = null!;
|
||||
private static IPAddress? _controllerIpAddress;
|
||||
private static EndPoint? _endPoint;
|
||||
|
||||
public static void InitializeCommunicationToMiddleware()
|
||||
{
|
||||
_controllerIpAddress = FindVpnIp();
|
||||
if (Equals(IPAddress.None, _controllerIpAddress))
|
||||
{
|
||||
Console.WriteLine("There is no VPN interface, exiting...");
|
||||
}
|
||||
|
||||
const Int32 udpPort = 9000;
|
||||
_endPoint = new IPEndPoint(_controllerIpAddress, udpPort);
|
||||
|
||||
_udpListener = new UdpClient();
|
||||
_udpListener.Client.Blocking = false;
|
||||
_udpListener.Client.Bind(_endPoint);
|
||||
}
|
||||
|
||||
private static IPAddress FindVpnIp()
|
||||
{
|
||||
const String interfaceName = "innovenergy";
|
||||
|
||||
var networkInterfaces = NetworkInterface.GetAllNetworkInterfaces();
|
||||
|
||||
foreach (var networkInterface in networkInterfaces)
|
||||
{
|
||||
if (networkInterface.Name == interfaceName)
|
||||
{
|
||||
var ipProps = networkInterface.GetIPProperties();
|
||||
var uniCastIPs = ipProps.UnicastAddresses;
|
||||
var controllerIpAddress = uniCastIPs[0].Address;
|
||||
|
||||
Console.WriteLine("VPN IP is: "+ uniCastIPs[0].Address);
|
||||
return controllerIpAddress;
|
||||
}
|
||||
}
|
||||
|
||||
return IPAddress.None;
|
||||
}
|
||||
|
||||
public static Configuration? SetConfigurationFile()
|
||||
{
|
||||
if (_udpListener.Available > 0)
|
||||
{
|
||||
|
||||
IPEndPoint? serverEndpoint = null;
|
||||
|
||||
var replyMessage = "ACK";
|
||||
var replyData = Encoding.UTF8.GetBytes(replyMessage);
|
||||
|
||||
var udpMessage = _udpListener.Receive(ref serverEndpoint);
|
||||
var message = Encoding.UTF8.GetString(udpMessage);
|
||||
|
||||
var config = JsonSerializer.Deserialize<Configuration>(message);
|
||||
|
||||
if (config != null)
|
||||
{
|
||||
Console.WriteLine($"Received a configuration message: GridSetPoint is " + config.GridSetPoint +
|
||||
", MinimumSoC is " + config.MinimumSoC + " and ForceCalibrationCharge is " +
|
||||
config.CalibrationChargeState + " and CalibrationChargeDate is " +
|
||||
config.CalibrationChargeDate);
|
||||
|
||||
// Send the reply to the sender's endpoint
|
||||
_udpListener.Send(replyData, replyData.Length, serverEndpoint);
|
||||
Console.WriteLine($"Replied to {serverEndpoint}: {replyMessage}");
|
||||
return config;
|
||||
}
|
||||
}
|
||||
|
||||
if (_endPoint != null && !_endPoint.Equals(_udpListener.Client.LocalEndPoint as IPEndPoint))
|
||||
{
|
||||
Console.WriteLine("UDP address has changed, rebinding...");
|
||||
InitializeCommunicationToMiddleware();
|
||||
}
|
||||
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using InnovEnergy.App.SaliMax.DataTypes;
|
||||
using RabbitMQ.Client;
|
||||
|
||||
namespace InnovEnergy.App.SaliMax.MiddlewareClasses;
|
||||
|
||||
public static class RabbitMqManager
|
||||
{
|
||||
public static ConnectionFactory? Factory ;
|
||||
public static IConnection ? Connection;
|
||||
public static IModel? Channel;
|
||||
|
||||
public static Boolean SubscribeToQueue(StatusMessage currentSalimaxState, String? s3Bucket,String VpnServerIp)
|
||||
{
|
||||
try
|
||||
{
|
||||
//_factory = new ConnectionFactory { HostName = VpnServerIp };
|
||||
|
||||
Factory = new ConnectionFactory
|
||||
{
|
||||
HostName = VpnServerIp,
|
||||
Port = 5672,
|
||||
VirtualHost = "/",
|
||||
UserName = "producer",
|
||||
Password = "b187ceaddb54d5485063ddc1d41af66f",
|
||||
|
||||
};
|
||||
|
||||
Connection = Factory.CreateConnection();
|
||||
Channel = Connection.CreateModel();
|
||||
Channel.QueueDeclare(queue: "statusQueue", durable: true, exclusive: false, autoDelete: false, arguments: null);
|
||||
|
||||
Console.WriteLine("The controller sends its status to the middleware for the first time");
|
||||
if (s3Bucket != null) InformMiddleware(currentSalimaxState);
|
||||
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine("An error occurred while connecting to the RabbitMQ queue: " + ex.Message);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public static void InformMiddleware(StatusMessage status)
|
||||
{
|
||||
var message = JsonSerializer.Serialize(status);
|
||||
var body = Encoding.UTF8.GetBytes(message);
|
||||
|
||||
Channel.BasicPublish(exchange: string.Empty,
|
||||
routingKey: "statusQueue",
|
||||
basicProperties: null,
|
||||
body: body);
|
||||
|
||||
Console.WriteLine($"Producer sent message: {message}");
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue