commit bc8776440f6f51828faad82f0180ce109f222ae7 Author: Karan Misra Date: Sat Dec 7 23:11:06 2013 +0530 initial commit diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e3d91ac --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2013 Karan Misra + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of +the Software, and to permit persons to whom the Software is furnished to do so, +subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS +FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..180ebe9 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# go-rpi + +Use various sensors on the RaspberryPi with Golang (like a ninja!) diff --git a/doc.go b/doc.go new file mode 100644 index 0000000..cb83f5d --- /dev/null +++ b/doc.go @@ -0,0 +1,2 @@ +// Package rpi provides modules which will help gophers deal with various sensors. +package rpi diff --git a/i2c/i2c.go b/i2c/i2c.go new file mode 100644 index 0000000..7166292 --- /dev/null +++ b/i2c/i2c.go @@ -0,0 +1,284 @@ +// Package i2c enables gophers i2c speaking ability. +package i2c + +import ( + "fmt" + "os" + "reflect" + "sync" + "syscall" + "time" + "unsafe" +) + +const ( + delay = 20 + + slaveCmd = 0x0703 + rdrwCmd = 0x0707 + + I2C_M_RD = 0x0001 +) + +type Bus interface { + ReadByte(addr byte) (value byte, err error) + WriteByte(addr, value byte) error + WriteBytes(addr byte, value []byte) error + + ReadFromReg(addr, reg byte, value []byte) (err error) + ReadByteFromReg(addr, reg byte) (value byte, err error) + ReadInt(addr, reg byte) (value int, err error) + + WriteToReg(addr, reg, value byte) error +} + +type i2c_msg struct { + addr uint16 + flags uint16 + len uint16 + buf uintptr +} + +type i2c_rdwr_ioctl_data struct { + msgs uintptr + nmsg uint32 +} + +var busMap map[byte]*bus +var busMapLock sync.Mutex +var Default Bus + +type bus struct { + file *os.File + addr byte + mu sync.Mutex +} + +func init() { + busMap = make(map[byte]*bus) + var err error + Default, err = NewBus(1) + if err != nil { + panic(err) + } +} + +// NewBus creates a new I2C bus interface. The l variable +// controls which bus we connect to. +// +// For the newer RaspberryPi, the number is 1 (earlier model uses 0.) +func NewBus(l byte) (Bus, error) { + busMapLock.Lock() + defer busMapLock.Unlock() + + var b *bus + + if b = busMap[l]; b == nil { + b = new(bus) + var err error + if b.file, err = os.OpenFile(fmt.Sprintf("/dev/i2c-%v", l), os.O_RDWR, os.ModeExclusive); err != nil { + return nil, err + } + busMap[l] = b + } + + return b, nil +} + +func (b *bus) setAddress(addr byte) (err error) { + if addr != b.addr { + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, b.file.Fd(), slaveCmd, uintptr(addr)); errno != 0 { + err = syscall.Errno(errno) + return + } + + b.addr = addr + } + + return +} + +// Read a byte from the given address. +func (b *bus) ReadByte(addr byte) (value byte, err error) { + b.mu.Lock() + defer b.mu.Unlock() + + if err = b.setAddress(addr); err != nil { + return + } + + bytes := make([]byte, 1) + n, err := b.file.Read(bytes) + + if n != 1 { + err = fmt.Errorf("i2c: Unexpected number (%v) of bytes read", n) + } + + value = bytes[0] + + return +} + +// Write a byte to the given address. +func (b *bus) WriteByte(addr, value byte) (err error) { + b.mu.Lock() + defer b.mu.Unlock() + + if err = b.setAddress(addr); err != nil { + return + } + + n, err := b.file.Write([]byte{value}) + + if n != 1 { + err = fmt.Errorf("i2c: Unexpected number (%v) of bytes written in WriteByte", n) + } + + return +} + +// Write a bunch of bytes ot the given address. +func (b *bus) WriteBytes(addr byte, value []byte) error { + b.mu.Lock() + defer b.mu.Unlock() + + if err := b.setAddress(addr); err != nil { + return err + } + + for i := range value { + n, err := b.file.Write([]byte{value[i]}) + + if n != 1 { + return fmt.Errorf("i2c: Unexpected number (%v) of bytes written in WriteBytes", n) + } + if err != nil { + return err + } + + time.Sleep(delay * time.Millisecond) + } + + return nil +} + +// Read a bunch of bytes (len(value)) from the given address and register. +func (b *bus) ReadFromReg(addr, reg byte, value []byte) (err error) { + b.mu.Lock() + defer b.mu.Unlock() + + if err = b.setAddress(addr); err != nil { + return + } + + hdrp := (*reflect.SliceHeader)(unsafe.Pointer(&value)) + + var messages [2]i2c_msg + messages[0].addr = uint16(addr) + messages[0].flags = 0 + messages[0].len = 1 + messages[0].buf = uintptr(unsafe.Pointer(®)) + + messages[1].addr = uint16(addr) + messages[1].flags = I2C_M_RD + messages[1].len = uint16(len(value)) + messages[1].buf = uintptr(unsafe.Pointer(hdrp.Data)) + + var packets i2c_rdwr_ioctl_data + + packets.msgs = uintptr(unsafe.Pointer(&messages)) + packets.nmsg = 2 + + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, b.file.Fd(), rdrwCmd, uintptr(unsafe.Pointer(&packets))); errno != 0 { + return syscall.Errno(errno) + } + + return nil +} + +// Read a byte from the given address and register. +func (b *bus) ReadByteFromReg(addr, reg byte) (value byte, err error) { + buf := make([]byte, 1) + if err = b.ReadFromReg(addr, reg, buf); err != nil { + return + } + value = buf[0] + return +} + +// Read a int from the given address and register. +func (b *bus) ReadInt(addr, reg byte) (value int, err error) { + var buf = make([]byte, 2) + if err = b.ReadFromReg(addr, reg, buf); err != nil { + return + } + value = int((int(buf[0]) << 8) | int(buf[1])) + return +} + +// Write a byte to the given address and register. +func (b *bus) WriteToReg(addr, reg, value byte) (err error) { + b.mu.Lock() + defer b.mu.Unlock() + + if err = b.setAddress(addr); err != nil { + return + } + + var outbuf [2]byte + var messages i2c_msg + messages.addr = uint16(addr) + messages.flags = 0 + messages.len = uint16(len(outbuf)) + messages.buf = uintptr(unsafe.Pointer(&outbuf)) + + outbuf[0] = reg + outbuf[1] = value + + var packets i2c_rdwr_ioctl_data + + packets.msgs = uintptr(unsafe.Pointer(&messages)) + packets.nmsg = 1 + + if _, _, errno := syscall.Syscall(syscall.SYS_IOCTL, b.file.Fd(), rdrwCmd, uintptr(unsafe.Pointer(&packets))); errno != 0 { + err = syscall.Errno(errno) + return + } + + return +} + +// Read a byte from the given address. +func ReadByte(addr byte) (value byte, err error) { + return Default.ReadByte(addr) +} + +// Write a byte to the given address. +func WriteByte(addr, value byte) (err error) { + return Default.WriteByte(addr, value) +} + +// Write a bunch of bytes ot the given address. +func WriteBytes(addr byte, value []byte) error { + return Default.WriteBytes(addr, value) +} + +// Read a bunch of bytes (len(value)) from the given address and register. +func ReadFromReg(addr, reg byte, value []byte) (err error) { + return Default.ReadFromReg(addr, reg, value) +} + +// Read a byte from the given address and register. +func ReadByteFromReg(addr, reg byte) (value byte, err error) { + return Default.ReadByteFromReg(addr, reg) +} + +// Read a int from the given address and register. +func ReadInt(addr, reg byte) (value int, err error) { + return Default.ReadInt(addr, reg) +} + +// Write a byte to the given address and register. +func WriteToReg(addr, reg, value byte) (err error) { + return Default.WriteToReg(addr, reg, value) +} diff --git a/samples/bmp085.go b/samples/bmp085.go new file mode 100644 index 0000000..f90c64e --- /dev/null +++ b/samples/bmp085.go @@ -0,0 +1,38 @@ +package main + +import ( + "log" + "time" + + "github.com/kid0m4n/go-rpi/i2c" + "github.com/kid0m4n/go-rpi/sensor/bmp085" +) + +func main() { + bus, err := i2c.NewBus(1) + if err != nil { + log.Panic(err) + } + baro := bmp085.New(bus) + defer baro.Close() + + for { + temp, err := baro.Temperature() + if err != nil { + log.Panic(err) + } + log.Printf("Temp is %v", temp) + pressure, err := baro.Pressure() + if err != nil { + log.Panic(err) + } + log.Printf("Pressure is %v", pressure) + altitude, err := baro.Altitude() + if err != nil { + log.Panic(err) + } + log.Printf("Altitude is %v", altitude) + + time.Sleep(500 * time.Millisecond) + } +} diff --git a/samples/lsm303.go b/samples/lsm303.go new file mode 100644 index 0000000..44008f2 --- /dev/null +++ b/samples/lsm303.go @@ -0,0 +1,28 @@ +package main + +import ( + "log" + "time" + + "github.com/kid0m4n/go-rpi/i2c" + "github.com/kid0m4n/go-rpi/sensor/lsm303" +) + +func main() { + bus, err := i2c.NewBus(1) + if err != nil { + log.Panic(err) + } + mems := lsm303.New(bus) + defer mems.Close() + + for { + heading, err := mems.Heading() + if err != nil { + log.Panic(err) + } + log.Printf("Heading is %v", heading) + + time.Sleep(500 * time.Millisecond) + } +} diff --git a/sensor/bmp085/bmp085.go b/sensor/bmp085/bmp085.go new file mode 100644 index 0000000..b31c696 --- /dev/null +++ b/sensor/bmp085/bmp085.go @@ -0,0 +1,462 @@ +// Package bmp085 allows interfacing with Bosch BMP085 barometric pressure sensor. This sensor +// has the ability to provided compensated temperature and pressure readings. +package bmp085 + +import ( + "log" + "math" + "sync" + "time" + + "github.com/kid0m4n/go-rpi/i2c" +) + +const ( + address = 0x77 + + calAc1 = 0xAA + calAc2 = 0xAC + calAc3 = 0xAE + calAc4 = 0xB0 + calAc5 = 0xB2 + calAc6 = 0xB4 + calB1 = 0xB6 + calB2 = 0xB8 + calMB = 0xBA + calMC = 0xBC + calMD = 0xBE + control = 0xF4 + tempData = 0xF6 + pressureData = 0xF6 + readTempCmd = 0x2E + readPressureCmd = 0x34 + + tempReadDelay = 5 * time.Millisecond + + p0 = 101325 + + pollDelay = 250 +) + +type BMP085 interface { + SetPollDelay(delay int) + + Temperature() (temp float64, err error) + Pressure() (pressure int, err error) + Altitude() (altitude float64, err error) + + Run() error + Close() +} + +type bmp085 struct { + bus i2c.Bus + oss uint + + ac1, ac2, ac3 int16 + ac4, ac5, ac6 uint16 + b1, b2, mb, mc, md int16 + b5 int32 + calibrated bool + cmu *sync.RWMutex + + poll int + temps chan uint16 + pressures chan int32 + altitudes chan float64 + quit chan struct{} + + debug bool +} + +var Default = New(i2c.Default) + +// New creates a new BMP085 interface. The bus variable controls +// the I2C bus used to communicate with the device. +func New(bus i2c.Bus) BMP085 { + return &bmp085{bus: bus, cmu: new(sync.RWMutex), poll: pollDelay} +} + +// SetPollDelay sets the delay between runs of the data acquisition loop. +func (d *bmp085) SetPollDelay(delay int) { + d.poll = delay +} + +func (d *bmp085) calibrate() (err error) { + d.cmu.RLock() + if d.calibrated { + d.cmu.RUnlock() + return + } + d.cmu.RUnlock() + + d.cmu.Lock() + defer d.cmu.Unlock() + + readInt16 := func(reg byte) (value int16, err error) { + var v int + if v, err = d.bus.ReadInt(address, reg); err != nil { + return + } + value = int16(v) + return + } + + readUInt16 := func(reg byte) (value uint16, err error) { + var v int + if v, err = d.bus.ReadInt(address, reg); err != nil { + return + } + value = uint16(v) + return + } + + d.ac1, err = readInt16(calAc1) + if err != nil { + return + } + d.ac2, err = readInt16(calAc2) + if err != nil { + return + } + d.ac3, err = readInt16(calAc3) + if err != nil { + return + } + d.ac4, err = readUInt16(calAc4) + if err != nil { + return + } + d.ac5, err = readUInt16(calAc5) + if err != nil { + return + } + d.ac6, err = readUInt16(calAc6) + if err != nil { + return + } + d.b1, err = readInt16(calB1) + if err != nil { + return + } + d.b2, err = readInt16(calB2) + if err != nil { + return + } + d.mb, err = readInt16(calMB) + if err != nil { + return + } + d.mc, err = readInt16(calMC) + if err != nil { + return + } + d.md, err = readInt16(calMD) + if err != nil { + return + } + + d.calibrated = true + + if d.debug { + log.Print("bmp085: calibration data retrieved") + log.Printf("bmp085: param AC1 = %v", d.ac1) + log.Printf("bmp085: param AC2 = %v", d.ac2) + log.Printf("bmp085: param AC3 = %v", d.ac3) + log.Printf("bmp085: param AC4 = %v", d.ac4) + log.Printf("bmp085: param AC5 = %v", d.ac5) + log.Printf("bmp085: param AC6 = %v", d.ac6) + log.Printf("bmp085: param B1 = %v", d.b1) + log.Printf("bmp085: param B2 = %v", d.b2) + log.Printf("bmp085: param MB = %v", d.mb) + log.Printf("bmp085: param MC = %v", d.mc) + log.Printf("bmp085: param MD = %v", d.md) + } + + return +} + +func (d *bmp085) readUncompensatedTemp() (temp uint16, err error) { + if err = d.bus.WriteToReg(address, control, readTempCmd); err != nil { + return + } + time.Sleep(tempReadDelay) + var t int + t, err = d.bus.ReadInt(address, tempData) + if err != nil { + return + } + temp = uint16(t) + return +} + +func (d *bmp085) calcTemp(utemp uint16) uint16 { + x1 := ((int(utemp) - int(d.ac6)) * int(d.ac5)) >> 15 + x2 := (int(d.mc) << 11) / (x1 + int(d.md)) + + d.cmu.Lock() + d.b5 = int32(x1 + x2) + d.cmu.Unlock() + + return uint16((d.b5 + 8) >> 4) +} + +func (d *bmp085) measureTemp() (temp uint16, err error) { + if err = d.calibrate(); err != nil { + return + } + + var utemp uint16 + if utemp, err = d.readUncompensatedTemp(); err != nil { + return + } + if d.debug { + log.Printf("bcm085: uncompensated temp: %v", utemp) + } + temp = d.calcTemp(utemp) + if d.debug { + log.Printf("bcm085: compensated temp %v", temp) + } + return +} + +// Return temperature reading. +func (d *bmp085) Temperature() (temp float64, err error) { + + select { + case t := <-d.temps: + temp = float64(t) / 10 + return + default: + if d.debug { + log.Print("bcm085: no temps available... measuring") + } + var t uint16 + t, err = d.measureTemp() + if err != nil { + return + } + temp = float64(t) / 10 + return + } +} + +func (d *bmp085) readUncompensatedPressure() (pressure uint32, err error) { + if err = d.bus.WriteToReg(address, control, byte(readPressureCmd+(d.oss<<6))); err != nil { + return + } + time.Sleep(time.Duration(2+(3<> (8 - d.oss) + + return +} + +func (d *bmp085) calcPressure(upressure uint32) (p int32) { + var x1, x2, x3 int32 + + l := func(s string, v interface{}) { + if d.debug { + log.Printf("bcm085: %v = %v", s, v) + } + } + + b6 := d.b5 - 4000 + l("b6", b6) + + // Calculate b3 + x1 = (int32(d.b2) * int32(b6*b6) >> 12) >> 11 + x2 = (int32(d.ac2) * b6) >> 11 + x3 = x1 + x2 + b3 := (((int32(d.ac1)*4 + x3) << d.oss) + 2) >> 2 + + l("x1", x1) + l("x2", x2) + l("x3", x3) + l("b3", b3) + + // Calculate b4 + x1 = (int32(d.ac3) * b6) >> 13 + x2 = (int32(d.b1) * ((b6 * b6) >> 12)) >> 16 + x3 = ((x1 + x2) + 2) >> 2 + b4 := (uint32(d.ac4) * uint32(x3+32768)) >> 15 + + l("x1", x1) + l("x2", x2) + l("x3", x3) + l("b4", b4) + + b7 := (uint32(upressure-uint32(b3)) * (50000 >> d.oss)) + if b7 < 0x80000000 { + p = int32((b7 << 1) / b4) + } else { + p = int32((b7 / b4) << 1) + } + l("b7", b7) + l("p", p) + + x1 = (p >> 8) * (p >> 8) + x1 = (x1 * 3038) >> 16 + x2 = (-7357 * p) >> 16 + p += (x1 + x2 + 3791) >> 4 + + l("x1", x1) + l("x2", x2) + l("x3", x3) + l("p", p) + + return +} + +func (d *bmp085) calcAltitude(pressure int32) float64 { + return 44330 * (1 - math.Pow(float64(pressure)/p0, 0.190295)) +} + +func (d *bmp085) measurePressureAndAltitude() (pressure int32, altitude float64, err error) { + if err = d.calibrate(); err != nil { + return + } + + var upressure uint32 + if upressure, err = d.readUncompensatedPressure(); err != nil { + return + } + if d.debug { + log.Printf("bcm085: uncompensated pressure: %v", upressure) + } + pressure = d.calcPressure(upressure) + if d.debug { + log.Printf("bcm085: compensated pressure %v", pressure) + } + altitude = d.calcAltitude(pressure) + if d.debug { + log.Printf("bcm085: calculated altitude %v", altitude) + } + return +} + +// Return pressure reading. +func (d *bmp085) Pressure() (pressure int, err error) { + if err = d.calibrate(); err != nil { + return + } + + select { + case p := <-d.pressures: + pressure = int(p) + return + default: + if d.debug { + log.Print("bcm085: no pressures available... measuring") + } + var p int32 + p, _, err = d.measurePressureAndAltitude() + if err != nil { + return + } + pressure = int(p) + return + } +} + +// Return altitude reading. +func (d *bmp085) Altitude() (altitude float64, err error) { + if err = d.calibrate(); err != nil { + return + } + + select { + case altitude = <-d.altitudes: + return + default: + if d.debug { + log.Print("bcm085: no altitudes available... measuring") + } + _, altitude, err = d.measurePressureAndAltitude() + if err != nil { + return + } + return + } +} + +// Start the sensor data acquisition loop. +func (d *bmp085) Run() (err error) { + go func() { + d.quit = make(chan struct{}) + timer := time.Tick(time.Duration(d.poll) * time.Millisecond) + + var temp uint16 + var pressure int32 + var altitude float64 + + for { + select { + case <-timer: + t, err := d.measureTemp() + if err == nil { + temp = t + } + if err == nil && d.temps == nil { + d.temps = make(chan uint16) + } + p, a, err := d.measurePressureAndAltitude() + if err == nil { + pressure = p + altitude = a + } + if err == nil && d.pressures == nil && d.altitudes == nil { + d.pressures = make(chan int32) + d.altitudes = make(chan float64) + } + case d.temps <- temp: + case d.pressures <- pressure: + case d.altitudes <- altitude: + case <-d.quit: + d.temps = nil + d.pressures = nil + d.altitudes = nil + return + } + } + }() + + return +} + +// Close. +func (d *bmp085) Close() { + if d.quit != nil { + d.quit <- struct{}{} + } +} + +// Return temperature reading. +func Temperature() (temp float64, err error) { + return Default.Temperature() +} + +// Return pressure reading. +func Pressure() (pressure int, err error) { + return Default.Pressure() +} + +// Return altitude reading. +func Altitude() (altitude float64, err error) { + return Default.Altitude() +} + +// Start the sensor data acquisition loop. +func Run() (err error) { + return Default.Run() +} + +// Close. +func Close() { + Default.Close() +} diff --git a/sensor/doc.go b/sensor/doc.go new file mode 100644 index 0000000..fa5d195 --- /dev/null +++ b/sensor/doc.go @@ -0,0 +1,2 @@ +// Package sensor contains the various sensors modules for use on your Raspberry Pi. +package sensor diff --git a/sensor/lsm303/lsm303.go b/sensor/lsm303/lsm303.go new file mode 100644 index 0000000..21c9d03 --- /dev/null +++ b/sensor/lsm303/lsm303.go @@ -0,0 +1,195 @@ +// Package lsm303 allows interfacing with the LSM303 magnetometer. +package lsm303 + +import ( + "log" + "math" + "sync" + "time" + + "github.com/kid0m4n/go-rpi/i2c" +) + +const ( + magAddress = 0x1E + + magConfigRegA = 0x00 + + MagHz75 = 0x00 + Mag1Hz5 = 0x04 + Mag3Hz = 0x08 + Mag7Hz5 = 0x0C + Mag15Hz = 0x10 + Mag30Hz = 0x14 + Mag75Hz = 0x18 + MagNormal = 0x00 + MagPositiveBias = 0x01 + MagNegativeBias = 0x02 + + MagCRADefault = Mag15Hz | MagNormal + + magModeReg = 0x02 + + MagContinuous = 0x00 + MagSleep = 0x03 + + MagMRDefault = MagContinuous + + magDataSignal = 0x02 + magData = 0x03 + + pollDelay = 250 +) + +type LSM303 interface { + SetPollDelay(delay int) + + Heading() (heading float64, err error) + + Run() error + Close() error +} + +type lsm303 struct { + bus i2c.Bus + + initialized bool + mu *sync.RWMutex + + headings chan float64 + + poll int + quit chan struct{} + + debug bool +} + +var Default = New(i2c.Default) + +// New creates a new LSM303 interface. The bus variable controls +// the I2C bus used to communicate with the device. +func New(bus i2c.Bus) LSM303 { + return &lsm303{bus: bus, mu: new(sync.RWMutex), poll: pollDelay} +} + +// Initialize the device +func (d *lsm303) setup() (err error) { + d.mu.RLock() + if d.initialized { + d.mu.RUnlock() + return + } + d.mu.RUnlock() + + d.mu.Lock() + defer d.mu.Unlock() + + if err = d.bus.WriteToReg(magAddress, magConfigRegA, MagCRADefault); err != nil { + return + } + if err = d.bus.WriteToReg(magAddress, magModeReg, MagMRDefault); err != nil { + return + } + + d.initialized = true + + return +} + +// SetPollDelay sets the delay between runs of the data acquisition loop. +func (d *lsm303) SetPollDelay(delay int) { + d.poll = delay +} + +func (d *lsm303) measureHeading() (heading float64, err error) { + if err = d.setup(); err != nil { + return + } + + if _, err = d.bus.ReadByteFromReg(magAddress, magDataSignal); err != nil { + return + } + + data := make([]byte, 6) + if err = d.bus.ReadFromReg(magAddress, magData, data); err != nil { + return + } + + x := int16(data[0])<<8 | int16(data[1]) + y := int16(data[2])<<8 | int16(data[3]) + + heading = math.Atan2(float64(y), float64(x)) / math.Pi * 180 + if heading < 0 { + heading += 360 + } + + return +} + +// Return heading [0, 360). +func (d *lsm303) Heading() (heading float64, err error) { + select { + case heading = <-d.headings: + return + default: + if d.debug { + log.Print("lsm303: no headings available... measuring") + } + return d.measureHeading() + } +} + +// Start the sensor data acquisition loop. +func (d *lsm303) Run() (err error) { + go func() { + d.quit = make(chan struct{}) + + timer := time.Tick(time.Duration(d.poll) * time.Millisecond) + + var heading float64 + + for { + select { + case <-timer: + h, err := d.measureHeading() + if err == nil { + heading = h + } + if err == nil && d.headings == nil { + d.headings = make(chan float64) + } + case d.headings <- heading: + case <-d.quit: + d.headings = nil + return + } + + } + }() + + return +} + +// Close the sensor data acquisition loop and put the LSM303 into sleep mode. +func (d *lsm303) Close() (err error) { + if d.quit != nil { + d.quit <- struct{}{} + } + err = d.bus.WriteToReg(magAddress, magModeReg, MagSleep) + return +} + +// Return heading [0, 360). +func Heading() (heading float64, err error) { + return Default.Heading() +} + +// Start the sensor data acquisition loop. +func Run() (err error) { + return Default.Run() +} + +// Close the sensor data acquisition loop and put the LSM303 into sleep mode. +func Close() (err error) { + return Default.Close() +}