From f2f52a32d876731c7efc57cccf2291b3edbc051d Mon Sep 17 00:00:00 2001 From: Trevor Rosen Date: Wed, 29 Aug 2018 13:15:04 -0600 Subject: [PATCH 01/47] Initial stab at Robot-based work --- robot.go | 21 ++++--- robot_work.go | 170 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 robot_work.go diff --git a/robot.go b/robot.go index 14aa9d8f..661cfed8 100644 --- a/robot.go +++ b/robot.go @@ -43,14 +43,15 @@ func NewJSONRobot(robot *Robot) *JSONRobot { // It contains its own work routine and a collection of // custom commands to control a robot remotely via the Gobot api. type Robot struct { - Name string - Work func() - connections *Connections - devices *Devices - trap func(chan os.Signal) - AutoRun bool - running atomic.Value - done chan bool + Name string + Work func() + connections *Connections + devices *Devices + trap func(chan os.Signal) + AutoRun bool + running atomic.Value + done chan bool + workRegistry *RobotWorkRegistry Commander Eventer } @@ -139,6 +140,10 @@ func NewRobot(v ...interface{}) *Robot { } } + r.workRegistry = &RobotWorkRegistry{ + r: make(map[string]*RobotWork), + } + r.running.Store(false) log.Println("Robot", r.Name, "initialized.") diff --git a/robot_work.go b/robot_work.go new file mode 100644 index 00000000..fbc52ea1 --- /dev/null +++ b/robot_work.go @@ -0,0 +1,170 @@ +// RobotWork and the RobotWork registry represent units of executing computation +// managed at the Robot level. Unlike the utility functions gobot.After and gobot.Every, +// RobotWork units require a context.Context, and can be cancelled externally by calling code. +package gobot + +import ( + "context" + "fmt" + "time" + + "sync" + + "github.com/gobuffalo/uuid" +) + +// RobotWorkRegistry contains all the work units registered on a Robot +type RobotWorkRegistry struct { + sync.RWMutex + + r map[string]*RobotWork +} + +const ( + EveryWorkKind = "every" + AfterWorkKind = "after" +) + +// RobotWork represents a unit of work (in the form of an arbitrary Go function) +// to be done once or on a recurring basis. It encapsulations notions of duration, +// context, count of successful runs, etc. +type RobotWork struct { + id uuid.UUID + kind string + tickCount int + ctx context.Context + cancelFunc context.CancelFunc + function func() + ticker *time.Ticker + duration time.Duration +} + +// ID returns the UUID of the RobotWork +func (rw *RobotWork) ID() uuid.UUID { + return rw.id +} + +// CancelFunc returns the context.CancelFunc used to cancel the work +func (rw *RobotWork) CancelFunc() context.CancelFunc { + return rw.cancelFunc +} + +// Ticker returns the time.Ticker used in an Every so that calling code can sync on the same channel +func (rw *RobotWork) Ticker() *time.Ticker { + if rw.kind == AfterWorkKind { + return nil + } + return rw.ticker +} + +// Duration returns the timeout until an After fires or the period of an Every +func (rw *RobotWork) Duration() time.Duration { + return rw.duration +} + +func (rw *RobotWork) String() string { + format := `ID: %s +Kind: %s +TickCount: %d + +` + return fmt.Sprintf(format, rw.id, rw.kind, rw.tickCount) +} + +// WorkRegistry returns the Robot's WorkRegistry +func (r *Robot) WorkRegistry() *RobotWorkRegistry { + return r.workRegistry +} + +// Every calls the given function for every tick of the provided duration. +func (r *Robot) Every(ctx context.Context, d time.Duration, f func()) *RobotWork { + rw := r.workRegistry.registerEvery(ctx, d, f) + go func() { + EVERYWORK: + for { + select { + case <-rw.ctx.Done(): + r.workRegistry.delete(rw.id) + break EVERYWORK + case <-rw.ticker.C: + rw.tickCount++ + f() + } + } + }() + + return rw +} + +// After calls the given function after the provided duration has elapsed +func (r *Robot) After(ctx context.Context, d time.Duration, f func()) *RobotWork { + rw := r.workRegistry.registerAfter(ctx, d, f) + ch := time.After(d) + go func() { + AFTERWORK: + for { + select { + case <-rw.ctx.Done(): + r.workRegistry.delete(rw.id) + break AFTERWORK + case <-ch: + f() + } + } + }() + return rw +} + +// Get returns the RobotWork specified by the provided ID. To delete something from the registry, it's +// necessary to call its context.CancelFunc, which will perform a goroutine-safe delete on the underlying +// map. +func (rwr *RobotWorkRegistry) Get(id uuid.UUID) *RobotWork { + rwr.Lock() + defer rwr.Unlock() + return rwr.r[id.String()] +} + +// Delete returns the RobotWork specified by the provided ID +func (rwr *RobotWorkRegistry) delete(id uuid.UUID) { + rwr.Lock() + defer rwr.Unlock() + delete(rwr.r, id.String()) +} + +// registerAfter creates a new unit of RobotWork and sets up its context/cancellation +func (rwr *RobotWorkRegistry) registerAfter(ctx context.Context, d time.Duration, f func()) *RobotWork { + rwr.Lock() + defer rwr.Unlock() + + id, _ := uuid.NewV4() + rw := &RobotWork{ + id: id, + kind: AfterWorkKind, + function: f, + duration: d, + } + + rw.ctx, rw.cancelFunc = context.WithCancel(ctx) + rwr.r[id.String()] = rw + return rw +} + +// registerEvery creates a new unit of RobotWork and sets up its context/cancellation +func (rwr *RobotWorkRegistry) registerEvery(ctx context.Context, d time.Duration, f func()) *RobotWork { + rwr.Lock() + defer rwr.Unlock() + + id, _ := uuid.NewV4() + rw := &RobotWork{ + id: id, + kind: EveryWorkKind, + function: f, + duration: d, + ticker: time.NewTicker(d), + } + + rw.ctx, rw.cancelFunc = context.WithCancel(ctx) + + rwr.r[id.String()] = rw + return rw +} From 0ecb66c2d0bb5406562152eb186ac6689db0c013 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Wed, 29 Aug 2018 14:00:33 -0600 Subject: [PATCH 02/47] i2c: update PCA9685 driver to use same protocol as Adafruit Python lib Signed-off-by: Ron Evans --- drivers/i2c/pca9685_driver.go | 81 +++++++++++++++++++++++++++--- drivers/i2c/pca9685_driver_test.go | 42 +++++++++++++--- 2 files changed, 110 insertions(+), 13 deletions(-) diff --git a/drivers/i2c/pca9685_driver.go b/drivers/i2c/pca9685_driver.go index 13ed4eca..2ffebb0e 100644 --- a/drivers/i2c/pca9685_driver.go +++ b/drivers/i2c/pca9685_driver.go @@ -11,6 +11,7 @@ const pca9685Address = 0x40 const ( PCA9685_MODE1 = 0x00 + PCA9685_MODE2 = 0x01 PCA9685_PRESCALE = 0xFE PCA9685_SUBADR1 = 0x02 PCA9685_SUBADR2 = 0x03 @@ -23,6 +24,12 @@ const ( PCA9685_ALLLED_ON_H = 0xFB PCA9685_ALLLED_OFF_L = 0xFC PCA9685_ALLLED_OFF_H = 0xFD + + PCA9685_RESTART = 0x80 + PCA9685_SLEEP = 0x10 + PCA9685_ALLCALL = 0x01 + PCA9685_INVRT = 0x10 + PCA9685_OUTDRV = 0x04 ) // PCA9685Driver is a Gobot Driver for the PCA9685 16-channel 12-bit PWM/Servo controller. @@ -101,14 +108,35 @@ func (p *PCA9685Driver) Start() (err error) { return err } - if _, err := p.connection.Write([]byte{PCA9685_MODE1, 0x00}); err != nil { + if err := p.SetAllPWM(0, 0); err != nil { return err } - if _, err := p.connection.Write([]byte{PCA9685_ALLLED_OFF_H, 0x10}); err != nil { + if _, err := p.connection.Write([]byte{PCA9685_MODE2, PCA9685_OUTDRV}); err != nil { return err } + if _, err := p.connection.Write([]byte{PCA9685_MODE1, PCA9685_ALLCALL}); err != nil { + return err + } + + time.Sleep(5 * time.Millisecond) + + if _, err := p.connection.Write([]byte{byte(PCA9685_MODE1)}); err != nil { + return err + } + oldmode, err := p.connection.ReadByte() + if err != nil { + return err + } + oldmode = oldmode &^ byte(PCA9685_SLEEP) + + if _, err := p.connection.Write([]byte{PCA9685_MODE1, oldmode}); err != nil { + return err + } + + time.Sleep(5 * time.Millisecond) + return } @@ -127,7 +155,46 @@ func (p *PCA9685Driver) Halt() (err error) { // Most typically you set "on" to a zero value, and then set "off" to your desired duty. // func (p *PCA9685Driver) SetPWM(channel int, on uint16, off uint16) (err error) { - if _, err := p.connection.Write([]byte{byte(PCA9685_LED0_ON_L + 4*channel), byte(on), byte(on >> 8), byte(off), byte(off >> 8)}); err != nil { + if _, err := p.connection.Write([]byte{byte(PCA9685_LED0_ON_L + 4*channel), byte(on) & 0xFF}); err != nil { + return err + } + + if _, err := p.connection.Write([]byte{byte(PCA9685_LED0_ON_H + 4*channel), byte(on >> 8)}); err != nil { + return err + } + + if _, err := p.connection.Write([]byte{byte(PCA9685_LED0_OFF_L + 4*channel), byte(off) & 0xFF}); err != nil { + return err + } + + if _, err := p.connection.Write([]byte{byte(PCA9685_LED0_OFF_H + 4*channel), byte(off >> 8)}); err != nil { + return err + } + + return +} + +// SetAllPWM sets all channels to a pwm value from 0-4096. +// Params: +// on uint16 - the time to start the pulse +// off uint16 - the time to stop the pulse +// +// Most typically you set "on" to a zero value, and then set "off" to your desired duty. +// +func (p *PCA9685Driver) SetAllPWM(on uint16, off uint16) (err error) { + if _, err := p.connection.Write([]byte{byte(PCA9685_ALLLED_ON_L), byte(on) & 0xFF}); err != nil { + return err + } + + if _, err := p.connection.Write([]byte{byte(PCA9685_ALLLED_ON_H), byte(on >> 8)}); err != nil { + return err + } + + if _, err := p.connection.Write([]byte{byte(PCA9685_ALLLED_OFF_L), byte(off) & 0xFF}); err != nil { + return err + } + + if _, err := p.connection.Write([]byte{byte(PCA9685_ALLLED_OFF_H), byte(off >> 8)}); err != nil { return err } @@ -149,8 +216,7 @@ func (p *PCA9685Driver) SetPWMFreq(freq float32) error { if _, err := p.connection.Write([]byte{byte(PCA9685_MODE1)}); err != nil { return err } - data := make([]byte, 1) - oldmode, err := p.connection.Read(data) + oldmode, err := p.connection.ReadByte() if err != nil { return err } @@ -170,9 +236,10 @@ func (p *PCA9685Driver) SetPWMFreq(freq float32) error { return err } - time.Sleep(100 * time.Millisecond) + time.Sleep(5 * time.Millisecond) + // Enable response to All Call address, enable auto-increment, clear restart - if _, err := p.connection.Write([]byte{byte(PCA9685_MODE1), byte(oldmode | 0xa1)}); err != nil { + if _, err := p.connection.Write([]byte{byte(PCA9685_MODE1), byte(oldmode | 0x80)}); err != nil { return err } diff --git a/drivers/i2c/pca9685_driver_test.go b/drivers/i2c/pca9685_driver_test.go index 52bf44c3..0b318542 100644 --- a/drivers/i2c/pca9685_driver_test.go +++ b/drivers/i2c/pca9685_driver_test.go @@ -52,8 +52,11 @@ func TestPCA9685DriverOptions(t *testing.T) { // Methods func TestPCA9685DriverStart(t *testing.T) { - pca := initTestPCA9685Driver() - + pca, adaptor := initTestPCA9685DriverWithStubbedAdaptor() + adaptor.i2cReadImpl = func(b []byte) (int, error) { + copy(b, []byte{0x01}) + return 1, nil + } gobottest.Assert(t, pca.Start(), nil) } @@ -72,21 +75,32 @@ func TestPCA9685DriverStartWriteError(t *testing.T) { } func TestPCA9685DriverHalt(t *testing.T) { - pca := initTestPCA9685Driver() + pca, adaptor := initTestPCA9685DriverWithStubbedAdaptor() + adaptor.i2cReadImpl = func(b []byte) (int, error) { + copy(b, []byte{0x01}) + return 1, nil + } gobottest.Assert(t, pca.Start(), nil) gobottest.Assert(t, pca.Halt(), nil) } func TestPCA9685DriverSetPWM(t *testing.T) { - pca := initTestPCA9685Driver() + pca, adaptor := initTestPCA9685DriverWithStubbedAdaptor() + adaptor.i2cReadImpl = func(b []byte) (int, error) { + copy(b, []byte{0x01}) + return 1, nil + } gobottest.Assert(t, pca.Start(), nil) gobottest.Assert(t, pca.SetPWM(0, 0, 256), nil) } func TestPCA9685DriverSetPWMError(t *testing.T) { pca, adaptor := initTestPCA9685DriverWithStubbedAdaptor() + adaptor.i2cReadImpl = func(b []byte) (int, error) { + copy(b, []byte{0x01}) + return 1, nil + } gobottest.Assert(t, pca.Start(), nil) - adaptor.i2cWriteImpl = func([]byte) (int, error) { return 0, errors.New("write error") } @@ -95,6 +109,10 @@ func TestPCA9685DriverSetPWMError(t *testing.T) { func TestPCA9685DriverSetPWMFreq(t *testing.T) { pca, adaptor := initTestPCA9685DriverWithStubbedAdaptor() + adaptor.i2cReadImpl = func(b []byte) (int, error) { + copy(b, []byte{0x01}) + return 1, nil + } gobottest.Assert(t, pca.Start(), nil) adaptor.i2cReadImpl = func(b []byte) (int, error) { @@ -106,6 +124,10 @@ func TestPCA9685DriverSetPWMFreq(t *testing.T) { func TestPCA9685DriverSetPWMFreqReadError(t *testing.T) { pca, adaptor := initTestPCA9685DriverWithStubbedAdaptor() + adaptor.i2cReadImpl = func(b []byte) (int, error) { + copy(b, []byte{0x01}) + return 1, nil + } gobottest.Assert(t, pca.Start(), nil) adaptor.i2cReadImpl = func(b []byte) (int, error) { @@ -116,6 +138,10 @@ func TestPCA9685DriverSetPWMFreqReadError(t *testing.T) { func TestPCA9685DriverSetPWMFreqWriteError(t *testing.T) { pca, adaptor := initTestPCA9685DriverWithStubbedAdaptor() + adaptor.i2cReadImpl = func(b []byte) (int, error) { + copy(b, []byte{0x01}) + return 1, nil + } gobottest.Assert(t, pca.Start(), nil) adaptor.i2cWriteImpl = func([]byte) (int, error) { @@ -131,7 +157,11 @@ func TestPCA9685DriverSetName(t *testing.T) { } func TestPCA9685DriverCommands(t *testing.T) { - pca := initTestPCA9685Driver() + pca, adaptor := initTestPCA9685DriverWithStubbedAdaptor() + adaptor.i2cReadImpl = func(b []byte) (int, error) { + copy(b, []byte{0x01}) + return 1, nil + } pca.Start() err := pca.Command("PwmWrite")(map[string]interface{}{"pin": "1", "val": "1"}) From 4ebaf399bec2d5ff6ad5e8104b52289ecdd9592a Mon Sep 17 00:00:00 2001 From: Trevor Rosen Date: Wed, 29 Aug 2018 16:07:46 -0600 Subject: [PATCH 03/47] Add some tests for RobotWork management --- robot_work.go | 1 + robot_work_test.go | 66 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 robot_work_test.go diff --git a/robot_work.go b/robot_work.go index fbc52ea1..c69b47c3 100644 --- a/robot_work.go +++ b/robot_work.go @@ -85,6 +85,7 @@ func (r *Robot) Every(ctx context.Context, d time.Duration, f func()) *RobotWork select { case <-rw.ctx.Done(): r.workRegistry.delete(rw.id) + rw.ticker.Stop() break EVERYWORK case <-rw.ticker.C: rw.tickCount++ diff --git a/robot_work_test.go b/robot_work_test.go new file mode 100644 index 00000000..84bf56d5 --- /dev/null +++ b/robot_work_test.go @@ -0,0 +1,66 @@ +package gobot + +import ( + "context" + "testing" + + "time" + + "github.com/gobuffalo/uuid" + "github.com/stretchr/testify/assert" +) + +func TestRobotWork(t *testing.T) { + id, _ := uuid.NewV4() + + rw := &RobotWork{ + id: id, + kind: EveryWorkKind, + function: func() {}, + } + + duration := time.Second * 1 + ctx, cancelFunc := context.WithCancel(context.Background()) + + rw.ctx = ctx + rw.cancelFunc = cancelFunc + rw.duration = duration + + t.Run("ID()", func(t *testing.T) { + assert.Equal(t, rw.ID(), id) + }) + + t.Run("Ticker()", func(t *testing.T) { + t.Skip() + }) + + t.Run("Duration()", func(t *testing.T) { + assert.Equal(t, rw.duration, duration) + }) +} + +func TestRobotWorkRegistry(t *testing.T) { + robot := NewRobot("testbot") + + rw := robot.Every(context.Background(), time.Millisecond*250, func() { + _ = 1 + 1 + }) + + t.Run("Get retrieves", func(t *testing.T) { + assert.Equal(t, robot.workRegistry.Get(rw.id), rw) + }) + + t.Run("delete deletes", func(t *testing.T) { + robot.workRegistry.delete(rw.id) + postDeleteKeys := collectStringKeysFromWorkRegistry(robot.workRegistry) + assert.NotContains(t, postDeleteKeys, rw.id.String()) + }) +} + +func collectStringKeysFromWorkRegistry(rwr *RobotWorkRegistry) []string { + keys := make([]string, len(rwr.r)) + for k, _ := range rwr.r { + keys = append(keys, k) + } + return keys +} From b3fc9c10e7e355147695fbb0b920a6c472c485cb Mon Sep 17 00:00:00 2001 From: Trevor Rosen Date: Wed, 29 Aug 2018 22:30:38 -0600 Subject: [PATCH 04/47] Tests and related tweaks --- robot.go | 24 +++++++++++++++--------- robot_work.go | 45 +++++++++++++++++++++++++++++++++++++-------- robot_work_test.go | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 86 insertions(+), 17 deletions(-) diff --git a/robot.go b/robot.go index 661cfed8..765d06ae 100644 --- a/robot.go +++ b/robot.go @@ -7,6 +7,8 @@ import ( "os/signal" "sync/atomic" + "sync" + multierror "github.com/hashicorp/go-multierror" ) @@ -43,15 +45,17 @@ func NewJSONRobot(robot *Robot) *JSONRobot { // It contains its own work routine and a collection of // custom commands to control a robot remotely via the Gobot api. type Robot struct { - Name string - Work func() - connections *Connections - devices *Devices - trap func(chan os.Signal) - AutoRun bool - running atomic.Value - done chan bool - workRegistry *RobotWorkRegistry + Name string + Work func() + connections *Connections + devices *Devices + trap func(chan os.Signal) + AutoRun bool + running atomic.Value + done chan bool + workRegistry *RobotWorkRegistry + WorkEveryWaitGroup *sync.WaitGroup + WorkAfterWaitGroup *sync.WaitGroup Commander Eventer } @@ -143,6 +147,8 @@ func NewRobot(v ...interface{}) *Robot { r.workRegistry = &RobotWorkRegistry{ r: make(map[string]*RobotWork), } + r.WorkAfterWaitGroup = &sync.WaitGroup{} + r.WorkEveryWaitGroup = &sync.WaitGroup{} r.running.Store(false) log.Println("Robot", r.Name, "initialized.") diff --git a/robot_work.go b/robot_work.go index c69b47c3..39729b47 100644 --- a/robot_work.go +++ b/robot_work.go @@ -1,6 +1,3 @@ -// RobotWork and the RobotWork registry represent units of executing computation -// managed at the Robot level. Unlike the utility functions gobot.After and gobot.Every, -// RobotWork units require a context.Context, and can be cancelled externally by calling code. package gobot import ( @@ -25,9 +22,28 @@ const ( AfterWorkKind = "after" ) -// RobotWork represents a unit of work (in the form of an arbitrary Go function) -// to be done once or on a recurring basis. It encapsulations notions of duration, -// context, count of successful runs, etc. +// RobotWork and the RobotWork registry represent units of executing computation +// managed at the Robot level. Unlike the utility functions gobot.After and gobot.Every, +// RobotWork units require a context.Context, and can be cancelled externally by calling code. +// +// Usage: +// +// someWork := myRobot.Every(context.Background(), time.Second * 2, func(){ +// fmt.Println("Here I am doing work") +// }) +// +// someWork.CallCancelFunc() // Cancel next tick and remove from work registry +// +// goroutines for Every and After are run on their own WaitGroups for synchronization: +// +// someWork2 := myRobot.Every(context.Background(), time.Second * 2, func(){ +// fmt.Println("Here I am doing more work") +// }) +// +// somework2.CallCancelFunc() +// +// // wait for both Every calls to finish +// robot.WorkEveryWaitGroup().Wait() type RobotWork struct { id uuid.UUID kind string @@ -49,6 +65,11 @@ func (rw *RobotWork) CancelFunc() context.CancelFunc { return rw.cancelFunc } +// CallCancelFunc calls the context.CancelFunc used to cancel the work +func (rw *RobotWork) CallCancelFunc() { + rw.cancelFunc() +} + // Ticker returns the time.Ticker used in an Every so that calling code can sync on the same channel func (rw *RobotWork) Ticker() *time.Ticker { if rw.kind == AfterWorkKind { @@ -57,6 +78,11 @@ func (rw *RobotWork) Ticker() *time.Ticker { return rw.ticker } +// TickCount returns the number of times the function successfully ran +func (rw *RobotWork) TickCount() int { + return rw.tickCount +} + // Duration returns the timeout until an After fires or the period of an Every func (rw *RobotWork) Duration() time.Duration { return rw.duration @@ -79,6 +105,7 @@ func (r *Robot) WorkRegistry() *RobotWorkRegistry { // Every calls the given function for every tick of the provided duration. func (r *Robot) Every(ctx context.Context, d time.Duration, f func()) *RobotWork { rw := r.workRegistry.registerEvery(ctx, d, f) + r.WorkEveryWaitGroup.Add(1) go func() { EVERYWORK: for { @@ -88,12 +115,12 @@ func (r *Robot) Every(ctx context.Context, d time.Duration, f func()) *RobotWork rw.ticker.Stop() break EVERYWORK case <-rw.ticker.C: - rw.tickCount++ f() + rw.tickCount++ } } + r.WorkEveryWaitGroup.Done() }() - return rw } @@ -101,6 +128,7 @@ func (r *Robot) Every(ctx context.Context, d time.Duration, f func()) *RobotWork func (r *Robot) After(ctx context.Context, d time.Duration, f func()) *RobotWork { rw := r.workRegistry.registerAfter(ctx, d, f) ch := time.After(d) + r.WorkAfterWaitGroup.Add(1) go func() { AFTERWORK: for { @@ -112,6 +140,7 @@ func (r *Robot) After(ctx context.Context, d time.Duration, f func()) *RobotWork f() } } + r.WorkAfterWaitGroup.Done() }() return rw } diff --git a/robot_work_test.go b/robot_work_test.go index 84bf56d5..2abe62c4 100644 --- a/robot_work_test.go +++ b/robot_work_test.go @@ -57,6 +57,40 @@ func TestRobotWorkRegistry(t *testing.T) { }) } +func TestRobotAutomationFunctions(t *testing.T) { + t.Run("Every with cancel", func(t *testing.T) { + robot := NewRobot("testbot") + + rw := robot.Every(context.Background(), time.Millisecond*10, func() { + _ = 1 + 1 // perform mindless computation! + }) + + time.Sleep(time.Millisecond * 25) + rw.CallCancelFunc() + + robot.WorkEveryWaitGroup.Wait() + + assert.Equal(t, 2, rw.tickCount) + postDeleteKeys := collectStringKeysFromWorkRegistry(robot.workRegistry) + assert.NotContains(t, postDeleteKeys, rw.id.String()) + }) + + t.Run("After with cancel", func(t *testing.T) { + robot := NewRobot("testbot") + + rw := robot.After(context.Background(), time.Millisecond*10, func() { + _ = 1 + 1 // perform mindless computation! + }) + + rw.CallCancelFunc() + + robot.WorkAfterWaitGroup.Wait() + + postDeleteKeys := collectStringKeysFromWorkRegistry(robot.workRegistry) + assert.NotContains(t, postDeleteKeys, rw.id.String()) + }) +} + func collectStringKeysFromWorkRegistry(rwr *RobotWorkRegistry) []string { keys := make([]string, len(rwr.r)) for k, _ := range rwr.r { From f19b319ccea0ee7e551403938392e0efa7a59088 Mon Sep 17 00:00:00 2001 From: Trevor Rosen Date: Wed, 29 Aug 2018 23:00:01 -0600 Subject: [PATCH 05/47] Update Gopkg and add test dep to Travis YML --- .travis.yml | 1 + Gopkg.lock | 176 +++++++++++++++++++++++++++++++++++++++++++--------- Gopkg.toml | 4 ++ 3 files changed, 150 insertions(+), 31 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3538c3b6..12807f58 100644 --- a/.travis.yml +++ b/.travis.yml @@ -43,6 +43,7 @@ before_install: - cd $HOME/gopath/src/gobot.io/x/gobot - go get github.com/axw/gocov/gocov - go get -u github.com/golang/dep/cmd/dep + - go get -u github.com/stretchr/testify before_cache: - rm -f $HOME/fresh-cache install: diff --git a/Gopkg.lock b/Gopkg.lock index 1b8d1964..6c7d9c18 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -3,39 +3,58 @@ [[projects]] branch = "master" + digest = "1:da54e55f2f63347514121c0d7b53631970367b26281d057ec66925c57324b8f1" name = "github.com/bmizerany/pat" packages = ["."] + pruneopts = "" revision = "6226ea591a40176dd3ff9cd8eff81ed6ca721a00" [[projects]] + digest = "1:e85837cb04b78f61688c6eba93ea9d14f60d611e2aaf8319999b1a60d2dafbfa" name = "github.com/codegangsta/cli" packages = ["."] + pruneopts = "" revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" version = "v1.20.0" [[projects]] branch = "master" + digest = "1:ea040951144214212d0402e862a786e8d68d2a1734f975933e6ba084b4d05f2f" name = "github.com/creack/goselect" packages = ["."] - revision = "1bd5ca702c6154bccc56ecd598932ee8b295cab2" + pruneopts = "" + revision = "58854f77ee8d858ce751b0a9bcc5533fef7bfa9e" + +[[projects]] + digest = "1:0deddd908b6b4b768cfc272c16ee61e7088a60f7fe2f06c547bd3d8e1f8b8e77" + name = "github.com/davecgh/go-spew" + packages = ["spew"] + pruneopts = "" + revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" + version = "v1.1.1" [[projects]] branch = "master" + digest = "1:917123ed8122468eb815cc418af3bbf298c91f9cb1c36164e063f50e1a726e97" name = "github.com/donovanhide/eventsource" packages = ["."] + pruneopts = "" revision = "3ed64d21fb0b6bd8b49bcfec08f3004daee8723d" [[projects]] + digest = "1:3fa846cb3feb4e65371fe3c347c299de9b5bc3e71e256c0d940cd19b767a6ba0" name = "github.com/eclipse/paho.mqtt.golang" packages = [ ".", - "packets" + "packets", ] + pruneopts = "" revision = "36d01c2b4cbeb3d2a12063e4880ce30800af9560" version = "v1.1.1" [[projects]] branch = "master" + digest = "1:c177fa1d2bdfc487f4bb7188f0fc683f85fd8cadcf1293eece899c86e75d6337" name = "github.com/go-ble/ble" packages = [ ".", @@ -47,153 +66,222 @@ "linux/hci", "linux/hci/cmd", "linux/hci/evt", - "linux/hci/socket" + "linux/hci/socket", ] - revision = "788214691384e85e345bff9fd5eeb046f5983594" + pruneopts = "" + revision = "731710e91806e163fe770d93dc318683f6f53c63" [[projects]] - branch = "master" + digest = "1:c7382630b7f8958a68cf9c9314477a192890bea01d6bed9b22f95d3bad5530d2" + name = "github.com/gobuffalo/uuid" + packages = ["."] + pruneopts = "" + revision = "7652001f1b1ff3d69aa899943b668e9be27284a0" + version = "v2.0.3" + +[[projects]] + digest = "1:8e3bd93036b4a925fe2250d3e4f38f21cadb8ef623561cd80c3c50c114b13201" name = "github.com/hashicorp/errwrap" packages = ["."] - revision = "7554cd9344cec97297fa6649b055a8c98c2a1e55" + pruneopts = "" + revision = "8a6fb523712970c966eefc6b39ed2c5e74880354" + version = "v1.0.0" [[projects]] branch = "master" + digest = "1:72308fdd6d5ef61106a95be7ca72349a5565809042b6426a3cfb61d99483b824" name = "github.com/hashicorp/go-multierror" packages = ["."] - revision = "83588e72410abfbe4df460eeb6f30841ae47d4c4" + pruneopts = "" + revision = "886a7fbe3eb1c874d46f623bfa70af45f425b3d1" [[projects]] branch = "master" + digest = "1:aba830e5898f09dd06027a27a10f4d44e8b0762a3ba0c83a771a01f4e72b81c3" name = "github.com/hybridgroup/go-ardrone" packages = [ "client", "client/commands", - "client/navdata" + "client/navdata", ] + pruneopts = "" revision = "b9750d8d7b78f9638e5fdd899835e99d46b5a56c" [[projects]] branch = "master" + digest = "1:9956f4f58b2ccea25656086bcd7be0273915961a8231965a1a6c886874172054" name = "github.com/hybridgroup/mjpeg" packages = ["."] + pruneopts = "" revision = "4680f319790ebffe28bbee775ecd1725693731ca" [[projects]] + digest = "1:9ea83adf8e96d6304f394d40436f2eb44c1dc3250d223b74088cc253a6cd0a1c" name = "github.com/mattn/go-colorable" packages = ["."] + pruneopts = "" revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" version = "v0.0.9" [[projects]] + digest = "1:78229b46ddb7434f881390029bd1af7661294af31f6802e0e1bedaad4ab0af3c" name = "github.com/mattn/go-isatty" packages = ["."] + pruneopts = "" revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" version = "v0.0.3" [[projects]] branch = "master" + digest = "1:50416da10e189bc201e122e20078fb8e680a439cbdd24aaece06c434b4415b60" name = "github.com/mgutz/ansi" packages = ["."] + pruneopts = "" revision = "9520e82c474b0a04dd04f8a40959027271bab992" [[projects]] + digest = "1:47af139ef25cee26d54b8422b73a043ab5d8f5ec26a1ae49d3ca9212f1dbfdac" name = "github.com/mgutz/logxi" packages = ["v1"] + pruneopts = "" revision = "aebf8a7d67ab4625e0fd4a665766fef9a709161b" version = "v1" [[projects]] + digest = "1:f04a78a43f55f089c919beee8ec4a1495dee1bd271548da2cb44bf44699a6a61" name = "github.com/nats-io/go-nats" packages = [ "encoders/builtin", - "util" + "util", ] - revision = "29f9728a183bf3fa7e809e14edac00b33be72088" - version = "v1.3.0" + pruneopts = "" + revision = "fb0396ee0bdb8018b0fef30d6d1de798ce99cd05" + version = "v1.6.0" [[projects]] + digest = "1:f04a78a43f55f089c919beee8ec4a1495dee1bd271548da2cb44bf44699a6a61" name = "github.com/nats-io/nats" packages = ["."] - revision = "29f9728a183bf3fa7e809e14edac00b33be72088" - version = "v1.3.0" + pruneopts = "" + revision = "fb0396ee0bdb8018b0fef30d6d1de798ce99cd05" + version = "v1.6.0" [[projects]] + digest = "1:be61e8224b84064109eaba8157cbb4bbe6ca12443e182b6624fdfa1c0dcf53d9" name = "github.com/nats-io/nuid" packages = ["."] + pruneopts = "" revision = "289cccf02c178dc782430d534e3c1f5b72af807f" version = "v1.0.0" [[projects]] + digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca" name = "github.com/pkg/errors" packages = ["."] + pruneopts = "" revision = "645ef00459ed84a119197bfb8d8205042c6df63d" version = "v0.8.0" [[projects]] - branch = "master" - name = "github.com/raff/goble" - packages = ["xpc"] - revision = "b12b34f940c4cf4363660073539b5fa9fd96bd16" + digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" + name = "github.com/pmezard/go-difflib" + packages = ["difflib"] + pruneopts = "" + revision = "792786c7400a136282c1664665ae0a8db921c6c2" + version = "v1.0.0" [[projects]] branch = "master" + digest = "1:7b2df17857c98bcb45e200d6005f7af1f1e04757a77cd9e20cf27db05f0ad611" + name = "github.com/raff/goble" + packages = ["xpc"] + pruneopts = "" + revision = "efeac611681b89806d673dfbd8fc1690ca6bd093" + +[[projects]] + branch = "master" + digest = "1:eb22cc3727f58f5939373c397b0b9a8076f63e5b19584dcc00901dc5d407a77b" name = "github.com/sigurn/crc8" packages = ["."] + pruneopts = "" revision = "e55481d6f45c5a8f040343bace9013571dae103e" [[projects]] branch = "master" + digest = "1:52ed74cbf4d1bb1975642c817ec91b221c8963fa599885319407bf468431851d" name = "github.com/sigurn/utils" packages = ["."] + pruneopts = "" revision = "f19e41f79f8f006116f682c1af454591bc278ad4" [[projects]] - branch = "master" - name = "github.com/tarm/serial" - packages = ["."] - revision = "794054cb266296569307c08d1b475f44505dfab1" + digest = "1:c587772fb8ad29ad4db67575dad25ba17a51f072ff18a22b4f0257a4d9c24f75" + name = "github.com/stretchr/testify" + packages = ["assert"] + pruneopts = "" + revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" + version = "v1.2.2" [[projects]] + branch = "master" + digest = "1:54f00349c493a13c9340be00aebfe75c1cf7c58f2ed2d1ed5d1952105c925992" + name = "github.com/tarm/serial" + packages = ["."] + pruneopts = "" + revision = "eaafced92e9619f03c72527efeab21e326f3bc36" + +[[projects]] + digest = "1:4aa77644fa15ef170580070adcb70bd0adb02b0980bcc632eeaa528fd0e8137e" name = "github.com/veandco/go-sdl2" packages = ["sdl"] + pruneopts = "" revision = "271d2ec43388932fd2a80f4c2e81021734685a62" version = "v0.3" [[projects]] branch = "master" + digest = "1:488c97dd29d2f0ddb48e8a04aed2da02f43efbd6e6c9a7bf9653819f55c8e90c" name = "go.bug.st/serial.v1" packages = [ ".", - "unixutils" + "unixutils", ] - revision = "eae1344f9f90101f887b08d13391c34399f97873" + pruneopts = "" + revision = "5f7892a7bb453066bdc6683b9b5d24d9dee03ec1" [[projects]] + digest = "1:a5253f1c452abd612cf8975cde3f96285a00dc9015660e8d446291a48662b54d" name = "gocv.io/x/gocv" packages = ["."] + pruneopts = "" revision = "116580adca5ee7dbf8d1307c9072f5094051116f" version = "v0.16.0" [[projects]] branch = "master" + digest = "1:7dd0f1b8c8bd70dbae4d3ed3fbfaec224e2b27bcc0fc65882d6f1dba5b1f6e22" name = "golang.org/x/net" packages = [ + "internal/socks", "proxy", - "websocket" + "websocket", ] - revision = "01c190206fbdffa42f334f4b2bf2220f50e64920" + pruneopts = "" + revision = "8a410e7b638dca158bf9e766925842f6651ff828" [[projects]] branch = "master" + digest = "1:79b4fb7cfed68c4d0727858bd32dbe3be4b97ea58e2d767f92287f67810cbc98" name = "golang.org/x/sys" packages = [ "unix", - "windows" + "windows", ] - revision = "8eb05f94d449fdf134ec24630ce69ada5b469c1c" + pruneopts = "" + revision = "d99a578cf41bfccdeaf48b0845c823a4b8b0ad5e" [[projects]] + digest = "1:5a0018233f499284a0f49f649d657ebe3ca4c3db74c7a7a26ef9bf08e41df4c9" name = "periph.io/x/periph" packages = [ ".", @@ -207,14 +295,40 @@ "conn/spi", "conn/spi/spireg", "host/fs", - "host/sysfs" + "host/sysfs", ] - revision = "000523f7fa26b78f012b93221c94434ac5b18e37" - version = "v3.0.0" + pruneopts = "" + revision = "f34a1b4e75c5935f6269723ae45eacd0aa1d1a2a" + version = "v3.1.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a0e4e9de33316ccc61210696bc25b441cb91684cb0197619239635be1d0fa7f7" + input-imports = [ + "github.com/bmizerany/pat", + "github.com/codegangsta/cli", + "github.com/donovanhide/eventsource", + "github.com/eclipse/paho.mqtt.golang", + "github.com/go-ble/ble", + "github.com/go-ble/ble/darwin", + "github.com/go-ble/ble/linux", + "github.com/gobuffalo/uuid", + "github.com/hashicorp/go-multierror", + "github.com/hybridgroup/go-ardrone/client", + "github.com/hybridgroup/mjpeg", + "github.com/nats-io/nats", + "github.com/pkg/errors", + "github.com/sigurn/crc8", + "github.com/stretchr/testify/assert", + "github.com/tarm/serial", + "github.com/veandco/go-sdl2/sdl", + "go.bug.st/serial.v1", + "gocv.io/x/gocv", + "golang.org/x/net/websocket", + "periph.io/x/periph/conn", + "periph.io/x/periph/conn/physic", + "periph.io/x/periph/conn/spi", + "periph.io/x/periph/host/sysfs", + ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 46baacab..65ef0ced 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -84,3 +84,7 @@ [[constraint]] branch = "master" name = "golang.org/x/net" + +[[constraint]] + name = "github.com/stretchr/testify" + version = "1.2.2" From 0ea1646d11117b5609bbd90b777f81840865a4eb Mon Sep 17 00:00:00 2001 From: npotts Date: Thu, 30 Aug 2018 15:51:05 -0600 Subject: [PATCH 06/47] Added rudiementary support for TH02 Grove Sensor This commit adds basic support for a TH02 based Grove Sensor which is silkscreened as Temperature&Humidity Sensor (High-Accuracy & Mini ) v1.0 https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-High-Accuracy-Min-p-1921.html This is a modified variant of a SHT2* driver, and still needs some changes to be more useable. It is written to assume you want to use the High Precisions (slow sample rate) Signed-off-by: npotts --- drivers/i2c/th02_driver.go | 192 +++++++++++++++++++++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 drivers/i2c/th02_driver.go diff --git a/drivers/i2c/th02_driver.go b/drivers/i2c/th02_driver.go new file mode 100644 index 00000000..efa87389 --- /dev/null +++ b/drivers/i2c/th02_driver.go @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2018 Nicholas Potts + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package i2c + +// TH02Driver is a driver for the TH02-D based devices. +// +// This module was tested with a Grove Temperature and Humidty Sensor (High Accuracy) +// https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-High-Accuracy-Min-p-1921.html + +import ( + "fmt" + "time" + + "gobot.io/x/gobot" +) + +// TH02Address is the default address of device +const TH02Address = 0x40 + +//TH02ConfigReg is the configuration register +const TH02ConfigReg = 0x03 + +// TH02HighAccuracyTemp is the CONFIG write value to start reading temperature +const TH02HighAccuracyTemp = 0x11 + +//TH02HighAccuracyRH is the CONFIG write value to start reading high accuracy RH +const TH02HighAccuracyRH = 0x01 + +// TH02Driver is a Driver for a TH02 humidity and temperature sensor +type TH02Driver struct { + Units string + name string + connector Connector + connection Connection + Config + addr byte + accuracy byte + delay time.Duration +} + +// NewTH02Driver creates a new driver with specified i2c interface +// Params: +// conn Connector - the Adaptor to use with this Driver +// +// Optional params: +// i2c.WithBus(int): bus to use with this driver +// i2c.WithAddress(int): address to use with this driver +// +func NewTH02Driver(a Connector, options ...func(Config)) *TH02Driver { + s := &TH02Driver{ + Units: "C", + name: gobot.DefaultName("TH02"), + connector: a, + addr: TH02Address, + Config: NewConfig(), + } + // s.SetAccuracy(TH02AccuracyHigh) + + for _, option := range options { + option(s) + } + + return s +} + +// Name returns the name for this Driver +func (s *TH02Driver) Name() string { return s.name } + +// SetName sets the name for this Driver +func (s *TH02Driver) SetName(n string) { s.name = n } + +// Connection returns the connection for this Driver +func (s *TH02Driver) Connection() gobot.Connection { return s.connector.(gobot.Connection) } + +// Start initializes the TH02 +func (s *TH02Driver) Start() (err error) { + bus := s.GetBusOrDefault(s.connector.GetDefaultBus()) + address := s.GetAddressOrDefault(int(s.addr)) + + s.connection, err = s.connector.GetConnection(address, bus) + return err +} + +// Halt returns true if devices is halted successfully +func (s *TH02Driver) Halt() (err error) { return } + +// SetAddress sets the address of the device +func (s *TH02Driver) SetAddress(address int) { s.addr = byte(address) } + +// SerialNumber returns the serial number of the chip +func (s *TH02Driver) SerialNumber() (sn uint32, err error) { + ret, err := s.readRegister(0x11) + return uint32(ret) >> 4, err +} + +// Sample returns the temperature in celsius and relative humidity for one sample +func (s *TH02Driver) Sample() (temp float32, rh float32, err error) { + if err := s.writeRegister(TH02ConfigReg, TH02HighAccuracyRH); err != nil { + return 0, 0, err + } + + rrh, err := s.readData() + if err != nil { + return 0, 0, err + } + rrh = rrh >> 4 + rh = float32(rrh)/16.0 - 24.0 + + if err := s.writeRegister(TH02ConfigReg, TH02HighAccuracyTemp); err != nil { + return 0, 0, err + } + + rt, err := s.readData() + if err != nil { + return 0, rh, err + } + rt = rt / 4 + temp = float32(rt)/32.0 - 50.0 + + switch s.Units { + case "F": + temp = 9.0/5.0 + 32.0 + } + + return temp, rh, nil + +} + +// getStatusRegister returns the device status register +func (s *TH02Driver) getStatusRegister() (status byte, err error) { + return s.readRegister(TH02ConfigReg) +} + +//writeRegister writes the value to the register. +func (s *TH02Driver) writeRegister(reg, value byte) error { + _, err := s.connection.Write([]byte{reg, value}) + return err +} + +//readRegister returns the value of a single regusterm and a non-nil error on problem +func (s *TH02Driver) readRegister(reg byte) (byte, error) { + if _, err := s.connection.Write([]byte{reg}); err != nil { + return 0, err + } + rcvd := make([]byte, 1) + _, err := s.connection.Read(rcvd) + return rcvd[0], err +} + +func (s *TH02Driver) waitForReady() error { + start := time.Now() + for { + if time.Since(start) > 100*time.Millisecond { + return fmt.Errorf("timeout on \\RDY") + } + reg, _ := s.readRegister(0x00) + if reg == 0 { + return nil + } + } +} + +func (s *TH02Driver) readData() (uint16, error) { + if err := s.waitForReady(); err != nil { + return 1, err + } + + if n, err := s.connection.Write([]byte{0x01}); err != nil || n != 1 { + return 0, fmt.Errorf("n=%d not 1, or err = %v", n, err) + } + rcvd := make([]byte, 3) + n, err := s.connection.Read(rcvd) + if err != nil || n != 3 { + return 0, fmt.Errorf("n=%d not 3, or err = %v", n, err) + } + return uint16(rcvd[1])<<8 + uint16(rcvd[2]), nil + +} From ebdaca8cc08ccb1c6a1068f2f23d7cc106ad8ea0 Mon Sep 17 00:00:00 2001 From: npotts Date: Thu, 30 Aug 2018 22:14:19 -0600 Subject: [PATCH 07/47] More reworking - Added ability to add heating profile - waitForReady now takes a variable timeout value - Added ability to change the sample accuracy --- drivers/i2c/th02_driver.go | 137 ++++++++++++++++++++++++++----------- 1 file changed, 98 insertions(+), 39 deletions(-) diff --git a/drivers/i2c/th02_driver.go b/drivers/i2c/th02_driver.go index efa87389..b5c9f437 100644 --- a/drivers/i2c/th02_driver.go +++ b/drivers/i2c/th02_driver.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018 Nicholas Potts + * Copyright (c) 2016-2017 Weston Schmidt * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,8 @@ package i2c // TH02Driver is a driver for the TH02-D based devices. // -// This module was tested with a Grove Temperature and Humidty Sensor (High Accuracy) -// https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-High-Accuracy-Min-p-1921.html +// This module was tested with AdaFruit Sensiron SHT32-D Breakout. +// https://www.adafruit.com/products/2857 import ( "fmt" @@ -28,17 +28,31 @@ import ( "gobot.io/x/gobot" ) -// TH02Address is the default address of device -const TH02Address = 0x40 +const ( -//TH02ConfigReg is the configuration register -const TH02ConfigReg = 0x03 + // TH02AddressA is the default address of device + TH02Address = 0x40 -// TH02HighAccuracyTemp is the CONFIG write value to start reading temperature -const TH02HighAccuracyTemp = 0x11 + //TH02ConfigReg is the configuration register + TH02ConfigReg = 0x03 -//TH02HighAccuracyRH is the CONFIG write value to start reading high accuracy RH -const TH02HighAccuracyRH = 0x01 + // TH02RegConfigHighAccuracyTemp is the CONFIG register to read temperature + // TH02HighAccuracyTemp = 0x11 + + //TH02RegConfigHighAccuracyRH is the CONFIG register to read high accuracy RH +// TH02HighAccuracyRH = 0x01 +) + +const ( + TH02HighAccuracy = 0 //High Accuracy + TH02LowAccuracy = 1 //Lower Accuracy +) + +var ( +//ErrInvalidAccuracy = errors.New("Invalid accuracy") +//ErrInvalidCrc = errors.New("Invalid crc") +//ErrInvalidTemp = errors.New("Invalid temperature units") +) // TH02Driver is a Driver for a TH02 humidity and temperature sensor type TH02Driver struct { @@ -49,10 +63,15 @@ type TH02Driver struct { Config addr byte accuracy byte - delay time.Duration + heating bool + + delay time.Duration } -// NewTH02Driver creates a new driver with specified i2c interface +// NewTH02Driver creates a new driver with specified i2c interface. +// Defaults to: +// - Using high accuracy (lower speed) measurements cycles. +// - Emitting values in "C". If you want F, set Units to "F" // Params: // conn Connector - the Adaptor to use with this Driver // @@ -67,8 +86,10 @@ func NewTH02Driver(a Connector, options ...func(Config)) *TH02Driver { connector: a, addr: TH02Address, Config: NewConfig(), + heating: false, } - // s.SetAccuracy(TH02AccuracyHigh) + + s.SetAccuracy(1) for _, option := range options { option(s) @@ -101,50 +122,77 @@ func (s *TH02Driver) Halt() (err error) { return } // SetAddress sets the address of the device func (s *TH02Driver) SetAddress(address int) { s.addr = byte(address) } +// Accuracy returns the accuracy of the sampling +func (s *TH02Driver) Accuracy() byte { return s.accuracy } + +// SetAccuracy sets the accuracy of the sampling. It will only be used on the next +// measurment request. Invalid value will use the default of High +func (s *TH02Driver) SetAccuracy(a byte) { + if a == TH02LowAccuracy { + s.accuracy = a + } else { + s.accuracy = TH02HighAccuracy + } +} + // SerialNumber returns the serial number of the chip func (s *TH02Driver) SerialNumber() (sn uint32, err error) { ret, err := s.readRegister(0x11) return uint32(ret) >> 4, err } +// Heater returns true if the heater is enabled +func (s *TH02Driver) Heater() (status bool, err error) { + st, err := s.readRegister(0x11) + return (0x02 & st) == 0x02, err +} + +func (s *TH02Driver) applysettings(base byte) byte { + if s.accuracy == TH02LowAccuracy { + base = base & 0xd5 + } else { + base = base | 0x20 + } + if s.heating { + base = base & 0xfd + } else { + base = base | 0x02 + } + base = base | 0x01 //set the "sample" bit + return base +} + // Sample returns the temperature in celsius and relative humidity for one sample -func (s *TH02Driver) Sample() (temp float32, rh float32, err error) { - if err := s.writeRegister(TH02ConfigReg, TH02HighAccuracyRH); err != nil { +func (s *TH02Driver) Sample() (temperature float32, relhumidity float32, _ error) { + + if err := s.writeRegister(TH02ConfigReg, s.applysettings(0x10)); err != nil { return 0, 0, err } - rrh, err := s.readData() + rawrh, err := s.readData() if err != nil { return 0, 0, err } - rrh = rrh >> 4 - rh = float32(rrh)/16.0 - 24.0 + relhumidity = float32(rawrh>>4)/16.0 - 24.0 - if err := s.writeRegister(TH02ConfigReg, TH02HighAccuracyTemp); err != nil { - return 0, 0, err + if err := s.writeRegister(TH02ConfigReg, s.applysettings(0x00)); err != nil { + return 0, relhumidity, err } - - rt, err := s.readData() + rawt, err := s.readData() if err != nil { - return 0, rh, err + return 0, relhumidity, err } - rt = rt / 4 - temp = float32(rt)/32.0 - 50.0 + temperature = float32(rawt>>2)/32.0 - 50.0 switch s.Units { case "F": - temp = 9.0/5.0 + 32.0 + temperature = 9.0/5.0*temperature + 32.0 } - return temp, rh, nil + return temperature, relhumidity, nil } -// getStatusRegister returns the device status register -func (s *TH02Driver) getStatusRegister() (status byte, err error) { - return s.readRegister(TH02ConfigReg) -} - //writeRegister writes the value to the register. func (s *TH02Driver) writeRegister(reg, value byte) error { _, err := s.connection.Write([]byte{reg, value}) @@ -161,22 +209,33 @@ func (s *TH02Driver) readRegister(reg byte) (byte, error) { return rcvd[0], err } -func (s *TH02Driver) waitForReady() error { +/*waitForReady blocks for up to the passed duration (which defaults to 50mS if nil) +until the ~RDY bit is cleared, meanign a sample has been fully sampled and is ready for reading. + +This is greedy. +*/ +func (s *TH02Driver) waitForReady(dur *time.Duration) error { + wait := 100 * time.Millisecond + if dur != nil { + wait = *dur + } start := time.Now() for { - if time.Since(start) > 100*time.Millisecond { + if time.Since(start) > wait { return fmt.Errorf("timeout on \\RDY") } - reg, _ := s.readRegister(0x00) - if reg == 0 { + + //yes, i am eating the error. + if reg, _ := s.readRegister(0x00); reg == 0 { return nil } } } +/*readData fetches the data from the data 'registers'*/ func (s *TH02Driver) readData() (uint16, error) { - if err := s.waitForReady(); err != nil { - return 1, err + if err := s.waitForReady(nil); err != nil { + return 0, err } if n, err := s.connection.Write([]byte{0x01}); err != nil || n != 1 { From 1ff89c1ef4d4105921422529e222b97b456e0964 Mon Sep 17 00:00:00 2001 From: Trevor Rosen Date: Fri, 31 Aug 2018 10:23:00 -0500 Subject: [PATCH 08/47] Update dep script for AppVeyor --- appveyor.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/appveyor.yml b/appveyor.yml index 0719d63f..2c7a350a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -14,6 +14,7 @@ install: - go version - go env - go get -d ./... + - go get github.com/stretchr/testify build_script: - go test -v -cpu=2 . From fdc54b91f56b812c02ba3a26b7c50c3ed65c3883 Mon Sep 17 00:00:00 2001 From: Ulises Flynn Date: Mon, 3 Sep 2018 16:08:00 -0600 Subject: [PATCH 09/47] add 128x32 and 96x16 sizes to the i2c ssd1306 driver --- drivers/i2c/ssd1306_driver.go | 424 +++++++++++++++++------------ drivers/i2c/ssd1306_driver_test.go | 129 +++++---- examples/raspi_ssd1306.go | 9 +- 3 files changed, 344 insertions(+), 218 deletions(-) diff --git a/drivers/i2c/ssd1306_driver.go b/drivers/i2c/ssd1306_driver.go index f9cadf30..8068c854 100644 --- a/drivers/i2c/ssd1306_driver.go +++ b/drivers/i2c/ssd1306_driver.go @@ -1,128 +1,195 @@ package i2c import ( - "errors" + "fmt" "image" "gobot.io/x/gobot" ) -const ssd1306I2CAddress = 0x3c +// register addresses for the ssd1306 +const ( + // default values + ssd1306Width = 128 + ssd1306Height = 64 + ssd1306ExternalVCC = false + ssd1306SetStartLine = 0x40 + ssd1306I2CAddress = 0x3c + // fundamental commands + ssd1306SetComOutput0 = 0xC0 + ssd1306SetComOutput1 = 0xC1 + ssd1306SetComOutput2 = 0xC2 + ssd1306SetComOutput3 = 0xC3 + ssd1306SetComOutput4 = 0xC4 + ssd1306SetComOutput5 = 0xC5 + ssd1306SetComOutput6 = 0xC6 + ssd1306SetComOutput7 = 0xC7 + ssd1306SetComOutput8 = 0xC8 + ssd1306SetContrast = 0x81 + // scrolling commands + ssd1306ContinuousHScrollRight = 0x26 + ssd1306ContinuousHScrollLeft = 0x27 + ssd1306ContinuousVHScrollRight = 0x29 + ssd1306ContinuousVHScrollLeft = 0x2A + ssd1306StopScroll = 0x2E + ssd1306StartScroll = 0x2F + // adressing settings commands + ssd1306SetMemoryAddressingMode = 0x20 + ssd1306ColumnAddr = 0x21 + ssd1306PageAddr = 0x22 + // hardware configuration commands + ssd1306SetSegmentRemap0 = 0xA0 + ssd1306SetSegmentRemap127 = 0xA1 + ssd1306DisplayOnResumeToRAM = 0xA4 + ssd1306SetDisplayNormal = 0xA6 + ssd1306SetDisplayInverse = 0xA7 + ssd1306SetDisplayOff = 0xAE + ssd1306SetDisplayOn = 0xAF + // timing and driving scheme commands + ssd1306SetDisplayClock = 0xD5 + ssd1306SetPrechargePeriod = 0xD9 + ssd1306SetVComDeselectLevel = 0xDB + ssd1306SetMultiplexRatio = 0xA8 + ssd1306SetComPins = 0xDA + ssd1306SetDisplayOffset = 0xD3 + // charge pump command + ssd1306ChargePumpSetting = 0x8D +) -const ssd1306Width = 128 -const ssd1306Height = 64 - -const ssd1306PageSize = 8 - -const ssd1306SetMemoryAddressingMode = 0x20 -const ssd1306SetComOutput0 = 0xC0 -const ssd1306SetComOutput1 = 0xC1 -const ssd1306SetComOutput2 = 0xC2 -const ssd1306SetComOutput3 = 0xC3 -const ssd1306SetComOutput4 = 0xC4 -const ssd1306SetComOutput5 = 0xC5 -const ssd1306SetComOutput6 = 0xC6 -const ssd1306SetComOutput7 = 0xC7 -const ssd1306SetComOutput8 = 0xC8 -const ssd1306ColumnAddr = 0x21 -const ssd1306PageAddr = 0x22 -const ssd1306SetContrast = 0x81 -const ssd1306SetSegmentRemap0 = 0xA0 -const ssd1306SetSegmentRemap127 = 0xA1 -const ssd1306DisplayOnResumeToRAM = 0xA4 -const ssd1306SetDisplayNormal = 0xA6 -const ssd1306SetDisplayInverse = 0xA7 -const ssd1306SetDisplayOff = 0xAE -const ssd1306SetDisplayOn = 0xAF -const ssd1306ContinuousHScrollRight = 0x26 -const ssd1306ContinuousHScrollLeft = 0x27 -const ssd1306ContinuousVHScrollRight = 0x29 -const ssd1306ContinuousVHScrollLeft = 0x2A -const ssd1306StopScroll = 0x2E -const ssd1306StartScroll = 0x2F -const ssd1306SetStartLine = 0x40 -const ssd1306ChargePumpSetting = 0x8D -const ssd1306SetDisplayClock = 0xD5 -const ssd1306SetMultiplexRatio = 0xA8 -const ssd1306SetComPins = 0xDA -const ssd1306SetDisplayOffset = 0xD3 -const ssd1306SetPrechargePeriod = 0xD9 -const ssd1306SetVComDeselectLevel = 0xDB - -var ssd1306InitSequence []byte = []byte{ - ssd1306SetDisplayNormal, - ssd1306SetDisplayOff, - ssd1306SetDisplayClock, 0x80, // the suggested ratio 0x80 - ssd1306SetMultiplexRatio, 0x3F, - ssd1306SetDisplayOffset, 0x0, //no offset - ssd1306SetStartLine | 0x0, //SETSTARTLINE - ssd1306ChargePumpSetting, 0x14, - ssd1306SetMemoryAddressingMode, 0x00, //0x0 act like ks0108 - ssd1306SetSegmentRemap0, - ssd1306SetComOutput0, - ssd1306SetComPins, 0x12, //COMSCANDEC - ssd1306SetContrast, 0xCF, - ssd1306SetPrechargePeriod, 0xF1, - ssd1306SetVComDeselectLevel, 0x40, - ssd1306DisplayOnResumeToRAM, - ssd1306SetDisplayNormal, - ssd1306StopScroll, - ssd1306SetSegmentRemap0, - ssd1306SetSegmentRemap127, - ssd1306SetComOutput8, - ssd1306SetMemoryAddressingMode, 0x00, - ssd1306SetContrast, 0xff, +// SSD1306Init contains the initialization settings for the ssd1306 display. +type SSD1306Init struct { + displayClock byte + multiplexRatio byte + displayOffset byte + startLine byte + chargePumpSetting byte + memoryAddressingMode byte + comPins byte + contrast byte + prechargePeriod byte + vComDeselectLevel byte } -// DisplayBuffer represents the display buffer intermediate memory +// GetSequence returns the initialization sequence for the ssd1306 display. +func (i *SSD1306Init) GetSequence() []byte { + return []byte{ + ssd1306SetDisplayNormal, + ssd1306SetDisplayOff, + ssd1306SetDisplayClock, i.displayClock, + ssd1306SetMultiplexRatio, i.multiplexRatio, + ssd1306SetDisplayOffset, i.displayOffset, + ssd1306SetStartLine | i.startLine, + ssd1306ChargePumpSetting, i.chargePumpSetting, + ssd1306SetMemoryAddressingMode, i.memoryAddressingMode, + ssd1306SetSegmentRemap0, + ssd1306SetComOutput0, + ssd1306SetComPins, i.comPins, + ssd1306SetContrast, i.contrast, + ssd1306SetPrechargePeriod, i.prechargePeriod, + ssd1306SetVComDeselectLevel, i.vComDeselectLevel, + ssd1306DisplayOnResumeToRAM, + ssd1306SetDisplayNormal, + } +} + +// 128x64 init sequence +var ssd1306Init128x64 = &SSD1306Init{ + displayClock: 0x80, + multiplexRatio: 0x3F, + displayOffset: 0x00, + startLine: 0x00, + chargePumpSetting: 0x14, // 0x10 if external vcc is set + memoryAddressingMode: 0x00, + comPins: 0x12, + contrast: 0xCF, // 0x9F if external vcc is set + prechargePeriod: 0xF1, // 0x22 if external vcc is set + vComDeselectLevel: 0x40, +} + +// 128x32 init sequence +var ssd1306Init128x32 = &SSD1306Init{ + displayClock: 0x80, + multiplexRatio: 0x1F, + displayOffset: 0x00, + startLine: 0x00, + chargePumpSetting: 0x14, // 0x10 if external vcc is set + memoryAddressingMode: 0x00, + comPins: 0x02, + contrast: 0x8F, // 0x9F if external vcc is set + prechargePeriod: 0xF1, // 0x22 if external vcc is set + vComDeselectLevel: 0x40, +} + +// 96x16 init sequence +var ssd1306Init96x16 = &SSD1306Init{ + displayClock: 0x60, + multiplexRatio: 0x0F, + displayOffset: 0x00, + startLine: 0x00, + chargePumpSetting: 0x14, // 0x10 if external vcc is set + memoryAddressingMode: 0x00, + comPins: 0x02, + contrast: 0x8F, // 0x9F if external vcc is set + prechargePeriod: 0xF1, // 0x22 if external vcc is set + vComDeselectLevel: 0x40, +} + +// DisplayBuffer represents the display buffer intermediate memory. type DisplayBuffer struct { - Width, Height int - buffer []byte + width, height, pageSize int + buffer []byte } -// NewDisplayBuffer creates a new DisplayBuffer -func NewDisplayBuffer(Width, Height int) *DisplayBuffer { - s := &DisplayBuffer{ - Width: Width, - Height: Height, +// NewDisplayBuffer creates a new DisplayBuffer. +func NewDisplayBuffer(width, height, pageSize int) *DisplayBuffer { + d := &DisplayBuffer{ + width: width, + height: height, + pageSize: pageSize, } - s.buffer = make([]byte, s.Size()) - return s + d.buffer = make([]byte, d.Size()) + return d } -// Size returns the memory size of the display buffer -func (s *DisplayBuffer) Size() int { - return (s.Width * s.Height) / ssd1306PageSize +// Size returns the memory size of the display buffer. +func (d *DisplayBuffer) Size() int { + return (d.width * d.height) / d.pageSize } -// Clear the contents of the display buffer -func (s *DisplayBuffer) Clear() { - s.buffer = make([]byte, s.Size()) +// Clear the contents of the display buffer. +func (d *DisplayBuffer) Clear() { + d.buffer = make([]byte, d.Size()) } -// Set sets the x, y pixel with c color -func (s *DisplayBuffer) Set(x, y, c int) { - idx := x + (y/ssd1306PageSize)*s.Width - bit := uint(y) % ssd1306PageSize - +// SetPixel sets the x, y pixel with c color. +func (d *DisplayBuffer) SetPixel(x, y, c int) { + idx := x + (y/d.pageSize)*d.width + bit := uint(y) % uint(d.pageSize) if c == 0 { - s.buffer[idx] &= ^(1 << bit) + d.buffer[idx] &= ^(1 << bit) } else { - s.buffer[idx] |= (1 << bit) + d.buffer[idx] |= (1 << bit) } } -// SSD1306Driver is a Gobot Driver for a SSD1306 Display +// Set sets the display buffer with the given buffer. +func (d *DisplayBuffer) Set(buf []byte) { + d.buffer = buf +} + +// SSD1306Driver is a Gobot Driver for a SSD1306 Display. type SSD1306Driver struct { name string connector Connector connection Connection Config gobot.Commander - - DisplayWidth int - DisplayHeight int - Buffer *DisplayBuffer + initSequence *SSD1306Init + displayWidth int + displayHeight int + externalVCC bool + pageSize int + buffer *DisplayBuffer } // NewSSD1306Driver creates a new SSD1306Driver. @@ -133,8 +200,9 @@ type SSD1306Driver struct { // Optional params: // WithBus(int): bus to use with this driver // WithAddress(int): address to use with this driver -// WithDisplayWidth(int): width of display (defaults to 128) -// WithDisplayHeight(int): height of display (defaults to 64) +// WithSSD1306DisplayWidth(int): width of display (defaults to 128) +// WithSSD1306DisplayHeight(int): height of display (defaults to 64) +// WithSSD1306ExternalVCC: set true when using an external OLED supply (defaults to false) // func NewSSD1306Driver(a Connector, options ...func(Config)) *SSD1306Driver { s := &SSD1306Driver{ @@ -142,172 +210,194 @@ func NewSSD1306Driver(a Connector, options ...func(Config)) *SSD1306Driver { Commander: gobot.NewCommander(), connector: a, Config: NewConfig(), - DisplayHeight: ssd1306Height, - DisplayWidth: ssd1306Width, + displayHeight: ssd1306Height, + displayWidth: ssd1306Width, + externalVCC: ssd1306ExternalVCC, } - + // set options for _, option := range options { option(s) } - - s.Buffer = NewDisplayBuffer(s.DisplayWidth, s.DisplayHeight) - + // set page size + s.pageSize = 8 + // set display buffer + s.buffer = NewDisplayBuffer(s.displayWidth, s.displayHeight, s.pageSize) + // add commands s.AddCommand("Display", func(params map[string]interface{}) interface{} { err := s.Display() return map[string]interface{}{"err": err} }) - s.AddCommand("On", func(params map[string]interface{}) interface{} { err := s.On() return map[string]interface{}{"err": err} }) - s.AddCommand("Off", func(params map[string]interface{}) interface{} { err := s.Off() return map[string]interface{}{"err": err} }) - s.AddCommand("Clear", func(params map[string]interface{}) interface{} { - err := s.Clear() - return map[string]interface{}{"err": err} + s.Clear() + return map[string]interface{}{} }) - s.AddCommand("SetContrast", func(params map[string]interface{}) interface{} { contrast := byte(params["contrast"].(byte)) err := s.SetContrast(contrast) return map[string]interface{}{"err": err} }) - s.AddCommand("Set", func(params map[string]interface{}) interface{} { x := int(params["x"].(int)) y := int(params["y"].(int)) c := int(params["c"].(int)) - s.Set(x, y, c) return nil }) - return s } -// Name returns the Name for the Driver +// Name returns the Name for the Driver. func (s *SSD1306Driver) Name() string { return s.name } -// SetName sets the Name for the Driver +// SetName sets the Name for the Driver. func (s *SSD1306Driver) SetName(n string) { s.name = n } -// Connection returns the connection for the Driver +// Connection returns the connection for the Driver. func (s *SSD1306Driver) Connection() gobot.Connection { return s.connector.(gobot.Connection) } // Start starts the Driver up, and writes start command func (s *SSD1306Driver) Start() (err error) { + // check device size for supported resolutions + switch { + case s.displayWidth == 128 && s.displayHeight == 64: + s.initSequence = ssd1306Init128x64 + case s.displayWidth == 128 && s.displayHeight == 32: + s.initSequence = ssd1306Init128x32 + case s.displayWidth == 96 && s.displayHeight == 16: + s.initSequence = ssd1306Init96x16 + default: + return fmt.Errorf("%dx%d resolution is unsupported, supported resolutions: 128x64, 128x32, 96x16", s.displayWidth, s.displayHeight) + } + // check for external vcc + if s.externalVCC { + s.initSequence.chargePumpSetting = 0x10 + s.initSequence.contrast = 0x9F + s.initSequence.prechargePeriod = 0x22 + } bus := s.GetBusOrDefault(s.connector.GetDefaultBus()) address := s.GetAddressOrDefault(ssd1306I2CAddress) - s.connection, err = s.connector.GetConnection(address, bus) if err != nil { - return + return err } - - s.Init() - s.On() - - return + if err = s.Init(); err != nil { + return err + } + if err = s.On(); err != nil { + return err + } + return nil } // Halt returns true if device is halted successfully func (s *SSD1306Driver) Halt() (err error) { return nil } -// WithDisplayWidth option sets the SSD1306Driver DisplayWidth option. -func WithDisplayWidth(val int) func(Config) { +// WithSSD1306DisplayWidth option sets the SSD1306Driver DisplayWidth option. +func WithSSD1306DisplayWidth(val int) func(Config) { return func(c Config) { d, ok := c.(*SSD1306Driver) if ok { - d.DisplayWidth = val - } else { - // TODO: return error for trying to set DisplayWidth for non-SSD1306Driver - return + d.displayWidth = val } } } -// WithDisplayHeight option sets the SSD1306Driver DisplayHeight option. -func WithDisplayHeight(val int) func(Config) { +// WithSSD1306DisplayHeight option sets the SSD1306Driver DisplayHeight option. +func WithSSD1306DisplayHeight(val int) func(Config) { return func(c Config) { d, ok := c.(*SSD1306Driver) if ok { - d.DisplayHeight = val - } else { - // TODO: return error for trying to set DisplayHeight for non-SSD1306Driver - return + d.displayHeight = val } } } -// Init turns display on +// WithSSD1306ExternalVCC option sets the SSD1306Driver ExternalVCC option. +func WithSSD1306ExternalVCC(val bool) func(Config) { + return func(c Config) { + d, ok := c.(*SSD1306Driver) + if ok { + d.externalVCC = val + } + } +} + +// Init initializes the ssd1306 display. func (s *SSD1306Driver) Init() (err error) { - s.Off() - s.commands(ssd1306InitSequence) - - s.commands([]byte{ssd1306ColumnAddr, 0, // Start at 0, - byte(s.Buffer.Width) - 1, // End at last column (127?) - }) - - s.commands([]byte{ssd1306PageAddr, 0, // Start at 0, - (byte(s.Buffer.Height) / ssd1306PageSize) - 1, // End at page 7 - }) - + // turn off screen + if err = s.Off(); err != nil { + return err + } + // run through initialization commands + if err = s.commands(s.initSequence.GetSequence()); err != nil { + return err + } + if err = s.commands([]byte{ssd1306ColumnAddr, 0, byte(s.buffer.width) - 1}); err != nil { + return err + } + if err = s.commands([]byte{ssd1306PageAddr, 0, (byte(s.buffer.height / s.pageSize)) - 1}); err != nil { + return err + } return nil } -// On turns display on +// On turns on the display. func (s *SSD1306Driver) On() (err error) { return s.command(ssd1306SetDisplayOn) } -// Off turns display off +// Off turns off the display. func (s *SSD1306Driver) Off() (err error) { return s.command(ssd1306SetDisplayOff) } -// Clear clears -func (s *SSD1306Driver) Clear() (err error) { - s.Buffer.Clear() - return nil +// Clear clears the display buffer. +func (s *SSD1306Driver) Clear() { + s.buffer.Clear() } -// Set sets a pixel +// Set sets a pixel in the buffer. func (s *SSD1306Driver) Set(x, y, c int) { - s.Buffer.Set(x, y, c) + s.buffer.SetPixel(x, y, c) } -// Reset sends the memory buffer to the display +// Reset clears display. func (s *SSD1306Driver) Reset() (err error) { - s.Off() + if err = s.Off(); err != nil { + return err + } s.Clear() - s.On() + if err = s.On(); err != nil { + return err + } return nil } -// SetContrast sets the display contrast +// SetContrast sets the display contrast. func (s *SSD1306Driver) SetContrast(contrast byte) (err error) { err = s.commands([]byte{ssd1306SetContrast, contrast}) return } -// Display sends the memory buffer to the display +// Display sends the memory buffer to the display. func (s *SSD1306Driver) Display() (err error) { - // Write the buffer - _, err = s.connection.Write(append([]byte{0x40}, s.Buffer.buffer...)) + _, err = s.connection.Write(append([]byte{0x40}, s.buffer.buffer...)) return err } -// ShowImage takes a standard Go image and shows it on the display in monochrome. +// ShowImage takes a standard Go image and displays it in monochrome. func (s *SSD1306Driver) ShowImage(img image.Image) (err error) { - if img.Bounds().Dx() != s.DisplayWidth || img.Bounds().Dy() != s.DisplayHeight { - return errors.New("Image must match the display width and height") + if img.Bounds().Dx() != s.displayWidth || img.Bounds().Dy() != s.displayHeight { + return fmt.Errorf("image must match display width and height: %dx%d", s.displayWidth, s.displayHeight) } - s.Clear() for y, w, h := 0, img.Bounds().Dx(), img.Bounds().Dy(); y < h; y++ { for x := 0; x < w; x++ { @@ -320,18 +410,18 @@ func (s *SSD1306Driver) ShowImage(img image.Image) (err error) { return s.Display() } -// command sends a unique command +// command sends a command to the ssd1306 func (s *SSD1306Driver) command(b byte) (err error) { _, err = s.connection.Write([]byte{0x80, b}) - return + return err } -// commands sends a command sequence +// commands sends a command sequence to the ssd1306 func (s *SSD1306Driver) commands(commands []byte) (err error) { var command []byte for _, d := range commands { command = append(command, []byte{0x80, d}...) } _, err = s.connection.Write(command) - return + return err } diff --git a/drivers/i2c/ssd1306_driver_test.go b/drivers/i2c/ssd1306_driver_test.go index d68fbba5..90d84a6a 100644 --- a/drivers/i2c/ssd1306_driver_test.go +++ b/drivers/i2c/ssd1306_driver_test.go @@ -15,46 +15,44 @@ import ( var _ gobot.Driver = (*SSD1306Driver)(nil) func TestDisplayBuffer(t *testing.T) { - width := 128 height := 64 size := 1024 // (width*height) / 8 - - display := NewDisplayBuffer(width, height) + display := NewDisplayBuffer(width, height, 8) if display.Size() != size { t.Errorf("invalid Size() (%d, expected %d)", display.Size(), size) } if len(display.buffer) != size { - t.Errorf("Allocated buffer size invalid (%d, expected %d)", + t.Errorf("allocated buffer size invalid (%d, expected %d)", len(display.buffer), size) } gobottest.Assert(t, display.buffer[0], byte(0)) gobottest.Assert(t, display.buffer[1], byte(0)) - display.Set(0, 0, 1) - display.Set(1, 0, 1) - display.Set(2, 0, 1) - display.Set(0, 1, 1) + display.SetPixel(0, 0, 1) + display.SetPixel(1, 0, 1) + display.SetPixel(2, 0, 1) + display.SetPixel(0, 1, 1) gobottest.Assert(t, display.buffer[0], byte(3)) gobottest.Assert(t, display.buffer[1], byte(1)) - display.Set(0, 1, 0) + display.SetPixel(0, 1, 0) gobottest.Assert(t, display.buffer[0], byte(1)) gobottest.Assert(t, display.buffer[1], byte(1)) } // --------- HELPERS -func initTestSSD1306Driver() (driver *SSD1306Driver) { - driver, _ = initTestSSD1306DriverWithStubbedAdaptor() +func initTestSSD1306Driver(width, height int, externalVCC bool) (driver *SSD1306Driver) { + driver, _ = initTestSSD1306DriverWithStubbedAdaptor(width, height, externalVCC) return } -func initTestSSD1306DriverWithStubbedAdaptor() (*SSD1306Driver, *i2cTestAdaptor) { +func initTestSSD1306DriverWithStubbedAdaptor(width, height int, externalVCC bool) (*SSD1306Driver, *i2cTestAdaptor) { adaptor := newI2cTestAdaptor() - return NewSSD1306Driver(adaptor), adaptor + return NewSSD1306Driver(adaptor, WithSSD1306DisplayWidth(width), WithSSD1306DisplayHeight(height), WithSSD1306ExternalVCC(externalVCC)), adaptor } // --------- TESTS @@ -64,7 +62,7 @@ func TestNewSSD1306Driver(t *testing.T) { var bm interface{} = NewSSD1306Driver(newI2cTestAdaptor()) _, ok := bm.(*SSD1306Driver) if !ok { - t.Errorf("NewSSD1306Driver() should have returned a *SSD1306Driver") + t.Errorf("new should have returned a *SSD1306Driver") } b := NewSSD1306Driver(newI2cTestAdaptor()) @@ -73,27 +71,47 @@ func TestNewSSD1306Driver(t *testing.T) { // Methods -func TestSSD1306DriverStart(t *testing.T) { - s, _ := initTestSSD1306DriverWithStubbedAdaptor() +func TestSSD1306DriverStartDefaul(t *testing.T) { + s, _ := initTestSSD1306DriverWithStubbedAdaptor(128, 64, false) + gobottest.Assert(t, s.Start(), nil) +} +func TestSSD1306DriverStart128x32(t *testing.T) { + s, _ := initTestSSD1306DriverWithStubbedAdaptor(128, 32, false) + gobottest.Assert(t, s.Start(), nil) +} + +func TestSSD1306DriverStart96x16(t *testing.T) { + s, _ := initTestSSD1306DriverWithStubbedAdaptor(96, 16, false) + gobottest.Assert(t, s.Start(), nil) +} + +func TestSSD1306DriverStartExternalVCC(t *testing.T) { + s, _ := initTestSSD1306DriverWithStubbedAdaptor(128, 32, true) gobottest.Assert(t, s.Start(), nil) } func TestSSD1306StartConnectError(t *testing.T) { - d, adaptor := initTestSSD1306DriverWithStubbedAdaptor() + d, adaptor := initTestSSD1306DriverWithStubbedAdaptor(128, 64, false) adaptor.Testi2cConnectErr(true) gobottest.Assert(t, d.Start(), errors.New("Invalid i2c connection")) } +func TestSSD1306StartSizeError(t *testing.T) { + d, _ := initTestSSD1306DriverWithStubbedAdaptor(128, 54, false) + //adaptor.Testi2cConnectErr(false) + gobottest.Assert(t, d.Start(), errors.New("128x54 resolution is unsupported, supported resolutions: 128x64, 128x32, 96x16")) +} + func TestSSD1306DriverHalt(t *testing.T) { - s := initTestSSD1306Driver() + s := initTestSSD1306Driver(128, 64, false) gobottest.Assert(t, s.Halt(), nil) } // Test Name & SetName func TestSSD1306DriverName(t *testing.T) { - s := initTestSSD1306Driver() + s := initTestSSD1306Driver(96, 16, false) gobottest.Assert(t, strings.HasPrefix(s.Name(), "SSD1306"), true) s.SetName("Ole Oled") @@ -101,37 +119,37 @@ func TestSSD1306DriverName(t *testing.T) { } func TestSSD1306DriverOptions(t *testing.T) { - s := NewSSD1306Driver(newI2cTestAdaptor(), WithBus(2), WithDisplayHeight(32), WithDisplayWidth(128)) + s := NewSSD1306Driver(newI2cTestAdaptor(), WithBus(2), WithSSD1306DisplayHeight(32), WithSSD1306DisplayWidth(128)) gobottest.Assert(t, s.GetBusOrDefault(1), 2) - gobottest.Assert(t, s.DisplayHeight, 32) - gobottest.Assert(t, s.DisplayWidth, 128) + gobottest.Assert(t, s.displayHeight, 32) + gobottest.Assert(t, s.displayWidth, 128) } func TestSSD1306DriverDisplay(t *testing.T) { - s, _ := initTestSSD1306DriverWithStubbedAdaptor() + s, _ := initTestSSD1306DriverWithStubbedAdaptor(96, 16, false) s.Start() gobottest.Assert(t, s.Display(), nil) } func TestSSD1306DriverShowImage(t *testing.T) { - s, _ := initTestSSD1306DriverWithStubbedAdaptor() + s, _ := initTestSSD1306DriverWithStubbedAdaptor(128, 64, false) s.Start() img := image.NewRGBA(image.Rect(0, 0, 640, 480)) - gobottest.Assert(t, s.ShowImage(img), errors.New("Image must match the display width and height")) + gobottest.Assert(t, s.ShowImage(img), errors.New("image must match display width and height: 128x64")) img = image.NewRGBA(image.Rect(0, 0, 128, 64)) gobottest.Assert(t, s.ShowImage(img), nil) } func TestSSD1306DriverCommand(t *testing.T) { - s, adaptor := initTestSSD1306DriverWithStubbedAdaptor() + s, adaptor := initTestSSD1306DriverWithStubbedAdaptor(128, 64, false) s.Start() adaptor.i2cWriteImpl = func(got []byte) (int, error) { expected := []byte{0x80, 0xFF} if !reflect.DeepEqual(got, expected) { - t.Logf("Sequence error, got %+v, expected %+v", got, expected) - return 0, fmt.Errorf("Woops!") + t.Logf("sequence error, got %+v, expected %+v", got, expected) + return 0, fmt.Errorf("oops") } return 0, nil } @@ -140,14 +158,14 @@ func TestSSD1306DriverCommand(t *testing.T) { } func TestSSD1306DriverCommands(t *testing.T) { - s, adaptor := initTestSSD1306DriverWithStubbedAdaptor() + s, adaptor := initTestSSD1306DriverWithStubbedAdaptor(128, 64, false) s.Start() adaptor.i2cWriteImpl = func(got []byte) (int, error) { expected := []byte{0x80, 0x00, 0x80, 0xFF} if !reflect.DeepEqual(got, expected) { - t.Logf("Sequence error, got %+v, expected %+v", got, expected) - return 0, fmt.Errorf("Woops!") + t.Logf("sequence error, got %+v, expected %+v", got, expected) + return 0, fmt.Errorf("oops") } return 0, nil } @@ -156,14 +174,14 @@ func TestSSD1306DriverCommands(t *testing.T) { } func TestSSD1306DriverOn(t *testing.T) { - s, adaptor := initTestSSD1306DriverWithStubbedAdaptor() + s, adaptor := initTestSSD1306DriverWithStubbedAdaptor(128, 64, false) s.Start() adaptor.i2cWriteImpl = func(got []byte) (int, error) { expected := []byte{0x80, ssd1306SetDisplayOn} if !reflect.DeepEqual(got, expected) { - t.Logf("Sequence error, got %+v, expected %+v", got, expected) - return 0, fmt.Errorf("Woops!") + t.Logf("sequence error, got %+v, expected %+v", got, expected) + return 0, fmt.Errorf("oops") } return 0, nil } @@ -172,14 +190,14 @@ func TestSSD1306DriverOn(t *testing.T) { } func TestSSD1306DriverOff(t *testing.T) { - s, adaptor := initTestSSD1306DriverWithStubbedAdaptor() + s, adaptor := initTestSSD1306DriverWithStubbedAdaptor(128, 64, false) s.Start() adaptor.i2cWriteImpl = func(got []byte) (int, error) { expected := []byte{0x80, ssd1306SetDisplayOff} if !reflect.DeepEqual(got, expected) { - t.Logf("Sequence error, got %+v, expected %+v", got, expected) - return 0, fmt.Errorf("Woops!") + t.Logf("sequence error, got %+v, expected %+v", got, expected) + return 0, fmt.Errorf("oops") } return 0, nil } @@ -187,10 +205,27 @@ func TestSSD1306DriverOff(t *testing.T) { gobottest.Assert(t, err, nil) } +func TestSSD1306Reset(t *testing.T) { + s, adaptor := initTestSSD1306DriverWithStubbedAdaptor(128, 64, false) + s.Start() + + adaptor.i2cWriteImpl = func(got []byte) (int, error) { + expectedOff := []byte{0x80, ssd1306SetDisplayOff} + expectedOn := []byte{0x80, ssd1306SetDisplayOn} + if !reflect.DeepEqual(got, expectedOff) && !reflect.DeepEqual(got, expectedOn) { + t.Logf("sequence error, got %+v, expected: %+v or %+v", got, expectedOff, expectedOn) + return 0, fmt.Errorf("oops") + } + return 0, nil + } + err := s.Reset() + gobottest.Assert(t, err, nil) +} + // COMMANDS func TestSSD1306DriverCommandsDisplay(t *testing.T) { - s := initTestSSD1306Driver() + s := initTestSSD1306Driver(128, 64, false) s.Start() result := s.Command("Display")(map[string]interface{}{}) @@ -198,7 +233,7 @@ func TestSSD1306DriverCommandsDisplay(t *testing.T) { } func TestSSD1306DriverCommandsOn(t *testing.T) { - s := initTestSSD1306Driver() + s := initTestSSD1306Driver(128, 64, false) s.Start() result := s.Command("On")(map[string]interface{}{}) @@ -206,7 +241,7 @@ func TestSSD1306DriverCommandsOn(t *testing.T) { } func TestSSD1306DriverCommandsOff(t *testing.T) { - s := initTestSSD1306Driver() + s := initTestSSD1306Driver(128, 64, false) s.Start() result := s.Command("Off")(map[string]interface{}{}) @@ -214,7 +249,7 @@ func TestSSD1306DriverCommandsOff(t *testing.T) { } func TestSSD1306DriverCommandsClear(t *testing.T) { - s := initTestSSD1306Driver() + s := initTestSSD1306Driver(128, 64, false) s.Start() result := s.Command("Clear")(map[string]interface{}{}) @@ -222,14 +257,14 @@ func TestSSD1306DriverCommandsClear(t *testing.T) { } func TestSSD1306DriverCommandsSetContrast(t *testing.T) { - s, adaptor := initTestSSD1306DriverWithStubbedAdaptor() + s, adaptor := initTestSSD1306DriverWithStubbedAdaptor(128, 64, false) s.Start() adaptor.i2cWriteImpl = func(got []byte) (int, error) { expected := []byte{0x80, ssd1306SetContrast, 0x80, 0x10} if !reflect.DeepEqual(got, expected) { - t.Logf("Sequence error, got %+v, expected %+v", got, expected) - return 0, fmt.Errorf("Woops!") + t.Logf("sequence error, got %+v, expected %+v", got, expected) + return 0, fmt.Errorf("oops") } return 0, nil } @@ -241,14 +276,14 @@ func TestSSD1306DriverCommandsSetContrast(t *testing.T) { } func TestSSD1306DriverCommandsSet(t *testing.T) { - s := initTestSSD1306Driver() + s := initTestSSD1306Driver(128, 64, false) s.Start() - gobottest.Assert(t, s.Buffer.buffer[0], byte(0)) + gobottest.Assert(t, s.buffer.buffer[0], byte(0)) s.Command("Set")(map[string]interface{}{ "x": int(0), "y": int(0), "c": int(1), }) - gobottest.Assert(t, s.Buffer.buffer[0], byte(1)) + gobottest.Assert(t, s.buffer.buffer[0], byte(1)) } diff --git a/examples/raspi_ssd1306.go b/examples/raspi_ssd1306.go index da6c8ef2..0d030f29 100644 --- a/examples/raspi_ssd1306.go +++ b/examples/raspi_ssd1306.go @@ -13,9 +13,10 @@ import ( ) func main() { - + width := 128 + height := 32 r := raspi.NewAdaptor() - oled := i2c.NewSSD1306Driver(r) + oled := i2c.NewSSD1306Driver(r, i2c.WithSSD1306DisplayWidth(width), i2c.WithSSD1306DisplayHeight(height)) stage := false @@ -24,8 +25,8 @@ func main() { gobot.Every(1*time.Second, func() { oled.Clear() if stage { - for x := 0; x < oled.Buffer.Width; x += 5 { - for y := 0; y < oled.Buffer.Height; y++ { + for x := 0; x < width; x += 5 { + for y := 0; y < height; y++ { oled.Set(x, y, 1) } } From 870455ade18ede8ff5c22ebb838beea21decb2d5 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Wed, 5 Sep 2018 20:13:14 +0200 Subject: [PATCH 10/47] up2: correct i2c default bus information to match correct values Signed-off-by: Ron Evans --- examples/up2_lcd.go | 49 +++++++++++++++++++++++++++ platforms/upboard/up2/adaptor.go | 8 ++--- platforms/upboard/up2/adaptor_test.go | 6 ++-- 3 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 examples/up2_lcd.go diff --git a/examples/up2_lcd.go b/examples/up2_lcd.go new file mode 100644 index 00000000..79e5d0ab --- /dev/null +++ b/examples/up2_lcd.go @@ -0,0 +1,49 @@ +// +build example +// +// Do not build by default. + +package main + +import ( + "time" + + "gobot.io/x/gobot" + "gobot.io/x/gobot/drivers/i2c" + "gobot.io/x/gobot/platforms/upboard/up2" +) + +func main() { + board := up2.NewAdaptor() + screen := i2c.NewGroveLcdDriver(board) + + work := func() { + screen.Write("hello") + + screen.SetRGB(255, 0, 0) + + gobot.After(5*time.Second, func() { + screen.Clear() + screen.Home() + screen.SetRGB(0, 255, 0) + // set a custom character in the first position + screen.SetCustomChar(0, i2c.CustomLCDChars["smiley"]) + // add the custom character at the end of the string + screen.Write("goodbye\nhave a nice day " + string(byte(0))) + gobot.Every(500*time.Millisecond, func() { + screen.Scroll(false) + }) + }) + + screen.Home() + time.Sleep(1 * time.Second) + screen.SetRGB(0, 0, 255) + } + + robot := gobot.NewRobot("screenBot", + []gobot.Connection{board}, + []gobot.Device{screen}, + work, + ) + + robot.Start() +} diff --git a/platforms/upboard/up2/adaptor.go b/platforms/upboard/up2/adaptor.go index ad65215c..111b0163 100644 --- a/platforms/upboard/up2/adaptor.go +++ b/platforms/upboard/up2/adaptor.go @@ -23,7 +23,7 @@ type Adaptor struct { pinmap map[string]sysfsPin digitalPins map[int]*sysfs.DigitalPin pwmPins map[int]*sysfs.PWMPin - i2cBuses [2]i2c.I2cDevice + i2cBuses [6]i2c.I2cDevice mutex *sync.Mutex spiDefaultBus int spiDefaultChip int @@ -209,12 +209,12 @@ func (c *Adaptor) PWMPin(pin string) (sysfsPin sysfs.PWMPinner, err error) { } // GetConnection returns a connection to a device on a specified bus. -// Valid bus number is [0..1] which corresponds to /dev/i2c-0 through /dev/i2c-1. +// Valid bus number is [5..6] which corresponds to /dev/i2c-5 through /dev/i2c-6. func (c *Adaptor) GetConnection(address int, bus int) (connection i2c.Connection, err error) { c.mutex.Lock() defer c.mutex.Unlock() - if (bus < 0) || (bus > 1) { + if (bus < 5) || (bus > 6) { return nil, fmt.Errorf("Bus number %d out of range", bus) } if c.i2cBuses[bus] == nil { @@ -225,7 +225,7 @@ func (c *Adaptor) GetConnection(address int, bus int) (connection i2c.Connection // GetDefaultBus returns the default i2c bus for this platform func (c *Adaptor) GetDefaultBus() int { - return 0 + return 5 } // GetSpiConnection returns an spi connection to a device on a specified bus. diff --git a/platforms/upboard/up2/adaptor_test.go b/platforms/upboard/up2/adaptor_test.go index 87e79274..7d23e37c 100644 --- a/platforms/upboard/up2/adaptor_test.go +++ b/platforms/upboard/up2/adaptor_test.go @@ -86,12 +86,12 @@ func TestAdaptorDigitalReadWriteError(t *testing.T) { func TestUP2AdaptorI2c(t *testing.T) { a := NewAdaptor() fs := sysfs.NewMockFilesystem([]string{ - "/dev/i2c-0", + "/dev/i2c-5", }) sysfs.SetFilesystem(fs) sysfs.SetSyscall(&sysfs.MockSyscall{}) - con, err := a.GetConnection(0xff, 0) + con, err := a.GetConnection(0xff, 5) gobottest.Assert(t, err, nil) con.Write([]byte{0x00, 0x01}) @@ -175,7 +175,7 @@ func TestUP2AdaptorPwmReadError(t *testing.T) { func TestUP2I2CDefaultBus(t *testing.T) { a, _ := initTestUP2Adaptor() - gobottest.Assert(t, a.GetDefaultBus(), 0) + gobottest.Assert(t, a.GetDefaultBus(), 5) } func TestUP2GetConnectionInvalidBus(t *testing.T) { From 748a736462c63d8a265a4ae21ea733413ccdcdac Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Thu, 6 Sep 2018 16:29:47 +0200 Subject: [PATCH 11/47] Update UP2 docs for latest information Signed-off-by: Ron Evans --- platforms/upboard/up2/README.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/platforms/upboard/up2/README.md b/platforms/upboard/up2/README.md index 9aa956fa..0a71d57d 100644 --- a/platforms/upboard/up2/README.md +++ b/platforms/upboard/up2/README.md @@ -6,9 +6,24 @@ For more info about the UP2 Board, go to [http://www.up-board.org/upsquared/](ht ## How to Install -We recommend updating to the latest Ubuntu when using the UP2. +### Setting up your UP2 board -You would normally install Go and Gobot on your workstation. Once installed, cross compile your program on your workstation, transfer the final executable to your UP2, and run the program on the UP2 as documented here. +We recommend updating to the latest Ubuntu and firmware when using the UP2 board. For more information go to: + +URL + +Once your UP@ has been updated, you will need to provide permission to the `upsquared` user to access the I2C subsystem on the board. To do this, run the following command: + +``` +sudo usermod -aG i2c upsquared +``` + +**IMPORTANT NOTE REGARDING I2C:** +The current UP2 firmware is not able to scan for I2C devices using the `i2cdetect` command line tool. If you run this tool, it will cause the I2C subsystem to malfunction until you reboot your system. That means at this time, do not use `i2cdetect` on the UP2 board. + +### Local setup + +You would normally install Go and Gobot on your local workstation. Once installed, cross compile your program on your workstation, transfer the final executable to your UP2, and run the program on the UP2 as documented below. ``` go get -d -u gobot.io/x/gobot/... @@ -30,12 +45,12 @@ led := gpio.NewLedDriver(r, "13") Compile your Gobot program on your workstation like this: ```bash -$ GOARCH=386 GOOS=linux go build examples/up2_blink.go +$ GOARCH=amd64 GOOS=linux go build examples/up2_blink.go ``` Once you have compiled your code, you can you can upload your program and execute it on the UP2 from your workstation using the `scp` and `ssh` commands like this: ```bash -$ scp up2_blink ubuntu@192.168.1.xxx:/home/ubuntu/ -$ ssh -t ubuntu@192.168.1.xxx "./up2_blink" +$ scp up2_blink upsquared@192.168.1.xxx:/home/upsquared/ +$ ssh -t upsquared@192.168.1.xxx "./up2_blink" ``` From cd25357f1daf9ad7eb0a65373f5428d34e5f2700 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Thu, 6 Sep 2018 16:30:41 +0200 Subject: [PATCH 12/47] examples: add missing nobuild header Signed-off-by: Ron Evans --- examples/raspi_stepper_move.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/examples/raspi_stepper_move.go b/examples/raspi_stepper_move.go index 1e61c7c4..15495481 100644 --- a/examples/raspi_stepper_move.go +++ b/examples/raspi_stepper_move.go @@ -1,3 +1,7 @@ +// +build example +// +// Do not build by default. + package main import ( From bf8b1d241ffbf97274885157baf0fd83ce4bd511 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Thu, 6 Sep 2018 17:12:48 +0200 Subject: [PATCH 13/47] up2: update README to include more complete setup information Signed-off-by: Ron Evans --- platforms/upboard/up2/README.md | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/platforms/upboard/up2/README.md b/platforms/upboard/up2/README.md index 0a71d57d..130e53ec 100644 --- a/platforms/upboard/up2/README.md +++ b/platforms/upboard/up2/README.md @@ -8,16 +8,41 @@ For more info about the UP2 Board, go to [http://www.up-board.org/upsquared/](ht ### Setting up your UP2 board -We recommend updating to the latest Ubuntu and firmware when using the UP2 board. For more information go to: +We recommend updating to the latest Ubuntu 16.04 and BIOS v3.3 when using the UP2 board. To update your UP2 OS go to: -URL +https://downloads.up-community.org/download/up-squared-iot-grove-development-kit-ubuntu-16-04-server-image/ -Once your UP@ has been updated, you will need to provide permission to the `upsquared` user to access the I2C subsystem on the board. To do this, run the following command: +To update your UP2 BIOS, go to: + +https://downloads.up-community.org/download/up-squared-uefi-bios-v3-3/ + +Once your UP@ has been updated, you will need to provide permission to the `upsquared` user to access the GPIO or I2C subsystems on the board. + +To access the GPIO subsystem, you will need to create a new group, add your user to the group, and then add a UDEV rule. + +First add a new UDEV rule to the UP2 board. Add the following text as a new UDEV rule named `/etc/udev/rules.d/99-gpio.rules`: + +``` +SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c '\ + chown -R root:gpiouser /sys/class/gpio && chmod -R 770 /sys/class/gpio;\ + chown -R root:gpiouser /sys/devices/virtual/gpio && chmod -R 770 /sys/devices/virtual/gpio;\ + chown -R root:gpiouser /sys$devpath && chmod -R 770 /sys$devpath\ +'" +``` + +``` +sudo groupadd gpio +sudo adduser upsquared gpio +``` + +To access the I2C subsystem, run the following command: ``` sudo usermod -aG i2c upsquared ``` +You should reboot your UP2 board after making these changes for them to take effect. + **IMPORTANT NOTE REGARDING I2C:** The current UP2 firmware is not able to scan for I2C devices using the `i2cdetect` command line tool. If you run this tool, it will cause the I2C subsystem to malfunction until you reboot your system. That means at this time, do not use `i2cdetect` on the UP2 board. From 3f65f1df377fea1ab65dc396780dd0d076744d45 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Thu, 6 Sep 2018 23:20:35 +0200 Subject: [PATCH 14/47] grovepi: add mutex to control transactionality of the device communication Signed-off-by: Ron Evans --- drivers/i2c/grovepi_driver.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/drivers/i2c/grovepi_driver.go b/drivers/i2c/grovepi_driver.go index 15b7ff5d..476eb520 100644 --- a/drivers/i2c/grovepi_driver.go +++ b/drivers/i2c/grovepi_driver.go @@ -3,6 +3,7 @@ package i2c import ( "strconv" "strings" + "sync" "time" "gobot.io/x/gobot" @@ -30,6 +31,7 @@ type GrovePiDriver struct { name string digitalPins map[int]string analogPins map[int]string + mutex *sync.Mutex connector Connector connection Connection Config @@ -48,6 +50,7 @@ func NewGrovePiDriver(a Connector, options ...func(Config)) *GrovePiDriver { name: gobot.DefaultName("GrovePi"), digitalPins: make(map[int]string), analogPins: make(map[int]string), + mutex: &sync.Mutex{}, connector: a, Config: NewConfig(), } @@ -192,6 +195,9 @@ func getPin(pin string) string { // readAnalog reads analog value from the GrovePi. func (d *GrovePiDriver) readAnalog(pin byte) (int, error) { + d.mutex.Lock() + defer d.mutex.Unlock() + b := []byte{CommandReadAnalog, pin, 0, 0} _, err := d.connection.Write(b) if err != nil { @@ -213,6 +219,9 @@ func (d *GrovePiDriver) readAnalog(pin byte) (int, error) { // readDigital reads digitally from the GrovePi. func (d *GrovePiDriver) readDigital(pin byte) (val int, err error) { + d.mutex.Lock() + defer d.mutex.Unlock() + buf := []byte{CommandReadDigital, pin, 0, 0} _, err = d.connection.Write(buf) if err != nil { @@ -232,6 +241,9 @@ func (d *GrovePiDriver) readDigital(pin byte) (val int, err error) { // writeDigital writes digitally to the GrovePi. func (d *GrovePiDriver) writeDigital(pin byte, val byte) error { + d.mutex.Lock() + defer d.mutex.Unlock() + buf := []byte{CommandWriteDigital, pin, val, 0} _, err := d.connection.Write(buf) From b4f53775434a206ce46f3ce1305342e3952dc44f Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Fri, 7 Sep 2018 21:26:45 +0200 Subject: [PATCH 15/47] up2: add support for built-in LEDs Signed-off-by: Ron Evans --- examples/up2_leds.go | 49 +++++++++++++++++++++++++++ platforms/upboard/up2/README.md | 17 ++++++++++ platforms/upboard/up2/adaptor.go | 20 +++++++++-- platforms/upboard/up2/adaptor_test.go | 7 ++++ 4 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 examples/up2_leds.go diff --git a/examples/up2_leds.go b/examples/up2_leds.go new file mode 100644 index 00000000..3676b73a --- /dev/null +++ b/examples/up2_leds.go @@ -0,0 +1,49 @@ +// +build example +// +// Do not build by default. + +package main + +import ( + "time" + + "gobot.io/x/gobot" + "gobot.io/x/gobot/drivers/gpio" + "gobot.io/x/gobot/platforms/upboard/up2" +) + +func main() { + b := up2.NewAdaptor() + red := gpio.NewLedDriver(b, "red") + blue := gpio.NewLedDriver(b, "blue") + green := gpio.NewLedDriver(b, "green") + yellow := gpio.NewLedDriver(b, "yellow") + + work := func() { + red.Off() + blue.Off() + green.Off() + yellow.Off() + + gobot.Every(1*time.Second, func() { + red.Toggle() + }) + gobot.Every(2*time.Second, func() { + green.Toggle() + }) + gobot.Every(4*time.Second, func() { + yellow.Toggle() + }) + gobot.Every(8*time.Second, func() { + blue.Toggle() + }) + } + + robot := gobot.NewRobot("blinkBot", + []gobot.Connection{b}, + []gobot.Device{red, blue, green, yellow}, + work, + ) + + robot.Start() +} diff --git a/platforms/upboard/up2/README.md b/platforms/upboard/up2/README.md index 130e53ec..4fd859c7 100644 --- a/platforms/upboard/up2/README.md +++ b/platforms/upboard/up2/README.md @@ -35,6 +35,23 @@ sudo groupadd gpio sudo adduser upsquared gpio ``` +To use the built-in LEDs you will need to create a new group, add your user to the group, and then add a UDEV rule. + +Add the following text as a new UDEV rule named `/etc/udev/rules.d/99-leds.rules`: + +``` +SUBSYSTEM=="leds*", PROGRAM="/bin/sh -c '\ + chown -R root:leds /sys/class/leds && chmod -R 770 /sys/class/leds;\ + chown -R root:leds /sys/devices/platform/up-pinctrl/leds && chmod -R 770 /sys/devices/platform/up-pinctrl/leds;\ + chown -R root:leds /sys/devices/platform/AANT0F01:00/upboard-led.* && chmod -R 770 /sys/devices/platform/AANT0F01:00/upboard-led.*;\ +'" +``` + +``` +sudo groupadd leds +sudo adduser upsquared leds +``` + To access the I2C subsystem, run the following command: ``` diff --git a/platforms/upboard/up2/adaptor.go b/platforms/upboard/up2/adaptor.go index 111b0163..10fa9add 100644 --- a/platforms/upboard/up2/adaptor.go +++ b/platforms/upboard/up2/adaptor.go @@ -3,6 +3,8 @@ package up2 import ( "errors" "fmt" + "os" + "strconv" "sync" multierror "github.com/hashicorp/go-multierror" @@ -21,6 +23,7 @@ type sysfsPin struct { type Adaptor struct { name string pinmap map[string]sysfsPin + ledPath string digitalPins map[int]*sysfs.DigitalPin pwmPins map[int]*sysfs.PWMPin i2cBuses [6]i2c.I2cDevice @@ -35,8 +38,9 @@ type Adaptor struct { // NewAdaptor creates a UP2 Adaptor func NewAdaptor() *Adaptor { c := &Adaptor{ - name: gobot.DefaultName("UP2"), - mutex: &sync.Mutex{}, + name: gobot.DefaultName("UP2"), + mutex: &sync.Mutex{}, + ledPath: "/sys/class/leds/upboard:%s:/brightness", } c.setPins() @@ -105,6 +109,18 @@ func (c *Adaptor) DigitalRead(pin string) (val int, err error) { // DigitalWrite writes digital value to the specified pin. func (c *Adaptor) DigitalWrite(pin string, val byte) (err error) { + // is it one of the built-in LEDs? + if pin == "red" || pin == "blue" || pin == "green" || pin == "yellow" { + pinPath := fmt.Sprintf(c.ledPath, pin) + fi, e := sysfs.OpenFile(pinPath, os.O_WRONLY|os.O_APPEND, 0666) + defer fi.Close() + if e != nil { + return e + } + _, err = fi.WriteString(strconv.Itoa(int(val))) + return err + } + // one of the normal GPIO pins, then sysfsPin, err := c.DigitalPin(pin, sysfs.OUT) if err != nil { return err diff --git a/platforms/upboard/up2/adaptor_test.go b/platforms/upboard/up2/adaptor_test.go index 7d23e37c..b448d953 100644 --- a/platforms/upboard/up2/adaptor_test.go +++ b/platforms/upboard/up2/adaptor_test.go @@ -39,6 +39,7 @@ func initTestUP2Adaptor() (*Adaptor, *sysfs.MockFilesystem) { "/sys/class/pwm/pwmchip0/pwm0/period", "/sys/class/pwm/pwmchip0/pwm0/duty_cycle", "/sys/class/pwm/pwmchip0/pwm0/polarity", + "/sys/class/leds/upboard:green:/brightness", }) sysfs.SetFilesystem(fs) @@ -63,6 +64,12 @@ func TestUP2AdaptorDigitalIO(t *testing.T) { i, _ := a.DigitalRead("13") gobottest.Assert(t, i, 1) + a.DigitalWrite("green", 1) + gobottest.Assert(t, + fs.Files["/sys/class/leds/upboard:green:/brightness"].Contents, + "1", + ) + gobottest.Assert(t, a.DigitalWrite("99", 1), errors.New("Not a valid pin")) gobottest.Assert(t, a.Finalize(), nil) } From 3358024288bd089351165c394d4dd3e603229ec3 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Sat, 8 Sep 2018 14:25:20 +0200 Subject: [PATCH 16/47] up2: useful constant values to access the built-in LEDs Signed-off-by: Ron Evans --- examples/up2_leds.go | 8 ++++---- platforms/upboard/up2/README.md | 7 +++++++ platforms/upboard/up2/adaptor.go | 16 +++++++++++++++- 3 files changed, 26 insertions(+), 5 deletions(-) diff --git a/examples/up2_leds.go b/examples/up2_leds.go index 3676b73a..2994837b 100644 --- a/examples/up2_leds.go +++ b/examples/up2_leds.go @@ -14,10 +14,10 @@ import ( func main() { b := up2.NewAdaptor() - red := gpio.NewLedDriver(b, "red") - blue := gpio.NewLedDriver(b, "blue") - green := gpio.NewLedDriver(b, "green") - yellow := gpio.NewLedDriver(b, "yellow") + red := gpio.NewLedDriver(b, up2.LEDRed) + blue := gpio.NewLedDriver(b, up2.LEDBlue) + green := gpio.NewLedDriver(b, up2.LEDGreen) + yellow := gpio.NewLedDriver(b, up2.LEDYellow) work := func() { red.Off() diff --git a/platforms/upboard/up2/README.md b/platforms/upboard/up2/README.md index 4fd859c7..950f34fc 100644 --- a/platforms/upboard/up2/README.md +++ b/platforms/upboard/up2/README.md @@ -80,6 +80,13 @@ r := up2.NewAdaptor() led := gpio.NewLedDriver(r, "13") ``` +You can also use the values `up2.LEDRed`, `up2.LEDBlue`, `up2.LEDGreen`, and `up2.LEDYellow` as pin reference to access the 4 built-in LEDs. For example: + +```go +r := up2.NewAdaptor() +led := gpio.NewLedDriver(r, up2.LEDRed) +``` + ## How to Connect ### Compiling diff --git a/platforms/upboard/up2/adaptor.go b/platforms/upboard/up2/adaptor.go index 10fa9add..51423589 100644 --- a/platforms/upboard/up2/adaptor.go +++ b/platforms/upboard/up2/adaptor.go @@ -14,6 +14,20 @@ import ( "gobot.io/x/gobot/sysfs" ) +const ( + // LEDRed is the built-in red LED. + LEDRed = "red" + + // LEDBlue is the built-in blue LED. + LEDBlue = "blue" + + // LEDGreen is the built-in green LED. + LEDGreen = "green" + + // LEDYellow is the built-in yellow LED. + LEDYellow = "yellow" +) + type sysfsPin struct { pin int pwmPin int @@ -110,7 +124,7 @@ func (c *Adaptor) DigitalRead(pin string) (val int, err error) { // DigitalWrite writes digital value to the specified pin. func (c *Adaptor) DigitalWrite(pin string, val byte) (err error) { // is it one of the built-in LEDs? - if pin == "red" || pin == "blue" || pin == "green" || pin == "yellow" { + if pin == LEDRed || pin == LEDBlue || pin == LEDGreen || pin == LEDYellow { pinPath := fmt.Sprintf(c.ledPath, pin) fi, e := sysfs.OpenFile(pinPath, os.O_WRONLY|os.O_APPEND, 0666) defer fi.Close() From 2a37428f387230355a554caa9783d737ed05747b Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Mon, 10 Sep 2018 12:30:07 +0200 Subject: [PATCH 17/47] up2: finalize docs for UP2 config steps Signed-off-by: Ron Evans --- platforms/upboard/up2/README.md | 38 +++++++++++++++++++++------------ 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/platforms/upboard/up2/README.md b/platforms/upboard/up2/README.md index 950f34fc..590dce00 100644 --- a/platforms/upboard/up2/README.md +++ b/platforms/upboard/up2/README.md @@ -1,12 +1,12 @@ -# UP2 (Squared) +# UP2 -The UP2 Board is a single board SoC computer based on the Intel Apollo Lake processor. It has built-in GPIO, PWM, SPI, and I2C interfaces. +The UP2 Board aka "Up Squared" is a single board SoC computer based on the Intel Apollo Lake processor. It has built-in GPIO, PWM, SPI, and I2C interfaces. For more info about the UP2 Board, go to [http://www.up-board.org/upsquared/](http://www.up-board.org/upsquared/). ## How to Install -### Setting up your UP2 board +### Update operating system and BIOS on UP2 board We recommend updating to the latest Ubuntu 16.04 and BIOS v3.3 when using the UP2 board. To update your UP2 OS go to: @@ -16,11 +16,20 @@ To update your UP2 BIOS, go to: https://downloads.up-community.org/download/up-squared-uefi-bios-v3-3/ -Once your UP@ has been updated, you will need to provide permission to the `upsquared` user to access the GPIO or I2C subsystems on the board. +Once your UP2 has been updated, you will need to provide permission to the `upsquared` user to access the GPIO or I2C subsystems on the board. + +### Configuring GPIO on UP2 board To access the GPIO subsystem, you will need to create a new group, add your user to the group, and then add a UDEV rule. -First add a new UDEV rule to the UP2 board. Add the following text as a new UDEV rule named `/etc/udev/rules.d/99-gpio.rules`: +First, run the following commands on the board itself: + +``` +sudo groupadd gpio +sudo adduser upsquared gpio +``` + +Now, add a new UDEV rule to the UP2 board. Add the following text as a new UDEV rule named `/etc/udev/rules.d/99-gpio.rules`: ``` SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c '\ @@ -30,14 +39,18 @@ SUBSYSTEM=="gpio*", PROGRAM="/bin/sh -c '\ '" ``` -``` -sudo groupadd gpio -sudo adduser upsquared gpio -``` +### Configuring built-in LEDs on UP2 board To use the built-in LEDs you will need to create a new group, add your user to the group, and then add a UDEV rule. -Add the following text as a new UDEV rule named `/etc/udev/rules.d/99-leds.rules`: +First, run the following commands on the board itself: + +``` +sudo groupadd leds +sudo adduser upsquared leds +``` + +Now add the following text as a new UDEV rule named `/etc/udev/rules.d/99-leds.rules`: ``` SUBSYSTEM=="leds*", PROGRAM="/bin/sh -c '\ @@ -47,10 +60,7 @@ SUBSYSTEM=="leds*", PROGRAM="/bin/sh -c '\ '" ``` -``` -sudo groupadd leds -sudo adduser upsquared leds -``` +### Configuring I2C on UP2 board To access the I2C subsystem, run the following command: From e1b18ae5d974957e53b6c0845b99ce5e15a35eeb Mon Sep 17 00:00:00 2001 From: Waldemar Quevedo Date: Wed, 12 Sep 2018 18:40:05 -0700 Subject: [PATCH 18/47] Update README and examples Signed-off-by: Waldemar Quevedo --- platforms/nats/README.md | 111 ++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/platforms/nats/README.md b/platforms/nats/README.md index 1eb7776b..9e6e9b1c 100644 --- a/platforms/nats/README.md +++ b/platforms/nats/README.md @@ -2,7 +2,7 @@ NATS is a lightweight messaging protocol perfect for your IoT/Robotics projects. It operates over TCP, offers a great number of features but an incredibly simple Pub Sub style model of communicating broadcast messages. NATS is blazingly fast as it is written in Go. -This repository contains the Gobot adaptor/drivers to connect to NATS servers. It uses the NATS Go Client available at https://github.com/nats-io/nats. The NATS project is maintained by Nats.io and sponsored by Apcera. Find more information on setting up a NATS server and its capability at http://nats.io/. +This repository contains the Gobot adaptor/drivers to connect to NATS servers. It uses the NATS Go Client available at https://github.com/nats-io/nats. The NATS project is a project part of the [CNCF](https://www.cncf.io/). Find more information on setting up a NATS server and its capability at http://nats.io/. The NATS messaging protocol (http://www.nats.io/documentation/internals/nats-protocol-demo/) is really easy to work with and can be practiced by setting up a NATS server using Go or Docker. For information on setting up a server using the source code, visit https://github.com/nats-io/gnatsd. For information on the Docker image up on Docker Hub, see https://hub.docker.com/_/nats/. Getting the server set up is very easy. The server itself is Golang, can be built for different architectures and installs in a small footprint. This is an excellent way to get communications going between your IoT and Robotics projects. @@ -16,85 +16,88 @@ go get -d -u gobot.io/x/gobot/... ## How to Use -Before running the example, make sure you have an NATS server running somewhere you can connect to +Before running the example, make sure you have an NATS server running somewhere you can connect to (for demo purposes you could also use the public endpoint `demo.nats.io:4222`). ```go package main import ( - "fmt" - "time" + "fmt" + "time" - "gobot.io/x/gobot" - "gobot.io/x/gobot/platforms/nats" + "gobot.io/x/gobot" + "gobot.io/x/gobot/platforms/nats" ) func main() { - natsAdaptor := nats.NewNatsAdaptor("nats", "localhost:4222", 1234) + natsAdaptor := nats.NewAdaptor("nats://localhost:4222", 1234) - work := func() { - natsAdaptor.On("hello", func(msg nats.Message) { - fmt.Println(subject) - }) - natsAdaptor.On("hola", func(msg nats.Message) { - fmt.Println(subject) - }) - data := []byte("o") - gobot.Every(1*time.Second, func() { - natsAdaptor.Publish("hello", data) - }) - gobot.Every(5*time.Second, func() { - natsAdaptor.Publish("hola", data) - }) - } + work := func() { + natsAdaptor.On("hello", func(msg nats.Message) { + fmt.Printf("[Received on %q] %s\n", msg.Subject, string(msg.Data)) + }) + natsAdaptor.On("hola", func(msg nats.Message) { + fmt.Printf("[Received on %q] %s\n", msg.Subject, string(msg.Data)) + }) - robot := gobot.NewRobot("natsBot", - []gobot.Connection{natsAdaptor}, - work, - ) + data := []byte("Hello Gobot!") + gobot.Every(1*time.Second, func() { + natsAdaptor.Publish("hello", data) + }) + gobot.Every(5*time.Second, func() { + natsAdaptor.Publish("hola", data) + }) + } - robot.Start() + robot := gobot.NewRobot("natsBot", + []gobot.Connection{natsAdaptor}, + work, + ) + + robot.Start() } ``` -To run with TLS enabled, set the URL scheme prefix to tls://. Make sure the NATS server has TLS enabled and use the NATS option parameters to pass in the TLS settings to the adaptor. Refer to the github.com/nats-io/go-nats README for more TLS option parameters. +To run with TLS enabled, set the URL scheme prefix to `tls://`. Make sure the NATS server has TLS enabled and use the NATS option parameters to pass in the TLS settings to the adaptor. Refer to the [github.com/nats-io/go-nats](https://github.com/nats-io/go-nats) README for more TLS option parameters. ```go package main import ( - "fmt" - "time" - natsio "github.com/nats-io/nats" - "gobot.io/x/gobot" - "gobot.io/x/gobot/platforms/nats" + "fmt" + "time" + + natsio "github.com/nats-io/go-nats" + "gobot.io/x/gobot" + "gobot.io/x/gobot/platforms/nats" ) func main() { - natsAdaptor := nats.NewNatsAdaptor("tls://localhost:4222", 1234, natsio.RootCAs("certs/ca.pem")) + natsAdaptor := nats.NewAdaptor("tls://localhost:4222", 1234, natsio.RootCAs("certs/ca.pem")) - work := func() { - natsAdaptor.On("hello", func(msg nats.Message) { - fmt.Println(subject) - }) - natsAdaptor.On("hola", func(msg nats.Message) { - fmt.Println(subject) - }) - data := []byte("o") - gobot.Every(1*time.Second, func() { - natsAdaptor.Publish("hello", data) - }) - gobot.Every(5*time.Second, func() { - natsAdaptor.Publish("hola", data) - }) - } + work := func() { + natsAdaptor.On("hello", func(msg nats.Message) { + fmt.Printf("[Received on %q] %s\n", msg.Subject, string(msg.Data)) + }) + natsAdaptor.On("hola", func(msg nats.Message) { + fmt.Printf("[Received on %q] %s\n", msg.Subject, string(msg.Data)) + }) - robot := gobot.NewRobot("natsBot", - []gobot.Connection{natsAdaptor}, - work, - ) + data := []byte("Hello Gobot!") + gobot.Every(1*time.Second, func() { + natsAdaptor.Publish("hello", data) + }) + gobot.Every(5*time.Second, func() { + natsAdaptor.Publish("hola", data) + }) + } - robot.Start() + robot := gobot.NewRobot("natsBot", + []gobot.Connection{natsAdaptor}, + work, + ) + + robot.Start() } ``` From c5b507a3e40ea4e85a4addc163a961450850510a Mon Sep 17 00:00:00 2001 From: Waldemar Quevedo Date: Wed, 12 Sep 2018 18:41:44 -0700 Subject: [PATCH 19/47] Update Go NATS client library import Signed-off-by: Waldemar Quevedo --- platforms/nats/nats_adaptor.go | 2 +- platforms/nats/nats_adaptor_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/platforms/nats/nats_adaptor.go b/platforms/nats/nats_adaptor.go index e7ff1c5a..504f305c 100644 --- a/platforms/nats/nats_adaptor.go +++ b/platforms/nats/nats_adaptor.go @@ -1,7 +1,7 @@ package nats import ( - "github.com/nats-io/nats" + "github.com/nats-io/go-nats" "gobot.io/x/gobot" "net/url" "strings" diff --git a/platforms/nats/nats_adaptor_test.go b/platforms/nats/nats_adaptor_test.go index ad34202f..c492d44d 100644 --- a/platforms/nats/nats_adaptor_test.go +++ b/platforms/nats/nats_adaptor_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/nats-io/nats" + "github.com/nats-io/go-nats" "gobot.io/x/gobot" "gobot.io/x/gobot/gobottest" ) From 192dbcbc4c41d021b6355062d8124061b1a10e08 Mon Sep 17 00:00:00 2001 From: npotts Date: Thu, 13 Sep 2018 21:36:02 -0600 Subject: [PATCH 20/47] Adde unit tests for TH02 & Minor improvement - Added ability to change sample resolution - Added unit tests for TH02Driver. The way the functions are written, it will be hard to get a fully working .Sample() coverage with my limited understanding of gobot's testing API. The chip is rather needy, and each sample requires multiple read and write steps, so getting both read and write for 2 independant measurements it a little over my head without some more code viewing. Id rather get this checked in and think of a better way to do this. Otherwise, this does test the functionality Signed-off-by: npotts --- drivers/i2c/th02_driver.go | 22 +-- drivers/i2c/th02_driver_test.go | 282 ++++++++++++++++++++++++++++++++ 2 files changed, 288 insertions(+), 16 deletions(-) create mode 100644 drivers/i2c/th02_driver_test.go diff --git a/drivers/i2c/th02_driver.go b/drivers/i2c/th02_driver.go index b5c9f437..34dba010 100644 --- a/drivers/i2c/th02_driver.go +++ b/drivers/i2c/th02_driver.go @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016-2017 Weston Schmidt + * Copyright (c) 2018 Nick Potts * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,9 @@ package i2c // TH02Driver is a driver for the TH02-D based devices. // -// This module was tested with AdaFruit Sensiron SHT32-D Breakout. -// https://www.adafruit.com/products/2857 +// This module was tested with a Grove "Temperature&Humidity Sensor (High-Accuracy & Mini ) v1.0" +// from https://www.seeedstudio.com/Grove-Temperature-Humidity-Sensor-High-Accuracy-Min-p-1921.htm +// Datasheet is at http://www.hoperf.com/upload/sensor/TH02_V1.1.pdf import ( "fmt" @@ -30,30 +31,19 @@ import ( const ( - // TH02AddressA is the default address of device + // TH02Address is the default address of device TH02Address = 0x40 //TH02ConfigReg is the configuration register TH02ConfigReg = 0x03 - - // TH02RegConfigHighAccuracyTemp is the CONFIG register to read temperature - // TH02HighAccuracyTemp = 0x11 - - //TH02RegConfigHighAccuracyRH is the CONFIG register to read high accuracy RH -// TH02HighAccuracyRH = 0x01 ) +//Accuracy constants for the TH02 devices const ( TH02HighAccuracy = 0 //High Accuracy TH02LowAccuracy = 1 //Lower Accuracy ) -var ( -//ErrInvalidAccuracy = errors.New("Invalid accuracy") -//ErrInvalidCrc = errors.New("Invalid crc") -//ErrInvalidTemp = errors.New("Invalid temperature units") -) - // TH02Driver is a Driver for a TH02 humidity and temperature sensor type TH02Driver struct { Units string diff --git a/drivers/i2c/th02_driver_test.go b/drivers/i2c/th02_driver_test.go new file mode 100644 index 00000000..e501c0d0 --- /dev/null +++ b/drivers/i2c/th02_driver_test.go @@ -0,0 +1,282 @@ +package i2c + +import ( + "errors" + "fmt" + "testing" + "time" + + "gobot.io/x/gobot" + "gobot.io/x/gobot/gobottest" +) + +var _ gobot.Driver = (*TH02Driver)(nil) + +// // --------- HELPERS +func initTestTH02Driver() *SHT3xDriver { + driver, _ := initTestSHT3xDriverWithStubbedAdaptor() + return driver +} + +func initTestTH02DriverWithStubbedAdaptor() (*TH02Driver, *i2cTestAdaptor) { + adaptor := newI2cTestAdaptor() + return NewTH02Driver(adaptor), adaptor +} + +// --------- TESTS + +func TestNewTH02Driver(t *testing.T) { + i2cd := newI2cTestAdaptor() + defer i2cd.Close() + // Does it return a pointer to an instance of SHT3xDriver? + var iface interface{} = NewTH02Driver(i2cd) + _, ok := iface.(*TH02Driver) + if !ok { + t.Errorf("NewTH02Driver() should have returned a *NewTH02Driver") + } + b := NewTH02Driver(i2cd, func(Config) {}) + gobottest.Refute(t, b.Connection(), nil) + + //cover some basically useless protions the Interface demands + if name := b.Name(); name != b.name { + t.Errorf("Didnt return the proper name. Got %q wanted %q", name, b.name) + } + + if b.SetName("42"); b.name != "42" { + t.Errorf("yikes - didnt set name.") + } +} + +func TestTH02Driver_Accuracy(t *testing.T) { + i2cd := newI2cTestAdaptor() + defer i2cd.Close() + b := NewTH02Driver(i2cd) + + if b.SetAddress(0x42); b.addr != 0x42 { + t.Error("Didnt set address as expected") + } + + if b.SetAccuracy(0x42); b.accuracy != TH02HighAccuracy { + t.Error("Setting an invalid accuracy should resolve to TH02HighAccuracy") + } + + if b.SetAccuracy(TH02LowAccuracy); b.accuracy != TH02LowAccuracy { + t.Error("Expected setting low accuracy to actually set to low accuracy") + } + + if acc := b.Accuracy(); acc != TH02LowAccuracy { + t.Errorf("Accuract() didnt return what was expected") + } +} + +func TestTH022DriverStart(t *testing.T) { + b, _ := initTestTH02DriverWithStubbedAdaptor() + gobottest.Assert(t, b.Start(), nil) +} + +func TestTH02StartConnectError(t *testing.T) { + d, adaptor := initTestTH02DriverWithStubbedAdaptor() + adaptor.Testi2cConnectErr(true) + gobottest.Assert(t, d.Start(), errors.New("Invalid i2c connection")) +} + +func TestTH02DriverHalt(t *testing.T) { + sht3x := initTestTH02Driver() + gobottest.Assert(t, sht3x.Halt(), nil) +} + +func TestTH02DriverOptions(t *testing.T) { + d := NewTH02Driver(newI2cTestAdaptor(), WithBus(2)) + gobottest.Assert(t, d.GetBusOrDefault(1), 2) + d.Halt() +} + +func TestTH02Driver_ReadData(t *testing.T) { + d, i2cd := initTestTH02DriverWithStubbedAdaptor() + gobottest.Assert(t, d.Start(), nil) + + type x struct { + rd, wr func([]byte) (int, error) + rtn uint16 + errNil bool + } + + tests := map[string]x{ + "example RH": x{ + rd: func(b []byte) (int, error) { + copy(b, []byte{0x00, 0x07, 0xC0}) + return 3, nil + }, + wr: func([]byte) (int, error) { + return 1, nil + }, + errNil: true, + rtn: 1984, + }, + "example T": x{ + rd: func(b []byte) (int, error) { + copy(b, []byte{0x00, 0x12, 0xC0}) + return 3, nil + }, + wr: func([]byte) (int, error) { + return 1, nil + }, + errNil: true, + rtn: 4800, + }, + "timeout - no wait for ready": x{ + rd: func(b []byte) (int, error) { + time.Sleep(200 * time.Millisecond) + copy(b, []byte{0x01}) + return 1, fmt.Errorf("nope") + }, + wr: func([]byte) (int, error) { + return 1, nil + }, + errNil: false, + rtn: 0, + }, + "unable to write status register": x{ + rd: func(b []byte) (int, error) { + copy(b, []byte{0x00}) + return 0, nil + }, + wr: func([]byte) (int, error) { + return 0, fmt.Errorf("Nope") + }, + errNil: false, + rtn: 0, + }, + "unable to read doesnt provide enought data": x{ + rd: func(b []byte) (int, error) { + copy(b, []byte{0x00, 0x01}) + return 2, nil + }, + wr: func([]byte) (int, error) { + return 1, nil + }, + errNil: false, + rtn: 0, + }, + } + + for name, x := range tests { + t.Log("Running", name) + i2cd.i2cReadImpl = x.rd + i2cd.i2cWriteImpl = x.wr + got, err := d.readData() + gobottest.Assert(t, err == nil, x.errNil) + gobottest.Assert(t, got, x.rtn) + } +} + +func TestTH02Driver_waitForReady(t *testing.T) { + d, i2cd := initTestTH02DriverWithStubbedAdaptor() + gobottest.Assert(t, d.Start(), nil) + + i2cd.i2cReadImpl = func(b []byte) (int, error) { + time.Sleep(50 * time.Millisecond) + copy(b, []byte{0x01, 0x00}) + return 3, nil + } + + i2cd.i2cWriteImpl = func([]byte) (int, error) { + return 1, nil + } + + timeout := 10 * time.Microsecond + if err := d.waitForReady(&timeout); err == nil { + t.Error("Expected a timeout error") + } +} + +func TestTH02Driver_WriteRegister(t *testing.T) { + d, i2cd := initTestTH02DriverWithStubbedAdaptor() + gobottest.Assert(t, d.Start(), nil) + + i2cd.i2cWriteImpl = func([]byte) (int, error) { + return 1, nil + } + + if err := d.writeRegister(0x00, 0x00); err != nil { + t.Errorf("expected a nil error write") + } +} + +func TestTH02Driver_Heater(t *testing.T) { + d, i2cd := initTestTH02DriverWithStubbedAdaptor() + gobottest.Assert(t, d.Start(), nil) + + i2cd.i2cReadImpl = func(b []byte) (int, error) { + copy(b, []byte{0xff}) + return 1, nil + } + + i2cd.i2cWriteImpl = func([]byte) (int, error) { + return 1, nil + } + + on, err := d.Heater() + gobottest.Assert(t, on, true) + gobottest.Assert(t, err, nil) +} +func TestTH02Driver_SerialNumber(t *testing.T) { + d, i2cd := initTestTH02DriverWithStubbedAdaptor() + gobottest.Assert(t, d.Start(), nil) + + i2cd.i2cReadImpl = func(b []byte) (int, error) { + copy(b, []byte{0x42}) + return 1, nil + } + + i2cd.i2cWriteImpl = func([]byte) (int, error) { + return 1, nil + } + + sn, err := d.SerialNumber() + + gobottest.Assert(t, sn, uint32((0x42)>>4)) + gobottest.Assert(t, err, nil) +} + +func TestTH02Driver_ApplySettings(t *testing.T) { + d := &TH02Driver{} + + type x struct { + acc, base, out byte + heating bool + } + + tests := map[string]x{ + "low acc, heating": x{acc: TH02LowAccuracy, base: 0x00, heating: true, out: 0x01}, + "high acc, no heating": x{acc: TH02HighAccuracy, base: 0x00, heating: false, out: 0x23}, + } + + for name, x := range tests { + t.Log(name) + d.accuracy = x.acc + d.heating = x.heating + got := d.applysettings(x.base) + gobottest.Assert(t, x.out, got) + } +} + +func TestTH02Driver_Sample(t *testing.T) { + d, i2cd := initTestTH02DriverWithStubbedAdaptor() + gobottest.Assert(t, d.Start(), nil) + + i2cd.i2cReadImpl = func(b []byte) (int, error) { + copy(b, []byte{0x00, 0x00, 0x07, 0xC0}) + return 4, nil + } + + i2cd.i2cWriteImpl = func([]byte) (int, error) { + return 1, nil + } + + temp, rh, _ := d.Sample() + + gobottest.Assert(t, temp, float32(0)) + gobottest.Assert(t, rh, float32(0)) + +} From 20ee7c03d16370f57b7296066ad1d81006490b9e Mon Sep 17 00:00:00 2001 From: Brendan Stennett Date: Wed, 12 Sep 2018 23:46:59 -0400 Subject: [PATCH 21/47] =?UTF-8?q?Add=20SparkFun=E2=80=99s=20EasyDriver=20(?= =?UTF-8?q?and=20BigEasyDriver)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Brendan Stennett --- README.md | 1 + drivers/gpio/easy_driver.go | 281 +++++++++++++++++++++++++++++++ drivers/gpio/easy_driver_test.go | 138 +++++++++++++++ 3 files changed, 420 insertions(+) create mode 100644 drivers/gpio/easy_driver.go create mode 100644 drivers/gpio/easy_driver_test.go diff --git a/README.md b/README.md index c7b580df..6f14e6b2 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,7 @@ a shared set of drivers provided using the `gobot/drivers/gpio` package: - Button - Buzzer - Direct Pin + - EasyDriver - Grove Button - Grove Buzzer - Grove LED diff --git a/drivers/gpio/easy_driver.go b/drivers/gpio/easy_driver.go new file mode 100644 index 00000000..e77ec37a --- /dev/null +++ b/drivers/gpio/easy_driver.go @@ -0,0 +1,281 @@ +package gpio + +import ( + "errors" + "strconv" + "time" + + "gobot.io/x/gobot" +) + +// EasyDriver object +type EasyDriver struct { + gobot.Commander + + name string + connection DigitalWriter + stepPin string + dirPin string + enPin string + sleepPin string + + angle float32 + rpm uint + dir int8 + moving bool + stepNum int + enabled bool + sleeping bool +} + +// NewEasyDriver returns a new EasyDriver from SparkFun (https://www.sparkfun.com/products/12779) +// TODO: Support selecting phase input instead of hard-wiring MS1 and MS2 to board truth table +// This should also work for the BigEasyDriver (untested) +// A - DigitalWriter +// stepPin - Pin corresponding to step input on EasyDriver +// dirPin - Pin corresponding to dir input on EasyDriver. Optional +// enPin - Pin corresponding to enabled input on EasyDriver. Optional +// sleepPin - Pin corresponding to sleep input on EasyDriver. Optional +// angle - Step angle of motor +func NewEasyDriver(a DigitalWriter, angle float32, stepPin string, dirPin string, enPin string, sleepPin string) *EasyDriver { + d := &EasyDriver{ + Commander: gobot.NewCommander(), + name: gobot.DefaultName("EasyDriver"), + connection: a, + stepPin: stepPin, + dirPin: dirPin, + enPin: enPin, + sleepPin: sleepPin, + + angle: angle, + rpm: 1, + dir: 1, + enabled: true, + sleeping: false, + } + + // panic if step pin isn't set + if stepPin == "" { + panic("Step pin is not set") + } + + // 1/4 of max speed. Not too fast, not too slow + d.rpm = d.GetMaxSpeed() / 4 + + d.AddCommand("Move", func(params map[string]interface{}) interface{} { + degs, _ := strconv.Atoi(params["degs"].(string)) + return d.Move(degs) + }) + d.AddCommand("Step", func(params map[string]interface{}) interface{} { + return d.Step() + }) + d.AddCommand("Run", func(params map[string]interface{}) interface{} { + return d.Run() + }) + d.AddCommand("Stop", func(params map[string]interface{}) interface{} { + return d.Stop() + }) + + return d +} + +// Name of EasyDriver +func (d *EasyDriver) Name() string { return d.name } + +// SetName sets name for EasyDriver +func (d *EasyDriver) SetName(n string) { d.name = n } + +// Connection returns EasyDriver's connection +func (d *EasyDriver) Connection() gobot.Connection { return d.connection.(gobot.Connection) } + +// Start implements the Driver interface +func (d *EasyDriver) Start() (err error) { return } + +// Halt implements the Driver interface; stops running the stepper +func (d *EasyDriver) Halt() (err error) { + d.Stop() + return +} + +// Move the motor given number of degrees at current speed. +func (d *EasyDriver) Move(degs int) (err error) { + if d.moving { + // don't do anything if already moving + return + } + + d.moving = true + + steps := int(float32(degs) / d.angle) + for i := 0; i < steps; i++ { + if !d.moving { + // don't continue to step if driver is stopped + break + } + + d.Step() + } + + d.moving = false + + return +} + +// Step the stepper 1 step +func (d *EasyDriver) Step() (err error) { + stepsPerRev := d.GetMaxSpeed() + + // a valid steps occurs for a low to high transition + d.connection.DigitalWrite(d.stepPin, 0) + // 1 minute / steps per revolution / revolutions per minute + // let's keep it as Microseconds so we only have to do integer math + time.Sleep(time.Duration(60*1000*1000/stepsPerRev/d.rpm) * time.Microsecond) + d.connection.DigitalWrite(d.stepPin, 1) + + // increment or decrement the number of steps by 1 + d.stepNum += int(d.dir) + + return +} + +// Run the stepper continuously +func (d *EasyDriver) Run() (err error) { + if d.moving { + // don't do anything if already moving + return + } + + d.moving = true + + go func() { + for d.moving { + d.Step() + } + }() + + return +} + +// Stop running the stepper +func (d *EasyDriver) Stop() (err error) { + d.moving = false + return +} + +// SetDirection sets the direction to be moving. Valid directions are "cw" or "ccw" +func (d *EasyDriver) SetDirection(dir string) (err error) { + // can't change direct if dirPin isn't set + if d.dirPin == "" { + return errors.New("dirPin is not set") + } + + if dir == "ccw" { + d.dir = -1 + d.connection.DigitalWrite(d.dirPin, 1) // high is ccw + } else { // default to cw, even if user specified wrong value + d.dir = 1 + d.connection.DigitalWrite(d.dirPin, 0) // low is cw + } + + return +} + +// SetSpeed sets the speed of the motor in RPMs. 1 is the lowest and GetMaxSpeed is the highest +func (d *EasyDriver) SetSpeed(rpm uint) (err error) { + if rpm < 1 { + d.rpm = 1 + } else if rpm > d.GetMaxSpeed() { + d.rpm = d.GetMaxSpeed() + } else { + d.rpm = rpm + } + + return +} + +// GetMaxSpeed returns the max speed of the stepper +func (d *EasyDriver) GetMaxSpeed() uint { + return uint(360 / d.angle) +} + +// GetCurrentStep returns current step number +func (d *EasyDriver) GetCurrentStep() int { + return d.stepNum +} + +// IsMoving returns a bool stating whether motor is currently in motion +func (d *EasyDriver) IsMoving() bool { + return d.moving +} + +// Enable enables all motor output +func (d *EasyDriver) Enable() (err error) { + // can't enable if enPin isn't set. This is fine normally since it will be enabled by default + if d.enPin == "" { + return errors.New("enPin is not set. Board is enabled by default") + } + + d.enabled = true + d.connection.DigitalWrite(d.enPin, 0) // enPin is active low + + return +} + +// Disable disables all motor output +func (d *EasyDriver) Disable() (err error) { + // can't disable if enPin isn't set + if d.enPin == "" { + return errors.New("enPin is not set") + } + + // let's stop the motor first + d.Stop() + + d.enabled = false + d.connection.DigitalWrite(d.enPin, 1) // enPin is active low + + return +} + +// IsEnabled returns a bool stating whether motor is enabled +func (d *EasyDriver) IsEnabled() bool { + return d.enabled +} + +// Sleep puts the driver to sleep and disables all motor output. Low power mode. +func (d *EasyDriver) Sleep() (err error) { + // can't sleep if sleepPin isn't set + if d.sleepPin == "" { + return errors.New("sleepPin is not set") + } + + // let's stop the motor first + d.Stop() + + d.sleeping = true + d.connection.DigitalWrite(d.sleepPin, 0) // sleepPin is active low + + return +} + +// Wake wakes up the driver +func (d *EasyDriver) Wake() (err error) { + // can't wake if sleepPin isn't set + if d.sleepPin == "" { + return errors.New("sleepPin is not set") + } + + d.sleeping = false + d.connection.DigitalWrite(d.sleepPin, 1) // sleepPin is active low + + // we need to wait 1ms after sleeping before doing a step to charge the step pump (according to data sheet) + // this will ensure that happens + time.Sleep(1 * time.Millisecond) + + return +} + +// IsSleeping returns a bool stating whether motor is enabled +func (d *EasyDriver) IsSleeping() bool { + return d.sleeping +} diff --git a/drivers/gpio/easy_driver_test.go b/drivers/gpio/easy_driver_test.go new file mode 100644 index 00000000..825c398e --- /dev/null +++ b/drivers/gpio/easy_driver_test.go @@ -0,0 +1,138 @@ +package gpio + +import ( + "gobot.io/x/gobot/gobottest" + "strings" + "testing" + "time" +) + +const ( + stepAngle = 0.5 // use non int step angle to check int math + stepsPerRev = 720 +) + +func initEasyDriver() *EasyDriver { + return NewEasyDriver(newGpioTestAdaptor(), stepAngle, "1", "2", "3", "4") +} + +func TestEasyDriverDefaultName(t *testing.T) { + d := initEasyDriver() + gobottest.Assert(t, strings.HasPrefix(d.Name(), "EasyDriver"), true) +} + +func TestEasyDriverSetName(t *testing.T) { + d := initEasyDriver() + d.SetName("OtherDriver") + gobottest.Assert(t, strings.HasPrefix(d.Name(), "OtherDriver"), true) +} + +func TestEasyDriverMove(t *testing.T) { + d := initEasyDriver() + d.Move(2) + time.Sleep(2 * time.Millisecond) + gobottest.Assert(t, d.GetCurrentStep(), 4) + gobottest.Assert(t, d.IsMoving(), false) +} + +func TestEasyDriverRun(t *testing.T) { + d := initEasyDriver() + d.Run() + gobottest.Assert(t, d.IsMoving(), true) + d.Run() + gobottest.Assert(t, d.IsMoving(), true) +} + +func TestEasyDriverStop(t *testing.T) { + d := initEasyDriver() + d.Run() + gobottest.Assert(t, d.IsMoving(), true) + d.Stop() + gobottest.Assert(t, d.IsMoving(), false) +} + +func TestEasyDriverStep(t *testing.T) { + d := initEasyDriver() + d.Step() + gobottest.Assert(t, d.GetCurrentStep(), 1) + d.Step() + d.Step() + d.Step() + gobottest.Assert(t, d.GetCurrentStep(), 4) + d.SetDirection("ccw") + d.Step() + gobottest.Assert(t, d.GetCurrentStep(), 3) +} + +func TestEasyDriverSetDirection(t *testing.T) { + d := initEasyDriver() + gobottest.Assert(t, d.dir, int8(1)) + d.SetDirection("cw") + gobottest.Assert(t, d.dir, int8(1)) + d.SetDirection("ccw") + gobottest.Assert(t, d.dir, int8(-1)) + d.SetDirection("nothing") + gobottest.Assert(t, d.dir, int8(1)) +} + +func TestEasyDriverSetSpeed(t *testing.T) { + d := initEasyDriver() + gobottest.Assert(t, d.rpm, uint(stepsPerRev/4)) // default speed of 720/4 + d.SetSpeed(0) + gobottest.Assert(t, d.rpm, uint(1)) + d.SetSpeed(200) + gobottest.Assert(t, d.rpm, uint(200)) + d.SetSpeed(1000) + gobottest.Assert(t, d.rpm, uint(stepsPerRev)) +} + +func TestEasyDriverGetMaxSpeed(t *testing.T) { + d := initEasyDriver() + gobottest.Assert(t, d.GetMaxSpeed(), uint(stepsPerRev)) +} + +func TestEasyDriverSleep(t *testing.T) { + // let's test basic functionality + d := initEasyDriver() + d.Sleep() + gobottest.Assert(t, d.IsSleeping(), true) + + // let's make sure it stops first + d = initEasyDriver() + d.Run() + d.Sleep() + gobottest.Assert(t, d.IsSleeping(), true) + gobottest.Assert(t, d.IsMoving(), false) +} + +func TestEasyDriverWake(t *testing.T) { + // let's test basic functionality + d := initEasyDriver() + d.Sleep() + gobottest.Assert(t, d.IsSleeping(), true) + d.Wake() + gobottest.Assert(t, d.IsSleeping(), false) +} + +func TestEasyDriverDisable(t *testing.T) { + // let's test basic functionality + d := initEasyDriver() + d.Disable() + gobottest.Assert(t, d.IsEnabled(), false) + + // let's make sure it stops first + d = initEasyDriver() + d.Run() + d.Disable() + gobottest.Assert(t, d.IsEnabled(), false) + gobottest.Assert(t, d.IsMoving(), false) +} + +func TestEasyDriverEnable(t *testing.T) { + // let's test basic functionality + d := initEasyDriver() + d.Disable() + gobottest.Assert(t, d.IsEnabled(), false) + d.Enable() + gobottest.Assert(t, d.IsEnabled(), true) +} \ No newline at end of file From 92d72486414b54cd527a8ada5af8a75c7992c48e Mon Sep 17 00:00:00 2001 From: Daniel Esteban Date: Sun, 16 Sep 2018 08:19:23 +0200 Subject: [PATCH 22/47] Simplify code as suggested in #617 --- drivers/gpio/max7219_driver.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/gpio/max7219_driver.go b/drivers/gpio/max7219_driver.go index 501df57e..3ef36241 100644 --- a/drivers/gpio/max7219_driver.go +++ b/drivers/gpio/max7219_driver.go @@ -62,7 +62,7 @@ func (a *MAX7219Driver) Start() (err error) { a.All(MAX7219Shutdown, 0x01) a.All(MAX7219DisplayTest, 0x00) a.ClearAll() - a.All(MAX7219Intensity, 0x0f&0x0f) + a.All(MAX7219Intensity, 0x0f) return } @@ -86,7 +86,7 @@ func (a *MAX7219Driver) SetIntensity(level byte) { if level > 15 { level = 15 } - a.All(MAX7219Intensity, level&level) + a.All(MAX7219Intensity, level) } // ClearAll turns off all LEDs of all modules From 66b61fab2ff0f16d5c7bdd66a6c4da3c18ed4944 Mon Sep 17 00:00:00 2001 From: Brendan Stennett Date: Thu, 20 Sep 2018 14:02:36 -0400 Subject: [PATCH 23/47] Added some missing tests to increase coverage --- drivers/gpio/easy_driver_test.go | 66 +++++++++++++++++++++++++++----- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/drivers/gpio/easy_driver_test.go b/drivers/gpio/easy_driver_test.go index 825c398e..f96358d5 100644 --- a/drivers/gpio/easy_driver_test.go +++ b/drivers/gpio/easy_driver_test.go @@ -12,8 +12,16 @@ const ( stepsPerRev = 720 ) +var adapter *gpioTestAdaptor + func initEasyDriver() *EasyDriver { - return NewEasyDriver(newGpioTestAdaptor(), stepAngle, "1", "2", "3", "4") + adapter = newGpioTestAdaptor() + return NewEasyDriver(adapter, stepAngle, "1", "2", "3", "4") +} + +func TestEasyDriver_Connection(t *testing.T) { + d := initEasyDriver() + gobottest.Assert(t, d.Connection(), adapter) } func TestEasyDriverDefaultName(t *testing.T) { @@ -27,6 +35,20 @@ func TestEasyDriverSetName(t *testing.T) { gobottest.Assert(t, strings.HasPrefix(d.Name(), "OtherDriver"), true) } +func TestEasyDriverStart(t *testing.T) { + d := initEasyDriver() + d.Start() + // noop - no error occurred +} + +func TestEasyDriverHalt(t *testing.T) { + d := initEasyDriver() + d.Run() + gobottest.Assert(t, d.IsMoving(), true) + d.Halt() + gobottest.Assert(t, d.IsMoving(), false) +} + func TestEasyDriverMove(t *testing.T) { d := initEasyDriver() d.Move(2) @@ -75,6 +97,13 @@ func TestEasyDriverSetDirection(t *testing.T) { gobottest.Assert(t, d.dir, int8(1)) } +func TestEasyDriverSetDirectionNoPin(t *testing.T) { + d := initEasyDriver() + d.dirPin = "" + err := d.SetDirection("cw") + gobottest.Refute(t, err, nil) +} + func TestEasyDriverSetSpeed(t *testing.T) { d := initEasyDriver() gobottest.Assert(t, d.rpm, uint(stepsPerRev/4)) // default speed of 720/4 @@ -105,6 +134,15 @@ func TestEasyDriverSleep(t *testing.T) { gobottest.Assert(t, d.IsMoving(), false) } +func TestEasyDriverSleepNoPin(t *testing.T) { + d := initEasyDriver() + d.sleepPin = "" + err := d.Sleep() + gobottest.Refute(t, err, nil) + err = d.Wake() + gobottest.Refute(t, err, nil) +} + func TestEasyDriverWake(t *testing.T) { // let's test basic functionality d := initEasyDriver() @@ -114,6 +152,24 @@ func TestEasyDriverWake(t *testing.T) { gobottest.Assert(t, d.IsSleeping(), false) } +func TestEasyDriverEnable(t *testing.T) { + // let's test basic functionality + d := initEasyDriver() + d.Disable() + gobottest.Assert(t, d.IsEnabled(), false) + d.Enable() + gobottest.Assert(t, d.IsEnabled(), true) +} + +func TestEasyDriverEnableNoPin(t *testing.T) { + d := initEasyDriver() + d.enPin = "" + err := d.Disable() + gobottest.Refute(t, err, nil) + err = d.Enable() + gobottest.Refute(t, err, nil) +} + func TestEasyDriverDisable(t *testing.T) { // let's test basic functionality d := initEasyDriver() @@ -128,11 +184,3 @@ func TestEasyDriverDisable(t *testing.T) { gobottest.Assert(t, d.IsMoving(), false) } -func TestEasyDriverEnable(t *testing.T) { - // let's test basic functionality - d := initEasyDriver() - d.Disable() - gobottest.Assert(t, d.IsEnabled(), false) - d.Enable() - gobottest.Assert(t, d.IsEnabled(), true) -} \ No newline at end of file From 693f0cde85780289f91a4703343398674747bd1a Mon Sep 17 00:00:00 2001 From: Silke Hofstra Date: Tue, 2 Oct 2018 15:58:16 +0200 Subject: [PATCH 24/47] tello: add direct vector access Signed-off-by: Silke Hofstra --- platforms/dji/tello/driver.go | 97 +++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/platforms/dji/tello/driver.go b/platforms/dji/tello/driver.go index 1d5d82b4..0bb82fea 100644 --- a/platforms/dji/tello/driver.go +++ b/platforms/dji/tello/driver.go @@ -420,6 +420,103 @@ func (d *Driver) Rate() (err error) { return } +// bound is a naive implementation that returns the smaller of x or y. +func bound(x, y float32) float32 { + if x < -y { + return -y + } + if x > y { + return y + } + return x +} + +// Vector returns the current motion vector. +// Values are from 0 to 1. +// x, y, z denote forward, side and vertical translation, +// and psi yaw (rotation around the z-axis). +func (d *Driver) Vector() (x, y, z, psi float32) { + return d.ry, d.rx, d.ly, d.lx +} + +// AddVector adds to the current motion vector. +// Pass values from 0 to 1. +// See Vector() for the frame of reference. +func (d *Driver) AddVector(x, y, z, psi float32) error { + d.cmdMutex.Lock() + defer d.cmdMutex.Unlock() + + d.ry = bound(d.ry+x, 1) + d.rx = bound(d.rx+y, 1) + d.ly = bound(d.ly+z, 1) + d.lx = bound(d.lx+psi, 1) + + return nil +} + +// SetVector sets the current motion vector. +// Pass values from 0 to 1. +// See Vector() for the frame of reference. +func (d *Driver) SetVector(x, y, z, psi float32) error { + d.cmdMutex.Lock() + defer d.cmdMutex.Unlock() + + d.ry = x + d.rx = y + d.ly = z + d.lx = psi + + return nil +} + +// SetX sets the x component of the current motion vector +// Pass values from 0 to 1. +// See Vector() for the frame of reference. +func (d *Driver) SetX(x float32) error { + d.cmdMutex.Lock() + defer d.cmdMutex.Unlock() + + d.ry = x + + return nil +} + +// SetY sets the y component of the current motion vector +// Pass values from 0 to 1. +// See Vector() for the frame of reference. +func (d *Driver) SetY(y float32) error { + d.cmdMutex.Lock() + defer d.cmdMutex.Unlock() + + d.rx = y + + return nil +} + +// SetZ sets the z component of the current motion vector +// Pass values from 0 to 1. +// See Vector() for the frame of reference. +func (d *Driver) SetZ(z float32) error { + d.cmdMutex.Lock() + defer d.cmdMutex.Unlock() + + d.ly = z + + return nil +} + +// SetPsi sets the psi component (yaw) of the current motion vector +// Pass values from 0 to 1. +// See Vector() for the frame of reference. +func (d *Driver) SetPsi(psi float32) error { + d.cmdMutex.Lock() + defer d.cmdMutex.Unlock() + + d.lx = psi + + return nil +} + // Up tells the drone to ascend. Pass in an int from 0-100. func (d *Driver) Up(val int) error { d.cmdMutex.Lock() From d7a05969c244067f0fb7bc8cf0070bd242c9125c Mon Sep 17 00:00:00 2001 From: Silke Hofstra Date: Thu, 4 Oct 2018 11:19:17 +0200 Subject: [PATCH 25/47] tello: update FlightData struct - Correct the name of EmSky, EmGround and GroundSpeed to Flying, OnGround and VerticalSpeed. - Remove FlySpeed, WifiDisturb and WifiStrength as these are not part of the data. - Add AirSpeed() and GroundSpeed() for calculating the airspeed and ground speed. Signed-off-by: Silke Hofstra --- platforms/dji/tello/driver.go | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/platforms/dji/tello/driver.go b/platforms/dji/tello/driver.go index 1d5d82b4..d06a313c 100644 --- a/platforms/dji/tello/driver.go +++ b/platforms/dji/tello/driver.go @@ -6,6 +6,7 @@ import ( "errors" "fmt" "io" + "math" "net" "strconv" "sync" @@ -144,19 +145,18 @@ type FlightData struct { DroneFlyTimeLeft int16 DroneHover bool EmOpen bool - EmSky bool - EmGround bool + Flying bool + OnGround bool EastSpeed int16 ElectricalMachineryState int16 FactoryMode bool FlyMode int8 - FlySpeed int16 FlyTime int16 FrontIn bool FrontLSC bool FrontOut bool GravityState bool - GroundSpeed int16 + VerticalSpeed int16 Height int16 ImuCalibrationState int8 ImuState bool @@ -166,10 +166,8 @@ type FlightData struct { PowerState bool PressureState bool SmartVideoExitMode int16 - TemperatureHeight bool + TemperatureHigh bool ThrowFlyTimer int8 - WifiDisturb int8 - WifiStrength int8 WindState bool } @@ -583,7 +581,7 @@ func (d *Driver) ParseFlightData(b []byte) (fd *FlightData, err error) { if err != nil { return } - err = binary.Read(buf, binary.LittleEndian, &fd.GroundSpeed) + err = binary.Read(buf, binary.LittleEndian, &fd.VerticalSpeed) if err != nil { return } @@ -625,8 +623,8 @@ func (d *Driver) ParseFlightData(b []byte) (fd *FlightData, err error) { if err != nil { return } - fd.EmSky = (data >> 0 & 0x1) == 1 - fd.EmGround = (data >> 1 & 0x1) == 1 + fd.Flying = (data >> 0 & 0x1) == 1 + fd.OnGround = (data >> 1 & 0x1) == 1 fd.EmOpen = (data >> 2 & 0x1) == 1 fd.DroneHover = (data >> 3 & 0x1) == 1 fd.OutageRecording = (data >> 4 & 0x1) == 1 @@ -665,7 +663,7 @@ func (d *Driver) ParseFlightData(b []byte) (fd *FlightData, err error) { if err != nil { return } - fd.TemperatureHeight = (data >> 0 & 0x1) == 1 + fd.TemperatureHigh = (data >> 0 & 0x1) == 1 return } @@ -847,3 +845,16 @@ func (d *Driver) connectionString() string { res := fmt.Sprintf("conn_req:%s", b) return res } + +func (f *FlightData) AirSpeed() float64 { + return math.Sqrt( + math.Pow(float64(f.NorthSpeed), 2) + + math.Pow(float64(f.EastSpeed), 2) + + math.Pow(float64(f.VerticalSpeed), 2)) +} + +func (f *FlightData) GroundSpeed() float64 { + return math.Sqrt( + math.Pow(float64(f.NorthSpeed), 2) + + math.Pow(float64(f.EastSpeed), 2)) +} From 935d4a826c6eb7ed0911676e9c1ec37838ee11d4 Mon Sep 17 00:00:00 2001 From: Kevin Lutzer Date: Mon, 8 Oct 2018 23:13:54 -0600 Subject: [PATCH 26/47] build out the ccs811 driver --- drivers/i2c/ccs811_driver.go | 349 ++++++++++++++++++++++++++++++ drivers/i2c/ccs811_driver_test.go | 256 ++++++++++++++++++++++ examples/raspi_ccs811.go | 76 +++++++ 3 files changed, 681 insertions(+) create mode 100644 drivers/i2c/ccs811_driver.go create mode 100644 drivers/i2c/ccs811_driver_test.go create mode 100644 examples/raspi_ccs811.go diff --git a/drivers/i2c/ccs811_driver.go b/drivers/i2c/ccs811_driver.go new file mode 100644 index 00000000..85150264 --- /dev/null +++ b/drivers/i2c/ccs811_driver.go @@ -0,0 +1,349 @@ +package i2c + +import ( + "fmt" + "math" + "time" + + "gobot.io/x/gobot" +) + +// CCS811DriveMode type +type CCS811DriveMode uint8 + +// Operating modes which dictate how often measurements are being made. If 0x00 is used as an operating mode, measurements will be disabled +const ( + CCS811DriveModeIdle CCS811DriveMode = 0x00 + CCS811DriveMode1Sec = 0x01 + CCS811DriveMode10Sec = 0x02 + CCS811DriveMode60Sec = 0x03 + CCS811DriveMode250MS = 0x04 +) + +const ( + + //DefaultAddress is the default I2C address for the ccs811 + ccs811DefaultAddress = 0x5A + + //Registers, all definitions have been taken from the datasheet + //Single byte read only register which indicates if a device is active, if new data is available or if an error occurred. + ccs811RegStatus = 0x00 + //This is Single byte register, which is used to enable sensor drive mode and interrupts. + ccs811RegMeasMode = 0x01 + //This multi-byte read only register contains the calculated eCO2 (ppm) and eTVOC (ppb) values followed by the STATUS register, ERROR_ID register and the RAW_DATA register. + ccs811RegAlgResultData = 0x02 + //Two byte read only register which contains the latest readings from the sensor. + ccs811RegRawData = 0x03 + //A multi-byte register that can be written with the current Humidity and Temperature values if known. + ccs811RegEnvData = 0x05 + //Register that holds the NTC value used for temperature calcualtions + ccs811RegNtc = 0x06 + //Asserting the SW_RESET will restart the CCS811 in Boot mode to enable new application firmware to be downloaded. + ccs811RegSwReset = 0xFF + //Single byte read only register which holds the HW ID which is 0x81 for this family of CCS81x devices. + ccs811RegHwID = 0x20 + //Single byte read only register that contains the hardware version. The value is 0x1X + ccs811RegHwVersion = 0x21 + //Two byte read only register which contain the version of the firmware bootloader stored in the CCS811 in the format Major.Minor.Trivial + ccs811RegFwBootVersion = 0x23 + //Two byte read only register which contain the version of the firmware application stored in the CCS811 in the format Major.Minor.Trivial + ccs811RegFwAppVersion = 0x24 + //To change the mode of the CCS811 from Boot mode to running the application, a single byte write of 0xF4 is required. + ccs811RegAppStart = 0xF4 + + // Constants + // The hardware ID code + ccs811HwIDCode = 0x81 +) + +var ( + // The sequence of bytes needed to do a software reset + ccs811SwResetSequence = []byte{0x11, 0xE5, 0x72, 0x8A} +) + +// CCS811Status represents the current status of the device defined by the ccs811RegStatus. +// The following definitions were taken from https://ams.com/documents/20143/36005/CCS811_DS000459_6-00.pdf/c7091525-c7e5-37ac-eedb-b6c6828b0dcf#page=15 +type CCS811Status struct { + //There is some sort of error on the i2c bus or there is an error with the internal sensor + HasError byte + //A new data sample is ready in ccs811RegAlgResultData + DataReady byte + //Valid application firmware loaded + AppValid byte + //Firmware is in application mode. CCS811 is ready to take sensor measurements + FwMode byte +} + +//NewCCS811Status returns a new instance of the package ccs811 status definiton +func NewCCS811Status(data uint8) *CCS811Status { + return &CCS811Status{ + HasError: data & 0x01, + DataReady: (data >> 3) & 0x01, + AppValid: (data >> 4) & 0x01, + FwMode: (data >> 7) & 0x01, + } +} + +//CCS811MeasMode represents the current measurement configuration. +//The following definitions were taken from the bit fields of the ccs811RegMeasMode defined in +//https://ams.com/documents/20143/36005/CCS811_DS000459_6-00.pdf/c7091525-c7e5-37ac-eedb-b6c6828b0dcf#page=16 +type CCS811MeasMode struct { + //If intThresh is 1 a data measurement will only be taken when the sensor value mets the threshold constraint. + //The threshold value is set in the threshold register (0x10) + intThresh uint8 + //If intDataRdy is 1, the nINT signal (pin 3 of the device) will be driven low when new data is avaliable. + intDataRdy uint8 + //driveMode represents the sampling rate of the sensor. If the value is 0, the measurement process is idle. + driveMode CCS811DriveMode +} + +//NewCCS811MeasMode returns a new instance of the package ccs811 measurement mode configuration. This represents the desired intial +//state of the measurement mode register. +func NewCCS811MeasMode() *CCS811MeasMode { + return &CCS811MeasMode{ + // Disable this by default as this library does not contain the functionality to use the internal interrupt feature. + intThresh: 0x00, + intDataRdy: 0x00, + driveMode: CCS811DriveMode1Sec, + } +} + +// GetMeasMode returns the measurement mode +func (mm *CCS811MeasMode) GetMeasMode() byte { + return (mm.intThresh << 2) | (mm.intDataRdy << 3) | uint8((mm.driveMode << 4)) +} + +//CCS811Driver is the Gobot driver for the CCS811 (air quality sensor) Adafruit breakout board +type CCS811Driver struct { + name string + connector Connector + connection Connection + measMode *CCS811MeasMode + ntcResistanceValue uint32 + Config +} + +//NewCCS811Driver creates a new driver for the CCS811 (air quality sensor) +func NewCCS811Driver(a Connector, options ...func(Config)) *CCS811Driver { + l := &CCS811Driver{ + name: gobot.DefaultName("CCS811"), + connector: a, + measMode: NewCCS811MeasMode(), + //Recommended resistance value is 100,000 + ntcResistanceValue: 100000, + Config: NewConfig(), + } + + for _, option := range options { + option(l) + } + + return l +} + +//WithCCS811MeasMode sets the sampling rate of the device +func WithCCS811MeasMode(mode CCS811DriveMode) func(Config) { + return func(c Config) { + d, _ := c.(*CCS811Driver) + d.measMode.driveMode = mode + } +} + +//WithCCS811NTCResistance sets reistor value used in the temperature calculations. +//This resistor must be placed between pin 4 and pin 8 of the chip +func WithCCS811NTCResistance(val uint32) func(Config) { + return func(c Config) { + d, _ := c.(*CCS811Driver) + d.ntcResistanceValue = val + } +} + +//Start initializes the sensor +func (d *CCS811Driver) Start() (err error) { + bus := d.GetBusOrDefault(d.connector.GetDefaultBus()) + address := d.GetAddressOrDefault(ccs811DefaultAddress) + + if d.connection, err = d.connector.GetConnection(address, bus); err != nil { + return err + } + + return d.initialize() +} + +//Name returns the Name for the Driver +func (d *CCS811Driver) Name() string { return d.name } + +//SetName sets the Name for the Driver +func (d *CCS811Driver) SetName(n string) { d.name = n } + +//Connection returns the connection for the Driver +func (d *CCS811Driver) Connection() gobot.Connection { return d.connector.(gobot.Connection) } + +//Halt returns true if devices is halted successfully +func (d *CCS811Driver) Halt() (err error) { return } + +//GetHardwareVersion returns the hardware version of the device in the form of 0x1X +func (d *CCS811Driver) GetHardwareVersion() (uint8, error) { + v, err := d.connection.ReadByteData(ccs811RegHwVersion) + if err != nil { + return 0, err + } + + return v, nil +} + +//GetFirmwareBootVersion returns the bootloader version +func (d *CCS811Driver) GetFirmwareBootVersion() (uint16, error) { + v, err := d.connection.ReadWordData(ccs811RegFwBootVersion) + if err != nil { + return 0, err + } + + return v, nil +} + +//GetFirmwareAppVersion returns the app code version +func (d *CCS811Driver) GetFirmwareAppVersion() (uint16, error) { + v, err := d.connection.ReadWordData(ccs811RegFwAppVersion) + if err != nil { + return 0, err + } + + return v, nil +} + +//GetStatus returns the current status of the device +func (d *CCS811Driver) GetStatus() (*CCS811Status, error) { + s, err := d.connection.ReadByteData(ccs811RegStatus) + if err != nil { + return nil, err + } + + cs := NewCCS811Status(s) + return cs, nil +} + +//GetTemperature returns the device temperature in celcius. +//If you do not have an NTC resistor installed, this function should not be called +func (d *CCS811Driver) GetTemperature() (float32, error) { + + buf, err := d.read(ccs811RegNtc, 4) + if err != nil { + return 0, err + } + + vref := ((uint16(buf[0]) << 8) | uint16(buf[1])) + vrntc := ((uint16(buf[2]) << 8) | uint16(buf[3])) + rntc := (float32(vrntc) * float32(d.ntcResistanceValue) / float32(vref)) + + ntcTemp := float32(math.Log(float64(rntc / 10000.0))) + ntcTemp /= 3380.0 + ntcTemp += 1.0 / (25 + 273.15) + ntcTemp = 1.0 / ntcTemp + ntcTemp -= 273.15 + + return ntcTemp, nil +} + +//GetGasData returns the data for the gas sensor. +//eco2 is returned in ppm and tvoc is returned in ppb +func (d *CCS811Driver) GetGasData() (uint16, uint16, error) { + + data, err := d.read(ccs811RegAlgResultData, 4) + if err != nil { + return 0, 0, err + } + + // Bit masks defined by https://ams.com/documents/20143/36005/CCS811_AN000369_2-00.pdf/25d0db9a-92b9-fa7f-362c-a7a4d1e292be#page=14 + eco2 := (uint16(data[0]) << 8) | uint16(data[1]) + tvoC := (uint16(data[2]) << 8) | uint16(data[3]) + + return eco2, tvoC, nil +} + +//HasData returns true if the device has not errored and temperature/gas data is avaliable +func (d *CCS811Driver) HasData() (bool, error) { + s, err := d.GetStatus() + if err != nil { + return false, err + } + + if !(s.DataReady == 0x01) || (s.HasError == 0x01) { + return false, nil + } + + return true, nil +} + +//EnableExternalInterrupt enables the external output hardware interrupt pin 3. +func (d *CCS811Driver) EnableExternalInterrupt() error { + d.measMode.intDataRdy = 1 + return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode()) +} + +//DisableExternalInterrupt disables the external output hardware interrupt pin 3. +func (d *CCS811Driver) DisableExternalInterrupt() error { + d.measMode.intDataRdy = 0 + return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode()) +} + +//updateMeasMode writes the current value of measMode to the measurement mode register. +func (d *CCS811Driver) updateMeasMode() error { + return d.connection.WriteByteData(ccs811RegMeasMode, d.measMode.GetMeasMode()) +} + +//ResetDevice does a software reset of the device. After this operation is done, +//the user must start the app code before the sensor can take any measurements +func (d *CCS811Driver) resetDevice() error { + return d.connection.WriteBlockData(ccs811RegSwReset, ccs811SwResetSequence) +} + +//startApp starts the app code in the device. This operation has to be done after a +//software reset to start taking sensor measurements. +func (d *CCS811Driver) startApp() error { + //Write without data is needed to start the app code + _, err := d.connection.Write([]byte{ccs811RegAppStart}) + return err +} + +func (d *CCS811Driver) initialize() error { + deviceID, err := d.connection.ReadByteData(ccs811RegHwID) + if err != nil { + return fmt.Errorf("Failed to get the device id from ccs811RegHwID with error: %s", err.Error()) + } + + // Verify that the connected device is the CCS811 sensor + if deviceID != ccs811HwIDCode { + return fmt.Errorf("The fetched device id %d is not the known id %d with error", deviceID, ccs811HwIDCode) + } + + if err := d.resetDevice(); err != nil { + return fmt.Errorf("Was not able to reset the device with error: %s", err.Error()) + } + + // Required sleep to allow device to switch states + time.Sleep(100 * time.Millisecond) + + if err := d.startApp(); err != nil { + return fmt.Errorf("Failed to start app code with error: %s", err.Error()) + } + + if err := d.updateMeasMode(); err != nil { + return fmt.Errorf("Failed to update the measMode register with error: %s", err.Error()) + } + + return nil +} + +// An implementation of the ReadBlockData i2c operation. This code was copied from the BMP280Driver code +func (d *CCS811Driver) read(reg byte, n int) ([]byte, error) { + if _, err := d.connection.Write([]byte{reg}); err != nil { + return nil, err + } + buf := make([]byte, n) + bytesRead, err := d.connection.Read(buf) + if bytesRead != n || err != nil { + return nil, err + } + return buf, nil +} diff --git a/drivers/i2c/ccs811_driver_test.go b/drivers/i2c/ccs811_driver_test.go new file mode 100644 index 00000000..57e912f5 --- /dev/null +++ b/drivers/i2c/ccs811_driver_test.go @@ -0,0 +1,256 @@ +package i2c + +import ( + "errors" + "testing" + + "gobot.io/x/gobot" + "gobot.io/x/gobot/gobottest" +) + +// The CCS811 Meets the Driver Definition +var _ gobot.Driver = (*CCS811Driver)(nil) + +// --------- HELPERS +func initTestCCS811Driver() (driver *CCS811Driver) { + driver, _ = initTestCCS811DriverWithStubbedAdaptor() + return +} + +func initTestCCS811DriverWithStubbedAdaptor() (*CCS811Driver, *i2cTestAdaptor) { + adaptor := newI2cTestAdaptor() + return NewCCS811Driver(adaptor), adaptor +} + +// --------- BASE TESTS +func TestNewCCS811Driver(t *testing.T) { + // Does it return a pointer to an instance of CCS811Driver? + var c interface{} = NewCCS811Driver(newI2cTestAdaptor()) + _, ok := c.(*CCS811Driver) + if !ok { + t.Errorf("NewCCS811Driver() should have returned a *CCS811Driver") + } +} + +func TestCCS811DriverSetName(t *testing.T) { + // Does it change the name of the driver + d := initTestCCS811Driver() + d.SetName("TESTME") + gobottest.Assert(t, d.Name(), "TESTME") +} + +func TestCCS811Connection(t *testing.T) { + // Does it create an instance of gobot.Connection + ccs811 := initTestCCS811Driver() + gobottest.Refute(t, ccs811.Connection(), nil) +} + +// // --------- CONFIG OVERIDE TESTS + +func TestCCS811DriverWithBus(t *testing.T) { + // Can it update the bus + d := NewCCS811Driver(newI2cTestAdaptor(), WithBus(2)) + gobottest.Assert(t, d.GetBusOrDefault(1), 2) +} + +func TestCCS811DriverWithAddress(t *testing.T) { + // Can it update the address + d := NewCCS811Driver(newI2cTestAdaptor(), WithAddress(0xFF)) + gobottest.Assert(t, d.GetAddressOrDefault(0x5a), 0xFF) +} + +func TestCCS811DriverWithCCS811MeasMode(t *testing.T) { + // Can it update the measurement mode + d := NewCCS811Driver(newI2cTestAdaptor(), WithCCS811MeasMode(CCS811DriveMode10Sec)) + gobottest.Assert(t, d.measMode.driveMode, CCS811DriveMode(CCS811DriveMode10Sec)) +} + +func TestCCS811DriverWithCCS811NTCResistance(t *testing.T) { + // Can it update the ntc resitor value used for temp calcuations + d := NewCCS811Driver(newI2cTestAdaptor(), WithCCS811NTCResistance(0xFF)) + gobottest.Assert(t, d.ntcResistanceValue, uint32(0xFF)) +} + +// // --------- DRIVER SPECIFIC TESTS + +func TestCCS811DriverGetGasData(t *testing.T) { + + cases := []struct { + readReturn func([]byte) (int, error) + eco2 uint16 + tvoc uint16 + err error + }{ + // Can it compute the gas data with ideal values taken from the bus + { + readReturn: func(b []byte) (int, error) { + copy(b, []byte{1, 156, 0, 86}) + return 4, nil + }, + eco2: 412, + tvoc: 86, + err: nil, + }, + // Can it compute the gas data with the max values possible taken from the bus + { + readReturn: func(b []byte) (int, error) { + copy(b, []byte{255, 255, 255, 255}) + return 4, nil + }, + eco2: 65535, + tvoc: 65535, + err: nil, + }, + // Does it return an error when the i2c operation fails + { + readReturn: func(b []byte) (int, error) { + copy(b, []byte{255, 255, 255, 255}) + return 4, errors.New("Error") + }, + eco2: 0, + tvoc: 0, + err: errors.New("Error"), + }, + } + + d, adaptor := initTestCCS811DriverWithStubbedAdaptor() + // Create stub function as it is needed by read submethod in driver code + adaptor.i2cWriteImpl = func([]byte) (int, error) { return 0, nil } + + d.Start() + for _, c := range cases { + adaptor.i2cReadImpl = c.readReturn + eco2, tvoc, err := d.GetGasData() + gobottest.Assert(t, eco2, c.eco2) + gobottest.Assert(t, tvoc, c.tvoc) + gobottest.Assert(t, err, c.err) + } + +} + +func TestCCS811DriverGetTemperature(t *testing.T) { + + cases := []struct { + readReturn func([]byte) (int, error) + temp float32 + err error + }{ + // Can it compute the temperature data with ideal values taken from the bus + { + readReturn: func(b []byte) (int, error) { + copy(b, []byte{10, 197, 0, 248}) + return 4, nil + }, + temp: 27.811005, + err: nil, + }, + // Can it compute the temperature data without bus values overflowing + { + readReturn: func(b []byte) (int, error) { + copy(b, []byte{129, 197, 10, 248}) + return 4, nil + }, + temp: 29.48822, + err: nil, + }, + // Can it compute a negative temperature + { + readReturn: func(b []byte) (int, error) { + copy(b, []byte{255, 255, 255, 255}) + return 4, nil + }, + temp: -25.334152, + err: nil, + }, + // Does it return an error if the i2c bus errors + { + readReturn: func(b []byte) (int, error) { + copy(b, []byte{129, 197, 0, 248}) + return 4, errors.New("Error") + }, + temp: 0, + err: errors.New("Error"), + }, + } + + d, adaptor := initTestCCS811DriverWithStubbedAdaptor() + // Create stub function as it is needed by read submethod in driver code + adaptor.i2cWriteImpl = func([]byte) (int, error) { return 0, nil } + + d.Start() + for _, c := range cases { + adaptor.i2cReadImpl = c.readReturn + temp, err := d.GetTemperature() + gobottest.Assert(t, temp, c.temp) + gobottest.Assert(t, err, c.err) + } + +} + +func TestCCS811DriverHasData(t *testing.T) { + + cases := []struct { + readReturn func([]byte) (int, error) + result bool + err error + }{ + // Does it return true for HasError = 0 and DataRdy = 1 + { + readReturn: func(b []byte) (int, error) { + copy(b, []byte{0x08}) + return 1, nil + }, + result: true, + err: nil, + }, + // Does it return false for HasError = 1 and DataRdy = 1 + { + readReturn: func(b []byte) (int, error) { + copy(b, []byte{0x09}) + return 1, nil + }, + result: false, + err: nil, + }, + // Does it return false for HasError = 1 and DataRdy = 0 + { + readReturn: func(b []byte) (int, error) { + copy(b, []byte{0x01}) + return 1, nil + }, + result: false, + err: nil, + }, + // Does it return false for HasError = 0 and DataRdy = 0 + { + readReturn: func(b []byte) (int, error) { + copy(b, []byte{0x00}) + return 1, nil + }, + result: false, + err: nil, + }, + // Does it return an error when the i2c read operation fails + { + readReturn: func(b []byte) (int, error) { + copy(b, []byte{0x00}) + return 1, errors.New("Error") + }, + result: false, + err: errors.New("Error"), + }, + } + + d, adaptor := initTestCCS811DriverWithStubbedAdaptor() + // Create stub function as it is needed by read submethod in driver code + adaptor.i2cWriteImpl = func([]byte) (int, error) { return 0, nil } + + d.Start() + for _, c := range cases { + adaptor.i2cReadImpl = c.readReturn + result, err := d.HasData() + gobottest.Assert(t, result, c.result) + gobottest.Assert(t, err, c.err) + } + +} diff --git a/examples/raspi_ccs811.go b/examples/raspi_ccs811.go new file mode 100644 index 00000000..c6ce5ba3 --- /dev/null +++ b/examples/raspi_ccs811.go @@ -0,0 +1,76 @@ +// +build example +// +// Do not build by default. + +package main + +import ( + "fmt" + "time" + + "gobot.io/x/gobot" + "gobot.io/x/gobot/drivers/i2c" + "gobot.io/x/gobot/platforms/raspi" +) + +func CCS811BootData(a *i2c.CCS811Driver) { + v, err := a.GetHardwareVersion() + if err != nil { + fmt.Printf(err.Error()) + } + + fmt.Printf("Hardare Version %#x\n", v) + + d, err := a.GetFirmwareBootVersion() + if err != nil { + fmt.Printf(err.Error()) + } + + fmt.Printf("Boot Version %#x\n", d) + + d, err = a.GetFirmwareAppVersion() + if err != nil { + fmt.Printf(err.Error()) + } + + fmt.Printf("App Version %#x\n\n", d) +} + +func main() { + r := raspi.NewAdaptor() + ccs811Driver := i2c.NewCCS811Driver(r) + + work := func() { + CCS811BootData(ccs811Driver) + gobot.Every(1*time.Second, func() { + s, err := ccs811Driver.GetStatus() + if err != nil { + fmt.Printf("Error fetching data from the status register: %+v\n", err.Error()) + } + fmt.Printf("Status %+v \n", s) + + hd, err := ccs811Driver.HasData() + if err != nil { + fmt.Printf("Error fetching data from the status register: %+v\n", err.Error()) + } + + if hd { + ec02, tv0C, _ := ccs811Driver.GetGasData() + fmt.Printf("Gas Data - eco2: %+v, tvoc: %+v \n", ec02, tv0C) + + temp, _ := ccs811Driver.GetTemperature() + fmt.Printf("Temperature %+v \n\n", temp) + } else { + fmt.Println("New data is not avaliable\n") + } + }) + } + + robot := gobot.NewRobot("adaFruitBot", + []gobot.Connection{r}, + []gobot.Device{ccs811Driver}, + work, + ) + + robot.Start() +} From e23c01dd98cf818bc465bdd332c2216a442d7192 Mon Sep 17 00:00:00 2001 From: eleniums Date: Mon, 8 Oct 2018 23:16:23 -0700 Subject: [PATCH 27/47] Check for error immediately and skip publish if error occurred Signed-off-by: eleniums --- platforms/dji/tello/driver.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/platforms/dji/tello/driver.go b/platforms/dji/tello/driver.go index 1d5d82b4..9eab69f3 100644 --- a/platforms/dji/tello/driver.go +++ b/platforms/dji/tello/driver.go @@ -816,11 +816,12 @@ func (d *Driver) processVideo() error { for { buf := make([]byte, 2048) n, _, err := d.videoConn.ReadFromUDP(buf) - d.Publish(d.Event(VideoFrameEvent), buf[2:n]) - if err != nil { fmt.Println("Error: ", err) + continue } + + d.Publish(d.Event(VideoFrameEvent), buf[2:n]) } }() From 64456b7d933b180500fe90785648eb66cc68a496 Mon Sep 17 00:00:00 2001 From: eleniums Date: Fri, 12 Oct 2018 09:16:45 -0700 Subject: [PATCH 28/47] Change fps to 60 This allows mplayer to keep up with the VideoFrameEvents. --- examples/tello_video.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/tello_video.go b/examples/tello_video.go index b30492c5..3d2bf15d 100644 --- a/examples/tello_video.go +++ b/examples/tello_video.go @@ -25,7 +25,7 @@ import ( func main() { drone := tello.NewDriver("8890") - mplayer := exec.Command("mplayer", "-fps", "25", "-") + mplayer := exec.Command("mplayer", "-fps", "60", "-") mplayerIn, _ := mplayer.StdinPipe() if err := mplayer.Start(); err != nil { fmt.Println(err) From a4ed1284f913bc6ef329c913405bddd045b130ad Mon Sep 17 00:00:00 2001 From: Harald Nordgren Date: Sat, 20 Oct 2018 12:07:53 +0200 Subject: [PATCH 29/47] Bump Travis versions --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3538c3b6..7b4044d3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,8 +3,9 @@ sudo: required dist: trusty go_import_path: gobot.io/x/gobot go: - - 1.9.7 - - 1.10.3 + - 1.9.x + - 1.10.x + - 1.11.x - tip matrix: allow_failures: From 0ea9edeac33301537a5220d435833658916da368 Mon Sep 17 00:00:00 2001 From: Trevor Rosen Date: Mon, 17 Dec 2018 11:06:34 -0600 Subject: [PATCH 30/47] Allow setting QoS on MTT adaptor Fixes #644 --- platforms/mqtt/mqtt_adaptor.go | 8 ++++++-- platforms/mqtt/mqtt_adaptor_test.go | 6 ++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/platforms/mqtt/mqtt_adaptor.go b/platforms/mqtt/mqtt_adaptor.go index d00d01a7..f7285346 100644 --- a/platforms/mqtt/mqtt_adaptor.go +++ b/platforms/mqtt/mqtt_adaptor.go @@ -28,6 +28,7 @@ type Adaptor struct { autoReconnect bool cleanSession bool client paho.Client + qos int } // NewAdaptor creates a new mqtt adaptor with specified host and client id @@ -86,6 +87,9 @@ func (a *Adaptor) SetUseSSL(val bool) { a.useSSL = val } // ServerCert returns the MQTT server SSL cert file func (a *Adaptor) ServerCert() string { return a.serverCert } +// SetQoS sets the QoS value passed into the MTT client on Publish/Subscribe events +func (a *Adaptor) SetQoS(qos int) { a.qos = qos } + // SetServerCert sets the MQTT server SSL cert file func (a *Adaptor) SetServerCert(val string) { a.serverCert = val } @@ -130,7 +134,7 @@ func (a *Adaptor) Publish(topic string, message []byte) bool { if a.client == nil { return false } - a.client.Publish(topic, 0, false, message) + a.client.Publish(topic, byte(a.qos), false, message) return true } @@ -139,7 +143,7 @@ func (a *Adaptor) On(event string, f func(msg Message)) bool { if a.client == nil { return false } - a.client.Subscribe(event, 0, func(client paho.Client, msg paho.Message) { + a.client.Subscribe(event, byte(a.qos), func(client paho.Client, msg paho.Message) { f(msg) }) return true diff --git a/platforms/mqtt/mqtt_adaptor_test.go b/platforms/mqtt/mqtt_adaptor_test.go index b764a460..2adc1987 100644 --- a/platforms/mqtt/mqtt_adaptor_test.go +++ b/platforms/mqtt/mqtt_adaptor_test.go @@ -125,3 +125,9 @@ func TestMqttAdaptorOnWhenConnected(t *testing.T) { fmt.Println("hola") }), true) } + +func TestMqttAdaptorQoS(t *testing.T) { + a := initTestMqttAdaptor() + a.SetQoS(1) + gobottest.Assert(t, 1, a.qos) +} From 6e9707ad6f6b69802ff11d138c7503e3dc47d781 Mon Sep 17 00:00:00 2001 From: Sam Kass Date: Wed, 26 Dec 2018 00:09:09 +0100 Subject: [PATCH 31/47] Update gobot leap platform to support Leap Motion API v6 --- platforms/leap/leap_motion_adaptor.go | 2 +- platforms/leap/leap_motion_driver.go | 29 +- platforms/leap/leap_motion_driver_test.go | 8 +- platforms/leap/parser.go | 55 +- .../leap/test/support/example_frame.json | 869 ++++++++++++++---- 5 files changed, 758 insertions(+), 205 deletions(-) diff --git a/platforms/leap/leap_motion_adaptor.go b/platforms/leap/leap_motion_adaptor.go index 37882a10..98f561f6 100644 --- a/platforms/leap/leap_motion_adaptor.go +++ b/platforms/leap/leap_motion_adaptor.go @@ -23,7 +23,7 @@ func NewAdaptor(port string) *Adaptor { name: gobot.DefaultName("LeapMotion"), port: port, connect: func(host string) (io.ReadWriteCloser, error) { - return websocket.Dial("ws://"+host+"/v3.json", "", "http://"+host) + return websocket.Dial("ws://"+host+"/v6.json", "", "http://"+host) }, } } diff --git a/platforms/leap/leap_motion_driver.go b/platforms/leap/leap_motion_driver.go index f3cc14fa..ce4f1958 100644 --- a/platforms/leap/leap_motion_driver.go +++ b/platforms/leap/leap_motion_driver.go @@ -30,7 +30,7 @@ type Driver struct { // Adds the following events: // "message" - Gets triggered when receiving a message from leap motion // "hand" - Gets triggered per-message when leap motion detects a hand -// "gesture" - Gets triggered per-message when leap motion detects a hand +// "gesture" - Gets triggered per-message when leap motion detects a gesture func NewDriver(a *Adaptor) *Driver { l := &Driver{ name: gobot.DefaultName("LeapMotion"), @@ -61,6 +61,20 @@ func (l *Driver) adaptor() *Adaptor { return l.Connection().(*Adaptor) } +func enableFeature(l *Driver, feature string) (err error) { + command := map[string]bool{feature: true} + b, e := json.Marshal(command) + if e != nil { + return e + } + _, e = l.adaptor().ws.Write(b) + if e != nil { + return e + } + + return nil +} + // Start inits leap motion driver by enabling gestures // and listening from incoming messages. // @@ -69,14 +83,13 @@ func (l *Driver) adaptor() *Adaptor { // "hand" - Emits Hand when detected in message from Leap. // "gesture" - Emits Gesture when detected in message from Leap. func (l *Driver) Start() (err error) { - enableGestures := map[string]bool{"enableGestures": true} - b, e := json.Marshal(enableGestures) - if e != nil { - return e + err = enableFeature(l,"enableGestures") + if err != nil { + return err } - _, e = l.adaptor().ws.Write(b) - if e != nil { - return e + err = enableFeature(l,"background") + if err != nil { + return err } go func() { diff --git a/platforms/leap/leap_motion_driver_test.go b/platforms/leap/leap_motion_driver_test.go index 8d444c03..2ac83609 100644 --- a/platforms/leap/leap_motion_driver_test.go +++ b/platforms/leap/leap_motion_driver_test.go @@ -89,8 +89,8 @@ func TestLeapMotionDriverParser(t *testing.T) { t.Errorf("ParseFrame incorrectly parsed frame") } - gobottest.Assert(t, parsedFrame.Timestamp, uint64(4729292670)) - gobottest.Assert(t, parsedFrame.Hands[0].X(), 117.546) - gobottest.Assert(t, parsedFrame.Hands[0].Y(), 236.007) - gobottest.Assert(t, parsedFrame.Hands[0].Z(), 76.3394) + gobottest.Assert(t, parsedFrame.Timestamp, uint64(134211791358)) + gobottest.Assert(t, parsedFrame.Hands[0].X(), 247.410) + gobottest.Assert(t, parsedFrame.Hands[0].Y(), 275.868) + gobottest.Assert(t, parsedFrame.Hands[0].Z(), 132.843) } diff --git a/platforms/leap/parser.go b/platforms/leap/parser.go index 296da7b2..56f8cc53 100644 --- a/platforms/leap/parser.go +++ b/platforms/leap/parser.go @@ -4,27 +4,37 @@ import ( "encoding/json" ) -// Gesture is a Leap Motion gesture tht has been detected +// Gesture is a Leap Motion gesture that has been detected type Gesture struct { + Center []float64 `json:"center"` Direction []float64 `json:"direction"` Duration int `json:"duration"` - Hands []Hand `json:"hands"` + HandIDs []int `json:"handIds"` ID int `json:"id"` - Pointables []Pointable `json:"pointables"` + Normal []float64 `json:"normal"` + PointableIDs []int `json:"pointableIds"` Position []float64 `json:"position"` + Progress float64 `json:"progress"` + Radius float64 `json:"radius"` Speed float64 `json:"speed"` StartPosition []float64 `json:"StartPosition"` State string `json:"state"` Type string `json:"type"` } -// Hand is a Leap Motion hand tht has been detected +// Hand is a Leap Motion hand that has been detected type Hand struct { + ArmBasis [][]float64 `json:"armBasis"` + ArmWidth float64 `json:"armWidth"` + Confidence float64 `json:"confidence"` Direction []float64 `json:"direction"` + Elbow []float64 `json:"elbow"` + GrabStrength float64 `json:"grabStrength"` ID int `json:"id"` PalmNormal []float64 `json:"palmNormal"` PalmPosition []float64 `json:"PalmPosition"` PalmVelocity []float64 `json:"PalmVelocity"` + PinchStrength float64 `json:"PinchStrength"` R [][]float64 `json:"r"` S float64 `json:"s"` SphereCenter []float64 `json:"sphereCenter"` @@ -32,26 +42,37 @@ type Hand struct { StabilizedPalmPosition []float64 `json:"stabilizedPalmPosition"` T []float64 `json:"t"` TimeVisible float64 `json:"TimeVisible"` + Type string `json:"type"` + Wrist []float64 `json:"wrist"` } -// Pointable is a Leap Motion pointing motion tht has been detected +// Pointable is a Leap Motion pointing motion that has been detected type Pointable struct { - Direction []float64 `json:"direction"` - HandID int `json:"handId"` - ID int `json:"id"` - Length float64 `json:"length"` - StabilizedTipPosition []float64 `json:"stabilizedTipPosition"` - TimeVisible float64 `json:"timeVisible"` - TipPosition []float64 `json:"tipPosition"` - TipVelocity []float64 `json:"tipVelocity"` - Tool bool `json:"tool"` - TouchDistance float64 `json:"touchDistance"` - TouchZone string `json:"touchZone"` + Bases [][]float64 `json:"bases"` + BTipPosition []float64 `json:"btipPosition"` + CarpPosition []float64 `json:"carpPosition"` + DipPosition []float64 `json:"dipPosition"` + Direction []float64 `json:"direction"` + Extended bool `json:"extended"` + HandID int `json:"handId"` + ID int `json:"id"` + Length float64 `json:"length"` + MCPPosition []float64 `json:"mcpPosition"` + PIPPosition []float64 `json:"pipPosition"` + StabilizedTipPosition []float64 `json:"stabilizedTipPosition"` + TimeVisible float64 `json:"timeVisible"` + TipPosition []float64 `json:"tipPosition"` + TipVelocity []float64 `json:"tipVelocity"` + Tool bool `json:"tool"` + TouchDistance float64 `json:"touchDistance"` + TouchZone string `json:"touchZone"` + Type int `json:"type"` + Width float64 `json:"width"` } // InteractionBox is the area within which the gestural interaction has been detected type InteractionBox struct { - Center []int `json:"center"` + Center []float64 `json:"center"` Size []float64 `json:"size"` } diff --git a/platforms/leap/test/support/example_frame.json b/platforms/leap/test/support/example_frame.json index 1509d569..6ef648fa 100644 --- a/platforms/leap/test/support/example_frame.json +++ b/platforms/leap/test/support/example_frame.json @@ -1,277 +1,796 @@ { - "currentFrameRate": 115.473, + "currentFrameRate": 110.583, + "devices": [], "gestures": [ { "direction": [ - -0.647384, - 0.750476, - -0.132964 + 0.721478, + -0.510801, + 0.467495 ], - "duration": 0, + "duration": 108349, "handIds": [ - 57 + 129 ], - "id": 72, + "id": 1, "pointableIds": [ - 14 + 1290 ], "position": [ - 117.665, - 313.471, - 27.2095 + 228.679, + 214.101, + 101.574 ], - "speed": 1050.66, - "startPosition": [ - 195.438, - 223.313, - 43.183 - ], - "state": "start", - "type": "swipe" + "progress": 1.00000, + "state": "stop", + "type": "keyTap" } ], "hands": [ { - "direction": [ - 0.772435, - 0.520335, - -0.364136 - ], - "id": 57, - "palmNormal": [ - -0.0100593, - -0.563263, - -0.826217 - ], - "palmPosition": [ - 117.546, - 236.007, - 76.3394 - ], - "palmVelocity": [ - -866.196, - -100.749, - 275.692 - ], - "r": [ + "armBasis": [ [ - 0.999844, - 0.0142022, - 0.0105289 + 0.642307, + 0.418027, + 0.642414 ], [ - -0.0141201, - 0.99987, - -0.00783186 + -0.760890, + 0.448533, + 0.468898 ], [ - -0.0106388, - 0.00768197, - 0.999914 + -0.0921319, + -0.789982, + 0.606168 ] ], - "s": 0.992511, - "sphereCenter": [ - 156.775, - 227.378, - 48.3453 + "armWidth": 62.2635, + "confidence": 0.152371, + "direction": [ + 0.221251, + -0.0617914, + -0.973257 ], - "sphereRadius": 75.3216, + "elbow": [ + 212.001, + 79.2408, + 343.710 + ], + "grabStrength": 0.00000, + "id": 129, + "palmNormal": [ + 0.813532, + -0.538649, + 0.219139 + ], + "palmPosition": [ + 247.410, + 275.868, + 132.843 + ], + "palmVelocity": [ + -0.713996, + -23.6569, + -13.8691 + ], + "palmWidth": 88.9102, + "pinchStrength": 0.00000, + "r": [ + [ + 0.192921, + 0.960078, + -0.202565 + ], + [ + -0.745728, + 0.00929180, + -0.666186 + ], + [ + -0.637708, + 0.279579, + 0.717750 + ] + ], + "s": 1.30419, + "sphereCenter": [ + 287.160, + 297.142, + 100.094 + ], + "sphereRadius": 67.0665, "stabilizedPalmPosition": [ - 119.009, - 236.071, - 75.951 + 247.480, + 276.296, + 133.123 ], "t": [ - -38.0468, - 28.2341, - -21.3291 + 61.7977, + 116.515, + 56.4642 ], - "timeVisible": 0.051952 + "timeVisible": 1.87085, + "type": "right", + "wrist": [ + 236.308, + 287.660, + 183.786 + ] } ], - "id": 99943, + "id": 641123, "interactionBox": { "center": [ - 0, - 200, - 0 + 0.00000, + 189.367, + 0.00000 ], "size": [ - 221.418, - 221.418, - 154.742 + 222.740, + 222.740, + 139.896 ] }, "pointables": [ { + "bases": [ + [ + [ + -0.468069, + 0.807844, + -0.358190 + ], + [ + -0.882976, + -0.411231, + 0.226369 + ], + [ + 0.0355723, + 0.422230, + 0.905791 + ] + ], + [ + [ + -0.466451, + 0.822774, + -0.324757 + ], + [ + -0.870335, + -0.361355, + 0.334573 + ], + [ + 0.157925, + 0.438709, + 0.884643 + ] + ], + [ + [ + -0.466451, + 0.822774, + -0.324757 + ], + [ + -0.884402, + -0.440445, + 0.154403 + ], + [ + -0.0159988, + 0.359238, + 0.933109 + ] + ], + [ + [ + -0.466451, + 0.822774, + -0.324757 + ], + [ + -0.817784, + -0.261199, + 0.512840 + ], + [ + 0.337125, + 0.504796, + 0.794687 + ] + ] + ], + "btipPosition": [ + 214.293, + 213.865, + 95.0224 + ], + "carpPosition": [ + 229.172, + 258.776, + 187.433 + ], + "dipPosition": [ + 222.002, + 225.409, + 113.196 + ], "direction": [ - 0.54044, - 0.174084, - -0.823176 + 0.0159988, + -0.359238, + -0.933109 + ], + "extended": true, + "handId": 129, + "id": 1290, + "length": 50.9251, + "mcpPosition": [ + 229.172, + 258.776, + 187.433 + ], + "pipPosition": [ + 221.469, + 237.377, + 144.284 ], - "handId": 57, - "id": 1, - "length": 48.393, "stabilizedTipPosition": [ - 194.714, - 291.812, - 20.6219 + 220.428, + 215.841, + 99.4813 ], - "timeVisible": 0.13873, + "timeVisible": 1.87085, "tipPosition": [ - 194.714, - 291.812, - 20.6219 + 212.427, + 218.656, + 99.7341 ], "tipVelocity": [ - -716.414, - 686.468, - -427.914 + -324.525, + 55.5317, + -41.1663 ], "tool": false, - "touchDistance": 0.333333, - "touchZone": "hovering" + "touchDistance": 0.341002, + "touchZone": "hovering", + "type": 0, + "width": 19.7871 }, { + "bases": [ + [ + [ + 0.654255, + 0.750454, + 0.0936476 + ], + [ + -0.683587, + 0.639792, + -0.351248 + ], + [ + -0.323510, + 0.165789, + 0.931588 + ] + ], + [ + [ + 0.641810, + 0.756085, + 0.128125 + ], + [ + -0.742571, + 0.654463, + -0.142363 + ], + [ + -0.191492, + -0.00377221, + 0.981487 + ] + ], + [ + [ + 0.641810, + 0.756085, + 0.128125 + ], + [ + -0.726482, + 0.652969, + -0.214139 + ], + [ + -0.245569, + 0.0443555, + 0.968364 + ] + ], + [ + [ + 0.641810, + 0.756085, + 0.128125 + ], + [ + -0.709564, + 0.648876, + -0.274734 + ], + [ + -0.290860, + 0.0854138, + 0.952946 + ] + ] + ], + "btipPosition": [ + 269.006, + 271.529, + 17.2009 + ], + "carpPosition": [ + 225.768, + 285.455, + 171.343 + ], + "dipPosition": [ + 263.665, + 273.097, + 34.6993 + ], "direction": [ - 0.655715, - 0.423222, - -0.625237 + 0.245569, + -0.0443555, + -0.968364 + ], + "extended": true, + "handId": 129, + "id": 1292, + "length": 65.4748, + "mcpPosition": [ + 247.823, + 274.152, + 107.833 + ], + "pipPosition": [ + 256.842, + 274.330, + 61.6066 ], - "handId": 57, - "id": 69, - "length": 43.4594, "stabilizedTipPosition": [ - 166.231, - 308.631, - 21.1697 + 268.409, + 272.318, + 21.5696 ], - "timeVisible": 0.156012, + "timeVisible": 1.87085, "tipPosition": [ - 166.231, - 308.631, - 21.1697 + 268.718, + 271.794, + 21.5136 ], "tipVelocity": [ - -817.678, - 511.988, - -496.456 + -13.9732, + 6.53920, + -19.5770 ], "tool": false, - "touchDistance": 0.333333, - "touchZone": "hovering" + "touchDistance": 0.332816, + "touchZone": "hovering", + "type": 2, + "width": 18.5630 }, { + "bases": [ + [ + [ + 0.649174, + 0.716291, + 0.255928 + ], + [ + -0.645061, + 0.696732, + -0.313783 + ], + [ + -0.403074, + 0.0386109, + 0.914353 + ] + ], + [ + [ + 0.610655, + 0.716881, + 0.336426 + ], + [ + -0.767227, + 0.640801, + 0.0271437 + ], + [ + -0.196123, + -0.274690, + 0.941319 + ] + ], + [ + [ + 0.610655, + 0.716881, + 0.336426 + ], + [ + -0.728617, + 0.675044, + -0.115904 + ], + [ + -0.310191, + -0.174348, + 0.934550 + ] + ], + [ + [ + 0.610655, + 0.716881, + 0.336426 + ], + [ + -0.679596, + 0.692496, + -0.242072 + ], + [ + -0.406510, + -0.0808113, + 0.910065 + ] + ] + ], + "btipPosition": [ + 282.293, + 310.673, + 32.4311 + ], + "carpPosition": [ + 233.241, + 294.849, + 171.406 + ], + "dipPosition": [ + 274.872, + 309.198, + 49.0461 + ], "direction": [ - 0.593251, - 0.566682, - -0.571773 + 0.310191, + 0.174348, + -0.934550 + ], + "extended": true, + "handId": 129, + "id": 1293, + "length": 62.9558, + "mcpPosition": [ + 257.913, + 292.486, + 115.440 + ], + "pipPosition": [ + 266.475, + 304.478, + 74.3433 ], - "handId": 57, - "id": 14, - "length": 47.3576, "stabilizedTipPosition": [ - 119.328, - 312.537, - 27.7416 + 281.546, + 310.434, + 36.7157 ], - "timeVisible": 0.164757, + "timeVisible": 1.87085, "tipPosition": [ - 117.665, - 313.471, - 27.2095 + 281.024, + 309.986, + 36.2968 ], "tipVelocity": [ - -779.297, - 651.055, - -269.665 + -49.1356, + 23.1718, + -14.1464 ], "tool": false, - "touchDistance": 0.333333, - "touchZone": "hovering" + "touchDistance": 0.332567, + "touchZone": "hovering", + "type": 3, + "width": 17.6638 }, { + "bases": [ + [ + [ + 0.695759, + 0.590913, + 0.408339 + ], + [ + -0.569298, + 0.800316, + -0.188135 + ], + [ + -0.437971, + -0.101569, + 0.893233 + ] + ], + [ + [ + 0.555629, + 0.543209, + 0.629445 + ], + [ + -0.762608, + 0.634559, + 0.125553 + ], + [ + -0.331219, + -0.549780, + 0.766835 + ] + ], + [ + [ + 0.555629, + 0.543209, + 0.629445 + ], + [ + -0.707544, + 0.706513, + 0.0148494 + ], + [ + -0.436645, + -0.453610, + 0.776903 + ] + ], + [ + [ + 0.555629, + 0.543209, + 0.629445 + ], + [ + -0.645129, + 0.759246, + -0.0857538 + ], + [ + -0.524486, + -0.358426, + 0.772299 + ] + ] + ], + "btipPosition": [ + 298.360, + 340.270, + 71.0701 + ], + "carpPosition": [ + 244.922, + 300.813, + 176.031 + ], + "dipPosition": [ + 289.526, + 334.233, + 84.0778 + ], "direction": [ - 0.292649, - -0.45091, - -0.84323 + 0.436645, + 0.453610, + -0.776904 + ], + "extended": true, + "handId": 129, + "id": 1294, + "length": 49.3562, + "mcpPosition": [ + 269.737, + 306.568, + 125.421 + ], + "pipPosition": [ + 281.181, + 325.564, + 98.9257 ], - "handId": 57, - "id": 29, - "length": 57.4251, "stabilizedTipPosition": [ - 223.535, - 239.849, - 37.7847 + 297.824, + 337.184, + 73.7178 ], - "timeVisible": 0.086657, + "timeVisible": 1.87085, "tipPosition": [ - 223.535, - 239.849, - 37.7847 + 295.463, + 338.639, + 73.5436 ], "tipVelocity": [ - -929.251, - 390.064, - -506.855 + -67.9258, + 44.5899, + -9.70583 ], "tool": false, - "touchDistance": 0.333333, - "touchZone": "hovering" + "touchDistance": 0.332281, + "touchZone": "hovering", + "type": 4, + "width": 15.6904 }, { + "bases": [ + [ + [ + 0.567299, + 0.817958, + -0.0954824 + ], + [ + -0.785572, + 0.502726, + -0.360755 + ], + [ + -0.247081, + 0.279664, + 0.927760 + ] + ], + [ + [ + 0.556741, + 0.828691, + -0.0575341 + ], + [ + -0.461401, + 0.250903, + -0.850974 + ], + [ + -0.690760, + 0.500319, + 0.522046 + ] + ], + [ + [ + 0.556741, + 0.828691, + -0.0575341 + ], + [ + 0.184936, + -0.191172, + -0.963977 + ], + [ + -0.809838, + 0.526046, + -0.259688 + ] + ], + [ + [ + 0.556741, + 0.828691, + -0.0575341 + ], + [ + 0.450238, + -0.359238, + -0.817456 + ], + [ + -0.698087, + 0.429207, + -0.573111 + ] + ] + ], + "btipPosition": [ + 297.722, + 214.346, + 100.704 + ], + "carpPosition": [ + 220.180, + 275.044, + 173.613 + ], + "dipPosition": [ + 286.067, + 221.511, + 91.1361 + ], "direction": [ - 0.887561, - 0.160925, - 0.43167 + 0.809838, + -0.526046, + 0.259688 + ], + "extended": false, + "handId": 129, + "id": 1291, + "length": 57.4633, + "mcpPosition": [ + 237.942, + 254.939, + 106.918 + ], + "pipPosition": [ + 266.941, + 233.936, + 85.0028 ], - "handId": -1, - "id": 83, - "length": 64.4149, "stabilizedTipPosition": [ - 1.8725087977383e-321, - 2.1433800000305e-314, - 2.1681499999593e-314 + 294.484, + 215.800, + 98.6516 ], - "timeVisible": 0.034634, + "timeVisible": 1.87085, "tipPosition": [ - 196.31, - 166.58, - 102.577 + 294.960, + 216.076, + 98.9796 ], "tipVelocity": [ - -478.099, - 493.551, - -417.63 + 77.7159, + 38.2101, + -21.6539 ], "tool": false, - "touchDistance": 0.0166667, - "touchZone": "none" + "touchDistance": 0.340420, + "touchZone": "none", + "type": 1, + "width": 18.9007 } ], "r": [ [ - -0.973344, - 0.0737047, - 0.217182 + 0.192921, + 0.960078, + -0.202565 ], [ - -0.0241526, - 0.908749, - -0.416645 + -0.745728, + 0.00929180, + -0.666186 ], [ - -0.228073, - -0.410784, - -0.882745 + -0.637708, + 0.279579, + 0.717750 ] ], - "s": -34.3848, + "s": 1.30419, "t": [ - -10091.8, - -24629.7, - 1011.04 + 61.7977, + 116.515, + 56.4642 ], - "timestamp": 4729292670 -} + "timestamp": 134211791358 +} \ No newline at end of file From b8437026f5336c8e3849c92c79279e3c65b7419c Mon Sep 17 00:00:00 2001 From: Sam Kass Date: Fri, 28 Dec 2018 02:53:13 +0100 Subject: [PATCH 32/47] Parser error in Pointable.Bases: Write test and fix --- platforms/leap/leap_motion_driver_test.go | 10 ++++++++++ platforms/leap/parser.go | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/platforms/leap/leap_motion_driver_test.go b/platforms/leap/leap_motion_driver_test.go index 2ac83609..6f42d372 100644 --- a/platforms/leap/leap_motion_driver_test.go +++ b/platforms/leap/leap_motion_driver_test.go @@ -93,4 +93,14 @@ func TestLeapMotionDriverParser(t *testing.T) { gobottest.Assert(t, parsedFrame.Hands[0].X(), 247.410) gobottest.Assert(t, parsedFrame.Hands[0].Y(), 275.868) gobottest.Assert(t, parsedFrame.Hands[0].Z(), 132.843) + + gobottest.Assert(t, parsedFrame.Pointables[0].BTipPosition[0], 214.293) + gobottest.Assert(t, parsedFrame.Pointables[0].BTipPosition[1], 213.865) + gobottest.Assert(t, parsedFrame.Pointables[0].BTipPosition[2], 95.0224) + + gobottest.Assert(t, parsedFrame.Pointables[0].Bases[0][0][0], -0.468069) + gobottest.Assert(t, parsedFrame.Pointables[0].Bases[0][0][1], 0.807844) + gobottest.Assert(t, parsedFrame.Pointables[0].Bases[0][0][2], -0.358190) + + gobottest.Assert(t, parsedFrame.Pointables[0].Width, 19.7871) } diff --git a/platforms/leap/parser.go b/platforms/leap/parser.go index 56f8cc53..53d615a9 100644 --- a/platforms/leap/parser.go +++ b/platforms/leap/parser.go @@ -48,7 +48,7 @@ type Hand struct { // Pointable is a Leap Motion pointing motion that has been detected type Pointable struct { - Bases [][]float64 `json:"bases"` + Bases [][][]float64 `json:"bases"` BTipPosition []float64 `json:"btipPosition"` CarpPosition []float64 `json:"carpPosition"` DipPosition []float64 `json:"dipPosition"` From 6d1e099e50c268d34a2b257301b542a6c11703e5 Mon Sep 17 00:00:00 2001 From: Trevor Rosen Date: Wed, 2 Jan 2019 18:12:38 -0600 Subject: [PATCH 33/47] Add some new MQTT adaptor functions with QOS #644 --- platforms/mqtt/mqtt_adaptor.go | 40 +++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/platforms/mqtt/mqtt_adaptor.go b/platforms/mqtt/mqtt_adaptor.go index f7285346..78bc508e 100644 --- a/platforms/mqtt/mqtt_adaptor.go +++ b/platforms/mqtt/mqtt_adaptor.go @@ -9,6 +9,12 @@ import ( paho "github.com/eclipse/paho.mqtt.golang" multierror "github.com/hashicorp/go-multierror" + "github.com/pkg/errors" +) + +var ( + // ErrNilClient is returned when a client action can't be taken because the struct has no client + ErrNilClient = errors.New("no MQTT client available") ) // Message is a message received from the broker. @@ -131,21 +137,43 @@ func (a *Adaptor) Finalize() (err error) { // Publish a message under a specific topic func (a *Adaptor) Publish(topic string, message []byte) bool { - if a.client == nil { + _, err := a.PublishWithQOS(topic, a.qos, message) + if err != nil { return false } - a.client.Publish(topic, byte(a.qos), false, message) + return true } +// PublishWithQOS allows per-publish QOS values to be set and returns a poken.Token +func (a *Adaptor) PublishWithQOS(topic string, qos int, message []byte) (paho.Token, error) { + if a.client == nil { + return nil, ErrNilClient + } + + token := a.client.Publish(topic, byte(qos), false, message) + return token, nil +} + +// OnWithQOS allows per-subscribe QOS values to be set and returns a paho.Token +func (a *Adaptor) OnWithQOS(event string, qos int, f func(msg Message)) (paho.Token, error) { + if a.client == nil { + return nil, ErrNilClient + } + + token := a.client.Subscribe(event, byte(qos), func(client paho.Client, msg paho.Message) { + f(msg) + }) + + return token, nil +} + // On subscribes to a topic, and then calls the message handler function when data is received func (a *Adaptor) On(event string, f func(msg Message)) bool { - if a.client == nil { + _, err := a.OnWithQOS(event, a.qos, f) + if err != nil { return false } - a.client.Subscribe(event, byte(a.qos), func(client paho.Client, msg paho.Message) { - f(msg) - }) return true } From a27beca99a17e9bf90bd172de37295ce0f9b388f Mon Sep 17 00:00:00 2001 From: Joe Kim Date: Mon, 14 Jan 2019 21:19:52 -0500 Subject: [PATCH 34/47] fix mavlink README to use correct example code --- platforms/mavlink/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platforms/mavlink/README.md b/platforms/mavlink/README.md index 9bf4aec6..422c7f21 100644 --- a/platforms/mavlink/README.md +++ b/platforms/mavlink/README.md @@ -39,7 +39,7 @@ func main() { iris := mavlink.NewDriver(adaptor) work := func() { - gobot.Once(iris.Event("packet"), func(data interface{}) { + iris.Once(iris.Event("packet"), func(data interface{}) { packet := data.(*common.MAVLinkPacket) dataStream := common.NewRequestDataStream(100, @@ -54,7 +54,7 @@ func main() { )) }) - gobot.On(iris.Event("message"), func(data interface{}) { + iris.On(iris.Event("message"), func(data interface{}) { if data.(common.MAVLinkMessage).Id() == 30 { message := data.(*common.Attitude) fmt.Println("Attitude") From 1f3cd7b77cb7b886edc3deb74e275ca2fc282748 Mon Sep 17 00:00:00 2001 From: Joe Kim Date: Sun, 20 Jan 2019 13:51:00 -0500 Subject: [PATCH 35/47] minor updates to opencv README --- platforms/opencv/README.md | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/platforms/opencv/README.md b/platforms/opencv/README.md index fed49a9e..14011f9b 100644 --- a/platforms/opencv/README.md +++ b/platforms/opencv/README.md @@ -6,7 +6,9 @@ For more info about OpenCV click [here](http://opencv.org/) ## How to Install -This package requires OpenCV version 3.4 be installed on your system, along with GoCV, which is the Go programming language wrapper used by Gobot. The best way is to follow the installation instructions on the GoCV website at [https://gocv.io](https://gocv.io). +This package requires OpenCV 3.4+ be installed on your system, along with GoCV, which is the Go programming language wrapper used by Gobot. The best way is to follow the installation instructions on the GoCV website at [https://gocv.io](https://gocv.io). + +The instructions should automatically install OpenCV 4+ ### macOS @@ -35,14 +37,6 @@ go get -d -u gobot.io/x/gobot/... ## How to Use -When you run code that uses OpenCV, you must setup some environment variables first. The best way to do this, is to first run the `env.sh` script that comes with GoCV, like this: - -``` -source $GOPATH/src/gocv.io/x/gocv/env.sh -``` - -Once you have run this script you can use `go run` or `go build` on your Gobot code that uses OpenCV as you normally would. - Here is an example using the camera: ```go From 20677abec3e22698ea32d7eb8bfdaa6124ed455e Mon Sep 17 00:00:00 2001 From: Rearth Date: Wed, 23 Jan 2019 14:30:55 +0100 Subject: [PATCH 36/47] Added some new features to the sphero ollie, bb-8 and sprkplus Signed-off-by: Rearth --- platforms/sphero/ollie/ollie_driver.go | 140 +++++++++++++++++--- platforms/sphero/ollie/ollie_driver_test.go | 86 ++++++++++++ platforms/sphero/ollie/ollie_packets.go | 86 ++++++++++++ 3 files changed, 294 insertions(+), 18 deletions(-) diff --git a/platforms/sphero/ollie/ollie_driver.go b/platforms/sphero/ollie/ollie_driver.go index ea9aeeff..d20558ee 100644 --- a/platforms/sphero/ollie/ollie_driver.go +++ b/platforms/sphero/ollie/ollie_driver.go @@ -19,7 +19,10 @@ type Driver struct { seq uint8 mtx sync.Mutex collisionResponse []uint8 - packetChannel chan *packet + packetChannel chan *Packet + asyncBuffer []byte + asyncMessage []byte + locatorCallback func(p Point2D) gobot.Eventer } @@ -57,6 +60,7 @@ const ( CollisionResponseSize = PacketHeaderSize + CollisionDataSize ) +// MotorModes is used to configure the motor type MotorModes uint8 // MotorModes required for SetRawMotorValues command @@ -68,10 +72,17 @@ const ( Ignore ) -type packet struct { - header []uint8 - body []uint8 - checksum uint8 +// Packet describes head, body and checksum for a data package to be sent to the sphero. +type Packet struct { + Header []uint8 + Body []uint8 + Checksum uint8 +} + +// Point2D represents a koordinate in 2-Dimensional space +type Point2D struct { + X int16 + Y int16 } // NewDriver creates a Driver for a Sphero Ollie @@ -80,7 +91,7 @@ func NewDriver(a ble.BLEConnector) *Driver { name: gobot.DefaultName("Ollie"), connection: a, Eventer: gobot.NewEventer(), - packetChannel: make(chan *packet, 1024), + packetChannel: make(chan *Packet, 1024), } n.AddEvent(Collision) @@ -88,6 +99,12 @@ func NewDriver(a ble.BLEConnector) *Driver { return n } +// PacketChannel returns the channel for packets to be sent to the sp +func (b *Driver) PacketChannel() chan *Packet { return b.packetChannel } + +// Sequence returns the Sequence number of the current packet +func (b *Driver) Sequence() uint8 { return b.seq } + // Connection returns the connection to this Ollie func (b *Driver) Connection() gobot.Connection { return b.connection } @@ -191,11 +208,61 @@ func (b *Driver) SetTXPower(level int) (err error) { // HandleResponses handles responses returned from Ollie func (b *Driver) HandleResponses(data []byte, e error) { - //fmt.Println("response data:", data, e) + + //since packets can only be 20 bytes long, we have to puzzle them together + newMessage := false + + //append message parts to existing + if len(data) > 0 && data[0] != 0xFF { + b.asyncBuffer = append(b.asyncBuffer, data...) + } + + //clear message when new one begins (first byte is always 0xFF) + if len(data) > 0 && data[0] == 0xFF { + b.asyncMessage = b.asyncBuffer + b.asyncBuffer = data + newMessage = true + } + + parts := b.asyncMessage + //3 is the id of data streaming, located at index 2 byte + if newMessage && len(parts) > 2 && parts[2] == 3 { + b.handleDataStreaming(parts) + } + + //index 1 is the type of the message, 0xFF being a direct response, 0xFE an asynchronous message + if len(data) > 4 && data[1] == 0xFF && data[0] == 0xFF { + //locator request + if data[4] == 0x0B && len(data) == 16 { + b.handleLocatorDetected(data) + } + } b.handleCollisionDetected(data) } +// GetLocatorData calls the passed function with the data from the locator +func (b *Driver) GetLocatorData(f func(p Point2D)) { + //CID 0x15 is the code for the locator request + b.PacketChannel() <- b.craftPacket([]uint8{}, 0x02, 0x15) + b.locatorCallback = f +} + +func (b *Driver) handleDataStreaming(data []byte) { + // ensure data is the right length: + if len(data) != 88 { + return + } + + //data packet is the same as for the normal sphero, since the same communication api is used + //only difference in communication is that the "newer" spheros use BLE for communinations + var dataPacket DataStreamingPacket + buffer := bytes.NewBuffer(data[5:]) // skip header + binary.Read(buffer, binary.BigEndian, &dataPacket) + + b.Publish(SensorData, dataPacket) +} + // SetRGB sets the Ollie to the given r, g, and b values func (b *Driver) SetRGB(r uint8, g uint8, bl uint8) { b.packetChannel <- b.craftPacket([]uint8{r, g, bl, 0x01}, 0x02, 0x20) @@ -264,9 +331,16 @@ func (b *Driver) ConfigureCollisionDetection(cc sphero.CollisionConfig) { b.packetChannel <- b.craftPacket([]uint8{cc.Method, cc.Xt, cc.Yt, cc.Xs, cc.Ys, cc.Dead}, 0x02, 0x12) } -func (b *Driver) write(packet *packet) (err error) { - buf := append(packet.header, packet.body...) - buf = append(buf, packet.checksum) +//SetDataStreamingConfig passes the config to the sphero to stream sensor data +func (b *Driver) SetDataStreamingConfig(d sphero.DataStreamingConfig) { + buf := new(bytes.Buffer) + binary.Write(buf, binary.BigEndian, d) + b.PacketChannel() <- b.craftPacket(buf.Bytes(), 0x02, 0x11) +} + +func (b *Driver) write(packet *Packet) (err error) { + buf := append(packet.Header, packet.Body...) + buf = append(buf, packet.Checksum) err = b.adaptor().WriteCharacteristic(commandsCharacteristic, buf) if err != nil { fmt.Println("send command error:", err) @@ -279,18 +353,48 @@ func (b *Driver) write(packet *packet) (err error) { return } -func (b *Driver) craftPacket(body []uint8, did byte, cid byte) *packet { +func (b *Driver) craftPacket(body []uint8, did byte, cid byte) *Packet { b.mtx.Lock() defer b.mtx.Unlock() - packet := new(packet) - packet.body = body - dlen := len(packet.body) + 1 - packet.header = []uint8{0xFF, 0xFF, did, cid, b.seq, uint8(dlen)} - packet.checksum = b.calculateChecksum(packet) + packet := new(Packet) + packet.Body = body + dlen := len(packet.Body) + 1 + packet.Header = []uint8{0xFF, 0xFF, did, cid, b.seq, uint8(dlen)} + packet.Checksum = b.calculateChecksum(packet) return packet } +func (b *Driver) handleLocatorDetected(data []uint8) { + //read the unsigned raw values + ux := binary.BigEndian.Uint16(data[5:7]) + uy := binary.BigEndian.Uint16(data[7:9]) + + //convert to signed values + var x, y int16 + + if ux > 32255 { + x = int16(ux - 65535) + } else { + x = int16(ux) + } + + if uy > 32255 { + y = int16(uy - 65535) + } else { + y = int16(uy) + } + + //create point obj + p := new(Point2D) + p.X = x + p.Y = y + + if b.locatorCallback != nil { + b.locatorCallback(*p) + } +} + func (b *Driver) handleCollisionDetected(data []uint8) { if len(data) == ResponsePacketMaxSize { @@ -335,8 +439,8 @@ func (b *Driver) handleCollisionDetected(data []uint8) { b.Publish(Collision, collision) } -func (b *Driver) calculateChecksum(packet *packet) uint8 { - buf := append(packet.header, packet.body...) +func (b *Driver) calculateChecksum(packet *Packet) uint8 { + buf := append(packet.Header, packet.Body...) return calculateChecksum(buf[2:]) } diff --git a/platforms/sphero/ollie/ollie_driver_test.go b/platforms/sphero/ollie/ollie_driver_test.go index a98f73e1..b1ec7048 100644 --- a/platforms/sphero/ollie/ollie_driver_test.go +++ b/platforms/sphero/ollie/ollie_driver_test.go @@ -1,10 +1,15 @@ package ollie import ( + "fmt" + "math" + "strconv" "testing" + "time" "gobot.io/x/gobot" "gobot.io/x/gobot/gobottest" + "gobot.io/x/gobot/platforms/sphero" ) var _ gobot.Driver = (*Driver)(nil) @@ -25,3 +30,84 @@ func TestOllieDriverStartAndHalt(t *testing.T) { gobottest.Assert(t, d.Start(), nil) gobottest.Assert(t, d.Halt(), nil) } + +func TestLocatorData(t *testing.T) { + d := initTestOllieDriver() + + tables := []struct { + x1 byte + x2 byte + y1 byte + y2 byte + x int16 + y int16 + }{ + {0x00, 0x05, 0x00, 0x05, 5, 5}, + {0x00, 0x00, 0x00, 0x00, 0, 0}, + {0x00, 0x0A, 0x00, 0xF0, 10, 240}, + {0x01, 0x00, 0x01, 0x00, 256, 256}, + {0xFF, 0xFE, 0xFF, 0xFE, -1, -1}, + } + + for _, point := range tables { + //0x0B is the locator ID + packet := []byte{0xFF, 0xFF, 0x00, 0x00, 0x0B, point.x1, point.x2, point.y1, point.y2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} + + d.GetLocatorData(func(p Point2D) { + gobottest.Assert(t, p.Y, point.y) + }) + d.HandleResponses(packet, nil) + } +} + +func TestDataStreaming(t *testing.T) { + d := initTestOllieDriver() + + d.SetDataStreamingConfig(sphero.DefaultDataStreamingConfig()) + + response := false + d.On("sensordata", func(data interface{}) { + cont := data.(DataStreamingPacket) + fmt.Printf("got streaming packet: %+v \n", cont) + gobottest.Assert(t, cont.RawAccX, int16(10)) + response = true + }) + + //example data packet + p1 := []string{"FFFE030053000A003900FAFFFE0007FFFF000000", + "000000000000000000FFECFFFB00010000004B01", + "BD1034FFFF000300000000000000000000000000", + "0000002701FDE500560000000000000065000000", + "0000000000000071", + } + + for _, elem := range p1 { + var bytes []byte + for i := 0; i < len([]rune(elem)); i += 2 { + a := []rune(elem)[i : i+2] + b, _ := strconv.ParseUint(string(a), 16, 16) + c := uint16(b) + bytes = append(bytes, byte(c)) + } + d.HandleResponses(bytes, nil) + + } + + //send empty packet to indicate start of next message + d.HandleResponses([]byte{0xFF}, nil) + time.Sleep(10 * time.Millisecond) + if response == false { + t.Error("no response recieved") + } +} + +func parseBytes(s string) (f byte) { + i, err := strconv.ParseUint(s, 16, 32) + if err != nil { + return + } + + f = byte(math.Float32frombits(uint32(i))) + + return +} diff --git a/platforms/sphero/ollie/ollie_packets.go b/platforms/sphero/ollie/ollie_packets.go index 6da74f59..2e80d72c 100644 --- a/platforms/sphero/ollie/ollie_packets.go +++ b/platforms/sphero/ollie/ollie_packets.go @@ -13,3 +13,89 @@ func DefaultCollisionConfig() sphero.CollisionConfig { Dead: 0x60, } } + +// DataStreamingPacket represents the response from a Data Streaming event +type DataStreamingPacket struct { + // 8000 0000h accelerometer axis X, raw -2048 to 2047 4mG + RawAccX int16 + // 4000 0000h accelerometer axis Y, raw -2048 to 2047 4mG + RawAccY int16 + // 2000 0000h accelerometer axis Z, raw -2048 to 2047 4mG + RawAccZ int16 + // 1000 0000h gyro axis X, raw -32768 to 32767 0.068 degrees + RawGyroX int16 + // 0800 0000h gyro axis Y, raw -32768 to 32767 0.068 degrees + RawGyroY int16 + // 0400 0000h gyro axis Z, raw -32768 to 32767 0.068 degrees + RawGyroZ int16 + // 0200 0000h Reserved + Rsrv1 int16 + // 0100 0000h Reserved + Rsrv2 int16 + // 0080 0000h Reserved + Rsrv3 int16 + // 0040 0000h right motor back EMF, raw -32768 to 32767 22.5 cm + RawRMotorBack int16 + // 0020 0000h left motor back EMF, raw -32768 to 32767 22.5 cm + RawLMotorBack int16 + // 0010 0000h left motor, PWM, raw -2048 to 2047 duty cycle + RawLMotor int16 + // 0008 0000h right motor, PWM raw -2048 to 2047 duty cycle + RawRMotor int16 + // 0004 0000h IMU pitch angle, filtered -179 to 180 degrees + FiltPitch int16 + // 0002 0000h IMU roll angle, filtered -179 to 180 degrees + FiltRoll int16 + // 0001 0000h IMU yaw angle, filtered -179 to 180 degrees + FiltYaw int16 + // 0000 8000h accelerometer axis X, filtered -32768 to 32767 1/4096 G + FiltAccX int16 + // 0000 4000h accelerometer axis Y, filtered -32768 to 32767 1/4096 G + FiltAccY int16 + // 0000 2000h accelerometer axis Z, filtered -32768 to 32767 1/4096 G + FiltAccZ int16 + // 0000 1000h gyro axis X, filtered -20000 to 20000 0.1 dps + FiltGyroX int16 + // 0000 0800h gyro axis Y, filtered -20000 to 20000 0.1 dps + FiltGyroY int16 + // 0000 0400h gyro axis Z, filtered -20000 to 20000 0.1 dps + FiltGyroZ int16 + // 0000 0200h Reserved + Rsrv4 int16 + // 0000 0100h Reserved + Rsrv5 int16 + // 0000 0080h Reserved + Rsrv6 int16 + // 0000 0040h right motor back EMF, filtered -32768 to 32767 22.5 cm + FiltRMotorBack int16 + // 0000 0020h left motor back EMF, filtered -32768 to 32767 22.5 cm + FiltLMotorBack int16 + // 0000 0010h Reserved 1 + Rsrv7 int16 + // 0000 0008h Reserved 2 + Rsrv8 int16 + // // 0000 0004h Reserved 3 + // Rsrv9 int16 + // // 0000 0002h Reserved 4 + // Rsrv10 int16 + // // 0000 0001h Reserved 5 + // Rsrv11 int16 + // 8000 0000h Quaternion Q0 -10000 to 10000 1/10000 Q + Quat0 int16 + // 4000 0000h Quaternion Q1 -10000 to 10000 1/10000 Q + Quat1 int16 + // 2000 0000h Quaternion Q2 -10000 to 10000 1/10000 Q + Quat2 int16 + // 1000 0000h Quaternion Q3 -10000 to 10000 1/10000 Q + Quat3 int16 + // 0800 0000h Odometer X -32768 to 32767 cm + OdomX int16 + // 0400 0000h Odometer Y -32768 to 32767 cm + OdomY int16 + // 0200 0000h AccelOne 0 to 8000 1 mG + AccelOne int16 + // 0100 0000h Velocity X -32768 to 32767 mm/s + VeloX int16 + // 0080 0000h Velocity Y -32768 to 32767 mm/s + VeloY int16 +} From 756d38db5b4bdfe9ee76b563fdc37d42b6f47511 Mon Sep 17 00:00:00 2001 From: Chris Morgan Date: Thu, 24 Jan 2019 10:23:37 -0500 Subject: [PATCH 37/47] pwm_pin - Fix DutyCycle() parse error, need to trim off trailing '\n' before calling strconv.Atoi(), as other functions in this package do --- sysfs/pwm_pin.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sysfs/pwm_pin.go b/sysfs/pwm_pin.go index 5747aded..f003e0a3 100644 --- a/sysfs/pwm_pin.go +++ b/sysfs/pwm_pin.go @@ -148,7 +148,8 @@ func (p *PWMPin) DutyCycle() (duty uint32, err error) { return } - val, e := strconv.Atoi(string(buf)) + v := bytes.TrimRight(buf, "\n") + val, e := strconv.Atoi(string(v)) return uint32(val), e } From 62e91760b0a1457dd53ce745d8ae0a7991a1a461 Mon Sep 17 00:00:00 2001 From: Rearth Date: Wed, 13 Feb 2019 13:16:52 +0100 Subject: [PATCH 38/47] Added methods to read Sphero Power States --- platforms/sphero/ollie/ollie_driver.go | 39 +++++++++++++++++++------ platforms/sphero/ollie/ollie_packets.go | 14 +++++++++ 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/platforms/sphero/ollie/ollie_driver.go b/platforms/sphero/ollie/ollie_driver.go index d20558ee..307c9475 100644 --- a/platforms/sphero/ollie/ollie_driver.go +++ b/platforms/sphero/ollie/ollie_driver.go @@ -14,15 +14,16 @@ import ( // Driver is the Gobot driver for the Sphero Ollie robot type Driver struct { - name string - connection gobot.Connection - seq uint8 - mtx sync.Mutex - collisionResponse []uint8 - packetChannel chan *Packet - asyncBuffer []byte - asyncMessage []byte - locatorCallback func(p Point2D) + name string + connection gobot.Connection + seq uint8 + mtx sync.Mutex + collisionResponse []uint8 + packetChannel chan *Packet + asyncBuffer []byte + asyncMessage []byte + locatorCallback func(p Point2D) + powerstateCallback func(p PowerStatePacket) gobot.Eventer } @@ -236,6 +237,10 @@ func (b *Driver) HandleResponses(data []byte, e error) { if data[4] == 0x0B && len(data) == 16 { b.handleLocatorDetected(data) } + + if data[4] == 0x09 { + b.handlePowerStateDetected(data) + } } b.handleCollisionDetected(data) @@ -248,6 +253,13 @@ func (b *Driver) GetLocatorData(f func(p Point2D)) { b.locatorCallback = f } +// GetPowerState calls the passed function with the Power State information from the sphero +func (b *Driver) GetPowerState(f func(p PowerStatePacket)) { + //CID 0x20 is the code for the power state + b.PacketChannel() <- b.craftPacket([]uint8{}, 0x00, 0x20) + b.powerstateCallback = f +} + func (b *Driver) handleDataStreaming(data []byte) { // ensure data is the right length: if len(data) != 88 { @@ -365,6 +377,15 @@ func (b *Driver) craftPacket(body []uint8, did byte, cid byte) *Packet { return packet } +func (b *Driver) handlePowerStateDetected(data []uint8) { + + var dataPacket PowerStatePacket + buffer := bytes.NewBuffer(data[5:]) // skip header + binary.Read(buffer, binary.BigEndian, &dataPacket) + + b.powerstateCallback(dataPacket) +} + func (b *Driver) handleLocatorDetected(data []uint8) { //read the unsigned raw values ux := binary.BigEndian.Uint16(data[5:7]) diff --git a/platforms/sphero/ollie/ollie_packets.go b/platforms/sphero/ollie/ollie_packets.go index 2e80d72c..74169cde 100644 --- a/platforms/sphero/ollie/ollie_packets.go +++ b/platforms/sphero/ollie/ollie_packets.go @@ -14,6 +14,20 @@ func DefaultCollisionConfig() sphero.CollisionConfig { } } +// PowerStatePacket contains all data relevant to the power state of the sphero +type PowerStatePacket struct { + // record Version Code + RecVer uint8 + // High-Level State of the Battery; 1=charging, 2=battery ok, 3=battery low, 4=battery critical + PowerState uint8 + // Battery Voltage, scaled in 100th of a Volt, 0x02EF would be 7.51 volts + BattVoltage uint16 + // Number of charges in the total lifetime of the sphero + NumCharges uint16 + //Seconds awake since last charge + TimeSinceChg uint16 +} + // DataStreamingPacket represents the response from a Data Streaming event type DataStreamingPacket struct { // 8000 0000h accelerometer axis X, raw -2048 to 2047 4mG From 07d9e09b1ea5b4dd7e1df45124c22fe687181564 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Tue, 21 May 2019 14:28:36 +0200 Subject: [PATCH 39/47] build: update deps to latest versions of dependencies for GoCV and others Signed-off-by: Ron Evans --- Gopkg.lock | 185 ++++++++++++++++------------------------------------- Gopkg.toml | 6 +- 2 files changed, 59 insertions(+), 132 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 6c7d9c18..b326833c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -3,58 +3,45 @@ [[projects]] branch = "master" - digest = "1:da54e55f2f63347514121c0d7b53631970367b26281d057ec66925c57324b8f1" name = "github.com/bmizerany/pat" packages = ["."] - pruneopts = "" revision = "6226ea591a40176dd3ff9cd8eff81ed6ca721a00" [[projects]] - digest = "1:e85837cb04b78f61688c6eba93ea9d14f60d611e2aaf8319999b1a60d2dafbfa" name = "github.com/codegangsta/cli" packages = ["."] - pruneopts = "" revision = "cfb38830724cc34fedffe9a2a29fb54fa9169cd1" version = "v1.20.0" [[projects]] - branch = "master" - digest = "1:ea040951144214212d0402e862a786e8d68d2a1734f975933e6ba084b4d05f2f" name = "github.com/creack/goselect" packages = ["."] - pruneopts = "" - revision = "58854f77ee8d858ce751b0a9bcc5533fef7bfa9e" + revision = "c98c4e62c41618cf78d3ff759af0d4dbaa677748" + version = "v0.1.0" [[projects]] - digest = "1:0deddd908b6b4b768cfc272c16ee61e7088a60f7fe2f06c547bd3d8e1f8b8e77" name = "github.com/davecgh/go-spew" packages = ["spew"] - pruneopts = "" revision = "8991bc29aa16c548c550c7ff78260e27b9ab7c73" version = "v1.1.1" [[projects]] branch = "master" - digest = "1:917123ed8122468eb815cc418af3bbf298c91f9cb1c36164e063f50e1a726e97" name = "github.com/donovanhide/eventsource" packages = ["."] - pruneopts = "" revision = "3ed64d21fb0b6bd8b49bcfec08f3004daee8723d" [[projects]] - digest = "1:3fa846cb3feb4e65371fe3c347c299de9b5bc3e71e256c0d940cd19b767a6ba0" name = "github.com/eclipse/paho.mqtt.golang" packages = [ ".", - "packets", + "packets" ] - pruneopts = "" - revision = "36d01c2b4cbeb3d2a12063e4880ce30800af9560" - version = "v1.1.1" + revision = "adca289fdcf8c883800aafa545bc263452290bae" + version = "v1.2.0" [[projects]] branch = "master" - digest = "1:c177fa1d2bdfc487f4bb7188f0fc683f85fd8cadcf1293eece899c86e75d6337" name = "github.com/go-ble/ble" packages = [ ".", @@ -66,222 +53,188 @@ "linux/hci", "linux/hci/cmd", "linux/hci/evt", - "linux/hci/socket", + "linux/hci/socket" ] - pruneopts = "" - revision = "731710e91806e163fe770d93dc318683f6f53c63" + revision = "e4c77014ff5a22003a43b91ce1c53381ac9de901" [[projects]] - digest = "1:c7382630b7f8958a68cf9c9314477a192890bea01d6bed9b22f95d3bad5530d2" name = "github.com/gobuffalo/uuid" packages = ["."] - pruneopts = "" - revision = "7652001f1b1ff3d69aa899943b668e9be27284a0" - version = "v2.0.3" + revision = "3f701655805f1ef76e9963d1b377497a0c66d7cd" + version = "v2.0.5" + +[[projects]] + name = "github.com/gofrs/uuid" + packages = ["."] + revision = "6b08a5c5172ba18946672b49749cde22873dd7c2" + version = "v3.2.0" [[projects]] - digest = "1:8e3bd93036b4a925fe2250d3e4f38f21cadb8ef623561cd80c3c50c114b13201" name = "github.com/hashicorp/errwrap" packages = ["."] - pruneopts = "" revision = "8a6fb523712970c966eefc6b39ed2c5e74880354" version = "v1.0.0" [[projects]] - branch = "master" - digest = "1:72308fdd6d5ef61106a95be7ca72349a5565809042b6426a3cfb61d99483b824" name = "github.com/hashicorp/go-multierror" packages = ["."] - pruneopts = "" revision = "886a7fbe3eb1c874d46f623bfa70af45f425b3d1" + version = "v1.0.0" [[projects]] branch = "master" - digest = "1:aba830e5898f09dd06027a27a10f4d44e8b0762a3ba0c83a771a01f4e72b81c3" name = "github.com/hybridgroup/go-ardrone" packages = [ "client", "client/commands", - "client/navdata", + "client/navdata" ] - pruneopts = "" revision = "b9750d8d7b78f9638e5fdd899835e99d46b5a56c" [[projects]] branch = "master" - digest = "1:9956f4f58b2ccea25656086bcd7be0273915961a8231965a1a6c886874172054" name = "github.com/hybridgroup/mjpeg" packages = ["."] - pruneopts = "" revision = "4680f319790ebffe28bbee775ecd1725693731ca" [[projects]] - digest = "1:9ea83adf8e96d6304f394d40436f2eb44c1dc3250d223b74088cc253a6cd0a1c" name = "github.com/mattn/go-colorable" packages = ["."] - pruneopts = "" - revision = "167de6bfdfba052fa6b2d3664c8f5272e23c9072" - version = "v0.0.9" + revision = "3a70a971f94a22f2fa562ffcc7a0eb45f5daf045" + version = "v0.1.1" [[projects]] - digest = "1:78229b46ddb7434f881390029bd1af7661294af31f6802e0e1bedaad4ab0af3c" name = "github.com/mattn/go-isatty" packages = ["."] - pruneopts = "" - revision = "0360b2af4f38e8d38c7fce2a9f4e702702d73a39" - version = "v0.0.3" + revision = "c2a7a6ca930a4cd0bc33a3f298eb71960732a3a7" + version = "v0.0.7" [[projects]] branch = "master" - digest = "1:50416da10e189bc201e122e20078fb8e680a439cbdd24aaece06c434b4415b60" name = "github.com/mgutz/ansi" packages = ["."] - pruneopts = "" revision = "9520e82c474b0a04dd04f8a40959027271bab992" [[projects]] - digest = "1:47af139ef25cee26d54b8422b73a043ab5d8f5ec26a1ae49d3ca9212f1dbfdac" name = "github.com/mgutz/logxi" packages = ["v1"] - pruneopts = "" revision = "aebf8a7d67ab4625e0fd4a665766fef9a709161b" version = "v1" [[projects]] - digest = "1:f04a78a43f55f089c919beee8ec4a1495dee1bd271548da2cb44bf44699a6a61" name = "github.com/nats-io/go-nats" packages = [ + ".", "encoders/builtin", - "util", + "util" ] - pruneopts = "" - revision = "fb0396ee0bdb8018b0fef30d6d1de798ce99cd05" - version = "v1.6.0" + revision = "70fe06cee50d4b6f98248d9675fb55f2a3aa7228" + version = "v1.7.2" [[projects]] - digest = "1:f04a78a43f55f089c919beee8ec4a1495dee1bd271548da2cb44bf44699a6a61" - name = "github.com/nats-io/nats" + name = "github.com/nats-io/nkeys" packages = ["."] - pruneopts = "" - revision = "fb0396ee0bdb8018b0fef30d6d1de798ce99cd05" - version = "v1.6.0" + revision = "1546a3320a8f195a5b5c84aef8309377c2e411d5" + version = "v0.0.2" [[projects]] - digest = "1:be61e8224b84064109eaba8157cbb4bbe6ca12443e182b6624fdfa1c0dcf53d9" name = "github.com/nats-io/nuid" packages = ["."] - pruneopts = "" - revision = "289cccf02c178dc782430d534e3c1f5b72af807f" - version = "v1.0.0" + revision = "4b96681fa6d28dd0ab5fe79bac63b3a493d9ee94" + version = "v1.0.1" [[projects]] - digest = "1:7365acd48986e205ccb8652cc746f09c8b7876030d53710ea6ef7d0bd0dcd7ca" name = "github.com/pkg/errors" packages = ["."] - pruneopts = "" - revision = "645ef00459ed84a119197bfb8d8205042c6df63d" - version = "v0.8.0" + revision = "ba968bfe8b2f7e042a574c888954fccecfa385b4" + version = "v0.8.1" [[projects]] - digest = "1:256484dbbcd271f9ecebc6795b2df8cad4c458dd0f5fd82a8c2fa0c29f233411" name = "github.com/pmezard/go-difflib" packages = ["difflib"] - pruneopts = "" revision = "792786c7400a136282c1664665ae0a8db921c6c2" version = "v1.0.0" [[projects]] branch = "master" - digest = "1:7b2df17857c98bcb45e200d6005f7af1f1e04757a77cd9e20cf27db05f0ad611" name = "github.com/raff/goble" packages = ["xpc"] - pruneopts = "" - revision = "efeac611681b89806d673dfbd8fc1690ca6bd093" + revision = "5a206277e7359d09af80eb4519b8fa331f5ac7da" [[projects]] branch = "master" - digest = "1:eb22cc3727f58f5939373c397b0b9a8076f63e5b19584dcc00901dc5d407a77b" name = "github.com/sigurn/crc8" packages = ["."] - pruneopts = "" revision = "e55481d6f45c5a8f040343bace9013571dae103e" [[projects]] branch = "master" - digest = "1:52ed74cbf4d1bb1975642c817ec91b221c8963fa599885319407bf468431851d" name = "github.com/sigurn/utils" packages = ["."] - pruneopts = "" revision = "f19e41f79f8f006116f682c1af454591bc278ad4" [[projects]] - digest = "1:c587772fb8ad29ad4db67575dad25ba17a51f072ff18a22b4f0257a4d9c24f75" name = "github.com/stretchr/testify" packages = ["assert"] - pruneopts = "" - revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" - version = "v1.2.2" + revision = "ffdc059bfe9ce6a4e144ba849dbedead332c6053" + version = "v1.3.0" [[projects]] branch = "master" - digest = "1:54f00349c493a13c9340be00aebfe75c1cf7c58f2ed2d1ed5d1952105c925992" name = "github.com/tarm/serial" packages = ["."] - pruneopts = "" - revision = "eaafced92e9619f03c72527efeab21e326f3bc36" + revision = "98f6abe2eb07edd42f6dfa2a934aea469acc29b7" [[projects]] - digest = "1:4aa77644fa15ef170580070adcb70bd0adb02b0980bcc632eeaa528fd0e8137e" name = "github.com/veandco/go-sdl2" packages = ["sdl"] - pruneopts = "" revision = "271d2ec43388932fd2a80f4c2e81021734685a62" version = "v0.3" [[projects]] branch = "master" - digest = "1:488c97dd29d2f0ddb48e8a04aed2da02f43efbd6e6c9a7bf9653819f55c8e90c" name = "go.bug.st/serial.v1" packages = [ ".", - "unixutils", + "unixutils" ] - pruneopts = "" revision = "5f7892a7bb453066bdc6683b9b5d24d9dee03ec1" [[projects]] - digest = "1:a5253f1c452abd612cf8975cde3f96285a00dc9015660e8d446291a48662b54d" name = "gocv.io/x/gocv" packages = ["."] - pruneopts = "" - revision = "116580adca5ee7dbf8d1307c9072f5094051116f" - version = "v0.16.0" + revision = "0f87fa5ef3fd497562e89a821fd87a77c28cb8ea" + version = "v0.20.0" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = [ + "ed25519", + "ed25519/internal/edwards25519" + ] + revision = "22d7a77e9e5f409e934ed268692e56707cd169e5" [[projects]] branch = "master" - digest = "1:7dd0f1b8c8bd70dbae4d3ed3fbfaec224e2b27bcc0fc65882d6f1dba5b1f6e22" name = "golang.org/x/net" packages = [ "internal/socks", "proxy", - "websocket", + "websocket" ] - pruneopts = "" - revision = "8a410e7b638dca158bf9e766925842f6651ff828" + revision = "018c4d40a106a7ae83689758294fcd8d23850745" [[projects]] branch = "master" - digest = "1:79b4fb7cfed68c4d0727858bd32dbe3be4b97ea58e2d767f92287f67810cbc98" name = "golang.org/x/sys" packages = [ "unix", - "windows", + "windows" ] - pruneopts = "" - revision = "d99a578cf41bfccdeaf48b0845c823a4b8b0ad5e" + revision = "c432e742b0af385916e013f6a34e9e73d139cf82" [[projects]] - digest = "1:5a0018233f499284a0f49f649d657ebe3ca4c3db74c7a7a26ef9bf08e41df4c9" name = "periph.io/x/periph" packages = [ ".", @@ -295,40 +248,14 @@ "conn/spi", "conn/spi/spireg", "host/fs", - "host/sysfs", + "host/sysfs" ] - pruneopts = "" - revision = "f34a1b4e75c5935f6269723ae45eacd0aa1d1a2a" - version = "v3.1.0" + revision = "df7c7a4f8f86972b1474695daf94a84c9165faed" + version = "v3.5.0" [solve-meta] analyzer-name = "dep" analyzer-version = 1 - input-imports = [ - "github.com/bmizerany/pat", - "github.com/codegangsta/cli", - "github.com/donovanhide/eventsource", - "github.com/eclipse/paho.mqtt.golang", - "github.com/go-ble/ble", - "github.com/go-ble/ble/darwin", - "github.com/go-ble/ble/linux", - "github.com/gobuffalo/uuid", - "github.com/hashicorp/go-multierror", - "github.com/hybridgroup/go-ardrone/client", - "github.com/hybridgroup/mjpeg", - "github.com/nats-io/nats", - "github.com/pkg/errors", - "github.com/sigurn/crc8", - "github.com/stretchr/testify/assert", - "github.com/tarm/serial", - "github.com/veandco/go-sdl2/sdl", - "go.bug.st/serial.v1", - "gocv.io/x/gocv", - "golang.org/x/net/websocket", - "periph.io/x/periph/conn", - "periph.io/x/periph/conn/physic", - "periph.io/x/periph/conn/spi", - "periph.io/x/periph/host/sysfs", - ] + inputs-digest = "a13da563fdab3a411f0b13a7d462e894978a9b433ba2d5c6726542a3c0f40d2d" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 65ef0ced..283dd6a4 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -35,7 +35,7 @@ [[constraint]] name = "github.com/eclipse/paho.mqtt.golang" - version = "1.1.1" + version = "1.2.0" [[constraint]] branch = "master" @@ -75,11 +75,11 @@ [[constraint]] name = "gocv.io/x/gocv" - version = "0.16.0" + version = "0.20.0" [[constraint]] name = "periph.io/x/periph" - version = "3.0.0" + version = "3.5.0" [[constraint]] branch = "master" From c1aa4f867846da4669ecf3bc3318bd96b7ee6f3f Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Tue, 21 May 2019 14:29:06 +0200 Subject: [PATCH 40/47] mqtt: make tests run correctly even when a local MQTT server is in fact running Signed-off-by: Ron Evans --- platforms/mqtt/mqtt_adaptor.go | 2 +- platforms/mqtt/mqtt_adaptor_test.go | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/platforms/mqtt/mqtt_adaptor.go b/platforms/mqtt/mqtt_adaptor.go index 78bc508e..b3df24a5 100644 --- a/platforms/mqtt/mqtt_adaptor.go +++ b/platforms/mqtt/mqtt_adaptor.go @@ -230,7 +230,7 @@ func (a *Adaptor) newTLSConfig() *tls.Config { ClientCAs: nil, // InsecureSkipVerify = verify that cert contents // match server. IP matches what is in cert etc. - InsecureSkipVerify: true, + InsecureSkipVerify: false, // Certificates = list of certs client sends to server. Certificates: certs, } diff --git a/platforms/mqtt/mqtt_adaptor_test.go b/platforms/mqtt/mqtt_adaptor_test.go index 2adc1987..c4f33445 100644 --- a/platforms/mqtt/mqtt_adaptor_test.go +++ b/platforms/mqtt/mqtt_adaptor_test.go @@ -72,21 +72,21 @@ func TestMqttAdaptorUseClientKey(t *testing.T) { } func TestMqttAdaptorConnectError(t *testing.T) { - a := initTestMqttAdaptor() + a := NewAdaptor("tcp://localhost:1884", "client") err := a.Connect() gobottest.Assert(t, strings.Contains(err.Error(), "connection refused"), true) } func TestMqttAdaptorConnectSSLError(t *testing.T) { - a := initTestMqttAdaptor() + a := NewAdaptor("tcp://localhost:1884", "client") a.SetUseSSL(true) err := a.Connect() gobottest.Assert(t, strings.Contains(err.Error(), "connection refused"), true) } func TestMqttAdaptorConnectWithAuthError(t *testing.T) { - a := NewAdaptorWithAuth("localhost:1883", "client", "user", "pass") + a := NewAdaptorWithAuth("xyz://localhost:1883", "client", "user", "pass") var expected error expected = multierror.Append(expected, errors.New("Network Error : Unknown protocol")) From 5710da3d096611d5cee0f479e4bc9b419062c247 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Tue, 21 May 2019 14:45:57 +0200 Subject: [PATCH 41/47] build: correct package version as suggested by @dlisin thanks Signed-off-by: Ron Evans --- Gopkg.lock | 2 +- Gopkg.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index b326833c..f1fc2d57 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -256,6 +256,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "a13da563fdab3a411f0b13a7d462e894978a9b433ba2d5c6726542a3c0f40d2d" + inputs-digest = "bc6d58e39da90e8b641ebbb857b01bbecb8f7a8df8eb1ed1029f1f721d1c2139" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 283dd6a4..98ecf79b 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -50,7 +50,7 @@ name = "github.com/hybridgroup/go-ardrone" [[constraint]] - name = "github.com/nats-io/nats" + name = "github.com/nats-io/go-nats" version = "1.3.0" [[constraint]] From 70fd202030bfa3fa0898c837bce460f9a702ecf5 Mon Sep 17 00:00:00 2001 From: Mike Zange <5443707+MikeZange@users.noreply.github.com> Date: Fri, 10 May 2019 12:49:22 +0100 Subject: [PATCH 42/47] Stop using Red parameter for brightness value This change corrects using Red parameter of RGBA for the Brightness value, to using the Alpha parameter of RGBA. As the brightness max value for APA102 is `31` the `math.Min` stops the driver from using any value higher than 31. --- drivers/spi/apa102.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/spi/apa102.go b/drivers/spi/apa102.go index f8b9ea06..2dd82e29 100644 --- a/drivers/spi/apa102.go +++ b/drivers/spi/apa102.go @@ -1,9 +1,9 @@ package spi import ( - "image/color" - "gobot.io/x/gobot" + "image/color" + "math" ) // APA102Driver is a driver for the APA102 programmable RGB LEDs @@ -93,7 +93,7 @@ func (d *APA102Driver) Draw() error { for i, c := range d.vals { j := (i + 1) * 4 - tx[j] = 0xe0 + byte(c.R) + tx[j] = 0xe0 + byte(math.Min(c.A, 31)) tx[j+1] = byte(c.B) tx[j+2] = byte(c.G) tx[j+3] = byte(c.R) From c6a415d88b87b77124326b6776f5bed95e314f60 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Tue, 21 May 2019 15:31:32 +0200 Subject: [PATCH 43/47] spi: correct param used for APA102 Draw() method Signed-off-by: Ron Evans --- drivers/spi/apa102.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/drivers/spi/apa102.go b/drivers/spi/apa102.go index 2dd82e29..e6170a22 100644 --- a/drivers/spi/apa102.go +++ b/drivers/spi/apa102.go @@ -1,9 +1,10 @@ package spi import ( - "gobot.io/x/gobot" "image/color" "math" + + "gobot.io/x/gobot" ) // APA102Driver is a driver for the APA102 programmable RGB LEDs @@ -93,7 +94,7 @@ func (d *APA102Driver) Draw() error { for i, c := range d.vals { j := (i + 1) * 4 - tx[j] = 0xe0 + byte(math.Min(c.A, 31)) + tx[j] = 0xe0 + byte(math.Min(float64(c.A), 31)) tx[j+1] = byte(c.B) tx[j+2] = byte(c.G) tx[j+3] = byte(c.R) From 2bb75a88c4124d6f25422889531a5e16858f9320 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Tue, 21 May 2019 15:32:09 +0200 Subject: [PATCH 44/47] build: only build last 2 versions of Go plus tip for CI Signed-off-by: Ron Evans --- .travis.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7b5e2c4a..16418062 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,9 +3,8 @@ sudo: required dist: trusty go_import_path: gobot.io/x/gobot go: - - 1.9.x - - 1.10.x - 1.11.x + - 1.12.x - tip matrix: allow_failures: From 37bcf291b98afa3199b5c06190c2eccb968191a0 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Tue, 21 May 2019 15:57:19 +0200 Subject: [PATCH 45/47] build: update OpenCV build script for OpenCV 4.1.0 Signed-off-by: Ron Evans --- travis_build_opencv.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/travis_build_opencv.sh b/travis_build_opencv.sh index ba4db750..9a383bcf 100755 --- a/travis_build_opencv.sh +++ b/travis_build_opencv.sh @@ -1,7 +1,7 @@ #!/bin/bash set -eux -o pipefail -OPENCV_VERSION=${OPENCV_VERSION:-3.4.2} +OPENCV_VERSION=${OPENCV_VERSION:-4.1.0} #GRAPHICAL=ON GRAPHICAL=${GRAPHICAL:-OFF} @@ -49,7 +49,9 @@ cmake -D WITH_IPP=${GRAPHICAL} \ -D BUILD_opencv_python=OFF \ -D BUILD_opencv_python2=OFF \ -D BUILD_opencv_python3=OFF \ + -D OPENCV_GENERATE_PKGCONFIG=ON \ -D CMAKE_INSTALL_PREFIX=$HOME/usr \ + -D OPENCV_ENABLE_NONFREE=ON \ -D OPENCV_EXTRA_MODULES_PATH=../../opencv_contrib-${OPENCV_VERSION}/modules .. make -j8 make install && touch $HOME/usr/installed-${OPENCV_VERSION} From 900a820a3650faeb5c80775fe09877e54af619b1 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Wed, 22 May 2019 09:13:00 +0200 Subject: [PATCH 46/47] docs: update to remove Gitter and replace with Slack, and update copyright dates Signed-off-by: Ron Evans --- LICENSE.txt | 2 +- README.md | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 9e3d2b6b..d4c47367 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -186,7 +186,7 @@ APPENDIX: How to apply the Apache License to your work. same "printed page" as the copyright notice for easier identification within third-party archives. -Copyright (c) 2013-2018 The Hybrid Group +Copyright (c) 2013-2019 The Hybrid Group Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/README.md b/README.md index 6f14e6b2..19bb5a25 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,12 @@ [![Coverage Status](https://codecov.io/gh/hybridgroup/gobot/branch/dev/graph/badge.svg)](https://codecov.io/gh/hybridgroup/gobot) [![Go Report Card](https://goreportcard.com/badge/hybridgroup/gobot)](https://goreportcard.com/report/hybridgroup/gobot) [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://github.com/hybridgroup/gobot/blob/master/LICENSE.txt) -[![Gitter chat](https://badges.gitter.im/gitterHQ/gitter.png)](https://gitter.im/hybridgroup/gobot) -Gobot (http://gobot.io/) is a framework using the Go programming language (http://golang.org/) for robotics, physical computing, and the Internet of Things. +Gobot (https://gobot.io/) is a framework using the Go programming language (https://golang.org/) for robotics, physical computing, and the Internet of Things. It provides a simple, yet powerful way to create solutions that incorporate multiple, different hardware devices at the same time. -Want to use Javascript robotics? Check out our sister project Cylon.js (http://cylonjs.com/) - -Want to use Ruby on robots? Check out our sister project Artoo (http://artoo.io) +Want to run Go directly on microcontrollers? Check out our sister project TinyGo (https://tinygo.org/) ## Getting Started @@ -336,14 +333,15 @@ Gobot uses the Gort [http://gort.io](http://gort.io) Command Line Interface (CLI Gobot also has its own CLI to generate new platforms, adaptors, and drivers. You can check it out in the `/cli` directory. ## Documentation -We're always adding documentation to our web site at http://gobot.io/ please check there as we continue to work on Gobot +We're always adding documentation to our web site at https://gobot.io/ please check there as we continue to work on Gobot Thank you! ## Need help? -* Join our mailing list: https://groups.google.com/forum/#!forum/gobotio * Issues: https://github.com/hybridgroup/gobot/issues -* twitter: [@gobotio](https://twitter.com/gobotio) +* Twitter: [@gobotio](https://twitter.com/gobotio) +* Slack: [https://gophers.slack.com/messages/C0N5HDB08](https://gophers.slack.com/messages/C0N5HDB08) +* Mailing list: https://groups.google.com/forum/#!forum/gobotio ## Contributing For our contribution guidelines, please go to [https://github.com/hybridgroup/gobot/blob/master/CONTRIBUTING.md @@ -353,6 +351,6 @@ For our contribution guidelines, please go to [https://github.com/hybridgroup/go Gobot is released with a Contributor Code of Conduct. By participating in this project you agree to abide by its terms. [You can read about it here](https://github.com/hybridgroup/gobot/tree/master/CODE_OF_CONDUCT.md). ## License -Copyright (c) 2013-2018 The Hybrid Group. Licensed under the Apache 2.0 license. +Copyright (c) 2013-2019 The Hybrid Group. Licensed under the Apache 2.0 license. The Contributor Covenant is released under the Creative Commons Attribution 4.0 International Public License, which requires that attribution be included. From d7f64206c40ab1751a292f259132f296cb443132 Mon Sep 17 00:00:00 2001 From: Ron Evans Date: Wed, 22 May 2019 11:33:26 +0200 Subject: [PATCH 47/47] release: update for v1.13.0 release Signed-off-by: Ron Evans --- CHANGELOG.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ version.go | 2 +- 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e6ee7d5..b4dc8228 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,64 @@ +1.13.0 +--- +* **api** + * Initial stab at Robot-based work +* **build** + * correct package version as suggested by @dlisin thanks + * only build last 2 versions of Go plus tip for CI + * Update dep script for AppVeyor + * update deps to latest versions of dependencies for GoCV and others + * Update Gopkg and add test dep to Travis YML + * update OpenCV build script for OpenCV 4.1.0 +* **docs** + * update to remove Gitter and replace with Slack, and update copyright dates +* **example** + * add missing nobuild header +* **gpio** + * Add SparkFun’s EasyDriver (and BigEasyDriver) + * Add unit tests for TH02 & Minor improvement + * Added rudiementary support for TH02 Grove Sensor + * pwm_pin - Fix DutyCycle() parse error, need to trim off trailing '\n' before calling strconv.Atoi(), as other functions in this package do + * Simplify code as suggested in #617 +* **grovepi** + * add mutex to control transactionality of the device communication +* **i2c** + * add 128x32 and 96x16 sizes to the i2c ssd1306 driver + * build out the ccs811 driver + * update PCA9685 driver to use same protocol as Adafruit Python lib +* **leapmotion** + * Parser error in Pointable.Bases: Write test and fix + * Update gobot leap platform to support Leap Motion API v6 +* **mavlink** + * fix mavlink README to use correct example code +* **mqtt** + * Add some new MQTT adaptor functions with QOS + * Allow setting QoS on MTT adaptor + * make tests run correctly even when a local MQTT server is in fact running + * Do not skip verification of root CA certificates by default InsecureSkipVerify +* **nats** + * Update Go NATS client library import +* **opencv** + * minor updates to opencv README + * update to OpenCV 4.1.0 +* **sphero** + * Added methods to read Sphero Power States + * Added some new features to the sphero ollie, bb-8 and sprkplus +* **spi** + * correct param used for APA102 Draw() method + * Stop using Red parameter for brightness value +* **tello** + * add direct vector access + * add example with keyboard + * Change fps to 60 + * Check for error immediately and skip publish if error occurred + * update FlightData struct +* **up2** + * add support for built-in LEDs + * correct i2c default bus information to match correct values + * finalize docs for UP2 config steps + * update README to include more complete setup information + * useful constant values to access the built-in LEDs + 1.12.0 --- * **api** diff --git a/version.go b/version.go index 66db957c..f3d8a693 100644 --- a/version.go +++ b/version.go @@ -1,6 +1,6 @@ package gobot -const version = "1.12.0" +const version = "1.13.0" // Version returns the current Gobot version func Version() string {