Updated backend to provide support for the overview tab

Updated frontend to parse chunks
This commit is contained in:
Noe 2024-06-26 17:05:27 +02:00
parent e3e9817f2b
commit abe69193e2
22 changed files with 690 additions and 286 deletions

View File

@ -137,6 +137,72 @@ public class Controller : ControllerBase
.ToList(); .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))] [HttpGet(nameof(GetUserById))]
public ActionResult<User> GetUserById(Int64 id, Token authToken) public ActionResult<User> GetUserById(Int64 id, Token authToken)
{ {
@ -555,6 +621,7 @@ public class Controller : ControllerBase
{ {
var session = Db.GetSession(authToken); var session = Db.GetSession(authToken);
var installationToUpdate = Db.GetInstallationById(installationId); var installationToUpdate = Db.GetInstallationById(installationId);
if (installationToUpdate != null) if (installationToUpdate != null)
{ {

View File

@ -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; }
}

View File

@ -32,6 +32,11 @@ public static partial class Db
return Insert(warning); return Insert(warning);
} }
public static Boolean Create(CsvTimestamp csvTimestamp)
{
return Insert(csvTimestamp);
}
public static Boolean Create(Folder folder) public static Boolean Create(Folder folder)
{ {
return Insert(folder); return Insert(folder);
@ -144,4 +149,35 @@ public static partial class Db
Create(newWarning); 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);
}
}
} }

View File

@ -37,6 +37,7 @@ public static partial class Db
fileConnection.CreateTable<Error>(); fileConnection.CreateTable<Error>();
fileConnection.CreateTable<Warning>(); fileConnection.CreateTable<Warning>();
fileConnection.CreateTable<UserAction>(); fileConnection.CreateTable<UserAction>();
fileConnection.CreateTable<CsvTimestamp>();
return fileConnection; return fileConnection;
//return CopyDbToMemory(fileConnection); //return CopyDbToMemory(fileConnection);
@ -57,6 +58,7 @@ public static partial class Db
memoryConnection.CreateTable<Error>(); memoryConnection.CreateTable<Error>();
memoryConnection.CreateTable<Warning>(); memoryConnection.CreateTable<Warning>();
fileConnection.CreateTable<UserAction>(); fileConnection.CreateTable<UserAction>();
fileConnection.CreateTable<CsvTimestamp>();
//Copy all the existing tables from the disk to main memory //Copy all the existing tables from the disk to main memory
fileConnection.Table<Session>().ForEach(memoryConnection.Insert); fileConnection.Table<Session>().ForEach(memoryConnection.Insert);
@ -68,7 +70,8 @@ public static partial class Db
fileConnection.Table<OrderNumber2Installation>().ForEach(memoryConnection.Insert); fileConnection.Table<OrderNumber2Installation>().ForEach(memoryConnection.Insert);
fileConnection.Table<Error>().ForEach(memoryConnection.Insert); fileConnection.Table<Error>().ForEach(memoryConnection.Insert);
fileConnection.Table<Warning>().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; return memoryConnection;
} }
@ -89,6 +92,7 @@ public static partial class Db
public static TableQuery<Error> Errors => Connection.Table<Error>(); public static TableQuery<Error> Errors => Connection.Table<Error>();
public static TableQuery<Warning> Warnings => Connection.Table<Warning>(); public static TableQuery<Warning> Warnings => Connection.Table<Warning>();
public static TableQuery<UserAction> UserActions => Connection.Table<UserAction>(); public static TableQuery<UserAction> UserActions => Connection.Table<UserAction>();
public static TableQuery<CsvTimestamp> CsvTimestamps => Connection.Table<CsvTimestamp>();
public static void Init() public static void Init()
{ {
@ -111,6 +115,7 @@ public static partial class Db
Connection.CreateTable<Error>(); Connection.CreateTable<Error>();
Connection.CreateTable<Warning>(); Connection.CreateTable<Warning>();
Connection.CreateTable<UserAction>(); Connection.CreateTable<UserAction>();
Connection.CreateTable<CsvTimestamp>();
}); });
//UpdateKeys(); //UpdateKeys();

View File

@ -77,6 +77,20 @@ public static partial class Db
return Warnings.Delete(error => error.Id == warningToDelete.Id) >0; 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) public static Boolean Delete(Installation installation)
{ {

View File

@ -68,7 +68,18 @@ public static class RabbitMqManager
//Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue //Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue
if (receivedStatusMessage.Type == MessageType.Heartbit) 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 else
{ {
@ -182,52 +193,5 @@ public static class RabbitMqManager
}; };
Channel.BasicConsume(queue: "statusQueue", autoAck: true, consumer: consumer); 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");
}
} }

