mruby

Practical mruby/c firmware development with CRuby

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.

RubyKaigi 2019 https://rubykaigi.org/2019/presentations/hasumikin.html#apr19

RubyKaigi 2019

00:00:00.060 Hello, everybody. Almost all the people listening to our talk today are interested in our specification story or CSV story. Thank you for participating! My name is Hitoshi Hasumi, and my talk is titled 'Practical mruby/c Firmware Development with CRuby.'
00:00:08.490 This is my IoT system for a sake brewery. The temperature sensor is connected to these boxes, which send data such as temperature and humidity to a server. I am using mruby/c for these devices. This box is a prototype. The first prototype I talked about last year at RubyKaigi looked very, very basic. This year I made it a little smarter, but its function is essentially the same as the first unit. I am using mruby/c as well.
00:00:34.350 First, I will give a short explanation of what mruby/c is. I will refer to it as mrbc for simplicity. So, what is mruby/c? It's one of the mruby family. The last letter 'C' symbolizes compact, concurrent, and capability. It is especially dedicated to one-chip microcontrollers.
00:01:01.590 A microcontroller is a monolithic integrated circuit that consists of a CPU, memory, and general-purpose I/O pins. You can regard it as a tiny computer. This is a comparison table of mruby and mruby/c. As you see, mruby is three years behind the Ruby version released in 2017.
00:01:20.450 mruby is designed for general embedded software. On the other hand, mruby/c is tailored for one-chip microcontrollers. It is assumed that the RAM is less than 400 kilobytes for mruby, while mruby/c assumes that the RAM is around 40 kilobytes, which is ten times less than mruby.
00:01:39.900 This diagram shows the basic components when they run on a microcontroller. Both mruby and mruby/c utilize the same bytecode compiled by a mruby compiler. The green colors indicate that each byte is exactly the same, while the blue areas are different.
00:02:02.759 Now, let’s provide an elementary explanation of bytecode. Bytecode is a kind of intermediate representation. A virtual machine (VM) then interprets the bytecode and processes the program. The red circles represent theory, while the black circle represents your Ruby script. Your Ruby script will be tokenized and compiled into bytecode, which the VM can execute.
00:02:14.440 The lower part shows that the mruby family and mruby/c share the same compilation process. However, mruby requires an RTOS to run on microcontrollers, while mruby/c does not need any OS for multitasking. This is because it employs a long-neck mechanism for multitasking named RT0 Ruby.
00:02:35.250 I believe that mruby's VM is much smaller than that of mruby, so mruby/c runs on smaller RAM. However, mruby/c has less functionality than mruby. For instance, mruby/c supports modules but does not support kernel modules.
00:02:55.710 You may wonder how you can test mruby/c. For example, mruby/c does not have an evil machine (error machine). Furthermore, it lacks certain features that might be favorites to some developers, such as refinements. That's problematic. Developers have only these classes available, and if you try to leverage unsupported features, the mruby/c VM will fail to process it even if your compilation went well.
00:03:41.470 Despite these limitations, mruby/c can handle embedded systems well, meaning we can fully develop applications using only the features available in mruby/c. Today's agenda will cover these topics.
00:04:09.900 Let's take a short break. Remember, there is a special place in Ruby and sake you should visit. The castle near me was built in 1611.
00:04:36.790 Now, let’s get back to the main topic. Developing firmware is made up of three parts: a peripheral API wrapper and main logic. The peripheral API acts like hardware control. Most of you might be familiar with Rails, so you may understand if I say it’s like a model in a business logic context.
00:05:07.920 This can make our development situation difficult, as the business logic needs our peripheral connections to function properly. If there is a deep dependency between the business logic and the peripheral, it can complicate development significantly.
00:05:33.080 So, how do we make them slightly coupled? Otherwise, we can easily find ourselves stuck in a situation where any modification to the code may require substantial reinstallation of the program on the microcontroller.
00:06:04.260 The first part is the peripheral API, which we can call layer C or a natural technical layer. It is almost entirely in C language, but I won’t delve into that today.
00:06:32.880 Last year, I talked about how to write the API wrapper in Ruby. I explained how to start programming with Ruby and provided tips for handling hardware for software programmers like us. I also explained why I chose a microcontroller instead of a single-board computer, like a Raspberry Pi, so feel free to check the material if you're interested.
00:07:02.440 Now let's move on to the next part, the business logic. This is another way of explaining the three components. Look at the upper left, which shows the business logic, while the right and bottom show the API wrapper written in Ruby. The infinite loop monitors values like sensors, switches, Bluetooth, Wi-Fi, etc.
00:07:34.360 The infinite loop continuously checks the sensors. When it detects a change, it interacts with the business logic, which in turn calls the appropriate Peripheral API functions. If your library is implemented, it should also be shipped with the microcontroller as a framework. By following the documentation, we should be able to implement correctly.
00:08:08.520 Now, let's talk about stubbing. What do you think about this? Yes, of course, I'm talking about methods that are not implemented. Often, we end up writing business logic without hitting the peripherals because it can be costly.
00:08:30.900 In some cases, the design of peripheral details might not even be finalized. In this situation, what can we expect? Stubbing is the answer—test-driven development (TDD) helps to ensure we can write tests even without peripheral implementations.
00:09:01.460 Does anyone here have experience with TDD for embedded Ruby? No? From now on, you can call me 'Professor.' Let’s proceed to a demonstration.
00:09:25.890 This is a test case for the string class in mruby/c. You can find many assertions. Note that test classes should inherit from mruby/c test case. I will explain it later. Let's run this test.
00:09:48.110 First, the test tool creates a test routine that identifies whether the test entity can be compiled into an executable binary. Then we execute the test to see the results.
00:10:08.950 The results show success after success, meaning all tests have passed, with various operations and assertions being validated.
00:10:30.330 When I started using mruby/c, there were no testing tools available. Debugging was challenging.
00:10:48.310 So, why did I use mruby/c? I realized it was essential, so I started creating the mruby/c test gem. This gem is the first testing framework I created specifically for mruby/c.
00:11:13.250 However, mruby/c initially lacked the features needed for building a comprehensive testing tool. Therefore, I designed it as a Ruby gem, implemented using mruby's API, so it could support mocking.
00:11:35.400 Now, you can write tests for your business logic without needing to implement peripheral functions. This is an example of how that can work in practice.
00:11:57.440 The upper part is responsible for your business logic. Let’s say a sample has a method named 'do_something,' and this method will call another not yet defined method. The test will inherit the mruby/c test case class.
00:12:19.450 As you create stubbed methods for unimplemented features, the assertions will pass successfully. If you don’t have defined methods, it’s similarly straightforward to implement stubs and mocks.
00:12:40.630 From the start, my personal tool was to address the lack of testing mechanisms in mruby/c. The official version has now accepted contributions from various developers.
00:13:02.620 Thanks to the MRuby/c test gem, it is now officially part of the mruby/c ecosystem, allowing you to send pull requests confidently.
00:13:23.530 Here is a diagram that shows how mruby/c tests work. It indicates that tests are created with various components, delivering results accurately.
00:13:50.660 Now, we can discuss how to make the tests efficient. I learned a lot about utilizing test cases and how they gather various assertions. With this setup, local variables can be changed dynamically.
00:14:15.960 In this segment with a generated stub, you can see how integration works with assertions. It creates methods that return expected values, similar to how we see in Ruby.
00:14:37.810 Next, let’s take a break before diving into the final part of my talk about loops written in mruby/c.
00:15:03.720 This segment contains an infinite loop while true. Programming often consists of threading, especially when configurations for concurrent applications are involved.
00:15:31.700 Applications typically monitor user inputs, sensor values, and Bluetooth or Wi-Fi messages all at once to ensure seamless status reporting.
00:15:55.890 Now let’s focus on creating threads, especially emphasizing how easy it is to write applications for multitasking.
00:16:21.470 This is a practical example of creating threads, using native threading for operations similar to Ruby threading.
00:16:50.130 In the debugging section, we can see our business logic class named 'MyClass,' which we've developed with quite a few methods.
00:17:14.270 We have an example of how to set breakpoints effectively with the current code structure.
00:17:39.310 When a condition is met, we can interact with the debug window to determine the current state of the application.
00:18:06.870 This involves checking local variables, understanding their values during execution, and observing how our conditions evolve.
00:18:29.060 We can utilize Ruby's debugging mechanisms to introspect our programs and understand the flow in live applications.
00:18:54.200 This gives us the ability to control the execution while it’s running. Additionally, we can move cursors during these sleep states, offering vital flexibility.
00:19:21.600 This summarizes mruby/c testing and debugging mechanisms. It signifies the beginning of a new ecosystem.
00:19:44.310 It reveals the opportunities offered by concurrent tasks to maximize our capabilities while working with mruby/c.
00:20:07.020 While developing in mruby/c appears restrictive due to its limitations, it ultimately allows for effective utilization of resources.
00:20:25.240 Terminal-based development can be fun and engaging.
00:20:39.720 My name is Hitoshi Hasumi, and I'm known as Hasumi-kun or simply Hitoshi.
00:21:01.000 I enjoy soccer, sake, and coffee. Thank you for your attention!