lepton: device driver for the FLIR Lepton

Include CLI tool to query the camera, grab a single frame and trigger a
calibration.
pull/1/head
Marc-Antoine Ruel 9 years ago
parent 2853fb49f6
commit debd177aa5

@ -0,0 +1,550 @@
// Copyright 2017 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.
// "stringer" can be installed with "go get golang.org/x/tools/cmd/stringer"
//go:generate stringer -output=strings_gen.go -type=CameraStatus,command,FFCShutterMode,FFCState,ShutterPos,ShutterTempLockoutState
// Package cci declares the Camera Command Interface to interact with a FLIR
// Lepton over I²C.
//
// This protocol controls and queries the camera but is not used to read the
// images.
//
// Datasheet
//
// http://www.flir.com/uploadedFiles/OEM/Products/LWIR-Cameras/Lepton/FLIR-Lepton-Software-Interface-Description-Document.pdf
//
// Found via http://www.flir.com/cores/display/?id=51878
package cci
import (
"encoding/binary"
"errors"
"fmt"
"reflect"
"strconv"
"strings"
"sync"
"time"
"periph.io/x/periph/conn/i2c"
"periph.io/x/periph/conn/mmr"
"periph.io/x/periph/devices"
"periph.io/x/periph/devices/lepton/internal"
)
// StatusBit is the status as returned by the FLIR Lepton.
type StatusBit uint16
// Status bitmask.
const (
StatusBusy StatusBit = 0x1
StatusBootNormal StatusBit = 0x2
StatusBooted StatusBit = 0x4
StatusErrorMask StatusBit = 0xFF00
)
func (s StatusBit) String() string {
var o []string
if s&StatusBusy != 0 {
o = append(o, "Busy")
}
if s&StatusBootNormal != 0 {
o = append(o, "BootNormal")
}
if s&StatusBooted != 0 {
o = append(o, "Booted")
}
if v := s & StatusErrorMask; v != 0 {
o = append(o, "0x"+strconv.FormatUint(uint64(v)>>8, 16))
}
return strings.Join(o, "|")
}
// CameraStatus returns the status of the FLIR Lepton's camera.
type CameraStatus uint32
// Valid values for CameraStatus.
const (
SystemReady CameraStatus = 0
SystemInitializing CameraStatus = 1
SystemInLowPowerMode CameraStatus = 2
SystemGoingIntoStandby CameraStatus = 3
SystemFlatFieldInProcess CameraStatus = 4
)
// Status returns the camera status as returned by the camera.
type Status struct {
CameraStatus CameraStatus
CommandCount uint16
}
// ShutterTempLockoutState is used in FFCMode.
type ShutterTempLockoutState uint32
// Valid values for ShutterTempLockoutState.
const (
ShutterTempLockoutStateInactive ShutterTempLockoutState = 0
ShutterTempLockoutStateHigh ShutterTempLockoutState = 1
ShutterTempLockoutStateLow ShutterTempLockoutState = 2
)
// FFCShutterMode is used in FFCMode.
type FFCShutterMode uint32
// Valid values for FFCShutterMode.
const (
FFCShutterModeManual FFCShutterMode = 0
FFCShutterModeAuto FFCShutterMode = 1
FFCShutterModeExternal FFCShutterMode = 2
)
// ShutterPos returns the shutter position, which is used to calibrate the
// camera.
type ShutterPos uint32
// Valid values for ShutterPos.
const (
ShutterPosUnknown ShutterPos = 0xFFFFFFFF // -1
ShutterPosIdle ShutterPos = 0
ShutterPosOpen ShutterPos = 1
ShutterPosClosed ShutterPos = 2
ShutterPosBrakeOn ShutterPos = 3
)
// FFCState describes the Flat-Field Correction state.
type FFCState uint8
const (
// No FFC was requested.
FFCNever FFCState = 0
// FFC is in progress. It lasts 23 frames (at 27fps) so it lasts less than a second.
FFCInProgress FFCState = 1
// FFC was completed successfully.
FFCComplete FFCState = 2
)
// FFCMode
type FFCMode struct {
FFCShutterMode FFCShutterMode // Default: FFCShutterModeExternal
ShutterTempLockoutState ShutterTempLockoutState // Default: ShutterTempLockoutStateInactive
ElapsedTimeSinceLastFFC time.Duration // Uptime
DesiredFFCPeriod time.Duration // Default: 300s
DesiredFFCTempDelta devices.Celsius // Default: 3°C
ImminentDelay uint16 // Default: 52
VideoFreezeDuringFFC bool // Default: true
FFCDesired bool // Default: false
ExplicitCommandToOpen bool // Default: false
}
//
// Dev is the Lepton specific Command and Control Interface (CCI).
//
//
// Dev can safely accessed concurrently via multiple goroutines.
//
// This interface is accessed via I²C and provides access to view and modify
// the internal state.
//
// Maximum I²C speed is 1Mhz.
type Dev struct {
c conn
serial uint64
}
// New returns a driver for the FLIR Lepton CCI protocol.
func New(i i2c.Bus) (*Dev, error) {
d := &Dev{
c: conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: i, Addr: 0x2A}, Order: internal.Big16}},
}
// Wait for the device to be booted.
for {
if status, err := d.c.waitIdle(); err != nil {
return nil, err
} else if status == StatusBootNormal|StatusBooted {
return d, nil
}
//log.Printf("lepton not yet booted: 0x%02x", status)
// Polling rocks.
time.Sleep(5 * time.Millisecond)
}
}
// Init initializes the FLIR Lepton in raw 14 bits mode, enables telemetry as
// header.
func (d *Dev) Init() error {
if err := d.c.set(agcEnable, internal.Disabled); err != nil {
return err
}
// Setup telemetry to always be as the header. There's no reason to make this
// configurable by the user.
if err := d.c.set(sysTelemetryEnable, internal.Enabled); err != nil {
return err
}
if err := d.c.set(sysTelemetryLocation, internal.Header); err != nil {
return err
}
/*
// Verification code in case the I²C do not work properly.
f := internal.Enabled
if err := d.c.get(agcEnable, &f); err != nil {
return err
} else if f != internal.Disabled {
return fmt.Errorf("lepton-cci: internal verification for AGC failed %v", f)
}
if err := d.c.get(sysTelemetryEnable, &f); err != nil {
return err
} else if f != internal.Enabled {
return fmt.Errorf("lepton-cci: internal verification for telemetry flag failed %v", f)
}
hdr := internal.Footer
if err := d.c.get(sysTelemetryLocation, &hdr); err != nil {
return err
} else if hdr != internal.Header {
return fmt.Errorf("lepton-cci: internal verification for telemetry position failed %s", hdr)
}
*/
return nil
}
// WaitIdle waits for camera to be ready.
//
// It loops forever and returns the StatusBit.
func (d *Dev) WaitIdle() (StatusBit, error) {
return d.c.waitIdle()
}
// Halt stops the camera.
func (d *Dev) Halt() error {
// TODO(maruel): Doc says it won't restart. Yo.
return d.c.run(oemPowerDown)
}
// GetStatus return the status of the camera as known by the camera itself.
func (d *Dev) GetStatus() (*Status, error) {
var v internal.Status
if err := d.c.get(sysStatus, &v); err != nil {
return nil, err
}
return &Status{
CameraStatus: CameraStatus(v.CameraStatus),
CommandCount: v.CommandCount,
}, nil
}
// GetSerial returns the FLIR Lepton serial number.
func (d *Dev) GetSerial() (uint64, error) {
if d.serial == 0 {
out := uint64(0)
if err := d.c.get(sysSerialNumber, &out); err != nil {
return out, err
}
d.serial = out
}
return d.serial, nil
}
// GetUptime returns the uptime. Rolls over after 1193 hours.
func (d *Dev) GetUptime() (time.Duration, error) {
var v internal.DurationMS
if err := d.c.get(sysUptime, &v); err != nil {
return 0, err
}
return v.ToD(), nil
}
// GetTemp returns the temperature inside the camera.
func (d *Dev) GetTemp() (devices.Celsius, error) {
var v internal.CentiK
if err := d.c.get(sysTemperature, &v); err != nil {
return 0, err
}
return v.ToC(), nil
}
// GetTempHousing returns the temperature of the camera housing.
func (d *Dev) GetTempHousing() (devices.Celsius, error) {
var v internal.CentiK
if err := d.c.get(sysHousingTemperature, &v); err != nil {
return 0, err
}
return v.ToC(), nil
}
// GetFFCModeControl returns the internal state with regards to calibration.
func (d *Dev) GetFFCModeControl() (*FFCMode, error) {
v := internal.FFCMode{}
if err := d.c.get(sysFFCMode, &v); err != nil {
return nil, err
}
return &FFCMode{
FFCShutterMode: FFCShutterMode(v.FFCShutterMode),
ShutterTempLockoutState: ShutterTempLockoutState(v.ShutterTempLockoutState),
ElapsedTimeSinceLastFFC: v.ElapsedTimeSinceLastFFC.ToD(),
DesiredFFCPeriod: v.DesiredFFCPeriod.ToD(),
DesiredFFCTempDelta: devices.Celsius(v.DesiredFFCTempDelta * 10),
ImminentDelay: v.ImminentDelay,
VideoFreezeDuringFFC: v.VideoFreezeDuringFFC == internal.Enabled,
FFCDesired: v.FFCDesired == internal.Enabled,
ExplicitCommandToOpen: v.ExplicitCommandToOpen == internal.Enabled,
}, nil
}
// GetShutterPos returns the position of the shutter if present.
func (d *Dev) GetShutterPos() (ShutterPos, error) {
out := ShutterPosUnknown
err := d.c.get(sysShutterPosition, &out)
return out, err
}
// RunFFC forces a Flat-Field Correction to be done by the camera for
// recalibration. It takes 23 frames and the camera runs at 27fps so it lasts
// less than a second.
func (d *Dev) RunFFC() error {
return d.c.run(sysFCCRunNormalization)
}
//
// conn is the low level connection.
//
// It implements the low level protocol to run the GET, SET and RUN commands
// via memory mapped registers.
type conn struct {
mu sync.Mutex
r mmr.Dev16
}
// waitIdle waits for the busy bit to clear.
func (c *conn) waitIdle() (StatusBit, error) {
// Do not take the lock.
for {
if s, err := c.r.ReadUint16(regStatus); err != nil || StatusBit(s)&StatusBusy == 0 {
return StatusBit(s), err
}
time.Sleep(5 * time.Millisecond)
}
}
// get returns an attribute by querying the device.
func (c *conn) get(cmd command, data interface{}) error {
if data == nil {
return errors.New("lepton-cci: get() argument must not be nil")
}
if t := reflect.TypeOf(data); t.Kind() != reflect.Ptr && t.Kind() != reflect.Slice {
return fmt.Errorf("lepton-cci: get() argument must be a pointer or a slice, got %T", data)
}
size := binary.Size(data)
if size&1 != 0 {
return errors.New("lepton-cci: get() argument must be 16 bits aligned")
}
nbWords := size / 2
if nbWords > 1024 {
return errors.New("cci: buffer too large")
}
c.mu.Lock()
defer c.mu.Unlock()
if _, err := c.waitIdle(); err != nil {
return err
}
if err := c.r.WriteUint16(regDataLength, uint16(nbWords)); err != nil {
return err
}
if err := c.r.WriteUint16(regCommandID, uint16(cmd)); err != nil {
return err
}
s, err := c.waitIdle()
if err != nil {
return err
}
if s&0xff00 != 0 {
return fmt.Errorf("cci: error 0x%x", byte(s>>8))
}
if nbWords <= 16 {
err = c.r.ReadStruct(regData0, data)
} else {
err = c.r.ReadStruct(regDataBuffer0, data)
}
if err != nil {
return err
}
/*
// Verify CRC:
if crc, err := c.r.ReadUint16(regDataCRC); err != nil {
return err
} else if expected := internal.CRC16(data); expected != crc {
return fmt.Errorf("invalid crc; expected 0x%04X; got 0x%04X", expected, crc)
}
*/
//log.Printf("get(%s) = %v", cmd, data)
return nil
}
// set returns an attribute on the device.
func (c *conn) set(cmd command, data interface{}) error {
if data == nil {
return errors.New("lepton-cci: set() argument must not be nil")
}
size := binary.Size(data)
if size&1 != 0 {
return errors.New("lepton-cci: set() argument must be 16 bits aligned")
}
nbWords := size / 2
if nbWords > 1024 {
return errors.New("lepton-cci: buffer too large")
}
c.mu.Lock()
defer c.mu.Unlock()
if _, err := c.waitIdle(); err != nil {
return err
}
var err error
if nbWords <= 16 {
err = c.r.WriteStruct(regData0, data)
} else {
err = c.r.WriteStruct(regDataBuffer0, data)
}
if err != nil {
return err
}
if err := c.r.WriteUint16(regDataLength, uint16(nbWords)); err != nil {
return err
}
if err := c.r.WriteUint16(regCommandID, uint16(cmd)|1); err != nil {
return err
}
s, err := c.waitIdle()
if err != nil {
return err
}
if s&0xff00 != 0 {
return fmt.Errorf("cci: error 0x%x", s>>8)
}
return nil
}
// run runs a command on the device that doesn't need any argument.
func (c *conn) run(cmd command) error {
c.mu.Lock()
defer c.mu.Unlock()
if _, err := c.waitIdle(); err != nil {
return err
}
if err := c.r.WriteUint16(regDataLength, 0); err != nil {
return err
}
if err := c.r.WriteUint16(regCommandID, uint16(cmd)|2); err != nil {
return err
}
s, err := c.waitIdle()
if err != nil {
return err
}
if s&0xff00 != 0 {
return fmt.Errorf("cci: error 0x%x", s>>8)
}
return nil
}
//
// All the available registers.
const (
regPower uint16 = 0
regStatus uint16 = 2
regCommandID uint16 = 4
regDataLength uint16 = 6
regData0 uint16 = 8
regData1 uint16 = 10
regData2 uint16 = 12
regData3 uint16 = 14
regData4 uint16 = 16
regData5 uint16 = 18
regData6 uint16 = 20
regData7 uint16 = 22
regData8 uint16 = 24
regData9 uint16 = 26
regData10 uint16 = 28
regData11 uint16 = 30
regData12 uint16 = 32
regData13 uint16 = 34
regData14 uint16 = 36
regData15 uint16 = 38
regDataCRC uint16 = 40
regDataBuffer0 uint16 = 0xF800
regDataBuffer1 uint16 = 0xFC00
)
// command is a command supported by the FLIR Lepton over its CCI interface.
type command uint16
// All the available commands.
//
// See page 17 for more details.
//
// Number of words and supported action.
const (
agcEnable command = 0x0100 // 2 GET/SET
agcRoiSelect command = 0x0108 // 4 GET/SET
agcHistogramStats command = 0x010C // 4 GET
agcHeqDampFactor command = 0x0124 // 1 GET/SET
agcHeqClipLimitHigh command = 0x012C // 1 GET/SET
agcHeqClipLimitLow command = 0x0130 // 1 GET/SET
agcHeqEmptyCounts command = 0x013C // 1 GET/SET
agcHeqOutputScaleFactor command = 0x0144 // 2 GET/SET
agcCalculationEnable command = 0x0148 // 2 GET/SET
oemPowerDown command = 0x4800 // 0 RUN
oemPartNumber command = 0x481C // 16 GET
oemSoftwareRevision command = 0x4820 // 4 GET
oemVideoOutputEnable command = 0x4824 // 2 GET/SET
oemVideoOutputFormat command = 0x4828 // 2 GET/SET
oemVideoOutputSource command = 0x482C // 2 GET/SET
oemCustomerPartNumber command = 0x4838 // 16 GET
oemVideoOutputConst command = 0x483C // 1 GET/SET
oemCameraReboot command = 0x4840 // 0 RUN
oemFCCNormalizationTarget command = 0x4844 // 1 GET/SET/RUN
oemStatus command = 0x4848 // 2 GET
oemFrameMeanIntensity command = 0x484C // 1 GET
oemGPIOModeSelect command = 0x4854 // 2 GET/SET
oemGPIOVSyncPhaseDelay command = 0x4858 // 2 GET/SET
oemUserDefaults command = 0x485C // 2 GET/RUN
oemRestoreUserDefaults command = 0x4860 // 0 RUN
oemShutterProfile command = 0x4064 // 2 GET/SET
oemThermalShutdownEnable command = 0x4868 // 2 GET/SET
oemBadPixel command = 0x486C // 2 GET/SET
oemTemporalFilter command = 0x4870 // 2 GET/SET
oemColumnNoiseFilter command = 0x4874 // 2 GET/SET
oemPixelNoiseFilter command = 0x4878 // 2 GET/SET
sysPing command = 0x0200 // 0 RUN
sysStatus command = 0x0204 // 4 GET
sysSerialNumber command = 0x0208 // 4 GET
sysUptime command = 0x020C // 2 GET
sysHousingTemperature command = 0x0210 // 1 GET
sysTemperature command = 0x0214 // 1 GET
sysTelemetryEnable command = 0x0218 // 2 GET/SET
sysTelemetryLocation command = 0x021C // 2 GET/SET
sysExecuteFrameAverage command = 0x0220 // 0 RUN Undocumented but listed in SDK
sysFlatFieldFrames command = 0x0224 // 2 GET/SET It's an enum, max is 128
sysCustomSerialNumber command = 0x0228 // 16 GET It's a string
sysRoiSceneStats command = 0x022C // 4 GET
sysRoiSceneSelect command = 0x0230 // 4 GET/SET
sysThermalShutdownCount command = 0x0234 // 1 GET Number of times it exceeded 80C
sysShutterPosition command = 0x0238 // 2 GET/SET
sysFFCMode command = 0x023C // 17 GET/SET Manual control; doc says 20 words but it's 17 in practice.
sysFCCRunNormalization command = 0x0240 // 0 RUN
sysFCCStatus command = 0x0244 // 2 GET
vidColorLookupSelect command = 0x0304 // 2 GET/SET
vidColorLookupTransfer command = 0x0308 // 512 GET/SET
vidFocusCalculationEnable command = 0x030C // 2 GET/SET
vidFocusRoiSelect command = 0x0310 // 4 GET/SET
vidFocusMetricThreshold command = 0x0314 // 2 GET/SET
vidFocusMetricGet command = 0x0318 // 2 GET
vidVideoFreezeEnable command = 0x0324 // 2 GET/SET
)
// TODO(maruel): Enable RadXXX commands.

