diff --git a/src-go/arke/celaeno.go b/src-go/arke/celaeno.go index 8427a62..9bd4fa0 100644 --- a/src-go/arke/celaeno.go +++ b/src-go/arke/celaeno.go @@ -1,83 +1,101 @@ package arke import ( "encoding/binary" "fmt" "time" ) type CelaenoSetPoint struct { Power uint8 } +func (m *CelaenoSetPoint) MessageClassID() uint16 { + return CelaenoSetPointMessage +} + func (c CelaenoSetPoint) Marshall(buf []byte) (int, error) { if err := checkSize(buf, 1); err != nil { return 0, err } buf[0] = c.Power return 1, nil } func (c *CelaenoSetPoint) Unmarshall(buf []byte) error { if err := checkSize(buf, 1); err != nil { return err } c.Power = buf[0] return nil } type CelaenoStatus struct { WaterLevel uint8 Fan FanStatusAndRPM } +func (m *CelaenoStatus) MessageClassID() uint16 { + return CelaenoStatusMessage +} + func (c *CelaenoStatus) Unmarshall(buf []byte) error { if err := checkSize(buf, 3); err != nil { return err } c.WaterLevel = buf[0] c.Fan = FanStatusAndRPM(binary.LittleEndian.Uint16(buf[1:])) return nil } type CelaenoConfig struct { RampUpTime time.Duration RampDownTime time.Duration MinimumOnTime time.Duration DebounceTime time.Duration } +func (m *CelaenoConfig) MessageClassID() uint16 { + return CelaenoConfigNessage +} + const MaxUint16 = ^uint16(0) func castDuration(t time.Duration) (uint16, error) { res := t.Nanoseconds() / 1000000 if res > int64(MaxUint16) { return 0xffff, fmt.Errorf("Time constant overflow") } return uint16(res), nil } func (c CelaenoConfig) Marshall(buf []byte) (int, error) { if err := checkSize(buf, 8); err != nil { return 0, err } for i, t := range []time.Duration{c.RampUpTime, c.RampDownTime, c.MinimumOnTime, c.DebounceTime} { data, err := castDuration(t) if err != nil { return 2 * i, err } binary.LittleEndian.PutUint16(buf[(2*i):], data) } return 8, nil } func (c *CelaenoConfig) Unmarshall(buf []byte) error { if err := checkSize(buf, 8); err != nil { return err } c.RampUpTime = time.Duration(binary.LittleEndian.Uint16(buf[0:])) * time.Millisecond c.RampDownTime = time.Duration(binary.LittleEndian.Uint16(buf[2:])) * time.Millisecond c.MinimumOnTime = time.Duration(binary.LittleEndian.Uint16(buf[4:])) * time.Millisecond c.DebounceTime = time.Duration(binary.LittleEndian.Uint16(buf[6:])) * time.Millisecond return nil } + +func init() { + messageFactory[CelaenoSetPointMessage] = func() ReceivableMessage { return &CelaenoSetPoint{} } + messageFactory[CelaenoStatusMessage] = func() ReceivableMessage { return &CelaenoStatus{} } + messageFactory[CelaenoConfigNessage] = func() ReceivableMessage { return &CelaenoConfig{} } +} diff --git a/src-go/arke/helios.go b/src-go/arke/helios.go index 35c8576..69ae06b 100644 --- a/src-go/arke/helios.go +++ b/src-go/arke/helios.go @@ -1,24 +1,32 @@ package arke type HeliosSetPoint struct { Visible uint8 UV uint8 } func (c HeliosSetPoint) Marshall(buf []byte) (int, error) { if err := checkSize(buf, 2); err != nil { return 0, err } buf[0] = c.Visible buf[1] = c.UV return 2, nil } func (c *HeliosSetPoint) Unmarshall(buf []byte) error { if err := checkSize(buf, 2); err != nil { return err } c.Visible = buf[0] c.UV = buf[1] return nil } + +func (m *HeliosSetPoint) MessageClassID() uint16 { + return HeliosSetPointMessage +} + +func init() { + messageFactory[HeliosSetPointMessage] = func() ReceivableMessage { return &HeliosSetPoint{} } +} diff --git a/src-go/arke/messages.go b/src-go/arke/messages.go index 9881bc5..0db4093 100644 --- a/src-go/arke/messages.go +++ b/src-go/arke/messages.go @@ -1,43 +1,151 @@ package arke // #include "../../include/arke.h" import "C" +import ( + "fmt" + + socketcan "github.com/atuleu/golang-socketcan" +) const ( NetworkControlCommand uint16 = C.ARKE_NETWORK_CONTROL_COMMAND HighPriorityMessage uint16 = C.ARKE_HIGH_PRIORITY_MESSAGE StandardMessage uint16 = C.ARKE_MESSAGE HeartBeat uint16 = C.ARKE_HEARTBEAT MessageTypeMask uint16 = C.ARKE_MESSAGE_TYPE_MASK BroadcastClass uint16 = C.ARKE_BROADCAST ZeusClass uint16 = C.ARKE_ZEUS HeliosClass uint16 = C.ARKE_HELIOS CelaenoClass uint16 = C.ARKE_CELAENO NodeClassMask uint16 = C.ARKE_NODE_CLASS_MASK ResetRequest uint16 = C.ARKE_RESET_REQUEST SynchronisationRequest uint16 = C.ARKE_SYNCHRONISATION HeartBeatRequest uint16 = C.ARKE_HEARTBEAT_REQUEST IDMask uint16 = C.ARKE_SUBID_MASK ZeusSetPointMessage uint16 = C.ARKE_ZEUS_SET_POINT ZeusReportMessage uint16 = C.ARKE_ZEUS_REPORT ZeusVibrationReportMessage uint16 = C.ARKE_ZEUS_VIBRATION_REPORT ZeusConfigMessage uint16 = C.ARKE_ZEUS_CONFIG ZeusStatusMessage uint16 = C.ARKE_ZEUS_STATUS ZeusControlPointMessage uint16 = C.ARKE_ZEUS_CONTROL_POINT HeliosSetPointMessage uint16 = C.ARKE_HELIOS_SET_POINT HeliosPulseModeMessage uint16 = C.ARKE_HELIOS_PULSE_MODE CelaenoSetPointMessage uint16 = C.ARKE_CELAENO_SET_POINT CelaenoStatusMessage uint16 = C.ARKE_CELAENO_STATUS CelaenoConfigNessage uint16 = C.ARKE_CELAENO_CONFIG ) type Marshaller interface { Marshall([]byte) (int, error) } type Unmarshaller interface { Unmarshall([]byte) error } + +type identifiable interface { + MessageClassID() uint16 +} + +type SendableMessage interface { + Marshaller + identifiable +} + +type ReceivableMessage interface { + Unmarshaller + identifiable +} + +type Message interface { + Marshaller + Unmarshaller + identifiable +} + +func checkID(ID uint8) error { + if ID > 7 { + return fmt.Errorf("Invalid device ID %d (max is 7)", ID) + } + return nil +} + +func SendMessage(itf *socketcan.RawInterface, m SendableMessage, highPriority bool, ID uint8) error { + if err := checkID(ID); err != nil { + return err + } + canID := (m.MessageClassID() << 3) | uint16(ID) + if highPriority == true { + canID |= HighPriorityMessage << 9 + } else { + canID |= StandardMessage << 9 + } + + f := socketcan.CanFrame{ + ID: uint32(canID), + Extended: false, + RTR: false, + Data: make([]byte, 8), + } + dlc, err := m.Marshall(f.Data) + if err != nil { + return fmt.Errorf("Could not marshall %v: %s", m, err) + } + f.Dlc = uint8(dlc) + return itf.Send(f) +} + +func RequestMessage(itf *socketcan.RawInterface, m ReceivableMessage, ID uint8) error { + if err := checkID(ID); err != nil { + return err + } + return itf.Send(socketcan.CanFrame{ + ID: uint32((StandardMessage << 9) | (m.MessageClassID() << 3) | uint16(ID)), + Extended: false, + RTR: false, + Data: make([]byte, 0), + Dlc: 0, + }) +} + +type messageCreator func() ReceivableMessage + +var messageFactory = make(map[uint16]messageCreator) + +func ParseMessage(f *socketcan.CanFrame) (ReceivableMessage, uint8, error) { + if f.Extended == true { + return nil, 0, fmt.Errorf("Arke does not support extended can ID") + } + + if f.RTR == true { + return nil, 0, fmt.Errorf("RTR frame") + } + + mType := uint16((f.ID & 0x7ff) >> 9) + if mType == NetworkControlCommand { + return nil, 0, fmt.Errorf("Network Command") + } + + if mType == HeartBeat { + return nil, 0, fmt.Errorf("Heartbeating not implemented yet") + } + + mID := uint8(f.ID & 0x7) + mClass := uint16((f.ID & 0x1f8) >> 3) + creator, ok := messageFactory[mClass] + if ok == false { + return nil, mID, fmt.Errorf("Unknown message type 0x%05x", mClass) + } + + m := creator() + err := m.Unmarshall(f.Data) + if err != nil { + err = fmt.Errorf("Could not parse message data: %s", err) + } + + return m, mID, err +} diff --git a/src-go/arke/zeus.go b/src-go/arke/zeus.go index be2e4d1..b4d55df 100644 --- a/src-go/arke/zeus.go +++ b/src-go/arke/zeus.go @@ -1,141 +1,169 @@ package arke import ( "encoding/binary" "fmt" "math" ) // #include "../../include/arke.h" import "C" type ZeusSetPoint struct { Humidity float32 Temperature float32 Wind uint8 } +func (m *ZeusSetPoint) MessageClassID() uint16 { + return ZeusSetPointMessage +} + func checkSize(buf []byte, expected int) error { if len(buf) < expected { return fmt.Errorf("Invalid buffer size %d, required %d", len(buf), expected) } return nil } func (m ZeusSetPoint) Marshall(buf []byte) (int, error) { if err := checkSize(buf, 5); err != nil { return 0, err } binary.LittleEndian.PutUint16(buf[0:], humidityFloatToBinary(m.Humidity)) binary.LittleEndian.PutUint16(buf[2:], hih6030TemperatureFloatToBinary(m.Temperature)) buf[4] = m.Wind return 5, nil } func (m *ZeusSetPoint) Unmarshall(buf []byte) error { if err := checkSize(buf, 5); err != nil { return err } m.Humidity = humidityBinaryToFloat(binary.LittleEndian.Uint16(buf[0:])) if math.IsNaN(float64(m.Humidity)) == true { return fmt.Errorf("Invalid humidity value") } m.Temperature = hih6030TemperatureBinaryToFloat(binary.LittleEndian.Uint16(buf[2:])) if math.IsNaN(float64(m.Temperature)) == true { return fmt.Errorf("Invalid temperature value") } m.Wind = buf[4] return nil } type ZeusReport struct { Humidity float32 Temperature [4]float32 } +func (m *ZeusReport) MessageClassID() uint16 { + return ZeusReportMessage +} + func (m *ZeusReport) Unmarshall(buf []byte) error { if err := checkSize(buf, 8); err != nil { return err } packed := []uint16{ binary.LittleEndian.Uint16(buf[0:]), binary.LittleEndian.Uint16(buf[2:]), binary.LittleEndian.Uint16(buf[4:]), binary.LittleEndian.Uint16(buf[6:]), } m.Humidity = humidityBinaryToFloat(packed[0] & 0x3fff) if math.IsNaN(float64(m.Humidity)) == true { return fmt.Errorf("Invalid humidity value") } m.Temperature[0] = hih6030TemperatureBinaryToFloat((packed[0] >> 14) | (packed[1]&0x0fff)<<2) if math.IsNaN(float64(m.Temperature[0])) == true { return fmt.Errorf("Invalid temperature value") } m.Temperature[1] = tmp1075BinaryToFloat((packed[1] >> 12) | (packed[2]&0x00ff)<<4) m.Temperature[2] = tmp1075BinaryToFloat((packed[2] >> 8) | (packed[3]&0x000f)<<8) m.Temperature[3] = tmp1075BinaryToFloat((packed[3] & 0xfff0) >> 4) return nil } type ZeusConfig struct { Humidity PDConfig Temperature PDConfig } +func (m *ZeusConfig) MessageClassID() uint16 { + return ZeusConfigMessage +} + func (m ZeusConfig) Marshall(buf []byte) (int, error) { if err := checkSize(buf, 8); err != nil { return 0, err } if err := m.Humidity.marshall(buf[0:]); err != nil { return 0, err } if err := m.Temperature.marshall(buf[4:]); err != nil { return 4, err } return 8, nil } func (m *ZeusConfig) Unmarshall(buf []byte) error { if err := checkSize(buf, 8); err != nil { return err } m.Humidity.unmarshall(buf[0:]) m.Temperature.unmarshall(buf[4:]) return nil } -type ZeusStatus struct { - Status uint8 - Fans [2]FanStatusAndRPM -} - const ( ZeusIdle uint8 = C.ARKE_ZEUS_IDLE ZeusActive uint8 = C.ARKE_ZEUS_ACTIVE ZeusClimateNotControlledWatchDog uint8 = C.ARKE_ZEUS_CLIMATE_UNCONTROLLED_WD ) +type ZeusStatus struct { + Status uint8 + Fans [2]FanStatusAndRPM +} + +func (m *ZeusStatus) MessageClassID() uint16 { + return ZeusStatusMessage +} + func (m *ZeusStatus) Unmarshall(buf []byte) error { if err := checkSize(buf, 5); err != nil { return err } m.Status = buf[0] m.Fans[0] = FanStatusAndRPM(binary.LittleEndian.Uint16(buf[1:])) m.Fans[1] = FanStatusAndRPM(binary.LittleEndian.Uint16(buf[3:])) return nil } type ZeusControlPoint struct { Humidity int16 Temperature int16 } +func (m *ZeusControlPoint) MessageClassID() uint16 { + return ZeusControlPointMessage +} + func (m *ZeusControlPoint) Unmarshall(buf []byte) error { if err := checkSize(buf, 4); err != nil { return err } m.Humidity = int16(binary.LittleEndian.Uint16(buf[0:])) m.Temperature = int16(binary.LittleEndian.Uint16(buf[2:])) return nil } + +func init() { + messageFactory[ZeusSetPointMessage] = func() ReceivableMessage { return &ZeusSetPoint{} } + messageFactory[ZeusReportMessage] = func() ReceivableMessage { return &ZeusReport{} } + messageFactory[ZeusConfigMessage] = func() ReceivableMessage { return &ZeusConfig{} } + messageFactory[ZeusStatusMessage] = func() ReceivableMessage { return &ZeusStatus{} } + messageFactory[ZeusControlPointMessage] = func() ReceivableMessage { return &ZeusControlPoint{} } +}