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.
devices/sen6x/status_test.go

366 lines
7.4 KiB
Go

// Copyright 2026 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 sen6x
import (
"testing"
"github.com/google/go-cmp/cmp"
)
var sen69CAllErrsSet uint32 = 1<<21 | // FanSpeedErr
1<<4 | // FanErr
1<<11 | // PMSensorErr
1<<6 | // RHTSensorErr
1<<12 | // CO2SensorErr (SEN69C uses bit 12)
1<<7 | // GasSensorErr
1<<10 // HCHOSensorErr
func TestDevReadDeviceStatus(t *testing.T) {
cmd := []byte{0xd2, 0x06}
cases := []writeAndReadTestCase[*Status]{
{
name: "all unset, SEN66",
model: SEN66,
tx: cmd,
rx: []byte{0x00, 0x00, 0x81, 0x00, 0x00, 0x81},
want: &Status{},
},
{
name: "all set, SEN69C",
model: SEN69C,
tx: cmd,
rx: packWordsWithCRC(
[]uint16{uint16(sen69CAllErrsSet >> 16), uint16(sen69CAllErrsSet)}),
want: &Status{
FanSpeedErr: true,
FanErr: true,
PMSensorErr: true,
RHTSensorErr: true,
CO2SensorErr: true,
GasSensorErr: true,
HCHOSensorErr: true,
},
},
{
// writeAndRead will fail because no response is set.
name: "read error",
model: SEN69C,
tx: cmd,
wantErr: true,
dontPanic: true,
},
}
runWriteAndReadTests(t, cases, (*Dev).ReadDeviceStatus)
}
func TestDevReadAndClearDeviceStatus(t *testing.T) {
cmd := []byte{0xd2, 0x10}
cases := []writeAndReadTestCase[*Status]{
{
name: "all unset, SEN66",
model: SEN66,
tx: cmd,
rx: []byte{0x00, 0x00, 0x81, 0x00, 0x00, 0x81},
want: &Status{},
},
{
name: "all set, SEN69C",
model: SEN69C,
tx: cmd,
rx: packWordsWithCRC(
[]uint16{uint16(sen69CAllErrsSet >> 16), uint16(sen69CAllErrsSet)}),
want: &Status{
FanSpeedErr: true,
FanErr: true,
PMSensorErr: true,
RHTSensorErr: true,
CO2SensorErr: true,
GasSensorErr: true,
HCHOSensorErr: true,
},
},
{
// writeAndRead will fail because no response is set.
name: "read error",
model: SEN69C,
tx: cmd,
wantErr: true,
dontPanic: true,
},
}
runWriteAndReadTests(t, cases, (*Dev).ReadAndClearDeviceStatus)
}
func TestRegisterBitBool(t *testing.T) {
cases := []struct {
name string
register uint32
bit uint
want bool
}{
{
name: "bit 0 set",
register: 0x00000001,
bit: 0,
want: true,
},
{
name: "bit 0 not set",
register: 0x00000000,
bit: 0,
want: false,
},
{
name: "bit 31 set",
register: 0x80000000,
bit: 31,
want: true,
},
{
name: "bit 31 not set",
register: 0x7FFFFFFF,
bit: 31,
want: false,
},
{
name: "bit 21 set",
register: 1 << 21,
bit: 21,
want: true,
},
{
name: "bit 21 not set",
register: 0xf3000a7d,
bit: 21,
want: false,
},
{
name: "adjacent bits don't bleed",
register: 0b1101,
bit: 1,
want: false,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := registerBitBool(tc.register, tc.bit)
if got != tc.want {
t.Errorf("got %v, want %v", got, tc.want)
}
})
}
}
func TestStatusFromRegister(t *testing.T) {
cases := []struct {
name string
model Model
register uint32
want *Status
}{
{
name: "no errors, SEN66",
model: SEN66,
register: 0x00000000,
want: &Status{},
},
{
name: "fan speed error, bit 21",
model: SEN66,
register: 1 << 21,
want: &Status{FanSpeedErr: true},
},
{
name: "fan error, bit 4",
model: SEN66,
register: 1 << 4,
want: &Status{FanErr: true},
},
{
name: "PM sensor error, bit 11",
model: SEN66,
register: 1 << 11,
want: &Status{PMSensorErr: true},
},
{
name: "RHT sensor error, bit 6",
model: SEN66,
register: 1 << 6,
want: &Status{RHTSensorErr: true},
},
{
// SEN66 CO2 error is bit 9.
name: "CO2 sensor error SEN66, bit 9",
model: SEN66,
register: 1 << 9,
want: &Status{CO2SensorErr: true},
},
{
// SEN63C/SEN69C CO2 error is bit 12.
name: "CO2 sensor error SEN63C, bit 12",
model: SEN63C,
register: 1 << 12,
want: &Status{CO2SensorErr: true},
},
{
// SEN66 CO2 error is bit 9; bit 12 should not trigger CO2SensorErr.
name: "SEN66 ignores bit 12 for CO2",
model: SEN66,
register: 1 << 12,
want: &Status{},
},
{
// SEN63C CO2 error is bit 12; bit 9 should not trigger CO2SensorErr.
name: "SEN63C ignores bit 9 for CO2",
model: SEN63C,
register: 1 << 9,
want: &Status{},
},
{
name: "gas sensor error SEN66, bit 7",
model: SEN66,
register: 1 << 7,
want: &Status{GasSensorErr: true},
},
{
// SEN62 has no gas sensor; bit 7 should not set GasSensorErr.
name: "SEN62 ignores gas sensor bit",
model: SEN62,
register: 1 << 7,
want: &Status{},
},
{
name: "HCHO sensor error SEN68, bit 10",
model: SEN68,
register: 1 << 10,
want: &Status{HCHOSensorErr: true},
},
{
// SEN66 has no HCHO sensor; bit 10 should not set HCHOSensorErr.
name: "SEN66 ignores HCHO sensor bit",
model: SEN66,
register: 1 << 10,
want: &Status{},
},
{
name: "all errors set, SEN69C",
model: SEN69C,
register: sen69CAllErrsSet,
want: &Status{
FanSpeedErr: true,
FanErr: true,
PMSensorErr: true,
RHTSensorErr: true,
CO2SensorErr: true,
GasSensorErr: true,
HCHOSensorErr: true,
},
},
{
// SEN62 only has fan, PM, and RHT errors.
name: "all applicable errors set, SEN62",
model: SEN62,
register: 1<<21 | // FanSpeedErr
1<<4 | // FanErr
1<<11 | // PMSensorErr
1<<6, // RHTSensorErr
want: &Status{
FanSpeedErr: true,
FanErr: true,
PMSensorErr: true,
RHTSensorErr: true,
},
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
d := &Dev{model: tc.model}
got := d.statusFromRegister(tc.register)
if diff := cmp.Diff(tc.want, got); diff != "" {
t.Errorf("mismatch (-want +got):\n%s", diff)
}
})
}
}
func TestStatusAnyErr(t *testing.T) {
cases := []struct {
name string
status Status
want bool
}{
{
name: "no errors",
status: Status{},
want: false,
},
{
name: "fan speed error only",
status: Status{FanSpeedErr: true},
want: true,
},
{
name: "fan error only",
status: Status{FanErr: true},
want: true,
},
{
name: "PM sensor error only",
status: Status{PMSensorErr: true},
want: true,
},
{
name: "RHT sensor error only",
status: Status{RHTSensorErr: true},
want: true,
},
{
name: "CO2 sensor error only",
status: Status{CO2SensorErr: true},
want: true,
},
{
name: "gas sensor error only",
status: Status{GasSensorErr: true},
want: true,
},
{
name: "HCHO sensor error only",
status: Status{HCHOSensorErr: true},
want: true,
},
{
name: "all errors",
status: Status{
FanSpeedErr: true,
FanErr: true,
PMSensorErr: true,
RHTSensorErr: true,
CO2SensorErr: true,
GasSensorErr: true,
HCHOSensorErr: true,
},
want: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := tc.status.AnyErr()
if got != tc.want {
t.Errorf("got %v, want %v", got, tc.want)
}
})
}
}