View File

@ -9,6 +9,7 @@ public class StatusMessage
public required MessageType Type { get; set; } public required MessageType Type { get; set; }
public List<AlarmOrWarning>? Warnings { get; set; } public List<AlarmOrWarning>? Warnings { get; set; }
public List<AlarmOrWarning>? Alarms { get; set; } public List<AlarmOrWarning>? Alarms { get; set; }
public Int32 Timestamp { get; set; }
} }
public enum MessageType public enum MessageType

View File

@ -16,9 +16,9 @@ dotnet publish \
-r linux-x64 -r linux-x64
echo -e "\n============================ Deploy ============================\n" 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.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")

View File

@ -10,6 +10,7 @@ public class StatusMessage
public required MessageType Type { get; set; } public required MessageType Type { get; set; }
public List<AlarmOrWarning>? Warnings { get; set; } public List<AlarmOrWarning>? Warnings { get; set; }
public List<AlarmOrWarning>? Alarms { get; set; } public List<AlarmOrWarning>? Alarms { get; set; }
public Int32 Timestamp { get; set; }
} }
public enum MessageType public enum MessageType

View File

@ -1,4 +1,4 @@
#undef Amax #define Amax
#undef GridLimit #undef GridLimit
using System.Diagnostics; using System.Diagnostics;
@ -58,9 +58,10 @@ internal static class Program
private static Boolean _subscribedToQueue = false; private static Boolean _subscribedToQueue = false;
private static Boolean _subscribeToQueueForTheFirstTime = false; private static Boolean _subscribeToQueueForTheFirstTime = false;
private static SalimaxAlarmState _prevSalimaxState = SalimaxAlarmState.Green; private static SalimaxAlarmState _prevSalimaxState = SalimaxAlarmState.Green;
private static Int32 _heartBitInterval = 0; //private static Int32 _heartBitInterval = 0;
private const UInt16 NbrOfFileToConcatenate = 15; private const UInt16 NbrOfFileToConcatenate = 15;
private static UInt16 _counterOfFile = 0; private static UInt16 _counterOfFile = 0;
private static SalimaxAlarmState _salimaxAlarmState = SalimaxAlarmState.Green;
static Program() static Program()
{ {
@ -185,7 +186,6 @@ internal static class Program
LoadOnAcGrid = gridBusLoad, LoadOnAcGrid = gridBusLoad,
LoadOnAcIsland = loadOnAcIsland, LoadOnAcIsland = loadOnAcIsland,
LoadOnDc = dcLoad, LoadOnDc = dcLoad,
StateMachine = StateMachine.Default, StateMachine = StateMachine.Default,
EssControl = EssControl.Default, EssControl = EssControl.Default,
Log = new SystemLog { SalimaxAlarmState = SalimaxAlarmState.Green, Message = null, SalimaxAlarms = null, SalimaxWarnings = null}, //TODO: Put real stuff 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; var subscribedNow = false;
//Every 15 iterations(30 seconds), the installation sends a heartbit message to the queue //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 //When the controller boots, it tries to subscribe to the queue
if (_subscribeToQueueForTheFirstTime == false) if (_subscribeToQueueForTheFirstTime == false)
@ -287,16 +287,16 @@ internal static class Program
if (s3Bucket != null) if (s3Bucket != null)
RabbitMqManager.InformMiddleware(currentSalimaxState); RabbitMqManager.InformMiddleware(currentSalimaxState);
} }
else if (_subscribedToQueue && _heartBitInterval >= 30) // else if (_subscribedToQueue && _heartBitInterval >= 30)
{ // {
//Send a heartbit to the backend // //Send a heartbit to the backend
Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------"); // Console.WriteLine("----------------------------------------Sending Heartbit----------------------------------------");
_heartBitInterval = 0; // _heartBitInterval = 0;
currentSalimaxState.Type = MessageType.Heartbit; // currentSalimaxState.Type = MessageType.Heartbit;
//
if (s3Bucket != null) // if (s3Bucket != null)
RabbitMqManager.InformMiddleware(currentSalimaxState); // RabbitMqManager.InformMiddleware(currentSalimaxState);
} // }
//If there is an available message from the RabbitMQ Broker, apply the configuration file //If there is an available message from the RabbitMQ Broker, apply the configuration file
Configuration? config = SetConfigurationFile(); Configuration? config = SetConfigurationFile();
@ -441,13 +441,13 @@ internal static class Program
}); });
} }
var salimaxAlarmsState = warningList.Any() _salimaxAlarmState = warningList.Any()
? SalimaxAlarmState.Orange ? SalimaxAlarmState.Orange
: SalimaxAlarmState.Green; // this will be replaced by LedState : SalimaxAlarmState.Green; // this will be replaced by LedState
salimaxAlarmsState = alarmList.Any() _salimaxAlarmState = alarmList.Any()
? SalimaxAlarmState.Red ? SalimaxAlarmState.Red
: salimaxAlarmsState; // this will be replaced by LedState : _salimaxAlarmState; // this will be replaced by LedState
int.TryParse(s3Bucket?.Split("-")[0], out var installationId); int.TryParse(s3Bucket?.Split("-")[0], out var installationId);
@ -455,7 +455,7 @@ internal static class Program
{ {
InstallationId = installationId, InstallationId = installationId,
Product = 0, Product = 0,
Status = salimaxAlarmsState, Status = _salimaxAlarmState,
Type = MessageType.AlarmOrWarning, Type = MessageType.AlarmOrWarning,
Alarms = alarmList, Alarms = alarmList,
Warnings = warningList Warnings = warningList
@ -683,6 +683,7 @@ internal static class Program
private static async Task<Boolean> UploadCsv(StatusRecord status, DateTime timeStamp) private static async Task<Boolean> UploadCsv(StatusRecord status, DateTime timeStamp)
{ {
var csv = status.ToCsv().LogInfo(); var csv = status.ToCsv().LogInfo();
await RestApiSavingfile(csv); await RestApiSavingfile(csv);
@ -730,6 +731,26 @@ internal static class Program
Console.WriteLine(error); Console.WriteLine(error);
return false; 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++; _counterOfFile++;

View File

@ -170,7 +170,10 @@ function BatteryView(props: BatteryViewProps) {
<Route <Route
path={routes.mainstats + '*'} path={routes.mainstats + '*'}
element={ element={
<MainStats s3Credentials={props.s3Credentials}></MainStats> <MainStats
s3Credentials={props.s3Credentials}
id={props.installationId}
></MainStats>
} }
/> />
{props.values.batteryView.map((battery) => ( {props.values.batteryView.map((battery) => (

View File

@ -28,6 +28,7 @@ import ArrowBackIcon from '@mui/icons-material/ArrowBack';
interface MainStatsProps { interface MainStatsProps {
s3Credentials: I_S3Credentials; s3Credentials: I_S3Credentials;
id: number;
} }
function MainStats(props: MainStatsProps) { function MainStats(props: MainStatsProps) {
@ -92,11 +93,7 @@ function MainStats(props: MainStatsProps) {
const resultPromise: Promise<{ const resultPromise: Promise<{
chartData: BatteryDataInterface; chartData: BatteryDataInterface;
chartOverview: BatteryOverviewInterface; chartOverview: BatteryOverviewInterface;
}> = transformInputToBatteryViewData( }> = transformInputToBatteryViewData(props.s3Credentials, props.id);
props.s3Credentials,
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)).start,
UnixTime.now()
);
resultPromise resultPromise
.then((result) => { .then((result) => {
@ -186,6 +183,7 @@ function MainStats(props: MainStatsProps) {
chartOverview: BatteryOverviewInterface; chartOverview: BatteryOverviewInterface;
}> = transformInputToBatteryViewData( }> = transformInputToBatteryViewData(
props.s3Credentials, props.s3Credentials,
props.id,
UnixTime.fromTicks(startDate.unix()), UnixTime.fromTicks(startDate.unix()),
UnixTime.fromTicks(endDate.unix()) UnixTime.fromTicks(endDate.unix())
); );
@ -246,6 +244,7 @@ function MainStats(props: MainStatsProps) {
chartOverview: BatteryOverviewInterface; chartOverview: BatteryOverviewInterface;
}> = transformInputToBatteryViewData( }> = transformInputToBatteryViewData(
props.s3Credentials, props.s3Credentials,
props.id,
UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)), UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2)) UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2))
); );

View File

@ -136,7 +136,7 @@ function HistoryOfActions(props: HistoryProps) {
label="Select Action Date" label="Select Action Date"
name="timestamp" name="timestamp"
value={actionDate} value={actionDate}
onChange={(newDate) => handleDateChange(newDate.toDate())} onChange={(newDate) => handleDateChange(newDate)}
sx={{ sx={{
width: 450, width: 450,
marginTop: 2 marginTop: 2

View File

@ -4,9 +4,13 @@ import {
CardContent, CardContent,
CircularProgress, CircularProgress,
Container, Container,
FormControl,
Grid, Grid,
IconButton, IconButton,
InputLabel,
MenuItem,
Modal, Modal,
Select,
TextField, TextField,
Typography, Typography,
useTheme useTheme
@ -42,6 +46,8 @@ function InformationSalidomo(props: InformationSalidomoProps) {
useState(false); useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
const DeviceTypes = ['Cerbo', 'Venus'];
const installationContext = useContext(InstallationsContext); const installationContext = useContext(InstallationsContext);
const { const {
updateInstallation, updateInstallation,
@ -259,6 +265,61 @@ function InformationSalidomo(props: InformationSalidomoProps) {
/> />
</div> </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> <div>
<TextField <TextField
label={ label={
@ -275,19 +336,6 @@ function InformationSalidomo(props: InformationSalidomoProps) {
/> />
</div> </div>
{/*<div>*/}
{/* <TextField*/}
{/* label={*/}
{/* <FormattedMessage id="vrmLink" defaultMessage="vrmLink" />*/}
{/* }*/}
{/* name="vrmLink"*/}
{/* value={formValues.vrmLink}*/}
{/* onChange={handleChange}*/}
{/* variant="outlined"*/}
{/* fullWidth*/}
{/* />*/}
{/*</div>*/}
<div> <div>
<TextField <TextField
label="S3 Bucket Name" label="S3 Bucket Name"

View File

@ -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 { Card, CircularProgress, Grid, Typography } from '@mui/material';
import { I_Installation } from 'src/interfaces/InstallationTypes'; import { I_Installation } from 'src/interfaces/InstallationTypes';
import { UserContext } from 'src/contexts/userContext'; import { UserContext } from 'src/contexts/userContext';
@ -35,16 +35,13 @@ function Installation(props: singleInstallationProps) {
const context = useContext(UserContext); const context = useContext(UserContext);
const { currentUser } = context; const { currentUser } = context;
const location = useLocation().pathname; const location = useLocation().pathname;
const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false); const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
const webSocketsContext = useContext(WebSocketContext); const webSocketsContext = useContext(WebSocketContext);
const { getStatus } = webSocketsContext; const { getStatus } = webSocketsContext;
const [currentTab, setCurrentTab] = useState<string>(undefined); const [currentTab, setCurrentTab] = useState<string>(undefined);
const [values, setValues] = useState<TopologyValues | null>(null); const [values, setValues] = useState<TopologyValues | null>(null);
const status = getStatus(props.current_installation.id); const status = getStatus(props.current_installation.id);
const [
failedToCommunicateWithInstallation,
setFailedToCommunicateWithInstallation
] = useState(0);
const [connected, setConnected] = useState(true); const [connected, setConnected] = useState(true);
if (props.current_installation == undefined) { if (props.current_installation == undefined) {
@ -68,51 +65,122 @@ function Installation(props: singleInstallationProps) {
const s3Credentials = { s3Bucket, ...S3data }; const s3Credentials = { s3Bucket, ...S3data };
const fetchDataPeriodically = async () => { function timeout(delay: number) {
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20)); return new Promise((res) => setTimeout(res, delay));
}
try { const continueFetching = useRef(false);
const res = await fetchData(now, s3Credentials);
if (res != FetchResult.notAvailable && res != FetchResult.tryLater) { const fetchDataForOneTime = async () => {
setFailedToCommunicateWithInstallation(0); var timeperiodToSearch = 80;
setConnected(true); let res;
setValues( let timestampToFetch;
extractValues({
time: now, for (var i = timeperiodToSearch; i > 0; i -= 2) {
value: res timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i));
}) try {
); res = await fetchData(timestampToFetch, s3Credentials);
return true; if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
} else { break;
setFailedToCommunicateWithInstallation((prevCount) => { }
if (prevCount + 1 >= 3) { } catch (err) {
setConnected(false); console.error('Error fetching data:', err);
} return false;
return prevCount + 1;
});
} }
} catch (err) { }
if (i <= 0) {
setConnected(false);
return 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 () => { const fetchDataPeriodically = async () => {
let success = false; var timeperiodToSearch = 80;
const max_retransmissions = 3; let res;
let timestampToFetch;
for (let i = 0; i < max_retransmissions; i++) { for (var i = timeperiodToSearch; i > 0; i -= 2) {
success = await fetchDataPeriodically(); if (!continueFetching.current) {
await new Promise((resolve) => setTimeout(resolve, 1000)); return false;
if (success) { }
break; 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(() => { useEffect(() => {
let path = location.split('/'); let path = location.split('/');
setCurrentTab(path[path.length - 1]); setCurrentTab(path[path.length - 1]);
}, [location]); }, [location]);
@ -129,30 +197,28 @@ function Installation(props: singleInstallationProps) {
currentTab == 'configuration' || currentTab == 'configuration' ||
location.includes('batteryview') location.includes('batteryview')
) { ) {
let interval;
if ( if (
currentTab == 'live' || currentTab == 'live' ||
(location.includes('batteryview') && !location.includes('mainstats')) || (location.includes('batteryview') && !location.includes('mainstats')) ||
currentTab == 'pvview' currentTab == 'pvview'
) { ) {
fetchDataPeriodically(); if (!continueFetching.current) {
interval = setInterval(fetchDataPeriodically, 2000); continueFetching.current = true;
if (!fetchFunctionCalled) {
setFetchFunctionCalled(true);
fetchDataPeriodically();
}
}
} }
if (currentTab == 'configuration' || location.includes('mainstats')) { if (currentTab == 'configuration') {
fetchDataOnlyOneTime(); fetchDataForOneTime();
} }
// Cleanup function to cancel interval
return () => { return () => {
if ( continueFetching.current = false;
currentTab == 'live' ||
currentTab == 'pvview' ||
(location.includes('batteryview') && !location.includes('mainstats'))
) {
clearInterval(interval);
}
}; };
} else {
continueFetching.current = false;
} }
}, [currentTab, location]); }, [currentTab, location]);
@ -324,7 +390,12 @@ function Installation(props: singleInstallationProps) {
<Route <Route
path={routes.overview} path={routes.overview}
element={<Overview s3Credentials={s3Credentials}></Overview>} element={
<Overview
s3Credentials={s3Credentials}
id={props.current_installation.id}
></Overview>
}
/> />
<Route <Route

View File

@ -3,10 +3,10 @@ import { I_S3Credentials } from 'src/interfaces/S3Types';
import { FetchResult } from 'src/dataCache/dataCache'; import { FetchResult } from 'src/dataCache/dataCache';
import { DataRecord } from 'src/dataCache/data'; import { DataRecord } from 'src/dataCache/data';
import { S3Access } from 'src/dataCache/S3/S3Access'; import { S3Access } from 'src/dataCache/S3/S3Access';
import { parseCsv } from '../Log/graph.util'; import { parseChunk, parseCsv } from '../Log/graph.util';
import JSZip from 'jszip'; import JSZip from 'jszip';
export const fetchDailyData = ( export const fetchAggregatedData = (
date: string, date: string,
s3Credentials?: I_S3Credentials s3Credentials?: I_S3Credentials
): Promise<FetchResult<DataRecord>> => { ): Promise<FetchResult<DataRecord>> => {
@ -25,10 +25,7 @@ export const fetchDailyData = (
if (r.status === 404) { if (r.status === 404) {
return Promise.resolve(FetchResult.notAvailable); return Promise.resolve(FetchResult.notAvailable);
} else if (r.status === 200) { } 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 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'); const contentEncoding = r.headers.get('content-type');
if (contentEncoding != 'application/base64; charset=utf-8') { if (contentEncoding != 'application/base64; charset=utf-8') {
@ -43,11 +40,7 @@ export const fetchDailyData = (
const zip = await JSZip.loadAsync(byteArray); const zip = await JSZip.loadAsync(byteArray);
// Assuming the CSV file is named "data.csv" inside the ZIP archive // Assuming the CSV file is named "data.csv" inside the ZIP archive
const csvContent = await zip.file('data.csv').async('text'); const csvContent = await zip.file('data.csv').async('text');
return parseCsv(csvContent); return parseCsv(csvContent);
//console.log(parseCsv(text));
//return parseCsv(text);
} else { } else {
return Promise.resolve(FetchResult.notAvailable); return Promise.resolve(FetchResult.notAvailable);
} }
@ -61,7 +54,7 @@ export const fetchDailyData = (
export const fetchData = ( export const fetchData = (
timestamp: UnixTime, timestamp: UnixTime,
s3Credentials?: I_S3Credentials s3Credentials?: I_S3Credentials
): Promise<FetchResult<DataRecord>> => { ): Promise<FetchResult<Record<string, DataRecord>>> => {
const s3Path = `${timestamp.ticks}.csv`; const s3Path = `${timestamp.ticks}.csv`;
if (s3Credentials && s3Credentials.s3Bucket) { if (s3Credentials && s3Credentials.s3Bucket) {
const s3Access = new S3Access( const s3Access = new S3Access(
@ -79,12 +72,10 @@ export const fetchData = (
return Promise.resolve(FetchResult.notAvailable); return Promise.resolve(FetchResult.notAvailable);
} else if (r.status === 200) { } else if (r.status === 200) {
const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as 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'); const contentEncoding = r.headers.get('content-type');
if (contentEncoding != 'application/base64; charset=utf-8') { if (contentEncoding != 'application/base64; charset=utf-8') {
return parseCsv(csvtext); return parseChunk(csvtext);
} }
const byteArray = Uint8Array.from(atob(csvtext), (c) => 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 // Assuming the CSV file is named "data.csv" inside the ZIP archive
const csvContent = await zip.file('data.csv').async('text'); const csvContent = await zip.file('data.csv').async('text');
return parseCsv(csvContent); return parseChunk(csvContent);
} else { } else {
return Promise.resolve(FetchResult.notAvailable); return Promise.resolve(FetchResult.notAvailable);
} }

View File

@ -23,6 +23,35 @@ export const parseCsv = (text: string): DataRecord => {
.reduce((acc, current) => ({ ...acc, ...current }), {} as 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 { export interface I_BoxDataValue {
unit: string; unit: string;
value: string | number; value: string | number;

View File

@ -13,15 +13,16 @@ import {
import Button from '@mui/material/Button'; import Button from '@mui/material/Button';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import CircularProgress from '@mui/material/CircularProgress'; import CircularProgress from '@mui/material/CircularProgress';
import { TimeSpan, UnixTime } from '../../../dataCache/time';
import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers'; import { DateTimePicker, LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { UserContext } from '../../../contexts/userContext'; import { UserContext } from '../../../contexts/userContext';
import { UserType } from '../../../interfaces/UserTypes'; import { UserType } from '../../../interfaces/UserTypes';
import { TimeSpan, UnixTime } from '../../../dataCache/time';
interface OverviewProps { interface OverviewProps {
s3Credentials: I_S3Credentials; s3Credentials: I_S3Credentials;
id: number;
} }
const computeLast7Days = (): string[] => { const computeLast7Days = (): string[] => {
@ -43,7 +44,6 @@ const computeLast7Days = (): string[] => {
function Overview(props: OverviewProps) { function Overview(props: OverviewProps) {
const context = useContext(UserContext); const context = useContext(UserContext);
const { currentUser } = context; const { currentUser } = context;
const [dailyData, setDailyData] = useState(true); const [dailyData, setDailyData] = useState(true);
const [aggregatedData, setAggregatedData] = useState(false); const [aggregatedData, setAggregatedData] = useState(false);
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
@ -85,11 +85,7 @@ function Overview(props: OverviewProps) {
const resultPromise: Promise<{ const resultPromise: Promise<{
chartData: chartDataInterface; chartData: chartDataInterface;
chartOverview: overviewInterface; chartOverview: overviewInterface;
}> = transformInputToDailyData( }> = transformInputToDailyData(props.s3Credentials, props.id);
props.s3Credentials,
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)).start,
UnixTime.now()
);
resultPromise resultPromise
.then((result) => { .then((result) => {
@ -119,6 +115,7 @@ function Overview(props: OverviewProps) {
chartOverview: overviewInterface; chartOverview: overviewInterface;
}> = transformInputToDailyData( }> = transformInputToDailyData(
props.s3Credentials, props.s3Credentials,
props.id,
UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)), UnixTime.fromTicks(startX).earlier(TimeSpan.fromHours(2)),
UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2)) UnixTime.fromTicks(endX).earlier(TimeSpan.fromHours(2))
); );
@ -174,7 +171,6 @@ function Overview(props: OverviewProps) {
chartOverview: overviewInterface; chartOverview: overviewInterface;
}> = transformInputToAggregatedData( }> = transformInputToAggregatedData(
props.s3Credentials, props.s3Credentials,
dayjs().subtract(1, 'week'), dayjs().subtract(1, 'week'),
dayjs() dayjs()
); );
@ -246,6 +242,7 @@ function Overview(props: OverviewProps) {
chartOverview: overviewInterface; chartOverview: overviewInterface;
}> = transformInputToDailyData( }> = transformInputToDailyData(
props.s3Credentials, props.s3Credentials,
props.id,
UnixTime.fromTicks(startDate.unix()), UnixTime.fromTicks(startDate.unix()),
UnixTime.fromTicks(endDate.unix()) UnixTime.fromTicks(endDate.unix())
); );

View File

@ -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 { Card, CircularProgress, Grid, Typography } from '@mui/material';
import { I_Installation } from 'src/interfaces/InstallationTypes'; import { I_Installation } from 'src/interfaces/InstallationTypes';
import { UserContext } from 'src/contexts/userContext'; import { UserContext } from 'src/contexts/userContext';
@ -56,93 +56,110 @@ function Installation(props: singleInstallationProps) {
'-' + '-' +
'c0436b6a-d276-4cd8-9c44-1eae86cf5d0e'; 'c0436b6a-d276-4cd8-9c44-1eae86cf5d0e';
const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
const s3Credentials = { s3Bucket, ...S3data }; const s3Credentials = { s3Bucket, ...S3data };
const fetchDataOnlyOneTime = async () => { function timeout(delay: number) {
var timeperiodToSearch = 70; return new Promise((res) => setTimeout(res, delay));
}
for (var i = timeperiodToSearch; i > 0; i -= 2) { const continueFetching = useRef(false);
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 fetchDataPeriodically = async () => { const fetchDataPeriodically = async () => {
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20)); var timeperiodToSearch = 80;
let res;
let timestampToFetch;
try { for (var i = timeperiodToSearch; i > 0; i -= 2) {
const res = await fetchData(now, s3Credentials); if (!continueFetching.current) {
return false;
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;
});
} }
} 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; 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(() => { useEffect(() => {
let path = location.split('/'); let path = location.split('/');
setCurrentTab(path[path.length - 1]); setCurrentTab(path[path.length - 1]);
}, [location]); }, [location]);
useEffect(() => { useEffect(() => {
if ( if (location.includes('batteryview')) {
currentTab == 'live' || if (location.includes('batteryview') && !location.includes('mainstats')) {
currentTab == 'configuration' || if (!continueFetching.current) {
location.includes('batteryview') continueFetching.current = true;
) { if (!fetchFunctionCalled) {
let interval; setFetchFunctionCalled(true);
fetchDataPeriodically();
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);
} }
}
return () => {
continueFetching.current = false;
}; };
} else {
continueFetching.current = false;
} }
}, [currentTab, location]); }, [currentTab, location]);

View File

@ -3,8 +3,12 @@ import {
Alert, Alert,
Box, Box,
CircularProgress, CircularProgress,
FormControl,
IconButton, IconButton,
InputLabel,
MenuItem,
Modal, Modal,
Select,
TextField, TextField,
useTheme useTheme
} from '@mui/material'; } from '@mui/material';
@ -37,6 +41,8 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) {
'vpnIp', 'vpnIp',
'vrmLink' 'vrmLink'
]; ];
const DeviceTypes = ['Cerbo', 'Venus'];
const installationContext = useContext(InstallationsContext); const installationContext = useContext(InstallationsContext);
const { createInstallation, loading, setLoading, error, setError } = const { createInstallation, loading, setLoading, error, setError } =
installationContext; installationContext;
@ -128,6 +134,7 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) {
value={formValues.region} value={formValues.region}
onChange={handleChange} onChange={handleChange}
required required
error={formValues.region === ''}
/> />
</div> </div>
<div> <div>
@ -180,6 +187,36 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) {
/> />
</div> </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> <div>
<TextField <TextField
label={ label={

View File

@ -1,11 +1,14 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { import {
fetchDailyData, fetchAggregatedData,
fetchData fetchData
} from '../content/dashboards/Installations/fetchData'; } from '../content/dashboards/Installations/fetchData';
import { FetchResult } from '../dataCache/dataCache'; import { FetchResult } from '../dataCache/dataCache';
import { I_S3Credentials } from './S3Types'; import { CsvTimestamp, I_S3Credentials } from './S3Types';
import { UnixTime } from '../dataCache/time'; import { TimeSpan, UnixTime } from '../dataCache/time';
import { DataRecord } from '../dataCache/data';
import axiosConfig from '../Resources/axiosConfig';
import { AxiosError, AxiosResponse } from 'axios';
export interface chartInfoInterface { export interface chartInfoInterface {
magnitude: number; magnitude: number;
@ -74,8 +77,9 @@ export interface BatteryOverviewInterface {
export const transformInputToBatteryViewData = async ( export const transformInputToBatteryViewData = async (
s3Credentials: I_S3Credentials, s3Credentials: I_S3Credentials,
startTimestamp: UnixTime, id: number,
endTimestamp: UnixTime start_time?: UnixTime,
end_time?: UnixTime
): Promise<{ ): Promise<{
chartData: BatteryDataInterface; chartData: BatteryDataInterface;
chartOverview: BatteryOverviewInterface; chartOverview: BatteryOverviewInterface;
@ -124,42 +128,69 @@ export const transformInputToBatteryViewData = async (
}; };
let initialiation = true; let initialiation = true;
//console.log(start_time);
//console.log(end_time);
let timestampArray: CsvTimestamp[] = [];
let adjustedTimestampArray = []; 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 = []; const timestampPromises = [];
while (startUnixTime < endTimestamp) { if (start_time && end_time) {
timestampPromises.push(fetchData(startUnixTime, s3Credentials)); 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); for (var i = 0; i < timestampArray.length; i++) {
if (startUnixTime.ticks % 2 !== 0) { timestampPromises.push(
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + 1); fetchDataForOneTime(
} UnixTime.fromTicks(timestampArray[i].timestamp),
const adjustedTimestamp = new Date(startUnixTime.ticks * 1000); 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.setHours(
adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60 adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
); );
adjustedTimestampArray.push(adjustedTimestamp); adjustedTimestampArray.push(adjustedTimestamp);
} }
//Wait until fetching all the data const results: Promise<FetchResult<Record<string, DataRecord>>>[] =
const results = await Promise.all(timestampPromises); await Promise.all(timestampPromises);
for (let i = 0; i < results.length; i++) { for (let i = 0; i < results.length; i++) {
const result = results[i]; if (results[i] == null) {
if (
result === FetchResult.notAvailable ||
result === FetchResult.tryLater
) {
// Handle not available or try later case // Handle not available or try later case
} else { } 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 const battery_nodes = result['/Config/Devices/BatteryNodes'].value
.toString() .toString()
.split(','); .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 ( export const transformInputToDailyData = async (
s3Credentials: I_S3Credentials, s3Credentials: I_S3Credentials,
startTimestamp: UnixTime, id: number,
endTimestamp: UnixTime start_time?: UnixTime,
end_time?: UnixTime
): Promise<{ ): Promise<{
chartData: chartDataInterface; chartData: chartDataInterface;
chartOverview: overviewInterface; chartOverview: overviewInterface;
}> => { }> => {
//const navigate = useNavigate();
//const tokencontext = useContext(TokenContext);
//const { removeToken } = tokencontext;
const prefixes = ['', 'k', 'M', 'G', 'T']; const prefixes = ['', 'k', 'M', 'G', 'T'];
const MAX_NUMBER = 9999999; const MAX_NUMBER = 9999999;
const pathsToSearch = [ const pathsToSearch = [
@ -309,45 +368,71 @@ export const transformInputToDailyData = async (
}; };
}); });
//console.log(start_time);
//console.log(end_time);
let timestampArray: CsvTimestamp[] = [];
let adjustedTimestampArray = []; 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 = []; const timestampPromises = [];
while (startUnixTime < endTimestamp) { if (start_time && end_time) {
timestampPromises.push(fetchData(startUnixTime, s3Credentials)); 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); //while (startUnixTime < endTimestamp) {
if (startUnixTime.ticks % 2 !== 0) { for (var i = 0; i < timestampArray.length; i++) {
startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + 1); timestampPromises.push(
} fetchDataForOneTime(
const adjustedTimestamp = new Date(startUnixTime.ticks * 1000); 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 //Timezone offset is negative, so we convert the timestamp to the current zone by subtracting the corresponding offset
adjustedTimestamp.setHours( adjustedTimestamp.setHours(
adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60 adjustedTimestamp.getHours() - adjustedTimestamp.getTimezoneOffset() / 60
); );
adjustedTimestampArray.push(adjustedTimestamp); 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++) { for (let i = 0; i < results.length; i++) {
const result = results[i]; if (results[i] == null) {
if (
result === FetchResult.notAvailable ||
result === FetchResult.tryLater
) {
// Handle not available or try later case // Handle not available or try later case
} else { } 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; let category_index = 0;
// eslint-disable-next-line @typescript-eslint/no-loop-func
pathsToSearch.forEach((path) => { pathsToSearch.forEach((path) => {
if (result[path]) { if (result[path]) {
const value = result[path]; const value = result[path];
@ -370,6 +455,7 @@ export const transformInputToDailyData = async (
}); });
} }
} }
categories.forEach((category) => { categories.forEach((category) => {
let value = Math.max( let value = Math.max(
Math.abs(chartOverview[category].max), Math.abs(chartOverview[category].max),
@ -500,7 +586,7 @@ export const transformInputToAggregatedData = async (
while (currentDay.isBefore(end_date)) { while (currentDay.isBefore(end_date)) {
timestampPromises.push( timestampPromises.push(
fetchDailyData(currentDay.format('YYYY-MM-DD'), s3Credentials) fetchAggregatedData(currentDay.format('YYYY-MM-DD'), s3Credentials)
); );
currentDay = currentDay.add(1, 'day'); currentDay = currentDay.add(1, 'day');
} }

View File

@ -17,6 +17,12 @@ export interface ErrorMessage {
seen: boolean; seen: boolean;
} }
export interface CsvTimestamp {
id: number;
installationId: number;
timestamp: number;
}
export interface Action { export interface Action {
id: number; id: number;
userName: string; userName: string;