לאחרונה, השתתפתי בהאקתון, הקמנו קבוצה ובנינו ב-24 שעות מערכת קטנה אבל מרשימה.
הרעיון היה, לבנות POC של מערכת שמדגימה הגנה ע"י smart card על מכשירי IOT (אם משהו לא מובן אפשר לשאול בתגובות).
הרעיון היה, לבנות POC של מערכת שמדגימה הגנה ע"י smart card על מכשירי IOT (אם משהו לא מובן אפשר לשאול בתגובות).
לאחר שעות רבות של עבודה ואינספור באגים, הצלחנו להעמיד מערכת שמדגימה את זה בצורה ממש יפה ומרשימה. אני הייתי אחראי על פיתוח ה-client וה-server ולכן אתמקד בשני הנושאים האלו.
רק ניתן ספויילר שהמאמץ השתלם וזכינו במקום השני 😏.
Client
בצד של הקליינט כתבתי אפליקציית ionic ששולחת http post לשרת עם צמד של key ו-value.
התחלתי את הפיתוח עם template שנקרא sidemenu ע"י הפקודה:
התחלתי את הפיתוח עם template שנקרא sidemenu ע"י הפקודה:
ionic start myApp sidemenu
הפקודה הזו יוצרת פרויקט שמוכן לשימוש ויש בו שתי ספריות בתוך ספריית \src\pages\. ספריית home וספריית list.
ספריית home אחראית על המסך הראשון ועליה בעיקר עבדתי.
את הקובץ home.html שיניתי לקוד הבא:
home.html
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | <ion-header> <ion-navbar> <button ion-button menuToggle> <ion-icon name="menu"></ion-icon> </button> <ion-title>Home</ion-title> </ion-navbar></ion-header><ion-content> <ion-list> <ion-item (click)="doorClicked()"> <ion-thumbnail item-start> <img src="assets/icon/door.jpg"> </ion-thumbnail> <h2>Door</h2> <p *ngIf="doorStatus==1">OPEN</p> <p *ngIf="doorStatus==0">CLOSE</p> </ion-item> <ion-item (click)="cameraClicked()"> <ion-thumbnail item-start> <img src="assets/icon/camera.jpg"> </ion-thumbnail> <h2>Camera</h2> <p *ngIf="cameraStatus==1">ON</p> <p *ngIf="cameraStatus==0">OFF</p> </ion-item> <ion-item (click)="safeboxClicked()"> <ion-thumbnail item-start> <img src="assets/icon/safebox.jpg"> </ion-thumbnail> <h2>Safe Box</h2> <p *ngIf="safeboxStatus==1">OPEN</p> <p *ngIf="safeboxStatus==0">CLOSE</p> </ion-item> <ion-item (click)="device4Clicked()"> <ion-thumbnail item-start> <img src="assets/icon/device4.jpg"> </ion-thumbnail> <h2>Device 4</h2> <p *ngIf="device4Status==1">ON</p> <p *ngIf="device4Status==0">OFF</p> </ion-item> </ion-list> </ion-content> |
בקוד הזה יצרתי רשימה של 4 מכשירים שעליהם אני רוצה לשלוט. לכל אחד הוספתי תמונה קטנה, כותרת, ומצב (יכול להיות OPEN/CLOSE או ON/OFF). לחיצה על כל אחד מהמכשירים מפעילה פונקציה התואמת לאותו המכשיר.
להלן ה-GUI של האפליקציה:
להלן ה-GUI של האפליקציה:
את המימוש של הפונקציות ושל כל הלוגיקה כתבתי בקובץ home.ts.
home.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | import { Component } from '@angular/core'; import { NavController } from 'ionic-angular'; import { HTTP } from '@ionic-native/http'; import { Http, Headers, RequestOptions } from '@angular/http'; import 'rxjs/add/operator/catch'; import 'rxjs/add/operator/toPromise'; import { Injectable } from '@angular/core'; @Component({ selector: 'page-home', templateUrl: 'home.html' }) @Injectable() export class HomePage { public doorStatus = 0; public cameraStatus = 0; public safeboxStatus = 0; public device4Status = 0; constructor(public navCtrl: NavController, private http: Http) { } doorClicked(){ let status = 'a0'; let headers = new Headers( { 'Content-Type' : 'application/json' }); let options = new RequestOptions({ headers: headers }); if(this.doorStatus == 0){ this.doorStatus = 1; status = 'a1'; } else{ this.doorStatus = 0; status = 'a0'; } let data = JSON.stringify({ t: status }); return new Promise((resolve, reject) => { this.http.post('http://localhost:3000/', data, options) .toPromise() .then((response) => { console.log('API Response : ', response.json()); resolve(response.json()); }) .catch((error) => { console.error('API Error : '); }); }); } cameraClicked(){ let status = 'b0'; let headers = new Headers( { 'Content-Type' : 'application/json' }); let options = new RequestOptions({ headers: headers }); if(this.cameraStatus == 0){ this.cameraStatus = 1; status = 'b1'; } else{ this.cameraStatus = 0; status = 'b0'; } let data = JSON.stringify({ t: status }); return new Promise((resolve, reject) => { this.http.post('http://localhost:3000/', data, options) .toPromise() .then((response) => { console.log('API Response : ', response.json()); resolve(response.json()); }) .catch((error) => { console.error('API Error : '); }); }); } safeboxClicked(){ let status = 'c0'; let headers = new Headers( { 'Content-Type' : 'application/json' }); let options = new RequestOptions({ headers: headers }); if(this.safeboxStatus == 0){ this.safeboxStatus = 1; status = 'c1'; } else{ this.safeboxStatus = 0; status = 'c0'; } let data = JSON.stringify({ t: status }); return new Promise((resolve, reject) => { this.http.post('http://localhost:3000/', data, options) .toPromise() .then((response) => { console.log('API Response : ', response.json()); resolve(response.json()); }) .catch((error) => { console.error('API Error : '); }); }); } device4Clicked(){ let status = 'd0'; let headers = new Headers( { 'Content-Type' : 'application/json' }); let options = new RequestOptions({ headers: headers }); if(this.device4Status == 0){ this.device4Status = 1; status = 'd1'; } else{ this.device4Status = 0; status = 'd0'; } let data = JSON.stringify({ t: status }); return new Promise((resolve, reject) => { this.http.post('http://localhost:3000/', data, options) .toPromise() .then((response) => { console.log('API Response : ', response.json()); resolve(response.json()); }) .catch((error) => { console.error('API Error : '); }); }); } } |
בתחילת הקוד יש לכל מכשיר משתנה ששומר את המצב של אותו מכשיר. למשל doorStatus קובע אם הדלת פתוחה (1) או סגורה (0).
לאחר מכן יש מימוש של כל ארבעת הפונקציות שראינו בקובץ ה-HTML. כל פונקציה עושה את אותה הלוגיקה למכשיר אחר.
הלוגיקה מאוד פשוטה, בלחיצה על הכפתור המצב של מכשיר משתנה מ-0 ל-1 (או מ-1 ל-0). בנוסף נשלח http post שמכיל צמד של key ו-value. במקרה הזה ה-key הוא תמיד t. וה-value משתנה לפי המכשיר והמצב שלו.
עבור המכשיר הראשון השתמשתי ב-a עבור השני ב-b וכן הלאה. כדי לשלוח את מצב המכשיר השתמשתי ב-0 ו-1.
כך ש-a1 אומר שהמכשיר הראשון דולק ו-a0 אומר שהוא כבוי. b0 אומר שהמכשיר השני כבוי, וכן הלאה.
אני יודע שאפשר לקצר את הקוד וליפות אותו, אבל בהאקתון אין הרבה זמן, אז לא מבזבזים זמן על קוד יפה וקצר.
שימו לב שה-ip שבו השתמשתי הוא localhost. זה נכון כשמריצים את הלקוח והשרת על אותו מחשב. אם רוצים שהשרת יהיה במחשב אחר צריך לשנות את ה-localhost ל-ip האמיתי של השרת ולשים לב שאנחנו באותה הרשת.
שימו לב שה-ip שבו השתמשתי הוא localhost. זה נכון כשמריצים את הלקוח והשרת על אותו מחשב. אם רוצים שהשרת יהיה במחשב אחר צריך לשנות את ה-localhost ל-ip האמיתי של השרת ולשים לב שאנחנו באותה הרשת.
קובץ נוסף שקצת נגעתי בו הוא app.module.ts.
app.module.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | import { BrowserModule } from '@angular/platform-browser'; import { ErrorHandler, NgModule } from '@angular/core'; import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular'; import { MyApp } from './app.component'; import { HomePage } from '../pages/home/home'; import { ListPage } from '../pages/list/list'; import { StatusBar } from '@ionic-native/status-bar'; import { SplashScreen } from '@ionic-native/splash-screen'; import { HTTP } from '@ionic-native/http'; import {HttpModule} from '@angular/http'; @NgModule({ declarations: [ MyApp, HomePage, ListPage ], imports: [ BrowserModule, IonicModule.forRoot(MyApp), HttpModule ], bootstrap: [IonicApp], entryComponents: [ MyApp, HomePage, ListPage ], providers: [ StatusBar, SplashScreen, HTTP, {provide: ErrorHandler, useClass: IonicErrorHandler} ] }) export class AppModule {} |
מה שהוספתי מסומן בצהוב. אלו הם כמה הוספות קטנות לצורך תמיכה ב-http.
Server
את השרת כתבתי ב-node תוך שימוש ב-express.
כדי שהכל יעבוד כמו שצריך הייתי צריך להתקין כמה חבילות:
npm install express --save npm install cors
ההתקנה של cors נועדה לאפשר לשרת לקבל קריאות http מדומיין אחר. ניתן לקרוא על זה בהרחבה כאן.
לקובץ קראתי listenerServer.js.
לקובץ קראתי listenerServer.js.
listenerServer.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | var express = require('express'); var app = express(); var date = new Date(); var current_hour = date.getHours(); var cors = require('cors'); var fs = require('fs'); var bodyParser = require('body-parser'); app.use(cors()); app.listen(3000, function () { console.log('Example app listening on port 3000!'); }); app.use(bodyParser.urlencoded({extended: true})); app.use(bodyParser.json()); app.post('/', function (req, res) { var t = req.param('t'); console.log(req.body); console.log("post arrived "+ t + " from post "); res.send( t + " from post " + date ); fs.appendFile('IotCommands.txt', 't = '+t+"\n", function (err) { if (err) throw err; console.log('Saved!'); }); }); |
כפי שניתן לראות, השרת פשוט מאוד. הוא מאזין על פורט 3000. ברגע שהוא מקבל http post הוא מחלץ את הפרמטר t שנשלח אליו, מדפיס הודעה קצרה בקונסול, שולח response, והדבר החשוב לענייננו - שומר בקובץ שנקרא IotCommands.txt את המחרוזת של מה שהתקבל. לדוגמה:
t = a1
t = b0
כדי להריץ את השרת צריך פשוט לכתוב ב-cmd:
node listenerServer.js
במערכת שלנו רצה על השרת תוכנית שדוגמת את הקובץ כל שניה ולפי מה שכתוב בקובץ המערכת הדליקה וכיבתה נורות.
אני חושב שהקוד הזה יכול להוות בסיס לכל מיני יישומים של לקוח ושרת כשהלקוח הוא אפליקציה, והשרת נכתב בפשטות ומהירות.
מקווה שעזרתי למישהו.
בהצלחה!
אין תגובות:
הוסף רשומת תגובה