From f058b94f1fa785566d9b5130b2ae5f68e338137f Mon Sep 17 00:00:00 2001 From: Sven Date: Sat, 7 Dec 2019 22:59:56 +0100 Subject: [PATCH] Initial Commit --- .gitignore | 2 + README.md | 40 +++++ config.json | 19 ++ gpio-numbers-pi2.png | Bin 0 -> 54469 bytes main.go | 409 +++++++++++++++++++++++++++++++++++++++++++ websocket.go | 102 +++++++++++ 6 files changed, 572 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 config.json create mode 100644 gpio-numbers-pi2.png create mode 100644 main.go create mode 100644 websocket.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..50b033c --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +out/ +test.exe diff --git a/README.md b/README.md new file mode 100644 index 0000000..6396081 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# Go gPIo app + +Binarys befinden sich unter https://nc.masilux.de/index.php/s/bEzP2msezHPzyee + +Zum Kompilieren folgende Go-Abhängigkeiten installieren: + - `go get github.com/gorilla/mux` + - `go get github.com/stianeikeland/go-rpio` + - `go get github.com/frickelblog/surgemq/service` + - `go get github.com/frickelblog/message` + +Kompilieren mit: `go build main.go -o gPIo.bin` +Kompilieren unter Windows (Powesrhell) mit Zielsystem Linux/ARM: `$env:GOOS="linux"; $env:GOARCH="arm"; go build -o gPIo.arm` + + +**Konfiguration** +Die Konfiguration erfolgt in der Datei `config.json`, z.B: +``` +{ + "debug": true, + "enablefilesystem": true, + "enablemqttserver": true, + "enablemqttclient": true, + "mqttserverhost": "127.0.0.1", + "mqttservertcpport": "1883", + "mqttserverwsport": "8080", + "mqttclientaddress": "127.0.0.1", + "mqttclientport": "1883", + "mqttclientuser": "", + "mqttclientpass": "", + "pins": [ + { "pin":17, "io":"in", "state":"0", "file":"", "mqtttopic":"/pin/17" }, + { "pin":20, "io":"out", "state":"1", "file":"", "mqtttopic":"/pin/20" }, + { "pin":21, "io":"in", "state":"0", "file":"", "mqtttopic":"/pin/21" }, + { "pin":22, "io":"in", "state":"0", "file":"","mqtttopic":"/pin/22" } + ] +} +``` + +**Anwendung** + diff --git a/config.json b/config.json new file mode 100644 index 0000000..7d52368 --- /dev/null +++ b/config.json @@ -0,0 +1,19 @@ +{ + "debug": true, + "enablefilesystem": true, + "enablemqttserver": true, + "enablemqttclient": true, + "mqttserverhost": "127.0.0.1", + "mqttservertcpport": "1883", + "mqttserverwsport": "8080", + "mqttclientaddress": "127.0.0.1", + "mqttclientport": "1883", + "mqttclientuser": "", + "mqttclientpass": "", + "pins": [ + { "pin":17, "io":"in", "state":"0", "file":"", "mqtttopic":"/pin/17" }, + { "pin":20, "io":"out", "state":"1", "file":"", "mqtttopic":"/pin/20" }, + { "pin":21, "io":"in", "state":"0", "file":"", "mqtttopic":"/pin/21" }, + { "pin":22, "io":"in", "state":"0", "file":"","mqtttopic":"/pin/22" } + ] +} \ No newline at end of file diff --git a/gpio-numbers-pi2.png b/gpio-numbers-pi2.png new file mode 100644 index 0000000000000000000000000000000000000000..c50ea4fa4e3c124b1fdc4d21ccee6d7791af87af GIT binary patch literal 54469 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 Ly&FZ>9|!+GBexIS literal 0 HcmV?d00001 diff --git a/main.go b/main.go new file mode 100644 index 0000000..e9cbb10 --- /dev/null +++ b/main.go @@ -0,0 +1,409 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "strconv" + "strings" + "time" + + "github.com/frickelblog/message" + "github.com/frickelblog/surgemq/service" + "github.com/stianeikeland/go-rpio" +) + +var ( + config = readConfig("./config.json") + MQTTClient service.Client + srv *service.Server + client *service.Client +) + +type Configuration struct { + Debug bool `json:"debug"` + EnableFileSystem bool `json:"enablefilesystem"` + EnableMQTTServer bool `json:"enablemqttserver"` + EnableMQTTClient bool `json:"enablemqttclient"` + MQTTServerHost string `json:"mqttserverhost"` + MQTTServerTCPPort string `json:"mqttservertcpport"` + MQTTServerWSPort string `json:"mqttserverwsport"` + MQTTClientAddress string `json:"mqttclientaddress"` + MQTTClientPort string `json:"mqttclientport"` + MQTTClientUser string `json:"mqttclientuser"` + MQTTClientPass string `json:"mqttclientpass"` + Pins []PinConfiguration `json:"pins"` +} + +type PinConfiguration struct { + Pin int8 `json:"pin"` + IO string `json:"io"` + File string `json:"file"` + MQTTTopic string `json:"mqtttopic"` + State string `json:"state"` +} + +// our main function +func main() { + + // -------------------------------------------------------------------------------- + // Create a new mqtt server + if config.EnableMQTTServer { + srv = &service.Server{ + KeepAlive: 300, // seconds + ConnectTimeout: 2, // seconds + SessionsProvider: "mem", // keeps sessions in memory + Authenticator: "mockSuccess", // always succeed + TopicsProvider: "mem", // keeps topic subscriptions in memory + } + + // Websocket // "tcp://127.0.0.1:1883" => TCP COnnection vom MQTT Server als WS-Backend + addr := "tcp://" + config.MQTTServerHost + ":" + config.MQTTServerTCPPort + AddWebsocketHandler("/mqtt", addr) + go ListenAndServeWebsocket(":" + config.MQTTServerWSPort) // ":8081" => WS-Connection + // MQTT-Server + go srv.ListenAndServe(addr) + } + // -------------------------------------------------------------------------------- + + for i, s := range config.Pins { + fmt.Println(i, s) + } + + fmt.Println("--------") + + if config.EnableMQTTClient { + fmt.Println("Warte auf MQTT Client...") + time.Sleep(1000 * time.Millisecond) // 2 Sek auf Server warten + + MQTTClient = mqttConnect() + go heartbeatProcess(&MQTTClient, 30) + } + /* + icounter := 0 + for { + icounter = icounter + 1 + time.Sleep(1000 * time.Millisecond) + mqttPubMSG("/pin/17", string(icounter)) + } + */ + + err := rpio.Open() + if err != nil { + panic(fmt.Sprint("unable to open gpio", err.Error())) + } + + defer rpio.Close() + + //------------------------------------------------------------ + // Pin Configuration + for _, pinConfig := range config.Pins { + pin := rpio.Pin(pinConfig.Pin) + + if pinConfig.IO == "in" { + pin.Input() + //pin.PullUp() + //pin.PullDown() + //pin.Detect(rpio.RiseEdge) // AnyEdge / FallEdge 1->0 / RiseEdge 0->1 + } + + if pinConfig.IO == "out" { + pin.Output() + + // Default PinState aus config setzen + if pinConfig.State == "1" { + pin.High() + + } else { + pin.Low() + } + } + } + + //------------------------------------------------------------ + mqttStreamOutput := "" + + for { + + for i, pinConfig := range config.Pins { + //fmt.Println(i, pinConfig) + + pin := rpio.Pin(pinConfig.Pin) + + // Edge-Detection test + //if pin.EdgeDetected() { + // newState := stringState(pin.Read()) + // fmt.Println("Edge raised: " + newState) + //} + + if pinConfig.IO == "in" { + + newState := stringState(pin.Read()) + + if newState != pinConfig.State { + // Pinstate in Config speichern + config.Pins[i].State = newState + + // Pinstate in Datei schreiben + if config.EnableFileSystem { + writePinFileState(pinConfig.File, newState) + } + + // Pinstate in MQTT-Topic kippen + if config.EnableMQTTClient { + pubmsg := message.NewPublishMessage() + pubmsg.SetTopic([]byte(pinConfig.MQTTTopic)) // /pin/17 + pubmsg.SetPayload([]byte(newState)) + MQTTClient.Publish(pubmsg, nil) + } + } + + } + + if pinConfig.IO == "out" { + + // PinState aus Datei lesen + if config.EnableFileSystem { + if readPinFileState(pinConfig.File) { + pin.High() + + } else { + pin.Low() + } + } + + // Pinstate in MQTT-Topic kippen, falls er durch Datei geändert wurde + newState := stringState(pin.Read()) + if newState != config.Pins[i].State { + if config.EnableMQTTClient { + pubmsg := message.NewPublishMessage() + pubmsg.SetTopic([]byte(pinConfig.MQTTTopic)) // /pin/17 + pubmsg.SetPayload([]byte(newState)) + MQTTClient.Publish(pubmsg, nil) + } + } + } + + // Pinstate Ausgabe auf der Konsole + res := pin.Read() + fmt.Print(pinConfig.Pin) + fmt.Print(": ") + fmt.Println(res) + + mqttStreamOutput = mqttStreamOutput + "" + strconv.Itoa(int(pinConfig.Pin)) + ": " + strconv.Itoa(int(res)) + "\r\n" + } + + if config.EnableMQTTClient { + pubmsg := message.NewPublishMessage() + pubmsg.SetTopic([]byte("/pins")) // /pin/17 + pubmsg.SetPayload([]byte(mqttStreamOutput)) + MQTTClient.Publish(pubmsg, nil) + } + mqttStreamOutput = "" + + // 100ms warten + time.Sleep(400 * time.Millisecond) + cmd := exec.Command("clear") //Linux example, its tested + cmd.Stdout = os.Stdout + cmd.Run() + } + +} + +//-------------------------------------------------------------------------- +// Hilfsfunktionen +//-------------------------------------------------------------------------- + +func fileExists(filename string) bool { + info, err := os.Stat(filename) + if os.IsNotExist(err) { + return false + } + return !info.IsDir() +} + +func stringState(state rpio.State) string { + if state == Low { + return "0" + } + if state == High { + return "1" + } + + return "" +} + +func readPinFileState(filename string) bool { + if fileExists(filename) { + content, err := ioutil.ReadFile(filename) + if err != nil { + return false + } + + text := strings.TrimSpace(string(content)) + if strings.EqualFold(text, "1") { + return true + } + + } + + // Standardwert: false + return false +} + +func writePinFileState(filename string, content string) bool { + if filename != "" { + f, err := os.Create(filename) + if err != nil { + fmt.Println(err) + return false + } + _, err = f.WriteString(content) + if err != nil { + fmt.Println(err) + f.Close() + return false + } + + err = f.Close() + if err != nil { + fmt.Println(err) + return false + } + } + return true +} + +func readConfig(filename string) *Configuration { + // initialize conf with default values. + conf := &Configuration{} + + b, err := ioutil.ReadFile(filename) + if err != nil { + return conf + } + if err = json.Unmarshal(b, conf); err != nil { + return conf + } + return conf +} + +// State of pin, High / Low +const ( + Low rpio.State = iota + High +) + +func mqttConnect() service.Client { + // Instantiates a new Client + client = &service.Client{} + + // Creates a new MQTT CONNECT message and sets the proper parameters + msg := message.NewConnectMessage() + msg.SetWillQos(0) + msg.SetVersion(4) + msg.SetCleanSession(true) + msg.SetClientId([]byte("gPIo")) + msg.SetKeepAlive(20) + //msg.SetWillTopic([]byte("will")) + //msg.SetWillMessage([]byte("send me home")) + //msg.SetUsername([]byte("gPIo")) + //msg.SetPassword([]byte("")) + + // Connects to the remote server at 127.0.0.1 port 1883 + //c.Connect("tcp://127.0.0.1:1883", msg) + //c.Connect("tcp://test.mosquitto.org:1883", msg) + if err := client.Connect("tcp://"+config.MQTTClientAddress+":"+config.MQTTClientPort, msg); err != nil { + log.Fatal(err) + } + + //time.Sleep(5000 * time.Millisecond) + + pubmsg := message.NewPublishMessage() + pubmsg.SetTopic([]byte("/topic")) + pubmsg.SetPayload([]byte("msg")) + pubmsg.SetQoS(0) + + // Publishes to the server by sending the message + err := client.Publish(pubmsg, nil) + if err != nil { + fmt.Print("Fehler: ") + } + + // Subscribe MQTTTopics + submsg := message.NewSubscribeMessage() + + for _, pincfg := range config.Pins { + if pincfg.MQTTTopic != "" { + fmt.Println(" - Subscribe MQTT-Topic: " + pincfg.MQTTTopic) + submsg.AddTopic([]byte(pincfg.MQTTTopic), 0) + } + } + + client.Subscribe(submsg, nil, mqttOnPublishFunc) + + return *client +} + +func mqttOnPublishFunc(msg *message.PublishMessage) error { + topic := string(msg.Topic()) + payload := string(msg.Payload()) + + fmt.Println(topic) + fmt.Println(payload) + + for i, pinConfig := range config.Pins { + if pinConfig.MQTTTopic == topic { + pin := rpio.Pin(pinConfig.Pin) + if payload == "1" { + pin.High() + } else if payload == "0" { + pin.Low() + } + + // Pinstate in Config speichern + newState := stringState(pin.Read()) + config.Pins[i].State = newState + + // Pinstate in Datei schreiben + if config.EnableFileSystem { + writePinFileState(pinConfig.File, newState) + } + } + } + + return nil +} + +func mqttPubMSG(topic string, msg string) { + // Creates a new PUBLISH message with the appropriate contents for publishing + pubmsg := message.NewPublishMessage() + pubmsg.SetTopic([]byte(topic)) + pubmsg.SetPayload([]byte(msg)) + pubmsg.SetQoS(0) + + // Publishes to the server by sending the message + err := MQTTClient.Publish(pubmsg, nil) + if err != nil { + fmt.Print("Fehler: ") + fmt.Println(err) + //MQTTClient.Unsubscribe() + MQTTClient.Disconnect() + MQTTClient = mqttConnect() + MQTTClient.Publish(pubmsg, nil) + } +} + +func heartbeatProcess(c *service.Client, KeepAlive int) { + for _ = range time.Tick(time.Duration(KeepAlive) * time.Second) { + //fmt.Println("Ping OK") + err := c.Ping(nil) + if err != nil { + fmt.Print("Fehler: ") + fmt.Println(err) + } + } +} diff --git a/websocket.go b/websocket.go new file mode 100644 index 0000000..7d20e9e --- /dev/null +++ b/websocket.go @@ -0,0 +1,102 @@ +package main + +import ( + "io" + "net" + "net/http" + "net/url" + + "github.com/surge/glog" + "golang.org/x/net/websocket" +) + +func DefaultListenAndServeWebsocket() error { + if err := AddWebsocketHandler("/mqtt", "test.mosquitto.org:1883"); err != nil { + return err + } + return ListenAndServeWebsocket(":1234") +} + +func AddWebsocketHandler(urlPattern string, uri string) error { + glog.Debugf("AddWebsocketHandler urlPattern=%s, uri=%s", urlPattern, uri) + u, err := url.Parse(uri) + if err != nil { + glog.Errorf("surgemq/main: %v", err) + return err + } + + h := func(ws *websocket.Conn) { + WebsocketTcpProxy(ws, u.Scheme, u.Host) + } + http.Handle(urlPattern, websocket.Handler(h)) + return nil +} + +/* start a listener that proxies websocket <-> tcp */ +func ListenAndServeWebsocket(addr string) error { + return http.ListenAndServe(addr, nil) +} + +/* starts an HTTPS listener */ +func ListenAndServeWebsocketSecure(addr string, cert string, key string) error { + return http.ListenAndServeTLS(addr, cert, key, nil) +} + +/* copy from websocket to writer, this copies the binary frames as is */ +func io_copy_ws(src *websocket.Conn, dst io.Writer) (int, error) { + var buffer []byte + count := 0 + for { + err := websocket.Message.Receive(src, &buffer) + if err != nil { + return count, err + } + n := len(buffer) + count += n + i, err := dst.Write(buffer) + if err != nil || i < 1 { + return count, err + } + } + return count, nil +} + +/* copy from reader to websocket, this copies the binary frames as is */ +func io_ws_copy(src io.Reader, dst *websocket.Conn) (int, error) { + buffer := make([]byte, 2048) + count := 0 + for { + n, err := src.Read(buffer) + if err != nil || n < 1 { + return count, err + } + count += n + err = websocket.Message.Send(dst, buffer[0:n]) + if err != nil { + return count, err + } + } + return count, nil +} + +/* handler that proxies websocket <-> unix domain socket */ +func WebsocketTcpProxy(ws *websocket.Conn, nettype string, host string) error { + client, err := net.Dial(nettype, host) + if err != nil { + return err + } + defer client.Close() + defer ws.Close() + chDone := make(chan bool) + + go func() { + io_ws_copy(client, ws) + chDone <- true + }() + go func() { + io_copy_ws(ws, client) + chDone <- true + }() + <-chDone + return nil +}