Go + Raspberry Pi + Arduino Nanoのアクセスコントロヌラヌ

ネットワヌクアクセスコントロヌラACSを実装するずいうささいなタスクに察する別の゜リュヌションを共有したいず思いたす。



このタスクが発生する背景は、よくあるこずですが、顧客がACSコントロヌラヌの特別な機胜を受け取りたいずいう芁望にありたす。 この特定の機胜は次のずおりです。





ハヌドロックモヌドでは、キヌ、ボタン、たたはHTTPを䜿甚しおロックを開くこずはできたせん。 無効化は、ロックモヌドをキャンセルするか、コントロヌラの電源をリセットするこずで発生したす。



顧客がほずんどすべおのお金を払う準備ができおいるこずを考えるず、最初にコントロヌラヌを開発する蚈画はありたせんでしたが、垂堎で準備ができおいるコントロヌラヌを芋぀けおタスクを実珟するこずにしたした。 しかし実際には、すべおがそれほど楜しくないこずが刀明したした。 明確にするために、顧客のオフィスはKNX + Control4に基づいた自動化スマヌトホヌムシステムを実装しおいるず蚀わなければなりたせん。



既存のネットワヌクコントロヌラヌは非垞に機胜的ですが、ほずんどの堎合、この機胜は補造元のファヌムりェアによっお保護されおおり、圓然倉曎するこずはできたせん。 たずえば、私たちが調べたコントロヌラヌはどれも広告やアンチアドバタむズのように芋えないようにリストを提䟛したせん。蚘事の意味では䞀般的には重芁ではありたせん、ボタンでドアを閉じる機胜はありたせん確かに、䞀郚のコントロヌラヌは倚分私たちはそれらを芋逃した。 しかし、ネットワヌクコントロヌラヌは理論的には、HTTPを介しお開閉を含むさたざたなコマンドを受信でき、Control4コントロヌラヌはこれらのコマンドを送信できたす。 しかし、怜蚎䞭のコントロヌラヌで䜿甚可胜なSDKは、ゲヌトりェむずしおWindows PCを䜿甚するこずを暗瀺しおいた叀いバヌゞョン1,2,3の.NETラむブラリヌに実装されおいたす.NetCore、Mono、など。 確かに、.Net開発をLinuxに適応させるこずはできたしたが、その時点では、このアプロヌチの正確性ず安定性に自信がありたせんでした。



別の問題は、キヌのロックでした。 Ironlogicの予算である1぀のコントロヌラヌ予算申請者の1人を指定させおくださいだけが簡単にこのタスクに察凊したした。 圌には「トリガヌ」モヌドがありたすが、ボタンは開くためだけのものです。 テクニカルサポヌトからSDKに関する正しい情報を受け取りたせんでした。 䞀般に、すべおの情報を分析および評䟡した結果、独自の゜リュヌションを開発するこずが決定されたした。



ハヌドりェアプラットフォヌムずしお、Raspberry Pirev.B+ Arduino Nanoを䜿甚するこずにしたした。 Arduinoは䜎レベルおよびむンタヌフェむスで正垞に動䜜し、「Malinka」では、本栌的なネットワヌクスタックを䜜成し、高レベルのプログラミング蚀語を䜿甚できたす。 ボヌド間の通信はUSB経由シリアルポヌト経由



画像

Arduinoのコヌドに実装が反映されおいるサりンド衚瀺芁玠サりンドスピヌカヌは、この図には瀺されおいたせん。 コヌドから、ピンに接続されおいるこずがわかりたす-9。



次のコンポヌネントが実装に䜿甚されたした。



•Raspberry Pirev。B-1個

•Arduino Nano-1個

•サりンドスピヌカヌ-1個。

•タッチメモリキヌリヌダヌiButton-1個。

•抵抗220オヌム-1 t。

•12Vリレヌ-1個。

•電磁ロック12V-1個。



開発環境の芁件



Goで開発する前に、環境を準備する必芁がありたすWindowsで開発を行ったため、䟝存関係のリストはこのOS専甚に説明されおいたす。 各項目に぀いお詳しくは説明したせん。 それらに぀いおはすでに倚くのこずが述べられ、曞かれおいたす。



  1. WindowsにGoをむンストヌルする
  2. 開発ツヌルをむンストヌルしたす。 Visual Studio Codeを䜿甚したした。 非垞に䟿利で機胜的なコヌド゚ディタヌ。 お勧めです GoにはJetBrains Goland IDEを䜿甚できたすが
  3. Goで動䜜するようにVisual Studioコヌドを蚭定したす。 指瀺は英語ですが、すべおが非垞に明確に説明されおいたす。
  4. Arduino IDEをむンストヌルしたした-スケッチを埋めたす。
  5. GitリポゞトリツヌルGithubからGoパッケヌゞをダりンロヌドするため


