github.com/bluenviron/gomavlib/v2@v2.2.1-0.20240308101627-2c07e3da629c/pkg/message/readwriter_test.go (about)

     1  package message
     2  
     3  import (
     4  	"bytes"
     5  	"testing"
     6  
     7  	"github.com/stretchr/testify/require"
     8  )
     9  
    10  type (
    11  	MAV_TYPE              uint64 //nolint:revive
    12  	MAV_AUTOPILOT         uint64 //nolint:revive
    13  	MAV_MODE_FLAG         uint64 //nolint:revive
    14  	MAV_STATE             uint64 //nolint:revive
    15  	MAV_SYS_STATUS_SENSOR uint64 //nolint:revive
    16  	MAV_CMD               uint64 //nolint:revive
    17  	MYENUM                uint64
    18  )
    19  
    20  type MessageAllTypes struct {
    21  	A uint8
    22  	B int8
    23  	C uint16
    24  	D int16
    25  	E uint32
    26  	F int32
    27  	G uint64
    28  	H int64
    29  	I float32
    30  	J float64
    31  	K string `mavlen:"30"`
    32  	L MYENUM `mavenum:"uint8"`
    33  	M MYENUM `mavenum:"int8"`
    34  	N MYENUM `mavenum:"uint16"`
    35  	P MYENUM `mavenum:"uint32"`
    36  	Q MYENUM `mavenum:"int32"`
    37  	R MYENUM `mavenum:"uint64"`
    38  	S string
    39  }
    40  
    41  func (*MessageAllTypes) GetID() uint32 {
    42  	return 155
    43  }
    44  
    45  type MessageHeartbeat struct {
    46  	Type           MAV_TYPE      `mavenum:"uint8"`
    47  	Autopilot      MAV_AUTOPILOT `mavenum:"uint8"`
    48  	BaseMode       MAV_MODE_FLAG `mavenum:"uint8"`
    49  	CustomMode     uint32
    50  	SystemStatus   MAV_STATE `mavenum:"uint8"`
    51  	MavlinkVersion uint8
    52  }
    53  
    54  func (*MessageHeartbeat) GetID() uint32 {
    55  	return 0
    56  }
    57  
    58  type MessageSysStatus struct {
    59  	OnboardControlSensorsPresent MAV_SYS_STATUS_SENSOR `mavenum:"uint32"`
    60  	OnboardControlSensorsEnabled MAV_SYS_STATUS_SENSOR `mavenum:"uint32"`
    61  	OnboardControlSensorsHealth  MAV_SYS_STATUS_SENSOR `mavenum:"uint32"`
    62  	Load                         uint16
    63  	VoltageBattery               uint16
    64  	CurrentBattery               int16
    65  	BatteryRemaining             int8
    66  	DropRateComm                 uint16
    67  	ErrorsComm                   uint16
    68  	ErrorsCount1                 uint16
    69  	ErrorsCount2                 uint16
    70  	ErrorsCount3                 uint16
    71  	ErrorsCount4                 uint16
    72  }
    73  
    74  func (m *MessageSysStatus) GetID() uint32 {
    75  	return 1
    76  }
    77  
    78  type MessageChangeOperatorControl struct {
    79  	TargetSystem   uint8
    80  	ControlRequest uint8
    81  	Version        uint8
    82  	Passkey        string `mavlen:"25"`
    83  }
    84  
    85  func (m *MessageChangeOperatorControl) GetID() uint32 {
    86  	return 5
    87  }
    88  
    89  type MessageAttitudeQuaternionCov struct {
    90  	TimeUsec   uint64
    91  	Q          [4]float32
    92  	Rollspeed  float32
    93  	Pitchspeed float32
    94  	Yawspeed   float32
    95  	Covariance [9]float32
    96  }
    97  
    98  func (m *MessageAttitudeQuaternionCov) GetID() uint32 {
    99  	return 61
   100  }
   101  
   102  type MessageOpticalFlow struct {
   103  	TimeUsec       uint64
   104  	SensorId       uint8 //nolint:revive
   105  	FlowX          int16
   106  	FlowY          int16
   107  	FlowCompMX     float32
   108  	FlowCompMY     float32
   109  	Quality        uint8
   110  	GroundDistance float32
   111  	FlowRateX      float32 `mavext:"true"`
   112  	FlowRateY      float32 `mavext:"true"`
   113  }
   114  
   115  func (*MessageOpticalFlow) GetID() uint32 {
   116  	return 100
   117  }
   118  
   119  type MessagePlayTune struct {
   120  	TargetSystem    uint8
   121  	TargetComponent uint8
   122  	Tune            string `mavlen:"30"`
   123  	Tune2           string `mavext:"true" mavlen:"200"`
   124  }
   125  
   126  func (*MessagePlayTune) GetID() uint32 {
   127  	return 258
   128  }
   129  
   130  type MessageAhrs struct {
   131  	OmegaIx     float32 `mavname:"omegaIx"`
   132  	OmegaIy     float32 `mavname:"omegaIy"`
   133  	OmegaIz     float32 `mavname:"omegaIz"`
   134  	AccelWeight float32
   135  	RenormVal   float32
   136  	ErrorRp     float32
   137  	ErrorYaw    float32
   138  }
   139  
   140  func (*MessageAhrs) GetID() uint32 {
   141  	return 163
   142  }
   143  
   144  type MessageTrajectoryRepresentationWaypoints struct {
   145  	TimeUsec    uint64
   146  	ValidPoints uint8
   147  	PosX        [5]float32
   148  	PosY        [5]float32
   149  	PosZ        [5]float32
   150  	VelX        [5]float32
   151  	VelY        [5]float32
   152  	VelZ        [5]float32
   153  	AccX        [5]float32
   154  	AccY        [5]float32
   155  	AccZ        [5]float32
   156  	PosYaw      [5]float32
   157  	VelYaw      [5]float32
   158  	Command     [5]MAV_CMD `mavenum:"uint16"`
   159  }
   160  
   161  func (*MessageTrajectoryRepresentationWaypoints) GetID() uint32 {
   162  	return 332
   163  }
   164  
   165  var casesCRC = []struct {
   166  	msg Message
   167  	crc byte
   168  }{
   169  	{
   170  		&MessageHeartbeat{},
   171  		50,
   172  	},
   173  	{
   174  		&MessageSysStatus{},
   175  		124,
   176  	},
   177  	{
   178  		&MessageChangeOperatorControl{},
   179  		217,
   180  	},
   181  	{
   182  		&MessageAttitudeQuaternionCov{},
   183  		167,
   184  	},
   185  	{
   186  		&MessageOpticalFlow{},
   187  		175,
   188  	},
   189  	{
   190  		&MessagePlayTune{},
   191  		187,
   192  	},
   193  	{
   194  		&MessageAhrs{},
   195  		127,
   196  	},
   197  }
   198  
   199  func TestCRC(t *testing.T) {
   200  	for _, c := range casesCRC {
   201  		mp, err := NewReadWriter(c.msg)
   202  		require.NoError(t, err)
   203  		require.Equal(t, c.crc, mp.CRCExtra())
   204  	}
   205  }
   206  
   207  var casesReadWriter = []struct {
   208  	name   string
   209  	isV2   bool
   210  	parsed Message
   211  	raw    []byte
   212  }{
   213  	{
   214  		"v1",
   215  		false,
   216  		&MessageAllTypes{
   217  			A: 127,
   218  			B: -12,
   219  			C: 1343,
   220  			D: 5652,
   221  			E: 5323,
   222  			F: 7987,
   223  			G: 8654,
   224  			H: 6753,
   225  			I: 5764,
   226  			J: 3423,
   227  			K: "teststring",
   228  			L: 232,
   229  			M: 34,
   230  			N: 1422,
   231  			P: 1233,
   232  			Q: 2343,
   233  			R: 1232,
   234  			S: "a",
   235  		},
   236  		[]byte{
   237  			0xce, 0x21, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   238  			0x61, 0x1a, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   239  			0x0, 0x0, 0x0, 0x0, 0x0, 0xbe, 0xaa, 0x40,
   240  			0xd0, 0x4, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   241  			0xcb, 0x14, 0x0, 0x0, 0x33, 0x1f, 0x0, 0x0,
   242  			0x0, 0x20, 0xb4, 0x45, 0xd1, 0x4, 0x0, 0x0,
   243  			0x27, 0x9, 0x0, 0x0, 0x3f, 0x5, 0x14, 0x16,
   244  			0x8e, 0x5, 0x7f, 0xf4, 0x74, 0x65, 0x73, 0x74,
   245  			0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x0, 0x0,
   246  			0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   247  			0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
   248  			0x0, 0x0, 0xe8, 0x22, 0x61,
   249  		},
   250  	},
   251  	{
   252  		"v1 with string with max length",
   253  		false,
   254  		&MessageChangeOperatorControl{
   255  			Passkey: "abcdefghijklmnopqrstuvwxy",
   256  		},
   257  		[]byte("\x00\x00\x00\x61\x62\x63\x64\x65" +
   258  			"\x66\x67\x68\x69\x6a\x6b\x6c\x6d" +
   259  			"\x6e\x6f\x70\x71\x72\x73\x74\x75" +
   260  			"\x76\x77\x78\x79"),
   261  	},
   262  	{
   263  		"v1 with array",
   264  		false,
   265  		&MessageAttitudeQuaternionCov{
   266  			TimeUsec:   2,
   267  			Q:          [4]float32{1, 1, 1, 1},
   268  			Rollspeed:  1,
   269  			Pitchspeed: 1,
   270  			Yawspeed:   1,
   271  			Covariance: [9]float32{1, 1, 1, 1, 1, 1, 1, 1, 1},
   272  		},
   273  		append(
   274  			[]byte("\x02\x00\x00\x00\x00\x00\x00\x00"),
   275  			bytes.Repeat([]byte("\x00\x00\x80\x3F"), 16)...),
   276  	},
   277  	{
   278  		"v1 with extensions",
   279  		false,
   280  		&MessageOpticalFlow{
   281  			TimeUsec:       3,
   282  			FlowCompMX:     1,
   283  			FlowCompMY:     1,
   284  			GroundDistance: 1,
   285  			FlowX:          7,
   286  			FlowY:          8,
   287  			SensorId:       9,
   288  			Quality:        0x0A,
   289  		},
   290  		[]byte("\x03\x00\x00\x00\x00\x00\x00\x00" +
   291  			"\x00\x00\x80\x3F\x00\x00\x80\x3F" +
   292  			"\x00\x00\x80\x3F\x07\x00\x08\x00" +
   293  			"\x09\x0A"),
   294  	},
   295  	{
   296  		"v1 with array of enums",
   297  		false,
   298  		&MessageTrajectoryRepresentationWaypoints{
   299  			TimeUsec:    1,
   300  			ValidPoints: 2,
   301  			PosX:        [5]float32{1, 2, 3, 4, 5},
   302  			PosY:        [5]float32{1, 2, 3, 4, 5},
   303  			PosZ:        [5]float32{1, 2, 3, 4, 5},
   304  			VelX:        [5]float32{1, 2, 3, 4, 5},
   305  			VelY:        [5]float32{1, 2, 3, 4, 5},
   306  			VelZ:        [5]float32{1, 2, 3, 4, 5},
   307  			AccX:        [5]float32{1, 2, 3, 4, 5},
   308  			AccY:        [5]float32{1, 2, 3, 4, 5},
   309  			AccZ:        [5]float32{1, 2, 3, 4, 5},
   310  			PosYaw:      [5]float32{1, 2, 3, 4, 5},
   311  			VelYaw:      [5]float32{1, 2, 3, 4, 5},
   312  			Command:     [5]MAV_CMD{1, 2, 3, 4, 5},
   313  		},
   314  		[]byte("\x01\x00\x00\x00\x00\x00\x00\x00" +
   315  			"\x00\x00\x80\x3f\x00\x00\x00\x40" +
   316  			"\x00\x00\x40\x40\x00\x00\x80\x40" +
   317  			"\x00\x00\xa0\x40\x00\x00\x80\x3f" +
   318  			"\x00\x00\x00\x40\x00\x00\x40\x40" +
   319  			"\x00\x00\x80\x40\x00\x00\xa0\x40" +
   320  			"\x00\x00\x80\x3f\x00\x00\x00\x40" +
   321  			"\x00\x00\x40\x40\x00\x00\x80\x40" +
   322  			"\x00\x00\xa0\x40\x00\x00\x80\x3f" +
   323  			"\x00\x00\x00\x40\x00\x00\x40\x40" +
   324  			"\x00\x00\x80\x40\x00\x00\xa0\x40" +
   325  			"\x00\x00\x80\x3f\x00\x00\x00\x40" +
   326  			"\x00\x00\x40\x40\x00\x00\x80\x40" +
   327  			"\x00\x00\xa0\x40\x00\x00\x80\x3f" +
   328  			"\x00\x00\x00\x40\x00\x00\x40\x40" +
   329  			"\x00\x00\x80\x40\x00\x00\xa0\x40" +
   330  			"\x00\x00\x80\x3f\x00\x00\x00\x40" +
   331  			"\x00\x00\x40\x40\x00\x00\x80\x40" +
   332  			"\x00\x00\xa0\x40\x00\x00\x80\x3f" +
   333  			"\x00\x00\x00\x40\x00\x00\x40\x40" +
   334  			"\x00\x00\x80\x40\x00\x00\xa0\x40" +
   335  			"\x00\x00\x80\x3f\x00\x00\x00\x40" +
   336  			"\x00\x00\x40\x40\x00\x00\x80\x40" +
   337  			"\x00\x00\xa0\x40\x00\x00\x80\x3f" +
   338  			"\x00\x00\x00\x40\x00\x00\x40\x40" +
   339  			"\x00\x00\x80\x40\x00\x00\xa0\x40" +
   340  			"\x00\x00\x80\x3f\x00\x00\x00\x40" +
   341  			"\x00\x00\x40\x40\x00\x00\x80\x40" +
   342  			"\x00\x00\xa0\x40\x01\x00\x02\x00" +
   343  			"\x03\x00\x04\x00\x05\x00\x02"),
   344  	},
   345  	{
   346  		"v2 with empty-byte truncation and empty",
   347  		true,
   348  		&MessageAhrs{},
   349  		[]byte("\x00"),
   350  	},
   351  	{
   352  		"v2 with empty-byte truncation a",
   353  		true,
   354  		&MessageChangeOperatorControl{
   355  			TargetSystem:   0,
   356  			ControlRequest: 1,
   357  			Version:        2,
   358  			Passkey:        "testing",
   359  		},
   360  		[]byte("\x00\x01\x02\x74\x65\x73\x74\x69" +
   361  			"\x6e\x67"),
   362  	},
   363  	{
   364  		"v2 with empty-byte truncation b",
   365  		true,
   366  		&MessageAhrs{
   367  			OmegaIx:     1,
   368  			OmegaIy:     2,
   369  			OmegaIz:     3,
   370  			AccelWeight: 4,
   371  			RenormVal:   5,
   372  			ErrorRp:     0,
   373  			ErrorYaw:    0,
   374  		},
   375  		[]byte("\x00\x00\x80\x3f\x00\x00\x00\x40" +
   376  			"\x00\x00\x40\x40\x00\x00\x80\x40" +
   377  			"\x00\x00\xa0\x40"),
   378  	},
   379  	{
   380  		"v2 with extensions a",
   381  		true,
   382  		&MessageOpticalFlow{
   383  			TimeUsec:       3,
   384  			FlowCompMX:     1,
   385  			FlowCompMY:     1,
   386  			GroundDistance: 1,
   387  			FlowX:          7,
   388  			FlowY:          8,
   389  			SensorId:       9,
   390  			Quality:        0x0A,
   391  			FlowRateX:      1,
   392  			FlowRateY:      1,
   393  		},
   394  		[]byte("\x03\x00\x00\x00\x00\x00\x00\x00" +
   395  			"\x00\x00\x80\x3F\x00\x00\x80\x3F" +
   396  			"\x00\x00\x80\x3F\x07\x00\x08\x00" +
   397  			"\x09\x0A\x00\x00\x80\x3F\x00\x00" +
   398  			"\x80\x3F"),
   399  	},
   400  	{
   401  		"v2 with extensions b",
   402  		true,
   403  		&MessagePlayTune{
   404  			TargetSystem:    1,
   405  			TargetComponent: 2,
   406  			Tune:            "test1",
   407  			Tune2:           "test2",
   408  		},
   409  		[]byte("\x01\x02\x74\x65\x73\x74\x31\x00" +
   410  			"\x00\x00\x00\x00\x00\x00\x00\x00" +
   411  			"\x00\x00\x00\x00\x00\x00\x00\x00" +
   412  			"\x00\x00\x00\x00\x00\x00\x00\x00" +
   413  			"\x74\x65\x73\x74\x32"),
   414  	},
   415  }
   416  
   417  type Invalid struct{}
   418  
   419  func (*Invalid) GetID() uint32 {
   420  	return 0
   421  }
   422  
   423  type MYENUM2 int8
   424  
   425  type MessageInvalidEnum struct {
   426  	MyEnum MYENUM2 `mavenum:"int8"`
   427  }
   428  
   429  func (*MessageInvalidEnum) GetID() uint32 {
   430  	return 0
   431  }
   432  
   433  type MYENUM3 uint64
   434  
   435  type MessageInvalidEnum2 struct {
   436  	MyEnum MYENUM3 `mavenum:"invalid"`
   437  }
   438  
   439  func (*MessageInvalidEnum2) GetID() uint32 {
   440  	return 0
   441  }
   442  
   443  type MYENUM4 uint64
   444  
   445  type MessageInvalidEnum3 struct {
   446  	MyEnum MYENUM4 `mavenum:"int64"`
   447  }
   448  
   449  func (*MessageInvalidEnum3) GetID() uint32 {
   450  	return 0
   451  }
   452  
   453  type MessageInvalid2 struct {
   454  	Pointer *int
   455  }
   456  
   457  func (*MessageInvalid2) GetID() uint32 {
   458  	return 0
   459  }
   460  
   461  type MessageInvalid3 struct {
   462  	Str string `mavlen:"invalid"`
   463  }
   464  
   465  func (*MessageInvalid3) GetID() uint32 {
   466  	return 0
   467  }
   468  
   469  func TestNewReadWriterErrors(t *testing.T) {
   470  	_, err := NewReadWriter(&Invalid{})
   471  	require.EqualError(t, err, "struct name must begin with 'Message'")
   472  
   473  	_, err = NewReadWriter(&MessageInvalidEnum{})
   474  	require.EqualError(t, err, "an enum must be an uint64")
   475  
   476  	_, err = NewReadWriter(&MessageInvalidEnum2{})
   477  	require.EqualError(t, err, "unsupported Go type: invalid")
   478  
   479  	_, err = NewReadWriter(&MessageInvalidEnum3{})
   480  	require.EqualError(t, err, "type 'int64' cannot be used as enum")
   481  
   482  	_, err = NewReadWriter(&MessageInvalid2{})
   483  	require.EqualError(t, err, "unsupported Go type: ")
   484  
   485  	_, err = NewReadWriter(&MessageInvalid3{})
   486  	require.EqualError(t, err, "string has invalid length: invalid")
   487  }
   488  
   489  func TestRead(t *testing.T) {
   490  	for _, c := range casesReadWriter {
   491  		t.Run(c.name, func(t *testing.T) {
   492  			mp, err := NewReadWriter(c.parsed)
   493  			require.NoError(t, err)
   494  			msg, err := mp.Read(&MessageRaw{
   495  				ID:      c.parsed.GetID(),
   496  				Payload: c.raw,
   497  			}, c.isV2)
   498  			require.NoError(t, err)
   499  			require.Equal(t, c.parsed, msg)
   500  		})
   501  	}
   502  }
   503  
   504  func TestWrite(t *testing.T) {
   505  	for _, c := range casesReadWriter {
   506  		t.Run(c.name, func(t *testing.T) {
   507  			mp, err := NewReadWriter(c.parsed)
   508  			require.NoError(t, err)
   509  			msgRaw := mp.Write(c.parsed, c.isV2)
   510  			require.Equal(t, &MessageRaw{
   511  				ID:      c.parsed.GetID(),
   512  				Payload: c.raw,
   513  			}, msgRaw)
   514  		})
   515  	}
   516  }