Upgrade to Pro — share decks privately, control downloads, hide ads and more …

A Droid's Journey - RubyKaigi 2018

B87c43d4be875c9b41cd436f5c364f75?s=47 hone
June 01, 2018

A Droid's Journey - RubyKaigi 2018

Joint talk with @code0100fun

Intro here: https://dee-too-intro.herokuapp.com/

Ruby has never been at the forefront of dealing with robots, IoT, or other low level systems. What Ruby is great at is scripting and building DSLs. Using mruby we can leverage existing ecosystems while still using the language we love.

In this talk, we'll deep dive into how we can execute mruby handlers inside a Go event reactor to control a Sphero R2-D2. With surprisingly few lines of code, you can coordinate motors, lights, and sound concurrently. Come learn about mruby & robotics and see the Droids™ you're looking for in action.

B87c43d4be875c9b41cd436f5c364f75?s=128

hone

June 01, 2018
Tweet

More Decks by hone

Other Decks in Programming

Transcript

  1. Ruby Kaigi Episode XII It is a dark time for

    the galaxy. The new GO ORDER has seized power over the systems, imposing it’s tyrannical rules on the population. Space battles are being fought across the galaxy and the TAB fighters are winning. The ORDER has been busy converting historic libraries, compiling their scripts into new libraries that will bend to the will of GO. With the support of a large group of fanatic and loyal followers it seems all other languages will be lost to the might of the ORDER. But a small RUBY ALLIANCE has plans to hijack the ORDER’s Goroutines and in doing so, embed itself into their droid network. A difficult task lies ahead... A surprisingly short time ago, when this conference was not too far away...
  2. A Droid’s™ Journey with mruby & Go

  3. Chase McCarthy @code0100fun

  4. Terence Lee @hone02

  5. None
  6. Austin, TX

  7. Austin, TX

  8. These are the droids you're looking for... August 2017 Sphero

    Announces R2-D2
  9. Best R2-D2 replica toy

  10. What can I do with this thing?

  11. R2D2 + Ruby = ?

  12. Ruby on Robots

  13. • Sphero support* • *Only Bluetooth Classic • No support

    for BLE! • No BB8! GitHub issue #165 Artoo
  14. Gobot

  15. Pure Go BLE library *Linux and OS X only github.com/go-ble/ble

  16. BB8 Hello World func main() { bleAdaptor := ble.NewClientAdaptor(os.Args[1]) bb8

    := bb8.NewDriver(bleAdaptor) work := func() { gobot.Every(1*time.Second, func() { r := uint8(gobot.Rand(255)) g := uint8(gobot.Rand(255)) b := uint8(gobot.Rand(255)) bb8.SetRGB(r, g, b) }) } robot := gobot.NewRobot("bb", []gobot.Connection{bleAdaptor}, []gobot.Device{bb8}, work, ) robot.Start() }
  17. gobot.NewRobot() bleAdaptor := ble.NewClientAdaptor(os.Args[1]) robot := gobot.NewRobot("bb", []gobot.Connection{bleAdaptor}, []gobot.Device{bb8}, work,

    ) robot.Start()
  18. gobot.NewRobot() bb8 := bb8.NewDriver(bleAdaptor) robot := gobot.NewRobot("bb", []gobot.Connection{bleAdaptor}, []gobot.Device{bb8}, work,

    ) robot.Start()
  19. gobot.NewRobot() work := func() { gobot.Every(1*time.Second, func() { r :=

    uint8(gobot.Rand(255)) g := uint8(gobot.Rand(255)) b := uint8(gobot.Rand(255)) bb8.SetRGB(r, g, b) }) } robot := gobot.NewRobot("bb", []gobot.Connection{bleAdaptor}, []gobot.Device{bb8}, work, ) robot.Start()
  20. gobot.NewRobot() robot := gobot.NewRobot("bb", []gobot.Connection{bleAdaptor}, []gobot.Device{bb8}, work, ) robot.Start()

  21. Demo

  22. No R2-D2 Driver

  23. Where do we start?

  24. Protocol

  25. Terminology • BLE - Bluetooth Low Energy ◦ Modern Bluetooth

    4.* spec • GATT - Generic Attribute ◦ Like a BLE plugin system
  26. What do we need to know?

  27. Services • Encapsulate the behavior of part of a device

    • Collection of data and associated behaviors
  28. Service API

  29. Characteristics • The API of a service • The “endpoints”

    to control a device
  30. How do we find the services?

  31. BlueZ

  32. gatttool $ sudo hcitool lescan

  33. gatttool $ sudo hcitool lescan LE Scan ... DF:59:60:07:1F:3B D2-1F3B

  34. gatttool $ sudo hcitool lescan LE Scan ... DF:59:60:07:1F:3B D2-1F3B

    $ gatttool -b DF:59:60:07:1F:3B -I [DF:59:60:07:1F:3B][LE]> connect Attempting to connect to DF:59:60:07:1F:3B Connection successful
  35. gatttool $ sudo hcitool lescan LE Scan ... DF:59:60:07:1F:3B D2-1F3B

    $ gatttool -b DF:59:60:07:1F:3B -I [DF:59:60:07:1F:3B][LE]> connect Attempting to connect to DF:59:60:07:1F:3B Connection successful [DF:59:60:07:1F:3B][LE]> (gatttool:11398): GLib-WARNING **: Invalid file descriptor.
  36. Attempt #2

  37. Bluetooth Sniffing Process

  38. Sniffing: Over-the-air https://www.adafruit.com/product/2269

  39. Attempt #3

  40. Sniffing: Man-in-the-middle

  41. Sniffing: Man-in-the-middle

  42. Sniffing: Man-in-the-middle

  43. Sniffing: Man-in-the-middle

  44. Sniffing: Man-in-the-middle

  45. Sniffing: Man-in-the-middle https://github.com/DigitalSecurity/btlejuice BtleJuice Framework

  46. There is a better way!

  47. Sniffing: btsnoop (Android 8+)

  48. Sniffing: btsnoop (Android 8+)

  49. Sniffing: btsnoop (Android 8+)

  50. Sniffing: btsnoop (Android 8+)

  51. Sniffing: btsnoop (Android 8+)

  52. BLE Packet Sniffing (Android 8+)

  53. BLE Packet Sniffing (Android 8+) FS > data > misc

    > bluetooth > logs
  54. BLE Packet Sniffing (Android 8+)

  55. BLE Packet Sniffing (Android 8+)

  56. BLE Packet Sniffing (Android 8+) Service UUID

  57. BLE Packet Sniffing (Android 8+) Characteristic UUID

  58. BLE Packet Sniffing (Android 8+) Packet Value

  59. • Reset logfiles • Send a command from the app

    • Dump logfiles • Load and find command • Record value • Repeat • Repeat again • ...and again Extracting Commands
  60. Look for patterns Tripod - 8d:0a:17:0d:01:01:cf:d8 Bipod - 8d:0a:17:0d:02:02:cd:d8 Tripod

    - 8d:0a:17:0d:03:01:cd:d8 Bipod - 8d:0a:17:0d:04:02:cb:d8 Tripod - 8d:0a:17:0d:05:01:cb:d8
  61. Look for patterns Tripod - 8d:0a:17:0d:01:01:cf:d8 Bipod - 8d:0a:17:0d:02:02:cd:d8 Tripod

    - 8d:0a:17:0d:03:01:cd:d8 Bipod - 8d:0a:17:0d:04:02:cb:d8 Tripod - 8d:0a:17:0d:05:01:cb:d8 Same for all
  62. Look for patterns Tripod - 8d:0a:17:0d:01:01:cf:d8 Bipod - 8d:0a:17:0d:02:02:cd:d8 Tripod

    - 8d:0a:17:0d:03:01:cd:d8 Bipod - 8d:0a:17:0d:04:02:cb:d8 Tripod - 8d:0a:17:0d:05:01:cb:d8 Changing
  63. Look for patterns Tripod - 8d:0a:17:0d:01:01:cf:d8 Bipod - 8d:0a:17:0d:02:02:cd:d8 Tripod

    - 8d:0a:17:0d:03:01:cd:d8 Bipod - 8d:0a:17:0d:04:02:cb:d8 Tripod - 8d:0a:17:0d:05:01:cb:d8 Sequence
  64. Look for patterns Tripod - 8d:0a:17:0d:01:01:cf:d8 Bipod - 8d:0a:17:0d:02:02:cd:d8 Tripod

    - 8d:0a:17:0d:03:01:cd:d8 Bipod - 8d:0a:17:0d:04:02:cb:d8 Tripod - 8d:0a:17:0d:05:01:cb:d8 Command Value
  65. Look for patterns Tripod - 8d:0a:17:0d:01:01:cf:d8 Bipod - 8d:0a:17:0d:02:02:cd:d8 Tripod

    - 8d:0a:17:0d:03:01:cd:d8 Bipod - 8d:0a:17:0d:04:02:cb:d8 Tripod - 8d:0a:17:0d:05:01:cb:d8 Checksum ~((0x0a + 0x17 + 0x0d + 0x05 + 0x01) % 0xFF) = 0xcb
  66. R2D2 Driver package r2d2 type Driver struct { name string

    connection gobot.Connection seq uint8 mtx sync.Mutex collisionResponse []uint8 packetChannel chan *packet gobot.Eventer } func (b *Driver) Tripod() { log.Print("Tripod") b.packetChannel <- b.craftPacket(0x17, 0x0d, []uint8{0x01}) }
  67. func (b *Driver) Tripod() { log.Print("Tripod") b.packetChannel <- b.craftPacket(0x17, 0x0d,

    []uint8{0x01}) } func (b *Driver) craftPacket(did byte, cid byte, body []uint8) *packet { b.mtx.Lock() defer b.mtx.Unlock() packet := new(packet) packet.header = []uint8{0x8d, 0x0a, did, cid, b.seq} packet.body = body packet.checksum = b.calculateChecksum(packet) packet.footer = []uint8{0xd8} return packet } craftPacket 8d:0a:17:0d:01:01:cf:d8
  68. Packet Sending

  69. Is Go the only option?

  70. Ruby Native Extensions in Go

  71. MRI concurrency limitations

  72. Packaging

  73. Embed mruby in Go

  74. Go has better concurrency primitives

  75. mruby & go have a packaging story

  76. mitchellh/go-mruby

  77. go-mruby Hello World func main() { mrb := mruby.NewMrb() defer

    mrb.Close() addFunc := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) { args := m.GetArgs() return mruby.Int(args[0].Fixnum() + args[1].Fixnum()), nil } class := mrb.DefineClass("Example", nil) class.DefineClassMethod("add", addFunc, mruby.ArgsReq(2)) result, err := mrb.LoadString(`Example.add(12, 30)`) if err != nil { panic(err.Error()) } fmt.Printf("Result: %s\n", result.String()) }
  78. go-mruby Hello World func main() { mrb := mruby.NewMrb() defer

    mrb.Close() addFunc := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) { args := m.GetArgs() return mruby.Int(args[0].Fixnum() + args[1].Fixnum()), nil } class := mrb.DefineClass("Example", nil) class.DefineClassMethod("add", addFunc, mruby.ArgsReq(2)) result, err := mrb.LoadString(`Example.add(12, 30)`) if err != nil { panic(err.Error()) } fmt.Printf("Result: %s\n", result.String()) }
  79. go-mruby Hello World func main() { mrb := mruby.NewMrb() defer

    mrb.Close() addFunc := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) { args := m.GetArgs() return mruby.Int(args[0].Fixnum() + args[1].Fixnum()), nil } class := mrb.DefineClass("Example", nil) class.DefineClassMethod("add", addFunc, mruby.ArgsReq(2)) result, err := mrb.LoadString(`Example.add(12, 30)`) if err != nil { panic(err.Error()) } fmt.Printf("Result: %s\n", result.String()) }
  80. go-mruby Hello World func main() { mrb := mruby.NewMrb() defer

    mrb.Close() addFunc := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) { args := m.GetArgs() return mruby.Int(args[0].Fixnum() + args[1].Fixnum()), nil } class := mrb.DefineClass("Example", nil) class.DefineClassMethod("add", addFunc, mruby.ArgsReq(2)) result, err := mrb.LoadString(`Example.add(12, 30)`) if err != nil { panic(err.Error()) } fmt.Printf("Result: %s\n", result.String()) }
  81. go-mruby Hello World func main() { mrb := mruby.NewMrb() defer

    mrb.Close() addFunc := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) { args := m.GetArgs() return mruby.Int(args[0].Fixnum() + args[1].Fixnum()), nil } class := mrb.DefineClass("Example", nil) class.DefineClassMethod("add", addFunc, mruby.ArgsReq(2)) result, err := mrb.LoadString(`Example.add(12, 30)`) if err != nil { panic(err.Error()) } fmt.Printf("Result: %s\n", result.String()) }
  82. go-mruby Hello World func main() { mrb := mruby.NewMrb() defer

    mrb.Close() addFunc := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) { args := m.GetArgs() return mruby.Int(args[0].Fixnum() + args[1].Fixnum()), nil } class := mrb.DefineClass("Example", nil) class.DefineClassMethod("add", addFunc, mruby.ArgsReq(2)) result, err := mrb.LoadString(`Example.add(12, 30)`) if err != nil { panic(err.Error()) } fmt.Printf("Result: %s\n", result.String()) }
  83. go-mruby Hello World func main() { mrb := mruby.NewMrb() defer

    mrb.Close() addFunc := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) { args := m.GetArgs() return mruby.Int(args[0].Fixnum() + args[1].Fixnum()), nil } class := mrb.DefineClass("Example", nil) class.DefineClassMethod("add", addFunc, mruby.ArgsReq(2)) result, err := mrb.LoadString(`Example.add(12, 30)`) if err != nil { panic(err.Error()) } fmt.Printf("Result: %s\n", result.String()) }
  84. go-mruby Hello World func main() { mrb := mruby.NewMrb() defer

    mrb.Close() addFunc := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) { args := m.GetArgs() return mruby.Int(args[0].Fixnum() + args[1].Fixnum()), nil } class := mrb.DefineClass("Example", nil) class.DefineClassMethod("add", addFunc, mruby.ArgsReq(2)) result, err := mrb.LoadString(`Example.add(12, 30)`) if err != nil { panic(err.Error()) } fmt.Printf("Result: %s\n", result.String()) }
  85. go-mruby Hello World func main() { mrb := mruby.NewMrb() defer

    mrb.Close() addFunc := func(m *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value) { args := m.GetArgs() return mruby.Int(args[0].Fixnum() + args[1].Fixnum()), nil } class := mrb.DefineClass("Example", nil) class.DefineClassMethod("add", addFunc, mruby.ArgsReq(2)) result, err := mrb.LoadString(`Example.add(12, 30)`) if err != nil { panic(err.Error()) } fmt.Printf("Result: %s\n", result.String()) }
  86. 1. go-mruby: Builds libmruby.a Build Process

  87. 1. go-mruby: Builds libmruby.a - MRUBY_CONFIG allows us to specify

    our own build_config.rb Build Process MRuby::Build.new do |conf| toolchain :gcc conf.gem :git => 'https://github.com/matsumoto-r/mruby-sleep.git' conf.gem :core => 'mruby-random' conf.gembox 'default' end
  88. 1. go-mruby: Builds libmruby.a - MRUBY_CONFIG allows us to specify

    our own build_config.rb - vendor mruby: clones mruby from github - build mruby - copy libmruby.a out Build Process
  89. 1. go-mruby: Builds libmruby.a - MRUBY_CONFIG allows us to specify

    our own build_config.rb - vendor mruby: clones mruby from github - build mruby - copy libmruby.a out 2. `go build` links against libmruby.a using Cgo Build Process // #cgo CFLAGS: -Ivendor/mruby/include // #cgo LDFLAGS: ${SRCDIR}/libmruby.a -lm import "C"
  90. DeeToo

  91. Vision • Only need to write Ruby • Extensible (leverage

    existing work) • Coordinate multiple async tasks • Small??
  92. r2d2_dome.mrb droid = R2D2.new("D2-1F3B") heading = 0; direction = -1

    while true do droid.dome(heading) direction = -1 if heading >= 180 direction = 1 if heading <= -160 heading = heading + direction Sleep::usleep(100) end
  93. r2d2_dome.mrb droid = R2D2.new("D2-1F3B") heading = 0; direction = -1

    while true do droid.dome(heading) direction = -1 if heading >= 180 direction = 1 if heading <= -160 heading = heading + direction Sleep::usleep(100) end
  94. r2d2_dome.mrb droid = R2D2.new("D2-1F3B") heading = 0; direction = -1

    while true do droid.dome(heading) direction = -1 if heading >= 180 direction = 1 if heading <= -160 heading = heading + direction Sleep::usleep(100) end
  95. R2D2 Dome Demo $ ./deetoo examples/r2d2_dome.mrb

  96. Setup mruby

  97. startWorker()

  98. startWorker() func main() { var wg sync.WaitGroup table := NewDroidTable()

    argsWithoutProg := os.Args[1:] for _, mrbFile := range argsWithoutProg { startWorker(mrbFile, table, &wg) } c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c for _ = range argsWithoutProg { wg.Done() } }() wg.Wait() }
  99. startWorker() func main() { var wg sync.WaitGroup table := NewDroidTable()

    argsWithoutProg := os.Args[1:] for _, mrbFile := range argsWithoutProg { startWorker(mrbFile, table, &wg) } c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c for _ = range argsWithoutProg { wg.Done() } }() wg.Wait() }
  100. startWorker() func main() { var wg sync.WaitGroup table := NewDroidTable()

    argsWithoutProg := os.Args[1:] for _, mrbFile := range argsWithoutProg { startWorker(mrbFile, table, &wg) } c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c for _ = range argsWithoutProg { wg.Done() } }() wg.Wait() }
  101. startWorker() func startWorker(file string, table droidTable, wg *sync.WaitGroup) { wg.Add(1)

    go func() { mrb := mruby.NewMrb() defer mrb.Close() droids.NewR2D2(table.r2d2, mrb) droids.NewBB8(table.bb8, mrb) dat, err := ioutil.ReadFile(file) content := string(dat) result, err := mrb.LoadString(content) fmt.Printf("Done: %s\n", result) }() }
  102. startWorker() func startWorker(file string, table droidTable, wg *sync.WaitGroup) { wg.Add(1)

    go func() { mrb := mruby.NewMrb() defer mrb.Close() droids.NewR2D2(table.r2d2, mrb) droids.NewBB8(table.bb8, mrb) dat, err := ioutil.ReadFile(file) content := string(dat) result, err := mrb.LoadString(content) fmt.Printf("Done: %s\n", result) }() }
  103. startWorker() func startWorker(file string, table droidTable, wg *sync.WaitGroup) { wg.Add(1)

    go func() { mrb := mruby.NewMrb() defer mrb.Close() droids.NewR2D2(table.r2d2, mrb) droids.NewBB8(table.bb8, mrb) dat, err := ioutil.ReadFile(file) content := string(dat) result, err := mrb.LoadString(content) fmt.Printf("Done: %s\n", result) }() }
  104. startWorker() func startWorker(file string, table droidTable, wg *sync.WaitGroup) { wg.Add(1)

    go func() { mrb := mruby.NewMrb() defer mrb.Close() droids.NewR2D2(table.r2d2, mrb) droids.NewBB8(table.bb8, mrb) dat, err := ioutil.ReadFile(file) content := string(dat) result, err := mrb.LoadString(content) fmt.Printf("Done: %s\n", result) }() }
  105. Define mruby Classes

  106. Define mruby Classes func startWorker(file string, table droidTable, wg *sync.WaitGroup)

    { wg.Add(1) go func() { mrb := mruby.NewMrb() defer mrb.Close() droids.NewR2D2(table.r2d2, mrb) droids.NewBB8(table.bb8, mrb) dat, err := ioutil.ReadFile(file) content := string(dat) result, err := mrb.LoadString(content) fmt.Printf("Done: %s\n", result) }() }
  107. Define mruby Classes func NewR2D2(table map[string]*gobotR2D2.Driver, mrb *mruby.Mrb) *r2d2 {

    droids := r2d2{ table: table, } class := mrb.DefineClass("R2D2", nil) _, err := mrb.LoadString(` class R2D2 attr_accessor :name end `) class.DefineMethod("initialize", droids.Initialize, mruby.ArgsReq(1)) class.DefineMethod("dome", droids.Dome, mruby.ArgsReq(1)) class.DefineMethod("tripod", droids.Tripod, mruby.ArgsReq(0)) class.DefineMethod("bipod", droids.Bipod, mruby.ArgsReq(0)) class.DefineMethod("macro", droids.Macro, mruby.ArgsReq(1)) class.DefineMethod("move", droids.Move, mruby.ArgsReq(2)) return &droids }
  108. Define mruby Classes func NewR2D2(table map[string]*gobotR2D2.Driver, mrb *mruby.Mrb) *r2d2 {

    droids := r2d2{ table: table, } class := mrb.DefineClass("R2D2", nil) _, err := mrb.LoadString(` class R2D2 attr_accessor :name end `) class.DefineMethod("initialize", droids.Initialize, mruby.ArgsReq(1)) class.DefineMethod("dome", droids.Dome, mruby.ArgsReq(1)) class.DefineMethod("tripod", droids.Tripod, mruby.ArgsReq(0)) class.DefineMethod("bipod", droids.Bipod, mruby.ArgsReq(0)) class.DefineMethod("macro", droids.Macro, mruby.ArgsReq(1)) class.DefineMethod("move", droids.Move, mruby.ArgsReq(2)) return &droids }
  109. R2D2.new

  110. startWorker func startWorker(file string, table droidTable, wg *sync.WaitGroup) { wg.Add(1)

    go func() { mrb := mruby.NewMrb() defer mrb.Close() droids.NewR2D2(table.r2d2, mrb) droids.NewBB8(table.bb8, mrb) dat, err := ioutil.ReadFile(file) content := string(dat) result, err := mrb.LoadString(content) fmt.Printf("Done: %s\n", result) }() }
  111. R2D2.new droid = R2D2.new("D2-1F3B") heading = 0 direction = -1

    while true do droid.dome(heading) direction = -1 if heading >= 180 direction = 1 if heading <= -160 heading = heading + direction Sleep::usleep(100) end
  112. R2D2.new func NewR2D2(table map[string]*gobotR2D2.Driver, mrb *mruby.Mrb) *r2d2 { droids :=

    r2d2{ table: table, } class := mrb.DefineClass("R2D2", nil) _, err := mrb.LoadString(` class R2D2 attr_accessor :name end `) class.DefineMethod("initialize", droids.Initialize, mruby.ArgsReq(1)) class.DefineMethod("dome", droids.Dome, mruby.ArgsReq(1)) class.DefineMethod("tripod", droids.Tripod, mruby.ArgsReq(0)) class.DefineMethod("bipod", droids.Bipod, mruby.ArgsReq(0)) class.DefineMethod("macro", droids.Macro, mruby.ArgsReq(1)) class.DefineMethod("move", droids.Move, mruby.ArgsReq(2)) return &droids }
  113. R2D2.new func (droids *r2d2) Initialize(mrb *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value)

    { args := mrb.GetArgs() name := string(args[0].String()) bleAdaptor := ble.NewClientAdaptor(name) droidDriver := gobotR2D2.NewDriver(bleAdaptor) droids.table[name] = droidDriver self.Call("name=", args[0]) var wg sync.WaitGroup wg.Add(1) work := func() { wg.Done() } robot := gobot.NewRobot("R2D2", []gobot.Connection{bleAdaptor}, []gobot.Device{droidDriver}, work) go robot.Start() wg.Wait() return self, nil }
  114. R2D2.new func (droids *r2d2) Initialize(mrb *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value)

    { args := mrb.GetArgs() name := string(args[0].String()) bleAdaptor := ble.NewClientAdaptor(name) droidDriver := gobotR2D2.NewDriver(bleAdaptor) droids.table[name] = droidDriver self.Call("name=", args[0]) var wg sync.WaitGroup wg.Add(1) work := func() { wg.Done() } robot := gobot.NewRobot("R2D2", []gobot.Connection{bleAdaptor}, []gobot.Device{droidDriver}, work) go robot.Start() wg.Wait() return self, nil }
  115. R2D2.new func (droids *r2d2) Initialize(mrb *mruby.Mrb, self *mruby.MrbValue) (mruby.Value, mruby.Value)

    { args := mrb.GetArgs() name := string(args[0].String()) bleAdaptor := ble.NewClientAdaptor(name) droidDriver := gobotR2D2.NewDriver(bleAdaptor) droids.table[name] = droidDriver self.Call("name=", args[0]) var wg sync.WaitGroup wg.Add(1) work := func() { wg.Done() } robot := gobot.NewRobot("R2D2", []gobot.Connection{bleAdaptor}, []gobot.Device{droidDriver}, work) go robot.Start() wg.Wait() return self, nil }
  116. dome() in mruby droid = R2D2.new("D2-1F3B") heading = 0; direction

    = -1 while true do droid.dome(heading) direction = -1 if heading >= 180 direction = 1 if heading <= -160 heading = heading + direction Sleep::usleep(100) end
  117. dome() Method Call

  118. dome() in mruby func NewR2D2(table map[string]*gobotR2D2.Driver, mrb *mruby.Mrb) *r2d2 {

    droids := r2d2{ table: table, } class := mrb.DefineClass("R2D2", nil) _, err := mrb.LoadString(` class R2D2 attr_accessor :name end `) class.DefineMethod("initialize", droids.Initialize, mruby.ArgsReq(1)) class.DefineMethod("dome", droids.Dome, mruby.ArgsReq(1)) class.DefineMethod("tripod", droids.Tripod, mruby.ArgsReq(0)) class.DefineMethod("bipod", droids.Bipod, mruby.ArgsReq(0)) class.DefineMethod("macro", droids.Macro, mruby.ArgsReq(1)) class.DefineMethod("move", droids.Move, mruby.ArgsReq(2)) return &droids }
  119. Dome() in DeeToo func (droids *r2d2) Dome(mrb *mruby.Mrb, self *mruby.MrbValue)

    (mruby.Value, mruby.Value) { args := mrb.GetArgs() heading := int16(args[0].Fixnum()) droids.driver(self).Dome(heading) return nil, nil }
  120. Dome() in DeeToo func (droids *r2d2) Dome(mrb *mruby.Mrb, self *mruby.MrbValue)

    (mruby.Value, mruby.Value) { args := mrb.GetArgs() heading := int16(args[0].Fixnum()) droids.driver(self).Dome(heading) return nil, nil }
  121. Dome() in gobot R2D2 Driver func (b *Driver) Dome(heading int16)

    { log.Printf("Dome - heading: %v", heading) encoded,_ := EncodeSixShifted(heading) b.packetChannel <- b.craftPacket(0x17, 0x0f, append(encoded, 0x00, 0x00)) }
  122. Managing Droid Connections

  123. Droid Table func main() { var wg sync.WaitGroup table :=

    NewDroidTable() argsWithoutProg := os.Args[1:] for _, mrbFile := range argsWithoutProg { startWorker(mrbFile, table, &wg) } c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c for _ = range argsWithoutProg { wg.Done() } }() wg.Wait() }
  124. Droid Table func main() { var wg sync.WaitGroup table :=

    NewDroidTable() argsWithoutProg := os.Args[1:] for _, mrbFile := range argsWithoutProg { startWorker(mrbFile, table, &wg) } c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c for _ = range argsWithoutProg { wg.Done() } }() wg.Wait() }
  125. Droid Table type droidTable struct { bb8 map[string]*bb8.BB8Driver r2d2 map[string]*r2d2.Driver

    } func NewDroidTable() droidTable { table := droidTable{ bb8: make(map[string]*bb8.BB8Driver), r2d2: make(map[string]*r2d2.Driver), } return table }
  126. Droid Table func main() { var wg sync.WaitGroup table :=

    NewDroidTable() argsWithoutProg := os.Args[1:] for _, mrbFile := range argsWithoutProg { startWorker(mrbFile, table, &wg) } c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt, syscall.SIGTERM) go func() { <-c for _ = range argsWithoutProg { wg.Done() } }() wg.Wait() }
  127. Droid Table func startWorker(file string, table droidTable, wg *sync.WaitGroup) {

    wg.Add(1) go func() { mrb := mruby.NewMrb() defer mrb.Close() droids.NewR2D2(table.r2d2, mrb) droids.NewBB8(table.bb8, mrb) dat, err := ioutil.ReadFile(file) content := string(dat) result, err := mrb.LoadString(content) fmt.Printf("Done: %s\n", result) }() }
  128. Droid Table func (droids *r2d2) Initialize(mrb *mruby.Mrb, self *mruby.MrbValue) (mruby.Value,

    mruby.Value) { args := mrb.GetArgs() name := string(args[0].String()) bleAdaptor := ble.NewClientAdaptor(name) droidDriver := gobotR2D2.NewDriver(bleAdaptor) droids.table[name] = droidDriver self.Call("name=", args[0]) var wg sync.WaitGroup wg.Add(1) work := func() { wg.Done() } robot := gobot.NewRobot("R2D2", []gobot.Connection{bleAdaptor}, []gobot.Device{droidDriver}, work) go robot.Start() wg.Wait() return self, nil }
  129. Droid Table func NewR2D2(table map[string]*gobotR2D2.Driver, mrb *mruby.Mrb) *r2d2 { droids

    := r2d2{ table: table, } class := mrb.DefineClass("R2D2", nil) _, err := mrb.LoadString(` class R2D2 attr_accessor :name end `) class.DefineMethod("initialize", droids.Initialize, mruby.ArgsReq(1)) class.DefineMethod("dome", droids.Dome, mruby.ArgsReq(1)) class.DefineMethod("tripod", droids.Tripod, mruby.ArgsReq(0)) class.DefineMethod("bipod", droids.Bipod, mruby.ArgsReq(0)) class.DefineMethod("macro", droids.Macro, mruby.ArgsReq(1)) class.DefineMethod("move", droids.Move, mruby.ArgsReq(2)) return &droids }
  130. Droid Table func (droids *r2d2) driver(mrbInstance *mruby.MrbValue) *gobotR2D2.Driver { value,

    err := mrbInstance.Call("name") var driver *gobotR2D2.Driver if err != nil { panic(err.Error()) } else { ident := string(value.String()) driver = droids.table[ident] } return driver }
  131. Droid Table func (droids *r2d2) driver(mrbInstance *mruby.MrbValue) *gobotR2D2.Driver { value,

    err := mrbInstance.Call("name") var driver *gobotR2D2.Driver if err != nil { panic(err.Error()) } else { ident := string(value.String()) driver = droids.table[ident] } return driver }
  132. Final Demo

  133. Future/Roadmap • Single binary by compiling the mruby byte code

    into libmruby.a • Cross compiling the deetoo tool • Message passing between mrb files
  134. Thank You https://github.com/code0100fun/deetoo