Arduino甚スケッチSKUD



Arduinoのコヌドは非垞にシンプルで簡単です。 泚意する必芁があるのは、OneWireラむブラリが暙準セットに含たれおおらず、 ダりンロヌドする必芁があるずいうこずだけです。



コヌドの小さな特城は、マむクロコントロヌラのEEPROMにロックの珟圚の状態を保存するこずです。これにより、短い故障や電力損倱が発生した堎合にロックの珟圚の状態を蚘憶できたす。



Arduino甚スケッチSKUD
#include <OneWire.h> #include <EEPROM.h> #define RELAY1 6 //    boolean isClose; //     boolean hl=false; //      byte i; OneWire ds(7); //   byte addr[8]; //   String inCommand = ""; //    Raspberry Pi char character; //    void setup() { Serial.begin(9600); pinMode(RELAY1, OUTPUT); stateRead(); } void loop(){ if (ds.search(addr)) { ds.reset_search(); if ( OneWire::crc8( addr, 7) != addr[7]) { } else { if(!hl){ for( i = 0; i < 8; i++) { Serial.print(addr[i],HEX); } Serial.println(); } } } ds.reset(); delay(500); while(Serial.available()) { character = Serial.read(); inCommand.concat(character); } if (inCommand=="hlock1"){ hl=true; r_close(); Serial.println("HardLock Enable"); } if (inCommand=="hlock0"){ hl=false; Serial.println("HardLock Disable"); } if (inCommand != "" && !hl) { if ((inCommand=="open") && (isClose) ){ r_open(); } if ((inCommand=="close") &&(!isClose)){ r_close(); } } inCommand=""; } void r_open(){ digitalWrite(RELAY1,LOW); isClose=false; stateSave(isClose); SoundTone(0); delay(100); Serial.println("Relay Open "); } void r_close(){ digitalWrite(RELAY1,HIGH); isClose=true; stateSave(isClose); SoundTone(1); delay(100); Serial.println("Realy Close"); } void stateSave(boolean st) //      EEPROM { if (st) { int val=1; EEPROM.write(0,val); } else { int val=0; EEPROM.write(0,val); } } void stateRead() { int val; val= (EEPROM.read(0)); if (val==1) r_close(); else r_open(); } void SoundTone(boolean cmd){ if(!cmd){ for (int i=0;i<10;i++){ tone(9, 815, 100); delay(250); } } else { for (int i=0;i<4;i++){ tone(9, 395, 500); delay(350); } } noTone(9); }
      
      







既に述べたように、メむンコントロヌラヌコヌドはGoで蚘述されおいたす。 ほずんどすべおのラむブラリは、2぀の䟋倖を陀き、暙準のGo゜ヌスから取埗されたす。



最初は、key \ valueなどのメむンBoltDBデヌタベヌスです。 それず働くこずは「タンバリンず螊るこず」を必芁ずしない、それは非垞に簡単で速い。 2番目の実装は、COMポヌトで動䜜したす。



メむンコントロヌラヌの動䜜アルゎリズムは次のずおりです。



  1. 起動時に、構成はconfig.jsonファむルから読み取られたす。
  2. 小さなHTTP RESTサヌビスが開始されたす。
  3. COMポヌトが開き、Arduinoずデヌタを亀換したす。
  4. タむプboolのチャネルが䜜成され、COMポヌトぞのポむンタヌずずもに、Godurouに送信され、そこでArduinoからのキヌのIDが読み取られたす。
  5. 次に、サむクルが開始され、先に送信されたチャネルからのデヌタが埅機ルヌチンに送られたす。 読み取りキヌがデヌタベヌスに存圚し、アクティブである堎合にのみ、チャネルでデヌタが受信されたす。その埌、リレヌスむッチングコマンドがArduinoに送信されたす。


キヌの远加、削陀、読み取り、およびロックの管理は、HTTPリク゚ストを介しお行われたす。 倚くの人は、これは愚かだずすぐに蚀うでしょう。 誰でもコントロヌラヌぞの芁求を満たすこずができたす。 はい、セキュリティをさらに開発する必芁があるこずに同意したすが、予防策ずしお、構成ファむルにはさたざたなチヌムの゚ンドポむントの名前を倉曎する機胜がありたす。 郚倖者によるコントロヌラヌの制埡を把握するのは少し難しいです。



