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 0000000..bcf8144
Binary files /dev/null and b/public/logs/trigger/composite_1.csv.gz differ
diff --git a/public/logs/trigger/other/2021-12-10_18.41.41.csv b/public/logs/trigger/other/1.csv
similarity index 100%
rename from public/logs/trigger/other/2021-12-10_18.41.41.csv
rename to public/logs/trigger/other/1.csv
diff --git a/public/logs/trigger/other/2021-12-10_18.46.57.csv b/public/logs/trigger/other/2.csv
similarity index 100%
rename from public/logs/trigger/other/2021-12-10_18.46.57.csv
rename to public/logs/trigger/other/2.csv
diff --git a/public/logs/trigger/other/2021-12-10_18.54.44.csv b/public/logs/trigger/other/3.csv
similarity index 100%
rename from public/logs/trigger/other/2021-12-10_18.54.44.csv
rename to public/logs/trigger/other/3.csv
diff --git a/public/logs/trigger/other/2021-12-10_19.02.50.csv b/public/logs/trigger/other/4.csv
similarity index 100%
rename from public/logs/trigger/other/2021-12-10_19.02.50.csv
rename to public/logs/trigger/other/4.csv
diff --git a/public/logs/trigger/2021-05-31_14.24.40-trigger.csv b/public/logs/trigger/trigger_1.csv
similarity index 100%
rename from public/logs/trigger/2021-05-31_14.24.40-trigger.csv
rename to public/logs/trigger/trigger_1.csv
diff --git a/src/App.tsx b/src/App.tsx
index c1ab695..a7c72ca 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -7,10 +7,7 @@ import {
Redirect,
generatePath,
} from 'react-router-dom';
-import {
- Layout,
- Result,
-} from 'antd';
+import { Layout } from 'antd';
import { connect } from 'react-redux';
import {
useEffect,
@@ -32,6 +29,7 @@ import 'react-perfect-scrollbar/dist/css/styles.css';
import './App.less';
import { Routes } from './routes';
import Log from './components/Log';
+import Diagnose from './components/Diagnose';
import useStorage from './hooks/useStorage';
import useConfig from './hooks/useConfig';
@@ -113,11 +111,13 @@ const App = ({ ui, config }: { ui: UIState, config: ConfigType }) => {
-
+
+
+
+
+
+
+
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);