Irgendwann dachten mein Freund und ich, warum nicht versuchen, unser eigenes IoT-HeimgerĂ€t herzustellen? Ohne nachzudenken, haben wir uns fĂŒr das Konzept eines GerĂ€ts entschieden, mit dem Sie Eindringlinge verfolgen und den Host alarmieren können. Wie kann das gemacht werden und was ist dafĂŒr erforderlich?
Nach einiger Zeit wurde klar, dass der Raspberry Pi, begleitet von einem Bewegungssensor und einer Kamera, fĂŒr unsere Aufgabe geeignet sein sollte. Wir werden einen Treiber dafĂŒr schreiben, mehrere verschiedene Dienste auf einem Remote-Server aufhĂ€ngen, eine mobile Anwendung erstellen und das Ziel wird erreicht. Klingt ziemlich gut, es ist Zeit zu versuchen.
ZunÀchst bestellten wir:
Himbeere selbst
Kameramodul
Bewegungsmeldermodul mit pyroelektrischem IR-Sensor
VerbindungsdrÀhte
Die Bestellung enthielt kein Netzteil - ein 5V / 1A-Handy-LadegerÀt ist als Ersatz vollstÀndig geeignet. Als Ergebnis haben wir diese Art von GerÀt:
IoT-Systemarchitektur
Der nÀchste Schritt war die Gestaltung der Architektur:
, , , . 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. ljȉ ljȉ:
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, . .
.
!