Rabbit Slide Show

Practical mruby/c firmware development with CRuby

2019-04-19

Description

A talk of RubyKaigi 2019 in Fukuoka. Writing mruby/c firmware applications is like writing mrbgems. You need to make some C functions and mruby wrapper of them in order to handle peripherals like sensor, flash memory or BLE. Easy to imagine it's hard to develop for a team in a situation of TIGHT COUPLING, right? I will talk about some tools, mrubyc-test and mrubyc-debugger, which I made with CRuby for testing and debugging to keep our team slack coupling.

Text

Page: 1

Practical mruby/c firmware
development with CRuby
HASUMI Hitoshi @hasumikin
RubyKaigi 2019
April 19, 2019
Fukuoka International Congress Center

Page: 2

Sake IoT project

Page: 3

Sake IoT project

Page: 4

what is mruby/c?
github.com/mrubyc/mrubyc
one of the mruby family
`/c` symbolizes compact,
concurrent and capability
especially dedicated to
one-chip microcontroller

Page: 5

mruby and mruby/c
mruby
mruby/c
v1.0.0 in Jan 2014
v1.0 in Jan 2017
for general embedded
for one-chip
software
microcontroller
RAM < 400KB
RAM < 40KB
sometimes mruby is still too big to run on
microcontroller

Page: 6

both mruby and mruby/c
bytecodes are compiled by `mrbc`
virtual machine (VM) executes the bytecode

Page: 7

bytecode
a kind of intermediate representation
virtual machine dynamically interprets the bytecode
and processes the program

Page: 8

mruby on microcontroller
RTOS (Real-Time OS) manages mruby VMs. RTOS has
features like multi tasking, etc.

Page: 9

mruby/c on microcontroller
mruby/c has its own mechanism to manage the
runtime: rrt0

Page: 10

mruby/c - virtual machine (VM)
much smaller than mruby's one
that's why mruby/c runs on smaller RAM
accordingly, mruby/c has less functionality than
mruby

Page: 11

how less?

Page: 12

how less? - for example
mruby/c doesn't have module, hence there is no
Kernel module
then you must wonder how can you `#puts`?
in mruby/c, `#puts` is implemented in Object class

Page: 13

how less? - for example
mruby/c doesn't have #send, #eval, nor
#method_missing
moreover, mruby/c neither have your favorite
features like TracePoint nor Refinements 😞

Page: 14

how less? - actually
the full list of mruby/c's classes
Array, FalseClass, Fixnum, Float, Hash, Math,
Mutex, NilClass, Numeric, Object, Proc, Range,
String, Symbol, TrueClass, VM

Page: 15

despite the fact,
no problem in practical use of microcontroller
as far as IoT go, mruby/c is enough Ruby as I expect
we can fully develop firmwares with features of
mruby/c

Page: 16

So
というわけで

Page: 17

Today's agenda
きょうはこんな話をします

Page: 18

Little more Rubyish
もうちょいRubyっぽくやろう

Page: 19


        

Page: 20

mruby/c firmware is made up of three parts
1) peripheral API wrapper (C)
2) business logic (mruby)
3) infinite loop (mruby)

Page: 21

mruby/c firmware is made up of three parts
1) peripheral API wrapper (C)
2) business logic (mruby) - model
3) infinite loop (mruby) - controller

Page: 22

things make situation difficult
peripheral API needs real hardware
business logic needs peripheral APIs really work
infinite loop needs real data from business logic

Page: 23

mruby/c firmware is made up of three parts
1) peripheral API wrapper (C)
2) business logic (mruby)
3) infinite loop (mruby)

Page: 24

peripheral API wapper
https://rubykaigi.org/2018

Page: 25

mruby/c firmware is made up of three parts
1) peripheral API (C)
2) business logic (mruby)
3) infinite loop (mruby)

Page: 26

mruby/c firmware is made up of three parts

Page: 27

mruby/c firmware is made up of three parts

Page: 28

mruby/c firmware is made up of three parts

Page: 29

mruby/c firmware is made up of three parts

Page: 30

mruby/c firmware is made up of three parts

Page: 31

by the way,

Page: 32

fuga?

Page: 33

what is fuga?

Page: 34

will calling fuga raise error?

Page: 35

methods still not implemented
we often should write business logic without hitting
peripherals
it will cost a lot in some case
it is possible the design of peripheral details might
not be finished yet
what you expect in this situation?

Page: 36

Stub

Page: 37

Mock

Page: 38

Test Driven
Development for
Embedded Ruby

Page: 39

