
在本教程中,我將向您展示如何用樹莓派讀取傳感器的溫度,并使用Go構(gòu)建一個(gè)HTTP服務(wù)來存儲(chǔ)數(shù)據(jù)。
您將學(xué)到如下內(nèi)容:
- 如何從傳感器獲取溫度
- 如何發(fā)送JSON數(shù)據(jù)
- 構(gòu)建一個(gè)API服務(wù)來接收溫度數(shù)據(jù)
- 將數(shù)據(jù)存儲(chǔ)在SQLite數(shù)據(jù)庫中
我們會(huì)用Go來實(shí)現(xiàn)。實(shí)現(xiàn)需要的硬件設(shè)備: - 樹莓派一個(gè)
- AM2302傳感器
- Linux主機(jī)
開發(fā)此功能的目的
本教程的目的是讓您深入了解物聯(lián)網(wǎng)及其工作原理。您也可以使用云服務(wù)實(shí)現(xiàn),例如:
- AWS IoT
- Azure IoT Edge
- Google cloud IoT Core
這些云服務(wù)都很好。如果你正在構(gòu)建一個(gè)真正的項(xiàng)目或從事物聯(lián)網(wǎng)專業(yè)工作,使用這些云服務(wù)是對(duì)的。他們提供優(yōu)秀的安全服務(wù),為您處理很多的事情。
云服務(wù)雖然很好,但如果你想真正學(xué)習(xí)物聯(lián)網(wǎng),你需要從細(xì)節(jié)入手。云服務(wù)需要配置客戶端,把你的數(shù)據(jù)推上去,它為你提供可視化。在本教程中,我們將自己構(gòu)建所有這些東西并理解它。
連接溫度傳感器

