diff --git a/bmxx80/bmxx80.go b/bmxx80/bmxx80.go index 3885ea2..dda92a1 100644 --- a/bmxx80/bmxx80.go +++ b/bmxx80/bmxx80.go @@ -20,6 +20,9 @@ import ( "periph.io/x/conn/v3/spi" ) +// I2CAddr i2c default address. +const I2CAddr uint16 = 0x77 + // Oversampling affects how much time is taken to measure each of temperature, // pressure and humidity. // diff --git a/firmata/analog_mapping.go b/firmata/analog_mapping.go index 83a9944..b9cc1fd 100644 --- a/firmata/analog_mapping.go +++ b/firmata/analog_mapping.go @@ -10,6 +10,22 @@ type AnalogMappingResponse struct { DigitalPinToAnalog map[uint8]uint8 } +func ParseAnalogMappingResponse(data []byte) AnalogMappingResponse { + var response = AnalogMappingResponse{ + AnalogPinToDigital: []uint8{}, + DigitalPinToAnalog: map[uint8]uint8{}, + } + + for i := 0; i < len(data); i++ { + if data[i] != CapabilityResponsePinDelimiter { + response.DigitalPinToAnalog[uint8(i)] = uint8(len(response.AnalogPinToDigital)) + response.AnalogPinToDigital = append(response.AnalogPinToDigital, uint8(i)) + } + } + + return response +} + func (a AnalogMappingResponse) String() string { str := bytes.Buffer{} for analogPin, digitalPin := range a.AnalogPinToDigital { diff --git a/firmata/capability.go b/firmata/capability.go index c430a2e..71d2978 100644 --- a/firmata/capability.go +++ b/firmata/capability.go @@ -33,6 +33,30 @@ type CapabilityResponse struct { SupportedPinModes [][]pin.Func } +func ParseCapabilityResponse(data []byte) CapabilityResponse { + var response = CapabilityResponse{ + PinToModeToResolution: []map[pin.Func]uint8{{}}, + SupportedPinModes: [][]pin.Func{{}}, + } + + var pindex = 0 + for i := 0; i < len(data); { + if data[i] == CapabilityResponsePinDelimiter { + response.PinToModeToResolution = append(response.PinToModeToResolution, map[pin.Func]uint8{}) + response.SupportedPinModes = append(response.SupportedPinModes, []pin.Func{}) + i += 1 + pindex++ + } else { + pinFunc := pinModeToFuncMap[data[i]] + response.PinToModeToResolution[pindex][pinFunc] = data[i+1] + response.SupportedPinModes[pindex] = append(response.SupportedPinModes[pindex], pinFunc) + i += 2 + } + } + + return response +} + func (c CapabilityResponse) String() string { str := bytes.Buffer{} for p, modeMap := range c.PinToModeToResolution { diff --git a/firmata/client.go b/firmata/client.go index 03a5549..0af9ace 100644 --- a/firmata/client.go +++ b/firmata/client.go @@ -15,8 +15,10 @@ import ( // These max values are for data bytes as, within firmata, data is 7 bits long. const ( - MaxUInt8 uint8 = (1<<8 - 1) >> 1 - MaxUInt16 uint16 = (1<<16 - 1) >> 2 + MaxUInt8 uint8 = (1<<8 - 1) >> (8 / 8) + MaxUInt16 uint16 = (1<<16 - 1) >> (16 / 8) + MaxUInt24 uint32 = (1<<24 - 1) >> (24 / 8) + MaxUInt32 uint32 = (1<<32 - 1) >> (32 / 8) ) var commandResponseMap = map[SysExCmd]SysExCmd{ @@ -28,7 +30,7 @@ var commandResponseMap = map[SysExCmd]SysExCmd{ type ClientI interface { SendSysEx(SysExCmd, ...byte) (chan []byte, error) SendReset() error - ExtendedReportAnalogPin(uint8, int) error + ExtendedReportAnalogPin(uint8, uint8) error CapabilityQuery() (chan CapabilityResponse, error) PinStateQuery(uint8) (chan PinStateResponse, error) ReportFirmware() (chan FirmwareReport, error) @@ -124,6 +126,10 @@ func (c *Client) Start() error { c.started = true c.mu.Unlock() + if err := c.SendReset(); err != nil { + return err + } + if b, ok := c.board.(flusher); ok { b.Flush() } else if b, ok := c.board.(flusherErr); ok { @@ -132,12 +138,16 @@ func (c *Client) Start() error { } } - firmChannel := make(chan []byte, 1) // Don't call ReportFirmware as it is automatic, but we want to register a listener for it. + firmChannel := make(chan []byte, 1) c.mu.Lock() c.responseChannels[SysExReportFirmware] = []chan []byte{firmChannel} c.mu.Unlock() - report := c.parseReportFirmware(firmChannel) + + responseChannel := make(chan FirmwareReport, 1) + go func() { + responseChannel <- ParseFirmwareReport(<-firmChannel) + }() go func() { err := c.responseWatcher() @@ -146,7 +156,22 @@ func (c *Client) Start() error { } }() - fmt.Println("Firmware Info:", <-report) + // Do not move on until we receive our firmware report. This is a signal the device is ready. + // Anything sent before we receive this message will not be read by the device. + fmt.Println("Firmware Info:", <-responseChannel) + + analogMappingQuery, err := c.SendAnalogMappingQuery() + if err != nil { + return err + } + + capabilityQuery, err := c.CapabilityQuery() + if err != nil { + return err + } + + fmt.Println(<-analogMappingQuery) + fmt.Println(<-capabilityQuery) return nil } @@ -156,8 +181,6 @@ func (c *Client) write(payload []byte, withinMutex func()) error { c.mu.Lock() defer c.mu.Unlock() - //fmt.Println(SprintHexArray(payload)) - // Write to the board. _, err := c.board.Write(payload) if err != nil { @@ -280,6 +303,8 @@ func (c *Client) responseWatcher() (err error) { resp <- data close(resp) case cmd == SysExStringData: + // TODO: The Firmata spec defines this as mostly an error statement. + // Should we fail on receiving a string message? fmt.Printf("device: [%s]\n", TwoByteString(data)) default: str := "" @@ -347,41 +372,18 @@ func (c *Client) CapabilityQuery() (chan CapabilityResponse, error) { return nil, err } - resp := c.parseCapabilityCommand(future) - - return resp, nil -} - -func (c *Client) parseCapabilityCommand(future chan []byte) chan CapabilityResponse { resp := make(chan CapabilityResponse, 1) go func() { data := <-future - var response = CapabilityResponse{ - PinToModeToResolution: []map[pin.Func]uint8{{}}, - SupportedPinModes: [][]pin.Func{{}}, - } - var pindex = 0 - for i := 0; i < len(data); { - if data[i] == CapabilityResponsePinDelimiter { - response.PinToModeToResolution = append(response.PinToModeToResolution, map[pin.Func]uint8{}) - response.SupportedPinModes = append(response.SupportedPinModes, []pin.Func{}) - i += 1 - pindex++ - } else { - pinFunc := pinModeToFuncMap[data[i]] - response.PinToModeToResolution[pindex][pinFunc] = data[i+1] - response.SupportedPinModes[pindex] = append(response.SupportedPinModes[pindex], pinFunc) - i += 2 - } - } + var response = ParseCapabilityResponse(data) c.cr = response resp <- response close(resp) }() - return resp + return resp, nil } func (c *Client) SendAnalogMappingQuery() (chan AnalogMappingResponse, error) { @@ -390,46 +392,18 @@ func (c *Client) SendAnalogMappingQuery() (chan AnalogMappingResponse, error) { return nil, err } - resp := c.parseAnalogMappingQuery(future) - - return resp, nil -} - -func (c *Client) parseAnalogMappingQuery(future chan []byte) chan AnalogMappingResponse { resp := make(chan AnalogMappingResponse, 1) go func() { data := <-future - var response = AnalogMappingResponse{ - AnalogPinToDigital: []uint8{}, - DigitalPinToAnalog: map[uint8]uint8{}, - } - for i := 0; i < len(data); i++ { - if data[i] != CapabilityResponsePinDelimiter { - response.DigitalPinToAnalog[uint8(i)] = uint8(len(response.AnalogPinToDigital)) - response.AnalogPinToDigital = append(response.AnalogPinToDigital, uint8(i)) - } - } + var response = ParseAnalogMappingResponse(data) c.amr = response resp <- response close(resp) }() - return resp -} - -func (c *Client) ExtendedReportAnalogPin(p uint8, value int) error { - if value > 0xFFFFFFFFFFFFFF { - return fmt.Errorf("%w: 0x0 - 0xFFFFFFFFFFFFFF", ErrValueOutOfRange) - } - - _, err := c.SendSysEx(SysExExtendedAnalog, p, uint8(value), uint8(value>>7), uint8(value>>14)) - if err != nil { - return err - } - - return nil + return resp, nil } func (c *Client) PinStateQuery(p uint8) (chan PinStateResponse, error) { @@ -438,31 +412,17 @@ func (c *Client) PinStateQuery(p uint8) (chan PinStateResponse, error) { return nil, err } - resp := c.parsePinStateQuery(future) - - return resp, nil -} - -func (c *Client) parsePinStateQuery(future chan []byte) chan PinStateResponse { resp := make(chan PinStateResponse, 1) go func() { data := <-future - var ps = PinStateResponse{ - Pin: data[0], - Mode: pinModeToFuncMap[data[1]], - State: 0, - } + var response = ParsePinStateResponse(data) - for i, b := range data[2:] { - ps.State |= int(b << (i * 7)) - } - - resp <- ps + resp <- response close(resp) }() - return resp + return resp, nil } func (c *Client) ReportFirmware() (chan FirmwareReport, error) { @@ -471,27 +431,28 @@ func (c *Client) ReportFirmware() (chan FirmwareReport, error) { return nil, err } - resp := c.parseReportFirmware(future) - - return resp, nil -} - -func (c *Client) parseReportFirmware(future chan []byte) chan FirmwareReport { resp := make(chan FirmwareReport, 1) go func() { data := <-future - var rc = FirmwareReport{ - Major: data[0], - Minor: data[1], - Name: data[2:], - } + var response = ParseFirmwareReport(data) - resp <- rc + resp <- response close(resp) }() - return resp + return resp, nil +} + +func (c *Client) ExtendedReportAnalogPin(p uint8, value uint8) error { + lsb, msb := ByteToTwoByte(value) + + _, err := c.SendSysEx(SysExExtendedAnalog, p, lsb, msb) + if err != nil { + return err + } + + return nil } func (c *Client) SetAnalogPinReporting(analogPin uint8, report bool) error { @@ -523,7 +484,7 @@ func (c *Client) SetSamplingInterval(ms uint16) error { return c.write([]byte{byte(SysExSamplingInterval), byte(ms), byte(ms >> 7)}, nil) } -// This function only supports 7-bit I2C addresses +// WriteI2CData only supports 7-bit I2C addresses func (c *Client) WriteI2CData(address uint8, restart bool, data []uint8) error { if !c.i2cStarted { return ErrI2CNotEnabled @@ -539,14 +500,14 @@ func (c *Client) WriteI2CData(address uint8, restart bool, data []uint8) error { return err } -// This function only supports 7-bit I2C addresses +// ReadI2CData only supports 7-bit I2C addresses func (c *Client) ReadI2CData(address uint8, restart bool, length uint16) error { if !c.i2cStarted { return ErrI2CNotEnabled } if length > MaxUInt16 { - return fmt.Errorf("%w: 0x0 - 0xFFFFFFFFFFFFFF", ErrValueOutOfRange) + return fmt.Errorf("%w: 0x0 - 0x%X", ErrValueOutOfRange, MaxUInt16) } byte2 := byte(I2CModeRead) @@ -560,14 +521,14 @@ func (c *Client) ReadI2CData(address uint8, restart bool, length uint16) error { return err } -// This function only supports 7-bit I2C addresses +// ReadI2CRegister only supports 7-bit I2C addresses func (c *Client) ReadI2CRegister(address uint8, restart bool, register uint8, length uint16) error { if !c.i2cStarted { return ErrI2CNotEnabled } if length > MaxUInt16 { - return fmt.Errorf("%w: 0x0 - 0xFFFFFFFFFFFFFF", ErrValueOutOfRange) + return fmt.Errorf("%w: 0x0 - 0x%X", ErrValueOutOfRange, MaxUInt16) } byte2 := byte(I2CModeRead) @@ -599,7 +560,7 @@ func (c *Client) releaseI2CAddressListener(addr uint8) { delete(c.i2cListeners, addr) } -// This function only supports 7-bit I2C addresses +// SetI2CAddressListener only supports 7-bit I2C addresses func (c *Client) SetI2CAddressListener(addr uint8, ch chan I2CPacket) (release func(), err error) { c.i2cMU.Lock() defer c.i2cMU.Unlock() diff --git a/firmata/firmatareg/firmatareg.go b/firmata/firmatareg/firmatareg.go deleted file mode 100644 index af86d3b..0000000 --- a/firmata/firmatareg/firmatareg.go +++ /dev/null @@ -1,216 +0,0 @@ -package firmatareg - -import ( - "errors" - "strconv" - "strings" - "sync" - - "periph.io/x/devices/v3/firmata" -) - -// Opener opens a handle to a firmata device. -// -// It is provided by the actual device driver. -type Opener func() (firmata.ClientI, error) - -// Ref references a Firmata device. -// -// It is returned by All() to enumerate all registered devices. -type Ref struct { - // Name of the device. - // - // It must be unique across the host. - Name string - // Aliases are the alternative names that can be used to reference this bus. - Aliases []string - // Open is the factory to open a handle to this Firmata device. - Open Opener -} - -// Open opens a firmata device by its name, an alias or its file path and returns -// a handle to it. -// -// Specify the empty string "" to get the first available device. This is the -// recommended default value unless an application knows the exact device to use. -// -// Each device can register multiple aliases, each leading to the same device handle. -// -// "file path" is a generic concept that is highly dependent on the platform -// and OS. Depending on the OS, a serial port can be `/dev/tty*` or `COM*`. -func Open(name string) (firmata.ClientI, error) { - var r *Ref - var err error - func() { - mu.Lock() - defer mu.Unlock() - if len(byName) == 0 { - err = errors.New("firmatareg: no device found; did you forget to call Init()") - return - } - if len(name) == 0 { - r = getDefault() - return - } - // Try by name, by alias, by number. - if r = byName[name]; r == nil { - r = byAlias[name] - } - }() - if err != nil { - return nil, err - } - if r == nil { - return nil, errors.New("firmatareg: can't open unknown device: " + strconv.Quote(name)) - } - return r.Open() -} - -// All returns a copy of all the registered references to all know firmata devices -// available on this host. -// -// The list is sorted by the bus name. -func All() []*Ref { - mu.Lock() - defer mu.Unlock() - out := make([]*Ref, 0, len(byName)) - for _, v := range byName { - r := &Ref{Name: v.Name, Aliases: make([]string, len(v.Aliases)), Open: v.Open} - copy(r.Aliases, v.Aliases) - out = insertRef(out, r) - } - return out -} - -// Register registers a firmata device. -// -// Registering the same device name twice is an error, e.g. o.Name(). -func Register(name string, aliases []string, o Opener) error { - if len(name) == 0 { - return errors.New("firmatareg: can't register a bus with no name") - } - if o == nil { - return errors.New("firmatareg: can't register bus " + strconv.Quote(name) + " with nil Opener") - } - if _, err := strconv.Atoi(name); err == nil { - return errors.New("firmatareg: can't register bus " + strconv.Quote(name) + " with name being only a number") - } - if strings.Contains(name, ":") { - return errors.New("firmatareg: can't register bus " + strconv.Quote(name) + " with name containing ':'") - } - for _, alias := range aliases { - if len(alias) == 0 { - return errors.New("firmatareg: can't register bus " + strconv.Quote(name) + " with an empty alias") - } - if name == alias { - return errors.New("firmatareg: can't register bus " + strconv.Quote(name) + " with an alias the same as the bus name") - } - if _, err := strconv.Atoi(alias); err == nil { - return errors.New("firmatareg: can't register bus " + strconv.Quote(name) + " with an alias that is a number: " + strconv.Quote(alias)) - } - if strings.Contains(alias, ":") { - return errors.New("firmatareg: can't register bus " + strconv.Quote(name) + " with an alias containing ':': " + strconv.Quote(alias)) - } - } - - mu.Lock() - defer mu.Unlock() - if _, ok := byName[name]; ok { - return errors.New("firmatareg: can't register bus " + strconv.Quote(name) + " twice") - } - if _, ok := byAlias[name]; ok { - return errors.New("firmatareg: can't register bus " + strconv.Quote(name) + " twice; it is already an alias") - } - for _, alias := range aliases { - if _, ok := byName[alias]; ok { - return errors.New("firmatareg: can't register bus " + strconv.Quote(name) + " twice; alias " + strconv.Quote(alias) + " is already a bus") - } - if _, ok := byAlias[alias]; ok { - return errors.New("firmatareg: can't register bus " + strconv.Quote(name) + " twice; alias " + strconv.Quote(alias) + " is already an alias") - } - } - - r := &Ref{Name: name, Aliases: make([]string, len(aliases)), Open: o} - copy(r.Aliases, aliases) - byName[name] = r - for _, alias := range aliases { - byAlias[alias] = r - } - return nil -} - -// Unregister removes a previously registered firmata device. -// -// This can happen when a firmata device is exposed via a USB device and the device -// is unplugged. -func Unregister(name string) error { - mu.Lock() - defer mu.Unlock() - r := byName[name] - if r == nil { - return errors.New("firmatareg: can't unregister unknown bus name " + strconv.Quote(name)) - } - delete(byName, name) - for _, alias := range r.Aliases { - delete(byAlias, alias) - } - return nil -} - -// - -var ( - mu sync.Mutex - byName = map[string]*Ref{} - // Caches - byNumber = map[int]*Ref{} - byAlias = map[string]*Ref{} -) - -// getDefault returns the Ref that should be used as the default bus. -func getDefault() *Ref { - var o *Ref - if len(byNumber) == 0 { - // Fallback to use byName using a lexical sort. - name := "" - for n, o2 := range byName { - if len(name) == 0 || n < name { - o = o2 - name = n - } - } - return o - } - number := int((^uint(0)) >> 1) - for n, o2 := range byNumber { - if number > n { - number = n - o = o2 - } - } - return o -} - -func insertRef(l []*Ref, r *Ref) []*Ref { - n := r.Name - i := search(len(l), func(i int) bool { return l[i].Name > n }) - l = append(l, nil) - copy(l[i+1:], l[i:]) - l[i] = r - return l -} - -// search implements the same algorithm as sort.Search(). -// -// It was extracted to not depend on sort, which depends on reflect. -func search(n int, f func(int) bool) int { - lo := 0 - for hi := n; lo < hi; { - if i := int(uint(lo+hi) >> 1); !f(i) { - lo = i + 1 - } else { - hi = i - } - } - return lo -} diff --git a/firmata/firmware.go b/firmata/firmware.go index 7a0b00c..f47e36e 100644 --- a/firmata/firmware.go +++ b/firmata/firmware.go @@ -10,6 +10,14 @@ type FirmwareReport struct { Name []byte } +func ParseFirmwareReport(data []byte) FirmwareReport { + return FirmwareReport{ + Major: data[0], + Minor: data[1], + Name: data[2:], + } +} + func (r FirmwareReport) String() string { return fmt.Sprintf("%s [%d.%d]", TwoByteString(r.Name), r.Major, r.Minor) } diff --git a/firmata/pin.go b/firmata/pin.go index 285ed12..0ea9e9e 100644 --- a/firmata/pin.go +++ b/firmata/pin.go @@ -173,13 +173,17 @@ func (p *Pin) DefaultPull() gpio.Pull { } func (p *Pin) Out(l gpio.Level) error { - // TODO: - panic("implement me") + // No need to set mode as firmata does so automatically + return p.c.SetDigitalPinValue(p.pin, l) } +const dutyMax gpio.Duty = 1<<8 - 1 + +// PWM ignores physic.Frequency as there is no way to set it through firmata func (p *Pin) PWM(duty gpio.Duty, f physic.Frequency) error { - // TODO: - panic("implement me") + // No need to set mode as firmata does so automatically + // PWM duty scaled down from 24 to 8 bits + return p.c.ExtendedReportAnalogPin(p.pin, uint8((duty>>16)&0xFF)) } func (p *Pin) Func() pin.Func { diff --git a/firmata/pin_state.go b/firmata/pin_state.go index 9815eb5..56b84fa 100644 --- a/firmata/pin_state.go +++ b/firmata/pin_state.go @@ -12,6 +12,20 @@ type PinStateResponse struct { State int } +func ParsePinStateResponse(data []byte) PinStateResponse { + var response = PinStateResponse{ + Pin: data[0], + Mode: pinModeToFuncMap[data[1]], + State: 0, + } + + for i, b := range data[2:] { + response.State |= int(b << (i * 7)) + } + + return response +} + func (p PinStateResponse) String() string { return fmt.Sprintf("pin(%d) mode(%s) state(%d)", p.Pin, p.Mode, p.State) } diff --git a/pca9685/pins.go b/pca9685/pins.go index d5f75bc..6d007a9 100644 --- a/pca9685/pins.go +++ b/pca9685/pins.go @@ -96,7 +96,7 @@ func (p *pin) Read() gpio.Level { } func (p *pin) WaitForEdge(timeout time.Duration) bool { - return false + return gpio.INVALID.WaitForEdge(timeout) } func (p *pin) Pull() gpio.Pull {