Appareil IoT domestique à travers les yeux d'un développeur JS

À un moment donné, mon ami et moi avons pensé, pourquoi ne pas essayer de fabriquer notre propre appareil IoT domestique? Sans réfléchir à deux fois, nous nous sommes arrêtés sur le concept d'un appareil qui permet de suivre les intrus et d'alerter l'hôte. Comment cela peut-il être fait et que faut-il pour cela?





Après un certain temps, il est devenu clair que le Raspberry pi, accompagné d'un capteur de mouvement et d'une caméra, devrait être adapté à notre tâche. Nous allons écrire un pilote pour cela, raccrocher plusieurs services différents sur un serveur distant, créer une application mobile et l'objectif sera atteint. Ça sonne plutôt bien, il est temps d'essayer.





Pour commencer, nous avons commandé:





  • Framboise elle-même





  • module de caméra





  • module de détection de mouvement avec capteur pyroélectrique IR





  • fils de connexion





La commande n'incluait pas d'alimentation électrique - un chargeur de téléphone portable 5V / 1A est tout à fait approprié en remplacement. En conséquence, nous avons ce type d'appareil:





Architecture du système IoT

L'étape suivante consistait à concevoir l'architecture:





, , , . 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