(DEMO)
github.com/hasumikin/mrubyc-test

Page: 40

when I started to use mruby/c
there is no testing tool
even mruby/c itself sometimes regressed 😨
I had difficulties of writing my application

Page: 41

so, why did I use mruby/c?

Page: 42

DESTINO - 運命

Page: 43

Anyway, I started to create
mrubyc-test.gem

Page: 44

mrubyc-test.gem
it's the first testing tool for mruby/c ever
I wanted to go Rubyish in order to make it
but mruby/c doesn't have enough features to make
testing tool as you saw just before

Page: 45

mrubyc-test.gem - designed as
a RubyGem, implemented in CRuby instead of
mruby
Test::Unit-like API
supports stub and mock
now you can test your business logic without
implementing peripheral functions like #fuga

Page: 46

mrubyc-test.gem - stub
# app code
class Sample
attr_accessor :result
def do_something(arg)
@result = arg + still_not_defined_method
end
end
# test code
class SampleTest < MrubycTestCase
def stub_case
sample_obj = Sample.new
stub(sample_obj).still_not_defined_method { ", it must be Ruby" }
sample_obj.do_something("If it behaves like Ruby")
assert_equal "If it behaves like Ruby, it must be Ruby", sample_obj.result
end
end

Page: 47

mrubyc-test.gem - mock
# app code
class Sample
def do_other_thing
to_be_hit()
end
end
# test code
class SampleTest < MrubycTestCase
def mock_case
sample_obj = Sample.new
mock(sample_obj).to_be_hit
sample_obj.do_other_thing
end
end

Page: 48

it was my personal tool
github.com/hasumikin/mrubyc-test

Page: 49

but already abandoned because
github.com/hasumikin/mrubyc-test

Page: 50

now it's official 🎉
github.com/mrubyc/mrubyc-test

Page: 51

mrubyc-test.gem
adopted as the testing tool for mruby/c itself
so now you can safely send pull request to mruby/c
you can write mruby/c application with confidence

Page: 52

mrubyc-test.gem - internal
the gist is creating test.rb by `test code generator`
implemented in CRuby

Page: 53

mrubyc-test.gem - how to make the test.rb
gathers information of test cases by
#method_added
I learned this technique from Test::Unit
generates stub methods and mock methods
makes all-in-one script: test.rb
all the indispensable mechanism of assertion, stub,
mock, app code and test code get together

Page: 54

mrubyc-test.gem - Module#method_added
class MrubycTestCase
def self.method_added(name)
return false if %i(method_missing setup teardown).include?(name)
location = caller_locations(1, 1)[0]
path = location.absolute_path || location.path
line = location.lineno
@@added_methods << {
method_name: name.to_s,
path:
File.expand_path(path),
line:
line
}

Page: 55

mrubyc-test.gem
class SampleTest < MrubycTestCase
desc "stub test sample"
def stub_case # hooks #method_added
sample_obj = Sample.new
stub(sample_obj).still_not_defined_method {
", it must be Ruby"
}
test code inherits MrubycTestCase to be analyzed

Page: 56

mrubyc-test.gem -
BasicObject#method_missing
class MrubycTestCase
def method_missing(method_name, *args)
case method_name
when :stub, :mock
location = caller_locations(1, 1)[0]
Mrubyc::Test::Generator::Double.new(
method_name, args[0], location
)

Page: 57

mrubyc-test.gem - generated stub method
# part of test.rb
class Sample
def still_not_defined_method
", it must be Ruby"
end
end

Page: 58

mrubyc-test.gem - template of stub
<% test_cases.each do |test_case| -%>
<% test_case[:stubs].each do |stub| -%>
class <%= stub[:class_name] %>
attr_accessor <%= stub[:instance_variables] %>
def <%= stub[:method_name] %>
<% if stub[:return_value].is_a?(String) -%>
"<%= stub[:return_value] %>"
<% else -%>
<%= stub[:return_value] %>
<% end -%>
end
end
<% end -%>

Page: 59


        

Page: 60

mruby/c firmware is made up of three parts
1) peripheral API (C)
2) business logic (mruby)
3) infinite loop (mruby)

Page: 61

mruby/c firmware is made up of three parts

Page: 62

we have multiple infinite loops
firmware programming is essentially thread
programming which consists of multiple infinite loops
they keep watch on status like user input, changing
sensor value and BLE/WiFi message, then display
some information to indicate internal status

Page: 63

