diff --git a/specs/specs.md b/specs/specs.md index 4042e4a..e457708 100644 --- a/specs/specs.md +++ b/specs/specs.md @@ -1,248 +1,248 @@ # FORT Communication Protocol Specification ## Overview The Communication protocol is built upon CAN Bus 2.0 A. According to the OSI model it could be described has the following ### Physical and Data Link Layer [CAN Bus 2.0 A (11-bit identifier)](https://en.wikipedia.org/wiki/CAN_bus#Base_frame_format) specification running at 250 kbit/s. ### Network Layer The network consits of an host, usually a desktop computer running Linux, and a set of nodes. The host listen and acknowledge any message on the CAN bus. Each node has an unique 9 bit identifier. Each physical node is configured to respond to a certain set of CAN identifier alone on the bus, i.e. for any message identifier, only one single node could emit and should listen that type of message. The CAN base identifier 11 bit are subdicided as follow to specify any message on the bus:
Message Class Message Category subID
Bit 10 9 8 7 6 5 4 3 2 1 0
The first two bits are used to specify the class of the message according to the following table : | Bits Value | Message Class | Node Permission | |------------------:|:----------------------------------------|:-----------------------| | 0b00 | Network Control Command | Receive Only | | 0b01 | High Priority Message (error, emergency)| Emission Only | | 0b10 | Standard Priority Messages | Emission and Reception | | 0b11 | Heartbeat message | Emission Only | Except for control messages the following 9 bit are subdivided in two subset, a 6 bit message category (MSB) and a 3 bit subID (LSB). As mentionned before only one single node over the bus should be able to handle an unique IDT, therefore two physically identicate board should be used on the bus, they could use the subID field to specify their specific message. The special subID 0b000 is used for broadcasting, and therefore up to 7 identical device can work on the bus at the same time. Any node on the bus can manage several message category, as it will be describe furtherly. The unique 9-bit identifier of a node consist of its lowest Message Category he handles and its subID. Network control Message are a bit more perticular. They are always broadcasted the message category is used to specify all or a specifc type, and the subID is used to convey the actual command. ### Session Layer The session could be managed using heartbeat message. The host can use a Heartbeat request message to ask for all or for a specific node to provide a heartbeat every X milliseconds. This heartbeat could be used to monitor which node are available. The same command could be used to specify a single heartbeat to enumerate all present node on the bus. ## FORT Standard Message Category Table and Node Class ID This table gives all message categories that could be found on the bus. | Message Category | Name | Node | Node Class ID (without subID) | |-----------------:|:----------------------------------------------|:-------------|:------------------------------| -| 0x3a-0x3f | Reserved (Zeus) | Zeus | 0x38 | +| 0x39-0x3f | Reserved (Zeus) | Zeus | 0x38 | | 0x39 | Zeus Temperature and Humidity Report | Zeus | 0x38 | | 0x38 | Zeus Temperature and Humidity Set Poiny | Zeus | 0x38 | | 0x35-0x37 | Reserved (Helios) | Helios | 0x34 | | 0x34 | Helios Illumination Set Point | Helios | 0x34 | | 0x32-0x33 | Reserved (Celaeno) | Celaeno | 0x30 | | 0x31 | Celaeno Set Point | Celaeno | 0x30 | | 0x30 | Celaeno Status | Celaeno | 0x30 | | 0x04-0x29 | For future use | n.a | n.a | | 0x00 | Reserved for broadcast | all | n.a. | Any of this messages can be sent using low (0b10) and high (0b01) priority. This gives the following class ID for the FORT nodes currently available : | Node Name | Device Class ID | |----------:|:----------------| | Zeus | 0x38 | | Helios | 0x34 | | Celaeno | 0x30 | For all messages, the host can use a RTR request on the device to actively fetch the data if he requires it and this data is not sent periodically. ## Message Specifications. #### 0x39 Zeus Temperature and Humidity Report * Access : Read Only * Periodically emitted by node : yes (frequency not yet determined) * Write : n.a. * Read : * Data Length: to be determined. * Data Fields: to be determined. #### 0x38 Zeus Temperature and Humidity Set Point * Access : Read/Write * Periodically emitted by node : never * Read/Write : * Data Length: to be determined. * Data fields: to be determined. #### 0x34 Helios Illumination Set Point * Access : Read/Write * Periodically emitted by node : never * Read/Write : * Data Length: 2 * Data fields: * Byte 0: Visible Light Amount level from none to max (255). * Byte 1: UV Light Level Note, unstar the previous system, the infrared light pulse duration is solely managed by the framegrabber. #### 0x31 Celaeno Humidity Production Set Point * Access : Read/Write * Periodically emitted by node : never * Read/Write : * Data Length: 1 * Data fields: * Byte 0: Amount of humidity currently produced. #### 0x30 Celaeno Status * Access : Read * Periodically emitted by node : yes on exceptional situation (warning / critical level reached). * Write: n.a. * Read : * Data Length: 3 * Data fields: * Byte 0: Water Level Status * 0x00: Functionning Normally * 0x01: Warning Level Reached * 0x02: Critical Level Reached, Humidity Production Disabled. * 0x04: Incoherent sensor readout. * Byte 1-2: Fan Status * B0 - B13 : Fan Current RPM * B14 : If set, specifies a fan aging alert * B15 : If set, specifies a fan stall alert (Fan should spin but is not) ## FORT Network Control Command Specification As mentionned previously, Network Control Command are specified differently: * The category field is used to target specific node class, or 0x00 to broadcast to all classes * The subID field is used as a command specification, all node of the same class are broadcasted. The following command are specified, note that for some their implementation is not necesarly required | Code | Command | Implementation | |-------|---------------------------|----------------| | 0b000 | Software Reset Request | Required | | 0b001 | Timestamp Synchrnoization | Optional | | 0b111 | Heartbeat Request | Required | As these message are broadcasted, the RTR beat should always be set to zero. ### 0b000 Software Reset Request Any node on the bus should implement this feature and reset itself after acknowledgement. The expected data length is 0 byte. ### 0b001 Timestamp Synchronisation Not specified yet, be if timestamped data would be required on the bus, this high priority frame should be used for re-synchronizing node clocks to the Host one. ### 0b111 Heartbeat Request This command is used to request for the targeted nodes to transmit an heartbeat. The Host specifies an Heartbeat period in ms, and the targeted nodes are expected to transmit an heartbeat periodically after acknowledgment. If the period is 0 or simply omitted, a single heartbeat request should be sent by the targeted nodes. * Data Length: 0 or 2 * Data Field: * Byte 0: Heartbeat period LSB * Byte 1: Heartbeat period MSB ## Heartbeat Message Specification Heartbeat message are issued by nodes to monitor all the nodes status. The CAN IDT would consist of 0b11 followed by the node unique ID (Node Class + subID). In the case the heartbeat is emitted following a single heartbeat request (node enumeration), the heartbeat should contain a firmware version information. In case of heartbeat periodically sent, the nodes could avoid to transmit this information (but are allowed to transmit). Firmware version consist of 2 to 4 number, corresponding to Major,Minor,Patch and Tweak numbering * Data Length 0, 2, 3 or 4. * Data Field : * Byte 0 : Major Version number (required if any versionning is transmitted) * Byte 1 : Minor Version number (required if any versionning is transmitted) * Byte 2 : Patch Version number (optional) * Byte 3 : Tweak Version number (optional) diff --git a/src-go/arke/messages.go b/src-go/arke/messages.go index 0667d8c..b95beb7 100644 --- a/src-go/arke/messages.go +++ b/src-go/arke/messages.go @@ -1,170 +1,170 @@ package arke import ( "fmt" socketcan "github.com/atuleu/golang-socketcan" ) type MessageType uint16 type MessageClass uint16 type NodeClass uint16 type NodeID uint8 const ( NetworkControlCommand MessageType = 0x00 HighPriorityMessage MessageType = 0x01 StandardMessage MessageType = 0x02 HeartBeat MessageType = 0x03 MessageTypeMask uint16 = 0x03 << 9 BroadcastClass NodeClass = 0x0 ZeusClass NodeClass = 0x38 HeliosClass NodeClass = 0x34 CelaenoClass NodeClass = 0x30 NodeClassMask uint16 = 0x3f << 3 ResetRequest MessageClass = 0x00 SynchronisationRequest MessageClass = 0x01 HeartBeatRequest MessageClass = 0x07 IDMask uint16 = 0x07 BroadcastID NodeID = 0x00 HeartBeatMessage MessageClass = MessageClass(HeartBeat << 9) ZeusSetPointMessage MessageClass = 0x38 ZeusReportMessage MessageClass = 0x39 ZeusVibrationReportMessage MessageClass = 0x3a ZeusConfigMessage MessageClass = 0x3b ZeusStatusMessage MessageClass = 0x3c ZeusControlPointMessage MessageClass = 0x3d HeliosSetPointMessage MessageClass = 0x34 HeliosPulseModeMessage MessageClass = 0x35 CelaenoSetPointMessage MessageClass = 0x30 CelaenoStatusMessage MessageClass = 0x31 CelaenoConfigNessage MessageClass = 0x32 ) func makeCANIDT(t MessageType, c MessageClass, n NodeID) uint32 { return uint32((uint32(t) << 9) | (uint32(c) << 3) | uint32(n)) } func extractCANIDT(idt uint32) (t MessageType, c MessageClass, n NodeID) { n = NodeID(idt & 0x7) c = MessageClass((idt & 0x1f8) >> 3) t = MessageType((idt & 0x600) >> 9) return } type Marshaller interface { Marshall([]byte) (int, error) } type Unmarshaller interface { Unmarshall([]byte) error } type identifiable interface { MessageClassID() MessageClass } type SendableMessage interface { Marshaller identifiable } type ReceivableMessage interface { Unmarshaller identifiable } type Message interface { Marshaller Unmarshaller identifiable } func checkID(ID NodeID) 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 NodeID) error { if err := checkID(ID); err != nil { return err } mType := StandardMessage if highPriority == true { mType = HighPriorityMessage } f := socketcan.CanFrame{ ID: makeCANIDT(mType, m.MessageClassID(), ID), 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 NodeID) error { if err := checkID(ID); err != nil { return err } return itf.Send(socketcan.CanFrame{ ID: makeCANIDT(StandardMessage, m.MessageClassID(), ID), Extended: false, - RTR: false, + RTR: true, Data: make([]byte, 0), Dlc: 0, }) } type messageCreator func() ReceivableMessage var messageFactory = make(map[MessageClass]messageCreator) func ParseMessage(f *socketcan.CanFrame) (ReceivableMessage, NodeID, 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, mClass, mID := extractCANIDT(f.ID) if mType == NetworkControlCommand { return nil, 0, fmt.Errorf("Network Command Received") } if mType == HeartBeat { res := &HeartBeatData{} if err := res.Unmarshall(f.Data[0:f.Dlc]); err != nil { return nil, mID, err } res.Class = NodeClass(mClass) res.ID = mID return res, mID, nil } 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[0:f.Dlc]) if err != nil { err = fmt.Errorf("Could not parse message data: %s", err) } return m, mID, err }