mirror of https://github.com/periph/devices
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
193 lines
4.3 KiB
Go
193 lines
4.3 KiB
Go
// Copyright 2021 The Periph Authors. All rights reserved.
|
|
// Use of this source code is governed under the Apache License, Version 2.0
|
|
// that can be found in the LICENSE file.
|
|
|
|
package sgp30
|
|
|
|
import (
|
|
"context"
|
|
"encoding/binary"
|
|
"errors"
|
|
"log"
|
|
"strconv"
|
|
"sync"
|
|
"time"
|
|
|
|
"periph.io/x/conn/v3"
|
|
"periph.io/x/conn/v3/i2c"
|
|
)
|
|
|
|
const (
|
|
initAirQuality uint16 = 0x2003
|
|
measureAirQuality uint16 = 0x2008
|
|
getIAQBaseline uint16 = 0x2015
|
|
setIAQBaseline uint16 = 0x201e
|
|
setHumidity uint16 = 0x2061
|
|
measureTest uint16 = 0x2032
|
|
getFeatureSetVersion uint16 = 0x202f
|
|
measureRawSignals uint16 = 0x2050
|
|
getTVOCBaseline uint16 = 0x20b3
|
|
setTVOCBaseline uint16 = 0x2077
|
|
|
|
i2CAddress uint16 = 0x58
|
|
)
|
|
|
|
// commandDuration maps the defined maximum measurement duration from the sensor
|
|
var commandDuration = map[uint16]time.Duration{
|
|
initAirQuality: time.Millisecond * 10,
|
|
measureAirQuality: time.Millisecond * 12,
|
|
getIAQBaseline: time.Millisecond * 10,
|
|
setIAQBaseline: time.Millisecond * 10,
|
|
setHumidity: time.Millisecond * 10,
|
|
measureTest: time.Millisecond * 220,
|
|
getFeatureSetVersion: time.Millisecond * 10,
|
|
measureRawSignals: time.Millisecond * 25,
|
|
getTVOCBaseline: time.Millisecond * 10,
|
|
setTVOCBaseline: time.Millisecond * 10,
|
|
}
|
|
|
|
// commandResponseLength maps the defined response length including the CRC
|
|
var commandResponseLength = map[uint16]int{
|
|
measureAirQuality: 6,
|
|
getIAQBaseline: 6,
|
|
measureTest: 3,
|
|
getFeatureSetVersion: 3,
|
|
measureRawSignals: 6,
|
|
getTVOCBaseline: 3,
|
|
}
|
|
|
|
// CO2 represents the current carbon dioxide value in ppm
|
|
type CO2 uint16
|
|
|
|
func (c CO2) String() string {
|
|
return strconv.Itoa(int(c)) + "ppm"
|
|
}
|
|
|
|
func (c *CO2) set(b []byte) {
|
|
*c = (CO2)(binary.BigEndian.Uint16(b))
|
|
}
|
|
|
|
// TVOC represents the current total volatile organic compounds value in ppb
|
|
type TVOC uint16
|
|
|
|
func (t TVOC) String() string {
|
|
return strconv.Itoa(int(t)) + "ppb"
|
|
}
|
|
|
|
func (t *TVOC) set(b []byte) {
|
|
*t = (TVOC)(binary.BigEndian.Uint16(b))
|
|
}
|
|
|
|
// Env represents measurements from an environmental sensor.
|
|
type Env struct {
|
|
CO2 CO2
|
|
TVOC TVOC
|
|
}
|
|
|
|
// NewI2C returns an object that communicates over I2C to SGP30 environmental sensor.
|
|
//
|
|
// The address must be 0x58.
|
|
func NewI2C(b i2c.Bus, ctx context.Context) (*Dev, error) {
|
|
d := &Dev{
|
|
d: &i2c.Dev{Bus: b, Addr: i2CAddress},
|
|
env: Env{
|
|
CO2: 400,
|
|
TVOC: 0,
|
|
},
|
|
}
|
|
if err := d.makeDev(ctx); err != nil {
|
|
return nil, err
|
|
}
|
|
return d, nil
|
|
}
|
|
|
|
// Dev is a handle to an initialized SGP30 device.
|
|
type Dev struct {
|
|
d conn.Conn
|
|
mu sync.Mutex
|
|
env Env
|
|
}
|
|
|
|
// AirQuality return the value struct for the sensor
|
|
func (d *Dev) AirQuality() Env {
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
return d.env
|
|
}
|
|
|
|
func (d *Dev) makeDev(ctx context.Context) error {
|
|
// Sending a "sgp30_iaq_init" command starts the air quality measurement
|
|
if err := d.initAirQuality(); err != nil {
|
|
return err
|
|
}
|
|
|
|
// After the "sgp30_iaq_init" command, a "sgp30_measure_iaq" command has to be sent in regular
|
|
// intervals of 1s to ensure proper operation of the dynamic baseline compensation algorithm.
|
|
if err := d.measure(); err != nil {
|
|
log.Print(err)
|
|
}
|
|
|
|
ticker := time.NewTicker(1 * time.Second)
|
|
go func() {
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
if err := d.measure(); err != nil {
|
|
log.Print(err)
|
|
}
|
|
case <-ctx.Done():
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Dev) initAirQuality() error {
|
|
err := d.writeCommand(initAirQuality)
|
|
if err == nil {
|
|
time.Sleep(time.Second * 20)
|
|
}
|
|
return err
|
|
}
|
|
|
|
func (d *Dev) measure() error {
|
|
buf := make([]byte, commandResponseLength[measureAirQuality])
|
|
if err := d.readCommand(measureAirQuality, buf); err != nil {
|
|
return err
|
|
}
|
|
|
|
d.mu.Lock()
|
|
defer d.mu.Unlock()
|
|
|
|
d.env.CO2.set(buf[0:2])
|
|
d.env.TVOC.set(buf[3:5])
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *Dev) readCommand(cmd uint16, b []byte) error {
|
|
if len(b) != commandResponseLength[cmd] {
|
|
return errors.New("response length mismatch")
|
|
}
|
|
|
|
regAddr := []byte{byte(cmd >> 8), byte(cmd & 0xFF)}
|
|
if err := d.d.Tx(regAddr, nil); err != nil {
|
|
return err
|
|
}
|
|
time.Sleep(commandDuration[cmd])
|
|
|
|
return d.d.Tx(nil, b)
|
|
}
|
|
|
|
func (d *Dev) writeCommand(cmd uint16) error {
|
|
regAddr := []byte{byte(cmd >> 8), byte(cmd & 0xFF)}
|
|
if err := d.d.Tx(regAddr, nil); err != nil {
|
|
return err
|
|
}
|
|
time.Sleep(commandDuration[cmd])
|
|
return nil
|
|
}
|