Compare commits
	
		
			16 Commits
		
	
	
		
			ace77028c4
			...
			master
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| a1dc088a23 | |||
| d4a19060e8 | |||
| fe9f7e81e9 | |||
| 6e07c2be85 | |||
| 03db7575fd | |||
| df7470f147 | |||
| 17a3436017 | |||
| 4bdcd49e1f | |||
| 031c1ede21 | |||
| 08e5c2e3d2 | |||
| 2c80c4b279 | |||
| 66d688291c | |||
| 49430114fe | |||
| cfd0056197 | |||
| facd8d912f | |||
| b743cbc9a5 | 
@@ -7,6 +7,7 @@
 | 
			
		||||
    "@testing-library/jest-dom": "^4.2.4",
 | 
			
		||||
    "@testing-library/react": "^9.3.2",
 | 
			
		||||
    "@testing-library/user-event": "^7.1.2",
 | 
			
		||||
    "axios": "^0.21.1",
 | 
			
		||||
    "grommet": "^2.16.2",
 | 
			
		||||
    "grommet-icons": "^4.5.0",
 | 
			
		||||
    "polished": "^4.0.5",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										27
									
								
								src/Core.js
									
									
									
									
									
								
							
							
						
						
									
										27
									
								
								src/Core.js
									
									
									
									
									
								
							@@ -1,20 +1,28 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { useSelector, useDispatch } from "react-redux";
 | 
			
		||||
import { Box, Button, Clock } from "grommet";
 | 
			
		||||
import { Box, Button, Clock, Text } from "grommet";
 | 
			
		||||
 | 
			
		||||
import { setPlugin, selectCore } from "./coreSlice";
 | 
			
		||||
import { selectVoice } from "./voiceSlice";
 | 
			
		||||
import VoiceBars from "./components/VoiceBars";
 | 
			
		||||
import plugins from "./plugins";
 | 
			
		||||
 | 
			
		||||
