We built the SpinWheel and its engaging aesthetics around the Arduino platform because of its low barrier to entry. We also wanted our online learning resources to be interactive, in the style of Explorable Explanations, as this significantly improves learning outcomes. Thus, we needed to find a way to “compile” example C code in our webpages and show how changes to the code would affect the physical device. Since the SpinWheel contains motion sensors which are central to many of the lessons we want to teach, we sought a way to simulate that in a web page as well. Read on to see our misadventures in hacking together the most fragile C-to-Javascript transpiler: 100 lines of silly code that open up great pedagogical opportunities.
The SpinWheel - the C++ code which we want to simulate in the browser
This is the SpinWheel:
And this is the result of a lesson which teaches how to turn this wearable keychain/jewelry into a step counter. The large LEDs respond to the current motion, while the small LEDs accumulate the total motion, hence counting “steps”.
The C++ code to do this is fairly simple (or it will be after you go through our lessons). We can show it here in its entirety, but even such a short piece of code can look intimidating to those new to coding. Instead, let us explore how we can present it in an interactive fashion inside of the browser.
float total_motion = 0;
float threshold = 0.1;
float conversion_factor = 0.01;
void loop() {
// Read sensor
SpinWheel.readIMU();// Calculate intensity based on the detected acceleration
float total_acceleration = sqrt(SpinWheel.ax*SpinWheel.ax+SpinWheel.ay*SpinWheel.ay+SpinWheel.az*SpinWheel.az);
float kinematic_acceleration = abs(total_acceleration - 1.0);
int intensity = 20*kinematic_acceleration;
SpinWheel.setLargeLEDsUniform(intensity, intensity, intensity);// Accumulate all of the motion readings over time in a single number.
// To avoid false readings, perform the accumulation only if the motion
// was sufficiently strong.
if (kinematic_acceleration>threshold) {
total_motion = total_motion+conversion_factor*kinematic_acceleration;
}// Display how much you have moved, by turning on the corresponding
// number of small LEDs.
0,total_motion,255,255,255);
SpinWheel.setSmallLEDs(
SpinWheel.drawFrame(); }
Scoping the “Transpiler” Project
Given the aforementioned constraints, we have to find a way to compile C++ code inside of the browser and then let it run, mimicking the wearable device. Here were a few ideas we iterated through:
- Maybe we can send the code to a backend server which will compile it to a form executable in the browser (Emscripten maybe?), then send it back to the page and hook it up to a mock rendering of the SpinWheel…
- Definitely not something a group of volunteers with crazy schedules trying to prepare for their thesis defenses want to spend weeks on!
- Just the idea of sandboxing the server is unpleasant…
- And how do we incorporate the motion sensor?
- Reporting compilation errors back to students!? Terrible idea that will immediately turn them away from programming!
- Have a much more restricted set of examples, where students can modify only a single variable and the rest of the code is rendered and immutable. The actual simulation of the device will then be a completely separate piece of Javascript code that we write case-by-case.
- So much manual labor involved for such a limited and borderline boring experience…
- Use regex to turn the C++ code into Javascript code!
- Will we be able to live with the shame of such a hackish solution?
Yes, we will find a way to live with the shame. We will incorporate some of option 2 as well, so that the fragility of our “transpiler” is not too obvious. A silver lining of this approach is that separation between editable code and pre-rendered code will guide the attention of the student to the part of the code that is most important to the concept under study.
Without further ado, here is an example of what a humble version of the final result would look like. Click on Run
! Does it do what you would expect given the code?
void loop() { // Milliseconds since start int t = millis(); // Brightness periodic with time int b = (t/10)%255;
SpinWheel.drawFrame(); }
Play a bit with the modifiable part. It is still fragile, with many ways to break it, but it is sufficient for guided explorations. We will also explain the Debug
button shortly.
Editable vs Immutable Parts of the Examples
As mentioned, we will let the students modify only small parts of the “C++” code. We do this by writing parts of the code snippets in preformatted div
s, while the editable part is in textarea
tags. The entirety of html or markdown that the lesson writer needs to write for the above example is this:
<div class="ssw-codecontent" markdown=0>
<pre class="ssw-codeblock">
void loop() {
// Milliseconds since start
int t = millis();
// Brightness periodic with time
int b = (t/10)%255;</pre>
<textarea class="ssw-codeblock">
// Turn on only the RED and GREEN channels
SpinWheel.setLargeLEDsUniform(b, b, 0);</textarea>
<pre class="ssw-codeblock">
SpinWheel.drawFrame();
}</pre>
</div>
Our “transpiler” framework detects all ssw-codecontent
classes and adds all of the extra functionality around them. As a side note, you might have noticed that we are sticking to using scare quotes around “transpiler”… We are not really ready to live with the shame of the hackish solution you will see below ;) Nonetheless, we are proud that it becomes so simple to add pieces of interactive C++ code to our lessons, without requiring the lesson-writers to learn yet another framework.
The “Virtual” Device
We also needed to implement a virtual version of the device. Consider the drawFrame()
function that takes the buffer of color values and actually renders it to the LEDs. The implementation of the physical version involves a method in a C++ class (which you can view rendered in a beautiful literary programming style). It involves some crazy bit shifting and persistence of vision tricks running on the microcontroller. Thankfully, the Javascript version is much simpler. First, we recreate the device in html:
<div>
<img src="/simspinwheel/spinwheel_invertgray_nosmalls.png">
<div class="ssw-large-led ssw-large-led0"></div>
⋮<div class="ssw-large-led ssw-large-led7"></div>
<div class="ssw-small-led ssw-small-led0"></div>
⋮<div class="ssw-small-led ssw-small-led11"></div>
</div>
And we place all these small-led
and large-led
div
s using css.
.ssw-large-led {
position: absolute;
width: 5%;
height: 5%;
border-radius: 50%;
background: black;
}.ssw-large-led0 {left:49%;top:39%;}
⋮
Finally, we just use Javascript to modify them. Each <div class="ssw-codecontent">
has its own SpinWheel
Javascript class that has methods with the same names as the C++ methods, so that the “transpiler” has an easy time hooking up to the virtual SpinWheel. For example, here is the Javascript version of the drawFrame()
, without any painful bit-fiddling that the hardware version required. We also include the class constructor, as it is responsible for acquiring handles for all the div
s whose colors we will be changing (the virtual LEDs).
constructor(container) {
⋮this._ssw_lLEDdiv = Array.from(container.getElementsByClassName("ssw-large-led"));
this._ssw_lLEDarray = new Uint8Array(this._ssw_lLEDdiv.length*3);
⋮
}
drawFrame() {
this.drawLargeLEDFrame();
this.drawSmallLEDFrame();
}
drawSmallLEDFrame() {
for (let i = 0; i<this._ssw_sLEDdiv.length; i++) {
this._ssw_sLEDdiv[i].style.background = `rgb(${this._ssw_sLEDarray[3*i]},${this._ssw_sLEDarray[3*i+1]},${this._ssw_sLEDarray[3*i+2]})`;
} }
The _ssw_lLEDdiv
array contains the handles for all div
s, while the _ssw_lLEDarray
contains the RGB color values (as populated by setLargeLEDsUniform(r,g,b)
for instance.
The Second Best Part: The Actual Transpiler
It is embarrassingly simple, but quite effective:
= base_code.replace(RegExp('\\b'+pair[0]+'\\b','g'),
base_code 1]); pair[
pair
is just the pair of keywords that we need to translate: the C++ one and the Javascript one. For instance one such pair would be ['int', 'var']
. Obviously, we are losing some fidelity in the translation, especially around data types, and we can only use C++ constructs close to the Javascript syntax, but for a well-controlled educational snippet of code that is all we need. A big part of engineering is to know when to not over engineer your solution.
The regex needs to be a bit more carefully designed, but for the moment it suffices. E.g. we want to turn int intensity
into var intensity
, not var varensity
which a simple replace would have given us. To do that, the \b
piece in \bkeyword\b
matches everything that is not a word-character, i.e. not a-zA-Z0-9_
.
And we need to be a bit careful how we call that loop
function - we need a timed callback, because a busy loop would cause the webpage to hang. The gist of our solutions is:
var c = 200; // counts number of executions
function _ssw_loop() {
loop();
--;
cif (c>0) {
setTimeout(_ssw_loop, 50);
};
}_ssw_loop();
If we want to, we can even use async
to implement the popular Arduino delay
function, which pauses the execution of the program.
You can click the Debug
button to see what the generated Javascript code actually looks like.
The Truly Best Part: The Virtual Motion Sensor
Run the code below, grab the image of the SpinWheel with your mouse, and shake it. What happened to the LEDs?
void loop() { SpinWheel.readIMU(); float ax = abs(SpinWheel.ax)*100; float ay = abs(SpinWheel.ay)*100;
SpinWheel.drawFrame(); }
We still need to figure out how to present the colors in a more noticeable fashion, but your virtual SpinWheel lits up with colors infered from your virtual motion sensor. This also ended being embarrassingly simple to implement. We just added a onmousemove
handler to the div
containing the image of the SpinWheel. Then we calculate the numerical second derivative of the positions in the _ddx
and _ddy
properties of the class. Note that we are also using an exponential decay filter _ddx = 0.1*(dx-_dx)/dt + 0.9*_ddx
in order to smooth out the signal.
function elementDrag(e) {
= e || window.event;
e .preventDefault();
e// calculate the new cursor position:
= pos3 - e.clientX;
pos1 = pos4 - e.clientY;
pos2 var tnew = new Date().getTime();
var dt = tnew-t;
= tnew;
t = pos1/dt*200;
dx = pos2/dt*200;
dy ._ddx = 0.1*(dx-SpinWheel._dx)/dt + 0.9*SpinWheel._ddx;
SpinWheel._ddy = 0.1*(dy-SpinWheel._dy)/dt + 0.9*SpinWheel._ddy;
SpinWheel._dx = dx;
SpinWheel._dy = dy;
SpinWheel= e.clientX;
pos3 = e.clientY;
pos4 // set the element's new position:
.style.top = (elmnt.offsetTop - pos2) + "px";
elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
elmnt }
Now all we have to do is flesh out our Javascript SpinWheel
class such that it is up to feature parity with the C++ one running on our hardware, and start using this in all of our lessons. (We also plan to add functionality for the rotation sensor.)
We would be happy to hear your feedback on this little hack of ours. Feel free to contact us at mail@spinwearables.com
!