mirror of https://github.com/periph/devices
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
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…
Reference in New Issue