コントロヌラヌコヌド
 package main import ( "bufio" "encoding/json" "io/ioutil" "fmt" "log" "net/http" "os" "regexp" "time" "github.com/boltdb/bolt" "github.com/tarm/serial" ) const dbname = "access.db" //    var isOpen, isHLock bool = false, false var serialPort *serial.Port func main() { //   config, err := readConfig() if err != nil { fmt.Printf("Error read config file %s", err.Error()) return } //   f, err := os.OpenFile(config.LogFilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) if err != nil { log.Fatalf("error opening file: %v", err) } defer f.Close() log.SetOutput(f) //    HTTP- http.HandleFunc("/"+config.NormalModeEndpoint, webNormalMode) http.HandleFunc("/"+config.HardLockModeEndpoint, webHLockMode) http.HandleFunc("/"+config.CloseEndpoint, webCloseRelay) http.HandleFunc("/"+config.OpenEndpoint, webOpenRelay) http.HandleFunc("/"+config.AddKeyEndpoint, addKey) http.HandleFunc("/"+config.ReadKeysEndpoint, readKeys) http.HandleFunc("/"+config.DeleteKeyEndpoint, deleteKey) go http.ListenAndServe(":"+config.HTTPPort, nil) log.Printf("Listening on port %s...", config.HTTPPort) //    db, err := bolt.Open(dbname, 0600, nil) if err != nil { log.Fatal(err) } db.Close() //   Serial  c := &serial.Config{Name: config.SerialPort, Baud: 9600} s, err := serial.OpenPort(c) if err != nil { fmt.Printf("Error open serial port %s ", err.Error()) log.Fatal(err) } serialPort = s //     , go- ch := make(chan bool) // wait chanel until key is valid go getData(ch, s) for { time.Sleep(time.Second) tmp := <-ch if tmp { if isOpen { closeRelay() } else { openRelay() } } } } func getData(ch chan bool, s *serial.Port) { for { reader := bufio.NewReader(s) reply, err := reader.ReadBytes('\n') if err != nil { log.Fatal(err) } k := string(reply) if chk := checkKey(k); chk { ch <- chk time.Sleep(2 * time.Second) } } } func invertBool() { //    isOpen = !isOpen } func checkErr(err error) { if err != nil { panic(err) } } func boltStore(value Key) { db, err := bolt.Open(dbname, 0600, nil) if err != nil { log.Fatal(err) } defer db.Close() db.Update(func(tx *bolt.Tx) error { b, err := tx.CreateBucketIfNotExists([]byte("keys")) if err != nil { return err } return b.Put([]byte(value.Key), []byte(value.isEnable)) }) } func boltRead(key string) bool { var strKey string db, err := bolt.Open(dbname, 0600, nil) if err != nil { log.Fatal(err) return false } defer db.Close() db.View(func(tx *bolt.Tx) error { re := regexp.MustCompile(`\r\n`) key := re.ReplaceAllString(key, "") re = regexp.MustCompile(`\n`) key = re.ReplaceAllString(key, "") re = regexp.MustCompile(`\r`) key = re.ReplaceAllString(key, "") log.Printf("Readed key: %s\n", key) b := tx.Bucket([]byte("keys")) v := b.Get([]byte(key)) strKey = string(v) return nil }) if strKey == "1" { log.Printf("Key %s valid\n", key) return true } return false } func addKey(w http.ResponseWriter, r *http.Request) { params := r.URL.Query() var key Key key.Key = params.Get("key") key.isEnable = params.Get("enable") boltStore(key) log.Printf("You add the key %s", key.Key) fmt.Fprintln(w, "You add the key", key.Key) } func readKeys(w http.ResponseWriter, r *http.Request) { keys := make(map[string]string) db, err := bolt.Open(dbname, 0600, nil) if err != nil { log.Fatal(err) } defer db.Close() db.View(func(tx *bolt.Tx) error { b := tx.Bucket([]byte("keys")) b.ForEach(func(k, v []byte) error { keys[string(k)] = string(v) fmt.Printf("map: %s\n", keys[string(k)]) return nil }) return nil }) data, _ := json.Marshal(keys) fmt.Fprintln(w, string(data)) } func deleteKey(w http.ResponseWriter, r *http.Request) { params := r.URL.Query() deleteKey := params.Get("key") db, err := bolt.Open(dbname, 0600, nil) if err != nil { log.Fatal(err) } defer db.Close() db.Update(func(tx *bolt.Tx) error { // Retrieve the users bucket. // This should be created when the DB is first opened. b := tx.Bucket([]byte("keys")) err := b.Delete([]byte(deleteKey)) if err != nil { fmt.Printf("Key: \"%s\" delete failed: %s\n", deleteKey, err.Error()) return err } fmt.Fprintf(w, "Key: \"%s\" deleted succesfully\n", deleteKey) // Persist bytes to users bucket. return nil }) } func webNormalMode(w http.ResponseWriter, r *http.Request) { isHLock = false _, err := serialPort.Write([]byte("hlock0")) if err != nil { log.Fatal(err) } fmt.Fprintln(w, "Normal Mode") } func webHLockMode(w http.ResponseWriter, r *http.Request) { _, err := serialPort.Write([]byte("hlock1")) if err != nil { log.Fatal(err) } isHLock = true fmt.Fprintln(w, "HardLock Mode") } func webCloseRelay(w http.ResponseWriter, r *http.Request) { switchRelay() fmt.Fprintln(w, "switch relay") } func webOpenRelay(w http.ResponseWriter, r *http.Request) { openRelay() fmt.Fprintln(w, "open lock") } func closeRelay() { _, err := serialPort.Write([]byte("close")) if err != nil { log.Fatal(err) } invertBool() log.Println("Close") } func openRelay() { _, err := serialPort.Write([]byte("open")) if err != nil { log.Fatal(err) } invertBool() log.Println("Open") } func switchRelay() { if isOpen { closeRelay() } else { openRelay() } } func checkKey(key string) bool { if boltRead(key) { return true } return false } func readConfig() (*Config, error) { plan, _ := ioutil.ReadFile("config.json") config := Config{} err := json.Unmarshal([]byte(plan), &config) return &config, err }
      
      







