tinygo.org/x/drivers@v0.27.1-0.20240509133757-7dbca2a54349/scd4x/scd4x.go (about)

     1  // Package scd4x provides a driver for the scd4x I2C envrironment sensor.
     2  //
     3  // Datasheet: https://sensirion.com/media/documents/C4B87CE6/627C2DCD/CD_DS_SCD40_SCD41_Datasheet_D1.pdf
     4  //
     5  // This driver is heavily influenced by the scd4x code from Adafruit for CircuitPython:
     6  // https://github.com/adafruit/Adafruit_CircuitPython_SCD4X
     7  // Thank you!
     8  package scd4x // import "tinygo.org/x/drivers/scd4x"
     9  
    10  import (
    11  	"encoding/binary"
    12  	"time"
    13  
    14  	"tinygo.org/x/drivers"
    15  )
    16  
    17  type Device struct {
    18  	bus     drivers.I2C
    19  	tx      []byte
    20  	rx      []byte
    21  	Address uint8
    22  
    23  	// used to cache the most recent readings
    24  	co2         uint16
    25  	temperature uint16
    26  	humidity    uint16
    27  }
    28  
    29  // New returns SCD4x device for the provided I2C bus using default address of 0x62.
    30  func New(i2c drivers.I2C) *Device {
    31  	return &Device{
    32  		bus:     i2c,
    33  		tx:      make([]byte, 5),
    34  		rx:      make([]byte, 18),
    35  		Address: Address,
    36  	}
    37  }
    38  
    39  // Configure the device.
    40  func (d *Device) Configure() (err error) {
    41  	if err := d.StopPeriodicMeasurement(); err != nil {
    42  		return err
    43  	}
    44  	time.Sleep(500 * time.Millisecond)
    45  
    46  	// reset the chip
    47  	if err := d.sendCommand(CmdReinit); err != nil {
    48  		return err
    49  	}
    50  
    51  	time.Sleep(20 * time.Millisecond)
    52  	return
    53  }
    54  
    55  // Connected returns whether sensor has been found.
    56  func (d *Device) Connected() bool {
    57  	// TODO: something here to check if the sensor is connected
    58  	return true
    59  }
    60  
    61  // DataReady checks the sensor to see if new data is available.
    62  func (d *Device) DataReady() (bool, error) {
    63  	if err := d.sendCommandWithResult(CmdDataReady, d.rx[0:3]); err != nil {
    64  		return false, err
    65  	}
    66  	return !(d.rx[0]&0x07 == 0 && d.rx[1] == 0), nil
    67  }
    68  
    69  // StartPeriodicMeasurement puts the sensor into working mode, about 5s per measurement.
    70  func (d *Device) StartPeriodicMeasurement() error {
    71  	return d.sendCommand(CmdStartPeriodicMeasurement)
    72  }
    73  
    74  // StopPeriodicMeasurement stops the sensor reading data.
    75  func (d *Device) StopPeriodicMeasurement() error {
    76  	return d.sendCommand(CmdStopPeriodicMeasurement)
    77  }
    78  
    79  // StartLowPowerPeriodicMeasurement puts the sensor into low power working mode,
    80  // about 30s per measurement.
    81  func (d *Device) StartLowPowerPeriodicMeasurement() error {
    82  	return d.sendCommand(CmdStartLowPowerPeriodicMeasurement)
    83  }
    84  
    85  // ReadData reads the data from the sensor and caches it.
    86  func (d *Device) ReadData() error {
    87  	if err := d.sendCommandWithResult(CmdReadMeasurement, d.rx[0:9]); err != nil {
    88  		return err
    89  	}
    90  	d.co2 = binary.BigEndian.Uint16(d.rx[0:2])
    91  	d.temperature = binary.BigEndian.Uint16(d.rx[3:5])
    92  	d.humidity = binary.BigEndian.Uint16(d.rx[6:8])
    93  	return nil
    94  }
    95  
    96  // ReadCO2 returns the CO2 concentration in PPM (parts per million).
    97  func (d *Device) ReadCO2() (co2 int32, err error) {
    98  	ok, err := d.DataReady()
    99  	if err != nil {
   100  		return 0, err
   101  	}
   102  	if ok {
   103  		err = d.ReadData()
   104  	}
   105  	return int32(d.co2), err
   106  }
   107  
   108  // ReadTemperature returns the temperature in celsius milli degrees (°C/1000)
   109  func (d *Device) ReadTemperature() (temperature int32, err error) {
   110  	ok, err := d.DataReady()
   111  	if err != nil {
   112  		return 0, err
   113  	}
   114  	if ok {
   115  		err = d.ReadData()
   116  	}
   117  	// temp = -45 + 175 * value / 2¹⁶
   118  	return (-1 * 45000) + (21875 * (int32(d.temperature)) / 8192), err
   119  }
   120  
   121  // ReadTempC returns the value in the temperature value in Celsius.
   122  func (d *Device) ReadTempC() float32 {
   123  	t, _ := d.ReadTemperature()
   124  	return float32(t) / 1000
   125  }
   126  
   127  // ReadTempF returns the value in the temperature value in Fahrenheit.
   128  func (d *Device) ReadTempF() float32 {
   129  	return d.ReadTempC()*1.8 + 32.0
   130  }
   131  
   132  // ReadHumidity returns the current relative humidity in %rH.
   133  func (d *Device) ReadHumidity() (humidity int32, err error) {
   134  	ok, err := d.DataReady()
   135  	if err != nil {
   136  		return 0, err
   137  	}
   138  	if ok {
   139  		err = d.ReadData()
   140  	}
   141  	// humidity = 100 * value / 2¹⁶
   142  	return (25 * int32(d.humidity)) / 16384, err
   143  }
   144  
   145  func (d *Device) sendCommand(command uint16) error {
   146  	binary.BigEndian.PutUint16(d.tx[0:], command)
   147  	return d.bus.Tx(uint16(d.Address), d.tx[0:2], nil)
   148  }
   149  
   150  func (d *Device) sendCommandWithValue(command, value uint16) error {
   151  	binary.BigEndian.PutUint16(d.tx[0:], command)
   152  	binary.BigEndian.PutUint16(d.tx[2:], value)
   153  	d.tx[4] = crc8(d.tx[2:4])
   154  	return d.bus.Tx(uint16(d.Address), d.tx[0:5], nil)
   155  }
   156  
   157  func (d *Device) sendCommandWithResult(command uint16, result []byte) error {
   158  	binary.BigEndian.PutUint16(d.tx[0:], command)
   159  	if err := d.bus.Tx(uint16(d.Address), d.tx[0:2], nil); err != nil {
   160  		return err
   161  	}
   162  	time.Sleep(time.Millisecond)
   163  	return d.bus.Tx(uint16(d.Address), nil, result)
   164  }
   165  
   166  func crc8(buf []byte) uint8 {
   167  	var crc uint8 = 0xff
   168  	for _, b := range buf {
   169  		crc ^= b
   170  		for i := 0; i < 8; i++ {
   171  			if crc&0x80 != 0 {
   172  				crc = (crc << 1) ^ 0x31
   173  			} else {
   174  				crc <<= 1
   175  			}
   176  		}
   177  	}
   178  	return crc & 0xff
   179  }