gobot.io/x/gobot/v2@v2.1.0/platforms/parrot/minidrone/minidrone_driver.go (about) 1 package minidrone 2 3 import ( 4 "bytes" 5 "encoding/binary" 6 "fmt" 7 "sync" 8 "time" 9 10 "gobot.io/x/gobot/v2" 11 "gobot.io/x/gobot/v2/platforms/ble" 12 ) 13 14 // Driver is the Gobot interface to the Parrot Minidrone 15 type Driver struct { 16 name string 17 connection gobot.Connection 18 stepsfa0a uint16 19 stepsfa0b uint16 20 pcmdMutex sync.Mutex 21 flying bool 22 Pcmd Pcmd 23 gobot.Eventer 24 } 25 26 const ( 27 // BLE services 28 droneCommandService = "9a66fa000800919111e4012d1540cb8e" 29 droneNotificationService = "9a66fb000800919111e4012d1540cb8e" 30 31 // send characteristics 32 pcmdCharacteristic = "9a66fa0a0800919111e4012d1540cb8e" 33 commandCharacteristic = "9a66fa0b0800919111e4012d1540cb8e" 34 priorityCharacteristic = "9a66fa0c0800919111e4012d1540cb8e" 35 36 // receive characteristics 37 flightStatusCharacteristic = "9a66fb0e0800919111e4012d1540cb8e" 38 batteryCharacteristic = "9a66fb0f0800919111e4012d1540cb8e" 39 40 // piloting states 41 flatTrimChanged = 0 42 flyingStateChanged = 1 43 44 // flying states 45 flyingStateLanded = 0 46 flyingStateTakeoff = 1 47 flyingStateHovering = 2 48 flyingStateFlying = 3 49 flyingStateLanding = 4 50 flyingStateEmergency = 5 51 flyingStateRolling = 6 52 53 // Battery event 54 Battery = "battery" 55 56 // FlightStatus event 57 FlightStatus = "flightstatus" 58 59 // Takeoff event 60 Takeoff = "takeoff" 61 62 // Hovering event 63 Hovering = "hovering" 64 65 // Flying event 66 Flying = "flying" 67 68 // Landing event 69 Landing = "landing" 70 71 // Landed event 72 Landed = "landed" 73 74 // Emergency event 75 Emergency = "emergency" 76 77 // Rolling event 78 Rolling = "rolling" 79 80 // FlatTrimChange event 81 FlatTrimChange = "flattrimchange" 82 83 // LightFixed mode for LightControl 84 LightFixed = 0 85 86 // LightBlinked mode for LightControl 87 LightBlinked = 1 88 89 // LightOscillated mode for LightControl 90 LightOscillated = 3 91 92 // ClawOpen mode for ClawControl 93 ClawOpen = 0 94 95 // ClawClosed mode for ClawControl 96 ClawClosed = 1 97 ) 98 99 // Pcmd is the Parrot Command structure for flight control 100 type Pcmd struct { 101 Flag int 102 Roll int 103 Pitch int 104 Yaw int 105 Gaz int 106 Psi float32 107 } 108 109 // NewDriver creates a Parrot Minidrone Driver 110 func NewDriver(a ble.BLEConnector) *Driver { 111 n := &Driver{ 112 name: gobot.DefaultName("Minidrone"), 113 connection: a, 114 Pcmd: Pcmd{ 115 Flag: 0, 116 Roll: 0, 117 Pitch: 0, 118 Yaw: 0, 119 Gaz: 0, 120 Psi: 0, 121 }, 122 Eventer: gobot.NewEventer(), 123 } 124 125 n.AddEvent(Battery) 126 n.AddEvent(FlightStatus) 127 128 n.AddEvent(Takeoff) 129 n.AddEvent(Flying) 130 n.AddEvent(Hovering) 131 n.AddEvent(Landing) 132 n.AddEvent(Landed) 133 n.AddEvent(Emergency) 134 n.AddEvent(Rolling) 135 136 return n 137 } 138 139 // Connection returns the BLE connection 140 func (b *Driver) Connection() gobot.Connection { return b.connection } 141 142 // Name returns the Driver Name 143 func (b *Driver) Name() string { return b.name } 144 145 // SetName sets the Driver Name 146 func (b *Driver) SetName(n string) { b.name = n } 147 148 // adaptor returns BLE adaptor 149 func (b *Driver) adaptor() ble.BLEConnector { 150 return b.Connection().(ble.BLEConnector) 151 } 152 153 // Start tells driver to get ready to do work 154 func (b *Driver) Start() (err error) { 155 b.adaptor().WithoutResponses(true) 156 b.Init() 157 b.FlatTrim() 158 b.StartPcmd() 159 b.FlatTrim() 160 161 return 162 } 163 164 // Halt stops minidrone driver (void) 165 func (b *Driver) Halt() (err error) { 166 b.Land() 167 168 time.Sleep(500 * time.Millisecond) 169 return 170 } 171 172 // Init initializes the BLE insterfaces used by the Minidrone 173 func (b *Driver) Init() (err error) { 174 b.GenerateAllStates() 175 176 // subscribe to battery notifications 177 b.adaptor().Subscribe(batteryCharacteristic, func(data []byte, e error) { 178 b.Publish(b.Event(Battery), data[len(data)-1]) 179 }) 180 181 // subscribe to flying status notifications 182 b.adaptor().Subscribe(flightStatusCharacteristic, func(data []byte, e error) { 183 b.processFlightStatus(data) 184 }) 185 186 return 187 } 188 189 // GenerateAllStates sets up all the default states aka settings on the drone 190 func (b *Driver) GenerateAllStates() (err error) { 191 b.stepsfa0b++ 192 buf := []byte{0x04, byte(b.stepsfa0b), 0x00, 0x04, 0x01, 0x00, 0x32, 0x30, 0x31, 0x34, 0x2D, 0x31, 0x30, 0x2D, 0x32, 0x38, 0x00} 193 err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf) 194 195 return 196 } 197 198 // TakeOff tells the Minidrone to takeoff 199 func (b *Driver) TakeOff() (err error) { 200 b.stepsfa0b++ 201 buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x00, 0x01, 0x00} 202 err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf) 203 204 return 205 } 206 207 // Land tells the Minidrone to land 208 func (b *Driver) Land() (err error) { 209 b.stepsfa0b++ 210 buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x00, 0x03, 0x00} 211 err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf) 212 213 return err 214 } 215 216 // FlatTrim calibrates the Minidrone to use its current position as being level 217 func (b *Driver) FlatTrim() (err error) { 218 b.stepsfa0b++ 219 buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x00, 0x00, 0x00} 220 err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf) 221 222 return err 223 } 224 225 // Emergency sets the Minidrone into emergency mode 226 func (b *Driver) Emergency() (err error) { 227 b.stepsfa0b++ 228 buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x00, 0x04, 0x00} 229 err = b.adaptor().WriteCharacteristic(priorityCharacteristic, buf) 230 231 return err 232 } 233 234 // TakePicture tells the Minidrone to take a picture 235 func (b *Driver) TakePicture() (err error) { 236 b.stepsfa0b++ 237 buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x06, 0x01, 0x00} 238 err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf) 239 240 return err 241 } 242 243 // StartPcmd starts the continuous Pcmd communication with the Minidrone 244 func (b *Driver) StartPcmd() { 245 go func() { 246 // wait a little bit so that there is enough time to get some ACKs 247 time.Sleep(500 * time.Millisecond) 248 for { 249 err := b.adaptor().WriteCharacteristic(pcmdCharacteristic, b.generatePcmd().Bytes()) 250 if err != nil { 251 fmt.Println("pcmd write error:", err) 252 } 253 time.Sleep(50 * time.Millisecond) 254 } 255 }() 256 } 257 258 // Up tells the drone to ascend. Pass in an int from 0-100. 259 func (b *Driver) Up(val int) error { 260 b.pcmdMutex.Lock() 261 defer b.pcmdMutex.Unlock() 262 263 b.Pcmd.Flag = 1 264 b.Pcmd.Gaz = validatePitch(val) 265 return nil 266 } 267 268 // Down tells the drone to descend. Pass in an int from 0-100. 269 func (b *Driver) Down(val int) error { 270 b.pcmdMutex.Lock() 271 defer b.pcmdMutex.Unlock() 272 273 b.Pcmd.Flag = 1 274 b.Pcmd.Gaz = validatePitch(val) * -1 275 return nil 276 } 277 278 // Forward tells the drone to go forward. Pass in an int from 0-100. 279 func (b *Driver) Forward(val int) error { 280 b.pcmdMutex.Lock() 281 defer b.pcmdMutex.Unlock() 282 283 b.Pcmd.Flag = 1 284 b.Pcmd.Pitch = validatePitch(val) 285 return nil 286 } 287 288 // Backward tells drone to go in reverse. Pass in an int from 0-100. 289 func (b *Driver) Backward(val int) error { 290 b.pcmdMutex.Lock() 291 defer b.pcmdMutex.Unlock() 292 293 b.Pcmd.Flag = 1 294 b.Pcmd.Pitch = validatePitch(val) * -1 295 return nil 296 } 297 298 // Right tells drone to go right. Pass in an int from 0-100. 299 func (b *Driver) Right(val int) error { 300 b.pcmdMutex.Lock() 301 defer b.pcmdMutex.Unlock() 302 303 b.Pcmd.Flag = 1 304 b.Pcmd.Roll = validatePitch(val) 305 return nil 306 } 307 308 // Left tells drone to go left. Pass in an int from 0-100. 309 func (b *Driver) Left(val int) error { 310 b.pcmdMutex.Lock() 311 defer b.pcmdMutex.Unlock() 312 313 b.Pcmd.Flag = 1 314 b.Pcmd.Roll = validatePitch(val) * -1 315 return nil 316 } 317 318 // Clockwise tells drone to rotate in a clockwise direction. Pass in an int from 0-100. 319 func (b *Driver) Clockwise(val int) error { 320 b.pcmdMutex.Lock() 321 defer b.pcmdMutex.Unlock() 322 323 b.Pcmd.Flag = 1 324 b.Pcmd.Yaw = validatePitch(val) 325 return nil 326 } 327 328 // CounterClockwise tells drone to rotate in a counter-clockwise direction. 329 // Pass in an int from 0-100. 330 func (b *Driver) CounterClockwise(val int) error { 331 b.pcmdMutex.Lock() 332 defer b.pcmdMutex.Unlock() 333 334 b.Pcmd.Flag = 1 335 b.Pcmd.Yaw = validatePitch(val) * -1 336 return nil 337 } 338 339 // Stop tells the drone to stop moving in any direction and simply hover in place 340 func (b *Driver) Stop() error { 341 b.pcmdMutex.Lock() 342 defer b.pcmdMutex.Unlock() 343 344 b.Pcmd = Pcmd{ 345 Flag: 0, 346 Roll: 0, 347 Pitch: 0, 348 Yaw: 0, 349 Gaz: 0, 350 Psi: 0, 351 } 352 353 return nil 354 } 355 356 // StartRecording is not supported by the Parrot Minidrone 357 func (b *Driver) StartRecording() error { 358 return nil 359 } 360 361 // StopRecording is not supported by the Parrot Minidrone 362 func (b *Driver) StopRecording() error { 363 return nil 364 } 365 366 // HullProtection is not supported by the Parrot Minidrone 367 func (b *Driver) HullProtection(protect bool) error { 368 return nil 369 } 370 371 // Outdoor mode is not supported by the Parrot Minidrone 372 func (b *Driver) Outdoor(outdoor bool) error { 373 return nil 374 } 375 376 // FrontFlip tells the drone to perform a front flip 377 func (b *Driver) FrontFlip() (err error) { 378 return b.adaptor().WriteCharacteristic(commandCharacteristic, b.generateAnimation(0).Bytes()) 379 } 380 381 // BackFlip tells the drone to perform a backflip 382 func (b *Driver) BackFlip() (err error) { 383 return b.adaptor().WriteCharacteristic(commandCharacteristic, b.generateAnimation(1).Bytes()) 384 } 385 386 // RightFlip tells the drone to perform a flip to the right 387 func (b *Driver) RightFlip() (err error) { 388 return b.adaptor().WriteCharacteristic(commandCharacteristic, b.generateAnimation(2).Bytes()) 389 } 390 391 // LeftFlip tells the drone to perform a flip to the left 392 func (b *Driver) LeftFlip() (err error) { 393 return b.adaptor().WriteCharacteristic(commandCharacteristic, b.generateAnimation(3).Bytes()) 394 } 395 396 // LightControl controls lights on those Minidrone models which 397 // have the correct hardware, such as the Maclane, Blaze, & Swat. 398 // Params: 399 // 400 // id - always 0 401 // mode - either LightFixed, LightBlinked, or LightOscillated 402 // intensity - Light intensity from 0 (OFF) to 100 (Max intensity). 403 // Only used in LightFixed mode. 404 func (b *Driver) LightControl(id uint8, mode uint8, intensity uint8) (err error) { 405 b.stepsfa0b++ 406 buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x10, 0x00, id, mode, intensity, 0x00} 407 err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf) 408 return 409 } 410 411 // ClawControl controls the claw on the Parrot Mambo 412 // Params: 413 // 414 // id - always 0 415 // mode - either ClawOpen or ClawClosed 416 func (b *Driver) ClawControl(id uint8, mode uint8) (err error) { 417 b.stepsfa0b++ 418 buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x10, 0x01, id, mode, 0x00} 419 err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf) 420 return 421 } 422 423 // GunControl fires the gun on the Parrot Mambo 424 // Params: 425 // 426 // id - always 0 427 func (b *Driver) GunControl(id uint8) (err error) { 428 b.stepsfa0b++ 429 buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x10, 0x02, id, 0x00} 430 err = b.adaptor().WriteCharacteristic(commandCharacteristic, buf) 431 return 432 } 433 434 func (b *Driver) generateAnimation(direction int8) *bytes.Buffer { 435 b.stepsfa0b++ 436 buf := []byte{0x02, byte(b.stepsfa0b) & 0xff, 0x02, 0x04, 0x00, 0x00, byte(direction), 0x00, 0x00, 0x00} 437 return bytes.NewBuffer(buf) 438 } 439 440 func (b *Driver) generatePcmd() *bytes.Buffer { 441 b.pcmdMutex.Lock() 442 defer b.pcmdMutex.Unlock() 443 b.stepsfa0a++ 444 pcmd := b.Pcmd 445 446 cmd := &bytes.Buffer{} 447 binary.Write(cmd, binary.LittleEndian, int8(2)) 448 binary.Write(cmd, binary.LittleEndian, int8(b.stepsfa0a)) 449 binary.Write(cmd, binary.LittleEndian, int8(2)) 450 binary.Write(cmd, binary.LittleEndian, int8(0)) 451 binary.Write(cmd, binary.LittleEndian, int8(2)) 452 binary.Write(cmd, binary.LittleEndian, int8(0)) 453 binary.Write(cmd, binary.LittleEndian, int8(pcmd.Flag)) 454 binary.Write(cmd, binary.LittleEndian, int8(pcmd.Roll)) 455 binary.Write(cmd, binary.LittleEndian, int8(pcmd.Pitch)) 456 binary.Write(cmd, binary.LittleEndian, int8(pcmd.Yaw)) 457 binary.Write(cmd, binary.LittleEndian, int8(pcmd.Gaz)) 458 binary.Write(cmd, binary.LittleEndian, float32(pcmd.Psi)) 459 binary.Write(cmd, binary.LittleEndian, int16(0)) 460 binary.Write(cmd, binary.LittleEndian, int16(0)) 461 462 return cmd 463 } 464 465 func (b *Driver) processFlightStatus(data []byte) { 466 if len(data) < 5 { 467 // ignore, just a sync 468 return 469 } 470 471 b.Publish(FlightStatus, data[4]) 472 473 switch data[4] { 474 case flatTrimChanged: 475 b.Publish(FlatTrimChange, true) 476 477 case flyingStateChanged: 478 switch data[6] { 479 case flyingStateLanded: 480 if b.flying { 481 b.flying = false 482 b.Publish(Landed, true) 483 } 484 case flyingStateTakeoff: 485 b.Publish(Takeoff, true) 486 case flyingStateHovering: 487 if !b.flying { 488 b.flying = true 489 b.Publish(Hovering, true) 490 } 491 case flyingStateFlying: 492 if !b.flying { 493 b.flying = true 494 b.Publish(Flying, true) 495 } 496 case flyingStateLanding: 497 b.Publish(Landing, true) 498 case flyingStateEmergency: 499 b.Publish(Emergency, true) 500 case flyingStateRolling: 501 b.Publish(Rolling, true) 502 } 503 } 504 } 505 506 func validatePitch(val int) int { 507 if val > 100 { 508 return 100 509 } else if val < 0 { 510 return 0 511 } 512 513 return val 514 }