function Core() {
 | 
			
		||||
	const coreState = useSelector(selectCore);
 | 
			
		||||
	const voiceState = useSelector(selectVoice);
 | 
			
		||||
	const dispatch = useDispatch();
 | 
			
		||||
	const plugin = plugins[coreState.plugin];
 | 
			
		||||
	const props = {
 | 
			
		||||
		data: coreState.data,
 | 
			
		||||
		close: () => dispatch(setPlugin(false))
 | 
			
		||||
	};
 | 
			
		||||
	const dummyButtons = ["Maps", "Home Devices", "Phone"];
 | 
			
		||||
	return <>
 | 
			
		||||
		<Box height="5vh" background="light-1" justify="center" direction="row">
 | 
			
		||||
			{voiceState.recording ? <VoiceBars style={{height: "100%"}}/>:
 | 
			
		||||
				voiceState.text === null ? null:
 | 
			
		||||
					<Text style={{paddingTop: "1em"}}>{voiceState.text}</Text>
 | 
			
		||||
			}
 | 
			
		||||
		</Box>
 | 
			
		||||
		{plugin ? React.createElement(plugin, props, null) : (
 | 
			
		||||
			<Box
 | 
			
		||||
				align="center"
 | 
			
		||||
@@ -23,16 +31,19 @@ function Core() {
 | 
			
		||||
				justify="center"
 | 
			
		||||
				direction="column"
 | 
			
		||||
				alignContent="center"
 | 
			
		||||
				height="100vh"
 | 
			
		||||
				height="95vh"
 | 
			
		||||
			>
 | 
			
		||||
				<Clock size="xxlarge" />
 | 
			
		||||
				<Clock size="xxlarge" type="digital" />
 | 
			
		||||
				<Box direction="row" margin="1.5em" wrap={true}>
 | 
			
		||||
					<Button onClick={() => dispatch(setPlugin("sideMirrors"))} primary
 | 
			
		||||
						label="Side Mirrors" />
 | 
			
		||||
					{dummyButtons.map(i =>
 | 
			
		||||
						<Button primary label={i} key={i} margin="0 0 0 1em" />
 | 
			
		||||
					)}
 | 
			
		||||
					<Button onClick={() => dispatch(setPlugin("petMode"))} primary
 | 
			
		||||
						label="Pet Mode" />
 | 
			
		||||
					<Button onClick={() => dispatch(setPlugin("smartHome"))} primary
 | 
			
		||||
						label="Smart Home" margin="0 0 0 1em" />
 | 
			
		||||
					<Button onClick={() => dispatch(setPlugin("maps"))} primary
 | 
			
		||||
						label="Maps" margin="0 0 0 1em" />
 | 
			
		||||
					<Button onClick={() => dispatch(setPlugin("marketplace"))} primary
 | 
			
		||||
						label="Marketplace" margin="0 0 0 1em" />
 | 
			
		||||
				</Box>
 | 
			
		||||
			</Box>)
 | 
			
		||||
		}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,6 +4,7 @@ import io from "socket.io-client";
 | 
			
		||||
import { WS_BASE } from "./config";
 | 
			
		||||
import { useDispatch } from "react-redux";
 | 
			
		||||
import { setData, setPlugin } from "./coreSlice";
 | 
			
		||||
import { setRecording, setText } from "./voiceSlice";
 | 
			
		||||
 | 
			
		||||
const WebSocketContext = createContext(null);
 | 
			
		||||
 | 
			
		||||
@@ -26,6 +27,20 @@ function WebSocketProvider({ children }) {
 | 
			
		||||
				}, payload.time);
 | 
			
		||||
			}
 | 
			
		||||
		});
 | 
			
		||||
 | 
			
		||||
		socket.on("voice:wakeword", () => {
 | 
			
		||||
			dispatch(setRecording(true));
 | 
			
		||||
			dispatch(setText(null));
 | 
			
		||||
		});
 | 
			
		||||
		socket.on("voice:record_end", () => {
 | 
			
		||||
			dispatch(setRecording(false));
 | 
			
		||||
			dispatch(setText(null));
 | 
			
		||||
		});
 | 
			
		||||
		socket.on("voice:utterance", payload => {
 | 
			
		||||
			dispatch(setRecording(false));
 | 
			
		||||
			dispatch(setText(payload.text));
 | 
			
		||||
			setTimeout(() => dispatch(setText(null)), 3000);
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return (
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,10 @@
 | 
			
		||||
import { configureStore } from "@reduxjs/toolkit";
 | 
			
		||||
import coreReducer from "../coreSlice";
 | 
			
		||||
import voiceReducer from "../voiceSlice";
 | 
			
		||||
 | 
			
		||||
export default configureStore({
 | 
			
		||||
	reducer: {
 | 
			
		||||
		core: coreReducer,
 | 
			
		||||
		voice: voiceReducer,
 | 
			
		||||
	},
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										79
									
								
								src/components/VoiceBars.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/components/VoiceBars.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,79 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
 | 
			
		||||
function VoiceBars() {
 | 
			
		||||
	return (
 | 
			
		||||
		<div className="waves" style={{transform: "rotate(180deg)", marginTop: "130px", transition: "1s"}}>
 | 
			
		||||
			<svg width="100vw" fill="none" version="1.1"
 | 
			
		||||
				xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
				<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%">
 | 
			
		||||
					<stop offset="0%" stopColor="#00B4DB" />
 | 
			
		||||
					<stop offset="50%" stopColor="#224488" />
 | 
			
		||||
					<stop offset="100%" stopColor="#0083B0" />
 | 
			
		||||
				</linearGradient>
 | 
			
		||||
				<path
 | 
			
		||||
					fill="url(#grad1)"
 | 
			
		||||
					d="
 | 
			
		||||
					M0 67
 | 
			
		||||
					C 273,183
 | 
			
		||||
					822,-40
 | 
			
		||||
					1920.00,106
 | 
			
		||||
 | 
			
		||||
					V 359
 | 
			
		||||
					H 0
 | 
			
		||||
					V 67
 | 
			
		||||
					Z">
 | 
			
		||||
					<animate
 | 
			
		||||
						repeatCount="indefinite"
 | 
			
		||||
						fill="url(#grad1)"
 | 
			
		||||
						attributeName="d"
 | 
			
		||||
						dur="5s"
 | 
			
		||||
						attributeType="XML"
 | 
			
		||||
						values="
 | 
			
		||||
						M0 77
 | 
			
		||||
						C 473,283
 | 
			
		||||
						822,-40
 | 
			
		||||
						1920,116
 | 
			
		||||
 | 
			
		||||
						V 359
 | 
			
		||||
						H 0
 | 
			
		||||
						V 67
 | 
			
		||||
						Z;
 | 
			
		||||
 | 
			
		||||
						M0 77
 | 
			
		||||
						C 473,-40
 | 
			
		||||
						1222,283
 | 
			
		||||
						1920,136
 | 
			
		||||
 | 
			
		||||
						V 359
 | 
			
		||||
						H 0
 | 
			
		||||
						V 67
 | 
			
		||||
						Z;
 | 
			
		||||
 | 
			
		||||
						M0 77
 | 
			
		||||
						C 973,260
 | 
			
		||||
						1722,-53
 | 
			
		||||
						1920,120
 | 
			
		||||
 | 
			
		||||
						V 359
 | 
			
		||||
						H 0
 | 
			
		||||
						V 67
 | 
			
		||||
						Z;
 | 
			
		||||
 | 
			
		||||
						M0 77
 | 
			
		||||
						C 473,283
 | 
			
		||||
						822,-40
 | 
			
		||||
						1920,116
 | 
			
		||||
 | 
			
		||||
						V 359
 | 
			
		||||
						H 0
 | 
			
		||||
						V 67
 | 
			
		||||
						Z
 | 
			
		||||
						">
 | 
			
		||||
					</animate>
 | 
			
		||||
				</path>
 | 
			
		||||
			</svg>
 | 
			
		||||
		</div>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default VoiceBars;
 | 
			
		||||
@@ -3,7 +3,26 @@ import PropTypes from "prop-types";
 | 
			
		||||
import { Box, Button, Heading, Text } from "grommet";
 | 
			
		||||
import { Aid, Impact } from "grommet-icons";
 | 
			
		||||
 | 
			
		||||
import Telegram from "../utils/telegram";
 | 
			
		||||
import { TG_API, TG_USERID } from "../config";
 | 
			
		||||
 | 
			
		||||
function sendNotification(close) {
 | 
			
		||||
	if (window.sendingAccidentAlert)
 | 
			
		||||
		return;
 | 
			
		||||
	window.sendingAccidentAlert = true;
 | 
			
		||||
	const bot = new Telegram(TG_API);
 | 
			
		||||
	bot.sendMessage(TG_USERID, "User detected in an accident. Location has been attached below.");
 | 
			
		||||
	navigator.geolocation.getCurrentPosition(
 | 
			
		||||
		position => {
 | 
			
		||||
			window.sendingAccidentAlert = false;
 | 
			
		||||
			bot.sendLocation( TG_USERID,
 | 
			
		||||
				position.coords.latitude, position.coords.longitude);
 | 
			
		||||
		},
 | 
			
		||||
		() => {
 | 
			
		||||
			window.sendingAccidentAlert = false;
 | 
			
		||||
			bot.sendMessage(TG_USERID, "Error retrieving location");
 | 
			
		||||
		}
 | 
			
		||||
	);
 | 
			
		||||
	setTimeout(close, 2000);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@@ -37,12 +56,12 @@ function Accident(props) {
 | 
			
		||||
			justify="center"
 | 
			
		||||
			direction="column"
 | 
			
		||||
			alignContent="center"
 | 
			
		||||
			height="100vh"
 | 
			
		||||
			height="95vh"
 | 
			
		||||
		>
 | 
			
		||||
			{
 | 
			
		||||
				time === 0 ?
 | 
			
		||||
					<Aid color="plain" size="xlarge" />
 | 
			
		||||
					:<Impact color="#D0C100" size="xlarge" />
 | 
			
		||||
					:<Impact color="status-critical" size="xlarge" />
 | 
			
		||||
			}
 | 
			
		||||
			<Heading>{
 | 
			
		||||
				time === 0 ?
 | 
			
		||||
 
 | 
			
		||||
@@ -11,11 +11,14 @@ function GenericPageWithIcon(props) {
 | 
			
		||||
			justify="center"
 | 
			
		||||
			direction="column"
 | 
			
		||||
			alignContent="center"
 | 
			
		||||
			height="100vh"
 | 
			
		||||
			height="95vh"
 | 
			
		||||
			style={{
 | 
			
		||||
				padding: "0 3em",
 | 
			
		||||
			}}
 | 
			
		||||
		>
 | 
			
		||||
			{props.icon}
 | 
			
		||||
			<Heading>{props.title}</Heading>
 | 
			
		||||
			<Text>{props.description}</Text>
 | 
			
		||||
			<Text style={{ textAlign: "center" }}>{props.description}</Text>
 | 
			
		||||
			<Button primary size="large" onClick={props.close}
 | 
			
		||||
				label="Dismiss" margin="1.5em" />
 | 
			
		||||
		</Box>
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										53
									
								
								src/plugins/Maps.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								src/plugins/Maps.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
import React, { useState, useEffect } from "react";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import { Box, Button } from "grommet";
 | 
			
		||||
 | 
			
		||||
import { MAPS_API } from "../config";
 | 
			
		||||
 | 
			
		||||
function Maps(props) {
 | 
			
		||||
	const [ location, setLocation ] = useState([null, null]);
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		navigator.geolocation.getCurrentPosition(
 | 
			
		||||
			position => setLocation([position.coords.latitude, position.coords.longitude]),
 | 
			
		||||
			() => {}
 | 
			
		||||
		);
 | 
			
		||||
	});
 | 
			
		||||
 | 
			
		||||
	const mapsEmbedUrl = new URL("https://www.google.com/maps/embed/v1/view");
 | 
			
		||||
	mapsEmbedUrl.searchParams.append("key", MAPS_API);
 | 
			
		||||
	mapsEmbedUrl.searchParams.append("center", `${location[0]},${location[1]}`);
 | 
			
		||||
	mapsEmbedUrl.searchParams.append("zoom", 14);
 | 
			
		||||
	return (
 | 
			
		||||
		<Box
 | 
			
		||||
			align="center"
 | 
			
		||||
			background="light-1"
 | 
			
		||||
			width="100vw"
 | 
			
		||||
			justify="center"
 | 
			
		||||
			direction="column"
 | 
			
		||||
			alignContent="center"
 | 
			
		||||
			height="95vh"
 | 
			
		||||
		>
 | 
			
		||||
			{location[0] === null ?
 | 
			
		||||
				"Loading Location":
 | 
			
		||||
				<iframe
 | 
			
		||||
					width="650"
 | 
			
		||||
					height="300"
 | 
			
		||||
					frameBorder="0"
 | 
			
		||||
					style={{ border: 0 }}
 | 
			
		||||
					src={mapsEmbedUrl.href}>
 | 
			
		||||
				</iframe>
 | 
			
		||||
			}
 | 
			
		||||
			<Button primary size="large" onClick={props.close}
 | 
			
		||||
				label="Dismiss" margin="1.5em" />
 | 
			
		||||
		</Box>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Maps.propTypes = {
 | 
			
		||||
	data: PropTypes.object,
 | 
			
		||||
	close: PropTypes.func
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Maps.pluginName = "Maps";
 | 
			
		||||
 | 
			
		||||
export default Maps;
 | 
			
		||||
							
								
								
									
										67
									
								
								src/plugins/Marketplace.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/plugins/Marketplace.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,67 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import { Box, Button, Card, CardBody, CardFooter, CardHeader, Heading } from "grommet";
 | 
			
		||||
import { InstallOption, Trash } from "grommet-icons";
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
function Marketplace(props) {
 | 
			
		||||
	const plugins = [
 | 
			
		||||
		{
 | 
			
		||||
			name: "Maps",
 | 
			
		||||
			description: "Google Maps is a mapping service. It offers satellite imagery, aerial photography, street maps, 360° interactive panoramic views of streets, real-time traffic conditions, and route planning for traveling.",
 | 
			
		||||
			installed: true
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Smart Home",
 | 
			
		||||
			description: "Smart Home Plugin lets you integrate with various well known smart devices allowing you to stream video from various devices.",
 | 
			
		||||
			installed: true
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "Spotify",
 | 
			
		||||
			description: "With Spotify, you have access to a world of music. You can listen to artists and albums, or create your own playlist of your favourite songs.",
 | 
			
		||||
			installed: false
 | 
			
		||||
		},
 | 
			
		||||
	];
 | 
			
		||||
	return (
 | 
			
		||||
		<Box
 | 
			
		||||
			align="center"
 | 
			
		||||
			background="light-1"
 | 
			
		||||
			width="100vw"
 | 
			
		||||
			justify="center"
 | 
			
		||||
			direction="column"
 | 
			
		||||
			alignContent="center"
 | 
			
		||||
			height="95vh"
 | 
			
		||||
		>
 | 
			
		||||
			<Box wrap="wrap" direction="row">
 | 
			
		||||
				{
 | 
			
		||||
					plugins.map(i =>
 | 
			
		||||
						<Card key={i.name} style={{margin: "1em", width: "30%"}}>
 | 
			
		||||
							<CardHeader pad="medium"><Heading>{i.name}</Heading></CardHeader>
 | 
			
		||||
							<CardBody pad="medium">{i.description}</CardBody>
 | 
			
		||||
							<CardFooter pad="medium" justify="center">
 | 
			
		||||
								<Button
 | 
			
		||||
									label={i.installed ? "Uninstall": "Install"}
 | 
			
		||||
									icon={i.installed ?
 | 
			
		||||
										<Trash color="status-error" /> :
 | 
			
		||||
										<InstallOption color="status-ok" />
 | 
			
		||||
									}
 | 
			
		||||
								/>
 | 
			
		||||
							</CardFooter>
 | 
			
		||||
						</Card>
 | 
			
		||||
					)
 | 
			
		||||
				}
 | 
			
		||||
			</Box>
 | 
			
		||||
			<Button primary size="large" onClick={props.close}
 | 
			
		||||
				label="Dismiss" margin="1.5em" />
 | 
			
		||||
		</Box>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Marketplace.propTypes = {
 | 
			
		||||
	data: PropTypes.object,
 | 
			
		||||
	close: PropTypes.func
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
Marketplace.pluginName = "Marketplace";
 | 
			
		||||
 | 
			
		||||
export default Marketplace;
 | 
			
		||||
							
								
								
									
										59
									
								
								src/plugins/PetMode.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								src/plugins/PetMode.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,59 @@
 | 
			
		||||
import React, { useState, useEffect } from "react";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import GenericPageWithIcon from "./GenericPageWithIcon";
 | 
			
		||||
import { Sun } from "grommet-icons";
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
 | 
			
		||||
import Telegram from "../utils/telegram";
 | 
			
		||||
import { CAR_API, TG_API, TG_USERID  } from "../config";
 | 
			
		||||
 | 
			
		||||
function sendNotification(temp) {
 | 
			
		||||
	if (window.sendingPetAlert)
 | 
			
		||||
		return;
 | 
			
		||||
	window.sendingPetAlert = true;
 | 
			
		||||
	const bot = new Telegram(TG_API);
 | 
			
		||||
	bot.sendMessage(TG_USERID, `The temperature in the car is ${temp}°C. Please check your child/pet.`);
 | 
			
		||||
	window.sendingPetAlert = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function PetMode(props) {
 | 
			
		||||
	const [ temp, setTemp ] = useState(null);
 | 
			
		||||
	const [ , setNotified ] = useState(false);
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		axios.get(`${CAR_API}data/InsideTemperature`)
 | 
			
		||||
			.then(resp => setTemp(resp.data.value));
 | 
			
		||||
		const id = setInterval(
 | 
			
		||||
			() => axios.get(`${CAR_API}data/InsideTemperature`)
 | 
			
		||||
				.then(resp => {
 | 
			
		||||
					setTemp(resp.data.value);
 | 
			
		||||
					if (resp.data.value > 35 || resp.data.value < 5) {
 | 
			
		||||
						setNotified(notified => {
 | 
			
		||||
							if (notified)
 | 
			
		||||
								return true;
 | 
			
		||||
							sendNotification(resp.data.value);
 | 
			
		||||
							return true;
 | 
			
		||||
						});
 | 
			
		||||
					}
 | 
			
		||||
				}),
 | 
			
		||||
			1000
 | 
			
		||||
		);
 | 
			
		||||
		return () => clearInterval(id);
 | 
			
		||||
	}, []);
 | 
			
		||||
	if (temp === null)
 | 
			
		||||
		return <></>;
 | 
			
		||||
	return (
 | 
			
		||||
		<GenericPageWithIcon
 | 
			
		||||
			title={temp === null ? "Fetching temperature": `${temp}°C`}
 | 
			
		||||
			description={`The temperature inside is ${temp}°C. The pet / child is ${temp > 35 ? "un": ""}safe.`}
 | 
			
		||||
			icon={<Sun color={temp > 35 ? "status-critical": "plain"} size="xlarge" />}
 | 
			
		||||
			close={props.close}
 | 
			
		||||
		/>);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PetMode.propTypes = {
 | 
			
		||||
	close: PropTypes.func
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
PetMode.pluginName = "Temperature";
 | 
			
		||||
 | 
			
		||||
export default PetMode;
 | 
			
		||||
							
								
								
									
										50
									
								
								src/plugins/SmartHome.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/plugins/SmartHome.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
			
		||||
import React, {useState, useEffect} from "react";
 | 
			
		||||
import PropTypes from "prop-types";
 | 
			
		||||
import { Box, Button } from "grommet";
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
 | 
			
		||||
import { CAMERA_URL, LIGHTS_URL } from "../config";
 | 
			
		||||
 | 
			
		||||
function SmartHome(props) {
 | 
			
		||||
	const [ idx, setIdx ] = useState(0);
 | 
			
		||||
	useEffect(() => {
 | 
			
		||||
		const id = setInterval(() => setIdx(i => i + 1), 20);
 | 
			
		||||
		return () => clearInterval(id);
 | 
			
		||||
	}, []);
 | 
			
		||||
	function turnOn() {
 | 
			
		||||
		axios.get(`${LIGHTS_URL}/on`);
 | 
			
		||||
	}
 | 
			
		||||
	function turnOff() {
 | 
			
		||||
		axios.get(`${LIGHTS_URL}/off`);
 | 
			
		||||
	}
 | 
			
		||||
	return (
 | 
			
		||||
		<Box
 | 
			
		||||
			align="center"
 | 
			
		||||
			background="light-1"
 | 
			
		||||
			width="100vw"
 | 
			
		||||
			justify="center"
 | 
			
		||||
			direction="column"
 | 
			
		||||
			alignContent="center"
 | 
			
		||||
			height="95vh"
 | 
			
		||||
		>
 | 
			
		||||
			<img src={`${CAMERA_URL}?id=${idx}`} />
 | 
			
		||||
			<Box direction="row" margin="1em 0">
 | 
			
		||||
				<Button primary size="large" onClick={turnOn}
 | 
			
		||||
					label="Turn On Lights" margin="0 0 0 0" />
 | 
			
		||||
				<Button primary size="large" onClick={turnOff}
 | 
			
		||||
					label="Turn Off Lights" margin="0 0 0 1em" />
 | 
			
		||||
			</Box>
 | 
			
		||||
			<Button primary size="large" onClick={props.close}
 | 
			
		||||
				label="Dismiss" margin="0" />
 | 
			
		||||
		</Box>
 | 
			
		||||
	);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
SmartHome.propTypes = {
 | 
			
		||||
	data: PropTypes.object,
 | 
			
		||||
	close: PropTypes.func
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
SmartHome.pluginName = "Smart Home";
 | 
			
		||||
 | 
			
		||||
export default SmartHome;
 | 
			
		||||
@@ -8,7 +8,7 @@ function Warning(props) {
 | 
			
		||||
		<GenericPageWithIcon
 | 
			
		||||
			title={props.data.title}
 | 
			
		||||
			description={props.data.description}
 | 
			
		||||
			icon={<Alert color="#D0C100" size="xlarge" />}
 | 
			
		||||
			icon={<Alert color="status-critical" size="xlarge" />}
 | 
			
		||||
			close={props.close}
 | 
			
		||||
		/>);
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -2,10 +2,18 @@ import Warning from "./Warning";
 | 
			
		||||
import Manual from "./Manual";
 | 
			
		||||
import Temperature from "./Temperature";
 | 
			
		||||
import Accident from "./Accident";
 | 
			
		||||
import PetMode from "./PetMode";
 | 
			
		||||
import SmartHome from "./SmartHome";
 | 
			
		||||
import Maps from "./Maps";
 | 
			
		||||
import Marketplace from "./Marketplace";
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
	warning: Warning,
 | 
			
		||||
	manual: Manual,
 | 
			
		||||
	temperature: Temperature,
 | 
			
		||||
	accident: Accident,
 | 
			
		||||
	petMode: PetMode,
 | 
			
		||||
	smartHome: SmartHome,
 | 
			
		||||
	maps: Maps,
 | 
			
		||||
	marketplace: Marketplace,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -1,3 +1,9 @@
 | 
			
		||||
const WS_BASE = "http://localhost:5050/";
 | 
			
		||||
const TG_API = "xxxxxxxxx:xxxxxxxxxxxxxxxxxxxxxx";
 | 
			
		||||
const TG_USERID = 123456789;
 | 
			
		||||
const CAR_API = "http://localhost:5000/";
 | 
			
		||||
const CAMERA_URL = "http://path.to/still/image.jpg";
 | 
			
		||||
const MAPS_API = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX";
 | 
			
		||||
const LIGHTS_URL = "http://192.168.1.50:5000/";
 | 
			
		||||
 | 
			
		||||
export { WS_BASE };
 | 
			
		||||
export { WS_BASE, TG_API, TG_USERID, CAR_API, CAMERA_URL, MAPS_API, LIGHTS_URL };
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										0
									
								
								src/utils/index.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								src/utils/index.js
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										19
									
								
								src/utils/telegram.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/utils/telegram.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
			
		||||
import axios from "axios";
 | 
			
		||||
 | 
			
		||||
class Telegram {
 | 
			
		||||
	constructor(token) {
 | 
			
		||||
		this.axios = axios.create({
 | 
			
		||||
			baseURL: `https://api.telegram.org/bot${token}`
 | 
			
		||||
		});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sendMessage(chat_id, text) {
 | 
			
		||||
		this.axios.post("/sendMessage", {chat_id, text});
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	sendLocation(chat_id, latitude, longitude) {
 | 
			
		||||
		this.axios.post("/sendLocation", {chat_id, latitude, longitude});
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Telegram;
 | 
			
		||||
							
								
								
									
										17
									
								
								src/voiceSlice.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								src/voiceSlice.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
import { createSlice } from "@reduxjs/toolkit";
 | 
			
		||||
 | 
			
		||||
export const voiceSlice = createSlice({
 | 
			
		||||
	name: "voice",
 | 
			
		||||
	initialState: {
 | 
			
		||||
		recording: false,
 | 
			
		||||
		text: null
 | 
			
		||||
	},
 | 
			
		||||
	reducers: {
 | 
			
		||||
		setRecording: (state, action) => ({...state, recording: action.payload}),
 | 
			
		||||
		setText: (state, action) => ({...state, text: action.payload}),
 | 
			
		||||
	}
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
export const { setRecording, setText } = voiceSlice.actions;
 | 
			
		||||
export const selectVoice = state => state.voice;
 | 
			
		||||
export default voiceSlice.reducer;
 | 
			
		||||
@@ -2590,6 +2590,13 @@ axe-core@^4.0.2:
 | 
			
		||||
  resolved "https://registry.npmjs.org/axe-core/-/axe-core-4.1.1.tgz#70a7855888e287f7add66002211a423937063eaf"
 | 
			
		||||
  integrity sha512-5Kgy8Cz6LPC9DJcNb3yjAXTu3XihQgEdnIg50c//zOC/MyLP0Clg+Y8Sh9ZjjnvBrDZU4DgXS9C3T9r4/scGZQ==
 | 
			
		||||
 | 
			
		||||
axios@^0.21.1:
 | 
			
		||||
  version "0.21.1"
 | 
			
		||||
  resolved "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
 | 
			
		||||
  integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
 | 
			
		||||
  dependencies:
 | 
			
		||||
    follow-redirects "^1.10.0"
 | 
			
		||||
 | 
			
		||||
axobject-query@^2.2.0:
 | 
			
		||||
  version "2.2.0"
 | 
			
		||||
  resolved "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz#943d47e10c0b704aa42275e20edf3722648989be"
 | 
			
		||||
@@ -5204,7 +5211,7 @@ flush-write-stream@^1.0.0:
 | 
			
		||||
    inherits "^2.0.3"
 | 
			
		||||
    readable-stream "^2.3.6"
 | 
			
		||||
 | 
			
		||||
follow-redirects@^1.0.0:
 | 
			
		||||
follow-redirects@^1.0.0, follow-redirects@^1.10.0:
 | 
			
		||||
  version "1.13.1"
 | 
			
		||||
  resolved "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7"
 | 
			
		||||
  integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user