Dispositivo de IoT doméstico a través de los ojos de un desarrollador de JS

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});
    }
};
      
      



:





jsonwebtoken:





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");
    }
}
      
      



:









  1. ( , , )





  2. (/)





  3. ,





. front-end , React, React Native. , Expo. «‎»‎ «‎»‎:







Expo:





  1. ;





  2. ( QR- ) - .apk .ipa;





  3. (Push-, Asset Manager,...).





:





  1. , Java / Objective-C;





  2. - , .





«‎»‎ «‎», , 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, . .





.





!








All Articles