@ -0,0 +1,612 @@
// Copyright 2017 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 cci
import (
"testing"
"periph.io/x/periph/conn/i2c"
"periph.io/x/periph/conn/i2c/i2ctest"
"periph.io/x/periph/conn/mmr"
"periph.io/x/periph/devices/lepton/internal"
)
func TestStatusBit(t *testing.T) {
v := ^StatusBit(0)
if s := v.String(); s != "Busy|BootNormal|Booted|0xff" {
t.Fatal(s)
}
}
func TestNew_WaitIdle_fail(t *testing.T) {
bus := i2ctest.Playback{DontPanic: true}
if d, err := New(&bus); d != nil || err == nil {
t.Fatal("WaitIdle() should have returned an error")
}
}
func TestNew_WaitIdle(t *testing.T) {
bus := i2ctest.Playback{
Ops: []i2ctest.IO{
// WaitIdle loop once.
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x00}},
// WaitIdle return not booted.
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x01}},
// WaitIdle return booted.
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
},
}
_, err := New(&bus)
if err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestInit(t *testing.T) {
// set(agcEnable, internal.Disabled)
ops := setOps([]byte{0x0, 0x4, 0x1, 0x1}, []byte{0, 0, 0, 0})
// set(sysTelemetryEnable, internal.Enabled)
ops = append(ops, setOps([]byte{0x0, 0x4, 0x2, 0x19}, []byte{0, 1, 0, 0})...)
// set(sysTelemetryLocation, internal.Header)
ops = append(ops, setOps([]byte{0x0, 0x4, 0x2, 0x1d}, []byte{0, 0, 0, 0})...)
bus, d := getDev(ops)
if err := d.Init(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestInit_fail(t *testing.T) {
data := [][]i2ctest.IO{
// set(agcEnable, internal.Disabled)
setOps([]byte{0x0, 0x4, 0x1, 0x1}, []byte{0, 0, 0, 0}),
// set(sysTelemetryEnable, internal.Enabled)
setOps([]byte{0x0, 0x4, 0x2, 0x19}, []byte{0, 1, 0, 0}),
// set(sysTelemetryLocation, internal.Header)
setOps([]byte{0x0, 0x4, 0x2, 0x1d}, []byte{0, 0, 0, 0}),
}
var ops []i2ctest.IO
{
bus, d := getDev(ops)
bus.DontPanic = true
if d.Init() == nil {
t.Fatal("failed")
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
for i := 0; i < len(data)-1; i++ {
ops = append(ops, data[i]...)
bus, d := getDev(ops)
bus.DontPanic = true
if d.Init() == nil {
t.Fatal("failed")
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
}
func TestWaitIdle(t *testing.T) {
ops := []i2ctest.IO{
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
}
bus := i2ctest.Playback{Ops: ops}
d := Dev{c: conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}}
if _, err := d.WaitIdle(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestHalt(t *testing.T) {
bus, d := getDev(runOps([]byte{0x0, 0x4, 0x48, 0x2}))
if err := d.Halt(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestGetStatus(t *testing.T) {
bus, d := getDev(getOps([]byte{0x0, 0x4, 0x2, 0x4}, []byte{0, 0, 0, 0, 0, 0, 0, 0}))
if _, err := d.GetStatus(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestGetStatus_fail(t *testing.T) {
if _, err := getDevFail().GetStatus(); err == nil {
t.Fatal("failed")
}
}
func TestGetSerial(t *testing.T) {
bus, d := getDev(getOps([]byte{0x0, 0x4, 0x2, 0x8}, []byte{0, 0, 0, 0, 0, 0, 0, 0}))
if _, err := d.GetSerial(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestGetSerial_fail(t *testing.T) {
if _, err := getDevFail().GetSerial(); err == nil {
t.Fatal("failed")
}
}
func TestGetUptime(t *testing.T) {
bus, d := getDev(getOps([]byte{0x0, 0x4, 0x2, 0xc}, []byte{0, 0, 0, 0}))
if _, err := d.GetUptime(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestGetUptime_fail(t *testing.T) {
if _, err := getDevFail().GetUptime(); err == nil {
t.Fatal("failed")
}
}
func TestGetTemp(t *testing.T) {
bus, d := getDev(getOps([]byte{0x0, 0x4, 0x2, 0x14}, []byte{0, 0}))
if _, err := d.GetTemp(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestGetTemp_fail(t *testing.T) {
if _, err := getDevFail().GetTemp(); err == nil {
t.Fatal("failed")
}
}
func TestGetTempHousing(t *testing.T) {
bus, d := getDev(getOps([]byte{0x0, 0x4, 0x2, 0x10}, []byte{0, 0}))
if _, err := d.GetTempHousing(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestGetTempHousing_fail(t *testing.T) {
if _, err := getDevFail().GetTempHousing(); err == nil {
t.Fatal("failed")
}
}
func TestGetFFCModeControl(t *testing.T) {
bus, d := getDev(getOps([]byte{0x0, 0x4, 0x2, 0x3c}, []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}))
if _, err := d.GetFFCModeControl(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestGetFFCModeControl_fail(t *testing.T) {
if _, err := getDevFail().GetFFCModeControl(); err == nil {
t.Fatal("failed")
}
}
func TestGetShutterPos(t *testing.T) {
bus, d := getDev(getOps([]byte{0x0, 0x4, 0x2, 0x38}, []byte{0, 0, 0, 0}))
if _, err := d.GetShutterPos(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestGetShutterPos_fail(t *testing.T) {
if _, err := getDevFail().GetShutterPos(); err == nil {
t.Fatal("failed")
}
}
func TestRunFFC(t *testing.T) {
bus, d := getDev(runOps([]byte{0x0, 0x4, 0x2, 0x42}))
if err := d.RunFFC(); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
//
func TestConn_get(t *testing.T) {
ops := []i2ctest.IO{
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regDataLength
{Addr: 42, W: []byte{0x0, 0x6, 0x0, 0x4}},
// regCommandID
{Addr: 42, W: []byte{0x0, 0x4, 0x2, 0x4}},
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regData0
{Addr: 42, W: []byte{0x00, 0x08}, R: []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
}
bus := i2ctest.Playback{Ops: ops}
c := conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
var v internal.Status
if err := c.get(sysStatus, &v); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
// Test error paths.
for len(ops) != 0 {
ops = ops[:len(ops)-1]
bus := i2ctest.Playback{Ops: ops, DontPanic: true}
c = conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
var v internal.Status
if c.get(sysStatus, &v) == nil {
t.Fatal("should have failed")
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
}
func TestConn_get_large(t *testing.T) {
ops := []i2ctest.IO{
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regDataLength
{Addr: 42, W: []byte{0x0, 0x6, 0x4, 0x0}},
// regCommandID
{Addr: 42, W: []byte{0x0, 0x4, 0x2, 0x4}},
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regData0
{Addr: 42, W: []byte{0xf8, 0}, R: make([]byte, 2048)},
}
bus := i2ctest.Playback{Ops: ops}
c := conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
v := make([]byte, 2048)
if err := c.get(sysStatus, v); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestConn_get_fail_waitidle(t *testing.T) {
ops := []i2ctest.IO{
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regDataLength
{Addr: 42, W: []byte{0x0, 0x6, 0x0, 0x4}},
// regCommandID
{Addr: 42, W: []byte{0x0, 0x4, 0x2, 0x4}},
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x01, 0x00}},
}
bus := i2ctest.Playback{Ops: ops}
c := conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
var v internal.Status
if c.get(sysStatus, &v) == nil {
t.Fatal("waitIdle failed")
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestConn_get_fail(t *testing.T) {
bus := i2ctest.Playback{}
c := conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
if c.get(sysStatus, nil) == nil {
t.Fatal("nil value")
}
if c.get(sysStatus, 1) == nil {
t.Fatal("not a pointer")
}
v := []byte{0}
if c.get(sysStatus, &v) == nil {
t.Fatal("odd length")
}
v = make([]byte, 2048+2)
if c.get(sysStatus, &v) == nil {
t.Fatal("overflow")
}
}
func TestConn_set(t *testing.T) {
ops := []i2ctest.IO{
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regData0
{Addr: 42, W: []byte{0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
// regDataLength
{Addr: 42, W: []byte{0x0, 0x6, 0x0, 0x4}},
// regCommandID
{Addr: 42, W: []byte{0x0, 0x4, 0x2, 0x5}},
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
}
bus := i2ctest.Playback{Ops: ops}
c := conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
var v internal.Status
if err := c.set(sysStatus, &v); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
// Test error paths.
for len(ops) != 0 {
ops = ops[:len(ops)-1]
bus := i2ctest.Playback{Ops: ops, DontPanic: true}
c = conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
var v internal.Status
if c.set(sysStatus, &v) == nil {
t.Fatal("should have failed")
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
}
func TestConn_set_large(t *testing.T) {
ops := []i2ctest.IO{
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regData0
{Addr: 42, W: append([]byte{0xf8, 0}, make([]byte, 2048)...)},
// regDataLength
{Addr: 42, W: []byte{0x0, 0x6, 0x4, 0x0}},
// regCommandID
{Addr: 42, W: []byte{0x0, 0x4, 0x2, 0x5}},
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
}
bus := i2ctest.Playback{Ops: ops}
c := conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
v := make([]byte, 2048)
if err := c.set(sysStatus, v); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestConn_set_fail_waitidle(t *testing.T) {
ops := []i2ctest.IO{
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regData0
{Addr: 42, W: []byte{0x0, 0x8, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}},
// regDataLength
{Addr: 42, W: []byte{0x0, 0x6, 0x0, 0x4}},
// regCommandID
{Addr: 42, W: []byte{0x0, 0x4, 0x2, 0x5}},
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x0f, 0x00}},
}
bus := i2ctest.Playback{Ops: ops}
c := conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
var v internal.Status
if c.set(sysStatus, &v) == nil {
t.Fatal("waitIdle failed")
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
func TestConn_set_fail(t *testing.T) {
bus := i2ctest.Playback{}
c := conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
if c.set(sysStatus, nil) == nil {
t.Fatal("nil value")
}
v := []byte{0}
if c.set(sysStatus, &v) == nil {
t.Fatal("odd length")
}
v = make([]byte, 2048+2)
if c.set(sysStatus, &v) == nil {
t.Fatal("overflow")
}
}
func TestConn_run(t *testing.T) {
ops := []i2ctest.IO{
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regDataLength
{Addr: 42, W: []byte{0x0, 0x6, 0x0, 0x0}},
// regCommandID
{Addr: 42, W: []byte{0x0, 0x4, 0x2, 0x42}},
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
}
bus := i2ctest.Playback{Ops: ops}
c := conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
if err := c.run(sysFCCRunNormalization); err != nil {
t.Fatal(err)
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
// Test error paths.
for len(ops) != 0 {
ops = ops[:len(ops)-1]
bus := i2ctest.Playback{Ops: ops, DontPanic: true}
c = conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
if c.run(sysFCCRunNormalization) == nil {
t.Fatal("should have failed")
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
}
func TestConn_run_fail_waitidle(t *testing.T) {
ops := []i2ctest.IO{
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regDataLength
{Addr: 42, W: []byte{0x0, 0x6, 0x0, 0x0}},
// regCommandID
{Addr: 42, W: []byte{0x0, 0x4, 0x2, 0x42}},
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x0f, 0x00}},
}
bus := i2ctest.Playback{Ops: ops}
c := conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: &bus, Addr: 0x2A}, Order: internal.Big16}}
if c.run(sysFCCRunNormalization) == nil {
t.Fatal("waitIdle failed")
}
if err := bus.Close(); err != nil {
t.Fatal(err)
}
}
//
func TestStrings(t *testing.T) {
if s := SystemReady.String(); s != "SystemReady" {
t.Fatal(s)
}
if s := CameraStatus(30).String(); s != "CameraStatus(30)" {
t.Fatal(s)
}
if s := agcEnable.String(); s != "agcEnable" {
t.Fatal(s)
}
if s := command(0).String(); s != "command(0)" {
t.Fatal(s)
}
if s := FFCShutterModeManual.String(); s != "FFCShutterModeManual" {
t.Fatal(s)
}
if s := FFCShutterMode(30).String(); s != "FFCShutterMode(30)" {
t.Fatal(s)
}
if s := FFCNever.String(); s != "FFCNever" {
t.Fatal(s)
}
if s := FFCState(30).String(); s != "FFCState(30)" {
t.Fatal(s)
}
if s := ShutterPosIdle.String(); s != "ShutterPosIdle" {
t.Fatal(s)
}
if s := ShutterPos(30).String(); s != "ShutterPos(30)" {
t.Fatal(s)
}
if s := ShutterPosUnknown.String(); s != "ShutterPosUnknown" {
t.Fatal(s)
}
if s := ShutterTempLockoutStateInactive.String(); s != "ShutterTempLockoutStateInactive" {
t.Fatal(s)
}
if s := ShutterTempLockoutState(30).String(); s != "ShutterTempLockoutState(30)" {
t.Fatal(s)
}
}
//
func getDev(ops []i2ctest.IO) (*i2ctest.Playback, *Dev) {
bus := &i2ctest.Playback{Ops: ops}
d := &Dev{c: conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: bus, Addr: 0x2A}, Order: internal.Big16}}}
return bus, d
}
func getDevFail() *Dev {
bus := &i2ctest.Playback{DontPanic: true}
d := &Dev{c: conn{r: mmr.Dev16{Conn: &i2c.Dev{Bus: bus, Addr: 0x2A}, Order: internal.Big16}}}
return d
}
func getOps(cmd, data []byte) []i2ctest.IO {
return []i2ctest.IO{
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regDataLength
{Addr: 42, W: []byte{0x0, 0x6, 0x0, byte(len(data) / 2)}},
// regCommandID
{Addr: 42, W: cmd},
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regData0
{Addr: 42, W: []byte{0x00, 0x08}, R: data},
}
}
func setOps(cmd, data []byte) []i2ctest.IO {
return []i2ctest.IO{
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regData0
{Addr: 42, W: append([]byte{0, 8}, data...)},
// regDataLength
{Addr: 42, W: []byte{0x0, 0x6, 0x0, byte(len(data) / 2)}},
// regCommandID
{Addr: 42, W: cmd},
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
}
}
func runOps(c []byte) []i2ctest.IO {
return []i2ctest.IO{
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
// regDataLength
{Addr: 42, W: []byte{0x0, 0x6, 0x0, 0x0}},
// regCommandID
{Addr: 42, W: c},
// waitIdle
{Addr: 42, W: []byte{0x00, 0x02}, R: []byte{0x00, 0x06}},
}
}

@ -0,0 +1,138 @@
// Code generated by "stringer -output=strings_gen.go -type=CameraStatus,command,FFCShutterMode,FFCState,ShutterPos,ShutterTempLockoutState"; DO NOT EDIT.
package cci
import "fmt"
const _CameraStatus_name = "SystemReadySystemInitializingSystemInLowPowerModeSystemGoingIntoStandbySystemFlatFieldInProcess"
var _CameraStatus_index = [...]uint8{0, 11, 29, 49, 71, 95}
func (i CameraStatus) String() string {
if i >= CameraStatus(len(_CameraStatus_index)-1) {
return fmt.Sprintf("CameraStatus(%d)", i)
}
return _CameraStatus_name[_CameraStatus_index[i]:_CameraStatus_index[i+1]]
}
const _command_name = "agcEnableagcRoiSelectagcHistogramStatsagcHeqDampFactoragcHeqClipLimitHighagcHeqClipLimitLowagcHeqEmptyCountsagcHeqOutputScaleFactoragcCalculationEnablesysPingsysStatussysSerialNumbersysUptimesysHousingTemperaturesysTemperaturesysTelemetryEnablesysTelemetryLocationsysExecuteFrameAveragesysFlatFieldFramessysCustomSerialNumbersysRoiSceneStatssysRoiSceneSelectsysThermalShutdownCountsysShutterPositionsysFFCModesysFCCRunNormalizationsysFCCStatusvidColorLookupSelectvidColorLookupTransfervidFocusCalculationEnablevidFocusRoiSelectvidFocusMetricThresholdvidFocusMetricGetvidVideoFreezeEnableoemShutterProfileoemPowerDownoemPartNumberoemSoftwareRevisionoemVideoOutputEnableoemVideoOutputFormatoemVideoOutputSourceoemCustomerPartNumberoemVideoOutputConstoemCameraRebootoemFCCNormalizationTargetoemStatusoemFrameMeanIntensityoemGPIOModeSelectoemGPIOVSyncPhaseDelayoemUserDefaultsoemRestoreUserDefaultsoemThermalShutdownEnableoemBadPixeloemTemporalFilteroemColumnNoiseFilteroemPixelNoiseFilter"
var _command_map = map[command]string{
256: _command_name[0:9],
264: _command_name[9:21],
268: _command_name[21:38],
292: _command_name[38:54],
300: _command_name[54:73],
304: _command_name[73:91],
316: _command_name[91:108],
324: _command_name[108:131],
328: _command_name[131:151],
512: _command_name[151:158],
516: _command_name[158:167],
520: _command_name[167:182],
524: _command_name[182:191],
528: _command_name[191:212],
532: _command_name[212:226],
536: _command_name[226:244],
540: _command_name[244:264],
544: _command_name[264:286],
548: _command_name[286:304],
552: _command_name[304:325],
556: _command_name[325:341],
560: _command_name[341:358],
564: _command_name[358:381],
568: _command_name[381:399],
572: _command_name[399:409],
576: _command_name[409:431],
580: _command_name[431:443],
772: _command_name[443:463],
776: _command_name[463:485],
780: _command_name[485:510],
784: _command_name[510:527],
788: _command_name[527:550],
792: _command_name[550:567],
804: _command_name[567:587],
16484: _command_name[587:604],
18432: _command_name[604:616],
18460: _command_name[616:629],
18464: _command_name[629:648],
18468: _command_name[648:668],
18472: _command_name[668:688],
18476: _command_name[688:708],
18488: _command_name[708:729],
18492: _command_name[729:748],
18496: _command_name[748:763],
18500: _command_name[763:788],
18504: _command_name[788:797],
18508: _command_name[797:818],
18516: _command_name[818:835],
18520: _command_name[835:857],
18524: _command_name[857:872],
18528: _command_name[872:894],
18536: _command_name[894:918],
18540: _command_name[918:929],
18544: _command_name[929:946],
18548: _command_name[946:966],
18552: _command_name[966:985],
}
func (i command) String() string {
if str, ok := _command_map[i]; ok {
return str
}
return fmt.Sprintf("command(%d)", i)
}
const _FFCShutterMode_name = "FFCShutterModeManualFFCShutterModeAutoFFCShutterModeExternal"
var _FFCShutterMode_index = [...]uint8{0, 20, 38, 60}
func (i FFCShutterMode) String() string {
if i >= FFCShutterMode(len(_FFCShutterMode_index)-1) {
return fmt.Sprintf("FFCShutterMode(%d)", i)
}
return _FFCShutterMode_name[_FFCShutterMode_index[i]:_FFCShutterMode_index[i+1]]
}
const _FFCState_name = "FFCNeverFFCInProgressFFCComplete"
var _FFCState_index = [...]uint8{0, 8, 21, 32}
func (i FFCState) String() string {
if i >= FFCState(len(_FFCState_index)-1) {
return fmt.Sprintf("FFCState(%d)", i)
}
return _FFCState_name[_FFCState_index[i]:_FFCState_index[i+1]]
}
const (
_ShutterPos_name_0 = "ShutterPosIdleShutterPosOpenShutterPosClosedShutterPosBrakeOn"
_ShutterPos_name_1 = "ShutterPosUnknown"
)
var (
_ShutterPos_index_0 = [...]uint8{0, 14, 28, 44, 61}
_ShutterPos_index_1 = [...]uint8{0, 17}
)
func (i ShutterPos) String() string {
switch {
case 0 <= i && i <= 3:
return _ShutterPos_name_0[_ShutterPos_index_0[i]:_ShutterPos_index_0[i+1]]
case i == 4294967295:
return _ShutterPos_name_1
default:
return fmt.Sprintf("ShutterPos(%d)", i)
}
}
const _ShutterTempLockoutState_name = "ShutterTempLockoutStateInactiveShutterTempLockoutStateHighShutterTempLockoutStateLow"
var _ShutterTempLockoutState_index = [...]uint8{0, 31, 58, 84}
func (i ShutterTempLockoutState) String() string {
if i >= ShutterTempLockoutState(len(_ShutterTempLockoutState_index)-1) {
return fmt.Sprintf("ShutterTempLockoutState(%d)", i)
}
return _ShutterTempLockoutState_name[_ShutterTempLockoutState_index[i]:_ShutterTempLockoutState_index[i+1]]
}

@ -0,0 +1,170 @@
// Copyright 2017 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 internal
import (
"encoding/binary"
"time"
"periph.io/x/periph/devices"
)
// Flag is used in FFCMode.
type Flag uint32
// Valid values for Flag.
const (
Disabled Flag = 0
Enabled Flag = 1
)
// DurationMS is duration in millisecond.
//
// It is an implementation detail of the protocol.
type DurationMS uint32
func (d DurationMS) ToD() time.Duration {
return time.Duration(d) * time.Millisecond
}
// CentiK is temperature in 0.01°K
//
// It is an implementation detail of the protocol.
type CentiK uint16
func (c CentiK) ToC() devices.Celsius {
v := (int(c) - 27315) * 10
return devices.Celsius(v)
}
// Status returns the camera status as returned by the camera.
type Status struct {
CameraStatus uint32
CommandCount uint16
Reserved uint16
}
// FFCMode
type FFCMode struct {
FFCShutterMode uint32 // Default: FFCShutterModeExternal
ShutterTempLockoutState uint32 // Default: ShutterTempLockoutStateInactive
VideoFreezeDuringFFC Flag // Default: Enabled
FFCDesired Flag // Default: Disabled
ElapsedTimeSinceLastFFC DurationMS // Uptime in ms.
DesiredFFCPeriod DurationMS // Default: 300000
ExplicitCommandToOpen Flag // Default: Disabled
DesiredFFCTempDelta uint16 // Default: 300
ImminentDelay uint16 // Default: 52
// These are documented at page 51 but not listed in the structure.
// ClosePeriodInFrames uint16 // Default: 4
// OpenPeriodInFrames uint16 // Default: 1
}
// TelemetryLocation is used with SysTelemetryLocation.
type TelemetryLocation uint32
// Valid values for TelemetryLocation.
const (
Header TelemetryLocation = 0
Footer TelemetryLocation = 1
)
//
type table [256]uint16
const ccittFalse = 0x1021
var ccittFalseTable table
func init() {
makeReversedTable(ccittFalse, &ccittFalseTable)
}
func makeReversedTable(poly uint16, t *table) {
width := uint16(16)
for i := uint16(0); i < 256; i++ {
crc := i << (width - 8)
for j := 0; j < 8; j++ {
if crc&(1<<(width-1)) != 0 {
crc = (crc << 1) ^ poly
} else {
crc <<= 1
}
}
t[i] = crc
}
}
func updateReversed(crc uint16, t *table, p []byte) uint16 {
for _, v := range p {
crc = t[byte(crc>>8)^v] ^ (crc << 8)
}
return crc
}
// CRC16 calculates the reversed CCITT CRC16 checksum.
func CRC16(d []byte) uint16 {
return updateReversed(0, &ccittFalseTable, d)
}
//
// Big16 translates big endian 16bits words but everything larger is in little
// endian.
//
// It implements binary.ByteOrder.
var Big16 big16
type big16 struct{}
func (big16) Uint16(b []byte) uint16 {
_ = b[1] // bounds check hint to compiler; see golang.org/issue/14808
return uint16(b[1]) | uint16(b[0])<<8
}
func (big16) PutUint16(b []byte, v uint16) {
_ = b[1] // early bounds check to guarantee safety of writes below
b[0] = byte(v >> 8)
b[1] = byte(v)
}
func (big16) Uint32(b []byte) uint32 {
_ = b[3] // bounds check hint to compiler; see golang.org/issue/14808
return uint32(b[1]) | uint32(b[0])<<8 | uint32(b[3])<<16 | uint32(b[2])<<24
}
func (big16) PutUint32(b []byte, v uint32) {
_ = b[3] // early bounds check to guarantee safety of writes below
b[1] = byte(v)
b[0] = byte(v >> 8)
b[3] = byte(v >> 16)
b[2] = byte(v >> 24)
}
func (big16) Uint64(b []byte) uint64 {
_ = b[7] // bounds check hint to compiler; see golang.org/issue/14808
return uint64(b[1]) | uint64(b[0])<<8 | uint64(b[3])<<16 | uint64(b[2])<<24 |
uint64(b[5])<<32 | uint64(b[4])<<40 | uint64(b[7])<<48 | uint64(b[6])<<56
}
func (big16) PutUint64(b []byte, v uint64) {
_ = b[7] // early bounds check to guarantee safety of writes below
b[1] = byte(v)
b[0] = byte(v >> 8)
b[3] = byte(v >> 16)
b[2] = byte(v >> 24)
b[5] = byte(v >> 32)
b[4] = byte(v >> 40)
b[7] = byte(v >> 48)
b[6] = byte(v >> 56)
}
func (big16) String() string {
return "big16"
}
var _ binary.ByteOrder = Big16

@ -0,0 +1,62 @@
// Copyright 2017 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 internal
import (
"bytes"
"testing"
"time"
)
func TestDuration(t *testing.T) {
if d := DurationMS(100).ToD(); d != 100*time.Millisecond {
t.Fatal(d)
}
}
func TestCentiK(t *testing.T) {
if c := CentiK(100).ToC(); c != -272150 {
t.Fatal(c)
}
}
func TestCRC16(t *testing.T) {
d := []byte{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39}
if c := CRC16(d); c != 0x31c3 {
t.Fatal(c)
}
}
func TestBig16(t *testing.T) {
if s := Big16.String(); s != "big16" {
t.Fatal(s)
}
d := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
if v := Big16.Uint16(d); v != 0x0102 {
t.Fatal(v)
}
if v := Big16.Uint32(d); v != 0x03040102 {
t.Fatal(v)
}
if v := Big16.Uint64(d); v != 0x0708050603040102 {
t.Fatal(v)
}
d = make([]byte, 2)
Big16.PutUint16(d, 0x0102)
if !bytes.Equal(d, []byte{0x01, 0x02}) {
t.Fatal(d)
}
d = make([]byte, 4)
Big16.PutUint32(d, 0x01020304)
if !bytes.Equal(d, []byte{0x03, 0x04, 0x01, 0x02}) {
t.Fatal(d)
}
d = make([]byte, 8)
Big16.PutUint64(d, 0x0102030405060708)
if !bytes.Equal(d, []byte{0x07, 0x08, 0x05, 0x06, 0x03, 0x04, 0x01, 0x02}) {
t.Fatal(d)
}
}

@ -0,0 +1,450 @@
// Copyright 2017 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 lepton drivers a FLIR Lepton.
//
// References
//
// Official FLIR reference:
// http://www.flir.com/cvs/cores/view/?id=51878
//
// Product page:
// http://www.flir.com/cores/content/?id=66257
//
// Datasheet:
// http://www.flir.com/uploadedFiles/OEM/Products/LWIR-Cameras/Lepton/Lepton%20Engineering%20Datasheet%20-%20with%20Radiometry.pdf
package lepton
import (
"bytes"
"encoding/binary"
"errors"
"fmt"
"image"
"image/color"
"sync"
"time"
"periph.io/x/periph/conn"
"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/i2c"
"periph.io/x/periph/conn/spi"
"periph.io/x/periph/devices"
"periph.io/x/periph/devices/lepton/cci"
"periph.io/x/periph/devices/lepton/internal"
)
// Metadata is constructed from telemetry data, which is sent with each frame.
type Metadata struct {
SinceStartup time.Duration //
FrameCount uint32 // Number of frames since the start of the camera, in 27fps (not 9fps).
AvgValue uint16 // Average value of the buffer.
Temp devices.Celsius // Temperature inside the camera.
TempHousing devices.Celsius // Camera housing temperature.
RawTemp uint16 //
RawTempHousing uint16 //
FFCSince time.Duration // Time since last internal calibration.
FFCTemp devices.Celsius // Temperature at last internal calibration.
FFCTempHousing devices.Celsius //
FFCState cci.FFCState // Current calibration state.
FFCDesired bool // Asserted at start-up, after period (default 3m) or after temperature change (default 3°K). Indicates that a calibration should be triggered as soon as possible.
Overtemp bool // true 10s before self-shutdown.
}
// Frame is a FLIR Lepton frame, containing 14 bits resolution intensity stored
// as image.Gray16.
//
// Values centered around 8192 accorging to camera body temperature. Effective
// range is 14 bits, so [0, 16383].
//
// Each 1 increment is approximatively 0.025°K.
type Frame struct {
*image.Gray16
Metadata Metadata // Metadata that is sent along the pixels.
}
// Dev controls a FLIR Lepton.
//
// It assumes a specific breakout board. Sadly the breakout board doesn't
// expose the PWR_DWN_L and RESET_L lines so it is impossible to shut down the
// Lepton.
type Dev struct {
*cci.Dev
s spi.Conn
cs gpio.PinOut
prevImg *image.Gray16
frameA, frameB []byte
frameWidth int // in bytes
frameLines int
maxTxSize int
delay time.Duration
}
// New returns an initialized connection to the FLIR Lepton.
//
// The CS line is manually managed by using mode spi.NoCS when calling
// DevParams(). In this case pass nil for the cs parameter. Some spidev drivers
// refuse spi.NoCS, they do not implement proper support to not trigger the CS
// line so a manual CS (really, any GPIO pin) must be used instead.
//
// Maximum SPI speed is 20Mhz. Minimum usable rate is ~2.2Mhz to sustain a 9hz
// framerate at 80x60.
//
// Maximum I²C speed is 1Mhz.
//
// MOSI is not used and should be grounded.
func New(s spi.Conn, i i2c.Bus, cs gpio.PinOut) (*Dev, error) {
// Sadly the Lepton will inconditionally send 27fps, even if the effective
// rate is 9fps.
mode := spi.Mode3
if cs == nil {
// Query the CS pin before disabling it.
p, ok := s.(spi.Pins)
if !ok {
return nil, errors.New("lepton: require manual access to the CS pin")
}
cs = p.CS()
if cs == gpio.INVALID {
return nil, errors.New("lepton: require manual access to a valid CS pin")
}
mode |= spi.NoCS
}
// TODO(maruel): Switch to 16 bits per word, so that big endian 16 bits word
// decoding is done by the SPI driver.
if err := s.DevParams(20000000, mode, 8); err != nil {
return nil, err
}
c, err := cci.New(i)
if err != nil {
return nil, err
}
// TODO(maruel): Support Lepton 3 with 160x120.
w := 80
h := 60
// telemetry data is a 3 lines header.
frameLines := h + 3
frameWidth := w*2 + 4
d := &Dev{
Dev: c,
s: s,
cs: cs,
prevImg: image.NewGray16(image.Rect(0, 0, w, h)),
frameWidth: frameWidth,
frameLines: frameLines,
delay: time.Second,
}
if l, ok := s.(conn.Limits); ok {
d.maxTxSize = l.MaxTxSize()
}
if status, err := d.GetStatus(); err != nil {
return nil, err
} else if status.CameraStatus != cci.SystemReady {
// The lepton takes < 1 second to boot so it should not happen normally.
return nil, fmt.Errorf("lepton: camera is not ready: %s", status)
}
if err := d.Init(); err != nil {
return nil, err
}
return d, nil
}
// ReadImg reads an image.
//
// It is ok to call other functions concurrently to send commands to the
// camera.
func (d *Dev) ReadImg() (*Frame, error) {
f := &Frame{Gray16: image.NewGray16(d.prevImg.Bounds())}
for {
if err := d.readFrame(f); err != nil {
return nil, err
}
if f.Metadata.FFCDesired == true {
// TODO(maruel): Automatically trigger FFC when applicable, only do if
// the camera has a shutter.
//go d.RunFFC()
}
if !bytes.Equal(d.prevImg.Pix, f.Gray16.Pix) {
break
}
// It also happen if the image is 100% static without noise.
}
copy(d.prevImg.Pix, f.Pix)
return f, nil
}
// Private details.
// stream reads continuously from the SPI connection.
func (d *Dev) stream(done <-chan struct{}, c chan<- []byte) error {
lines := 8
if d.maxTxSize != 0 {
if l := d.maxTxSize / d.frameWidth; l < lines {
lines = l
}
}
if err := d.cs.Out(gpio.Low); err != nil {
return err
}
defer d.cs.Out(gpio.High)
for {
// TODO(maruel): Use a ring buffer to stop continuously allocating.
buf := make([]byte, d.frameWidth*lines)
if err := d.s.Tx(nil, buf); err != nil {
return err
}
for i := 0; i < len(buf); i += d.frameWidth {
select {
case <-done:
return nil
case c <- buf[i : i+d.frameWidth]:
}
}
}
}
// readFrame reads one frame.
//
// Each frame is sent as a packet over SPI including telemetry data as an
// header. See page 49-57 for "VoSPI" protocol explanation.
//
// This operation must complete within 32ms. Frames occur every 38.4ms at
// almost 27hz.
//
// Resynchronization is done by deasserting CS and CLK for at least 5 frames
// (>185ms).
//
// When a packet starts, it must be completely clocked out within 3 line
// periods.
//
// One frame of 80x60 at 2 byte per pixel, plus 4 bytes overhead per line plus
// 3 lines of telemetry is (3+60)*(4+160) = 10332. The sysfs-spi driver limits
// each transaction size, the default is 4Kb. To reduce the risks of failure,
// reads 4Kb at a time and figure out the lines from there. The Lepton is very
// cranky if reading is not done quickly enough.
func (d *Dev) readFrame(f *Frame) error {
done := make(chan struct{}, 1)
c := make(chan []byte, 1024)
var err error
var wg sync.WaitGroup
wg.Add(1)
go func() {
defer wg.Done()
defer close(c)
err = d.stream(done, c)
}()
defer func() {
done <- struct{}{}
}()
timeout := time.After(d.delay)
w := f.Bounds().Dx()
sync := 0
discard := 0
for {
select {
case <-timeout:
return fmt.Errorf("failed to synchronize after %s", d.delay)
case l, ok := <-c:
if !ok {
wg.Wait()
return err
}
h := internal.Big16.Uint16(l)
if h&packetHeaderDiscard == packetHeaderDiscard {
discard++
sync = 0
continue
}
headerID := h & packetHeaderMask
if discard != 0 {
//log.Printf("discarded %d", discard)
discard = 0
sync = 0
}
if int(headerID) == 0 && sync == 0 && !verifyCRC(l) {
//log.Printf("no crc")
sync = 0
continue
}
if int(headerID) != sync {
//log.Printf("%d != %d", headerID, sync)
sync = 0
continue
}
if sync == 0 {
// Parse the first row of telemetry data.
if err2 := f.Metadata.parseTelemetry(l[4:]); err2 != nil {
//log.Printf("Failed to parse telemetry line: %v", err2)
continue
}
} else if sync >= 3 {
// Image.
for x := 0; x < w; x++ {
o := 4 + x*2
f.SetGray16(x, sync-3, color.Gray16{internal.Big16.Uint16(l[o : o+2])})
}
}
if sync++; sync == d.frameLines {
// Last line, done.
return nil
}
}
}
}
func (m *Metadata) parseTelemetry(data []byte) error {
// Telemetry line.
var rowA telemetryRowA
if err := binary.Read(bytes.NewBuffer(data), internal.Big16, &rowA); err != nil {
return err
}
m.SinceStartup = rowA.TimeCounter.ToD()
m.FrameCount = rowA.FrameCounter
m.AvgValue = rowA.FrameMean
m.Temp = rowA.FPATemp.ToC()
m.TempHousing = rowA.HousingTemp.ToC()
m.RawTemp = rowA.FPATempCounts
m.RawTempHousing = rowA.HousingTempCounts
m.FFCSince = rowA.TimeCounterLastFFC.ToD()
m.FFCTemp = rowA.FPATempLastFFC.ToC()
m.FFCTempHousing = rowA.HousingTempLastFFC.ToC()
if rowA.StatusBits&statusMaskNil != 0 {
return fmt.Errorf("\n(Status: 0x%08X) & (Mask: 0x%08X) = (Extra: 0x%08X) in 0x%08X\n", rowA.StatusBits, statusMask, rowA.StatusBits&statusMaskNil, statusMaskNil)
}
m.FFCDesired = rowA.StatusBits&statusFFCDesired != 0
m.Overtemp = rowA.StatusBits&statusOvertemp != 0
fccstate := rowA.StatusBits & statusFFCStateMask >> statusFFCStateShift
if rowA.TelemetryRevision == 8 {
switch fccstate {
case 0:
m.FFCState = cci.FFCNever
case 1:
m.FFCState = cci.FFCInProgress
case 2:
m.FFCState = cci.FFCComplete
default:
return fmt.Errorf("unexpected fccstate %d; %v", fccstate, data)
}
} else {
switch fccstate {
case 0:
m.FFCState = cci.FFCNever
case 2:
m.FFCState = cci.FFCInProgress
case 3:
m.FFCState = cci.FFCComplete
default:
return fmt.Errorf("unexpected fccstate %d; %v", fccstate, data)
}
}
return nil
}
// As documented as page.21
const (
packetHeaderDiscard = 0x0F00
packetHeaderMask = 0x0FFF // ID field is 12 bits. Leading 4 bits are reserved.
// Observed status:
// 0x00000808
// 0x00007A01
// 0x00022200
// 0x01AD0000
// 0x02BF0000
// 0x1FFF0000
// 0x3FFF0001
// 0xDCD0FFFF
// 0xFFDCFFFF
statusFFCDesired uint32 = 1 << 3 // 0x00000008
statusFFCStateMask uint32 = 3 << 4 // 0x00000030
statusFFCStateShift uint32 = 4 //
statusReserved uint32 = 1 << 11 // 0x00000800
statusAGCState uint32 = 1 << 12 // 0x00001000
statusOvertemp uint32 = 1 << 20 // 0x00100000
statusMask = statusFFCDesired | statusFFCStateMask | statusAGCState | statusOvertemp | statusReserved // 0x00101838
statusMaskNil = ^statusMask // 0xFFEFE7C7
)
// telemetryRowA is the data structure returned after the frame as documented
// at p.19-20.
//
// '*' means the value observed in practice make sense.
// Value after '-' is observed value.
type telemetryRowA struct {
TelemetryRevision uint16 // 0 *
TimeCounter internal.DurationMS // 1 *
StatusBits uint32 // 3 * Bit field (mostly make sense)
ModuleSerial [16]uint8 // 5 - Is empty (!)
SoftwareRevision uint64 // 13 Junk.
Reserved17 uint16 // 17 - 1101
Reserved18 uint16 // 18
Reserved19 uint16 // 19
FrameCounter uint32 // 20 *
FrameMean uint16 // 22 * The average value from the whole frame.
FPATempCounts uint16 // 23
FPATemp internal.CentiK // 24 *
HousingTempCounts uint16 // 25
HousingTemp internal.CentiK // 27 *
Reserved27 uint16 // 27
Reserved28 uint16 // 28
FPATempLastFFC internal.CentiK // 29 *
TimeCounterLastFFC internal.DurationMS // 30 *
HousingTempLastFFC internal.CentiK // 32 *
Reserved33 uint16 // 33
AGCROILeft uint16 // 35 * - 0 (Likely inversed, haven't confirmed)
AGCROITop uint16 // 34 * - 0
AGCROIRight uint16 // 36 * - 79 - SDK was wrong!
AGCROIBottom uint16 // 37 * - 59 - SDK was wrong!
AGCClipLimitHigh uint16 // 38 *
AGCClipLimitLow uint16 // 39 *
Reserved40 uint16 // 40 - 1
Reserved41 uint16 // 41 - 128
Reserved42 uint16 // 42 - 64
Reserved43 uint16 // 43
Reserved44 uint16 // 44
Reserved45 uint16 // 45
Reserved46 uint16 // 46
Reserved47 uint16 // 47 - 1
Reserved48 uint16 // 48 - 128
Reserved49 uint16 // 49 - 1
Reserved50 uint16 // 50
Reserved51 uint16 // 51
Reserved52 uint16 // 52
Reserved53 uint16 // 53
Reserved54 uint16 // 54
Reserved55 uint16 // 55
Reserved56 uint16 // 56 - 30
Reserved57 uint16 // 57
Reserved58 uint16 // 58 - 1
Reserved59 uint16 // 59 - 1
Reserved60 uint16 // 60 - 78
Reserved61 uint16 // 61 - 58
Reserved62 uint16 // 62 - 7
Reserved63 uint16 // 63 - 90
Reserved64 uint16 // 64 - 40
Reserved65 uint16 // 65 - 210
Reserved66 uint16 // 66 - 255
Reserved67 uint16 // 67 - 255
Reserved68 uint16 // 68 - 23
Reserved69 uint16 // 69 - 6
Reserved70 uint16 // 70
Reserved71 uint16 // 71
Reserved72 uint16 // 72 - 7
Reserved73 uint16 // 73
Log2FFCFrames uint16 // 74 Found 3, should be 27?
Reserved75 uint16 // 75
Reserved76 uint16 // 76
Reserved77 uint16 // 77
Reserved78 uint16 // 78
Reserved79 uint16 // 79
}
// verifyCRC test the equation x^16 + x^12 + x^5 + x^0
func verifyCRC(d []byte) bool {
tmp := make([]byte, len(d))
copy(tmp, d)
tmp[0] &^= 0x0F
tmp[2] = 0
tmp[3] = 0
return internal.CRC16(tmp) == internal.Big16.Uint16(d[2:])
}

@ -0,0 +1,416 @@
// Copyright 2017 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 lepton
import (
"bytes"
"encoding/binary"
"encoding/hex"
"errors"
"image"
"image/color"
"testing"
"periph.io/x/periph/conn"
"periph.io/x/periph/conn/conntest"
"periph.io/x/periph/conn/gpio"
"periph.io/x/periph/conn/gpio/gpiotest"
"periph.io/x/periph/conn/i2c/i2ctest"
"periph.io/x/periph/conn/spi"
"periph.io/x/periph/conn/spi/spitest"
"periph.io/x/periph/devices"
"periph.io/x/periph/devices/lepton/internal"
)
func TestNew_cs(t *testing.T) {
i := i2ctest.Playback{
Ops: append(initSequence(),
[]i2ctest.IO{
{Addr: 42, W: []byte{0x0, 0x2}, R: []byte{0x0, 0x6}}, // waitIdle
{Addr: 42, W: []byte{0x0, 0x6, 0x0, 0x0}},
{Addr: 42, W: []byte{0x0, 0x4, 0x48, 0x2}},
{Addr: 42, W: []byte{0x0, 0x2}, R: []byte{0x0, 0x6}}, // waitIdle
}...),
}
s := spitest.Playback{}
d, err := New(&s, &i, &gpiotest.Pin{N: "CS"})
if err != nil {
t.Fatal(err)
}
if err := d.Halt(); err != nil {
t.Fatal(err)
}
if err := i.Close(); err != nil {
t.Fatal(err)
}
if err := s.Close(); err != nil {
t.Fatal(err)
}
}
func TestNew(t *testing.T) {
i := i2ctest.Playback{Ops: initSequence()}
s := spitest.Playback{CSPin: &gpiotest.Pin{N: "CS"}}
_, err := New(&s, &i, nil)
if err != nil {
t.Fatal(err)
}
if err := i.Close(); err != nil {
t.Fatal(err)
}
if err := s.Close(); err != nil {
t.Fatal(err)
}
}
func TestNew_Init_fail(t *testing.T) {
// Strip off last command.
ops := initSequence()
i := i2ctest.Playback{Ops: ops[:len(ops)-1], DontPanic: true}
s := spitest.Playback{CSPin: &gpiotest.Pin{N: "CS"}}
if _, err := New(&s, &i, nil); err == nil {
t.Fatal("cci.Dev.Init() failed")
}
if err := i.Close(); err != nil {
t.Fatal(err)
}
if err := s.Close(); err != nil {
t.Fatal(err)
}
}
func TestNew_GetStatus_fail(t *testing.T) {
i := i2ctest.Playback{
Ops: []i2ctest.IO{
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 6, 0, 4}}, // GetStatus()
{Addr: 42, W: []byte{0, 4, 2, 4}}, //
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
},
DontPanic: true,
}
s := spitest.Playback{CSPin: &gpiotest.Pin{N: "CS"}}
if _, err := New(&s, &i, nil); err == nil {
t.Fatal("cci.Dev.GetStatus() failed")
}
if err := i.Close(); err != nil {
t.Fatal(err)
}
if err := s.Close(); err != nil {
t.Fatal(err)
}
}
func TestNew_GetStatus_bad(t *testing.T) {
i := i2ctest.Playback{
Ops: []i2ctest.IO{
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 6, 0, 4}}, // GetStatus()
{Addr: 42, W: []byte{0, 4, 2, 4}}, //
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 8}, R: []byte{1, 0, 0, 0, 0, 0, 0, 0}}, // GetStatus() result
},
DontPanic: true,
}
s := spitest.Playback{CSPin: &gpiotest.Pin{N: "CS"}}
if _, err := New(&s, &i, nil); err == nil {
t.Fatal("cci.Dev.GetStatus() failed")
}
if err := i.Close(); err != nil {
t.Fatal(err)
}
if err := s.Close(); err != nil {
t.Fatal(err)
}
}
func TestNew_fail_invalid(t *testing.T) {
i := i2ctest.Record{}
s := spitest.Record{}
if _, err := New(&s, &i, nil); err == nil {
t.Fatal("spi.Pins.CS() returns INVALID")
}
}
func TestNew_fail_no_Pins(t *testing.T) {
i := i2ctest.Record{}
s := spiStream{}
if _, err := New(&s, &i, nil); err == nil {
t.Fatal("no CS and no spi.Pins")
}
}
func TestNew_DevParams(t *testing.T) {
i := i2ctest.Record{}
s := spiStream{err: errors.New("injected")}
if _, err := New(&s, &i, &gpiotest.Pin{N: "CS"}); err == nil {
t.Fatal("DevParams failed")
}
}
func TestNew_cci_New_fail(t *testing.T) {
i := i2ctest.Playback{DontPanic: true}
s := spitest.Record{}
if _, err := New(&s, &i, &gpiotest.Pin{N: "CS"}); err == nil {
t.Fatal("cci.New failed")
}
}
func TestReadImg(t *testing.T) {
i := i2ctest.Playback{Ops: initSequence()}
s := spiStream{data: prepareFrame(t)}
d, err := New(&s, &i, &gpiotest.Pin{N: "CS"})
if err != nil {
t.Fatal(err)
}
f, err := d.ReadImg()
if err != nil {
t.Fatal(err)
}
if f.Metadata.TempHousing != devices.Celsius(2000) {
t.Fatal(f.Metadata.TempHousing)
}
// Compare the frame with the reference image. It should match.
ref := referenceFrame()
if !bytes.Equal(ref.Pix, f.Pix) {
offset := 0
for {
if ref.Pix[offset] != f.Pix[offset] {
break
}
offset++
}
t.Fatalf("different pixels at offset %d:\n%s\n%s", offset, hex.EncodeToString(ref.Pix[offset:]), hex.EncodeToString(f.Pix[offset:]))
}
if err := i.Close(); err != nil {
t.Fatal(err)
}
}
func TestReadImg_fail_Tx(t *testing.T) {
i := i2ctest.Playback{Ops: initSequence()}
s := spitest.Playback{Playback: conntest.Playback{DontPanic: true}}
d, err := New(&s, &i, &gpiotest.Pin{N: "CS"})
if err != nil {
t.Fatal(err)
}
if _, err := d.ReadImg(); err == nil {
t.Fatal("spi bus Tx failed")
}
if err := i.Close(); err != nil {
t.Fatal(err)
}
}
func TestReadImg_fail_OUt(t *testing.T) {
i := i2ctest.Playback{Ops: initSequence()}
s := spitest.Playback{Playback: conntest.Playback{DontPanic: true}}
d, err := New(&s, &i, &failPin{})
if err != nil {
t.Fatal(err)
}
if _, err := d.ReadImg(); err == nil {
t.Fatal("spi bus Tx failed")
}
if err := i.Close(); err != nil {
t.Fatal(err)
}
}
func TestParseTelemetry_fail(t *testing.T) {
l := telemetryLine(t)
m := Metadata{}
if m.parseTelemetry(l[:len(l)-1]) == nil {
t.Fatal("buffer too short")
}
buf := bytes.Buffer{}
rowA := telemetryRowA{StatusBits: statusMaskNil}
if err := binary.Write(&buf, internal.Big16, &rowA); err != nil {
t.Fatal(err)
}
if m.parseTelemetry(buf.Bytes()) == nil {
t.Fatal("bad status")
}
}
func TestParseTelemetry(t *testing.T) {
m := Metadata{}
if err := m.parseTelemetry(telemetryLine(t)); err != nil {
t.Fatal(err)
}
data := []struct {
rowA telemetryRowA
success bool
}{
{telemetryRowA{TelemetryRevision: 8, StatusBits: 0 << statusFFCStateShift}, true},
{telemetryRowA{TelemetryRevision: 8, StatusBits: 1 << statusFFCStateShift}, true},
{telemetryRowA{TelemetryRevision: 8, StatusBits: 2 << statusFFCStateShift}, true},
{telemetryRowA{TelemetryRevision: 8, StatusBits: 3 << statusFFCStateShift}, false},
{telemetryRowA{StatusBits: 0 << statusFFCStateShift}, true},
{telemetryRowA{StatusBits: 1 << statusFFCStateShift}, false},
{telemetryRowA{StatusBits: 2 << statusFFCStateShift}, true},
{telemetryRowA{StatusBits: 3 << statusFFCStateShift}, true},
}
for _, line := range data {
buf := bytes.Buffer{}
if err := binary.Write(&buf, internal.Big16, &line.rowA); err != nil {
t.Fatal(err)
}
err := m.parseTelemetry(buf.Bytes())
if line.success {
if err != nil {
t.Fatal(err)
}
} else {
if err == nil {
t.Fatal("expected failure")
}
}
}
}
//
func initSequence() []i2ctest.IO {
return []i2ctest.IO{
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 6, 0, 4}}, // GetStatus()
{Addr: 42, W: []byte{0, 4, 2, 4}}, //
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 8}, R: []byte{0, 0, 0, 0, 0, 0, 0, 0}}, // GetStatus() result
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 8, 0, 0, 0, 0}}, // Init()
{Addr: 42, W: []byte{0, 6, 0, 0x2}}, //
{Addr: 42, W: []byte{0, 4, 1, 0x1}}, //
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 8, 0, 1, 0, 0}}, //
{Addr: 42, W: []byte{0, 6, 0, 2}}, //
{Addr: 42, W: []byte{0, 4, 2, 0x19}}, //
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
{Addr: 42, W: []byte{0, 8, 0, 0, 0, 0}}, //
{Addr: 42, W: []byte{0, 6, 0, 2}}, //
{Addr: 42, W: []byte{0, 4, 2, 0x1d}}, // Init() end
{Addr: 42, W: []byte{0, 2}, R: []byte{0, 6}}, // waitIdle
}
}
func telemetryLine(t *testing.T) []byte {
b := bytes.Buffer{}
rowA := telemetryRowA{
TelemetryRevision: 8,
StatusBits: statusFFCDesired,
HousingTemp: internal.CentiK(27515), // 2°C
}
if err := binary.Write(&b, internal.Big16, &rowA); err != nil {
t.Fatal(err)
}
return b.Bytes()
}
func appendHeader(t *testing.T, i int, d []byte) []byte {
if len(d) != 160 {
t.Fatalf("currently hardcoded for 80x60: %d", len(d))
}
out := make([]byte, 164)
internal.Big16.PutUint16(out, uint16(i))
copy(out[4:], d)
calcCRC(out)
return out
}
func referenceFrame() *image.Gray16 {
r := image.Rect(0, 0, 80, 60)
img := image.NewGray16(r)
for y := r.Min.Y; y < r.Max.Y; y++ {
for x := r.Min.X; x < r.Max.X; x++ {
img.SetGray16(x, y, color.Gray16{uint16(8192 - 80 + (x * 2))})
}
}
return img
}
func prepareFrame(t *testing.T) []byte {
buf := bytes.Buffer{}
tmp := make([]byte, 160)
buf.Write(appendHeader(t, 0, telemetryLine(t)))
buf.Write(appendHeader(t, 1, tmp))
buf.Write(appendHeader(t, 2, tmp))
img := referenceFrame()
r := img.Bounds()
for y := 0; y < r.Max.Y; y++ {
for x := 0; x < r.Max.X; x++ {
internal.Big16.PutUint16(tmp[x*2:], img.Gray16At(x, y).Y)
}
buf.Write(appendHeader(t, y+3, tmp))
}
return buf.Bytes()
}
func calcCRC(d []byte) {
tmp := make([]byte, len(d))
copy(tmp, d)
tmp[0] &^= 0x0F
tmp[2] = 0
tmp[3] = 0
internal.Big16.PutUint16(d[2:], internal.CRC16(tmp))
}
type spiStream struct {
t *testing.T
data []byte
offset int
err error
}
func (s *spiStream) DevParams(maxHz int64, mode spi.Mode, bits int) error {
if maxHz != 20000000 {
s.t.Fatal(maxHz)
}
if mode != spi.Mode3 {
s.t.Fatal(mode)
}
if bits != 8 {
s.t.Fatal(bits)
}
return s.err
}
func (s *spiStream) Tx(w, r []byte) error {
if w != nil {
s.t.Fatal("write is not implemented")
}
if s.offset < len(s.data) {
copy(r, s.data[s.offset:])
s.offset += len(r)
}
return s.err
}
func (s *spiStream) TxPackets(p []spi.Packet) error {
s.t.Fatal("TxPackets is not implemented")
return nil
}
func (s *spiStream) Duplex() conn.Duplex {
return conn.DuplexUnknown
}
func (s *spiStream) MaxTxSize() int {
return 7 * 164
}
type failPin struct {
gpiotest.Pin
}
func (f *failPin) Out(l gpio.Level) error {
return errors.New("injected")
}
Loading…
Cancel
Save