Compare commits

..

2 Commits

22 changed files with 543 additions and 381 deletions

View File

@ -16,7 +16,8 @@ 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" "10.2.4.211") 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")
for ip_address in "${ip_addresses[@]}"; do for ip_address in "${ip_addresses[@]}"; do
rsync -v \ rsync -v \

View File

@ -197,16 +197,17 @@ public static class Controller
(calibrationChargeForced == CalibrationChargeType.RepetitivelyEvery && repetitiveCalibrationRequired); (calibrationChargeForced == CalibrationChargeType.RepetitivelyEvery && repetitiveCalibrationRequired);
Console.WriteLine("Next Repetitive calibration charge date is "+statusRecord.Config.DayAndTimeForRepetitiveCalibration); Console.WriteLine("Next Repetitive calibration charge date is "+statusRecord.Config.DayAndTimeForRepetitiveCalibration);
Console.WriteLine("Next Additional calibration charge date is "+statusRecord.Config.DayAndTimeForAdditionalCalibration); Console.WriteLine("Next Additional calibration charge date is "+statusRecord.Config.DayAndTimeForAdditionalCalibration);
//Console.WriteLine("Time now is "+DateTime.Now);
if (statusRecord.Battery is not null) if (statusRecord.Battery is not null)
{ {
if (calibrationChargeForced == CalibrationChargeType.AdditionallyOnce && statusRecord.Battery.Eoc ) if (calibrationChargeForced == CalibrationChargeType.AdditionallyOnce && statusRecord.Battery.Eoc )
{ {
statusRecord.Config.ForceCalibrationChargeState = CalibrationChargeType.RepetitivelyEvery; statusRecord.Config.ForceCalibrationChargeState = CalibrationChargeType.RepetitivelyEvery;
_hasAdditionalCalibrationChargeChecked = false; //_hasAdditionalCalibrationChargeChecked = false;
} }
else if (calibrationChargeForced == CalibrationChargeType.RepetitivelyEvery && statusRecord.Battery.Eoc ) else if (calibrationChargeForced == CalibrationChargeType.RepetitivelyEvery && statusRecord.Battery.Eoc && _hasRepetitiveCalibrationChargeChecked)
{ {
statusRecord.Config.DayAndTimeForRepetitiveCalibration = statusRecord.Config.DayAndTimeForRepetitiveCalibration.AddDays(7); statusRecord.Config.DayAndTimeForRepetitiveCalibration = statusRecord.Config.DayAndTimeForRepetitiveCalibration.AddDays(7);
_hasRepetitiveCalibrationChargeChecked = false; _hasRepetitiveCalibrationChargeChecked = false;
@ -217,30 +218,41 @@ public static class Controller
private static Boolean RepetitiveCalibrationDateHasBeenPassed(DateTime calibrationChargeDate) private static Boolean RepetitiveCalibrationDateHasBeenPassed(DateTime calibrationChargeDate)
{ {
if (!_hasRepetitiveCalibrationChargeChecked) // if (!_hasRepetitiveCalibrationChargeChecked)
{ // {
// if (DateTime.Now >= calibrationChargeDate )
// {
// _hasRepetitiveCalibrationChargeChecked = true;
// return true;
// }
// return false;
// }
// return true;
if (DateTime.Now >= calibrationChargeDate ) if (DateTime.Now >= calibrationChargeDate )
{ {
_hasRepetitiveCalibrationChargeChecked = true; _hasRepetitiveCalibrationChargeChecked = true;
return true; return true;
} }
return false; return false;
}
return true;
} }
private static Boolean AdditionalCalibrationDateHasBeenPassed(DateTime calibrationChargeDate) private static Boolean AdditionalCalibrationDateHasBeenPassed(DateTime calibrationChargeDate)
{ {
if (!_hasAdditionalCalibrationChargeChecked) // if (!_hasAdditionalCalibrationChargeChecked)
{ // {
if (DateTime.Now >= calibrationChargeDate ) if (DateTime.Now >= calibrationChargeDate )
{ {
_hasAdditionalCalibrationChargeChecked = true; //_hasAdditionalCalibrationChargeChecked = true;
return true; return true;
} }
return false; return false;
} // }
return true; // return true;
} }

View File

@ -11,6 +11,7 @@ public static class MiddlewareAgent
{ {
public static UdpClient UdpListener = null!; public static UdpClient UdpListener = null!;
private static IPAddress? _controllerIpAddress; private static IPAddress? _controllerIpAddress;
private static EndPoint? _endPoint;
public static void InitializeCommunicationToMiddleware() public static void InitializeCommunicationToMiddleware()
{ {
@ -21,11 +22,11 @@ public static class MiddlewareAgent
} }
const Int32 udpPort = 9000; const Int32 udpPort = 9000;
var endPoint = new IPEndPoint(_controllerIpAddress, udpPort); _endPoint = new IPEndPoint(_controllerIpAddress, udpPort);
UdpListener = new UdpClient(); UdpListener = new UdpClient();
UdpListener.Client.Blocking = false; UdpListener.Client.Blocking = false;
UdpListener.Client.Bind(endPoint); UdpListener.Client.Bind(_endPoint);
} }
private static IPAddress FindVpnIp() private static IPAddress FindVpnIp()
@ -54,6 +55,7 @@ public static class MiddlewareAgent
{ {
if (UdpListener.Available > 0) if (UdpListener.Available > 0)
{ {
IPEndPoint? serverEndpoint = null; IPEndPoint? serverEndpoint = null;
var replyMessage = "ACK"; var replyMessage = "ACK";
@ -72,6 +74,13 @@ public static class MiddlewareAgent
return config; return config;
} }
if (!_endPoint.Equals((IPEndPoint)UdpListener.Client.LocalEndPoint))
{
Console.WriteLine("UDP address has changed, rebinding...");
InitializeCommunicationToMiddleware();
}
return null; return null;
} }

View File

@ -1,4 +1,4 @@
#define Amax #undef Amax
#undef GridLimit #undef GridLimit
using System.Reactive.Linq; using System.Reactive.Linq;

View File

@ -31,6 +31,7 @@ interface BatteryViewProps {
values: TopologyValues; values: TopologyValues;
s3Credentials: I_S3Credentials; s3Credentials: I_S3Credentials;
installationId: number; installationId: number;
productNum: number;
} }
function BatteryView(props: BatteryViewProps) { function BatteryView(props: BatteryViewProps) {
@ -157,6 +158,7 @@ function BatteryView(props: BatteryViewProps) {
s3Credentials={props.s3Credentials} s3Credentials={props.s3Credentials}
batteryData={findBatteryData(battery.BatteryId)} batteryData={findBatteryData(battery.BatteryId)}
installationId={props.installationId} installationId={props.installationId}
productNum={props.productNum}
></DetailedBatteryView> ></DetailedBatteryView>
} }
/> />
@ -272,23 +274,29 @@ function BatteryView(props: BatteryViewProps) {
battery.AverageTemperature.unit} battery.AverageTemperature.unit}
</TableCell> </TableCell>
{props.productNum === 0 && (
<>
<TableCell <TableCell
style={{ style={{
width: '20%', width: '20%',
textAlign: 'center', textAlign: 'center',
padding: '8px', padding: '8px',
fontWeight: fontWeight:
battery.Warnings.value !== '' ? 'bold' : 'inherit', battery.Warnings.value !== ''
? 'bold'
: 'inherit',
backgroundColor: backgroundColor:
battery.Warnings.value === '' ? 'inherit' : '#ff9900', battery.Warnings.value === ''
? 'inherit'
: '#ff9900',
color: color:
battery.Warnings.value != '' ? 'black' : 'inherit' battery.Warnings.value != '' ? 'black' : 'inherit'
}} }}
> >
{battery.Warnings.value === '' ? ( {battery.Warnings.value === '' ? (
'None' 'None'
) : battery.Warnings.value.toString().split('-').length > ) : battery.Warnings.value.toString().split('-')
1 ? ( .length > 1 ? (
<Link <Link
style={{ color: 'black' }} style={{ color: 'black' }}
to={ to={
@ -313,14 +321,17 @@ function BatteryView(props: BatteryViewProps) {
fontWeight: fontWeight:
battery.Alarms.value !== '' ? 'bold' : 'inherit', battery.Alarms.value !== '' ? 'bold' : 'inherit',
backgroundColor: backgroundColor:
battery.Alarms.value === '' ? 'inherit' : '#FF033E', battery.Alarms.value === ''
color: battery.Alarms.value != '' ? 'black' : 'inherit' ? 'inherit'
: '#FF033E',
color:
battery.Alarms.value != '' ? 'black' : 'inherit'
}} }}
> >
{battery.Alarms.value === '' ? ( {battery.Alarms.value === '' ? (
'None' 'None'
) : battery.Alarms.value.toString().split('-').length > ) : battery.Alarms.value.toString().split('-')
1 ? ( .length > 1 ? (
<Link <Link
style={{ color: 'black' }} style={{ color: 'black' }}
to={ to={
@ -338,6 +349,8 @@ function BatteryView(props: BatteryViewProps) {
battery.Alarms.value battery.Alarms.value
)} )}
</TableCell> </TableCell>
</>
)}
</TableRow> </TableRow>
))} ))}
</TableBody> </TableBody>

View File

@ -25,6 +25,7 @@ interface DetailedBatteryViewProps {
s3Credentials: I_S3Credentials; s3Credentials: I_S3Credentials;
batteryData: Battery; batteryData: Battery;
installationId: number; installationId: number;
productNum: number;
} }
function DetailedBatteryView(props: DetailedBatteryViewProps) { function DetailedBatteryView(props: DetailedBatteryViewProps) {
@ -273,6 +274,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
<ArrowBackIcon /> <ArrowBackIcon />
</IconButton> </IconButton>
{props.productNum === 0 && (
<Button <Button
variant="contained" variant="contained"
onClick={handleUpdateFirmware} onClick={handleUpdateFirmware}
@ -289,6 +291,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
defaultMessage="Update Firmware" defaultMessage="Update Firmware"
/> />
</Button> </Button>
)}
</Grid> </Grid>
</Grid> </Grid>
<Grid container> <Grid container>
@ -310,7 +313,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
style={{ style={{
...batteryStringStyle, ...batteryStringStyle,
backgroundColor: backgroundColor:
props.batteryData.String1Active.value == 'True' props.batteryData.String1Active.value == 'True' ||
props.batteryData.String4Active.value == 0
? '#32CD32' ? '#32CD32'
: '#FF033E' : '#FF033E'
}} }}
@ -319,7 +323,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
style={{ style={{
...batteryStringStyle, ...batteryStringStyle,
backgroundColor: backgroundColor:
props.batteryData.String2Active.value == 'True' props.batteryData.String2Active.value == 'True' ||
props.batteryData.String4Active.value == 0
? '#32CD32' ? '#32CD32'
: '#FF033E' : '#FF033E'
}} }}
@ -328,7 +333,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
style={{ style={{
...batteryStringStyle, ...batteryStringStyle,
backgroundColor: backgroundColor:
props.batteryData.String3Active.value == 'True' props.batteryData.String3Active.value == 'True' ||
props.batteryData.String4Active.value == 0
? '#32CD32' ? '#32CD32'
: '#FF033E' : '#FF033E'
}} }}
@ -337,7 +343,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
style={{ style={{
...batteryStringStyle, ...batteryStringStyle,
backgroundColor: backgroundColor:
props.batteryData.String4Active.value == 'True' props.batteryData.String4Active.value == 'True' ||
props.batteryData.String4Active.value == 0
? '#32CD32' ? '#32CD32'
: '#FF033E' : '#FF033E'
}} }}
@ -346,7 +353,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
style={{ style={{
...batteryStringStyle, ...batteryStringStyle,
backgroundColor: backgroundColor:
props.batteryData.String5Active.value == 'True' props.batteryData.String5Active.value == 'True' ||
props.batteryData.String4Active.value == 0
? '#32CD32' ? '#32CD32'
: '#FF033E' : '#FF033E'
}} }}
@ -514,6 +522,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
props.batteryData.Power.unit} props.batteryData.Power.unit}
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell <TableCell
component="th" component="th"
@ -609,6 +618,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
</Card> </Card>
</Grid> </Grid>
{/*----------------------------------------------------------------------------------------------------------------------------------*/} {/*----------------------------------------------------------------------------------------------------------------------------------*/}
{props.productNum === 0 && (
<>
<Grid item md={3} xs={3}> <Grid item md={3} xs={3}>
<Card <Card
sx={{ sx={{
@ -799,6 +810,8 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
</TableContainer> </TableContainer>
</Card> </Card>
</Grid> </Grid>
</>
)}
{/*----------------------------------------------------------------------------------------------------------------------------------*/} {/*----------------------------------------------------------------------------------------------------------------------------------*/}
<Grid item md={3} xs={3}> <Grid item md={3} xs={3}>
@ -807,6 +820,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
overflow: 'visible', overflow: 'visible',
marginTop: '30px', marginTop: '30px',
marginLeft: '20px', marginLeft: '20px',
marginBottom: '20px',
display: 'flex', display: 'flex',
flexDirection: 'column', flexDirection: 'column',
alignItems: 'center', alignItems: 'center',
@ -992,6 +1006,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
</Grid> </Grid>
{/*----------------------------------------------------------------------------------------------------------------------------------*/} {/*----------------------------------------------------------------------------------------------------------------------------------*/}
<Grid item md={3} xs={3}> <Grid item md={3} xs={3}>
<Card <Card
sx={{ sx={{
@ -1044,6 +1059,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
props.batteryData.Eoc.unit} props.batteryData.Eoc.unit}
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell <TableCell
component="th" component="th"
@ -1066,6 +1082,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
props.batteryData.SerialNumber.unit} props.batteryData.SerialNumber.unit}
</TableCell> </TableCell>
</TableRow> </TableRow>
<TableRow> <TableRow>
<TableCell <TableCell
component="th" component="th"
@ -1110,6 +1127,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
props.batteryData.TimeSinceTOC.unit} props.batteryData.TimeSinceTOC.unit}
</TableCell> </TableCell>
</TableRow> </TableRow>
{props.productNum === 0 && (
<TableRow> <TableRow>
<TableCell <TableCell
component="th" component="th"
@ -1132,6 +1150,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
props.batteryData.CalibrationChargeRequested.unit} props.batteryData.CalibrationChargeRequested.unit}
</TableCell> </TableCell>
</TableRow> </TableRow>
)}
<TableRow> <TableRow>
<TableCell <TableCell
component="th" component="th"
@ -1181,6 +1200,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) {
</TableContainer> </TableContainer>
</Card> </Card>
</Grid> </Grid>
{/*----------------------------------------------------------------------------------------------------------------------------------*/} {/*----------------------------------------------------------------------------------------------------------------------------------*/}
{/*<Grid item md={1.5} xs={1.5}>*/} {/*<Grid item md={1.5} xs={1.5}>*/}

View File

@ -43,7 +43,7 @@ function Configuration(props: ConfigurationProps) {
const CalibrationChargeOptions = [ const CalibrationChargeOptions = [
'Repetitive Calibration', 'Repetitive Calibration',
'Additional Calibration', 'Additional Calibration',
'Force Calibration Now' 'Force Permanent Calibration Now'
]; ];
const CalibrationChargeOptionsController = [ const CalibrationChargeOptionsController = [
@ -93,13 +93,11 @@ function Configuration(props: ConfigurationProps) {
CalibrationChargeOptionsController.indexOf( CalibrationChargeOptionsController.indexOf(
props.values.calibrationChargeState[0].value.toString() props.values.calibrationChargeState[0].value.toString()
) == 0 ) == 0
? dayjs ? dayjs(props.values.repetitiveCalibrationChargeDate[0].value)
.utc(props.values.repetitiveCalibrationChargeDate[0].value) // .add(localOffset, 'minute')
.add(localOffset, 'minute')
.toDate() .toDate()
: dayjs : dayjs(props.values.additionalCalibrationChargeDate[0].value)
.utc(props.values.additionalCalibrationChargeDate[0].value) // .add(localOffset, 'minute')
.add(localOffset, 'minute')
.toDate() .toDate()
}); });
@ -129,9 +127,14 @@ function Configuration(props: ConfigurationProps) {
minimumSoC: formValues.minimumSoC, minimumSoC: formValues.minimumSoC,
gridSetPoint: formValues.gridSetPoint, gridSetPoint: formValues.gridSetPoint,
CalibrationChargeState: formValues.CalibrationChargeState, CalibrationChargeState: formValues.CalibrationChargeState,
calibrationChargeDate: dayjs(formValues.calibrationChargeDate).toDate() calibrationChargeDate: dayjs
.utc(formValues.calibrationChargeDate)
.add(localOffset, 'minute')
.toDate()
}; };
// console.log('will send ', dayjs(formValues.calibrationChargeDate));
setLoading(true); setLoading(true);
const res = await axiosConfig const res = await axiosConfig
.post( .post(
@ -157,9 +160,11 @@ function Configuration(props: ConfigurationProps) {
}; };
const handleConfirm = (newDate) => { const handleConfirm = (newDate) => {
//console.log('non adapted day is ', newDate);
//console.log('adapted day is ', dayjs.utc(newDate).toDate());
setFormValues({ setFormValues({
...formValues, ...formValues,
['calibrationChargeDate']: dayjs.utc(newDate).toDate() ['calibrationChargeDate']: dayjs(newDate).toDate()
}); });
}; };
@ -170,6 +175,7 @@ function Configuration(props: ConfigurationProps) {
if (difference < 0) { if (difference < 0) {
difference += 7; difference += 7;
} }
const adjustedDate = currentDate.add(difference, 'day'); const adjustedDate = currentDate.add(difference, 'day');
setFormValues({ setFormValues({
...formValues, ...formValues,
@ -185,13 +191,11 @@ function Configuration(props: ConfigurationProps) {
), ),
['calibrationChargeDate']: ['calibrationChargeDate']:
CalibrationChargeOptions.indexOf(event.target.value) == 0 CalibrationChargeOptions.indexOf(event.target.value) == 0
? dayjs ? dayjs(props.values.repetitiveCalibrationChargeDate[0].value)
.utc(props.values.repetitiveCalibrationChargeDate[0].value) // .add(localOffset, 'minute')
.add(localOffset, 'minute')
.toDate() .toDate()
: dayjs : dayjs(props.values.additionalCalibrationChargeDate[0].value)
.utc(props.values.additionalCalibrationChargeDate[0].value) // .add(localOffset, 'minute')
.add(localOffset, 'minute')
.toDate() .toDate()
}); });
}; };
@ -349,7 +353,7 @@ function Configuration(props: ConfigurationProps) {
format="DD/MM/YYYY HH:mm" format="DD/MM/YYYY HH:mm"
ampm={false} ampm={false}
label="Select Next Calibration Charge Date" label="Select Next Calibration Charge Date"
value={dayjs.utc(formValues.calibrationChargeDate)} value={dayjs(formValues.calibrationChargeDate)}
onChange={handleConfirm} onChange={handleConfirm}
sx={{ sx={{
marginTop: 2 marginTop: 2
@ -397,8 +401,8 @@ function Configuration(props: ConfigurationProps) {
<TimePicker <TimePicker
ampm={false} ampm={false}
label="Calibration Charge Hour" label="Calibration Charge Hour"
value={dayjs.utc(formValues.calibrationChargeDate)} value={dayjs(formValues.calibrationChargeDate)}
onChange={handleConfirm} onChange={(newTime) => handleConfirm(dayjs(newTime))}
/> />
</LocalizationProvider> </LocalizationProvider>
</div> </div>

View File

@ -307,7 +307,7 @@ function Information(props: InformationProps) {
label="S3 Bucket Name" label="S3 Bucket Name"
name="s3writesecretkey" name="s3writesecretkey"
value={ value={
formValues.id + formValues.s3BucketId +
'-3e5b3069-214a-43ee-8d85-57d72000c19d' '-3e5b3069-214a-43ee-8d85-57d72000c19d'
} }
variant="outlined" variant="outlined"

View File

@ -37,7 +37,7 @@ function InformationSalidomo(props: InformationSalidomoProps) {
const { currentUser } = context; const { currentUser } = context;
const theme = useTheme(); const theme = useTheme();
const [formValues, setFormValues] = useState(props.values); const [formValues, setFormValues] = useState(props.values);
const requiredFields = ['name', 'region', 'location', 'country']; const requiredFields = ['installationName', 'region', 'location', 'country'];
const [openModalDeleteInstallation, setOpenModalDeleteInstallation] = const [openModalDeleteInstallation, setOpenModalDeleteInstallation] =
useState(false); useState(false);
const navigate = useNavigate(); const navigate = useNavigate();
@ -194,7 +194,7 @@ function InformationSalidomo(props: InformationSalidomoProps) {
/> />
} }
name="installationName" name="installationName"
value={formValues.name} value={formValues.installationName}
onChange={handleChange} onChange={handleChange}
variant="outlined" variant="outlined"
fullWidth fullWidth
@ -280,7 +280,8 @@ function InformationSalidomo(props: InformationSalidomoProps) {
label="S3 Bucket Name" label="S3 Bucket Name"
name="s3writesecretkey" name="s3writesecretkey"
value={ value={
formValues.id + '-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e' formValues.s3BucketId +
'-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e'
} }
variant="outlined" variant="outlined"
fullWidth fullWidth

View File

@ -102,11 +102,11 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
<TableBody> <TableBody>
{props.installations.map((installation) => { {props.installations.map((installation) => {
const isInstallationSelected = const isInstallationSelected =
installation.id === selectedInstallation; installation.s3BucketId === selectedInstallation;
const status = getStatus(installation.id); const status = getStatus(installation.id);
const rowStyles = const rowStyles =
isRowHovered === installation.id isRowHovered === installation.s3BucketId
? { ? {
cursor: 'pointer', cursor: 'pointer',
backgroundColor: theme.colors.primary.lighter backgroundColor: theme.colors.primary.lighter
@ -116,13 +116,15 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
return ( return (
<TableRow <TableRow
hover hover
key={installation.id} key={installation.s3BucketId}
selected={isInstallationSelected} selected={isInstallationSelected}
style={rowStyles} style={rowStyles}
onClick={() => onClick={() =>
handleSelectOneInstallation(installation.id) handleSelectOneInstallation(installation.s3BucketId)
}
onMouseEnter={() =>
handleRowMouseEnter(installation.s3BucketId)
} }
onMouseEnter={() => handleRowMouseEnter(installation.id)}
onMouseLeave={() => handleRowMouseLeave()} onMouseLeave={() => handleRowMouseLeave()}
> >
<TableCell> <TableCell>

View File

@ -1,5 +1,11 @@
import React, { useContext, useEffect, useState } from 'react'; import React, { useContext, useEffect, useState } from 'react';
import { Card, CircularProgress, Grid, Typography } from '@mui/material'; import {
Card,
CircularProgress,
Container,
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';
import AccessContextProvider from 'src/contexts/AccessContextProvider'; import AccessContextProvider from 'src/contexts/AccessContextProvider';
@ -39,6 +45,14 @@ function Installation(props: singleInstallationProps) {
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 [
openModalUnableToCommunicateWIthInstallation,
setOpenModalUnableToCommunicateWIthInstallation
] = useState(false);
if (props.current_installation == undefined) { if (props.current_installation == undefined) {
return null; return null;
@ -48,14 +62,15 @@ function Installation(props: singleInstallationProps) {
s3Region: props.current_installation.s3Region, s3Region: props.current_installation.s3Region,
s3Provider: props.current_installation.s3Provider, s3Provider: props.current_installation.s3Provider,
s3Key: props.current_installation.s3Key, s3Key: props.current_installation.s3Key,
s3Secret: props.current_installation.s3Secret s3Secret: props.current_installation.s3Secret,
s3BucketId: props.current_installation.s3BucketId
}; };
const s3Bucket = const s3Bucket =
props.current_installation.product === 0 props.current_installation.product === 0
? props.current_installation.id.toString() + ? props.current_installation.s3BucketId.toString() +
'-3e5b3069-214a-43ee-8d85-57d72000c19d' '-3e5b3069-214a-43ee-8d85-57d72000c19d'
: props.current_installation.id.toString() + : props.current_installation.s3BucketId.toString() +
'-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e'; '-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e';
const s3Credentials = { s3Bucket, ...S3data }; const s3Credentials = { s3Bucket, ...S3data };
@ -67,6 +82,8 @@ function Installation(props: singleInstallationProps) {
const res = await fetchData(now, s3Credentials); const res = await fetchData(now, s3Credentials);
if (res != FetchResult.notAvailable && res != FetchResult.tryLater) { if (res != FetchResult.notAvailable && res != FetchResult.tryLater) {
setFailedToCommunicateWithInstallation(0);
setOpenModalUnableToCommunicateWIthInstallation(false);
setValues( setValues(
extractValues({ extractValues({
time: now, time: now,
@ -74,6 +91,13 @@ function Installation(props: singleInstallationProps) {
}) })
); );
return true; return true;
} else {
setFailedToCommunicateWithInstallation((prevCount) => {
if (prevCount + 1 >= 3) {
setOpenModalUnableToCommunicateWIthInstallation(true);
}
return prevCount + 1;
});
} }
} catch (err) { } catch (err) {
return false; return false;
@ -99,6 +123,12 @@ function Installation(props: singleInstallationProps) {
setCurrentTab(path[path.length - 1]); setCurrentTab(path[path.length - 1]);
}, [location]); }, [location]);
useEffect(() => {
if (status === -1) {
setOpenModalUnableToCommunicateWIthInstallation(true);
}
}, [status]);
useEffect(() => { useEffect(() => {
if ( if (
currentTab == 'live' || currentTab == 'live' ||
@ -118,7 +148,7 @@ function Installation(props: singleInstallationProps) {
fetchDataOnlyOneTime(); fetchDataOnlyOneTime();
} }
// Cleanup function to cancel interval and update isMounted when unmounted // Cleanup function to cancel interval
return () => { return () => {
if ( if (
currentTab == 'live' || currentTab == 'live' ||
@ -130,6 +160,10 @@ function Installation(props: singleInstallationProps) {
} }
}, [currentTab, location]); }, [currentTab, location]);
const UnableToCommunicateModalHandleOk = () => {
setOpenModalUnableToCommunicateWIthInstallation(false);
};
return ( return (
<> <>
<Grid item xs={12} md={12}> <Grid item xs={12} md={12}>
@ -264,6 +298,31 @@ function Installation(props: singleInstallationProps) {
alignItems="stretch" alignItems="stretch"
spacing={0} spacing={0}
> >
{openModalUnableToCommunicateWIthInstallation && (
<Container
maxWidth="xl"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
height: '70vh'
}}
>
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
<Typography
variant="body2"
style={{ color: 'black', fontWeight: 'bold' }}
mt={2}
>
Unable to communicate with the installation
</Typography>
<Typography variant="body2" style={{ color: 'black' }}>
Please wait or refresh the page
</Typography>
</Container>
)}
<Routes> <Routes>
<Route <Route
path={routes.information} path={routes.information}
@ -283,6 +342,7 @@ function Installation(props: singleInstallationProps) {
values={values} values={values}
s3Credentials={s3Credentials} s3Credentials={s3Credentials}
installationId={props.current_installation.id} installationId={props.current_installation.id}
productNum={props.current_installation.product}
></BatteryView> ></BatteryView>
} }
></Route> ></Route>

View File

@ -63,11 +63,11 @@ function InstallationSearch(props: installationSearchProps) {
{filteredData.map((installation) => { {filteredData.map((installation) => {
return ( return (
<Route <Route
key={installation.id} key={installation.s3BucketId}
path={routes.installation + installation.id + '*'} path={routes.installation + installation.s3BucketId + '*'}
element={ element={
<Installation <Installation
key={installation.id} key={installation.s3BucketId}
current_installation={installation} current_installation={installation}
type="installation" type="installation"
></Installation> ></Installation>

View File

@ -25,6 +25,7 @@ function installationForm(props: installationFormProps) {
const theme = useTheme(); const theme = useTheme();
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
const [formValues, setFormValues] = useState<Partial<I_Installation>>({ const [formValues, setFormValues] = useState<Partial<I_Installation>>({
installationName: '',
name: '', name: '',
region: '', region: '',
location: '', location: '',
@ -32,7 +33,14 @@ function installationForm(props: installationFormProps) {
vpnIp: '', vpnIp: '',
orderNumbers: '' orderNumbers: ''
}); });
const requiredFields = ['name', 'region', 'location', 'country', 'vpnIp']; const requiredFields = [
'installationName',
'name',
'region',
'location',
'country',
'vpnIp'
];
const tokencontext = useContext(TokenContext); const tokencontext = useContext(TokenContext);
const { removeToken } = tokencontext; const { removeToken } = tokencontext;
@ -105,6 +113,21 @@ function installationForm(props: installationFormProps) {
noValidate noValidate
autoComplete="off" autoComplete="off"
> >
<div>
<TextField
label={
<FormattedMessage
id="installationName"
defaultMessage="Installation Name"
/>
}
name="installationName"
value={formValues.installationName}
onChange={handleChange}
required
error={formValues.installationName === ''}
/>
</div>
<div> <div>
<TextField <TextField
label={ label={
@ -120,6 +143,7 @@ function installationForm(props: installationFormProps) {
error={formValues.name === ''} error={formValues.name === ''}
/> />
</div> </div>
<div> <div>
<TextField <TextField
label={<FormattedMessage id="region" defaultMessage="Region" />} label={<FormattedMessage id="region" defaultMessage="Region" />}

View File

@ -174,7 +174,6 @@ const batteryPaths = [
'/Battery/Devices/%id%/Dc/Voltage', '/Battery/Devices/%id%/Dc/Voltage',
'/Battery/Devices/%id%/Soc', '/Battery/Devices/%id%/Soc',
'/Battery/Devices/%id%/Temperatures/Cells/Average', '/Battery/Devices/%id%/Temperatures/Cells/Average',
//'/Log/SalimaxWarnings/Battery/%id%',
'/Battery/Devices/%id%/Warnings', '/Battery/Devices/%id%/Warnings',
'/Battery/Devices/%id%/Alarms', '/Battery/Devices/%id%/Alarms',
@ -319,6 +318,8 @@ export const extractValues = (
): TopologyValues | null => { ): TopologyValues | null => {
const extractedValues: TopologyValues = {} as TopologyValues; const extractedValues: TopologyValues = {} as TopologyValues;
// console.log('timeSeriesData=', timeSeriesData);
for (const topologyKey of Object.keys(topologyPaths)) { for (const topologyKey of Object.keys(topologyPaths)) {
//Each topologykey may have more than one paths (for example inverter) //Each topologykey may have more than one paths (for example inverter)
const paths = topologyPaths[topologyKey]; const paths = topologyPaths[topologyKey];

View File

@ -100,11 +100,11 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
<TableBody> <TableBody>
{props.installations.map((installation) => { {props.installations.map((installation) => {
const isInstallationSelected = const isInstallationSelected =
installation.id === selectedInstallation; installation.s3BucketId === selectedInstallation;
const status = getStatus(installation.id); const status = getStatus(installation.id);
const rowStyles = const rowStyles =
isRowHovered === installation.id isRowHovered === installation.s3BucketId
? { ? {
cursor: 'pointer', cursor: 'pointer',
backgroundColor: theme.colors.primary.lighter backgroundColor: theme.colors.primary.lighter
@ -114,13 +114,15 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
return ( return (
<TableRow <TableRow
hover hover
key={installation.id} key={installation.s3BucketId}
selected={isInstallationSelected} selected={isInstallationSelected}
style={rowStyles} style={rowStyles}
onClick={() => onClick={() =>
handleSelectOneInstallation(installation.id) handleSelectOneInstallation(installation.s3BucketId)
}
onMouseEnter={() =>
handleRowMouseEnter(installation.s3BucketId)
} }
onMouseEnter={() => handleRowMouseEnter(installation.id)}
onMouseLeave={() => handleRowMouseLeave()} onMouseLeave={() => handleRowMouseLeave()}
> >
<TableCell> <TableCell>
@ -132,7 +134,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
noWrap noWrap
sx={{ marginTop: '10px', fontSize: 'small' }} sx={{ marginTop: '10px', fontSize: 'small' }}
> >
{installation.name} {installation.installationName}
</Typography> </Typography>
</TableCell> </TableCell>

View File

@ -10,7 +10,6 @@ import {
} from 'src/content/dashboards/Log/graph.util'; } from 'src/content/dashboards/Log/graph.util';
import { WebSocketContext } from 'src/contexts/WebSocketContextProvider'; import { WebSocketContext } from 'src/contexts/WebSocketContextProvider';
import { FormattedMessage } from 'react-intl'; import { FormattedMessage } from 'react-intl';
import Overview from '../Overview/overview';
import { fetchData } from 'src/content/dashboards/Installations/fetchData'; import { fetchData } from 'src/content/dashboards/Installations/fetchData';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom'; import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import routes from '../../../Resources/routes.json'; import routes from '../../../Resources/routes.json';
@ -41,11 +40,12 @@ function Installation(props: singleInstallationProps) {
s3Region: props.current_installation.s3Region, s3Region: props.current_installation.s3Region,
s3Provider: props.current_installation.s3Provider, s3Provider: props.current_installation.s3Provider,
s3Key: props.current_installation.s3Key, s3Key: props.current_installation.s3Key,
s3Secret: props.current_installation.s3Secret s3Secret: props.current_installation.s3Secret,
s3BucketId: props.current_installation.s3BucketId
}; };
const s3Bucket = const s3Bucket =
props.current_installation.id.toString() + props.current_installation.s3BucketId.toString() +
'-' + '-' +
'c0436b6a-d276-4cd8-9c44-1eae86cf5d0e'; 'c0436b6a-d276-4cd8-9c44-1eae86cf5d0e';
@ -182,13 +182,10 @@ function Installation(props: singleInstallationProps) {
values={values} values={values}
s3Credentials={s3Credentials} s3Credentials={s3Credentials}
installationId={props.current_installation.id} installationId={props.current_installation.id}
productNum={props.current_installation.product}
></BatteryView> ></BatteryView>
} }
></Route> ></Route>
<Route
path={routes.overview}
element={<Overview s3Credentials={s3Credentials}></Overview>}
/>
<Route <Route
path={'*'} path={'*'}

View File

@ -107,11 +107,11 @@ function InstallationSearch(props: installationSearchProps) {
{filteredData.map((installation) => { {filteredData.map((installation) => {
return ( return (
<Route <Route
key={installation.id} key={installation.s3BucketId}
path={routes.installation + installation.id + '*'} path={routes.installation + installation.s3BucketId + '*'}
element={ element={
<Installation <Installation
key={installation.id} key={installation.s3BucketId}
current_installation={installation} current_installation={installation}
type="installation" type="installation"
></Installation> ></Installation>

View File

@ -23,14 +23,20 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) {
const theme = useTheme(); const theme = useTheme();
const [open, setOpen] = useState(true); const [open, setOpen] = useState(true);
const [formValues, setFormValues] = useState<Partial<I_Installation>>({ const [formValues, setFormValues] = useState<Partial<I_Installation>>({
name: '', installationName: '',
region: '', region: '',
location: '', location: '',
country: '', country: '',
vpnIp: '', vpnIp: '',
vrmLink: '' vrmLink: ''
}); });
const requiredFields = ['name', 'location', 'country', 'vpnIp', 'vrmLink']; const requiredFields = [
'installationName',
'location',
'country',
'vpnIp',
'vrmLink'
];
const installationContext = useContext(InstallationsContext); const installationContext = useContext(InstallationsContext);
const { createInstallation, loading, setLoading, error, setError } = const { createInstallation, loading, setLoading, error, setError } =
installationContext; installationContext;
@ -108,11 +114,11 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) {
defaultMessage="Installation Name" defaultMessage="Installation Name"
/> />
} }
name="name" name="installationName"
value={formValues.name} value={formValues.installationName}
onChange={handleChange} onChange={handleChange}
required required
error={formValues.name === ''} error={formValues.installationName === ''}
/> />
</div> </div>
<div> <div>

View File

@ -52,7 +52,7 @@ function CustomTreeItem(props: CustomTreeItemProps) {
routes.installations + routes.installations +
routes.tree + routes.tree +
routes.installation + routes.installation +
installation.id + installation.s3BucketId +
'/' + '/' +
routes.live, routes.live,
{ {

View File

@ -28,7 +28,11 @@ function InstallationTree() {
subnode != node && subnode != node &&
subnode.parentId == node.id && ( subnode.parentId == node.id && (
<TreeNode <TreeNode
key={subnode.id.toString() + subnode.type} key={
subnode.type == 'Installation'
? subnode.s3BucketId.toString() + subnode.type
: subnode.id.toString() + subnode.type
}
node={subnode} node={subnode}
parent_id={node.id} parent_id={node.id}
/> />
@ -58,7 +62,11 @@ function InstallationTree() {
{foldersAndInstallations.map((node, index) => { {foldersAndInstallations.map((node, index) => {
return ( return (
<TreeNode <TreeNode
key={node.id.toString() + node.type} key={
node.type == 'Installation'
? node.s3BucketId.toString() + node.type
: node.id.toString() + node.type
}
node={node} node={node}
parent_id={'0'} parent_id={'0'}
/> />
@ -72,11 +80,11 @@ function InstallationTree() {
if (installation.type == 'Installation') { if (installation.type == 'Installation') {
return ( return (
<Route <Route
key={installation.id} key={installation.s3BucketId}
path={routes.installation + installation.id + '*'} path={routes.installation + installation.s3BucketId + '*'}
element={ element={
<Installation <Installation
key={installation.id} key={installation.s3BucketId}
current_installation={installation} current_installation={installation}
type="installation" type="installation"
></Installation> ></Installation>

View File

@ -25,5 +25,6 @@ export interface I_Folder {
information: string; information: string;
parentId: number; parentId: number;
type: string; type: string;
s3BucketId: number;
children?: (I_Installation | I_Folder)[]; children?: (I_Installation | I_Folder)[];
} }

View File

@ -3,7 +3,8 @@ export interface I_S3Credentials {
s3Provider: string; s3Provider: string;
s3Key: string; s3Key: string;
s3Secret: string; s3Secret: string;
s3Bucket?: string; s3Bucket: string;
s3BucketId: number;
} }
export interface ErrorMessage { export interface ErrorMessage {