diff --git a/typescript/frontend-marios2/package-lock.json b/typescript/frontend-marios2/package-lock.json index fc39e5adb..fdc2eb98a 100644 --- a/typescript/frontend-marios2/package-lock.json +++ b/typescript/frontend-marios2/package-lock.json @@ -22,6 +22,7 @@ "clsx": "1.1.1", "cytoscape": "^3.26.0", "date-fns": "^2.28.0", + "dayjs": "^1.11.10", "history": "5.3.0", "linq-to-typescript": "^11.0.0", "nprogress": "0.2.0", @@ -7103,6 +7104,11 @@ "url": "https://opencollective.com/date-fns" } }, + "node_modules/dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "node_modules/debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", @@ -23246,6 +23252,11 @@ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.28.0.tgz", "integrity": "sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==" }, + "dayjs": { + "version": "1.11.10", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.10.tgz", + "integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ==" + }, "debug": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", diff --git a/typescript/frontend-marios2/package.json b/typescript/frontend-marios2/package.json index 23ef0f8a9..a8f8d0977 100644 --- a/typescript/frontend-marios2/package.json +++ b/typescript/frontend-marios2/package.json @@ -18,6 +18,7 @@ "clsx": "1.1.1", "cytoscape": "^3.26.0", "date-fns": "^2.28.0", + "dayjs": "^1.11.10", "history": "5.3.0", "linq-to-typescript": "^11.0.0", "nprogress": "0.2.0", diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx index c8abf209b..2e5369d12 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/fetchData.tsx @@ -5,6 +5,38 @@ import { DataRecord } from 'src/dataCache/data'; import { S3Access } from 'src/dataCache/S3/S3Access'; import { parseCsv } from '../Log/graph.util'; +export const fetchDailyData = ( + date: string, + s3Credentials?: I_S3Credentials +): Promise> => { + const s3Path = `${date}.csv`; + if (s3Credentials && s3Credentials.s3Bucket) { + const s3Access = new S3Access( + s3Credentials.s3Bucket, + s3Credentials.s3Region, + s3Credentials.s3Provider, + s3Credentials.s3Key, + s3Credentials.s3Secret + ); + 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(); + //console.log(parseCsv(text)); + return parseCsv(text); + } else { + return Promise.resolve(FetchResult.notAvailable); + } + }) + .catch((e) => { + return Promise.resolve(FetchResult.tryLater); + }); + } +}; + export const fetchData = ( timestamp: UnixTime, s3Credentials?: I_S3Credentials diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/chartOptions.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/chartOptions.tsx index 42e98559c..2a738f01e 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Overview/chartOptions.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Overview/chartOptions.tsx @@ -159,20 +159,56 @@ export const getChartOptions = ( enabled: true } }, + yaxis: { - axisBorder: { - show: false - }, - axisTicks: { - show: false + min: + chartInfo.min >= 0 + ? 0 + : chartInfo.max <= 0 + ? Math.ceil(chartInfo.min / findPower(chartInfo.min).value) * + findPower(chartInfo.min).value + : undefined, + max: + chartInfo.min >= 0 + ? Math.ceil(chartInfo.max / findPower(chartInfo.max).value) * + findPower(chartInfo.max).value + : chartInfo.max <= 0 + ? 0 + : undefined, + title: { + text: chartInfo.unit, + style: { + fontSize: '12px' + }, + offsetY: -160, + offsetX: 25, + rotate: 0 }, labels: { - show: false, - formatter: function (val) { - return val + '%'; + formatter: function (value: number) { + return formatPowerForGraph( + value, + Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) + ).value.toString(); } } } + + // + // yaxis: { + // axisBorder: { + // show: false + // }, + // axisTicks: { + // show: false + // }, + // labels: { + // show: false, + // formatter: function (val) { + // return val + '%'; + // } + // } + // } } : { chart: { @@ -189,8 +225,15 @@ export const getChartOptions = ( }, dataLabels: { enabled: true, - formatter: function (val) { - return val + '%'; + formatter: function (val, opts) { + return ( + formatPowerForGraph( + val, + Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) + ).value.toFixed(2) + + ' ' + + chartInfo.unit + ); }, offsetY: -20, style: { @@ -213,19 +256,66 @@ export const getChartOptions = ( } }, yaxis: { - axisBorder: { - show: false - }, - axisTicks: { - show: false + min: + chartInfo.min >= 0 + ? 0 + : chartInfo.max <= 0 + ? Math.ceil(chartInfo.min / findPower(chartInfo.min).value) * + findPower(chartInfo.min).value + : undefined, + max: + chartInfo.min >= 0 + ? Math.ceil(chartInfo.max / findPower(chartInfo.max).value) * + findPower(chartInfo.max).value + : chartInfo.max <= 0 + ? 0 + : undefined, + title: { + text: chartInfo.unit, + style: { + fontSize: '12px' + }, + offsetY: -160, + offsetX: 25, + rotate: 0 }, labels: { - show: false, - formatter: function (val) { - return val + '%'; + formatter: function (value: number) { + return formatPowerForGraph( + value, + Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) + ).value.toString(); + } + } + }, + tooltip: { + y: { + formatter: function (val, opts) { + return ( + formatPowerForGraph( + val, + Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min)) + ).value.toFixed(2) + + ' ' + + chartInfo.unit + ); } } } + // yaxis: { + // axisBorder: { + // show: false + // }, + // axisTicks: { + // show: false + // }, + // labels: { + // show: false, + // formatter: function (val) { + // return val + '%'; + // } + // } + // } }; return chartOptions; diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx index 91eacf4ff..f291d8b96 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx @@ -1,302 +1,246 @@ import { Box, Card, Container, Grid, Typography } from '@mui/material'; import ReactApexChart from 'react-apexcharts'; -import React, { useEffect, useMemo, useState } from 'react'; -import DataCache from 'src/dataCache/dataCache'; -import { TimeRange, TimeSpan, UnixTime } from 'src/dataCache/time'; -import { - BehaviorSubject, - combineLatest, - startWith, - tap, - throttleTime -} from 'rxjs'; -import { RecordSeries } from 'src/dataCache/data'; -import { createTimes } from '../Log/graph.util'; +import React, { useEffect, useState } from 'react'; import { I_S3Credentials } from 'src/interfaces/S3Types'; import { getChartOptions } from './chartOptions'; -import { chartDataInterface, overviewInterface } from 'src/interfaces/Chart'; -import { fetchData } from 'src/content/dashboards/Installations/fetchData'; +import { + chartAggregatedDataInterface, + chartDataInterface, + overviewInterface, + transformInputToAggregatedData, + transformInputToDailyData +} from 'src/interfaces/Chart'; import Button from '@mui/material/Button'; import { FormattedMessage } from 'react-intl'; - -const prefixes = ['', 'k', 'M', 'G', 'T']; -const MAX_NUMBER = 9999999; +import CircularProgress from '@mui/material/CircularProgress'; +import { TimeSpan, UnixTime } from '../../../dataCache/time'; interface OverviewProps { s3Credentials: I_S3Credentials; } function Overview(props: OverviewProps) { - const numOfPointsToFetch = 100; - const [timeRange, setTimeRange] = useState( - createTimes( - UnixTime.now().rangeBefore(TimeSpan.fromDays(1)), - numOfPointsToFetch - ) - ); + // const numOfPointsToFetch = 100; + // const [timeRange, setTimeRange] = useState( + // createTimes( + // UnixTime.now().rangeBefore(TimeSpan.fromDays(1)), + // numOfPointsToFetch + // ) + // ); + // + // const cache = useMemo(() => { + // return new DataCache( + // fetchData, + // TimeSpan.fromSeconds(2), + // props.s3Credentials + // ); + // }, []); - const [chartData, setChartData] = useState({ - soc: [], - temperature: [], - dcPower: [], - gridPower: [], - pvProduction: [], - dcBusVoltage: [] - }); - - const [chartOverview, setChartOverview] = useState({ - soc: { magnitude: 0, unit: '', min: 0, max: 0 }, - temperature: { magnitude: 0, unit: '', min: 0, max: 0 }, - dcPower: { magnitude: 0, unit: '', min: 0, max: 0 }, - gridPower: { magnitude: 0, unit: '', min: 0, max: 0 }, - pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 }, - dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 } - }); - - const pathsToSearch = [ - '/Battery/Soc', - '/Battery/Temperature', - '/Battery/Dc/Power', - '/GridMeter/Ac/Power/Active', - '/PvOnDc/Dc/Power', - '/DcDc/Dc/Link/Voltage' - ]; - - const cache = useMemo(() => { - return new DataCache( - fetchData, - TimeSpan.fromSeconds(2), - props.s3Credentials - ); - }, []); - - const times$ = useMemo(() => new BehaviorSubject(timeRange), []); + //const times$ = useMemo(() => new BehaviorSubject(timeRange), []); const [dailyData, setDailyData] = useState(true); const [weeklyData, setWeeklyData] = useState(false); const [monthlyData, setMonthlyData] = useState(false); + const [loading, setLoading] = useState(true); + const [chartState, setChartState] = useState(0); - const transformToGraphData = ( - input: RecordSeries - ): { - chartData: chartDataInterface; - chartOverview: overviewInterface; - } => { - const data = {}; - const overviewData = {}; + // const [chartData, setChartData] = useState({ + // soc: [], + // temperature: [], + // dcPower: [], + // gridPower: [], + // pvProduction: [], + // dcBusVoltage: [] + // }); + // + // const [chartOverview, setChartOverview] = useState({ + // soc: { magnitude: 0, unit: '', min: 0, max: 0 }, + // temperature: { magnitude: 0, unit: '', min: 0, max: 0 }, + // dcPower: { magnitude: 0, unit: '', min: 0, max: 0 }, + // gridPower: { magnitude: 0, unit: '', min: 0, max: 0 }, + // pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 }, + // dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 } + // }); - const chartData: chartDataInterface = { - soc: [], - temperature: [], - dcPower: [], - gridPower: [], - pvProduction: [], - dcBusVoltage: [] - }; + const [dailyDataArray, setDailyDataArray] = useState< + { + chartData: chartDataInterface; + chartOverview: overviewInterface; + }[] + >([]); - const chartOverview: overviewInterface = { + const [chartAggregatedData, setChartAggregatedData] = + useState({ + soc: [{ data: [] }], + pvProduction: [{ data: [] }], + dcPower: [{ data: [] }] + }); + + const [chartAggregatedOverview, setChartAggregatedOverview] = + useState({ soc: { magnitude: 0, unit: '', min: 0, max: 0 }, temperature: { magnitude: 0, unit: '', min: 0, max: 0 }, dcPower: { magnitude: 0, unit: '', min: 0, max: 0 }, gridPower: { magnitude: 0, unit: '', min: 0, max: 0 }, pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 }, dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 } - }; - - pathsToSearch.forEach((path) => { - data[path] = []; - overviewData[path] = { - magnitude: 0, - unit: '', - min: MAX_NUMBER, - max: -MAX_NUMBER - }; }); - //console.log(input); - input.forEach((item) => { - const csvContent = item.value; - pathsToSearch.forEach((path) => { - if (csvContent) { - const timestamp = item.time.ticks * 1000; - - const adjustedTimestamp = new Date(timestamp); - adjustedTimestamp.setHours(adjustedTimestamp.getHours() + 1); - - if (csvContent[path]) { - const value = csvContent[path]; - - if (value.value < overviewData[path].min) { - overviewData[path].min = value.value; - } - - if (value.value > overviewData[path].max) { - overviewData[path].max = value.value; - } - - data[path].push([adjustedTimestamp, value.value]); - } else { - //data[path].push([adjustedTimestamp, null]); - } - } else { - // data[path].push([ - // addHours(new Date(item.time.ticks * 1000), 2), - // null - // ]); - } - }); - }); - - pathsToSearch.forEach((path) => { - let value = Math.max( - Math.abs(overviewData[path].max), - Math.abs(overviewData[path].min) - ); - let magnitude = 0; - - if (value < 0) { - value = -value; - } - while (value >= 1000) { - value /= 1000; - magnitude++; - } - overviewData[path].magnitude = prefixes[magnitude]; - }); - - let path = '/Battery/Soc'; - chartData.soc = [{ name: 'State of Charge', data: data[path] }]; - - chartOverview.soc = { - unit: '(%)', - magnitude: overviewData[path].magnitude, - min: 0, - max: 100 - }; - - path = '/Battery/Temperature'; - chartData.temperature = [ - { name: 'Battery Temperature:', data: data[path] } - ]; - - chartOverview.temperature = { - unit: '(°C)', - magnitude: overviewData[path].magnitude, - min: overviewData[path].min, - max: overviewData[path].max - }; - - path = '/Battery/Dc/Power'; - chartData.dcPower = [{ name: 'Battery Power', data: data[path] }]; - - chartOverview.dcPower = { - magnitude: overviewData[path].magnitude, - unit: '(' + overviewData[path].magnitude + 'W' + ')', - min: overviewData[path].min, - max: overviewData[path].max - }; - - path = '/GridMeter/Ac/Power/Active'; - chartData.gridPower = [{ name: 'Grid Power', data: data[path] }]; - - chartOverview.gridPower = { - magnitude: overviewData[path].magnitude, - unit: '(' + overviewData[path].magnitude + 'W' + ')', - min: overviewData[path].min, - max: overviewData[path].max - }; - - path = '/PvOnDc/Dc/Power'; - chartData.pvProduction = [{ name: 'Pv Production', data: data[path] }]; - - chartOverview.pvProduction = { - magnitude: overviewData[path].magnitude, - unit: '(' + overviewData[path].magnitude + 'W' + ')', - min: overviewData[path].min, - max: overviewData[path].max - }; - - path = '/DcDc/Dc/Link/Voltage'; - chartData.dcBusVoltage = [{ name: 'DC Bus Voltage', data: data[path] }]; - - chartOverview.dcBusVoltage = { - magnitude: overviewData[path].magnitude, - unit: '(' + overviewData[path].magnitude + 'V' + ')', - min: overviewData[path].min, - max: overviewData[path].max - }; - - return { - chartData: chartData, - chartOverview: chartOverview - }; - }; - useEffect(() => { - const left = cache.gotData.pipe( - startWith(UnixTime.fromTicks(0)), - throttleTime(200, undefined, { leading: true, trailing: true }) + const resultPromise: Promise<{ + chartData: chartDataInterface; + chartOverview: overviewInterface; + }> = transformInputToDailyData( + props.s3Credentials, + UnixTime.now().rangeBefore(TimeSpan.fromDays(1)).start, + UnixTime.now() ); - const right = times$.pipe( - tap((times) => { - //console.log(times); + resultPromise + .then((result) => { + // setChartData(result.chartData); + // setChartOverview(result.chartOverview); + + setDailyDataArray((prevData) => + prevData.concat({ + chartData: result.chartData, + chartOverview: result.chartOverview + }) + ); + setLoading(false); }) - ); + .catch((error) => { + console.error('Error:', error); + }); - const combined = combineLatest([left, right]); - - const subscription = combined.subscribe(([_, times]) => { - const timeSeries = cache.getSeries(times); - const result: { - chartData: chartDataInterface; - chartOverview: overviewInterface; - } = transformToGraphData(timeSeries); - - setChartData(result.chartData); - setChartOverview(result.chartOverview); - }); - return () => subscription.unsubscribe(); + // const left = cache.gotData.pipe( + // startWith(UnixTime.fromTicks(0)), + // throttleTime(200, undefined, { leading: true, trailing: true }) + // ); + // + // const right = times$.pipe( + // tap((times) => { + // //console.log(times); + // }) + // ); + // + // const combined = combineLatest([left, right]); + // + // const subscription = combined.subscribe(([_, times]) => { + // const timeSeries = cache.getSeries(times); + // const result: { + // chartData: chartDataInterface; + // chartOverview: overviewInterface; + // } = transformInputToDailyData(timeSeries); + // + // setChartData(result.chartData); + // setChartOverview(result.chartOverview); + // }); + // return () => subscription.unsubscribe(); }, []); const handleBeforeZoom = (chartContext, { xaxis }) => { const startX = parseInt(xaxis.min) / 1000; const endX = parseInt(xaxis.max) / 1000; - const times = createTimes( - TimeRange.fromTimes(UnixTime.fromTicks(startX), UnixTime.fromTicks(endX)), - numOfPointsToFetch + //console.log(UnixTime.fromTicks(startX)); + //console.log(endX); + + setLoading(true); + const resultPromise: Promise<{ + chartData: chartDataInterface; + chartOverview: overviewInterface; + }> = transformInputToDailyData( + props.s3Credentials, + UnixTime.fromTicks(startX), + UnixTime.fromTicks(endX) ); - cache.getSeries(times); - times$.next(times); - }; + resultPromise + .then((result) => { + //setChartData(result.chartData); + //setChartOverview(result.chartOverview); - const handleDoubleClick = () => { - const times = createTimes( - UnixTime.now().rangeBefore(TimeSpan.fromDays(1)), - numOfPointsToFetch - ); - cache.getSeries(times); - times$.next(times); + setDailyDataArray((prevData) => + prevData.concat({ + chartData: result.chartData, + chartOverview: result.chartOverview + }) + ); + + setLoading(false); + setChartState(dailyDataArray.length); + }) + .catch((error) => { + console.error('Error:', error); + }); + + // const times = createTimes( + // TimeRange.fromTimes(UnixTime.fromTicks(startX), UnixTime.fromTicks(endX)), + // numOfPointsToFetch + // ); + // + // cache.getSeries(times); + // times$.next(times); }; const handle24HourData = () => { setDailyData(true); setWeeklyData(false); setMonthlyData(false); - const times = createTimes( - UnixTime.now().rangeBefore(TimeSpan.fromDays(1)), - numOfPointsToFetch - ); - cache.getSeries(times); - times$.next(times); + // const times = createTimes( + // UnixTime.now().rangeBefore(TimeSpan.fromDays(1)), + // numOfPointsToFetch + // ); + // cache.getSeries(times); + // times$.next(times); + // setLoading(true); + // const resultPromise: Promise<{ + // chartData: chartDataInterface; + // chartOverview: overviewInterface; + // }> = transformInputToDailyData( + // props.s3Credentials, + // UnixTime.now().rangeBefore(TimeSpan.fromDays(1)).start, + // UnixTime.now() + // ); + // + // resultPromise + // .then((result) => { + // //setChartData(result.chartData); + // //setChartOverview(result.chartOverview); + // setDailyDataArray((prevData) => + // prevData.concat({ + // chartData: result.chartData, + // chartOverview: result.chartOverview + // }) + // ); + // setLoading(false); + // setChartState(dailyDataArray.length); + // }) + // .catch((error) => { + // console.error('Error:', error); + // }); + setChartState(0); }; const handleWeekData = () => { setDailyData(false); setWeeklyData(true); setMonthlyData(false); - //fetchData(12312,props.s3Credentials); + + const resultPromise: Promise<{ + chartAggregatedData: chartAggregatedDataInterface; + chartOverview: overviewInterface; + }> = transformInputToAggregatedData(props.s3Credentials); + + resultPromise + .then((result) => { + setChartAggregatedData(result.chartAggregatedData); + setChartAggregatedOverview(result.chartOverview); + }) + .catch((error) => { + console.error('Error:', error); + }); + // const times = createTimes( // UnixTime.now().rangeBefore(TimeSpan.fromWeeks(1)), // numOfPointsToFetch @@ -305,6 +249,18 @@ function Overview(props: OverviewProps) { // times$.next(times); }; + const handleGoBack = () => { + if (chartState > 0) { + setChartState(chartState - 1); + } + }; + + const handleGoForward = () => { + if (chartState + 1 < dailyDataArray.length) { + setChartState(chartState + 1); + } + }; + const handleMonthData = () => { setDailyData(false); setWeeklyData(false); @@ -317,18 +273,32 @@ function Overview(props: OverviewProps) { // times$.next(times); }; - const series = [ - { - name: 'Inflation', - data: [2.3, 3.1, 4.0, 10.1, 4.0, 3.6, 3.2, 2.3, 1.4, 0.8, 0.5, 0.2] - } - ]; - const renderGraphs = () => { + if (loading) { + // Display a loading indicator while waiting for the data + return ( + + + + Fetching data... + + + ); + } + return ( - + + + + + + @@ -429,9 +440,12 @@ function Overview(props: OverviewProps) { {weeklyData && ( @@ -440,9 +454,12 @@ function Overview(props: OverviewProps) { {monthlyData && ( @@ -481,21 +498,22 @@ function Overview(props: OverviewProps) { }} > -
- -
+ } + }} + series={dailyDataArray[chartState].chartData.temperature} + type="area" + height={350} + />
@@ -539,21 +557,53 @@ function Overview(props: OverviewProps) { }} > -
+ + {dailyData && ( -
+ )} + + {weeklyData && ( + + )} + + {monthlyData && ( + + )}
@@ -588,21 +638,22 @@ function Overview(props: OverviewProps) { }} > -
- -
+ } + }} + series={dailyDataArray[chartState].chartData.gridPower} + type="area" + height={350} + />
@@ -645,21 +696,53 @@ function Overview(props: OverviewProps) { }} > -
+ + {dailyData && ( -
+ )} + + {weeklyData && ( + + )} + + {monthlyData && ( + + )} @@ -694,21 +777,22 @@ function Overview(props: OverviewProps) { }} > -
- -
+ } + }} + series={dailyDataArray[chartState].chartData.dcBusVoltage} + type="area" + height={350} + />
diff --git a/typescript/frontend-marios2/src/dataCache/data.ts b/typescript/frontend-marios2/src/dataCache/data.ts index ffb4b83a1..3bd7306e5 100644 --- a/typescript/frontend-marios2/src/dataCache/data.ts +++ b/typescript/frontend-marios2/src/dataCache/data.ts @@ -4,9 +4,9 @@ import { isDefined } from './utils/maybe'; import { I_CsvEntry } from 'src/content/dashboards/Log/graph.util'; export type DataRecord = Record; - export type DataPoint = Timestamped>; export type RecordSeries = Array; +export type DataRecordSeries = Array; export type PointSeries = Array>>; export type DataSeries = Array>; diff --git a/typescript/frontend-marios2/src/interfaces/Chart.tsx b/typescript/frontend-marios2/src/interfaces/Chart.tsx index fd97b0c70..6f4a1238e 100644 --- a/typescript/frontend-marios2/src/interfaces/Chart.tsx +++ b/typescript/frontend-marios2/src/interfaces/Chart.tsx @@ -1,4 +1,12 @@ import { ApexOptions } from 'apexcharts'; +import dayjs from 'dayjs'; +import { + fetchDailyData, + fetchData +} from '../content/dashboards/Installations/fetchData'; +import { FetchResult } from '../dataCache/dataCache'; +import { I_S3Credentials } from './S3Types'; +import { UnixTime } from '../dataCache/time'; export interface overviewInterface { soc: chartInfoInterface; @@ -16,6 +24,12 @@ export interface chartInfoInterface { max: number; } +export interface chartAggregatedDataInterface { + soc: [{ data: number[] }]; + pvProduction: [{ data: number[] }]; + dcPower: [{ data: number[] }]; +} + export interface chartDataInterface { soc: ApexOptions['series']; temperature: ApexOptions['series']; @@ -24,3 +38,360 @@ export interface chartDataInterface { pvProduction: ApexOptions['series']; dcBusVoltage: ApexOptions['series']; } + +export const transformInputToDailyData = async ( + s3Credentials: I_S3Credentials, + startTimestamp: UnixTime, + endTimestamp: UnixTime +): Promise<{ + chartData: chartDataInterface; + chartOverview: overviewInterface; +}> => { + const data = {}; + const overviewData = {}; + const prefixes = ['', 'k', 'M', 'G', 'T']; + const MAX_NUMBER = 9999999; + const pathsToSearch = [ + '/Battery/Soc', + '/Battery/Temperature', + '/Battery/Dc/Power', + '/GridMeter/Ac/Power/Active', + '/PvOnDc/Dc/Power', + '/DcDc/Dc/Link/Voltage' + ]; + + const chartData: chartDataInterface = { + soc: [], + temperature: [], + dcPower: [], + gridPower: [], + pvProduction: [], + dcBusVoltage: [] + }; + + const chartOverview: overviewInterface = { + soc: { magnitude: 0, unit: '', min: 0, max: 0 }, + temperature: { magnitude: 0, unit: '', min: 0, max: 0 }, + dcPower: { magnitude: 0, unit: '', min: 0, max: 0 }, + gridPower: { magnitude: 0, unit: '', min: 0, max: 0 }, + pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 }, + dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 } + }; + + pathsToSearch.forEach((path) => { + data[path] = []; + overviewData[path] = { + magnitude: 0, + unit: '', + min: MAX_NUMBER, + max: -MAX_NUMBER + }; + }); + + // const currentTimestamp = UnixTime.now(); + // const twentyFourHoursAgoRange = currentTimestamp.rangeBefore( + // TimeSpan.fromDays(1) + // ); + + let startTimestampToNum = Number(startTimestamp); + if (startTimestampToNum % 2 != 0) { + startTimestampToNum += 1; + } + let startUnixTime = UnixTime.fromTicks(startTimestampToNum); + + let diff = endTimestamp.ticks - startUnixTime.ticks; + + //console.log('Current Unix Timestamp:', currentTimestamp); + //console.log('Unix Timestamp 24 hours ago (even):', startTimestamp); + + while (startUnixTime < endTimestamp) { + // console.log('Current day:', currentDay.format('YYYY-MM-DD')); + + let result = await Promise.resolve(fetchData(startUnixTime, s3Credentials)); + if ( + result === FetchResult.notAvailable || + result === FetchResult.tryLater + ) { + // Handle not available or try later case + } else { + //console.log('Received data:', result); + // eslint-disable-next-line @typescript-eslint/no-loop-func + pathsToSearch.forEach((path) => { + const timestamp = startUnixTime.ticks * 1000; + + const adjustedTimestamp = new Date(timestamp); + adjustedTimestamp.setHours(adjustedTimestamp.getHours() + 1); + + if (result[path]) { + const value = result[path]; + + if (value.value < overviewData[path].min) { + overviewData[path].min = value.value; + } + + if (value.value > overviewData[path].max) { + overviewData[path].max = value.value; + } + + data[path].push([adjustedTimestamp, value.value]); + } else { + //data[path].push([adjustedTimestamp, null]); + } + }); + } + + startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + diff / 100); + if (startUnixTime.ticks % 2 != 0) { + startUnixTime = UnixTime.fromTicks(startUnixTime.ticks + 1); + } + + console.log('Try next timestamp: ', startUnixTime); + } + + //console.log(input); + // input.forEach((item) => { + // const csvContent = item.value; + // pathsToSearch.forEach((path) => { + // if (csvContent) { + // const timestamp = item.time.ticks * 1000; + // + // const adjustedTimestamp = new Date(timestamp); + // adjustedTimestamp.setHours(adjustedTimestamp.getHours() + 1); + // + // if (csvContent[path]) { + // const value = csvContent[path]; + // + // if (value.value < overviewData[path].min) { + // overviewData[path].min = value.value; + // } + // + // if (value.value > overviewData[path].max) { + // overviewData[path].max = value.value; + // } + // + // data[path].push([adjustedTimestamp, value.value]); + // } else { + // //data[path].push([adjustedTimestamp, null]); + // } + // } else { + // // data[path].push([ + // // addHours(new Date(item.time.ticks * 1000), 2), + // // null + // // ]); + // } + // }); + // }); + + pathsToSearch.forEach((path) => { + let value = Math.max( + Math.abs(overviewData[path].max), + Math.abs(overviewData[path].min) + ); + let magnitude = 0; + + if (value < 0) { + value = -value; + } + while (value >= 1000) { + value /= 1000; + magnitude++; + } + overviewData[path].magnitude = prefixes[magnitude]; + }); + + let path = '/Battery/Soc'; + chartData.soc = [{ name: 'State of Charge', data: data[path] }]; + + chartOverview.soc = { + unit: '(%)', + magnitude: overviewData[path].magnitude, + min: 0, + max: 100 + }; + + path = '/Battery/Temperature'; + chartData.temperature = [{ name: 'Battery Temperature:', data: data[path] }]; + + chartOverview.temperature = { + unit: '(°C)', + magnitude: overviewData[path].magnitude, + min: overviewData[path].min, + max: overviewData[path].max + }; + + path = '/Battery/Dc/Power'; + chartData.dcPower = [{ name: 'Battery Power', data: data[path] }]; + + chartOverview.dcPower = { + magnitude: overviewData[path].magnitude, + unit: '(' + overviewData[path].magnitude + 'W' + ')', + min: overviewData[path].min, + max: overviewData[path].max + }; + + path = '/GridMeter/Ac/Power/Active'; + chartData.gridPower = [{ name: 'Grid Power', data: data[path] }]; + + chartOverview.gridPower = { + magnitude: overviewData[path].magnitude, + unit: '(' + overviewData[path].magnitude + 'W' + ')', + min: overviewData[path].min, + max: overviewData[path].max + }; + + path = '/PvOnDc/Dc/Power'; + chartData.pvProduction = [{ name: 'Pv Production', data: data[path] }]; + + chartOverview.pvProduction = { + magnitude: overviewData[path].magnitude, + unit: '(' + overviewData[path].magnitude + 'W' + ')', + min: overviewData[path].min, + max: overviewData[path].max + }; + + path = '/DcDc/Dc/Link/Voltage'; + chartData.dcBusVoltage = [{ name: 'DC Bus Voltage', data: data[path] }]; + + chartOverview.dcBusVoltage = { + magnitude: overviewData[path].magnitude, + unit: '(' + overviewData[path].magnitude + 'V' + ')', + min: overviewData[path].min, + max: overviewData[path].max + }; + + return { + chartData: chartData, + chartOverview: chartOverview + }; +}; + +export const transformInputToAggregatedData = async ( + s3Credentials: I_S3Credentials +): Promise<{ + chartAggregatedData: chartAggregatedDataInterface; + chartOverview: overviewInterface; +}> => { + const data = {}; + const overviewData = {}; + const prefixes = ['', 'k', 'M', 'G', 'T']; + const MAX_NUMBER = 9999999; + + let currentDate = dayjs().add(1, 'day'); + let currentDay = currentDate.subtract(1, 'week'); + + const pathsToSearch = [ + '/AvgSoc', + '/AvgPvPower', + '/BatteryPowerAverage', + '/GridMeter/Ac/Power/Active', + '/PvOnDc/Dc/Power', + '/DcDc/Dc/Link/Voltage' + ]; + + const chartAggregatedData: chartAggregatedDataInterface = { + soc: [{ data: [] }], + pvProduction: [{ data: [] }], + dcPower: [{ data: [] }] + }; + + const chartOverview: overviewInterface = { + soc: { magnitude: 0, unit: '', min: 0, max: 0 }, + temperature: { magnitude: 0, unit: '', min: 0, max: 0 }, + dcPower: { magnitude: 0, unit: '', min: 0, max: 0 }, + gridPower: { magnitude: 0, unit: '', min: 0, max: 0 }, + pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 }, + dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 } + }; + + pathsToSearch.forEach((path) => { + data[path] = []; + overviewData[path] = { + magnitude: 0, + unit: '', + min: MAX_NUMBER, + max: -MAX_NUMBER + }; + }); + + while (currentDay.isBefore(currentDate)) { + // console.log('Current day:', currentDay.format('YYYY-MM-DD')); + + let result = await Promise.resolve( + fetchDailyData(currentDay.format('YYYY-MM-DD'), s3Credentials) + ); + if ( + result === FetchResult.notAvailable || + result === FetchResult.tryLater + ) { + // Handle not available or try later case + } else { + console.log('Received data:', result); + pathsToSearch.forEach((path) => { + if (result[path]) { + if (result[path].value < overviewData[path].min) { + overviewData[path].min = result[path].value; + } + + if (result[path].value > overviewData[path].max) { + overviewData[path].max = result[path].value; + } + data[path].push(result[path].value as number); + } + }); + } + // Add one day to move to the next day + currentDay = currentDay.add(1, 'day'); + } + + pathsToSearch.forEach((path) => { + let value = Math.max( + Math.abs(overviewData[path].max), + Math.abs(overviewData[path].min) + ); + let magnitude = 0; + + if (value < 0) { + value = -value; + } + while (value >= 1000) { + value /= 1000; + magnitude++; + } + overviewData[path].magnitude = prefixes[magnitude]; + }); + + let path = '/AvgSoc'; + chartAggregatedData.soc[0].data = data[path]; + + chartOverview.soc = { + unit: '(%)', + magnitude: overviewData[path].magnitude, + min: 0, + max: 100 + }; + + path = '/AvgPvPower'; + chartAggregatedData.pvProduction[0].data = data[path]; + + chartOverview.pvProduction = { + magnitude: overviewData[path].magnitude, + unit: '(' + overviewData[path].magnitude + 'W' + ')', + min: overviewData[path].min, + max: overviewData[path].max + }; + + path = '/BatteryPowerAverage'; + chartAggregatedData.dcPower[0].data = data[path]; + + chartOverview.dcPower = { + magnitude: overviewData[path].magnitude, + unit: '(' + overviewData[path].magnitude + 'W' + ')', + min: overviewData[path].min, + max: overviewData[path].max + }; + + return { + chartAggregatedData: chartAggregatedData, + chartOverview: chartOverview + }; +};