傳感器有三根線需要連接到樹莓派的GPIO,如上所示。有一根紅色的電線連到樹莓派1腳。黑色導(dǎo)線接地并連接到第6腳。橙色(有時(shí)是黃色或白色)是數(shù)據(jù)線,接到第11引腳。連接非常簡單,如果你需要額外的幫助,這里有一個(gè)很好的連接AM2302的指南:https://learn.adafruit.com/dht/connecting-to-a-dhtxx-sensor?ref=hackernoon.com。
讀取溫度傳感器
我們將在樹莓派上創(chuàng)建一個(gè)Go文件來讀取傳感器的溫濕度。我把它命名為readsensor.go。你需要在樹莓派上安裝Go來運(yùn)行它。接下來,需要安裝Go-DHT庫。
go get github.com/MichaelS11/go-dht
這是我喜歡使用的一個(gè)庫,因?yàn)樗浅:唵?,并且?duì)我來說非常可靠。
首先,我們驗(yàn)證一下傳感器的功能。讓我們從頭開始構(gòu)建文件。在文件頭中添加以下內(nèi)容:
package main
import (
"fmt"
"github.com/MichaelS11/go-dht"
)
這將引入Go-DHT包和fmt來格式化輸出。接下來,我將創(chuàng)建一個(gè)常量來設(shè)置GPIO.Pin引腳11是GPIO 17(傳感器數(shù)據(jù)來源引腳)。如果你喜歡,你可以使用其他的GPIO引腳。
const GPIO = "GPIO17"
接下來,我們需要對(duì)溫度傳感器做三件事:
- 初始化GPIO主機(jī)
- 創(chuàng)建一個(gè)新的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ù)就實(shí)現(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)建一個(gè)結(jié)構(gòu)體,是我們將發(fā)送到服務(wù)端的數(shù)據(jù)模型。
type reading struct {
TimeStamp string
Temperature float64
Humidity float64
}
這是我們要發(fā)送的數(shù)據(jù)。我們將在這里為服務(wù)添加另一個(gè)常量。這將是您服務(wù)器的URL。
const Endpoint = "http://[YOUR DOMAIN or IP]:5000/reading"
在上面的結(jié)構(gòu)中,我們有一個(gè)時(shí)間戳。將在應(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í)例。如果你還想將讀取的數(shù)據(jù)輸出到控制臺(tái),你可以添加以下內(nèi)容:
fmt.Printf("Our Reading was: \nTemperature: %v\nHumidity:%v\n", temperature, humidity)
現(xiàn)在我們創(chuàng)建http請(qǐng)求內(nèi)容:
var requestBody, reqerr = json.Marshal(newReading)
if reqerr != nil {
fmt.Println("Request error:", readerr)
return}
然后將該請(qǐng)求內(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)建一個(gè)defer來關(guān)閉請(qǐng)求:
defer resp.Body.Close()
ok!現(xiàn)在我們有了一個(gè)實(shí)際的應(yīng)用程序來讀取傳感器并將值發(fā)送到服務(wù)端,接下來需要構(gòu)建服務(wù)端代碼。源代碼庫地址:https://github.com/JeremyMorgan/GoTempSensor/blob/master/readsensor.go?ref=hackernoon.com
創(chuàng)建API服務(wù)端
因此,對(duì)于服務(wù)端設(shè)置,您幾乎可以使用任何Linux或Windows虛擬機(jī)。我使用AWS Lightsail FreeBSD機(jī)器部署服務(wù)端。您需要在機(jī)器上安裝Go,除非您想編譯可執(zhí)行文件發(fā)送到服務(wù)器上直接執(zhí)行。
如果您使用的是LightSail,請(qǐng)確保打開端口5000。這個(gè)端口需要在您使用的任何防火墻或服務(wù)上打開。我們的服務(wù)端提供以下功能:
- 提供一個(gè)API來接收從樹莓派上發(fā)送的POST請(qǐng)求
- 將結(jié)果存儲(chǔ)在SQLlite數(shù)據(jù)庫中
- 檢索最近的10條傳感器數(shù)據(jù)
配制包
我們需要安裝幾個(gè)Go包:
go get github.com/gin-gonic/gin
go get github.com/mattn/go-sqlite3
然后,創(chuàng)建一個(gè)名為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ù)庫交互。對(duì)于大型應(yīng)用程序,您可能希望使用規(guī)模更大的數(shù)據(jù)庫服務(wù)。但是為了學(xué)習(xí)和實(shí)驗(yàn),SQLlite就很好。然后我們將使用fmt標(biāo)準(zhǔn)庫來打印我們的錯(cuò)誤消息,
Gin將處理我們的API調(diào)用,然后go-sqllite3庫將處理與我們的SQLite數(shù)據(jù)庫的交互。同樣,我們?yōu)樽x取post請(qǐng)求body創(chuàng)建一個(gè)類似的結(jié)構(gòu):
type Reading struct {
TimeStamp string
Temperature float64
Humidity float64
}
然后下面為db創(chuàng)建一個(gè)實(shí)例:
var db *sql.DB
接下來,讓我們創(chuàng)建一個(gè)“Check”函數(shù)來檢查和報(bào)告錯(cuò)誤。它看起來是這樣的:
func Check(e error) {
if e != nil {
panic(e)
}
}
設(shè)置數(shù)據(jù)庫
接下來,我們將創(chuàng)建一個(gè)init函數(shù),這樣我們就可以在啟動(dòng)時(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()
}
以上代碼會(huì)連接sqlite數(shù)據(jù)庫。我們通過sql.Open函數(shù)來連接數(shù)據(jù)庫,參數(shù)sqlite3指定使用的數(shù)據(jù)庫類型,第二個(gè)參數(shù)指定數(shù)據(jù)庫存儲(chǔ)文件。如果數(shù)據(jù)庫存儲(chǔ)文件不存在將創(chuàng)建新的,最后檢查錯(cuò)誤信息。
接下來,我們有一個(gè)預(yù)備聲明。表示如果這個(gè)數(shù)據(jù)表不存在,我們就創(chuàng)建它。注意,我們添加了時(shí)間戳、溫度和濕度。然后,我們?cè)俅握{(diào)用check以確保沒有任何錯(cuò)誤。調(diào)用statement.Exec()執(zhí)行我們的SQL查詢。
存儲(chǔ)數(shù)據(jù)
現(xiàn)在我們需要設(shè)置將數(shù)據(jù)存儲(chǔ)到數(shù)據(jù)庫中的方法。這很容易做到,并且比您想象的代碼要少。首先,讓我們創(chuàng)建一個(gè)函數(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)建了一個(gè)函數(shù)來存儲(chǔ)時(shí)間戳,浮點(diǎn)數(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ù)存儲(chǔ)數(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!",
})
}
}
在這個(gè)函數(shù)中,我們從gin上下文中提取POST數(shù)據(jù)。我們創(chuàng)建read結(jié)構(gòu)的一個(gè)實(shí)例,并將JSON綁定到它。通過將JSON中的數(shù)據(jù)傳遞給剛剛創(chuàng)建的saveToDatabase函數(shù),將數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)庫中。
然后我們返回一個(gè)JSON串,狀態(tài)為200 OK,如果你想在某個(gè)時(shí)候調(diào)用它,你可以添加一些消息。
獲得最近十條數(shù)據(jù)
我們需要?jiǎng)?chuàng)建的下一個(gè)函數(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}
這里我們有一個(gè)函數(shù),它不接受輸入,但返回?cái)?shù)據(jù)庫中最近10條數(shù)據(jù)。首先,創(chuàng)建select語句和臨時(shí)變量。我們創(chuàng)建一個(gè)名為lastTen切片。將使用一個(gè)for循環(huán)來遍歷返回的行,先將數(shù)據(jù)存放在臨時(shí)變量中,然后將它們添加到切片中。 最后,從函數(shù)返回lastTen
配制API
我們將使用Gin作為API服務(wù)框架。使用的API非常簡單,可以手工寫,但是通過使用Gin,可以擴(kuò)展接口,使得以后更健壯。
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)建一個(gè)默認(rèn)的Gin路由。然后我們會(huì)找到返回最后10個(gè)讀數(shù)的接口。這里我們創(chuàng)建了一個(gè)路由來捕獲發(fā)送到/讀取的任何GET命令。然后我們調(diào)用剛剛創(chuàng)建的getLastTen()函數(shù),將該切片序列化為JSON,并返回一個(gè)200 OK消息。
一旦啟動(dòng)這個(gè)服務(wù),你可以接受來自樹莓派的POST命令,并將接收到的傳感器數(shù)據(jù)存儲(chǔ)在數(shù)據(jù)庫中。
總結(jié)
我知道這是一個(gè)很長的教程。以下是我們學(xué)到的:
- 如何從傳感器獲取溫度
- 如何發(fā)送JSON中數(shù)據(jù)
- 使用Gin構(gòu)建一個(gè)API服務(wù)來接收它
- 將數(shù)據(jù)存儲(chǔ)在SQLite數(shù)據(jù)庫中
有大量的前期工作可以幫助您理解物聯(lián)網(wǎng)是如何工作的,以及如何構(gòu)建自己的系統(tǒng)。