github.com/jordan-bonecutter/can-go@v0.0.0-20230901155856-d83995b18e50/frame.go (about) 1 package can 2 3 import ( 4 "encoding/hex" 5 "fmt" 6 "strconv" 7 "strings" 8 ) 9 10 const ( 11 idBits = 11 12 extendedIDBits = 29 13 ) 14 15 // CAN format constants. 16 const ( 17 MaxID = 0x7ff 18 MaxExtendedID = 0x1fffffff 19 ) 20 21 // Frame represents a CAN frame. 22 // 23 // A Frame is intentionally designed to fit into 16 bytes on common architectures 24 // and is therefore amenable to pass-by-value and judicious copying. 25 type Frame struct { 26 // ID is the CAN ID 27 ID uint32 28 // Length is the number of bytes of data in the frame. 29 Length uint8 30 // Data is the frame data. 31 Data Data 32 // IsRemote is true for remote frames. 33 IsRemote bool 34 // IsExtended is true for extended frames, i.e. frames with 29-bit IDs. 35 IsExtended bool 36 } 37 38 // Validate returns an error if the Frame is not a valid CAN frame. 39 func (f *Frame) Validate() error { 40 // Validate: ID 41 if f.IsExtended && f.ID > MaxExtendedID { 42 return fmt.Errorf( 43 "invalid extended CAN id: %v does not fit in %v bits", 44 f.ID, 45 extendedIDBits, 46 ) 47 } else if !f.IsExtended && f.ID > MaxID { 48 return fmt.Errorf( 49 "invalid standard CAN id: %v does not fit in %v bits", 50 f.ID, 51 idBits, 52 ) 53 } 54 // Validate: Data 55 if f.Length > MaxDataLength { 56 return fmt.Errorf("invalid data length: %v", f.Length) 57 } 58 return nil 59 } 60 61 // String returns an ASCII representation the CAN frame. 62 // 63 // Format: 64 // 65 // ([0-9A-F]{3}|[0-9A-F]{3})#(R[0-8]?|[0-9A-F]{0,16}) 66 // 67 // The format is compatible with the candump(1) log file format. 68 func (f Frame) String() string { 69 var id string 70 if f.IsExtended { 71 id = fmt.Sprintf("%08X", f.ID) 72 } else { 73 id = fmt.Sprintf("%03X", f.ID) 74 } 75 if f.IsRemote && f.Length == 0 { 76 return id + "#R" 77 } else if f.IsRemote { 78 return id + "#R" + strconv.Itoa(int(f.Length)) 79 } 80 return id + "#" + strings.ToUpper(hex.EncodeToString(f.Data[:f.Length])) 81 } 82 83 // UnmarshalString sets *f using the provided ASCII representation of a Frame. 84 func (f *Frame) UnmarshalString(s string) error { 85 // Split split into parts 86 parts := strings.Split(s, "#") 87 if len(parts) != 2 { 88 return fmt.Errorf("invalid frame format: %v", s) 89 } 90 idPart, dataPart := parts[0], parts[1] 91 var frame Frame 92 // Parse: IsExtended 93 if len(idPart) != 3 && len(idPart) != 8 { 94 return fmt.Errorf("invalid ID length: %v", s) 95 } 96 frame.IsExtended = len(idPart) == 8 97 // Parse: ID 98 id, err := strconv.ParseUint(idPart, 16, 32) 99 if err != nil { 100 return fmt.Errorf("invalid frame ID: %v", s) 101 } 102 frame.ID = uint32(id) 103 if len(dataPart) == 0 { 104 *f = frame 105 return nil 106 } 107 // Parse: IsRemote 108 if dataPart[0] == 'R' { 109 frame.IsRemote = true 110 if len(dataPart) > 2 { 111 return fmt.Errorf("invalid remote length: %v", s) 112 } else if len(dataPart) == 2 { 113 dataLength, err := strconv.Atoi(dataPart[1:2]) 114 if err != nil { 115 return fmt.Errorf("invalid remote length: %v: %w", s, err) 116 } 117 frame.Length = uint8(dataLength) 118 } 119 *f = frame 120 return nil 121 } 122 // Parse: Length 123 if len(dataPart) > 16 || len(dataPart)%2 != 0 { 124 return fmt.Errorf("invalid data length: %v", s) 125 } 126 frame.Length = uint8(len(dataPart) / 2) 127 // Parse: Data 128 decodedData, err := hex.DecodeString(dataPart) 129 if err != nil { 130 return fmt.Errorf("invalid data: %v: %w", s, err) 131 } 132 copy(frame.Data[:], decodedData) 133 *f = frame 134 return nil 135 }