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 }