pmsa003i.go (2903B)
1 // Copyright 2020 by Brian C. Lane <bcl@brianlane.com>. All rights reserved. 2 // Use of this source code is governed under the Apache License, Version 2.0 3 // that can be found in the LICENSE file. 4 5 package pmsa003i 6 7 import ( 8 "fmt" 9 10 "periph.io/x/periph/conn" 11 "periph.io/x/periph/conn/i2c" 12 ) 13 14 func checksum(data []byte) bool { 15 var cksum uint16 16 for i := 0; i < len(data)-2; i++ { 17 cksum = cksum + uint16(data[i]) 18 } 19 return word(data, 0x1e) == cksum 20 } 21 22 // Results contains the measurements from the PMSA003i 23 type Results struct { 24 CfPm1 uint16 // PM1.0 in μg/m3 standard particle 25 CfPm2_5 uint16 // PM2.5 in μg/m3 standard particle 26 CfPm10 uint16 // PM10 in μg/m3 standard particle 27 EnvPm1 uint16 // PM1.0 in μg/m3 atmospheric environment 28 EnvPm2_5 uint16 // PM2.5 in μg/m3 atmospheric environment 29 EnvPm10 uint16 // PM10 in μg/m3 atmospheric environment 30 Cnt0_3 uint16 // Count of particles > 0.3μm in 0.1L of air 31 Cnt0_5 uint16 // Count of particles > 0.5μm in 0.1L of air 32 Cnt1 uint16 // Count of particles > 1.0μm in 0.1L of air 33 Cnt2_5 uint16 // Count of particles > 2.5μm in 0.1L of air 34 Cnt5 uint16 // Count of particles > 5.0μm in 0.1L of air 35 Cnt10 uint16 // Count of particles > 10.0μm in 0.1L of air 36 Version uint8 37 } 38 39 // New returns a PMSA003I device struct for communicating with the device 40 // 41 func New(i i2c.Bus) (*Dev, error) { 42 d := &Dev{i2c: &i2c.Dev{Bus: i, Addr: 0x12}} 43 44 _, err := d.ReadSensor() 45 if err != nil { 46 return nil, err 47 } 48 return d, nil 49 } 50 51 // Dev holds the connection and error details for the device 52 type Dev struct { 53 i2c conn.Conn // i2c device handle for the pmsa003i 54 err error //nolint 55 } 56 57 // Halt implements conn.Resource. 58 func (d *Dev) Halt() error { 59 return nil 60 } 61 62 // ReadSensor returns particle measurement results 63 func (d *Dev) ReadSensor() (Results, error) { 64 // Receive 32 bytes 65 var data [32]byte 66 if err := d.i2c.Tx(nil, data[:]); err != nil { 67 return Results{}, fmt.Errorf("pmsa003i: Error while reading the sensor: %w", err) 68 } 69 70 if word(data[:], 0) != 0x424d { 71 return Results{}, fmt.Errorf("pmsa003i: Bad start word") 72 } 73 if !checksum(data[:]) { 74 return Results{}, fmt.Errorf("pmsa003i: Bad checksum") 75 } 76 if data[0x1d] != 0x00 { 77 return Results{}, fmt.Errorf("pmsa003i: Error code %x", data[0x1d]) 78 } 79 80 return Results{ 81 CfPm1: word(data[:], 0x04), 82 CfPm2_5: word(data[:], 0x06), 83 CfPm10: word(data[:], 0x08), 84 EnvPm1: word(data[:], 0x0a), 85 EnvPm2_5: word(data[:], 0x0c), 86 EnvPm10: word(data[:], 0x0e), 87 Cnt0_3: word(data[:], 0x10), 88 Cnt0_5: word(data[:], 0x12), 89 Cnt1: word(data[:], 0x14), 90 Cnt2_5: word(data[:], 0x16), 91 Cnt5: word(data[:], 0x18), 92 Cnt10: word(data[:], 0x1a), 93 Version: data[0x1c], 94 }, nil 95 } 96 97 // word returns 16 bits from the byte stream, starting at index i 98 func word(data []byte, i int) uint16 { 99 return uint16(data[i])<<8 + uint16(data[i+1]) 100 }