github.com/blueinnovationsgroup/can-go@v0.0.0-20230518195432-d0567cda0028/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  }