visit
These services are great. They’re awesome. If you’re building a real project or working on IoT professionally, this is the way to go. They provide excellent secure services and handle so many things for you.
That’s great, but if you want to truly learn IoT you need to get down to the nuts and bolts. The cloud services have clients you configure, and you push the data up, and it’s visualized for you. In this tutorial, we’re going to build all that stuff ourselves and understand it.Let’s Rock and Roll!We’re going to create a Go file on the Raspberry Pi to read the temperature from the sensor. I named it readsensor.go.
You will need to to run this.Next, you need to install the Go-DHT libraries from .go get github.com/MichaelS11/go-dht
This is a library I like to use because it’s incredibly simple and has worked reliably for me.First, let’s just verify the functionality of the sensor. Let’s build our file from the top. Add the following to the header:package main
import (
"fmt"
"github.com/MichaelS11/go-dht"
)
This will pull in the Go-DHT package and fmt to format the output.Next, I’ll create a CONST to set the GPIO. Pin 11 is GPIO 17. You can use other GPIO pins if you like.const GPIO = "GPIO17"
Next, we need to do three things for the temperature sensor:hosterr := dht.HostInit()
if hosterr != nil {
fmt.Println("HostInit error:", hosterr)
return
}
Then we’ll create our new DHT reader:dht, dhterr := dht.NewDHT(GPIO, dht.Fahrenheit, "")
if dhterr != nil {
fmt.Println("NewDHT error:", dhterr)
return
}
Notice I’m using the GPIO we set in the const above, and calling NewDHT and setting the parameter to Fahrenheit. You can use Celsius if you choose.Finally, we’ll read the sensor and output the results.humidity, temperature, readerr := dht.Read()
if readerr != nil {
fmt.Println("Reader error:", readerr)
return
}
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/MichaelS11/go-dht"
)
This will bring in the packages needed to send our data to an endpoint.Next, we need to create a struct for our data. This is what we’ll be packaging up and sending to our endpoint.type reading struct {
TimeStamp string
Temperature float64
Humidity float64
}
This is the data that we’ll be sending.And we’ll add another CONST here for our endpoint. This will be your URL to your server.const Endpoint = "//[YOUR DOMAIN or IP]:5000/reading"
OK, so in our struct above, we have a timestamp. We’ll create that at the top of our application:timeStamp := time.Now()
Now, go down below your dht.Read function:newReading := reading{TimeStamp: timeStamp.Format("2006-01-02T15:04:05-0700"), Temperature: temperature, Humidity: humidity}
This creates a new struct with our reading.If you want to output the reading to the console also, you can add the following:fmt.Printf("Our Reading was: \nTemperature: %v\nHumidity:%v\n", temperature, humidity)
Now we create our request:var requestBody, reqerr = json.Marshal(newReading)
if reqerr != nil {
fmt.Println("Request error:", readerr)
return
}
And then send that request as a POST to our endpoint.resp, resperror := http.Post(Endpoint, "application/json", bytes.NewBuffer(requestBody))
if resperror != nil {
fmt.Println("Response error:", resperror)
return
}
Finally, we will create a defer to close the request:defer resp.Body.Close()
And we’re done! Now we have a practical application to read the sensor and send the values to an endpoint.You can view the to verify it.Setup Our Packages
We’ll need to install a couple of Go packages:go get github.com/gin-gonic/gin
go get github.com/mattn/go-sqlite3
Then, create a file named reader.go or whatever you want it to be named.
At the top, let’s put in our packages:package main
import (
"database/sql"
"fmt"
"net/http"
"github.com/gin-gonic/gin"
_ "github.com/mattn/go-sqlite3"
)
We’re going to use the database/sql library to interact with our SQLlite database. For larger applications, you may want to use a larger scale database service. But for learning purposes and playing around, SQLlite does just fine.Then we’ll use the FMT library to print our error messages nicely,Gin will be handling our API calls, Then Matt’s go-sqllite3 library will handle interacting with our SQLite database.Once again we’ll create a similar struct for our readings:type Reading struct {
TimeStamp string
Temperature float64
Humidity float64
}
Then, below that we’ll create a spot for our db instance:var db *sql.DB
Next, let’s create a “Check” function to check for and report errors. It will look like this:func Check(e error) {
if e != nil {
panic(e)
}
}
It’s not much, but it will do for now.Setup Our Database
Next, we’re going to create an init function, so we can initialize and connect to our database on startup.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()
}
Let’s take a look at this.First, we’ll connect to the database:db, dbError = sql.Open("sqlite3", "./readings.db")
Check(dbError)
Here we’re calling sql.Open and specifying sqlite3, and a path to our database. If it doesn’t exist, a new one will be created. Then we’ll check for an error.Next, we have a prepare statement. It says if the reading table doesn’t exist, we’ll create it. Note we’re adding a timestamp, temperature, and humidity.Then, of course, we call check again to make sure there weren’t any errors. Then call statement.Exec() to execute our SQL query.Store Our Data
Now we need to set up a way to store data into our database. It’s easy to do and less code than you might think.First, let’s create a function to save to our database: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)
}
So we’re creating a func to store data that takes the timestamp, float, and humidity as inputs.Then we call db.Prepare() to prepare a statement. We do an error check on the prepare.Then, we call statement.Exec() and insert our data to be saved. Simple and easy.Now we’ll create another function named tempData: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!",
})
}
}
In this function, we pull the POST data from the gin context. We create an instance of our Reading struct and bind our JSON to it. Then we store the data from the JSON in our database by passing it to the saveToDatabase function we just created.Then we return a JSON file with a 200 OK status and some messaging you can add in case you want to call that back at some point.Get the Last Ten Records
The next function we need to make will get the last ten records from our database.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
}
So here we have a func that accepts no input but returns a slice of readings.First, the select statement is created.Then we create temporary variables.Then we’ll create a slice named lastTen.We’ll use a for loop to iterate through the rows returns, scan the data into the temporary variables, then append them to our slice.Finally, we return lastTen from the function.
Setup Our API
For the last step, we’ll set up our API.So we’re going to use for our API endpoint. Our API is so simple we could write it out by hand, but by using Gin we can expand it and make it more robust later.r := gin.Default()
This creates a default Gin router. Then we’ll great the endpoint that returns our last ten readings:r.GET("/reading", func(c *gin.Context) {
lastTen := getLastTen()
// stuff into a JSON object and return it
c.JSON(200, gin.H{"message": lastTen})
})
Here we’re creating a route to capture any GET commands sent to /reading. We then make a call to the getLastTen() func we just created, and serialize that slice into JSON and return it with a 200 OK message.For input, we add the following:r.POST("/reading", tempData)
This captures any POST commands to /reading and directs it to the tempData function we created a while back.Finally, we start the API.r.Run(":5000")
And, we’re done! You can here.Once you start this service, you can accept POST commands from your Raspberry Pi, and store them in the database.