the loops of mruby/c are
user space threads managed by mruby/c's runtime
/* main.c */
#define MEMORY_SIZE (1024 * 40) /* 40KB */
static uint8_t mrubyc_vm_pool[MEMORY_SIZE];
int main(void) {
mrbc_init(mrubyc_vm_pool, MEMORY_SIZE);
mrbc_create_task(watch_user_interace, 0);
mrbc_create_task(change_display, 0);
mrbc_create_task(watch_sensor_value, 0);
mrbc_run();
}

Page: 64

threads of CRuby
correspond to native threads (with GVL)
def start_loops
threads = []
threads << Thread.new { watch_user_interface }
threads << Thread.new { change_display }
threads << Thread.new { watch_sensor_value }
threads.each(&:join)
end

Page: 65

(DEMO)
github.com/hasumikin/mrubyc-debugger

Page: 66

mrubyc-debugger.gem
mrubyc-debugger runs mruby/c loop script as a
CRuby thread
it simultaneously shows which lines are being
executed
besides, it have to take over the debug print of the
script
in order to do that, we can use your favorite CRuby
features like ...

Page: 67

TracePoint

Page: 68

mrubyc-debugger.gem - TracePoint
tasks = Dir.glob(File.join(Dir.pwd, "mrubyc_loops_dir", "*.rb"))
TracePoint.new(:c_call, :call, :line) do |tp|
number = nil
caller_locations(1, 1).each do |caller_location|
tasks.each_with_index do |task, i|
number = i if caller_location.to_s.include?(File.basename(task))
end
if number
@@mutex.lock
event = {
method_id:
tp.method_id,
lineno:
tp.lineno,
caller_location: caller_location,
binding:
tp.binding }
$event_queues[number].push event
@@mutex.unlock

Page: 69

Refinements

Page: 70

mrubyc-debugger.gem - Refinements
module DebugQueue
refine Kernel do
def puts(text)
$debug_queues[Thread.current[:index]] << {
level: :debug,
body: text }
assuming mruby/c loops use `#puts` for print
debug on serial console,
mrubyc-debugger takes it over to print on Curses
window

Page: 71

Curses

Page: 72

mrubyc-debugger.gem - Curses
include Curses
debug = $debug_queues[i].pop # took over by Refinements
wins[i][:out].addstr " #{debug[:level]} " + debug[:body]
event = $event_queues[i].pop # event info by TracePoint
(1..(wins[i][:src].maxy - 2)).each do |y|
wins[i][:src].setpos(y, 1)
if !@srcs[i][y]
wins[i][:src].addstr ' ' * wins[i][:src].maxx
else
# hilighten current line
wins[i][:src].attron(A_REVERSE) if y == event[:lineno]
end
end
vars = {}
event[:tp_binding].local_variables.each do |var|
vars[var] = event[:tp_binding].local_variable_get(var).inspect
end

Page: 73

Binding

Page: 74

mrubyc-debugger.gem - Binding
binding.local_variables
# => [:var_a, :var_b, ...]
binding.local_variable_get(:var_a)
# => "foo"
binding.local_variable_set(:var_a, "bar")
binding.local_variable_get(:var_a)
# => "bar"

Page: 75

summary

Page: 76

summary
mrubyc-test is the first testing tool for mruby/c. it
means mruby/c started to have its ecosystem

Page: 77

summary
mrubyc-test is the first testing tool for mruby/c. it
means mruby/c started to have its ecosystem
even if Matz hates test

Page: 78

summary
mrubyc-test is the first testing tool for mruby/c. it
means mruby/c started to have its ecosystem
even if Matz hates test
mrubyc-debugger is a visualization tool of
concurrent mruby/c loop tasks powered by CRuby's
Thread

Page: 79

summary
mrubyc-test is the first testing tool for mruby/c. it
means mruby/c started to have its ecosystem
even if Matz hates test
mrubyc-debugger is a visualization tool of
concurrent mruby/c loop tasks powered by CRuby's
Thread
no matter what Matz regrets

Page: 80

summary
at a glance, developing with mruby/c seems to be
very restricted due to lack of dynamic features

Page: 81

summary
at a glance, developing with mruby/c seems to be
very restricted due to lack of dynamic features
however, it will be more effective by using the
power of CRuby and our own tools

Page: 82

summary
at a glance, developing with mruby/c seems to be
very restricted due to lack of dynamic features
however, it will be more effective by using the
power of CRuby and our own tools
above all, Rubyish-terminal-based development is
fun!

Page: 83

me
HASUMI Hitoshi
@hasumikin
Monstar Lab, inc.
Shimane office
Sake 🍶
Soba 🍜
Coffee ☕

Page: 84

Thank you!

Other slides