[WIP] add color and style to liveview, add new connections, refactor components, nearly finished

This commit is contained in:
Sina Blattmann 2023-06-19 17:23:09 +02:00
parent 4b9f3e6a84
commit 9e56dffb27
10 changed files with 286 additions and 165 deletions

View File

@ -53,10 +53,13 @@ const UsersWithInheritedAccess = () => {
<Link <Link
id={"inherited-access-user-link-" + user.id} id={"inherited-access-user-link-" + user.id}
to={ to={
routes.groups + routes.manageAccess + user.parentId routes.installations +
routes.tree +
routes.manageAccess +
user.parentId
} }
> >
{folderName} {` ${folderName}`}
</Link> </Link>
</> </>
} }

View File

@ -4,6 +4,8 @@ import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs from "dayjs"; import dayjs from "dayjs";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import ShortcutButton from "./ShortcutButton"; import ShortcutButton from "./ShortcutButton";
import { createTimes } from "../../../util/graph.util";
import { TimeRange, UnixTime } from "../../../dataCache/time";
interface DateRangePickerProps { interface DateRangePickerProps {
setRange: (value: Date[]) => void; setRange: (value: Date[]) => void;
@ -14,8 +16,15 @@ const DateRangePicker = (props: DateRangePickerProps) => {
const { setRange, range, getCacheSeries } = props; const { setRange, range, getCacheSeries } = props;
const handleChange = (fromDate: Date, toDate: Date) => { const handleChange = (fromDate: Date, toDate: Date) => {
setRange([fromDate, toDate]); const timeRange = createTimes(
getCacheSeries(fromDate.getMilliseconds(), toDate.getMilliseconds()); TimeRange.fromTimes(
UnixTime.fromDate(fromDate),
UnixTime.fromDate(toDate)
),
100
);
setRange([timeRange[0].toDate(), timeRange[timeRange.length - 1].toDate()]);
getCacheSeries(timeRange[0].ticks, timeRange[timeRange.length - 1].ticks);
}; };
return ( return (

View File

@ -6,7 +6,6 @@ const Log = () => {
return ( return (
<> <>
<TopologyView /> <TopologyView />
<ScalarGraph /> <ScalarGraph />
</> </>
); );

View File

@ -1,7 +1,6 @@
import Plot from "react-plotly.js"; import Plot from "react-plotly.js";
import { RecordSeries } from "../../../dataCache/data"; import { DataRecord, RecordSeries } from "../../../dataCache/data";
import { import {
Csv,
GraphData, GraphData,
createTimes, createTimes,
flattenToggles, flattenToggles,
@ -481,12 +480,43 @@ export const testData = `/AcDc/SystemControl/Alarms;;
/Config/HoldSocZone;1; /Config/HoldSocZone;1;
/Config/ControllerPConstant;0.5; /Config/ControllerPConstant;0.5;
/SystemState/Message;Panic: Unknown State!; /SystemState/Message;Panic: Unknown State!;
/SystemState/Id;100;`; /SystemState/Id;100;
/LoadOnDc/Power;100;`;
const s3Access = new S3Access(
"saliomameiringen",
"sos-ch-dk-2",
"exo.io",
"EXO464a9ff62fdfa407aa742855",
"f2KtCWN4EHFqtvH2kotdyI0w5SjjdHVPAADdcD3ik8g",
""
);
export const fetchData = (
timestamp: UnixTime
): Promise<FetchResult<DataRecord>> => {
const s3Path = `${timestamp.ticks}.csv`;
return s3Access
.get(s3Path)
.then(async (r) => {
if (r.status === 404) {
return Promise.resolve(FetchResult.notAvailable);
} else if (r.status === 200) {
const text = await r.text();
return parseCsv(text);
} else {
console.error("unexpected status code");
return Promise.resolve(FetchResult.notAvailable);
}
})
.catch((e) => {
return Promise.resolve(FetchResult.tryLater);
});
};
const ScalarGraph = () => { const ScalarGraph = () => {
const timeRange = createTimes( const timeRange = createTimes(
UnixTime.now() /* .fromTicks(1682085650) */ UnixTime.now() /* .fromTicks(1682085650) */
.rangeBefore(TimeSpan.fromDays(7)), .rangeBefore(TimeSpan.fromHours(5)),
NUMBER_OF_NODES NUMBER_OF_NODES
); );
const [timeSeries, setTimeSeries] = useState<RecordSeries>([]); const [timeSeries, setTimeSeries] = useState<RecordSeries>([]);
@ -502,15 +532,6 @@ const ScalarGraph = () => {
const times$ = useMemo(() => new BehaviorSubject(timeRange), []); const times$ = useMemo(() => new BehaviorSubject(timeRange), []);
const s3Access = new S3Access(
"saliomameiringen",
"sos-ch-dk-2",
"exo.io",
"EXO464a9ff62fdfa407aa742855",
"f2KtCWN4EHFqtvH2kotdyI0w5SjjdHVPAADdcD3ik8g",
""
);
useEffect(() => { useEffect(() => {
const subscription = cache.gotData const subscription = cache.gotData
.pipe( .pipe(
@ -532,26 +553,6 @@ const ScalarGraph = () => {
return () => subscription.unsubscribe(); return () => subscription.unsubscribe();
}, [toggles]); }, [toggles]);
const fetchData = (timestamp: UnixTime): Promise<FetchResult<Csv>> => {
const s3Path = `${timestamp.ticks}.csv`;
return s3Access
.get(s3Path)
.then(async (r) => {
if (r.status === 404) {
return Promise.resolve(FetchResult.notAvailable);
} else if (r.status === 200) {
const text = await r.text();
return parseCsv(text);
} else {
console.error("unexpected status code");
return Promise.resolve(FetchResult.notAvailable);
}
})
.catch((e) => {
return Promise.resolve(FetchResult.tryLater);
});
};
const cache = useMemo( const cache = useMemo(
() => new DataCache(fetchData, TimeSpan.fromSeconds(2)), () => new DataCache(fetchData, TimeSpan.fromSeconds(2)),
[] []
@ -570,9 +571,7 @@ const ScalarGraph = () => {
marker: { color: stringToColor(key) }, marker: { color: stringToColor(key) },
}; };
} }
transformedObject[key].x.push( transformedObject[key].x.push(new Date(item.time.ticks * 1000));
new Date(item.time.ticks * 1000).toISOString()
);
transformedObject[key].y.push(item.value?.[key].value); transformedObject[key].y.push(item.value?.[key].value);
}); });
} }
@ -600,12 +599,11 @@ const ScalarGraph = () => {
const getCacheSeries = (xaxisRange0: number, xaxisRange1: number) => { const getCacheSeries = (xaxisRange0: number, xaxisRange1: number) => {
const times = createTimes( const times = createTimes(
TimeRange.fromTimes( TimeRange.fromTimes(
UnixTime.fromDate(new Date(xaxisRange0)), UnixTime.fromTicks(xaxisRange0),
UnixTime.fromDate(new Date(xaxisRange1)) UnixTime.fromTicks(xaxisRange1)
), ),
NUMBER_OF_NODES NUMBER_OF_NODES
); );
console.log("getcacheseries");
cache.getSeries(times); cache.getSeries(times);
times$.next(times); times$.next(times);
}; };
@ -615,7 +613,6 @@ const ScalarGraph = () => {
const visibleGraphs = Object.keys(data).filter((path) => { const visibleGraphs = Object.keys(data).filter((path) => {
return checkedToggles ? checkedToggles[path] : false; return checkedToggles ? checkedToggles[path] : false;
}); });
if (data[visibleGraphs[index]]) { if (data[visibleGraphs[index]]) {
const isScalar = isNumeric(data[visibleGraphs[index]].y[0]); const isScalar = isNumeric(data[visibleGraphs[index]].y[0]);
const graphData = isScalar const graphData = isScalar
@ -685,7 +682,9 @@ const ScalarGraph = () => {
checkedToggles && checkedToggles &&
Object.keys(checkedToggles).find((toggle) => checkedToggles[toggle]) Object.keys(checkedToggles).find((toggle) => checkedToggles[toggle])
) { ) {
console.log("timeseries", timeSeries);
const coordinateTimeSeries = transformToGraphData(timeSeries); const coordinateTimeSeries = transformToGraphData(timeSeries);
console.log("timeSeries", timeSeries);
return ( return (
<> <>
<LocalizationProvider dateAdapter={AdapterDayjs}> <LocalizationProvider dateAdapter={AdapterDayjs}>

View File

@ -17,6 +17,7 @@ const ShortcutButton = (props: ShortcutButtonProps) => {
UnixTime.now().rangeBefore(TimeSpan.fromDays(props.dayRange)), UnixTime.now().rangeBefore(TimeSpan.fromDays(props.dayRange)),
100 100
); );
console.log("weekrange", weekRange[0].ticks);
props.setRange([ props.setRange([
weekRange[0].toDate(), weekRange[0].toDate(),
weekRange[weekRange.length - 1].toDate(), weekRange[weekRange.length - 1].toDate(),

View File

@ -8,45 +8,61 @@ export type BoxData = {
export type TopologyBoxProps = { export type TopologyBoxProps = {
title?: string; title?: string;
data?: BoxData[]; data?: BoxData;
}; };
const isInt = (value: number) => { const isInt = (value: number) => {
return value % 1 === 0; return value % 1 === 0;
}; };
export const BOX_SIZE = 150; export const BOX_SIZE = 85;
const TopologyBox = (props: TopologyBoxProps) => { const TopologyBox = (props: TopologyBoxProps) => {
return ( return (
<Box <Box
sx={{ sx={{
border: "1px solid grey",
visibility: props.title ? "visible" : "hidden", visibility: props.title ? "visible" : "hidden",
height: BOX_SIZE + "px", height: BOX_SIZE + "px",
width: BOX_SIZE + "px", width: BOX_SIZE + "px",
borderRadius: "4px", borderRadius: "4px",
color: "white",
}} }}
> >
<Box sx={{ padding: "5px" }}> <p
<p style={{ marginBlockStart: "2px" }}>{props.title}</p> style={{
{props.data && marginBlockStart: "0",
props.data.map((el) => ( marginBlockEnd: "0",
<div> backgroundColor: "#e74c3c",
{el.label} padding: "5px",
{el.values.map((value) => { borderTopLeftRadius: "4px",
console.log("value", value); borderTopRightRadius: "4px",
return ( }}
<p >
style={{ marginBlockStart: "2px", marginBlockEnd: "2px" }} {props.title}
// eslint-disable-next-line formatjs/no-literal-string-in-jsx </p>
>{`${ <div
!isInt(Number(value)) ? Number(value).toPrecision(8) : value style={{
}${el.unit}`}</p> backgroundColor: "#c0392b",
); borderBottomLeftRadius: "4px",
})} borderBottomRightRadius: "4px",
</div> padding: "5px",
))} height: "52px",
</Box> }}
>
{props.data && (
<>
{props.data.values.map((value) => {
return (
<p
style={{ marginBlockStart: "0", marginBlockEnd: "2px" }}
// eslint-disable-next-line formatjs/no-literal-string-in-jsx
>{`${
!isInt(Number(value)) ? Number(value).toPrecision(4) : value
}${props.data?.unit}`}</p>
);
})}
</>
)}
</div>
</Box> </Box>
); );
}; };

View File

@ -9,8 +9,8 @@
.container { .container {
position: relative; position: relative;
width: 130px; width: 150px;
height: 130px; height: 150px;
overflow: hidden; overflow: hidden;
} }

View File

@ -5,12 +5,12 @@ import "./TopologyFlow.scss";
export type TopologyFlowProps = { export type TopologyFlowProps = {
orientation?: "vertical" | "horizontal"; orientation?: "vertical" | "horizontal";
amount?: number; amount?: number;
rightToLeft?: boolean; data?: BoxData;
data?: BoxData[];
hidden?: boolean; hidden?: boolean;
}; };
const TopologyFlow = (props: TopologyFlowProps) => { const TopologyFlow = (props: TopologyFlowProps) => {
const length = Math.abs((props.amount ?? 1) * BOX_SIZE); const length = Math.abs((props.amount ?? 1) * BOX_SIZE);
const values = props.data?.values;
return ( return (
<div <div
style={{ style={{
@ -28,38 +28,36 @@ const TopologyFlow = (props: TopologyFlowProps) => {
backgroundColor: "#f4b3504d", backgroundColor: "#f4b3504d",
visibility: props.hidden || !props.data ? "hidden" : "visible", visibility: props.hidden || !props.data ? "hidden" : "visible",
display: "flex", display: "flex",
alignItems: "center",
position: "relative",
justifyContent: "center",
}} }}
> >
<p
style={{
position: "absolute",
zIndex: 1,
}}
>
{values?.map(
(value) => `${Math.round(value as number)} ${props.data?.unit}`
)}
</p>
<div <div
className="container" className="container"
style={{ style={{
transform: transform:
props.orientation === "vertical" props.orientation === "vertical"
? "rotate(90deg)" ? "rotate(90deg)"
: props.rightToLeft : Number(values?.[0]) < 0
? "rotate(180deg)" ? "rotate(180deg)"
: "", : "",
overflow: "hidden",
display: "flex", display: "flex",
height: BOX_SIZE, alignItems: "center",
width: BOX_SIZE, justifyContent: "center",
}} }}
> >
<p <div className="data-flow">
style={{
position: "absolute",
zIndex: 1,
transform:
props.orientation === "vertical"
? "rotate(-90deg)"
: props.rightToLeft
? "rotate(-180deg)"
: "",
}}
>
{props.data?.map((value) => value.values)}
</p>
<div className="data-flow" style={{ overflow: "hidden" }}>
<div className="dot"></div> <div className="dot"></div>
<div className="dot"></div> <div className="dot"></div>
<div className="dot"></div> <div className="dot"></div>

View File

@ -1,60 +1,119 @@
import { Box } from "@mui/material"; import { Box } from "@mui/material";
import TopologyColumn from "./TopologyColumn"; import TopologyColumn from "./TopologyColumn";
import { UnixTime } from "../../../dataCache/time"; import { TimeSpan, UnixTime } from "../../../dataCache/time";
import { extractTopologyValues, parseCsv } from "../../../util/graph.util"; import {
import { testData } from "./ScalarGraph"; TopologyValues,
extractTopologyValues,
getAmount,
getHighestConnectionValue,
} from "../../../util/graph.util";
import { fetchData } from "./ScalarGraph";
import { useEffect, useState } from "react";
import { FetchResult } from "../../../dataCache/dataCache";
const TopologyView = () => { const TopologyView = () => {
const values = extractTopologyValues({ const [values, setValues] = useState<TopologyValues | null>(null);
time: UnixTime.fromTicks(192384239),
value: parseCsv(testData), useEffect(() => {
}); const interval = setInterval(() => {
console.log("csvValue", parseCsv(testData)); const now = UnixTime.now().earlier(TimeSpan.fromSeconds(15));
fetchData(now.round(2))
.then((res) => {
if (
res !== FetchResult.notAvailable &&
res !== FetchResult.tryLater
) {
setValues(
extractTopologyValues({
time: now,
value: res,
})
);
}
})
.catch((err) => console.log(err));
}, 2000);
return () => clearInterval(interval);
}, []);
if (values) { if (values) {
const highestConnectionValue = getHighestConnectionValue(values);
return ( return (
<Box <Box
sx={{ sx={{
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
overflow: "auto",
padding: 2, padding: 2,
fontFamily: `"Ubuntu", sans-serif`, fontFamily: `"Ubuntu", sans-serif`,
fontSize: "12px",
}} }}
> >
<div> <div>
<TopologyColumn <TopologyColumn
centerBox={{ centerBox={{
title: "Grid", title: "Grid",
data: values.acInBus,
}} }}
centerConnection={{ centerConnection={{
amount: 0.6, amount: getAmount(
data: values.gridToAcIn, highestConnectionValue,
rightToLeft: true, values.gridToAcInConnection.values
),
data: values.gridToAcInConnection,
}} }}
/> />
</div> </div>
<div> <div>
<TopologyColumn <TopologyColumn
centerBox={{ centerBox={{
title: "AcInBus", title: "GridBus",
data: values.acInBus, data: values.islandBus,
}} }}
centerConnection={{ centerConnection={{
amount: 0.3, amount: 0.3,
data: values.gridToAcIn, data: values.gridToAcInConnection,
}}
topConnection={{
amount: getAmount(
highestConnectionValue,
values.gridBusToPvOnGridbusConnection.values
),
data: values.gridBusToPvOnGridbusConnection,
}}
topBox={{
title: "PvOnGridBus",
}}
bottomConnection={{
amount: getAmount(
highestConnectionValue,
values.gridBusToLoadOnGridBusConnection.values
),
data: values.gridBusToLoadOnGridBusConnection,
}}
bottomBox={{
title: "LoadOnGridBus",
}} }}
/> />
</div> </div>
<div> <div>
<TopologyColumn <TopologyColumn
centerBox={{ centerBox={{
title: "AcOutBus", title: "IslandBus",
data: values.acOutBus, data: values.islandBus,
}} }}
centerConnection={{ centerConnection={{
amount: 0.5, amount: 0.5,
data: values.gridToAcIn, data: values.gridToAcInConnection,
}}
bottomBox={{
title: "LoadOnIslandBus",
}}
bottomConnection={{
amount: getAmount(
highestConnectionValue,
values.islandBusToLoadOnIslandBusConnection.values
),
data: values.islandBusToLoadOnIslandBusConnection,
}} }}
/> />
</div> </div>
@ -62,32 +121,40 @@ const TopologyView = () => {
<TopologyColumn <TopologyColumn
centerBox={{ centerBox={{
title: "Inverter", title: "Inverter",
data: values.acOutBus, data: values.islandBus,
}} }}
centerConnection={{ centerConnection={{
amount: 0.3, amount: 0.3,
data: values.gridToAcIn, data: values.gridToAcInConnection,
}} }}
/> />
</div> </div>
<div> <div>
<TopologyColumn <TopologyColumn
topBox={{ topBox={{
title: "MPPT", title: "PvOnDc",
data: values.acOutBus,
}} }}
topConnection={{ topConnection={{
amount: 0.6, amount: 0.6,
data: values.gridToAcIn, data: values.gridToAcInConnection,
}} }}
centerBox={{ centerBox={{
title: "DcBus", title: "DcBus",
data: values.acOutBus, data: values.dcBus,
}} }}
centerConnection={{ centerConnection={{
amount: getAmount(
highestConnectionValue,
values.dcBusToDcDcConnection.values
),
data: values.dcBusToDcDcConnection,
}}
bottomConnection={{
amount: 0.2, amount: 0.2,
data: values.gridToAcIn, data: values.dcBusToLoadOnDcConnection,
rightToLeft: true, }}
bottomBox={{
title: "LoadOnDc",
}} }}
/> />
</div> </div>
@ -95,11 +162,14 @@ const TopologyView = () => {
<TopologyColumn <TopologyColumn
centerBox={{ centerBox={{
title: "DcDc", title: "DcDc",
data: values.acOutBus, data: values.islandBus,
}} }}
centerConnection={{ centerConnection={{
amount: 0.8, amount: getAmount(
data: values.gridToAcIn, highestConnectionValue,
values.dcDCToBatteryConnection.values
),
data: values.dcDCToBatteryConnection,
}} }}
/> />
</div> </div>
@ -107,7 +177,7 @@ const TopologyView = () => {
<TopologyColumn <TopologyColumn
centerBox={{ centerBox={{
title: "Battery", title: "Battery",
data: values.acOutBus, data: values.battery,
}} }}
/> />
</div> </div>

View File

@ -35,29 +35,60 @@ export interface GraphData {
[path: string]: GraphCoordinates; [path: string]: GraphCoordinates;
} }
// connections must have the word Connection in the prop name, so the topology works correctly
export type TopologyValues = { export type TopologyValues = {
gridToAcIn: BoxData[]; gridToAcInConnection: BoxData;
acInBus: BoxData[]; gridBus: BoxData;
acOutBus: BoxData[]; islandBus: BoxData;
dcBus: BoxData[]; dcBus: BoxData;
mpptToDcBus: BoxData[]; dcBusToDcDcConnection: BoxData;
dcBusToDcDc: BoxData[]; dcDCToBatteryConnection: BoxData;
dcDCToBattery: BoxData[]; battery: BoxData;
battery: BoxData[]; dcBusToLoadOnDcConnection: BoxData;
islandBusToLoadOnIslandBusConnection: BoxData;
gridBusToPvOnGridbusConnection: BoxData;
gridBusToLoadOnGridBusConnection: BoxData;
}; };
type TopologyPaths = { [key in keyof TopologyValues]: string[] };
export const topologyPaths: TopologyPaths = {
gridToAcInConnection: [
"/GridMeter/Ac/L1/Power/Apparent",
"/GridMeter/Ac/L2/Power/Apparent",
"/GridMeter/Ac/L3/Power/Apparent",
],
gridBus: ["/GridMeter/Ac/L2/Voltage"],
islandBus: [
"/AcDc/Ac/L1/Voltage",
"/AcDc/Ac/L2/Voltage",
"/AcDc/Ac/L3/Voltage",
],
dcBus: ["/AcDc/Dc/Voltage"],
dcBusToDcDcConnection: ["/DcDc/Dc/Link/Power"],
dcDCToBatteryConnection: ["/DcDc/Dc/Battery/Power"],
battery: ["/Battery/Soc", "/Battery/Dc/Voltage"],
dcBusToLoadOnDcConnection: ["/LoadOnDc/Power"],
islandBusToLoadOnIslandBusConnection: ["/LoadOnAcIsland/Ac/Power/Apparent"],
gridBusToPvOnGridbusConnection: ["/PvOnAcGrid/Power/Apparent"],
gridBusToLoadOnGridBusConnection: ["/LoadOnAcGrid/Power/Apparent"],
};
const getBoxColor = () => {};
export const extractTopologyValues = ( export const extractTopologyValues = (
timeSeriesData: DataPoint timeSeriesData: DataPoint
): TopologyValues | null => { ): TopologyValues | null => {
const timeSeriesValue = timeSeriesData.value; const timeSeriesValue = timeSeriesData.value;
let topologyValues: (string | number)[];
if (isDefined(timeSeriesValue)) { if (isDefined(timeSeriesValue)) {
return Object.keys(topologyPaths).reduce((acc, topologyKey) => { return Object.keys(topologyPaths).reduce((acc, topologyKey) => {
const values = topologyPaths[topologyKey].map( let topologyValues: (string | number)[];
const values = topologyPaths[topologyKey as keyof TopologyValues].map(
(topologyPath) => timeSeriesValue[topologyPath] (topologyPath) => timeSeriesValue[topologyPath]
); );
switch (topologyKey) { console.log("AAA", topologyKey);
case "gridToAcIn": switch (topologyKey as keyof TopologyValues) {
case "gridToAcInConnection":
topologyValues = [ topologyValues = [
values.reduce((acc, curr) => Number(acc) + Number(curr.value), 0), values.reduce((acc, curr) => Number(acc) + Number(curr.value), 0),
]; ];
@ -65,50 +96,45 @@ export const extractTopologyValues = (
default: default:
topologyValues = values.map(({ value }) => value); topologyValues = values.map(({ value }) => value);
} }
console.log("topologyValues", topologyValues);
return { return {
...acc, ...acc,
[topologyKey]: [ [topologyKey]: {
{ values: topologyValues,
values: topologyValues, label: topologyPaths[topologyKey as keyof TopologyValues][0]
label: topologyPaths[topologyKey][0].split("/").pop(), .split("/")
unit: values[0].unit, .pop(),
} as BoxData, unit: values[0].unit,
], } as BoxData,
}; };
}, {} as TopologyValues); }, {} as TopologyValues);
} }
return null; return null;
}; };
export const topologyPaths: { [key: string]: string[] } = { export const getHighestConnectionValue = (values: TopologyValues) =>
gridToAcIn: [ Object.keys(values)
"/GridMeter/Ac/L1/Power/Apparent", .filter((value) => value.includes("Connection"))
"/GridMeter/Ac/L2/Power/Apparent", .reduce((acc, curr) => {
"/GridMeter/Ac/L3/Power/Apparent", const value = values[curr as keyof TopologyValues].values[0] as number;
], console.log("reduce", value, acc);
acInBus: ["/GridMeter/Ac/L2/Voltage"], return value > acc ? value : acc;
acOutBus: [ }, 0);
"/AcDc/Ac/L1/Voltage",
"/AcDc/Ac/L2/Voltage", export const getAmount = (
"/AcDc/Ac/L3/Voltage", highestConnectionValue: number,
], values: (string | number)[]
dcBus: ["/AcDc/Dc/Voltage"], ) => {
mpptToDcBus: ["/Mppt/Dc/Power"], console.log("amount", values[0] as number, highestConnectionValue);
dcBusToDcDc: ["/DcDc/Dc/Link/Power"], return (values[0] as number) / highestConnectionValue;
dcDCToBattery: ["/DcDc/Dc/Battery/Power"],
battery: ["/Battery/Soc", "/Battery/Dc/Voltage"],
}; };
export interface CsvEntry { export interface CsvEntry {
value: string | number; value: string | number;
unit: string; unit: string;
} }
export interface Csv {
[key: string]: CsvEntry;
}
export const parseCsv = (text: string): Csv => { export const parseCsv = (text: string): DataRecord => {
console.log("split", text.split(/\r?\n/));
const y = text const y = text
.split(/\r?\n/) .split(/\r?\n/)
.filter((split) => split.length > 0) .filter((split) => split.length > 0)
@ -118,12 +144,12 @@ export const parseCsv = (text: string): Csv => {
const x = y const x = y
.map((fields) => { .map((fields) => {
if (typeof fields[1] === "string") { if (isNaN(Number(fields[1]))) {
return { [fields[0]]: { value: fields[1], unit: fields[2] } }; return { [fields[0]]: { value: fields[1], unit: fields[2] } };
} }
return { [fields[0]]: { value: parseFloat(fields[1]), unit: fields[2] } }; return { [fields[0]]: { value: parseFloat(fields[1]), unit: fields[2] } };
}) })
.reduce((acc, current) => ({ ...acc, ...current }), {} as Csv); .reduce((acc, current) => ({ ...acc, ...current }), {} as DataRecord);
return x; return x;
}; };