You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
devices/firmata/onewire.go

182 lines
4.0 KiB
Go

package firmata
import (
"encoding/binary"
"fmt"
"math"
"sync"
"periph.io/x/conn/v3/gpio"
"periph.io/x/conn/v3/onewire"
)
type OneWireBus struct {
c ClientI
q *Pin
mu sync.Mutex
closer func() error
cid uint16
}
func newOneWireBus(c ClientI, q *Pin, closer func() error) *OneWireBus {
return &OneWireBus{
c: c,
q: q,
closer: closer,
}
}
func (b *OneWireBus) Close() error {
return b.closer()
}
func (b *OneWireBus) String() string {
panic("implement me")
}
func (b *OneWireBus) Search(alarmOnly bool) ([]onewire.Address, error) {
b.mu.Lock()
defer b.mu.Unlock()
ch := make(chan []byte)
defer close(ch)
release, err := b.c.SetOneWireListener(b.q.pin, ch)
if err != nil {
return nil, err
}
defer release()
if alarmOnly {
if _, err := b.c.SendSysEx(SysExOneWireData, byte(OneWireInstructionSearchAlarmed), b.q.pin); err != nil {
return nil, err
}
} else {
if _, err := b.c.SendSysEx(SysExOneWireData, byte(OneWireInstructionSearch), b.q.pin); err != nil {
return nil, err
}
}
data := <-ch
ins := OneWireInstruction(data[0])
pin := data[1]
if ins != OneWireInstructionSearchAlarmedReply && ins != OneWireInstructionSearchReply {
return nil, fmt.Errorf("%w: did not receive search reply", ErrUnhandledMessage)
}
if pin != b.q.pin {
return nil, fmt.Errorf("%w: received message from wrong bus", ErrInvalidOneWirePin)
}
data = Decoder7Bit(data[2:])
addresses := make([]onewire.Address, len(data)/8)
for i := range addresses {
addresses[i] = onewire.Address(binary.LittleEndian.Uint64(data[i*8:]))
}
return addresses, nil
}
func (b *OneWireBus) Tx(w, r []byte, power onewire.Pullup) error {
if len(w) == 0 && len(r) == 0 {
return nil
}
if len(r) > math.MaxUint16 {
return fmt.Errorf("%w: cannot reach more than %d bytes", ErrValueOutOfRange, math.MaxUint16)
}
b.mu.Lock()
defer b.mu.Unlock()
ch := make(chan []byte)
defer close(ch)
release, err := b.c.SetOneWireListener(b.q.pin, ch)
if err != nil {
return err
}
defer release()
var powerVal byte = 0x00
if power {
powerVal = 0x01
}
if _, err := b.c.SendSysEx(SysExOneWireData, byte(OneWireInstructionConfigure), b.q.pin, powerVal); err != nil {
return err
}
cmd := OneWireCommandReset
if len(w) > 0 {
switch w[0] {
case 0xF0: // search rom
if _, err := b.c.SendSysEx(SysExOneWireData, byte(OneWireInstructionSearch), b.q.pin); err != nil {
return err
}
case 0xEC: // search rom (alarmed)
if _, err := b.c.SendSysEx(SysExOneWireData, byte(OneWireInstructionSearchAlarmed), b.q.pin); err != nil {
return err
}
case 0xCC: // skip rom
panic("not implemented")
case 0x33: // read rom
panic("not implemented")
case 0x55: // match rom
cmd |= OneWireCommandSelect
payload := w[1:9]
if len(w) > 9 { // command length (1) + address length (8)
cmd |= OneWireCommandWrite
}
if len(r) > 0 {
cmd |= OneWireCommandRead
// Write the amount of bytes to read.
payload = append(payload, byte(len(r)&0xFF), byte((len(r)>>8)&0xFF))
// Write the correlation id for sanity checking.
payload = append(payload, byte(b.cid&0xFF), byte((b.cid>>8)&0xFF))
b.cid++
}
cmd |= OneWireCommandDelay
payload = append(payload, 10, 0, 0, 0)
payload = append(payload, w[9:]...)
payload = append([]byte{byte(cmd), b.q.pin}, Encoder7Bit(payload)...)
if _, err := b.c.SendSysEx(SysExOneWireData, payload...); err != nil {
return err
}
default:
return fmt.Errorf("%w: no onewire command matching 0x%02X", ErrUnsupportedFeature, w[0])
}
}
if len(r) > 0 {
data := <-ch
ins := OneWireInstruction(data[0])
pin := data[1]
if ins != OneWireInstructionReadReply {
return fmt.Errorf("%w: did not receive read reply", ErrUnhandledMessage)
}
if pin != b.q.pin {
return fmt.Errorf("%w: received message from wrong bus", ErrInvalidOneWirePin)
}
data = Decoder7Bit(data[2:])
// Drop the correlation id when copying.
copy(r, data[2:])
}
return nil
}
func (b *OneWireBus) Q() gpio.PinIO {
return b.q
}