在本教程中,我將向您展示如何用樹莓派讀取傳感器的溫度,并使用Go構(gòu)建一個HTTP服務(wù)來存儲數(shù)據(jù)。
您將學(xué)到如下內(nèi)容:
- 如何從傳感器獲取溫度
- 如何發(fā)送JSON數(shù)據(jù)
- 構(gòu)建一個API服務(wù)來接收溫度數(shù)據(jù)
- 將數(shù)據(jù)存儲在SQLite數(shù)據(jù)庫中
我們會用Go來實現(xiàn)。實現(xiàn)需要的硬件設(shè)備: - 樹莓派一個
- AM2302傳感器
- Linux主機
開發(fā)此功能的目的
本教程的目的是讓您深入了解物聯(lián)網(wǎng)及其工作原理违寞。您也可以使用云服務(wù)實現(xiàn),例如:
- AWS IoT
- Azure IoT Edge
- Google cloud IoT Core
這些云服務(wù)都很好偶房。如果你正在構(gòu)建一個真正的項目或從事物聯(lián)網(wǎng)專業(yè)工作趁曼,使用這些云服務(wù)是對的。他們提供優(yōu)秀的安全服務(wù)棕洋,為您處理很多的事情挡闰。
云服務(wù)雖然很好,但如果你想真正學(xué)習(xí)物聯(lián)網(wǎng)掰盘,你需要從細(xì)節(jié)入手摄悯。云服務(wù)需要配置客戶端,把你的數(shù)據(jù)推上去庆杜,它為你提供可視化。在本教程中碟摆,我們將自己構(gòu)建所有這些東西并理解它晃财。
連接溫度傳感器
傳感器有三根線需要連接到樹莓派的GPIO,如上所示。有一根紅色的電線連到樹莓派1腳断盛。黑色導(dǎo)線接地并連接到第6腳罗洗。橙色(有時是黃色或白色)是數(shù)據(jù)線,接到第11引腳钢猛。連接非常簡單伙菜,如果你需要額外的幫助,這里有一個很好的連接AM2302的指南:https://learn.adafruit.com/dht/connecting-to-a-dhtxx-sensor?ref=hackernoon.com命迈。
讀取溫度傳感器
我們將在樹莓派上創(chuàng)建一個Go文件來讀取傳感器的溫濕度贩绕。我把它命名為readsensor.go。你需要在樹莓派上安裝Go來運行它壶愤。接下來淑倾,需要安裝Go-DHT庫。
go get github.com/MichaelS11/go-dht
這是我喜歡使用的一個庫征椒,因為它非常簡單娇哆,并且對我來說非常可靠勃救。
首先碍讨,我們驗證一下傳感器的功能。讓我們從頭開始構(gòu)建文件蒙秒。在文件頭中添加以下內(nèi)容:
package main
import (
"fmt"
"github.com/MichaelS11/go-dht"
)
這將引入Go-DHT包和fmt來格式化輸出勃黍。接下來,我將創(chuàng)建一個常量來設(shè)置GPIO.Pin引腳11是GPIO 17(傳感器數(shù)據(jù)來源引腳)税肪。如果你喜歡溉躲,你可以使用其他的GPIO引腳。
const GPIO = "GPIO17"
接下來益兄,我們需要對溫度傳感器做三件事:
- 初始化GPIO主機
- 創(chuàng)建一個新的DHT reader
- 讀取數(shù)據(jù)
因此在main函數(shù)中锻梳,需初始化:
hosterr := dht.HostInit()
if hosterr != nil {
fmt.Println("HostInit error:", hosterr)
return
}
創(chuàng)建新的DHT reader:
dht, dhterr := dht.NewDHT(GPIO, dht.Fahrenheit, "")
if dhterr != nil {
fmt.Println("NewDHT error:", dhterr)
return
}
注意,我使用的是上面const中設(shè)置的GPIO净捅,調(diào)用NewDHT并將參數(shù)設(shè)置為Fahrenheit疑枯。你可以選擇攝氏溫度。最后蛔六,我們將讀取傳感器并輸出結(jié)果荆永。
humidity, temperature, readerr := dht.Read()
if readerr != nil {
fmt.Println("Reader error:", readerr)
return
}
很好,現(xiàn)在從傳感器中讀溫濕度數(shù)據(jù)就實現(xiàn)了国章。接下來讓我們準(zhǔn)備將數(shù)據(jù)發(fā)送到HTTP服務(wù)具钥。
發(fā)送JSON格式數(shù)據(jù)
我們要在import中添加一些包:
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/MichaelS11/go-dht"
)
引入這些包能將數(shù)據(jù)發(fā)送到HTTP服務(wù)上去。接下來液兽,我們需要為數(shù)據(jù)創(chuàng)建一個結(jié)構(gòu)體骂删,是我們將發(fā)送到服務(wù)端的數(shù)據(jù)模型。
type reading struct {
TimeStamp string
Temperature float64
Humidity float64
}
這是我們要發(fā)送的數(shù)據(jù)。我們將在這里為服務(wù)添加另一個常量宁玫。這將是您服務(wù)器的URL粗恢。
const Endpoint = "http://[YOUR DOMAIN or IP]:5000/reading"
在上面的結(jié)構(gòu)中,我們有一個時間戳欧瘪。將在應(yīng)用程序的頂部創(chuàng)建它:
timeStamp := time.Now()
接下來眷射,在dht.read函數(shù)后面添加以下代碼:
newReading := reading{TimeStamp: timeStamp.Format("2006-01-02T15:04:05-0700"),
Temperature: temperature, Humidity: humidity}
根據(jù)讀取到的傳感器數(shù)據(jù)創(chuàng)建reading結(jié)構(gòu)體實例。如果你還想將讀取的數(shù)據(jù)輸出到控制臺佛掖,你可以添加以下內(nèi)容:
fmt.Printf("Our Reading was: \nTemperature: %v\nHumidity:%v\n", temperature, humidity)
現(xiàn)在我們創(chuàng)建http請求內(nèi)容:
var requestBody, reqerr = json.Marshal(newReading)
if reqerr != nil {
fmt.Println("Request error:", readerr)
return}
然后將該請求內(nèi)容以POST發(fā)送到我們的服務(wù)端妖碉。
resp, resperror := http.Post(Endpoint, "application/json", bytes.NewBuffer(requestBody))
if resperror != nil {
fmt.Println("Response error:", resperror)
return }
最后,我們將創(chuàng)建一個defer來關(guān)閉請求:
defer resp.Body.Close()
ok苦囱!現(xiàn)在我們有了一個實際的應(yīng)用程序來讀取傳感器并將值發(fā)送到服務(wù)端嗅绸,接下來需要構(gòu)建服務(wù)端代碼。源代碼庫地址:https://github.com/JeremyMorgan/GoTempSensor/blob/master/readsensor.go?ref=hackernoon.com
創(chuàng)建API服務(wù)端
因此撕彤,對于服務(wù)端設(shè)置鱼鸠,您幾乎可以使用任何Linux或Windows虛擬機。我使用AWS Lightsail FreeBSD機器部署服務(wù)端羹铅。您需要在機器上安裝Go蚀狰,除非您想編譯可執(zhí)行文件發(fā)送到服務(wù)器上直接執(zhí)行。
如果您使用的是LightSail职员,請確保打開端口5000麻蹋。這個端口需要在您使用的任何防火墻或服務(wù)上打開。我們的服務(wù)端提供以下功能:
- 提供一個API來接收從樹莓派上發(fā)送的POST請求
- 將結(jié)果存儲在SQLlite數(shù)據(jù)庫中
- 檢索最近的10條傳感器數(shù)據(jù)
配制包
我們需要安裝幾個Go包:
go get github.com/gin-gonic/gin
go get github.com/mattn/go-sqlite3
然后焊切,創(chuàng)建一個名為reader.go的文件扮授,你想叫它什么都行。
package main
import (
"database/sql"
"fmt" "net/http"
"github.com/gin-gonic/gin"
_ "github.com/mattn/go-sqlite3")
我們將使用database/sql庫與我們的SQLlite數(shù)據(jù)庫交互专肪。對于大型應(yīng)用程序刹勃,您可能希望使用規(guī)模更大的數(shù)據(jù)庫服務(wù)。但是為了學(xué)習(xí)和實驗嚎尤,SQLlite就很好荔仁。然后我們將使用fmt標(biāo)準(zhǔn)庫來打印我們的錯誤消息,
Gin將處理我們的API調(diào)用芽死,然后go-sqllite3庫將處理與我們的SQLite數(shù)據(jù)庫的交互乏梁。同樣,我們?yōu)樽x取post請求body創(chuàng)建一個類似的結(jié)構(gòu):
type Reading struct {
TimeStamp string
Temperature float64
Humidity float64
}
然后下面為db創(chuàng)建一個實例:
var db *sql.DB
接下來关贵,讓我們創(chuàng)建一個“Check”函數(shù)來檢查和報告錯誤遇骑。它看起來是這樣的:
func Check(e error) {
if e != nil {
panic(e)
}
}
設(shè)置數(shù)據(jù)庫
接下來,我們將創(chuàng)建一個init函數(shù)揖曾,這樣我們就可以在啟動時初始化并連接到數(shù)據(jù)庫落萎。
func init() {
db, _ = sql.Open("sqlite3", "./readings.db")
statement, prepError := db.Prepare("CREATE TABLE IF NOT EXISTS reading (TimeStamp TEXT, Temperature NUMERIC, Humidity NUMERIC)")
Check(prepError)
statement.Exec()
}
以上代碼會連接sqlite數(shù)據(jù)庫势篡。我們通過sql.Open函數(shù)來連接數(shù)據(jù)庫,參數(shù)sqlite3指定使用的數(shù)據(jù)庫類型模暗,第二個參數(shù)指定數(shù)據(jù)庫存儲文件。如果數(shù)據(jù)庫存儲文件不存在將創(chuàng)建新的念祭,最后檢查錯誤信息兑宇。
接下來,我們有一個預(yù)備聲明粱坤。表示如果這個數(shù)據(jù)表不存在隶糕,我們就創(chuàng)建它。注意站玄,我們添加了時間戳枚驻、溫度和濕度。然后株旷,我們再次調(diào)用check以確保沒有任何錯誤再登。調(diào)用statement.Exec()執(zhí)行我們的SQL查詢。
存儲數(shù)據(jù)
現(xiàn)在我們需要設(shè)置將數(shù)據(jù)存儲到數(shù)據(jù)庫中的方法晾剖。這很容易做到锉矢,并且比您想象的代碼要少。首先齿尽,讓我們創(chuàng)建一個函數(shù)來保存?zhèn)鞲衅鲾?shù)據(jù)到數(shù)據(jù)庫:
func saveToDatabase(TimeStamp string, Temperature float64, Humidity float64) {
statement, err := db.Prepare("INSERT INTO reading (TimeStamp, Temperature, Humidity) VALUES (?,?,?)")
Check(err)
_, err = statement.Exec(TimeStamp, Temperature, Humidity)
Check(err)}
我們創(chuàng)建了一個函數(shù)來存儲時間戳沽损,浮點數(shù)和濕度作為輸入數(shù)據(jù)。db.Prepare() 是為寫數(shù)據(jù)庫準(zhǔn)備sql語句循头,statement.Exec() 執(zhí)行數(shù)據(jù)庫操作绵估。這里是向數(shù)據(jù)庫插入數(shù)據(jù)。下面我們創(chuàng)建接口卡骂,來接收客戶端發(fā)送的數(shù)據(jù)国裳,然后調(diào)以上函數(shù)存儲數(shù)據(jù):
func tempData(c *gin.Context) {
// pull from original post and put into our struct
if c.Request.Method == "POST" {
var r Reading
c.BindJSON(&r)
// save to database here
saveToDatabase(r.TimeStamp, r.Temperature, r.Humidity)
c.JSON(http.StatusOK, gin.H{
"status": "Posted!",
"Message": "This worked!",
})
}
}
在這個函數(shù)中,我們從gin上下文中提取POST數(shù)據(jù)偿警。我們創(chuàng)建read結(jié)構(gòu)的一個實例躏救,并將JSON綁定到它。通過將JSON中的數(shù)據(jù)傳遞給剛剛創(chuàng)建的saveToDatabase函數(shù)螟蒸,將數(shù)據(jù)存儲在數(shù)據(jù)庫中盒使。
然后我們返回一個JSON串,狀態(tài)為200 OK七嫌,如果你想在某個時候調(diào)用它少办,你可以添加一些消息。
獲得最近十條數(shù)據(jù)
我們需要創(chuàng)建的下一個函數(shù)是從數(shù)據(jù)庫中獲取最近接收到10條記錄诵原。
func getLastTen() []Reading {
// query the database for readings
rows, _ := db.Query("SELECT TimeStamp, Temperature, Humidity from reading LIMIT 20")
// create some temp variables
var TimeStamp string
var Temperature float64
var Humidity float64
// make a slice
lastTen := make([]Reading, 10)
// insert data into slice
for rows.Next() {
rows.Scan(&TimeStamp, &Temperature, &Humidity)
lastTen = append(lastTen, Reading{TimeStamp: TimeStamp, Temperature: Temperature, Humidity: Humidity})
}
// return it
return lastTen}
這里我們有一個函數(shù)英妓,它不接受輸入挽放,但返回數(shù)據(jù)庫中最近10條數(shù)據(jù)。首先蔓纠,創(chuàng)建select語句和臨時變量辑畦。我們創(chuàng)建一個名為lastTen切片。將使用一個for循環(huán)來遍歷返回的行腿倚,先將數(shù)據(jù)存放在臨時變量中纯出,然后將它們添加到切片中。 最后敷燎,從函數(shù)返回lastTen
配制API
我們將使用Gin作為API服務(wù)框架暂筝。使用的API非常簡單,可以手工寫硬贯,但是通過使用Gin焕襟,可以擴展接口,使得以后更健壯饭豹。
r := gin.Default()
r.GET("/reading", func(c *gin.Context) {
lastTen := getLastTen()
// stuff into a JSON object and return it
c.JSON(200, gin.H{"message": lastTen})
})
r.POST("/reading", tempData)
r.Run(":5000")
這將創(chuàng)建一個默認(rèn)的Gin路由鸵赖。然后我們會找到返回最后10個讀數(shù)的接口。這里我們創(chuàng)建了一個路由來捕獲發(fā)送到/讀取的任何GET命令拄衰。然后我們調(diào)用剛剛創(chuàng)建的getLastTen()函數(shù)卫漫,將該切片序列化為JSON,并返回一個200 OK消息肾砂。
一旦啟動這個服務(wù)列赎,你可以接受來自樹莓派的POST命令,并將接收到的傳感器數(shù)據(jù)存儲在數(shù)據(jù)庫中镐确。
總結(jié)
我知道這是一個很長的教程包吝。以下是我們學(xué)到的:
- 如何從傳感器獲取溫度
- 如何發(fā)送JSON中數(shù)據(jù)
- 使用Gin構(gòu)建一個API服務(wù)來接收它
- 將數(shù)據(jù)存儲在SQLite數(shù)據(jù)庫中
有大量的前期工作可以幫助您理解物聯(lián)網(wǎng)是如何工作的,以及如何構(gòu)建自己的系統(tǒng)源葫。