-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
466 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
# Sensirion SCD30 CO2 sensor i2c driver module for Golang | ||
|
||
## Overview | ||
|
||
With this module Sensirion SCD30 CO2 sensor can be accessed throug i2c bus. | ||
Implemented: | ||
- starting and stopping continuous measurements | ||
- checking for ready measurement | ||
- reading ready measurement | ||
- getting and setting temperature compensation | ||
|
||
## Example | ||
|
||
```go | ||
package main | ||
|
||
import ( | ||
"log" | ||
"time" | ||
|
||
"github.com/pvainio/scd30" | ||
"periph.io/x/conn/v3/i2c/i2creg" | ||
"periph.io/x/host/v3" | ||
) | ||
|
||
func main() { | ||
if _, err := host.Init(); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
bus, err := i2creg.Open("") | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer bus.Close() | ||
|
||
dev, err := scd30.Open(bus) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
var interval uint16 = 5 | ||
|
||
dev.StartMeasurements(interval) | ||
|
||
for { | ||
time.Sleep(time.Duration(interval) * time.Second) | ||
if hasMeasurement, err := dev.HasMeasurement(); err != nil { | ||
log.Fatalf("error %v", err) | ||
} else if !hasMeasurement { | ||
return | ||
} | ||
|
||
m, err := dev.GetMeasurement() | ||
if err != nil { | ||
log.Fatalf("error %v", err) | ||
} | ||
|
||
log.Printf("Got measure %f ppm %f%% %fC", m.CO2, m.Humidity, m.Temperature) | ||
} | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package scd30_test | ||
|
||
import ( | ||
"log" | ||
"time" | ||
|
||
"github.com/pvainio/scd30" | ||
"periph.io/x/conn/v3/i2c/i2creg" | ||
"periph.io/x/host/v3" | ||
) | ||
|
||
func Example() { | ||
if _, err := host.Init(); err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
bus, err := i2creg.Open("") | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
defer bus.Close() | ||
|
||
dev, err := scd30.Open(bus) | ||
if err != nil { | ||
log.Fatal(err) | ||
} | ||
|
||
var interval uint16 = 5 | ||
|
||
dev.StartMeasurements(interval) | ||
|
||
for { | ||
time.Sleep(time.Duration(interval) * time.Second) | ||
if hasMeasurement, err := dev.HasMeasurement(); err != nil { | ||
log.Fatalf("error %v", err) | ||
} else if !hasMeasurement { | ||
return | ||
} | ||
|
||
m, err := dev.GetMeasurement() | ||
if err != nil { | ||
log.Fatalf("error %v", err) | ||
} | ||
|
||
log.Printf("Got measure %f ppm %f%% %fC", m.CO2, m.Humidity, m.Temperature) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
module github.com/pvainio/scd30 | ||
|
||
go 1.17 | ||
|
||
require ( | ||
github.com/sigurn/crc8 v0.0.0-20160107002456-e55481d6f45c | ||
periph.io/x/conn/v3 v3.6.9 | ||
periph.io/x/host/v3 v3.7.1 | ||
) | ||
|
||
require github.com/sigurn/utils v0.0.0-20190728110027-e1fefb11a144 // indirect |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
github.com/jonboulle/clockwork v0.2.2 h1:UOGuzwb1PwsrDAObMuhUnj0p5ULPj8V/xJ7Kx9qUBdQ= | ||
github.com/jonboulle/clockwork v0.2.2/go.mod h1:Pkfl5aHPm1nk2H9h0bjmnJD/BcgbGXUBGnn1kMkgxc8= | ||
github.com/sigurn/crc8 v0.0.0-20160107002456-e55481d6f45c h1:hk0Jigjfq59yDMgd6bzi22Das5tyxU0CtOkh7a9io84= | ||
github.com/sigurn/crc8 v0.0.0-20160107002456-e55481d6f45c/go.mod h1:cyrWuItcOVIGX6fBZ/G00z4ykprWM7hH58fSavNkjRg= | ||
github.com/sigurn/utils v0.0.0-20190728110027-e1fefb11a144 h1:ccb8W1+mYuZvlpn/mJUMAbsFHTMCpcJBS78AsBQxNcY= | ||
github.com/sigurn/utils v0.0.0-20190728110027-e1fefb11a144/go.mod h1:VRI4lXkrUH5Cygl6mbG1BRUfMMoT2o8BkrtBDUAm+GU= | ||
periph.io/x/conn/v3 v3.6.9 h1:cSAvXC6IRRYC9pTW/Fzhp0a7zq+aeAxV8+/JZ+oxwZI= | ||
periph.io/x/conn/v3 v3.6.9/go.mod h1:UqWNaPMosWmNCwtufoTSTTYhB2wXWsMRAJyo1PlxO4Q= | ||
periph.io/x/d2xx v0.0.4/go.mod h1:38Euaaj+s6l0faIRHh32a+PrjXvxFTFkPBEQI0TKg34= | ||
periph.io/x/host/v3 v3.7.1 h1:SAe/7IWSOoFsqh2/74+SxbqehzOPny+jAPs25fd/NUI= | ||
periph.io/x/host/v3 v3.7.1/go.mod h1:kqMB+cJHtIPQCCMqDoiIMwr0pu1p+qQObkrPha3mX6E= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
package scd30 | ||
|
||
import ( | ||
"bytes" | ||
"encoding/binary" | ||
"fmt" | ||
"math" | ||
"sync" | ||
"time" | ||
|
||
"periph.io/x/conn/v3/i2c" | ||
|
||
"github.com/sigurn/crc8" | ||
) | ||
|
||
type Measurement struct { | ||
CO2 float32 | ||
Temperature float32 | ||
Humidity float32 | ||
} | ||
|
||
type SCD30 struct { | ||
dev *i2c.Dev | ||
} | ||
|
||
var mutex sync.Mutex | ||
|
||
func Open(bus i2c.Bus) (*SCD30, error) { | ||
mutex.Lock() | ||
defer mutex.Unlock() | ||
|
||
dev := &i2c.Dev{Addr: 0x61, Bus: bus} | ||
|
||
return &SCD30{dev: dev}, nil | ||
} | ||
|
||
// StartMeasurements starts continous measerements at given interval seconds | ||
func (dev SCD30) StartMeasurements(interval uint16) error { | ||
mutex.Lock() | ||
defer mutex.Unlock() | ||
|
||
// Set measurement interval | ||
if err := dev.sendCommandArg(0x4600, interval); err != nil { | ||
return err | ||
} | ||
|
||
// Start continuous measurements | ||
if err := dev.sendCommandArg(0x0010, 0); err != nil { | ||
return err | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// StopMeasurements stops continuous measurements | ||
func (dev SCD30) StopMeasurements() error { | ||
mutex.Lock() | ||
defer mutex.Unlock() | ||
return dev.sendCommand(0x0104) | ||
} | ||
|
||
// GetTemperatureOffset gets temperature offset to compensate internal | ||
// heating. Value is 1/100C | ||
func (dev SCD30) GetTemperatureOffset() (uint16, error) { | ||
mutex.Lock() | ||
defer mutex.Unlock() | ||
|
||
if err := dev.sendCommand(0x5403); err != nil { | ||
return 0, err | ||
} | ||
|
||
data, err := dev.readData(3) | ||
|
||
if err != nil { | ||
return 0, err | ||
} | ||
data, err = readValid16(bytes.NewBuffer(data)) | ||
if err != nil { | ||
return 0, err | ||
} else { | ||
return binary.BigEndian.Uint16(data), nil | ||
} | ||
} | ||
|
||
// SetTemperatureOffset sets temperature offset to compensate internal | ||
// heating. Value is 1/100C | ||
func (dev SCD30) SetTemperatureOffset(offset uint16) error { | ||
mutex.Lock() | ||
defer mutex.Unlock() | ||
return dev.sendCommandArg(0x5403, offset) | ||
} | ||
|
||
// GetMeasurement returns ready measurement. HasMeasurement should be | ||
// used first to check if there is one. | ||
func (dev SCD30) GetMeasurement() (*Measurement, error) { | ||
mutex.Lock() | ||
defer mutex.Unlock() | ||
|
||
if err := dev.sendCommand(0x0300); err != nil { | ||
return nil, err | ||
} | ||
|
||
if data, err := dev.readData(18); err != nil { | ||
return nil, err | ||
} else { | ||
buf := bytes.NewBuffer(data) | ||
co2, err := readValidFloat32(buf) | ||
if err != nil { | ||
return nil, err | ||
} | ||
temp, err := readValidFloat32(buf) | ||
if err != nil { | ||
return nil, err | ||
} | ||
hum, err := readValidFloat32(buf) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &Measurement{CO2: co2, Temperature: temp, Humidity: hum}, nil | ||
} | ||
} | ||
|
||
// HasMeasurement checks if there is ready measurement | ||
func (dev SCD30) HasMeasurement() (bool, error) { | ||
mutex.Lock() | ||
defer mutex.Unlock() | ||
|
||
if err := dev.sendCommand(0x0202); err != nil { | ||
return false, err | ||
} | ||
|
||
if data, err := dev.readData(3); err != nil { | ||
return false, err | ||
} else { | ||
if data[2] != crc(data[:2]) { | ||
return false, fmt.Errorf("crc error, expected %x got %x", crc(data[:2]), data[2]) | ||
} | ||
return data[1] == 1, nil | ||
} | ||
} | ||
|
||
func (dev SCD30) readData(len int) ([]byte, error) { | ||
|
||
data := make([]byte, len) | ||
|
||
if err := dev.dev.Tx(nil, data); err != nil { | ||
return nil, err | ||
} else { | ||
return data, nil | ||
} | ||
} | ||
|
||
func (dev SCD30) sendCommand(cmd uint16) error { | ||
cmdData := make([]byte, 2) | ||
binary.BigEndian.PutUint16(cmdData, cmd) | ||
return dev.writeAndWait(cmdData) | ||
} | ||
|
||
func (dev SCD30) sendCommandArg(cmd uint16, arg uint16) error { | ||
cmdData := make([]byte, 2) | ||
argData := make([]byte, 2) | ||
binary.BigEndian.PutUint16(cmdData, cmd) | ||
binary.BigEndian.PutUint16(argData, arg) | ||
write := []byte{cmdData[0], cmdData[1], argData[0], argData[1], crc(argData)} | ||
return dev.writeAndWait(write) | ||
} | ||
|
||
func (dev SCD30) writeAndWait(data []byte) error { | ||
|
||
if err := dev.dev.Tx(data, nil); err != nil { | ||
return err | ||
} | ||
|
||
time.Sleep(4 * time.Millisecond) | ||
|
||
return nil | ||
} | ||
|
||
func crc(data []byte) byte { | ||
return crc8.Checksum(data, crcTable) | ||
} | ||
|
||
func readValid16(buf *bytes.Buffer) ([]byte, error) { | ||
data := buf.Next(2) | ||
crc8, err := buf.ReadByte() | ||
if err != nil { | ||
return nil, err | ||
} | ||
if crc(data) != crc8 { | ||
return nil, fmt.Errorf("crc error, expected %x got %x", crc(data), crc8) | ||
} | ||
return data, nil | ||
} | ||
|
||
func readValidFloat32(buf *bytes.Buffer) (float32, error) { | ||
var out bytes.Buffer | ||
|
||
for i := 0; i < 2; i++ { | ||
if data, err := readValid16(buf); err != nil { | ||
return float32(math.NaN()), err | ||
} else { | ||
out.Write(data) | ||
} | ||
} | ||
uint := binary.BigEndian.Uint32(out.Bytes()) | ||
return math.Float32frombits(uint), nil | ||
} | ||
|
||
var crcTable *crc8.Table | ||
|
||
func init() { | ||
crcTable = crc8.MakeTable(crc8.Params{Poly: 0x31, Init: 0xff, RefIn: false, RefOut: false, XorOut: 0x00, Check: 0xff, Name: "Sensirion"}) | ||
} |
Oops, something went wrong.