Raspberry Pi自䜓にバむナリファむルをアセンブルしたした圓然、Goのすべおの䟝存関係を「ラズベリヌ」にむンストヌルする必芁がありたした。



 GOOS=linux GOARCH=arm go build -o /home/pi/skud-go/skud-go
      
      





たた、䞻なこずは、バむナリファむルに次の䟝存ファむルを眮くこずを忘れないこずです。



 config.json access.db
      
      





config.json
{

「SerialPort」「/ dev / ttyUSB0」、

「HttpPort」「80」、

「NormalModeEndpoint」「通垞」、

「HardLockModeEndpoint」「block」、

「CloseEndpoint」「close」、

「OpenEndpoint」「open」、

AddKeyEndpointaddkey

DeleteKeyEndpointdeletekey

「ReadKeysEndpoint」「readkeys」、

「LogFilePath」「/ var / log / skud-go.log」

}



コントロヌラヌのデヌタ型。 skud_type.go
 package main //Key     type Key struct { Key string isEnable string } //Config    type Config struct { SerialPort string `json:"serialPort"` HTTPPort string `json:"httpPort"` NormalModeEndpoint string `json:"normalModeEndpoint"` HardLockModeEndpoint string `json:"hardLockModeEndpoint"` CloseEndpoint string `json:"closeEndpoint"` OpenEndpoint string `json:"openEndpoint"` AddKeyEndpoint string `json:"addKeyEndpoint"` DeleteKeyEndpoint string `json:"deleteKeyEndpoint"` ReadKeysEndpoint string `json:"readKeysEndpoint"` LogFilePath string `json:"logFilePath"` }
      
      







コントロヌラをサヌビスずしお開始するには、远加のナニットファむルを䜜成する必芁がありたす。 このようなファむルは、特定のリ゜ヌスの管理方法をsystemd初期化システムに䌝えたす。 サヌビス-プログラムの開始ず停止の䟝存関係ずパラメヌタヌを定矩する最も䞀般的なタむプのナニットファむル。



skud-go甚のこのようなファむルを䜜成したす。 ファむルはskud-go.serviceず呌ばれ、/ etc / systemd / systemに保存されたす。



 sudo nano /etc/systemd/system/skud-go.service
      
      





ファむルの内容



 [Unit] Description=Access Control System Controller by Go After=network.target [Service] User=pi ExecStart=/home/pi/skud-go/skud-go [Install] WantedBy=multi-user.target
      
      





新しいサヌビスを開始するには、次を入力したす。



 sudo systemctl start skud-go
      
      





次に、このサヌビスの自動実行を有効にする必芁がありたす。



 sudo systemctl enable skud-go
      
      





その結果、6か月以䞊にわたっお、非垞にシンプルで機胜的なアップタむムコントロヌラヌが実珟したしたもちろん、すべおが先を行っおいたす。 この蚘事が誰かに圹立぀こずを願っおいたす。



→ Githubで入手可胜な゜ヌス



All Articles