Updated backend to provide support for the overview tab
Updated frontend to parse chunks
This commit is contained in:
parent
e3e9817f2b
commit
abe69193e2
|
@ -137,6 +137,72 @@ public class Controller : ControllerBase
|
|||
.ToList();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetCsvTimestampsForInstallation))]
|
||||
public ActionResult<IEnumerable<CsvTimestamp>> 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<CsvTimestamp>();
|
||||
|
||||
if (start != 0 && end != 0)
|
||||
{
|
||||
allTimestamps = Db.CsvTimestamps
|
||||
.Where(csvTimestamp => csvTimestamp.InstallationId == id && csvTimestamp.Timestamp > start && csvTimestamp.Timestamp < end)
|
||||
.OrderBy(csvTimestamp => csvTimestamp.Timestamp).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
allTimestamps = Db.CsvTimestamps
|
||||
.Where(csvTimestamp => csvTimestamp.InstallationId == id)
|
||||
.OrderBy(csvTimestamp => csvTimestamp.Timestamp).ToList();
|
||||
|
||||
}
|
||||
|
||||
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<CsvTimestamp>();
|
||||
|
||||
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;
|
||||
|
||||
// return Db.CsvTimestamps
|
||||
// .Where(csvTimestamp => csvTimestamp.InstallationId == id)
|
||||
// .OrderByDescending(csvTimestamp => csvTimestamp.Timestamp)
|
||||
// .ToList();
|
||||
}
|
||||
|
||||
[HttpGet(nameof(GetUserById))]
|
||||
public ActionResult<User> GetUserById(Int64 id, Token authToken)
|
||||
{
|
||||
|
@ -555,6 +621,7 @@ public class Controller : ControllerBase
|
|||
{
|
||||
var session = Db.GetSession(authToken);
|
||||
var installationToUpdate = Db.GetInstallationById(installationId);
|
||||
|
||||
|
||||
if (installationToUpdate != null)
|
||||
{
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
using SQLite;
|
||||
|
||||
namespace InnovEnergy.App.Backend.DataTypes;
|
||||
|
||||
public class CsvTimestamp{
|
||||
[PrimaryKey, AutoIncrement]
|
||||
public Int64 Id { get; set; }
|
||||
public Int64 InstallationId { get; set; }
|
||||
public Int32 Timestamp { get; set; }
|
||||
|
||||
}
|
|
@ -32,6 +32,11 @@ public static partial class Db
|
|||
return Insert(warning);
|
||||
}
|
||||
|
||||
public static Boolean Create(CsvTimestamp csvTimestamp)
|
||||
{
|
||||
return Insert(csvTimestamp);
|
||||
}
|
||||
|
||||
public static Boolean Create(Folder folder)
|
||||
{
|
||||
return Insert(folder);
|
||||
|
@ -144,4 +149,35 @@ public static partial class Db
|
|||
Create(newWarning);
|
||||
}
|
||||
}
|
||||
|
||||
public static void AddCsvTimestamp(CsvTimestamp newCsvTimestamp,int installationId)
|
||||
{
|
||||
var maxCSvPerInstallation = 2 * 60 * 24;
|
||||
//Find the total number of warnings for this installation
|
||||
var totalCsvNames = CsvTimestamps.Count(csvTimestamp => csvTimestamp.InstallationId == installationId);
|
||||
|
||||
//If there are 100 warnings, remove the one with the oldest timestamp
|
||||
if (totalCsvNames == maxCSvPerInstallation)
|
||||
{
|
||||
var oldestCSvTimestamp =
|
||||
CsvTimestamps.Where(csvTimestamp => csvTimestamp.InstallationId == installationId)
|
||||
.OrderBy(csvTimestamp => csvTimestamp.Timestamp)
|
||||
.FirstOrDefault();
|
||||
|
||||
//Remove the old error
|
||||
Delete(oldestCSvTimestamp);
|
||||
|
||||
//Add the new error
|
||||
Create(newCsvTimestamp);
|
||||
}
|
||||
else
|
||||
{
|
||||
Console.WriteLine("---------------Added the new Csv Timestamp to the database-----------------");
|
||||
Create(newCsvTimestamp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -37,6 +37,7 @@ public static partial class Db
|
|||
fileConnection.CreateTable<Error>();
|
||||
fileConnection.CreateTable<Warning>();
|
||||
fileConnection.CreateTable<UserAction>();
|
||||
fileConnection.CreateTable<CsvTimestamp>();
|
||||
|
||||
return fileConnection;
|
||||
//return CopyDbToMemory(fileConnection);
|
||||
|
@ -57,6 +58,7 @@ public static partial class Db
|
|||
memoryConnection.CreateTable<Error>();
|
||||
memoryConnection.CreateTable<Warning>();
|
||||
fileConnection.CreateTable<UserAction>();
|
||||
fileConnection.CreateTable<CsvTimestamp>();
|
||||
|
||||
//Copy all the existing tables from the disk to main memory
|
||||
fileConnection.Table<Session>().ForEach(memoryConnection.Insert);
|
||||
|
@ -68,7 +70,8 @@ public static partial class Db
|
|||
fileConnection.Table<OrderNumber2Installation>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<Error>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<Warning>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<UserAction>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<UserAction>().ForEach(memoryConnection.Insert);
|
||||
fileConnection.Table<CsvTimestamp>().ForEach(memoryConnection.Insert);
|
||||
|
||||
return memoryConnection;
|
||||
}
|
||||
|
@ -89,6 +92,7 @@ public static partial class Db
|
|||
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 TableQuery<CsvTimestamp> CsvTimestamps => Connection.Table<CsvTimestamp>();
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
|
@ -111,6 +115,7 @@ public static partial class Db
|
|||
Connection.CreateTable<Error>();
|
||||
Connection.CreateTable<Warning>();
|
||||
Connection.CreateTable<UserAction>();
|
||||
Connection.CreateTable<CsvTimestamp>();
|
||||
});
|
||||
|
||||
//UpdateKeys();
|
||||
|
|
|
@ -77,6 +77,20 @@ public static partial class Db
|
|||
return Warnings.Delete(error => error.Id == warningToDelete.Id) >0;
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean Delete(CsvTimestamp csvTimestampToDelete)
|
||||
{
|
||||
var deleteSuccess = RunTransaction(DeleteCsvTimestampToDelete);
|
||||
if (deleteSuccess)
|
||||
BackupDatabase();
|
||||
return deleteSuccess;
|
||||
|
||||
|
||||
Boolean DeleteCsvTimestampToDelete()
|
||||
{
|
||||
return CsvTimestamps.Delete(csvTimestamp => csvTimestamp.Id == csvTimestampToDelete.Id) >0;
|
||||
}
|
||||
}
|
||||
|
||||
public static Boolean Delete(Installation installation)
|
||||
{
|
||||
|
|
|
@ -68,7 +68,18 @@ public static class RabbitMqManager
|
|||
//Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue
|
||||
if (receivedStatusMessage.Type == MessageType.Heartbit)
|
||||
{
|
||||
Console.WriteLine("This is a heartbit message from installation: " + installationId);
|
||||
Console.WriteLine("This is a heartbit message from installation: " + installationId + " Name of the file is "+ receivedStatusMessage.Timestamp);
|
||||
|
||||
if (receivedStatusMessage.Timestamp != 0)
|
||||
{
|
||||
CsvTimestamp newCsvTimestamp = new CsvTimestamp
|
||||
{
|
||||
InstallationId = installationId,
|
||||
Timestamp = receivedStatusMessage.Timestamp
|
||||
};
|
||||
|
||||
Db.AddCsvTimestamp(newCsvTimestamp, installationId);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -182,52 +193,5 @@ public static class RabbitMqManager
|
|||
};
|
||||
Channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer);
|
||||
}
|
||||
|
||||
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");
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -9,6 +9,7 @@ public class StatusMessage
|
|||
public required MessageType Type { get; set; }
|
||||
public List<AlarmOrWarning>? Warnings { get; set; }
|
||||
public List<AlarmOrWarning>? Alarms { get; set; }
|
||||
public Int32 Timestamp { get; set; }
|
||||
}
|
||||
|
||||
public enum MessageType
|
||||
|
|
|
@ -16,9 +16,9 @@ dotnet publish \
|
|||
-r linux-x64
|
||||
|
||||
echo -e "\n============================ Deploy ============================\n"
|
||||
#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")
|
||||
#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.33" "10.2.4.32" "10.2.4.36" "10.2.4.35" "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,6 +10,7 @@ public class StatusMessage
|
|||
public required MessageType Type { get; set; }
|
||||
public List<AlarmOrWarning>? Warnings { get; set; }
|
||||
public List<AlarmOrWarning>? Alarms { get; set; }
|
||||
public Int32 Timestamp { get; set; }
|
||||
}
|
||||
|
||||
public enum MessageType
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#undef Amax
|
||||
#define Amax
|
||||
#undef GridLimit
|
||||
|
||||
using System.Diagnostics;
|
||||
|
@ -58,9 +58,10 @@ internal static class Program
|
|||
private static Boolean _subscribedToQueue = false;
|
||||
private static Boolean _subscribeToQueueForTheFirstTime = false;
|
||||
private static SalimaxAlarmState _prevSalimaxState = SalimaxAlarmState.Green;
|
||||
private static Int32 _heartBitInterval = 0;
|
||||
//private static Int32 _heartBitInterval = 0;
|
||||
private const UInt16 NbrOfFileToConcatenate = 15;
|
||||
private static UInt16 _counterOfFile = 0;
|
||||
private static SalimaxAlarmState _salimaxAlarmState = SalimaxAlarmState.Green;
|
||||
|
||||
static Program()
|
||||
{
|
||||
|
@ -185,7 +186,6 @@ internal static class Program
|
|||
LoadOnAcGrid = gridBusLoad,
|
||||
LoadOnAcIsland = loadOnAcIsland,
|
||||
LoadOnDc = dcLoad,
|
||||
|
||||
StateMachine = StateMachine.Default,
|
||||
EssControl = EssControl.Default,
|
||||
Log = new SystemLog { SalimaxAlarmState = SalimaxAlarmState.Green, Message = null, SalimaxAlarms = null, SalimaxWarnings = null}, //TODO: Put real stuff
|
||||
|
@ -269,7 +269,7 @@ internal static class Program
|
|||
var subscribedNow = false;
|
||||
|
||||
//Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue
|
||||
_heartBitInterval++;
|
||||
//_heartBitInterval++;
|
||||
|
||||
//When the controller boots, it tries to subscribe to the queue
|
||||
if (_subscribeToQueueForTheFirstTime == false)
|
||||
|
@ -287,16 +287,16 @@ internal static class Program
|
|||
if (s3Bucket != null)
|
||||
RabbitMqManager.InformMiddleware(currentSalimaxState);
|
||||
}
|
||||
else if (_subscribedToQueue && _heartBitInterval >= 30)
|
||||
{
|
||||
//Send a heartbit to the backend
|
||||
Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
|
||||
_heartBitInterval = 0;
|
||||
currentSalimaxState.Type = MessageType.Heartbit;
|
||||
|
||||
if (s3Bucket != null)
|
||||
RabbitMqManager.InformMiddleware(currentSalimaxState);
|
||||
}
|
||||
// else if (_subscribedToQueue && _heartBitInterval >= 30)
|
||||
// {
|
||||
// //Send a heartbit to the backend
|
||||
// Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
|
||||
// _heartBitInterval = 0;
|
||||
// currentSalimaxState.Type = MessageType.Heartbit;
|
||||
//
|
||||
// if (s3Bucket != null)
|
||||
// RabbitMqManager.InformMiddleware(currentSalimaxState);
|
||||
// }
|
||||
|
||||
//If there is an available message from the RabbitMQ Broker, apply the configuration file
|
||||
Configuration? config = SetConfigurationFile();
|
||||
|
@ -441,13 +441,13 @@ internal static class Program
|
|||
});
|
||||
}
|
||||
|
||||
var salimaxAlarmsState = warningList.Any()
|
||||
_salimaxAlarmState = warningList.Any()
|
||||
? SalimaxAlarmState.Orange
|
||||
: SalimaxAlarmState.Green; // this will be replaced by LedState
|
||||
|
||||
salimaxAlarmsState = alarmList.Any()
|
||||
_salimaxAlarmState = alarmList.Any()
|
||||
? SalimaxAlarmState.Red
|
||||
: salimaxAlarmsState; // this will be replaced by LedState
|
||||
: _salimaxAlarmState; // this will be replaced by LedState
|
||||
|
||||
int.TryParse(s3Bucket?.Split("-")[0], out var installationId);
|
||||
|
||||
|
@ -455,7 +455,7 @@ internal static class Program
|
|||
{
|
||||
InstallationId = installationId,
|
||||
Product = 0,
|
||||
Status = salimaxAlarmsState,
|
||||
Status = _salimaxAlarmState,
|
||||
Type = MessageType.AlarmOrWarning,
|
||||
Alarms = alarmList,
|
||||
Warnings = warningList
|
||||
|
@ -683,6 +683,7 @@ internal static class Program
|
|||
|
||||
private static async Task<Boolean> UploadCsv(StatusRecord status, DateTime timeStamp)
|
||||
{
|
||||
|
||||
var csv = status.ToCsv().LogInfo();
|
||||
|
||||
await RestApiSavingfile(csv);
|
||||
|
@ -730,6 +731,26 @@ internal static class Program
|
|||
Console.WriteLine(error);
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
|
||||
|
||||
var s3Bucket = Config.Load().S3?.Bucket;
|
||||
int.TryParse(s3Bucket?.Split("-")[0], out var installationId);
|
||||
int.TryParse(timeStamp.ToUnixTime().ToString(), out var nameOfCsvFile);
|
||||
|
||||
var returnedStatus = new StatusMessage
|
||||
{
|
||||
InstallationId = installationId,
|
||||
Product = 0,
|
||||
Status = _salimaxAlarmState,
|
||||
Type = MessageType.Heartbit,
|
||||
Timestamp = nameOfCsvFile
|
||||
};
|
||||
|
||||
|
||||
if (s3Bucket != null)
|
||||
RabbitMqManager.InformMiddleware(returnedStatus);
|
||||
|
||||
}
|
||||
_counterOfFile++;
|
||||
|
||||
|
|
|
@ -170,7 +170,10 @@ function BatteryView(props: BatteryViewProps) {
|
|||
<Route
|
||||
path={routes.mainstats + '*'}
|
||||
element={
|
||||
<MainStats s3Credentials={props.s3Credentials}></MainStats>
|
||||
<MainStats
|
||||
s3Credentials={props.s3Credentials}
|
||||
id={props.installationId}
|
||||
></MainStats>
|
||||
}
|
||||
/>
|
||||
{props.values.batteryView.map((battery) => (
|
||||
|
|
|
@ -28,6 +28,7 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
|||
|
||||
interface MainStatsProps {
|
||||
s3Credentials: I_S3Credentials;
|
||||
id: number;
|
||||
}
|
||||
|
||||
function MainStats(props: MainStatsProps) {
|
||||
|
@ -92,11 +93,7 @@ function MainStats(props: MainStatsProps) {
|
|||
const resultPromise: Promise<{
|
||||
chartData: BatteryDataInterface;
|
||||
chartOverview: BatteryOverviewInterface;
|
||||
}> = transformInputToBatteryViewData(
|
||||
props.s3Credentials,
|
||||
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)).start,
|
||||
UnixTime.now()
|
||||
);
|
||||
}> = transformInputToBatteryViewData(props.s3Credentials, props.id);
|
||||
|
||||
resultPromise
|
||||
.then((result) => {
|
||||
|
@ -186,6 +183,7 @@ function MainStats(props: MainStatsProps) {
|
|||
chartOverview: BatteryOverviewInterface;
|
||||
}> = transformInputToBatteryViewData(
|
||||
props.s3Credentials,
|
||||
props.id,
|
||||
UnixTime.fromTicks(startDate.unix()),
|
||||
UnixTime.fromTicks(endDate.unix())
|
||||
);
|
||||
|
@ -246,6 +244,7 @@ function MainStats(props: MainStatsProps) {
|
|||
chartOverview: BatteryOverviewInterface;
|
||||
}> = transformInputToBatteryViewData(
|
||||
props.s3Credentials,
|
||||
props.id,
|
||||
UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
|
||||
UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2))
|
||||
);
|
||||
|
|
|
@ -136,7 +136,7 @@ function HistoryOfActions(props: HistoryProps) {
|
|||
label="Select Action Date"
|
||||
name="timestamp"
|
||||
value={actionDate}
|
||||
onChange={(newDate) => handleDateChange(newDate.toDate())}
|
||||
onChange={(newDate) => handleDateChange(newDate)}
|
||||
sx={{
|
||||
width: 450,
|
||||
marginTop: 2
|
||||
|
|
|
@ -4,9 +4,13 @@ import {
|
|||
CardContent,
|
||||
CircularProgress,
|
||||
Container,
|
||||
FormControl,
|
||||
Grid,
|
||||
IconButton,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Modal,
|
||||
Select,
|
||||
TextField,
|
||||
Typography,
|
||||
useTheme
|
||||
|
@ -42,6 +46,8 @@ function InformationSalidomo(props: InformationSalidomoProps) {
|
|||
useState(false);
|
||||
const navigate = useNavigate();
|
||||
|
||||
const DeviceTypes = ['Cerbo', 'Venus'];
|
||||
|
||||
const installationContext = useContext(InstallationsContext);
|
||||
const {
|
||||
updateInstallation,
|
||||
|
@ -259,6 +265,61 @@ function InformationSalidomo(props: InformationSalidomoProps) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label={
|
||||
<FormattedMessage id="vrmLink" defaultMessage="vrmLink" />
|
||||
}
|
||||
name="vrmLink"
|
||||
value={formValues.vrmLink}
|
||||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FormControl
|
||||
fullWidth
|
||||
sx={{
|
||||
marginLeft: 1,
|
||||
marginTop: 1,
|
||||
marginBottom: 1,
|
||||
width: 390
|
||||
}}
|
||||
>
|
||||
<InputLabel
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="DeviceType"
|
||||
defaultMessage="Device Type"
|
||||
/>
|
||||
</InputLabel>
|
||||
<Select
|
||||
name="device"
|
||||
value={
|
||||
DeviceTypes.indexOf(
|
||||
DeviceTypes[formValues.device - 1]
|
||||
) + 1
|
||||
}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{DeviceTypes.map((type) => (
|
||||
<MenuItem
|
||||
key={type}
|
||||
value={DeviceTypes.indexOf(type) + 1}
|
||||
>
|
||||
{type}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label={
|
||||
|
@ -275,19 +336,6 @@ function InformationSalidomo(props: InformationSalidomoProps) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
{/*<div>*/}
|
||||
{/* <TextField*/}
|
||||
{/* label={*/}
|
||||
{/* <FormattedMessage id="vrmLink" defaultMessage="vrmLink" />*/}
|
||||
{/* }*/}
|
||||
{/* name="vrmLink"*/}
|
||||
{/* value={formValues.vrmLink}*/}
|
||||
{/* onChange={handleChange}*/}
|
||||
{/* variant="outlined"*/}
|
||||
{/* fullWidth*/}
|
||||
{/* />*/}
|
||||
{/*</div>*/}
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label="S3 Bucket Name"
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { Card, CircularProgress, Grid, Typography } from '@mui/material';
|
||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||
import { UserContext } from 'src/contexts/userContext';
|
||||
|
@ -35,16 +35,13 @@ function Installation(props: singleInstallationProps) {
|
|||
const context = useContext(UserContext);
|
||||
const { currentUser } = context;
|
||||
const location = useLocation().pathname;
|
||||
const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
|
||||
const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
|
||||
const webSocketsContext = useContext(WebSocketContext);
|
||||
const { getStatus } = webSocketsContext;
|
||||
const [currentTab, setCurrentTab] = useState<string>(undefined);
|
||||
const [values, setValues] = useState<TopologyValues | null>(null);
|
||||
const status = getStatus(props.current_installation.id);
|
||||
const [
|
||||
failedToCommunicateWithInstallation,
|
||||
setFailedToCommunicateWithInstallation
|
||||
] = useState(0);
|
||||
const [connected, setConnected] = useState(true);
|
||||
|
||||
if (props.current_installation == undefined) {
|
||||
|
@ -68,51 +65,122 @@ function Installation(props: singleInstallationProps) {
|
|||
|
||||
const s3Credentials = { s3Bucket, ...S3data };
|
||||
|
||||
const fetchDataPeriodically = async () => {
|
||||
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20));
|
||||
function timeout(delay: number) {
|
||||
return new Promise((res) => setTimeout(res, delay));
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetchData(now, s3Credentials);
|
||||
const continueFetching = useRef(false);
|
||||
|
||||
if (res != FetchResult.notAvailable && res != FetchResult.tryLater) {
|
||||
setFailedToCommunicateWithInstallation(0);
|
||||
setConnected(true);
|
||||
setValues(
|
||||
extractValues({
|
||||
time: now,
|
||||
value: res
|
||||
})
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
setFailedToCommunicateWithInstallation((prevCount) => {
|
||||
if (prevCount + 1 >= 3) {
|
||||
setConnected(false);
|
||||
}
|
||||
return prevCount + 1;
|
||||
});
|
||||
const fetchDataForOneTime = async () => {
|
||||
var timeperiodToSearch = 80;
|
||||
let res;
|
||||
let timestampToFetch;
|
||||
|
||||
for (var i = timeperiodToSearch; i > 0; i -= 2) {
|
||||
timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
|
||||
try {
|
||||
res = await fetchData(timestampToFetch, s3Credentials);
|
||||
if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching data:', err);
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
}
|
||||
|
||||
if (i <= 0) {
|
||||
setConnected(false);
|
||||
return false;
|
||||
}
|
||||
setConnected(true);
|
||||
|
||||
const timestamp = Object.keys(res)[Object.keys(res).length - 1];
|
||||
|
||||
setValues(
|
||||
extractValues({
|
||||
time: UnixTime.fromTicks(parseInt(timestamp, 10)),
|
||||
value: res[timestamp]
|
||||
})
|
||||
);
|
||||
return true;
|
||||
};
|
||||
|
||||
const fetchDataOnlyOneTime = async () => {
|
||||
let success = false;
|
||||
const max_retransmissions = 3;
|
||||
const fetchDataPeriodically = async () => {
|
||||
var timeperiodToSearch = 80;
|
||||
let res;
|
||||
let timestampToFetch;
|
||||
|
||||
for (let i = 0; i < max_retransmissions; i++) {
|
||||
success = await fetchDataPeriodically();
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
if (success) {
|
||||
break;
|
||||
for (var i = timeperiodToSearch; i > 0; i -= 2) {
|
||||
if (!continueFetching.current) {
|
||||
return false;
|
||||
}
|
||||
timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
|
||||
|
||||
try {
|
||||
res = await fetchData(timestampToFetch, s3Credentials);
|
||||
if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching data:', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (i <= 0) {
|
||||
setConnected(false);
|
||||
return false;
|
||||
}
|
||||
setConnected(true);
|
||||
|
||||
while (continueFetching.current) {
|
||||
for (const timestamp of Object.keys(res)) {
|
||||
if (!continueFetching.current) {
|
||||
setFetchFunctionCalled(false);
|
||||
return false;
|
||||
}
|
||||
console.log(`Timestamp: ${timestamp}`);
|
||||
console.log(res[timestamp]);
|
||||
|
||||
// Set values asynchronously with delay
|
||||
setValues(
|
||||
extractValues({
|
||||
time: UnixTime.fromTicks(parseInt(timestamp, 10)),
|
||||
value: res[timestamp]
|
||||
})
|
||||
);
|
||||
// Wait for 2 seconds before processing next timestamp
|
||||
await timeout(2000);
|
||||
}
|
||||
|
||||
timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(30));
|
||||
console.log('NEW TIMESTAMP TO FETCH IS ' + timestampToFetch);
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
if (!continueFetching.current) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Trying to fetch timestamp ' + timestampToFetch);
|
||||
res = await fetchData(timestampToFetch, s3Credentials);
|
||||
if (
|
||||
res !== FetchResult.notAvailable &&
|
||||
res !== FetchResult.tryLater
|
||||
) {
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching data:', err);
|
||||
return false;
|
||||
}
|
||||
timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(1));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
let path = location.split('/');
|
||||
|
||||
setCurrentTab(path[path.length - 1]);
|
||||
}, [location]);
|
||||
|
||||
|
@ -129,30 +197,28 @@ function Installation(props: singleInstallationProps) {
|
|||
currentTab == 'configuration' ||
|
||||
location.includes('batteryview')
|
||||
) {
|
||||
let interval;
|
||||
|
||||
if (
|
||||
currentTab == 'live' ||
|
||||
(location.includes('batteryview') && !location.includes('mainstats')) ||
|
||||
currentTab == 'pvview'
|
||||
) {
|
||||
fetchDataPeriodically();
|
||||
interval = setInterval(fetchDataPeriodically, 2000);
|
||||
if (!continueFetching.current) {
|
||||
continueFetching.current = true;
|
||||
if (!fetchFunctionCalled) {
|
||||
setFetchFunctionCalled(true);
|
||||
fetchDataPeriodically();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (currentTab == 'configuration' || location.includes('mainstats')) {
|
||||
fetchDataOnlyOneTime();
|
||||
if (currentTab == 'configuration') {
|
||||
fetchDataForOneTime();
|
||||
}
|
||||
|
||||
// Cleanup function to cancel interval
|
||||
return () => {
|
||||
if (
|
||||
currentTab == 'live' ||
|
||||
currentTab == 'pvview' ||
|
||||
(location.includes('batteryview') && !location.includes('mainstats'))
|
||||
) {
|
||||
clearInterval(interval);
|
||||
}
|
||||
continueFetching.current = false;
|
||||
};
|
||||
} else {
|
||||
continueFetching.current = false;
|
||||
}
|
||||
}, [currentTab, location]);
|
||||
|
||||
|
@ -324,7 +390,12 @@ function Installation(props: singleInstallationProps) {
|
|||
|
||||
<Route
|
||||
path={routes.overview}
|
||||
element={<Overview s3Credentials={s3Credentials}></Overview>}
|
||||
element={
|
||||
<Overview
|
||||
s3Credentials={s3Credentials}
|
||||
id={props.current_installation.id}
|
||||
></Overview>
|
||||
}
|
||||
/>
|
||||
|
||||
<Route
|
||||
|
|
|
@ -3,10 +3,10 @@ import { I_S3Credentials } from 'src/interfaces/S3Types';
|
|||
import { FetchResult } from 'src/dataCache/dataCache';
|
||||
import { DataRecord } from 'src/dataCache/data';
|
||||
import { S3Access } from 'src/dataCache/S3/S3Access';
|
||||
import { parseCsv } from '../Log/graph.util';
|
||||
import { parseChunk, parseCsv } from '../Log/graph.util';
|
||||
import JSZip from 'jszip';
|
||||
|
||||
export const fetchDailyData = (
|
||||
export const fetchAggregatedData = (
|
||||
date: string,
|
||||
s3Credentials?: I_S3Credentials
|
||||
): Promise<FetchResult<DataRecord>> => {
|
||||
|
@ -25,10 +25,7 @@ export const fetchDailyData = (
|
|||
if (r.status === 404) {
|
||||
return Promise.resolve(FetchResult.notAvailable);
|
||||
} else if (r.status === 200) {
|
||||
// const text = await r.text();
|
||||
const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text
|
||||
|
||||
//const response = await fetch(url); // Fetch the resource from the server
|
||||
const contentEncoding = r.headers.get('content-type');
|
||||
|
||||
if (contentEncoding != 'application/base64; charset=utf-8') {
|
||||
|
@ -43,11 +40,7 @@ export const fetchDailyData = (
|
|||
const zip = await JSZip.loadAsync(byteArray);
|
||||
// Assuming the CSV file is named "data.csv" inside the ZIP archive
|
||||
const csvContent = await zip.file('data.csv').async('text');
|
||||
|
||||
return parseCsv(csvContent);
|
||||
|
||||
//console.log(parseCsv(text));
|
||||
//return parseCsv(text);
|
||||
} else {
|
||||
return Promise.resolve(FetchResult.notAvailable);
|
||||
}
|
||||
|
@ -61,7 +54,7 @@ export const fetchDailyData = (
|
|||
export const fetchData = (
|
||||
timestamp: UnixTime,
|
||||
s3Credentials?: I_S3Credentials
|
||||
): Promise<FetchResult<DataRecord>> => {
|
||||
): Promise<FetchResult<Record<string, DataRecord>>> => {
|
||||
const s3Path = `${timestamp.ticks}.csv`;
|
||||
if (s3Credentials && s3Credentials.s3Bucket) {
|
||||
const s3Access = new S3Access(
|
||||
|
@ -79,12 +72,10 @@ export const fetchData = (
|
|||
return Promise.resolve(FetchResult.notAvailable);
|
||||
} else if (r.status === 200) {
|
||||
const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text
|
||||
|
||||
//const response = await fetch(url); // Fetch the resource from the server
|
||||
const contentEncoding = r.headers.get('content-type');
|
||||
|
||||
if (contentEncoding != 'application/base64; charset=utf-8') {
|
||||
return parseCsv(csvtext);
|
||||
return parseChunk(csvtext);
|
||||
}
|
||||
|
||||
const byteArray = Uint8Array.from(atob(csvtext), (c) =>
|
||||
|
@ -96,7 +87,7 @@ export const fetchData = (
|
|||
// Assuming the CSV file is named "data.csv" inside the ZIP archive
|
||||
const csvContent = await zip.file('data.csv').async('text');
|
||||
|
||||
return parseCsv(csvContent);
|
||||
return parseChunk(csvContent);
|
||||
} else {
|
||||
return Promise.resolve(FetchResult.notAvailable);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,35 @@ export const parseCsv = (text: string): DataRecord => {
|
|||
.reduce((acc, current) => ({ ...acc, ...current }), {} as DataRecord);
|
||||
};
|
||||
|
||||
export const parseChunk = (text: string): Record<string, DataRecord> => {
|
||||
const lines = text.split(/\r?\n/).filter((line) => line.length > 0);
|
||||
|
||||
let result: Record<string, DataRecord> = {};
|
||||
let currentTimestamp = null;
|
||||
|
||||
lines.forEach((line) => {
|
||||
const fields = line.split(';');
|
||||
if (fields[0] === 'Timestamp') {
|
||||
currentTimestamp = fields[1];
|
||||
result[currentTimestamp] = {};
|
||||
} else if (currentTimestamp) {
|
||||
let key = fields[0];
|
||||
let value = fields[1];
|
||||
let unit = fields[2];
|
||||
|
||||
if (isNaN(Number(value)) || value === '') {
|
||||
result[currentTimestamp][key] = { value: value, unit: unit };
|
||||
} else {
|
||||
result[currentTimestamp][key] = {
|
||||
value: parseFloat(value),
|
||||
unit: unit
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
};
|
||||
|
||||
export interface I_BoxDataValue {
|
||||
unit: string;
|
||||
value: string | number;
|
||||
|
|
|
@ -13,15 +13,16 @@ import {
|
|||
import Button from '@mui/material/Button';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import CircularProgress from '@mui/material/CircularProgress';
|
||||
import { TimeSpan, UnixTime } from '../../../dataCache/time';
|
||||
import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers';
|
||||
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
|
||||
import dayjs from 'dayjs';
|
||||
import { UserContext } from '../../../contexts/userContext';
|
||||
import { UserType } from '../../../interfaces/UserTypes';
|
||||
import { TimeSpan, UnixTime } from '../../../dataCache/time';
|
||||
|
||||
interface OverviewProps {
|
||||
s3Credentials: I_S3Credentials;
|
||||
id: number;
|
||||
}
|
||||
|
||||
const computeLast7Days = (): string[] => {
|
||||
|
@ -43,7 +44,6 @@ const computeLast7Days = (): string[] => {
|
|||
function Overview(props: OverviewProps) {
|
||||
const context = useContext(UserContext);
|
||||
const { currentUser } = context;
|
||||
|
||||
const [dailyData, setDailyData] = useState(true);
|
||||
const [aggregatedData, setAggregatedData] = useState(false);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
@ -85,11 +85,7 @@ function Overview(props: OverviewProps) {
|
|||
const resultPromise: Promise<{
|
||||
chartData: chartDataInterface;
|
||||
chartOverview: overviewInterface;
|
||||
}> = transformInputToDailyData(
|
||||
props.s3Credentials,
|
||||
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)).start,
|
||||
UnixTime.now()
|
||||
);
|
||||
}> = transformInputToDailyData(props.s3Credentials, props.id);
|
||||
|
||||
resultPromise
|
||||
.then((result) => {
|
||||
|
@ -119,6 +115,7 @@ function Overview(props: OverviewProps) {
|
|||
chartOverview: overviewInterface;
|
||||
}> = transformInputToDailyData(
|
||||
props.s3Credentials,
|
||||
props.id,
|
||||
UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
|
||||
UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2))
|
||||
);
|
||||
|
@ -174,7 +171,6 @@ function Overview(props: OverviewProps) {
|
|||
chartOverview: overviewInterface;
|
||||
}> = transformInputToAggregatedData(
|
||||
props.s3Credentials,
|
||||
|
||||
dayjs().subtract(1, 'week'),
|
||||
dayjs()
|
||||
);
|
||||
|
@ -246,6 +242,7 @@ function Overview(props: OverviewProps) {
|
|||
chartOverview: overviewInterface;
|
||||
}> = transformInputToDailyData(
|
||||
props.s3Credentials,
|
||||
props.id,
|
||||
UnixTime.fromTicks(startDate.unix()),
|
||||
UnixTime.fromTicks(endDate.unix())
|
||||
);
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useRef, useState } from 'react';
|
||||
import { Card, CircularProgress, Grid, Typography } from '@mui/material';
|
||||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||
import { UserContext } from 'src/contexts/userContext';
|
||||
|
@ -56,93 +56,110 @@ function Installation(props: singleInstallationProps) {
|
|||
'-' +
|
||||
'c0436b6a-d276-4cd8-9c44-1eae86cf5d0e';
|
||||
|
||||
const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
|
||||
const s3Credentials = { s3Bucket, ...S3data };
|
||||
|
||||
const fetchDataOnlyOneTime = async () => {
|
||||
var timeperiodToSearch = 70;
|
||||
function timeout(delay: number) {
|
||||
return new Promise((res) => setTimeout(res, delay));
|
||||
}
|
||||
|
||||
for (var i = timeperiodToSearch; i > 0; i -= 2) {
|
||||
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
|
||||
|
||||
const res = await fetchData(now, s3Credentials);
|
||||
|
||||
if (res != FetchResult.notAvailable && res != FetchResult.tryLater) {
|
||||
setConnected(true);
|
||||
setFailedToCommunicateWithInstallation(0);
|
||||
setValues(
|
||||
extractValues({
|
||||
time: now,
|
||||
value: res
|
||||
})
|
||||
);
|
||||
return now;
|
||||
}
|
||||
}
|
||||
};
|
||||
const continueFetching = useRef(false);
|
||||
|
||||
const fetchDataPeriodically = async () => {
|
||||
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20));
|
||||
var timeperiodToSearch = 80;
|
||||
let res;
|
||||
let timestampToFetch;
|
||||
|
||||
try {
|
||||
const res = await fetchData(now, s3Credentials);
|
||||
|
||||
if (res != FetchResult.notAvailable && res != FetchResult.tryLater) {
|
||||
setConnected(true);
|
||||
setFailedToCommunicateWithInstallation(0);
|
||||
setValues(
|
||||
extractValues({
|
||||
time: now,
|
||||
value: res
|
||||
})
|
||||
);
|
||||
return true;
|
||||
} else {
|
||||
setFailedToCommunicateWithInstallation((prevCount) => {
|
||||
if (prevCount + 1 >= 20) {
|
||||
setConnected(false);
|
||||
}
|
||||
return prevCount + 1;
|
||||
});
|
||||
for (var i = timeperiodToSearch; i > 0; i -= 2) {
|
||||
if (!continueFetching.current) {
|
||||
return false;
|
||||
}
|
||||
} catch (err) {
|
||||
timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
|
||||
|
||||
try {
|
||||
res = await fetchData(timestampToFetch, s3Credentials);
|
||||
if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching data:', err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (i <= 0) {
|
||||
setConnected(false);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
setConnected(true);
|
||||
|
||||
while (continueFetching.current) {
|
||||
for (const timestamp of Object.keys(res)) {
|
||||
if (!continueFetching.current) {
|
||||
setFetchFunctionCalled(false);
|
||||
return false;
|
||||
}
|
||||
console.log(`Timestamp: ${timestamp}`);
|
||||
console.log(res[timestamp]);
|
||||
|
||||
// Set values asynchronously with delay
|
||||
setValues(
|
||||
extractValues({
|
||||
time: UnixTime.fromTicks(parseInt(timestamp, 10)),
|
||||
value: res[timestamp]
|
||||
})
|
||||
);
|
||||
// Wait for 2 seconds before processing next timestamp
|
||||
await timeout(2000);
|
||||
}
|
||||
|
||||
timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(30));
|
||||
console.log('NEW TIMESTAMP TO FETCH IS ' + timestampToFetch);
|
||||
|
||||
for (i = 0; i < 10; i++) {
|
||||
if (!continueFetching.current) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
console.log('Trying to fetch timestamp ' + timestampToFetch);
|
||||
res = await fetchData(timestampToFetch, s3Credentials);
|
||||
if (
|
||||
res !== FetchResult.notAvailable &&
|
||||
res !== FetchResult.tryLater
|
||||
) {
|
||||
break;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching data:', err);
|
||||
return false;
|
||||
}
|
||||
timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(1));
|
||||
}
|
||||
}
|
||||
};
|
||||
useEffect(() => {
|
||||
let path = location.split('/');
|
||||
|
||||
setCurrentTab(path[path.length - 1]);
|
||||
}, [location]);
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
currentTab == 'live' ||
|
||||
currentTab == 'configuration' ||
|
||||
location.includes('batteryview')
|
||||
) {
|
||||
let interval;
|
||||
|
||||
if (
|
||||
currentTab == 'live' ||
|
||||
(location.includes('batteryview') && !location.includes('mainstats'))
|
||||
) {
|
||||
fetchDataOnlyOneTime();
|
||||
interval = setInterval(fetchDataPeriodically, 2000);
|
||||
}
|
||||
if (currentTab == 'configuration' || location.includes('mainstats')) {
|
||||
fetchDataOnlyOneTime();
|
||||
}
|
||||
|
||||
// Cleanup function to cancel interval and update isMounted when unmounted
|
||||
return () => {
|
||||
if (
|
||||
currentTab == 'live' ||
|
||||
(location.includes('batteryview') && !location.includes('mainstats'))
|
||||
) {
|
||||
clearInterval(interval);
|
||||
if (location.includes('batteryview')) {
|
||||
if (location.includes('batteryview') && !location.includes('mainstats')) {
|
||||
if (!continueFetching.current) {
|
||||
continueFetching.current = true;
|
||||
if (!fetchFunctionCalled) {
|
||||
setFetchFunctionCalled(true);
|
||||
fetchDataPeriodically();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return () => {
|
||||
continueFetching.current = false;
|
||||
};
|
||||
} else {
|
||||
continueFetching.current = false;
|
||||
}
|
||||
}, [currentTab, location]);
|
||||
|
||||
|
|
|
@ -3,8 +3,12 @@ import {
|
|||
Alert,
|
||||
Box,
|
||||
CircularProgress,
|
||||
FormControl,
|
||||
IconButton,
|
||||
InputLabel,
|
||||
MenuItem,
|
||||
Modal,
|
||||
Select,
|
||||
TextField,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
|
@ -37,6 +41,8 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) {
|
|||
'vpnIp',
|
||||
'vrmLink'
|
||||
];
|
||||
|
||||
const DeviceTypes = ['Cerbo', 'Venus'];
|
||||
const installationContext = useContext(InstallationsContext);
|
||||
const { createInstallation, loading, setLoading, error, setError } =
|
||||
installationContext;
|
||||
|
@ -128,6 +134,7 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) {
|
|||
value={formValues.region}
|
||||
onChange={handleChange}
|
||||
required
|
||||
error={formValues.region === ''}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
|
@ -180,6 +187,36 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) {
|
|||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<FormControl
|
||||
fullWidth
|
||||
sx={{ marginTop: 1, marginBottom: 1, width: 390 }}
|
||||
>
|
||||
<InputLabel
|
||||
sx={{
|
||||
fontSize: 14,
|
||||
backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="DeviceType"
|
||||
defaultMessage="Device Type"
|
||||
/>
|
||||
</InputLabel>
|
||||
<Select
|
||||
name="device"
|
||||
value={DeviceTypes[formValues.device]}
|
||||
onChange={handleChange}
|
||||
>
|
||||
{DeviceTypes.map((type) => (
|
||||
<MenuItem key={type} value={DeviceTypes.indexOf(type) + 1}>
|
||||
{type}
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label={
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import dayjs from 'dayjs';
|
||||
import {
|
||||
fetchDailyData,
|
||||
fetchAggregatedData,
|
||||
fetchData
|
||||
} from '../content/dashboards/Installations/fetchData';
|
||||
import { FetchResult } from '../dataCache/dataCache';
|
||||
import { I_S3Credentials } from './S3Types';
|
||||
import { UnixTime } from '../dataCache/time';
|
||||
import { CsvTimestamp, I_S3Credentials } from './S3Types';
|
||||
import { TimeSpan, UnixTime } from '../dataCache/time';
|
||||
import { DataRecord } from '../dataCache/data';
|
||||
import axiosConfig from '../Resources/axiosConfig';
|
||||
import { AxiosError, AxiosResponse } from 'axios';
|
||||
|
||||
export interface chartInfoInterface {
|
||||
magnitude: number;
|
||||
|
@ -74,8 +77,9 @@ export interface BatteryOverviewInterface {
|
|||
|
||||
export const transformInputToBatteryViewData = async (
|
||||
s3Credentials: I_S3Credentials,
|
||||
startTimestamp: UnixTime,
|
||||
endTimestamp: UnixTime
|
||||
id: number,
|
||||
start_time?: UnixTime,
|
||||
end_time?: UnixTime
|
||||
): Promise<{
|
||||
chartData: BatteryDataInterface;
|
||||
chartOverview: BatteryOverviewInterface;
|
||||
|
@ -124,42 +128,69 @@ export const transformInputToBatteryViewData = async (
|
|||
};
|
||||
|
||||
let initialiation = true;
|
||||
|
||||
//console.log(start_time);
|
||||
//console.log(end_time);
|
||||
|
||||
let timestampArray: CsvTimestamp[] = [];
|
||||
let adjustedTimestampArray = [];
|
||||
|
||||
let startTimestampToNum = Number(startTimestamp);
|
||||
if (startTimestampToNum % 2 != 0) {
|
||||
startTimestampToNum += 1;
|
||||
}
|
||||
let startUnixTime = UnixTime.fromTicks(startTimestampToNum);
|
||||
let diff = endTimestamp.ticks - startUnixTime.ticks;
|
||||
|
||||
const timestampPromises = [];
|
||||
|
||||
while (startUnixTime < endTimestamp) {
|
||||
timestampPromises.push(fetchData(startUnixTime, s3Credentials));
|
||||
if (start_time && end_time) {
|
||||
await axiosConfig
|
||||
.get(
|
||||
`/GetCsvTimestampsForInstallation?id=${id}&start=${start_time.ticks}&end=${end_time.ticks}`
|
||||
)
|
||||
.then((res: AxiosResponse<CsvTimestamp[]>) => {
|
||||
timestampArray = res.data;
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
if (err.response && err.response.status == 401) {
|
||||
//removeToken();
|
||||
//navigate(routes.login);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await axiosConfig
|
||||
.get(`/GetCsvTimestampsForInstallation?id=${id}&start=${0}&end=${0}`)
|
||||
.then((res: AxiosResponse<CsvTimestamp[]>) => {
|
||||
timestampArray = res.data;
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
if (err.response && err.response.status == 401) {
|
||||
//removeToken();
|
||||
//navigate(routes.login);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + diff / 100);
|
||||
if (startUnixTime.ticks % 2 !== 0) {
|
||||
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + 1);
|
||||
}
|
||||
const adjustedTimestamp = new Date(startUnixTime.ticks * 1000);
|
||||
for (var i = 0; i < timestampArray.length; i++) {
|
||||
timestampPromises.push(
|
||||
fetchDataForOneTime(
|
||||
UnixTime.fromTicks(timestampArray[i].timestamp),
|
||||
s3Credentials
|
||||
)
|
||||
);
|
||||
|
||||
const adjustedTimestamp = new Date(timestampArray[i].timestamp * 1000);
|
||||
//Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
|
||||
adjustedTimestamp.setHours(
|
||||
adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
|
||||
);
|
||||
adjustedTimestampArray.push(adjustedTimestamp);
|
||||
}
|
||||
|
||||
//Wait until fetching all the data
|
||||
const results = await Promise.all(timestampPromises);
|
||||
const results: Promise<FetchResult<Record<string, DataRecord>>>[] =
|
||||
await Promise.all(timestampPromises);
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const result = results[i];
|
||||
if (
|
||||
result === FetchResult.notAvailable ||
|
||||
result === FetchResult.tryLater
|
||||
) {
|
||||
if (results[i] == null) {
|
||||
// Handle not available or try later case
|
||||
} else {
|
||||
const timestamp = Object.keys(results[i])[
|
||||
Object.keys(results[i]).length - 1
|
||||
];
|
||||
const result = results[i][timestamp];
|
||||
const battery_nodes = result['/Config/Devices/BatteryNodes'].value
|
||||
.toString()
|
||||
.split(',');
|
||||
|
@ -252,14 +283,42 @@ export const transformInputToBatteryViewData = async (
|
|||
};
|
||||
};
|
||||
|
||||
const fetchDataForOneTime = async (
|
||||
startUnixTime: UnixTime,
|
||||
s3Credentials: I_S3Credentials
|
||||
): Promise<FetchResult<Record<string, DataRecord>>> => {
|
||||
var timeperiodToSearch = 2;
|
||||
let res;
|
||||
let timestampToFetch;
|
||||
|
||||
for (var i = 0; i < timeperiodToSearch; i++) {
|
||||
timestampToFetch = startUnixTime.later(TimeSpan.fromSeconds(i));
|
||||
try {
|
||||
res = await fetchData(timestampToFetch, s3Credentials);
|
||||
|
||||
if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
|
||||
//console.log('Successfully fetched ' + timestampToFetch);
|
||||
return res;
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error fetching data:', err);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
export const transformInputToDailyData = async (
|
||||
s3Credentials: I_S3Credentials,
|
||||
startTimestamp: UnixTime,
|
||||
endTimestamp: UnixTime
|
||||
id: number,
|
||||
start_time?: UnixTime,
|
||||
end_time?: UnixTime
|
||||
): Promise<{
|
||||
chartData: chartDataInterface;
|
||||
chartOverview: overviewInterface;
|
||||
}> => {
|
||||
//const navigate = useNavigate();
|
||||
//const tokencontext = useContext(TokenContext);
|
||||
//const { removeToken } = tokencontext;
|
||||
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
||||
const MAX_NUMBER = 9999999;
|
||||
const pathsToSearch = [
|
||||
|
@ -309,45 +368,71 @@ export const transformInputToDailyData = async (
|
|||
};
|
||||
});
|
||||
|
||||
//console.log(start_time);
|
||||
//console.log(end_time);
|
||||
|
||||
let timestampArray: CsvTimestamp[] = [];
|
||||
let adjustedTimestampArray = [];
|
||||
|
||||
let startTimestampToNum = Number(startTimestamp);
|
||||
if (startTimestampToNum % 2 != 0) {
|
||||
startTimestampToNum += 1;
|
||||
}
|
||||
let startUnixTime = UnixTime.fromTicks(startTimestampToNum);
|
||||
let diff = endTimestamp.ticks - startUnixTime.ticks;
|
||||
|
||||
const timestampPromises = [];
|
||||
|
||||
while (startUnixTime < endTimestamp) {
|
||||
timestampPromises.push(fetchData(startUnixTime, s3Credentials));
|
||||
if (start_time && end_time) {
|
||||
await axiosConfig
|
||||
.get(
|
||||
`/GetCsvTimestampsForInstallation?id=${id}&start=${start_time.ticks}&end=${end_time.ticks}`
|
||||
)
|
||||
.then((res: AxiosResponse<CsvTimestamp[]>) => {
|
||||
timestampArray = res.data;
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
if (err.response && err.response.status == 401) {
|
||||
//removeToken();
|
||||
//navigate(routes.login);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
await axiosConfig
|
||||
.get(`/GetCsvTimestampsForInstallation?id=${id}&start=${0}&end=${0}`)
|
||||
.then((res: AxiosResponse<CsvTimestamp[]>) => {
|
||||
timestampArray = res.data;
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
if (err.response && err.response.status == 401) {
|
||||
//removeToken();
|
||||
//navigate(routes.login);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + diff / 100);
|
||||
if (startUnixTime.ticks % 2 !== 0) {
|
||||
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + 1);
|
||||
}
|
||||
const adjustedTimestamp = new Date(startUnixTime.ticks * 1000);
|
||||
//while (startUnixTime < endTimestamp) {
|
||||
for (var i = 0; i < timestampArray.length; i++) {
|
||||
timestampPromises.push(
|
||||
fetchDataForOneTime(
|
||||
UnixTime.fromTicks(timestampArray[i].timestamp),
|
||||
s3Credentials
|
||||
)
|
||||
);
|
||||
|
||||
const adjustedTimestamp = new Date(timestampArray[i].timestamp * 1000);
|
||||
//Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
|
||||
adjustedTimestamp.setHours(
|
||||
adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
|
||||
);
|
||||
|
||||
adjustedTimestampArray.push(adjustedTimestamp);
|
||||
}
|
||||
|
||||
const results = await Promise.all(timestampPromises);
|
||||
const results: Promise<FetchResult<Record<string, DataRecord>>>[] =
|
||||
await Promise.all(timestampPromises);
|
||||
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const result = results[i];
|
||||
if (
|
||||
result === FetchResult.notAvailable ||
|
||||
result === FetchResult.tryLater
|
||||
) {
|
||||
if (results[i] == null) {
|
||||
// Handle not available or try later case
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||
const timestamp = Object.keys(results[i])[
|
||||
Object.keys(results[i]).length - 1
|
||||
];
|
||||
const result = results[i][timestamp];
|
||||
let category_index = 0;
|
||||
// eslint-disable-next-line @typescript-eslint/no-loop-func
|
||||
pathsToSearch.forEach((path) => {
|
||||
if (result[path]) {
|
||||
const value = result[path];
|
||||
|
@ -370,6 +455,7 @@ export const transformInputToDailyData = async (
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
categories.forEach((category) => {
|
||||
let value = Math.max(
|
||||
Math.abs(chartOverview[category].max),
|
||||
|
@ -500,7 +586,7 @@ export const transformInputToAggregatedData = async (
|
|||
|
||||
while (currentDay.isBefore(end_date)) {
|
||||
timestampPromises.push(
|
||||
fetchDailyData(currentDay.format('YYYY-MM-DD'), s3Credentials)
|
||||
fetchAggregatedData(currentDay.format('YYYY-MM-DD'), s3Credentials)
|
||||
);
|
||||
currentDay = currentDay.add(1, 'day');
|
||||
}
|
||||
|
|
|
@ -17,6 +17,12 @@ export interface ErrorMessage {
|
|||
seen: boolean;
|
||||
}
|
||||
|
||||
export interface CsvTimestamp {
|
||||
id: number;
|
||||
installationId: number;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export interface Action {
|
||||
id: number;
|
||||
userName: string;
|
||||
|
|
Loading…
Reference in New Issue