En un momento, mi amigo y yo pensamos, ¿por qué no intentar hacer nuestro propio dispositivo IoT doméstico? Sin pensarlo dos veces, nos decidimos por el concepto de un dispositivo que le permite rastrear a los intrusos y alertar al anfitrión. ¿Cómo se puede hacer esto y qué se requiere para esto?
Después de un tiempo, quedó claro que la Raspberry pi, acompañada de un sensor de movimiento y una cámara, debería ser adecuada para nuestra tarea. Escribiremos un controlador para ello, colgaremos varios servicios diferentes en un servidor remoto, crearemos una aplicación móvil y se logrará el objetivo. Suena bastante bien, es hora de intentarlo.
Para empezar, ordenamos:
Frambuesa misma
Módulo de cámara
módulo detector de movimiento con sensor piroeléctrico IR
cables de conexión
El pedido no incluía una fuente de alimentación: un cargador de teléfono móvil de 5 V / 1 A es completamente adecuado como reemplazo. Como resultado, obtuvimos este tipo de dispositivo:
Arquitectura del sistema de IoT
El siguiente paso fue diseñar la arquitectura:
, , , . Java Python.
«»( «», , ). «» - ( Postgres) . «» Java.
3 :
Rest API (Java)
Auth (Node.JS) -
Notification (Node.JS) - push-
, , . React Native.
: JS-, , Auth Notification . . ( ).
Auth service
JWT-. .
:
const router = require('express').Router();
const {loggedIn, adminOnly} = require("../helpers/auth.middleware");
const userController = require('../controllers/user.controller');
//
router.post('/register', userController.register);
//
router.post('/login', userController.login);
//
router.get('/auth', loggedIn, (req, res) => res.send(true));
//
router.get('/adminonly', loggedIn, adminOnly, userController.adminonly);
module.exports = router;
- bcryptjs .
exports.register = async (req, res) => {
//
const salt = await bcrypt.genSalt(10);
const hasPassword = await bcrypt.hash(req.body.password, salt);
//
const user = new User({
mobile: req.body.mobile,
email: req.body.email,
username: req.body.username,
password: hasPassword,
status: req.body.status || 1
});
//
try {
const id = await User.create(user);
user.id = id;
delete user.password;
res.send(user);
}
catch (err){
res.status(500).send({error: err.message});
}
};
:
exports.login = async (req, res) => {
try {
//
const user = await User.login(req.body.username);
if (user) {
const validPass = await bcrypt.compare(req.body.password, user.password);
if (!validPass) return res.status(400).send({error: "Password is wrong"});
//
const token = jwt.sign({id: user.id, user_type_id: user.user_type_id}, config.TOKEN_SECRET,{ expiresIn: config.EXPIRATION});
res.header("auth-token", token).send({"token": token, user: user.username});
}
}
catch (err) {
if( err instanceof NotFoundError ) {
res.status(401).send({error: err.message});
}
else {
const error_data = {
entity: 'User',
model_obj: {param: req.params, body: req.body},
error_obj: err,
error_msg: err.message
};
res.status(500).send(error_data);
}
}
};
:
exports.loggedIn = function (req, res, next) {
let token = req.header('Authorization');
if (!token) return res.status(401).send("Access Denied");
try {
//
if (token.startsWith('Bearer ')) {
token = token.slice(7, token.length).trimLeft();
}
// ,
const verified = jwt.verify(token, config.TOKEN_SECRET);
req.user = verified;
next();
}
catch (err) {
res.status(400).send("Invalid Token");
}
}
:
( , , )
(/)
,
. front-end , React, React Native. , Expo. «» «»:
Expo:
;
( QR- ) - .apk .ipa;
(Push-, Asset Manager,...).
:
, Java / Objective-C;
- , .
«» «», , Expo , . . , , , . detach, , , . , , Expo.
, , React . !
state- MobX - observable .
HTTP axios, superagent . , :
import superagentPromise from 'superagent-promise';
import _superagent from 'superagent';
import Auth from './auth';
import Alarms from './alarms';
import Notification from './notification';
import Devices from './devices';
import commonStore from "../store/commonStore";
import authStore from "../store/authStore";
import getEnvVars from "../environment";
const superagent = superagentPromise(_superagent, global.Promise);
const {apiRoot: API_ROOT} = getEnvVars();
const handleErrors = (err: any) => {
if (err && err.response && err.response.status === 401) {
authStore.logout();
}
return err;
};
const responseBody = (res: any) => res.body;
//
const tokenPlugin = (req: any) => {
if (commonStore.token) {
req.set('authorization', `Token ${commonStore.token}`);
}
};
export interface RequestsAgent {
del: (url: string) => any;
get: (url: string) => any;
put: (url: string, body: object) => any;
post: (url: string, body: object, root?: string) => any;
}
const requests: RequestsAgent = {
del: (url: string) =>
superagent
.del(`${API_ROOT}${url}`)
.use(tokenPlugin)
.end(handleErrors)
.then(responseBody),
get: (url: string) =>
superagent
.get(`${API_ROOT}${url}`)
.use(tokenPlugin)
.end(handleErrors)
.then(responseBody),
put: (url: string, body: object) =>
superagent
.put(`${API_ROOT}${url}`, body)
.use(tokenPlugin)
.end(handleErrors)
.then(responseBody),
post: (url: string, body: object, root?: string) =>
superagent
.post(`${root ? root : API_ROOT}${url}`, body)
.use(tokenPlugin)
.end(handleErrors)
.then(responseBody),
};
export default {
Auth: Auth(requests),
Alarms: Alarms(requests),
Notification: Notification(requests),
Devices: Devices(requests)
};
api auth.ts:
import {RequestsAgent} from "./index";
import getEnvVars from "../environment";
const {apiAuth} = getEnvVars();
export default (requests: RequestsAgent) => {
return {
login: (username: string, password: string) =>
requests.post('/api/users/login', {username, password}, apiAuth),
register: (username: string, email: string, password: string) =>
requests.post('/api/users/register', { user: { username, email, password } }),
};
}
. authStore:
@action
register(): any {
this.inProgress = true;
this.errors = null;
return agent.Auth.register(this.values.username, this.values.email, this.values.password)
.then(({ user }) => commonStore.setToken(user.token))
.then(() => userStore.pullUser())
.catch(action((err) => {
this.errors = err.response && err.response.body && err.response.body.errors;
throw err;
}))
.finally(action(() => { this.inProgress = false; }));
}
, React Native, LocalStorage, AsyncStorage. token . AsyncStorage , :
const token = await AsyncStorage.getItem('token');
Expo BottomTabNavigator. - :
const BottomTab = createBottomTabNavigator<BottomTabParamList>();
export default function BottomTabNavigator() {
const colorScheme = useColorScheme();
return (
<BottomTab.Navigator
tabBarOptions={{activeTintColor: Colors[colorScheme].tint}}>
<BottomTab.Screen
name=""
component={DeviceNavigator}
options={{
tabBarIcon: ({color}) => <TabBarIcon name="calculator-outline" color={color}></TabBarIcon>,
}}
/>
<BottomTab.Screen
name=""
component={AlarmsNavigator}
options={{
tabBarIcon: ({color}) => <NotificationBadge color={color}/>,
}}
/>
</BottomTab.Navigator>
);
}
- DeviceNavigator:
const TabThreeStack = createStackNavigator<TabThreeParamList>();
function DeviceNavigator() {
const navigation = useNavigation();
const {colors} = useTheme();
return (
<TabThreeStack.Navigator>
<TabThreeStack.Screen
name="DeviceScreen"
component={DevicesScreen}
options={{
headerTitle: '',
headerRight: () => <Ionicons color={colors.primary} onPress={() => navigation.navigate('DeviceScreenAdd')} name={"add-circle-outline"}/>
}}
/>
<TabThreeStack.Screen
name="AddDeviceScreen"
component={AddDeviceScreen}
options={{
headerTitle: ' '
}}
/>
<TabThreeStack.Screen
name="DeviceInfoScreen"
component={DeviceInfoScreen}
options={{
headerTitle: ' '
}}
/>
</TabThreeStack.Navigator>
);
}
react- . :
expo-video-player. , uri . , Content-range. :
Notification service
push- . push . :
client.query('LISTEN new_alarm_event');
client.on('notification', async (data) => {
writeToAll(data.payload)
});
expo :
const writeToAll = async msg => {
const tokensArray = Array.from(tokensSet);
if (tokensArray.length > 0) {
const messages = tokensArray.map(token => ({
to: token,
sound: 'default',
body: msg,
data: { msg },
}))
// ,
let chunks = expo.chunkPushNotifications(messages);
(async () => {
for (let chunk of chunks) {
try {
// Expo
const receipts = await expo.sendPushNotificationsAsync(chunk);
console.log(receipts);
} catch (error) {
console.error(error);
}
}
})();
}
else {
console.log(`cant write, ${tokensArray.length} users`)
}
return tokensArray.length
}
:
const registerForPushNotifications = async () => {
const { status } = await Permissions.askAsync(Permissions.NOTIFICATIONS);
if (status !== 'granted') {
alert('No notification permissions!');
return;
}
//
let token = await Notifications.getExpoPushTokenAsync();
// notification service
await sendPushNotification(token);
}
export default registerForPushNotifications;
IoT-. . - , ( ).
, , JS frontend , , backend, . .
.
!