Skip to content

Commit a58feb8

Browse files
committedMay 9, 2021
Implement serial monitor
1 parent 7818a31 commit a58feb8

File tree

11 files changed

+1085
-227
lines changed

11 files changed

+1085
-227
lines changed
 

‎src/api/src/services/SerialMonitor/index.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,11 @@ export default class SerialMonitorService {
6363
this.port.on('error', (err) => {
6464
this.sendLogs(`Serial port error: ${err.message}`);
6565
this.sendEvent(SerialMonitorEventType.Error);
66+
this.port = null;
6667
});
6768

6869
this.port.on('data', (data) => {
69-
this.sendLogs(data);
70+
this.sendLogs(data.toString());
7071
});
7172

7273
this.port.on('close', () => {
@@ -89,15 +90,12 @@ export default class SerialMonitorService {
8990

9091
async disconnect(): Promise<void> {
9192
if (this.port === null) {
92-
throw new Error('not connected');
93+
return Promise.resolve();
9394
}
94-
return new Promise((resolve, reject) => {
95-
this.port?.close((err) => {
96-
if (err) {
97-
reject(err);
98-
} else {
99-
resolve();
100-
}
95+
return new Promise((resolve) => {
96+
this.port?.close(() => {
97+
this.port = null;
98+
resolve();
10199
});
102100
});
103101
}

‎src/ui/App.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import client from './gql';
1010
import ConfiguratorView from './views/ConfiguratorView';
1111
import SettingsView from './views/SettingsView';
1212
import LogsView from './views/LogsView';
13+
import SerialMonitorView from './views/SerialMonitorView';
1314
import SupportView from './views/SupportView';
1415

1516
export default function App() {
@@ -25,6 +26,7 @@ export default function App() {
2526
<Route path="/configurator" component={ConfiguratorView} />
2627
<Route path="/settings" component={SettingsView} />
2728
<Route path="/logs" component={LogsView} />
29+
<Route path="/serial-monitor" component={SerialMonitorView} />
2830
<Route path="/support" component={SupportView} />
2931
</Switch>
3032
</HashRouter>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
import { Button, Grid, makeStyles, TextField } from '@material-ui/core';
2+
import React, { FunctionComponent, useEffect, useState } from 'react';
3+
import Omnibox, { Option } from '../Omnibox';
4+
import {
5+
SerialPortInformation,
6+
useAvailableDevicesListQuery,
7+
} from '../../gql/generated/types';
8+
import Loader from '../Loader';
9+
import ShowAlerts from '../ShowAlerts';
10+
11+
const useStyles = makeStyles((theme) => ({
12+
root: {
13+
marginTop: theme.spacing(2),
14+
marginBottom: theme.spacing(2),
15+
},
16+
loader: {
17+
marginTop: theme.spacing(2),
18+
marginBottom: theme.spacing(2),
19+
},
20+
button: {
21+
paddingTop: '15px !important',
22+
paddingBottom: '15px !important',
23+
},
24+
}));
25+
26+
interface SerialConnectionFormProps {
27+
serialDevice: string | null;
28+
baudRate: number;
29+
onConnect: (serialDevice: string | null, baudRate: number) => void;
30+
}
31+
32+
const SerialConnectionForm: FunctionComponent<SerialConnectionFormProps> = (
33+
props
34+
) => {
35+
const { onConnect, serialDevice, baudRate } = props;
36+
const styles = useStyles();
37+
38+
const { loading, data, error, previousData } = useAvailableDevicesListQuery({
39+
pollInterval: 1000,
40+
});
41+
const options: Option[] =
42+
data?.availableDevicesList?.map((target) => ({
43+
label: `${target.path} ${target.manufacturer}`,
44+
value: target.path,
45+
})) ?? [];
46+
47+
const [currentBaud, setCurrentBaud] = useState<number>(baudRate);
48+
const [currentValue, setCurrentValue] = useState<Option | null>(
49+
serialDevice
50+
? {
51+
label: serialDevice,
52+
value: serialDevice,
53+
}
54+
: null
55+
);
56+
const onSerialDeviceChange = (value: string | null) => {
57+
if (value === null) {
58+
setCurrentValue(null);
59+
} else {
60+
setCurrentValue({
61+
label: value,
62+
value,
63+
});
64+
}
65+
};
66+
const onBaudChange = (
67+
event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>
68+
) => {
69+
if (event.target.validity.valid) {
70+
try {
71+
const value = parseInt(event.target.value, 10);
72+
setCurrentBaud(value);
73+
} catch (e) {
74+
console.error('failed to parse number', e);
75+
}
76+
} else {
77+
console.log('only numbers');
78+
}
79+
};
80+
useEffect(() => {
81+
const difference = (
82+
a: readonly Pick<SerialPortInformation, 'path' | 'manufacturer'>[],
83+
b: readonly Pick<SerialPortInformation, 'path' | 'manufacturer'>[]
84+
): Pick<SerialPortInformation, 'path' | 'manufacturer'>[] => {
85+
return a.filter((item) => {
86+
return b.find((item2) => item2.path === item.path) === undefined;
87+
});
88+
};
89+
if (currentValue === null) {
90+
const added = difference(
91+
data?.availableDevicesList ?? [],
92+
previousData?.availableDevicesList ?? []
93+
);
94+
if (added.length > 0) {
95+
onSerialDeviceChange(added[0].path);
96+
}
97+
} else {
98+
const added = difference(
99+
data?.availableDevicesList ?? [],
100+
previousData?.availableDevicesList ?? []
101+
);
102+
if (added.length > 0) {
103+
onSerialDeviceChange(added[0].path);
104+
return;
105+
}
106+
const removed = difference(
107+
previousData?.availableDevicesList ?? [],
108+
data?.availableDevicesList ?? []
109+
);
110+
if (
111+
removed.find((item) => item.path === currentValue.value) !==
112+
undefined &&
113+
data?.availableDevicesList &&
114+
data?.availableDevicesList.length > 0
115+
) {
116+
onSerialDeviceChange(data?.availableDevicesList[0].path);
117+
}
118+
}
119+
}, [data, previousData]);
120+
const onSubmit = () => {
121+
if (currentValue !== null) {
122+
console.log('currentValue', currentValue);
123+
onConnect(currentValue?.value, baudRate);
124+
}
125+
};
126+
return (
127+
<div className={styles.root}>
128+
<Grid container spacing={2}>
129+
<Grid item xs={3}>
130+
<Omnibox
131+
title="Serial device"
132+
currentValue={currentValue}
133+
onChange={onSerialDeviceChange}
134+
options={options}
135+
loading={loading}
136+
/>
137+
<Loader className={styles.loader} loading={loading} />
138+
<ShowAlerts severity="error" messages={error} />
139+
</Grid>
140+
<Grid item>
141+
<TextField
142+
size="medium"
143+
onBlur={onBaudChange}
144+
defaultValue={currentBaud}
145+
fullWidth
146+
inputProps={{
147+
min: 0,
148+
type: 'number',
149+
step: '1',
150+
}}
151+
label="Baud rate"
152+
/>
153+
</Grid>
154+
<Grid item>
155+
<Button
156+
onClick={onSubmit}
157+
size="large"
158+
variant="contained"
159+
className={styles.button}
160+
>
161+
Connect
162+
</Button>
163+
</Grid>
164+
</Grid>
165+
</div>
166+
);
167+
};
168+
169+
export default SerialConnectionForm;

‎src/ui/components/Sidebar/index.tsx

+18-1
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import {
1212
} from '@material-ui/core';
1313
import BuildIcon from '@material-ui/icons/Build';
1414
import HelpIcon from '@material-ui/icons/Help';
15+
import DvrIcon from '@material-ui/icons/Dvr';
1516
// import SettingsIcon from '@material-ui/icons/Settings';
1617
import ListIcon from '@material-ui/icons/List';
1718
import { matchPath, useLocation, Link } from 'react-router-dom';
1819

19-
const drawerWidth = 205;
20+
const drawerWidth = 215;
2021
const useStyles = makeStyles((theme) => ({
2122
drawer: {
2223
width: drawerWidth,
@@ -45,6 +46,8 @@ const Sidebar: FunctionComponent<SidebarProps> = ({ navigationEnabled }) => {
4546
matchPath(location.pathname, '/configurator') !== null;
4647
// const settingsActive = matchPath(location.pathname, '/settings') !== null;
4748
const logsActive = matchPath(location.pathname, '/logs') !== null;
49+
const serialMonitorActive =
50+
matchPath(location.pathname, '/serial-monitor') !== null;
4851
const supportActive = matchPath(location.pathname, '/support') !== null;
4952

5053
return (
@@ -100,6 +103,20 @@ const Sidebar: FunctionComponent<SidebarProps> = ({ navigationEnabled }) => {
100103
<ListItemText primary="Logs" />
101104
</ListItem>
102105

106+
<ListItem
107+
component={Link}
108+
to="/serial-monitor"
109+
selected={serialMonitorActive}
110+
className={styles.menuItem}
111+
button
112+
disabled={!navigationEnabled}
113+
>
114+
<ListItemIcon>
115+
<DvrIcon />
116+
</ListItemIcon>
117+
<ListItemText primary="Serial Monitor" />
118+
</ListItem>
119+
103120
<ListItem
104121
component={Link}
105122
to="/support"

0 commit comments

Comments
 (0)
Please sign in to comment.