From 41e56101aeef2f95b5a64adbf9c3e8273139f4f3 Mon Sep 17 00:00:00 2001 From: Piotr Rogowski Date: Thu, 16 Dec 2021 23:13:18 +0100 Subject: [PATCH] Base work for Diagnose tab (#323) --- ...14.19.11-composite.csv => composite_1.csv} | 2 +- public/logs/trigger/composite_1.csv.gz | Bin 0 -> 3085 bytes .../other/{2021-12-10_18.41.41.csv => 1.csv} | 0 .../other/{2021-12-10_18.46.57.csv => 2.csv} | 0 .../other/{2021-12-10_18.54.44.csv => 3.csv} | 0 .../other/{2021-12-10_19.02.50.csv => 4.csv} | 0 ...-31_14.24.40-trigger.csv => trigger_1.csv} | 0 src/App.tsx | 18 +- src/components/Diagnose.tsx | 250 ++++++++++++++++++ src/components/Log.tsx | 4 +- src/components/Log/LogCanvas.tsx | 4 +- src/components/TriggerLog/CompositeCanvas.tsx | 195 ++++++++++++++ src/utils/api.ts | 11 +- 13 files changed, 468 insertions(+), 16 deletions(-) rename public/logs/trigger/{2021-05-31_14.19.11-composite.csv => composite_1.csv} (99%) create mode 100644 public/logs/trigger/composite_1.csv.gz rename public/logs/trigger/other/{2021-12-10_18.41.41.csv => 1.csv} (100%) rename public/logs/trigger/other/{2021-12-10_18.46.57.csv => 2.csv} (100%) rename public/logs/trigger/other/{2021-12-10_18.54.44.csv => 3.csv} (100%) rename public/logs/trigger/other/{2021-12-10_19.02.50.csv => 4.csv} (100%) rename public/logs/trigger/{2021-05-31_14.24.40-trigger.csv => trigger_1.csv} (100%) create mode 100644 src/components/Diagnose.tsx create mode 100644 src/components/TriggerLog/CompositeCanvas.tsx diff --git a/public/logs/trigger/2021-05-31_14.19.11-composite.csv b/public/logs/trigger/composite_1.csv similarity index 99% rename from public/logs/trigger/2021-05-31_14.19.11-composite.csv rename to public/logs/trigger/composite_1.csv index b6e9beb..db1536c 100644 --- a/public/logs/trigger/2021-05-31_14.19.11-composite.csv +++ b/public/logs/trigger/composite_1.csv @@ -1,4 +1,4 @@ -# +#Firmware: Speeduino 2021.09-dev PriLevel,SecLevel,Trigger,Sync,RefTime,MaxTime,ToothTime,Time Flag,Flag,Flag,Flag,ms,ms,ms,ms 0.0,1.0,1.0,1.0,560535.4,560535.4,2274.308,560535.4 diff --git a/public/logs/trigger/composite_1.csv.gz b/public/logs/trigger/composite_1.csv.gz new file mode 100644 index 0000000000000000000000000000000000000000..bcf8144d7e996f5afb5ef17e052ec05eda4dbb7a GIT binary patch literal 3085 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 { - + + + + + + + diff --git a/src/components/Diagnose.tsx b/src/components/Diagnose.tsx new file mode 100644 index 0000000..8c0382a --- /dev/null +++ b/src/components/Diagnose.tsx @@ -0,0 +1,250 @@ +/* eslint-disable import/no-webpack-loader-syntax */ +import { + useCallback, + useEffect, + useRef, + useState, +} from 'react'; +import { + Layout, + Tabs, + Skeleton, + Progress, + Steps, + Space, + Divider, +} from 'antd'; +import { + FileTextOutlined, + GlobalOutlined, +} from '@ant-design/icons'; +import pako from 'pako'; +import useBreakpoint from 'antd/lib/grid/hooks/useBreakpoint'; +import { connect } from 'react-redux'; +import PerfectScrollbar from 'react-perfect-scrollbar'; +import { + AppState, + UIState, + Config, + Logs, +} from '@speedy-tuner/types'; +import { loadCompositeLogs } from '../utils/api'; +import store from '../store'; +import { formatBytes } from '../utils/number'; +import CompositeCanvas from './TriggerLog/CompositeCanvas'; +import { isNumber } from '../utils/tune/expression'; + +const { TabPane } = Tabs; +const { Content } = Layout; +const { Step } = Steps; +const edgeUnknown = 'Unknown'; + +const mapStateToProps = (state: AppState) => ({ + ui: state.ui, + status: state.status, + config: state.config, + loadedLogs: state.logs, +}); + +// TODO: extract this to types package +interface CompositeLogEntry { + type: 'trigger' | 'marker'; + primaryLevel: number; + secondaryLevel: number; + trigger: number; + sync: number; + refTime: number; + maxTime: number; + toothTime: number; + time: number; +} + +const Diagnose = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLogs: Logs }) => { + const { lg } = useBreakpoint(); + const { Sider } = Layout; + const [progress, setProgress] = useState(0); + const [fileSize, setFileSize] = useState(); + const [step, setStep] = useState(0); + const [edgeLocation, setEdgeLocation] = useState(edgeUnknown); + const [fetchError, setFetchError] = useState(); + const contentRef = useRef(null); + const margin = 30; + const [canvasWidth, setCanvasWidth] = useState(0); + const sidebarWidth = 250; + const calculateCanvasWidth = useCallback(() => setCanvasWidth((contentRef.current?.clientWidth || 0) - margin), []); + const siderProps = { + width: sidebarWidth, + collapsible: true, + breakpoint: 'xl', + collapsed: ui.sidebarCollapsed, + onCollapse: (collapsed: boolean) => { + store.dispatch({ type: 'ui/sidebarCollapsed', payload: collapsed }); + setTimeout(calculateCanvasWidth, 1); + }, + }; + const [logs, setLogs] = useState(); + + useEffect(() => { + const controller = new AbortController(); + const { signal } = controller; + + const loadData = async () => { + try { + const raw = await loadCompositeLogs((percent, total, edge) => { + setProgress(percent); + setFileSize(formatBytes(total)); + setEdgeLocation(edge || edgeUnknown); + }, signal); + + setFileSize(formatBytes(raw.byteLength)); + + const buff = pako.inflate(new Uint8Array(raw)); + const string = (new TextDecoder()).decode(buff); + const result: CompositeLogEntry[] = []; + + setStep(1); + + // TODO: extract this, make a parser class + string.split('\n').forEach((line, index) => { + const trimmed = line.trim(); + + // skip comments + if (trimmed.startsWith('#')) { + return; + } + + // markers + if (trimmed.startsWith('MARK')) { + const previous = result[result.length - 1] || { + primaryLevel: 0, + secondaryLevel: 0, + trigger: 0, + sync: 0, + refTime: 0, + maxTime: 0, + toothTime: 0, + time: 0, + }; + + result.push({ + type: 'marker', + primaryLevel: previous.primaryLevel, + secondaryLevel: previous.secondaryLevel, + trigger: previous.trigger, + sync: previous.sync, + refTime: previous.refTime, + maxTime: previous.maxTime, + toothTime: previous.toothTime, + time: previous.time, + }); + } + + const split = trimmed.split(','); + if (!isNumber(split[0])) { + return; + } + + const time = Number(split[7]); + if (!time) { + return; + } + + result.push({ + type: 'trigger', + primaryLevel: Number(split[0]), + secondaryLevel: Number(split[1]), + trigger: Number(split[2]), + sync: Number(split[3]), + refTime: Number(split[4]), + maxTime: Number(split[5]), + toothTime: Number(split[6]), + time, + }); + }); + + setLogs(result); + setStep(2); + } catch (error) { + setFetchError(error as Error); + console.error(error); + } + }; + + loadData(); + calculateCanvasWidth(); + + window.addEventListener('resize', calculateCanvasWidth); + + return () => { + controller.abort(); + window.removeEventListener('resize', calculateCanvasWidth); + }; + }, [calculateCanvasWidth, loadedLogs]); + + return ( + <> + + {!logs && !loadedLogs.length ? +
+ : + !ui.sidebarCollapsed && + + } key="files"> + + composite.csv + + + + } +
+ + +
+ {logs + ? + + : + + + + + + {edgeLocation} + + } + /> + + + + + } +
+
+
+ + ); +}; + +export default connect(mapStateToProps)(Diagnose); diff --git a/src/components/Log.tsx b/src/components/Log.tsx index e917b24..63a89df 100644 --- a/src/components/Log.tsx +++ b/src/components/Log.tsx @@ -211,7 +211,7 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo } key="files"> - Files + some_tune.mlg @@ -225,7 +225,7 @@ const Log = ({ ui, config, loadedLogs }: { ui: UIState, config: Config, loadedLo : diff --git a/src/components/Log/LogCanvas.tsx b/src/components/Log/LogCanvas.tsx index 1ab7456..f983170 100644 --- a/src/components/Log/LogCanvas.tsx +++ b/src/components/Log/LogCanvas.tsx @@ -130,7 +130,7 @@ const LogCanvas = ({ data, width, height, selectedFields }: Props) => { }); let chart: TimeChart; - if (canvasRef.current) { + if (canvasRef.current && sm) { chart = new TimeChart(canvasRef.current, { series, lineWidth: 2, @@ -148,7 +148,7 @@ const LogCanvas = ({ data, width, height, selectedFields }: Props) => { } return () => chart && chart.dispose(); - }, [data, fieldsToPlot, hsl, selectedFields, width, height]); + }, [data, fieldsToPlot, hsl, selectedFields, width, height, sm]); if (!sm) { return ; diff --git a/src/components/TriggerLog/CompositeCanvas.tsx b/src/components/TriggerLog/CompositeCanvas.tsx new file mode 100644 index 0000000..257f1e5 --- /dev/null +++ b/src/components/TriggerLog/CompositeCanvas.tsx @@ -0,0 +1,195 @@ +import { + useEffect, + useRef, +} from 'react'; +import { + Popover, + Space, + Typography, + Grid, +} from 'antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import TimeChart from 'timechart'; +import { EventsPlugin } from 'timechart/dist/lib/plugins_extra/events'; +import LandscapeNotice from '../Dialog/LandscapeNotice'; + +enum Colors { + RED = '#f32450', + CYAN = '#8dd3c7', + YELLOW = '#ffff00', + PURPLE = '#bebada', + GREEN = '#77de3c', + BLUE = '#2fe3ff', + GREY = '#334455', + WHITE = '#fff', + BG = '#222629', +} + +const { Text } = Typography; +const { useBreakpoint } = Grid; + +export interface SelectedField { + name: string; + label: string; + units: string; + scale: string | number; + transform: string | number; + format: string; +}; + +// TODO: extract this to types package +interface CompositeLogEntry { + type: 'trigger' | 'marker'; + primaryLevel: number; + secondaryLevel: number; + trigger: number; + sync: number; + refTime: number; + maxTime: number; + toothTime: number; + time: number; +} + +interface Props { + data: CompositeLogEntry[]; + width: number; + height: number; +}; + +interface DataPoint { + x: number; + y: number; +} + +const CompositeCanvas = ({ data, width, height }: Props) => { + const { sm } = useBreakpoint(); + const canvasRef = useRef(null); + + useEffect(() => { + let chart: TimeChart; + const markers: { x: number, name: string }[] = []; + const primary: DataPoint[] = []; + const secondary: DataPoint[] = []; + const sync: DataPoint[] = []; + + data.forEach((entry, index) => { + if (entry.type === 'marker') { + markers.push({ + x: index, + name: '', + }); + } + + if (entry.type === 'trigger') { + const prevSecondary = data[index - 1] ? data[index - 1].secondaryLevel : 0; + const currentSecondary = (entry.secondaryLevel + 3) * 2; // apply scale + + const prevPrimary = data[index - 1] ? data[index - 1].primaryLevel : 0; + const currentPrimary = (entry.primaryLevel + 1) * 2; // apply scale + + const prevSync = data[index - 1] ? data[index - 1].sync : 0; + const currentSync = entry.sync; + + // make it square + if (prevSecondary !== currentSecondary) { + secondary.push({ + x: index - 1, + y: currentSecondary, + }); + } + secondary.push({ + x: index, + y: currentSecondary, + }); + + if (prevPrimary !== currentPrimary) { + primary.push({ + x: index - 1, + y: currentPrimary, + }); + } + primary.push({ + x: index, + y: currentPrimary, + }); + + if (prevSync !== currentSync) { + sync.push({ + x: index - 1, + y: currentSync, + }); + } + sync.push({ + x: index, + y: currentSync, + }); + } + }); + + const series = [{ + name: 'Primary', + color: Colors.BLUE, + data: primary, + }, { + name: 'Secondary', + color: Colors.GREEN, + data: secondary, + }, { + name: 'Sync', + color: Colors.RED, + data: sync, + }]; + + if (canvasRef.current && sm) { + chart = new TimeChart(canvasRef.current, { + series, + lineWidth: 2, + tooltip: true, + legend: false, + zoom: { + x: { autoRange: true }, + y: { autoRange: true }, + }, + yRange: { min: -1, max: 9 }, + tooltipXLabel: 'Event', + plugins: { + events: new EventsPlugin(markers), + }, + }); + } + + return () => chart && chart.dispose(); + }, [data, width, height, sm]); + + if (!sm) { + return ; + } + + return ( + <> +
+ + Navigation + Pinch to zoom + Drag to pan + Ctrl + wheel scroll to zoom X axis + Hold Shift to speed up zoom 5 times + + } + > + + +
+
+ + ); +}; + +export default CompositeCanvas; diff --git a/src/utils/api.ts b/src/utils/api.ts index 25bf237..cffd865 100644 --- a/src/utils/api.ts +++ b/src/utils/api.ts @@ -86,5 +86,12 @@ export const loadLogs = (onProgress?: onProgressType, signal?: AbortSignal) => // 'https://d29mjpbgm6k6md.cloudfront.net/logs/markers.mlg.gz', onProgress, signal, - ) - .then((response) => response); + ).then((response) => response); + +export const loadCompositeLogs = (onProgress?: onProgressType, signal?: AbortSignal) => + fetchWithProgress( + 'https://d29mjpbgm6k6md.cloudfront.net/trigger-logs/composite_1.csv.gz', + // 'https://d29mjpbgm6k6md.cloudfront.net/trigger-logs/2.csv.gz', + onProgress, + signal, + ).then((response) => response);