CEC-2291 Remote Commands (#194)
This commit is contained in:
@@ -4,19 +4,24 @@
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@datadog/browser-logs": "^3.11.0",
|
||||
"@date-io/date-fns": "1.x",
|
||||
"@date-io/moment": "1.x",
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@material-ui/core": "^4.12.4",
|
||||
"@material-ui/icons": "^4.11.3",
|
||||
"@material-ui/pickers": "^3.3.10",
|
||||
"@testing-library/jest-dom": "^5.16.4",
|
||||
"@testing-library/react": "^12.1.4",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^0.26.1",
|
||||
"clsx": "^1.1.1",
|
||||
"date-fns": "^2.29.2",
|
||||
"email-validator": "^2.0.4",
|
||||
"env-cmd": "^10.1.0",
|
||||
"leaflet": "^1.8.0",
|
||||
"material-ui-dropzone": "^3.5.0",
|
||||
"moment": "^2.29.4",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-leaflet": "^3.2.5",
|
||||
|
||||
@@ -6873,6 +6873,24 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
aria-controls="tabpanel-5"
|
||||
aria-selected="false"
|
||||
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit"
|
||||
id="tab-5"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiTab-wrapper"
|
||||
>
|
||||
Remote Commands
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
class="PrivateTabIndicator-root-0 PrivateTabIndicator-colorSecondary-0 MuiTabs-indicator"
|
||||
@@ -7036,6 +7054,13 @@ exports[`App Route /vehicle-status authenticated 1`] = `
|
||||
id="tabpanel-4"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-5"
|
||||
class="makeStyles-fullWidth-0"
|
||||
hidden=""
|
||||
id="tabpanel-5"
|
||||
role="tabpanel"
|
||||
/>
|
||||
</div>
|
||||
</main>
|
||||
</main>
|
||||
|
||||
26
src/components/Cars/Status/RemoteCommandsTab.jsx
Normal file
26
src/components/Cars/Status/RemoteCommandsTab.jsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import useStyles from "../../useStyles";
|
||||
import clsx from "clsx";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import SendCommand from "../../Controls/SendCommand";
|
||||
import PropTypes from "prop-types";
|
||||
import {VehicleProvider} from "../../Contexts/VehicleContext";
|
||||
|
||||
const RemoteCommandsTab = (props) => {
|
||||
const { vin } = props;
|
||||
const classes = useStyles();
|
||||
|
||||
return (
|
||||
<div className={clsx(classes.paper, classes.tableSize)}>
|
||||
<Typography variant="h6">Vehicle Commands</Typography>
|
||||
<VehicleProvider>
|
||||
<SendCommand vins={[vin]}></SendCommand>
|
||||
</VehicleProvider>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
RemoteCommandsTab.propTypes = {
|
||||
vin: PropTypes.string,
|
||||
}
|
||||
|
||||
export default RemoteCommandsTab
|
||||
40
src/components/Cars/Status/RemoteCommandsTab.test.jsx
Normal file
40
src/components/Cars/Status/RemoteCommandsTab.test.jsx
Normal file
@@ -0,0 +1,40 @@
|
||||
jest.mock("../../Contexts/VehicleContext");
|
||||
jest.mock("../../Contexts/StatusContext");
|
||||
jest.mock("../../Contexts/UserContext");
|
||||
jest.mock("@material-ui/core/utils/unstable_useId", () =>
|
||||
jest.fn().mockReturnValue("mui-test-id")
|
||||
);
|
||||
|
||||
import {render, waitFor} from "@testing-library/react";
|
||||
import {StatusProvider} from "../../Contexts/StatusContext";
|
||||
import {setToken, UserProvider} from "../../Contexts/UserContext";
|
||||
import RemoteCommandsTab from "./RemoteCommandsTab";
|
||||
import addSnapshotSerializer from "../../../utils/snapshot";
|
||||
import {TEST_AUTH_OBJECT} from "../../../utils/testing";
|
||||
import React from "react";
|
||||
|
||||
const renderRemoteCommandsTab = async () => {
|
||||
const { container } = render(
|
||||
<StatusProvider>
|
||||
<UserProvider>
|
||||
<RemoteCommandsTab vin="TESTVIN1234567890" />
|
||||
</UserProvider>
|
||||
</StatusProvider>
|
||||
);
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
return container;
|
||||
};
|
||||
|
||||
describe("RemoteCommandsTab", () => {
|
||||
beforeAll(() => {
|
||||
addSnapshotSerializer(expect);
|
||||
});
|
||||
|
||||
it("Render", async () => {
|
||||
setToken(TEST_AUTH_OBJECT);
|
||||
const container = await renderRemoteCommandsTab();
|
||||
expect(container).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,173 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`RemoteCommandsTab Render 1`] = `
|
||||
<div>
|
||||
<div
|
||||
data-testid="mocked-statusprovider"
|
||||
>
|
||||
<div
|
||||
data-testid="mocked-userprovider"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-0 makeStyles-tableSize-0"
|
||||
>
|
||||
<h6
|
||||
class="MuiTypography-root MuiTypography-h6"
|
||||
>
|
||||
Vehicle Commands
|
||||
</h6>
|
||||
<div
|
||||
data-testid="mocked-vehicleprovider"
|
||||
>
|
||||
<div
|
||||
class="makeStyles-paper-0"
|
||||
style="margin-top: 20px;"
|
||||
>
|
||||
<div
|
||||
class="MuiFormControl-root makeStyles-formControl-0"
|
||||
>
|
||||
<label
|
||||
class="MuiFormLabel-root MuiInputLabel-root makeStyles-whiteBackground-0 MuiInputLabel-formControl MuiInputLabel-animated MuiInputLabel-shrink MuiInputLabel-marginDense MuiInputLabel-outlined MuiFormLabel-filled"
|
||||
data-shrink="true"
|
||||
for="send-command"
|
||||
>
|
||||
Command
|
||||
</label>
|
||||
<div
|
||||
class="MuiInputBase-root MuiOutlinedInput-root MuiInputBase-formControl MuiInputBase-marginDense MuiOutlinedInput-marginDense"
|
||||
>
|
||||
<select
|
||||
aria-invalid="false"
|
||||
class="MuiSelect-root MuiSelect-select MuiSelect-outlined MuiInputBase-input MuiOutlinedInput-input MuiInputBase-inputMarginDense MuiOutlinedInput-inputMarginDense"
|
||||
id="send-command"
|
||||
name="send-command"
|
||||
>
|
||||
<option
|
||||
value="doors_lock"
|
||||
>
|
||||
Lock doors
|
||||
</option>
|
||||
<option
|
||||
value="doors_unlock"
|
||||
>
|
||||
Unlock doors
|
||||
</option>
|
||||
<option
|
||||
value="vent_windows"
|
||||
>
|
||||
Vent windows
|
||||
</option>
|
||||
<option
|
||||
value="close_windows"
|
||||
>
|
||||
Close windows
|
||||
</option>
|
||||
<option
|
||||
value="flash_headlights"
|
||||
>
|
||||
Flash headlights
|
||||
</option>
|
||||
<option
|
||||
value="trunk_close"
|
||||
>
|
||||
Close trunk
|
||||
</option>
|
||||
<option
|
||||
value="alert"
|
||||
>
|
||||
Alert
|
||||
</option>
|
||||
<option
|
||||
value="precondition"
|
||||
>
|
||||
Precondition
|
||||
</option>
|
||||
<option
|
||||
value="california_mode"
|
||||
>
|
||||
California mode
|
||||
</option>
|
||||
<option
|
||||
value="trunk_open"
|
||||
>
|
||||
Open trunk
|
||||
</option>
|
||||
<option
|
||||
value="temp_cabin"
|
||||
>
|
||||
Set cabin temperature(°C)
|
||||
</option>
|
||||
<option
|
||||
value="defrost"
|
||||
>
|
||||
Defrost
|
||||
</option>
|
||||
<option
|
||||
value="driver_seat_preheat"
|
||||
>
|
||||
Driver seat preheat
|
||||
</option>
|
||||
<option
|
||||
value="passenger_seat_preheat"
|
||||
>
|
||||
Preheat passenger seat
|
||||
</option>
|
||||
<option
|
||||
value="steering_wheel_preheat"
|
||||
>
|
||||
Preheat Steering wheel
|
||||
</option>
|
||||
<option
|
||||
value="charging"
|
||||
>
|
||||
Charging
|
||||
</option>
|
||||
</select>
|
||||
<svg
|
||||
aria-hidden="true"
|
||||
class="MuiSvgIcon-root MuiSelect-icon MuiSelect-iconOutlined"
|
||||
focusable="false"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<path
|
||||
d="M7 10l5 5 5-5z"
|
||||
/>
|
||||
</svg>
|
||||
<fieldset
|
||||
aria-hidden="true"
|
||||
class="PrivateNotchedOutline-root-0 MuiOutlinedInput-notchedOutline"
|
||||
style="padding-left: 8px;"
|
||||
>
|
||||
<legend
|
||||
class="PrivateNotchedOutline-legend-0"
|
||||
style="width: 0.01px;"
|
||||
>
|
||||
<span>
|
||||
|
||||
</span>
|
||||
</legend>
|
||||
</fieldset>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-label="send command"
|
||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained makeStyles-submit-0 MuiButton-containedPrimary MuiButton-fullWidth"
|
||||
tabindex="0"
|
||||
type="submit"
|
||||
>
|
||||
<span
|
||||
class="MuiButton-label"
|
||||
>
|
||||
Send
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -119,6 +119,24 @@ exports[`CarStatus Render 1`] = `
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
<button
|
||||
aria-controls="tabpanel-5"
|
||||
aria-selected="false"
|
||||
class="MuiButtonBase-root MuiTab-root MuiTab-textColorInherit"
|
||||
id="tab-5"
|
||||
role="tab"
|
||||
tabindex="-1"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
class="MuiTab-wrapper"
|
||||
>
|
||||
Remote Commands
|
||||
</span>
|
||||
<span
|
||||
class="MuiTouchRipple-root"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<span
|
||||
class="PrivateTabIndicator-root-0 PrivateTabIndicator-colorSecondary-0 MuiTabs-indicator"
|
||||
@@ -228,6 +246,13 @@ exports[`CarStatus Render 1`] = `
|
||||
id="tabpanel-4"
|
||||
role="tabpanel"
|
||||
/>
|
||||
<div
|
||||
aria-labelledby="tab-5"
|
||||
class="makeStyles-fullWidth-0"
|
||||
hidden=""
|
||||
id="tabpanel-5"
|
||||
role="tabpanel"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -12,6 +12,7 @@ import TabPanel from "../../Controls/TabPanel";
|
||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||
import useStyles from "../../useStyles";
|
||||
import CANSignalsTab from "./CANSignalsTab";
|
||||
import RemoteCommandsTab from "./RemoteCommandsTab";
|
||||
|
||||
const tabHashes = ["details", "updates", "filters"];
|
||||
|
||||
@@ -64,6 +65,7 @@ const CarStatus = () => {
|
||||
<Tab label="CAN Filters" {...tabProps(2)} />
|
||||
<Tab label="Digital Twin" {...tabProps(3)} />
|
||||
<Tab label="CAN Signals" {...tabProps(4)} />
|
||||
<Tab label="Remote Commands" {...tabProps(5)} />
|
||||
</Tabs>
|
||||
</Box>
|
||||
|
||||
@@ -86,6 +88,10 @@ const CarStatus = () => {
|
||||
<TabPanel value={tabIndex} index={4} className={classes.fullWidth}>
|
||||
<CANSignalsTab vin={vin} />
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel value={tabIndex} index={5} className={classes.fullWidth}>
|
||||
<RemoteCommandsTab vin={vin} />
|
||||
</TabPanel>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -164,10 +164,10 @@ export const VehicleProvider = ({ children }) => {
|
||||
}
|
||||
};
|
||||
|
||||
const sendCommand = async (vins, command, parameters, token) => {
|
||||
const sendCommand = async (vins, command, token) => {
|
||||
try {
|
||||
setBusy(true);
|
||||
const result = await api.sendCommand(vins, command, parameters, token);
|
||||
const result = await api.sendCommand(vins, command, token);
|
||||
if (result.error)
|
||||
throw new Error(`Send command error. ${result.message}`);
|
||||
return result;
|
||||
|
||||
@@ -317,6 +317,55 @@ describe("VehicleContext", () => {
|
||||
checkBaseResults("", "false");
|
||||
});
|
||||
});
|
||||
|
||||
describe("sendCommand", () => {
|
||||
beforeEach(async () => {
|
||||
const TestComp = () => {
|
||||
const {busy, sendCommand} = useVehicleContext();
|
||||
const { message, setMessage } = useStatusContext();
|
||||
|
||||
const sendC = async (vin, command) => {
|
||||
try {
|
||||
await sendCommand(vin, command);
|
||||
} catch (e) {
|
||||
setMessage(e.message);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div data-testid="error">{message}</div>
|
||||
<div data-testid="busy">{busy.toString()}</div>
|
||||
<button data-testid="sendCommandNullVin" onClick={() => sendC(null, {command:"doors_lock"})} />
|
||||
<button data-testid="sendCommandNonexistentVin" onClick={() => sendC(["11111111111111111"], {command:"doors_lock"})} />
|
||||
<button
|
||||
data-testid="sendCommandVin"
|
||||
onClick={() => sendC("3C4PDCBG0ET127145", {command:"doors_lock"})}
|
||||
/>
|
||||
<button
|
||||
data-testid="sendCommandWrongCommand"
|
||||
onClick={() => sendC("3C4PDCBG0ET127145", {command:"noSuchCommand"})}
|
||||
/>
|
||||
</>
|
||||
);}
|
||||
render(
|
||||
<StatusProvider>
|
||||
<VehicleProvider>
|
||||
<TestComp />
|
||||
</VehicleProvider>
|
||||
</StatusProvider>
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it("initial state", () => {
|
||||
checkBaseResults("", "false");
|
||||
});
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
const expectedFilters = [
|
||||
|
||||
251
src/components/Controls/SendCommand/Commands.jsx
Normal file
251
src/components/Controls/SendCommand/Commands.jsx
Normal file
@@ -0,0 +1,251 @@
|
||||
import {Grid, Slider, Switch} from "@material-ui/core";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
|
||||
const tempMarks = [...[{value: 0, label: "Off"}, {value: 1, label: "On"},],
|
||||
...Array.from({length: 26}, (_, i) => {
|
||||
return {value: i + 2, label: `${i + 15}°C`}
|
||||
})];
|
||||
|
||||
const valuetext = (value) => {
|
||||
return `${value}`;
|
||||
}
|
||||
|
||||
const highMidLowOffMarks = [{
|
||||
value: 3, label: "High",
|
||||
}, {
|
||||
value: 2, label: "Mid",
|
||||
}, {
|
||||
value: 1, label: "Low",
|
||||
}, {
|
||||
value: 0, label: "Off",
|
||||
},]
|
||||
|
||||
const precondMarks = [
|
||||
{value: 0, label: "Battery"},
|
||||
{value: 1, label: "All"},
|
||||
{value: 2, label: "Climate"},
|
||||
{value: 3, label: "Stop"},
|
||||
]
|
||||
|
||||
const labelFunc = (marks) => (value) => marks.find(mark => mark.value === value)?.label;
|
||||
|
||||
const labelRemoved = (marks) => Array.from(marks).map((mark) => {
|
||||
return {value: mark.value}
|
||||
})
|
||||
|
||||
const Commands = [
|
||||
{value: "doors_lock", label: "Lock doors"},
|
||||
{value: "doors_unlock", label: "Unlock doors"},
|
||||
{value: "vent_windows", label: "Vent windows"},
|
||||
{value: "close_windows", label: "Close windows"},
|
||||
{value: "flash_headlights", label: "Flash headlights"},
|
||||
{value: "trunk_close", label: "Close trunk"},
|
||||
{value: "alert", label: "Alert"},
|
||||
{
|
||||
value: "precondition", label: "Precondition", params: {
|
||||
dataFunc: (val, handleValChange) => <span>
|
||||
<Typography id="precondition-slider" gutterBottom>
|
||||
Set driver seat preheat
|
||||
</Typography>
|
||||
<Slider
|
||||
valueLabelFormat={labelFunc(precondMarks)}
|
||||
getAriaValueText={labelFunc(precondMarks)}
|
||||
defaultValue={0}
|
||||
value={val}
|
||||
aria-labelledby="precondition-slider"
|
||||
valueLabelDisplay="auto"
|
||||
step={null}
|
||||
min={0}
|
||||
max={3}
|
||||
marks={precondMarks}
|
||||
size="small"
|
||||
onChange={handleValChange}
|
||||
/>
|
||||
</span>,
|
||||
}
|
||||
},
|
||||
{
|
||||
value: "california_mode", label: "California mode", params: {
|
||||
dataFunc: (val, handleValChange) => {
|
||||
if (typeof val !== "boolean") {
|
||||
val = false
|
||||
}
|
||||
return <span>
|
||||
<Typography id="california_mode-slider" gutterBottom>
|
||||
Set California mode
|
||||
</Typography>
|
||||
<Typography component="div">
|
||||
<Grid component="label" container alignItems="center" spacing={1}>
|
||||
<Grid item>Off</Grid>
|
||||
<Grid item>
|
||||
<Switch checked={val} onChange={handleValChange} name="checkedC"/>
|
||||
</Grid>
|
||||
<Grid item>On</Grid>
|
||||
</Grid>
|
||||
</Typography>
|
||||
</span>
|
||||
},
|
||||
},
|
||||
}, {
|
||||
value: "trunk_open", label: "Open trunk", params: {
|
||||
dataFunc: (val, handleValChange) => <span>
|
||||
<Typography id="trunk_open-slider" gutterBottom>
|
||||
Set trunk's openness level
|
||||
</Typography>
|
||||
<Slider
|
||||
defaultValue={1}
|
||||
value={val}
|
||||
getAriaValueText={valuetext}
|
||||
aria-label="Open trunk"
|
||||
aria-labelledby="trunk_open-slider"
|
||||
valueLabelDisplay="auto"
|
||||
step={1}
|
||||
marks
|
||||
size="small"
|
||||
min={1}
|
||||
max={5}
|
||||
onChange={handleValChange}
|
||||
/>
|
||||
</span>,
|
||||
}
|
||||
}, {
|
||||
value: "temp_cabin", label: "Set cabin temperature(°C)", params: {
|
||||
dataFunc: (val, handleValChange) => {
|
||||
return <span>
|
||||
<Typography id="temp_cabin-slider" gutterBottom>
|
||||
Set cabin temperature
|
||||
</Typography>
|
||||
<Slider
|
||||
valueLabelFormat={labelFunc(tempMarks)}
|
||||
getAriaValueText={labelFunc(tempMarks)}
|
||||
defaultValue={0}
|
||||
value={val}
|
||||
aria-labelledby="temp_cabin-slider"
|
||||
valueLabelDisplay="auto"
|
||||
step={1}
|
||||
min={0}
|
||||
max={27}
|
||||
marks={labelRemoved(tempMarks)}
|
||||
size="small"
|
||||
onChange={handleValChange}
|
||||
/>
|
||||
</span>
|
||||
}
|
||||
}
|
||||
}, {
|
||||
value: "defrost", label: "Defrost", params: {
|
||||
dataFunc: (val, handleValChange) => {
|
||||
if (typeof val !== "boolean") {
|
||||
val = false
|
||||
}
|
||||
return <span>
|
||||
<Typography id="defrost-slider" gutterBottom>
|
||||
Set defrost
|
||||
</Typography>
|
||||
<Typography component="div">
|
||||
<Grid component="label" container alignItems="center" spacing={1}>
|
||||
<Grid item>Off</Grid>
|
||||
<Grid item>
|
||||
<Switch checked={val} onChange={handleValChange} name="checkedC"/>
|
||||
</Grid>
|
||||
<Grid item>On</Grid>
|
||||
</Grid>
|
||||
</Typography>
|
||||
</span>
|
||||
},
|
||||
|
||||
},
|
||||
}, {
|
||||
value: "driver_seat_preheat", label: "Driver seat preheat", params: {
|
||||
dataFunc: (val, handleValChange) => <span>
|
||||
<Typography id="driver_seat_preheat-slider" gutterBottom>
|
||||
Set driver seat preheat
|
||||
</Typography>
|
||||
<Slider
|
||||
valueLabelFormat={labelFunc(highMidLowOffMarks)}
|
||||
getAriaValueText={labelFunc(highMidLowOffMarks)}
|
||||
defaultValue={0}
|
||||
value={val}
|
||||
aria-labelledby="driver_seat_preheat-slider"
|
||||
valueLabelDisplay="auto"
|
||||
step={null}
|
||||
min={0}
|
||||
max={3}
|
||||
marks={highMidLowOffMarks}
|
||||
size="small"
|
||||
onChange={handleValChange}
|
||||
/>
|
||||
</span>,
|
||||
}
|
||||
}, {
|
||||
value: "passenger_seat_preheat", label: "Preheat passenger seat", params: {
|
||||
dataFunc: (val, handleValChange) => <span>
|
||||
<Typography id="passenger_seat_preheat-slider" gutterBottom>
|
||||
Set passenger seat preheat
|
||||
</Typography>
|
||||
<Slider
|
||||
valueLabelFormat={labelFunc(highMidLowOffMarks)}
|
||||
getAriaValueText={labelFunc(highMidLowOffMarks)}
|
||||
defaultValue={0}
|
||||
value={val}
|
||||
aria-labelledby="passenger_seat_preheat-slider"
|
||||
valueLabelDisplay="auto"
|
||||
step={null}
|
||||
min={0}
|
||||
max={3}
|
||||
marks={highMidLowOffMarks}
|
||||
size="small"
|
||||
onChange={handleValChange}
|
||||
/>
|
||||
</span>,
|
||||
}
|
||||
}, {
|
||||
value: "steering_wheel_preheat", label: "Preheat Steering wheel", params: {
|
||||
dataFunc: (val, handleValChange) => {
|
||||
if (typeof val !== "boolean") {
|
||||
val = false
|
||||
}
|
||||
|
||||
return <span>
|
||||
<Typography id="steering_wheel_preheat-slider" gutterBottom>
|
||||
Set steering wheel preheat on/off
|
||||
</Typography>
|
||||
<Typography component="div">
|
||||
<Grid component="label" container alignItems="center" spacing={1}>
|
||||
<Grid item>Off</Grid>
|
||||
<Grid item>
|
||||
<Switch checked={val} onChange={handleValChange} name="checkedC"/>
|
||||
</Grid>
|
||||
<Grid item>On</Grid>
|
||||
</Grid>
|
||||
</Typography>
|
||||
</span>
|
||||
},
|
||||
|
||||
},
|
||||
}, {
|
||||
value: "charging", label: "Charging", params: {
|
||||
dataFunc: (val, handleValChange) => {
|
||||
if (typeof val !== "boolean") {
|
||||
val = false
|
||||
}
|
||||
return <span>
|
||||
<Typography id="charging-slider" gutterBottom>
|
||||
Set charging on/off
|
||||
</Typography>
|
||||
<Typography component="div">
|
||||
<Grid component="label" container alignItems="center" spacing={1}>
|
||||
<Grid item>Off</Grid>
|
||||
<Grid item>
|
||||
<Switch checked={val} onChange={handleValChange} name="checkedC"/>
|
||||
</Grid>
|
||||
<Grid item>On</Grid>
|
||||
</Grid>
|
||||
</Typography>
|
||||
</span>
|
||||
},
|
||||
|
||||
},
|
||||
}]
|
||||
|
||||
export default Commands;
|
||||
73
src/components/Controls/SendCommand/Parameters.jsx
Normal file
73
src/components/Controls/SendCommand/Parameters.jsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import PropTypes from "prop-types";
|
||||
import {FormControl} from "@material-ui/core";
|
||||
import useStyles from "../../useStyles";
|
||||
import {MuiPickersUtilsProvider} from "@material-ui/pickers";
|
||||
import DateFnsUtils from "@date-io/date-fns";
|
||||
|
||||
export const Dates = (
|
||||
{
|
||||
startDateFunc,
|
||||
endDateFunc,
|
||||
startDate,
|
||||
handleStartChange,
|
||||
endDate,
|
||||
handleEndChange,
|
||||
}) => {
|
||||
if (startDateFunc && endDateFunc) {
|
||||
return (<MuiPickersUtilsProvider utils={DateFnsUtils}>
|
||||
{startDateFunc(startDate, handleStartChange)}
|
||||
{endDateFunc(endDate, handleEndChange, startDate)}
|
||||
</MuiPickersUtilsProvider>);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Dates.propTypes = {
|
||||
startDateFunc: PropTypes.func,
|
||||
endDateFunc: PropTypes.func,
|
||||
startDate: PropTypes.instanceOf(Date),
|
||||
handleStartChange: PropTypes.func,
|
||||
endDate: PropTypes.instanceOf(Date),
|
||||
handleEndChange: PropTypes.func
|
||||
}
|
||||
|
||||
export const Parameters = (props) => {
|
||||
const {params} = props;
|
||||
|
||||
const classes = useStyles();
|
||||
|
||||
if (params === null || params === undefined || params === "") {
|
||||
return null;
|
||||
}
|
||||
|
||||
const {data, handleDataChange} = props;
|
||||
const {startDate, handleStartChange} = props;
|
||||
const {endDate, handleEndChange} = props;
|
||||
|
||||
return (
|
||||
<FormControl size="small" className={classes.formControl}>
|
||||
<div style={{width: "300px", marginTop: "1em"}}>
|
||||
{params.dataFunc(data, handleDataChange)}
|
||||
</div>
|
||||
<Dates
|
||||
startDateFunc={params.startDateFunc}
|
||||
endDateFunc={params.endDateFunc}
|
||||
startDate={startDate}
|
||||
handleStartChange={handleStartChange}
|
||||
endDate={endDate}
|
||||
handleEndChange={handleEndChange}
|
||||
></Dates>
|
||||
</FormControl>
|
||||
)
|
||||
}
|
||||
|
||||
Parameters.propTypes = {
|
||||
params: PropTypes.any,
|
||||
data: PropTypes.any,
|
||||
handleDataChange: PropTypes.func,
|
||||
startDate: PropTypes.instanceOf(Date),
|
||||
handleStartChange: PropTypes.func,
|
||||
endDate: PropTypes.instanceOf(Date),
|
||||
handleEndChange: PropTypes.func
|
||||
};
|
||||
140
src/components/Controls/SendCommand/Parameters.test.jsx
Normal file
140
src/components/Controls/SendCommand/Parameters.test.jsx
Normal file
@@ -0,0 +1,140 @@
|
||||
jest.mock("@material-ui/pickers/MuiPickersUtilsProvider")
|
||||
|
||||
|
||||
import {render, waitFor} from "@testing-library/react";
|
||||
import {Dates, Parameters} from "./Parameters";
|
||||
import addSnapshotSerializer from "../../../utils/snapshot";
|
||||
|
||||
|
||||
const date = Date.parse("2011-10-10T14:48:00")
|
||||
|
||||
const renderDates = async (empty) => {
|
||||
if (empty) {
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
const { container } = render(<Dates startDateFunc={null} endDateFunc={null}/>)
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
|
||||
const [start, setStart] = [date, (_) => {}];
|
||||
const [end, setEnd] = [date, (_) => {}];
|
||||
|
||||
const handleStartChange = (val) => {
|
||||
setStart(val)
|
||||
}
|
||||
|
||||
const handleEndChange = (val) => {
|
||||
setEnd(val)
|
||||
}
|
||||
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
|
||||
const {container} = render(
|
||||
<div>
|
||||
<Dates
|
||||
startDateFunc={(val, handleValChange) => <div>{val.toString()}</div>}
|
||||
endDateFunc={(val, handleValChange, prevVal) => <div>{val.toString()}, {prevVal.toString()}</div>}
|
||||
startDate={start}
|
||||
endDate={end}
|
||||
handleStartChange={handleStartChange}
|
||||
handleEndChange={handleEndChange}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
||||
return container
|
||||
}
|
||||
|
||||
describe("Dates", () => {
|
||||
beforeAll(() => {
|
||||
addSnapshotSerializer(expect);
|
||||
})
|
||||
|
||||
it("Render empty", async () => {
|
||||
const container = await renderDates(true);
|
||||
expect(container).toMatchSnapshot();
|
||||
})
|
||||
|
||||
it("Render filled", async () => {
|
||||
const container = await renderDates(false);
|
||||
expect(container).toMatchSnapshot();
|
||||
})
|
||||
})
|
||||
|
||||
const renderState = {
|
||||
EMPTY: 0,
|
||||
JUST_DATA: 1,
|
||||
WITH_DATE: 2,
|
||||
}
|
||||
|
||||
const renderParameters = async (rs) => {
|
||||
await waitFor(() => {
|
||||
/* render */
|
||||
});
|
||||
|
||||
if (rs===renderState.EMPTY) {
|
||||
const { container } = render(<Parameters/>)
|
||||
|
||||
return container;
|
||||
}
|
||||
const params = {dataFunc:(val, handleValChange) => <div>val.toString()</div>}
|
||||
const [data, handleDataChange] = [true, (_)=>{}];
|
||||
|
||||
if (rs===renderState.JUST_DATA) {
|
||||
const { container } = render(<Parameters
|
||||
params={params}
|
||||
data={data}
|
||||
handleDataChange={handleDataChange}
|
||||
/>)
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
params.startDateFunc = (val, handleValChange) => (<div>val.toString()</div>)
|
||||
params.endDateFunc = (val, handleValChange, prevVal) => (<div>val.toString(), prevVal.toString()</div>)
|
||||
|
||||
const [start, handleStartChange] = [date, (_)=>{}];
|
||||
const [end, handleEndChange] = [date, (_)=>{}];
|
||||
|
||||
if (rs===renderState.WITH_DATE) {
|
||||
const {container} = render(<Parameters
|
||||
params={params}
|
||||
data={data}
|
||||
handleDataChange={handleDataChange}
|
||||
start={start}
|
||||
handleStartChange={handleStartChange}
|
||||
end={end}
|
||||
handleEndChange={handleEndChange}
|
||||
/>
|
||||
)
|
||||
|
||||
return container
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
describe("Params", () => {
|
||||
beforeAll(() => {
|
||||
addSnapshotSerializer(expect);
|
||||
})
|
||||
|
||||
it("Render empty", async () => {
|
||||
const container = await renderParameters(renderState.EMPTY);
|
||||
expect(container).toMatchSnapshot();
|
||||
})
|
||||
|
||||
it("Render just data", async () => {
|
||||
const container = await renderDates(renderState.JUST_DATA);
|
||||
expect(container).toMatchSnapshot();
|
||||
})
|
||||
|
||||
it("Render with date", async () => {
|
||||
const container = await renderDates(renderState.WITH_DATE);
|
||||
expect(container).toMatchSnapshot();
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,24 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Dates Render empty 1`] = `<div />`;
|
||||
|
||||
exports[`Dates Render filled 1`] = `
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
1318258080000
|
||||
</div>
|
||||
<div>
|
||||
1318258080000
|
||||
,
|
||||
1318258080000
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Params Render empty 1`] = `<div />`;
|
||||
|
||||
exports[`Params Render just data 1`] = `<div />`;
|
||||
|
||||
exports[`Params Render with date 1`] = `<div />`;
|
||||
@@ -1,31 +1,32 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, {useEffect, useState} from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import { FormControl, IconButton, InputLabel, Select } from "@material-ui/core";
|
||||
import SendIcon from "@material-ui/icons/Send";
|
||||
import {Button, FormControl, InputLabel, Select} from "@material-ui/core";
|
||||
|
||||
import { useVehicleContext } from "../../Contexts/VehicleContext";
|
||||
import commands from "../../../services/commands";
|
||||
import {useVehicleContext} from "../../Contexts/VehicleContext";
|
||||
import commands from "./Commands";
|
||||
import useStyles from "../../useStyles";
|
||||
import { useUserContext } from "../../Contexts/UserContext";
|
||||
import { useStatusContext } from "../../Contexts/StatusContext";
|
||||
import { logger } from "../../../services/monitoring";
|
||||
import {useUserContext} from "../../Contexts/UserContext";
|
||||
import {useStatusContext} from "../../Contexts/StatusContext";
|
||||
import {logger} from "../../../services/monitoring";
|
||||
import {Parameters} from "./Parameters";
|
||||
import clsx from "clsx";
|
||||
import {sanitize} from "./sanitize";
|
||||
|
||||
const SendCommand = ({ vins }) => {
|
||||
const SendCommand = ({vins}) => {
|
||||
const classes = useStyles();
|
||||
const { sendCommand, busy } = useVehicleContext();
|
||||
const {busy, sendCommand} = useVehicleContext();
|
||||
const {
|
||||
token: {
|
||||
idToken: { jwtToken: token },
|
||||
idToken: {jwtToken: token},
|
||||
},
|
||||
} = useUserContext();
|
||||
const NoParameters = {
|
||||
value: "",
|
||||
label: "None",
|
||||
};
|
||||
const { setMessage } = useStatusContext();
|
||||
const {setMessage} = useStatusContext();
|
||||
const [command, setCommand] = useState("");
|
||||
const [parameters, setParameters] = useState([NoParameters]);
|
||||
const [parameter, setParameter] = useState("");
|
||||
const [parameters, setParameters] = useState(null);
|
||||
const [data, setData] = useState(null);
|
||||
const [startTime, setStartTime] = useState(null)
|
||||
const [endTime, setEndTime] = useState(null)
|
||||
|
||||
const changeCommandHandler = (e) => {
|
||||
selectCommand(e.target.value);
|
||||
};
|
||||
@@ -34,16 +35,27 @@ const SendCommand = ({ vins }) => {
|
||||
const params = getParameters(cmd);
|
||||
setCommand(cmd);
|
||||
setParameters(params);
|
||||
setParameter(params[0].value);
|
||||
setData(null);
|
||||
setStartTime(null);
|
||||
setEndTime(null);
|
||||
};
|
||||
|
||||
const changeParametersHandler = (e) => {
|
||||
setParameter(e.target.value);
|
||||
const handleDataChange = (_, value) => {
|
||||
setData(value);
|
||||
};
|
||||
|
||||
const clickHandler = async (e) => {
|
||||
const handleStartChange = (date, _) => {
|
||||
setStartTime(date);
|
||||
}
|
||||
|
||||
const handleEndChange = (date, _) => {
|
||||
setEndTime(date)
|
||||
}
|
||||
|
||||
const clickHandler = async (_) => {
|
||||
try {
|
||||
await sendCommand(vins, command, parameter, token);
|
||||
const cmd = sanitize({command: command, data: data, start: startTime, end: endTime});
|
||||
await sendCommand(vins, cmd, token);
|
||||
if (vins.length === 1) {
|
||||
setMessage(`Sent command to ${vins[0]}`);
|
||||
} else {
|
||||
@@ -59,13 +71,13 @@ const SendCommand = ({ vins }) => {
|
||||
for (let i = 0, len = commands.length; i < len; i += 1) {
|
||||
const item = commands[i];
|
||||
if (item.value === cmd) {
|
||||
if (!item.parameters) {
|
||||
if (!item.params) {
|
||||
break;
|
||||
}
|
||||
return item.parameters;
|
||||
return item.params;
|
||||
}
|
||||
}
|
||||
return [NoParameters];
|
||||
return null;
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -75,9 +87,9 @@ const SendCommand = ({ vins }) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={classes.form} style={{ marginTop: 20 }}>
|
||||
<div className={clsx(classes.paper)} style={{marginTop: 20}}>
|
||||
<FormControl
|
||||
className={classes.formControlInline}
|
||||
className={classes.formControl}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
>
|
||||
@@ -101,45 +113,27 @@ const SendCommand = ({ vins }) => {
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<FormControl
|
||||
className={classes.formControlInline}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
>
|
||||
<InputLabel
|
||||
htmlFor="send-parameter"
|
||||
className={classes.whiteBackground}
|
||||
>
|
||||
Parameter
|
||||
</InputLabel>
|
||||
<Select
|
||||
native
|
||||
value={parameter}
|
||||
variant="outlined"
|
||||
inputProps={{
|
||||
name: "send-parameter",
|
||||
id: "send-parameter",
|
||||
}}
|
||||
onChange={changeParametersHandler}
|
||||
disabled={parameters.length === 0}
|
||||
>
|
||||
{parameters.map((item) => (
|
||||
<option key={item.value} value={item.value}>
|
||||
{item.label}
|
||||
</option>
|
||||
))}
|
||||
</Select>
|
||||
</FormControl>
|
||||
<IconButton
|
||||
color="primary"
|
||||
<Parameters
|
||||
params={parameters}
|
||||
data={data}
|
||||
handleDataChange={handleDataChange}
|
||||
startDate={startTime}
|
||||
handleStartChange={handleStartChange}
|
||||
endDate={endTime}
|
||||
handleEndChange={handleEndChange}
|
||||
/>
|
||||
<Button
|
||||
type="submit"
|
||||
aria-label="send command"
|
||||
component="span"
|
||||
onClick={clickHandler}
|
||||
size="small"
|
||||
disabled={busy || vins.length === 0}
|
||||
fullWidth
|
||||
variant="contained"
|
||||
color="primary"
|
||||
className={classes.submit}
|
||||
onClick={clickHandler}
|
||||
>
|
||||
<SendIcon fontSize="large" />
|
||||
</IconButton>
|
||||
{busy ? "Sending..." : "Send"}
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
115
src/components/Controls/SendCommand/sanitize.js
Normal file
115
src/components/Controls/SendCommand/sanitize.js
Normal file
@@ -0,0 +1,115 @@
|
||||
export const onOff = {
|
||||
false: "off",
|
||||
true: "on",
|
||||
}
|
||||
|
||||
export const onOffTemp = {
|
||||
0: "off",
|
||||
1: "on",
|
||||
...Object.fromEntries(Array.from({length: 26}, (_, i) => [i+2, `${i+15}`]))
|
||||
}
|
||||
|
||||
export const precond = {
|
||||
0: "battery",
|
||||
1: "all",
|
||||
2: "climate",
|
||||
3: "stop"
|
||||
}
|
||||
|
||||
export const hmol = {
|
||||
0: "off",
|
||||
1: "low",
|
||||
2: "mid",
|
||||
3: "high"
|
||||
}
|
||||
|
||||
export const emptyCommands = [
|
||||
"doors_lock", "doors_unlock", "vent_windows",
|
||||
"close_windows", "trunk_close", "flash_headlights",
|
||||
"alert"
|
||||
]
|
||||
|
||||
export const onOffCommands = ["california_mode", "steering_wheel_preheat", "defrost", "charging"]
|
||||
|
||||
export const hmolCommands = ["passenger_seat_preheat", "driver_seat_preheat"]
|
||||
|
||||
|
||||
const removeIfFieldsAreEmpty = (cmd, fields) => {
|
||||
for (const field of fields) {
|
||||
if (cmd[field] == null) {
|
||||
delete cmd[field]
|
||||
}
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
const tempCabinPeriodConvert = (cmd) => {
|
||||
if (cmd.data == null || typeof cmd.data !== "number" || cmd.data < 0 || cmd.data > 27) {
|
||||
cmd.data = 0
|
||||
}
|
||||
|
||||
cmd.data = onOffTemp[cmd.data]
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
const onOffConvert = (cmd) => {
|
||||
if (cmd.data == null || cmd.data !== true) {
|
||||
cmd.data = false
|
||||
}
|
||||
|
||||
cmd.data = onOff[cmd.data]
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
const precondConvert = cmd => {
|
||||
if (cmd.data == null || typeof cmd.data !== "number" || cmd.data < 0 || cmd.data > 3) {
|
||||
cmd.data = 0
|
||||
}
|
||||
|
||||
cmd.data = precond[cmd.data]
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
const hmolConvert = (cmd) => {
|
||||
if (cmd.data == null || typeof cmd.data !== "number" || cmd.data < 0 || cmd.data > 3) {
|
||||
cmd.data = 0
|
||||
}
|
||||
|
||||
cmd.data = hmol[cmd.data]
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
const trunkOpenConvert = (cmd) => {
|
||||
if (cmd.data == null || typeof cmd.data !== "number" || cmd.data < 1 || cmd.data > 5) {
|
||||
cmd.data = 1
|
||||
}
|
||||
|
||||
cmd.data = cmd.data.toString()
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
export const sanitize = (cmd) => {
|
||||
cmd = removeIfFieldsAreEmpty(cmd, ["data", "start", "end"])
|
||||
|
||||
if (onOffCommands.includes(cmd.command)) {
|
||||
cmd = onOffConvert(cmd)
|
||||
} else if (hmolCommands.includes(cmd.command)) {
|
||||
cmd = hmolConvert(cmd)
|
||||
} else if (cmd.command === "precondition") {
|
||||
cmd = precondConvert(cmd)
|
||||
} else if (cmd.command === "trunk_open") {
|
||||
cmd = trunkOpenConvert(cmd)
|
||||
} else if (cmd.command === "temp_cabin") {
|
||||
cmd = tempCabinPeriodConvert(cmd)
|
||||
} else {
|
||||
delete cmd.data;
|
||||
}
|
||||
|
||||
return cmd
|
||||
}
|
||||
150
src/components/Controls/SendCommand/sanitize.test.js
Normal file
150
src/components/Controls/SendCommand/sanitize.test.js
Normal file
@@ -0,0 +1,150 @@
|
||||
import {emptyCommands, hmol, hmolCommands, onOff, onOffCommands, onOffTemp, precond, sanitize,} from "./sanitize";
|
||||
|
||||
const randomValues = [null, undefined, "someString", 33]
|
||||
|
||||
describe("Sanitize test", () => {
|
||||
it("empty commands", () => {
|
||||
for (const command of emptyCommands) {
|
||||
const cmd = sanitize({command})
|
||||
expect(cmd.command).toEqual(command)
|
||||
expect("data" in cmd).toEqual(false);
|
||||
expect("start" in cmd).toEqual(false);
|
||||
expect("end" in cmd).toEqual(false);
|
||||
}
|
||||
})
|
||||
|
||||
it("on-off commands with proper values", () => {
|
||||
for (const command of onOffCommands) {
|
||||
for (const data of [false, true]) {
|
||||
const cmd = sanitize({command, data})
|
||||
|
||||
expect(cmd.data).toEqual(onOff[data])
|
||||
expect(cmd.command).toEqual(command)
|
||||
expect("start" in cmd).toEqual(false);
|
||||
expect("end" in cmd).toEqual(false);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it("on-off commands with dirty values", () => {
|
||||
for (const command of onOffCommands) {
|
||||
const cmd = {command}
|
||||
for (const rVal of randomValues) {
|
||||
if (rVal !== undefined) {
|
||||
cmd.data = rVal
|
||||
}
|
||||
const res = sanitize(cmd)
|
||||
|
||||
expect(res.data).toEqual("off")
|
||||
expect(res.command).toEqual(command)
|
||||
expect("start" in res).toEqual(false);
|
||||
expect("end" in res).toEqual(false);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it("high-mid-low-off with proper values", () => {
|
||||
for (const command of hmolCommands) {
|
||||
for (const data of [0,1,2,3]) {
|
||||
const cmd = sanitize({command, data})
|
||||
|
||||
expect(cmd.data).toEqual(hmol[data])
|
||||
expect(cmd.command).toEqual(command)
|
||||
expect("start" in cmd).toEqual(false);
|
||||
expect("end" in cmd).toEqual(false);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it("precondition with proper values", () => {
|
||||
for (const data of [0,1,2,3]) {
|
||||
const cmd = sanitize({command:"precondition", data})
|
||||
|
||||
expect(cmd.data).toEqual(precond[data])
|
||||
expect(cmd.command).toEqual("precondition")
|
||||
expect("start" in cmd).toEqual(false);
|
||||
expect("end" in cmd).toEqual(false);
|
||||
}
|
||||
})
|
||||
|
||||
it("precondition with wrong values", () => {
|
||||
const cmd = {command:"precondition"}
|
||||
for (const rVal of randomValues) {
|
||||
if (rVal !== undefined) {
|
||||
cmd.data = rVal
|
||||
}
|
||||
const res = sanitize(cmd)
|
||||
|
||||
expect(res.data).toEqual("battery")
|
||||
expect(res.command).toEqual("precondition")
|
||||
expect("start" in res).toEqual(false);
|
||||
expect("end" in res).toEqual(false);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
it("high-mid-low-off with wrong values", () => {
|
||||
for (const command of hmolCommands) {
|
||||
const cmd = {command}
|
||||
for (const rVal of randomValues) {
|
||||
if (rVal !== undefined) {
|
||||
cmd.data = rVal
|
||||
}
|
||||
const res = sanitize(cmd)
|
||||
|
||||
expect(res.data).toEqual("off")
|
||||
expect(res.command).toEqual(command)
|
||||
expect("start" in res).toEqual(false);
|
||||
expect("end" in res).toEqual(false);
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
it("open trunk", () => {
|
||||
const cmd = {command: "trunk_open"}
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
cmd.data = i
|
||||
|
||||
const res = sanitize(cmd)
|
||||
|
||||
expect(res.data).toEqual(i.toString())
|
||||
expect(res.command).toEqual("trunk_open")
|
||||
expect("start" in res).toEqual(false);
|
||||
expect("end" in res).toEqual(false);
|
||||
}
|
||||
})
|
||||
|
||||
it("open trunk with wrong values", () => {
|
||||
const cmd = {command: "trunk_open"}
|
||||
for (const rVal of randomValues) {
|
||||
if (rVal !== undefined) {
|
||||
cmd.data = rVal
|
||||
}
|
||||
const res = sanitize(cmd)
|
||||
|
||||
expect(res.data).toEqual("1")
|
||||
expect(res.command).toEqual("trunk_open")
|
||||
expect("start" in res).toEqual(false);
|
||||
expect("end" in res).toEqual(false);
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
it("cabin temp with period with proper values", () => {
|
||||
for (let i = 0; i <= 27; i++) {
|
||||
const res = sanitize({
|
||||
command: "temp_cabin",
|
||||
data: i,
|
||||
start: new Date(),
|
||||
end: new Date(),
|
||||
})
|
||||
|
||||
|
||||
expect(res.command).toEqual("temp_cabin")
|
||||
expect(res.data).toEqual(onOffTemp[i])
|
||||
expect(typeof res.start).toEqual("object")
|
||||
expect(typeof res.end).toEqual("object")
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -94,11 +94,10 @@ const vehiclesAPI = {
|
||||
data: [2021, 2022],
|
||||
};
|
||||
},
|
||||
sendCommand: async (vin, command, parameters) => {
|
||||
sendCommand: async (vin, command) => {
|
||||
return {
|
||||
vin,
|
||||
command,
|
||||
parameters,
|
||||
};
|
||||
},
|
||||
updateVehicle: async (vin, vehicle) => {
|
||||
|
||||
@@ -1,81 +1,26 @@
|
||||
const Locks = [
|
||||
const Commands = [
|
||||
{value: "doors_lock", label: "Lock doors"},
|
||||
{value: "doors_unlock", label: "Unlock doors"},
|
||||
{value: "vent_windows", label: "Vent windows"},
|
||||
{value: "close_windows", label: "Close windows"},
|
||||
{value: "california_mode", label: "California mode"},
|
||||
{value: "trunk_open", label: "Open trunk"},
|
||||
{value: "trunk_close", label: "Close trunk "},
|
||||
{value: "flash_headlights", label: "Flash headlights"},
|
||||
{value: "alert", label: "Alert"},
|
||||
{value: "temp_cabin", label: "Set cabin temperature"},
|
||||
{
|
||||
value: "right_front",
|
||||
label: "Front right door",
|
||||
},{
|
||||
value: "left_front",
|
||||
label: "Front left door",
|
||||
},{
|
||||
value: "right_rear",
|
||||
label: "Rear right door",
|
||||
},{
|
||||
value: "left_rear",
|
||||
label: "Rear left door",
|
||||
},{
|
||||
value: "trunk",
|
||||
label: "Trunk",
|
||||
value: "temp_cabin",
|
||||
label: "Set cabin temperature for period",
|
||||
params: {
|
||||
data: ""
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const Windows = [
|
||||
{
|
||||
value: "right_front",
|
||||
label: "Front right window",
|
||||
},{
|
||||
value: "left_front",
|
||||
label: "Front left window",
|
||||
},{
|
||||
value: "right_rear",
|
||||
label: "Rear right window",
|
||||
},{
|
||||
value: "left_rear",
|
||||
label: "Rear left window",
|
||||
},
|
||||
];
|
||||
|
||||
const Commands = [{
|
||||
value: "lock",
|
||||
label: "Lock door",
|
||||
parameters: Locks,
|
||||
},
|
||||
{
|
||||
value: "unlock",
|
||||
label: "Unlock door",
|
||||
parameters: Locks,
|
||||
},{
|
||||
value: "open",
|
||||
label: "Open window",
|
||||
parameters: Windows,
|
||||
},
|
||||
{
|
||||
value: "close",
|
||||
label: "Close window",
|
||||
parameters: Windows,
|
||||
},
|
||||
{
|
||||
value: "ecu",
|
||||
label: "ECU Versions",
|
||||
},
|
||||
{
|
||||
value: "log",
|
||||
label: "Log level",
|
||||
parameters: [
|
||||
{
|
||||
value: "info",
|
||||
label: "Info",
|
||||
},
|
||||
{
|
||||
value: "debug",
|
||||
label: "Debug",
|
||||
},
|
||||
{
|
||||
value: "trace",
|
||||
label: "Trace",
|
||||
},
|
||||
],
|
||||
},{
|
||||
value: "headlights",
|
||||
label: "Flash headlights",
|
||||
}];
|
||||
{value: "defrost", label: "Defrost"},
|
||||
{value: "driver_seat_preheat", label: "Driver seat preheat"},
|
||||
{value: "passenger_seat_preheat", label: "Preheat passenger seat"},
|
||||
{value: "steering_wheel_preheat", label: "Preheat Steering wheel"},
|
||||
{value: "precondition", label: "Precondition"},
|
||||
{value: "charging", label: "Charging"}]
|
||||
|
||||
export default Commands;
|
||||
|
||||
@@ -125,7 +125,7 @@ const vehiclesAPI = {
|
||||
.then(fetchRespHandler)
|
||||
.catch(errorHandler),
|
||||
|
||||
sendCommand: async (vins, command, parameters, token) =>
|
||||
sendCommand: async (vins, command, token) =>
|
||||
fetch(`${API_ENDPOINT}/vehiclecommand`, {
|
||||
method: "POST",
|
||||
headers: Object.assign(
|
||||
@@ -134,8 +134,7 @@ const vehiclesAPI = {
|
||||
),
|
||||
body: JSON.stringify({
|
||||
vins,
|
||||
command,
|
||||
parameters,
|
||||
...command,
|
||||
}),
|
||||
})
|
||||
.then(fetchRespHandler)
|
||||
|
||||
Reference in New Issue
Block a user