DEV Community

Cover image for Get and Store Temperature from a Raspberry Pi with Go
Jeremy Morgan
Jeremy Morgan

Posted on • Edited on • Originally published at jeremymorgan.com

Get and Store Temperature from a Raspberry Pi with Go

In this tutorial, I'll show you how to grab temperature from a Raspberry Pi and build an endpoint to store the data, with Go. You will learn:

  • How to retrieve the temperature from a sensor
  • How to send that data in JSON
  • Build an API endpoint to receive it
  • Store the data in SQLite database

And we'll do it all with Go. I did a live stream of the entire process that you can watch here.

What You'll Need for This Tutorial

Temperature from Raspberry Pi with Go

I'm using Pop!_OS to develop on, but you can use anything you'd like.

Note: If you'd like to learn more about Raspberry Pi, check out this new course, setting up a Raspberry Pi Home Server.

Why Are We Doing This?

I've previously written a tutorial to grab room temperature from a Raspberry Pi, and it's very similar, only using Python. This is another "hand-rolled" tutorial. Why are we doing this by hand? Why not use a cloud service?

The purpose of this tutorial is to give you a deep understanding of IoT and how it works. You can easily use a cloud provider such as:

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.

Connect the Temperature Sensor

Temperature from Raspberry Pi with Go

The sensor will have three wires coming out from it and will need to be connected to the GPIO of the Raspberry Pi, as shown above.

Temperature from Raspberry Pi with Go

Temperature from Raspberry Pi with Go

  • There is a red power wire that goes to pin 1.
  • The black wire is ground and goes to pin 6.
  • The orange (sometimes yellow or white) is data and goes to Pin 11.

It's pretty simple to hook up, if you need additional help, here's a good guide to hooking up the AM2302.

Read the Temperature Sensor

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 install Go on the Raspberry Pi to run this.

Next, you need to install the Go-DHT libraries from this repo.

go get github.com/MichaelS11/go-dht
Enter fullscreen mode Exit fullscreen mode

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"
)
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Next, we need to do three things for the temperature sensor:

  • Initialize the GPIO Host
  • Create a new DHT reader
  • Read it

So in our main func, we'll initialize it:

hosterr := dht.HostInit()
if hosterr != nil {
    fmt.Println("HostInit error:", hosterr)
    return
}
Enter fullscreen mode Exit fullscreen mode

Then we'll create our new DHT reader:

dht, dhterr := dht.NewDHT(GPIO, dht.Fahrenheit, "")
if dhterr != nil {
    fmt.Println("NewDHT error:", dhterr)
    return
}
Enter fullscreen mode Exit fullscreen mode

Notice I'm using the GPIO we set in the const above, and calling NewDHT and setting the parameter to Fahrenheit. You can use Celcius 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
}
Enter fullscreen mode Exit fullscreen mode

Temperature from Raspberry Pi with Go

Awesome! It works. Now let's prepare it for sending to an endpoint.

Send the Data with JSON

Now we're going to add some packages to our imports:

import (
    "bytes"
    "encoding/json"
    "fmt"
    "net/http"
    "time"
    "github.com/MichaelS11/go-dht"
)
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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 = "http://[YOUR DOMAIN or IP]:5000/reading"
Enter fullscreen mode Exit fullscreen mode

OK, so in our struct above, we have a timestamp. We'll create that at the top of our application:

timeStamp := time.Now()
Enter fullscreen mode Exit fullscreen mode

Now, go down below your dht.Read function:

Temperature from Raspberry Pi with Go

and replace the Printf line with this line:

newReading := reading{TimeStamp: timeStamp.Format("2006-01-02T15:04:05-0700"), Temperature: temperature, Humidity: humidity}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

Now we create our request:

var requestBody, reqerr = json.Marshal(newReading)
if reqerr != nil {
   fmt.Println("Request error:", readerr)
     return
}
Enter fullscreen mode Exit fullscreen mode

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
    }
Enter fullscreen mode Exit fullscreen mode

Finally, we will create a defer to close the request:

defer resp.Body.Close()
Enter fullscreen mode Exit fullscreen mode

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 full source code here to verify it.

Build Our API Endpoint

So for this setup, you can use pretty much any Linux or Windows virtual machine. I'm using an AWS Lightsail FreeBSD machine for this.

You'll need to install Go on the machine unless you want to build the executable and send it over.

If you're using LightSail, make sure to open up port 5000. This port will need to be opened on whatever firewall or service you're using.

Temperature from Raspberry Pi with Go

Here's what our endpoint is going to look like.

It will be an application that:

  • Hosts an API to catch the POST command sent from the Raspberry PI
  • Stores the results in a SQLlite database
  • Retrieves the last 10 of those results.

So let's do 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
Enter fullscreen mode Exit fullscreen mode

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"
)
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

Then, below that we'll create a spot for our db instance:

var db *sql.DB
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

Let's take a look at this.

First, we'll connect to the database:

db, dbError = sql.Open("sqlite3", "./readings.db")
Check(dbError)
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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!",
        })
    }
}
Enter fullscreen mode Exit fullscreen mode

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 10")

    // 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
}
Enter fullscreen mode Exit fullscreen mode

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 Gin 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()
Enter fullscreen mode Exit fullscreen mode

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})
    })
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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")
Enter fullscreen mode Exit fullscreen mode

And, we're done! You can view the full source code here.

Once you start this service, you can accept POST commands from your Raspberry Pi, and store them in the database.

Temperature from Raspberry Pi with Go

Conclusion

This was a pretty lengthy tutorial, I know. Here's what we learned:

  • How to retrieve the temperature from a sensor
  • How to send that data in JSON
  • Build an API endpoint to receive it
  • Store the data in SQLite database

It's a lot of work upfront that helps you understand how IoT works, and how you can build your own system. If you don't want to go through the tutorial, you can watch me put it together in a stream or download the source code here.

I'll be adding to this project in the future and tightening things up, making it into a real product. I'll be streaming it on Twitch, so follow me if you're interested in watching!

If you decide to build this or add on to it, let me know!

Helpful courses to learn more:

Top comments (4)

Collapse
 
devhammed profile image
Hammed Oyedele • Edited

Nice but I am not sure why you are not using LIMIT 10 in getLastTen like this:

 func getLastTen() []Reading {

    // query the database for readings
-    rows, _ := db.Query("SELECT TimeStamp, Temperature, Humidity from reading LIMIT 20")
+    rows, _ := db.Query("SELECT TimeStamp, Temperature, Humidity from reading LIMIT 10")

    // 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
}
Collapse
 
jeremycmorgan profile image
Jeremy Morgan

Ah, yes I took the code from my stream and forgot to change that. Thank you for the feedback!

Collapse
 
sjellen profile image
SJellen

Check out episode 8 of codenewbie. It’s all about this ........

When William Jeffries had to think of a project to work on as a bootcamp student, he decided to build an app that could detect and report temperatures in apartments when they dropped below a certain degree. His mission was to help people find heat in the winter, and he called it Heat Seek. In a few months, his student project grew past the classroom walls, getting the attention of city officals, entrepreneurs, and citizens excited to help. William tells us how the technology works, how he built the app as he was learning to code himself, and about the mindset that helped him get through the doubts many newbies face.

Collapse
 
sanderdebr profile image
sanderdebr

Awesome! Thanks for this.