This is a pretty cool little line-follower. They publish the "Static Codes", but not the "Flash Codes" which are used with OzoBlockly. There is no SDK. So, we must resort to reverse engineering ;) It's a fun toy and even more fun as an enigma to be unravelled.
FlashForth is my first cut at a simple programming "IDE" available here. Have fun!
Many "Words" in FlashForth correspond directly with Ozobot instructions (see Bytecodes section below). Some words though are macros. These are mainly to construct control structure without having to think about addresses and such.
For example the FlashForth construct
loop translates into conditional and unconditional branches to relative addresses.
while COLOR sensor RED = do 127 -127 wheels loop
Macros execute at compile-time.
while merely pushes the current address (
0000) to a compile-time stack.
do emits an
if bytecode, pushes the current address (
0005), and emits a placeholder address along with the (still mysterious)
97 bytecode. Finally,
loop does all the magic. Now that the extent of the loop body is known, it pops the addresses and emits a
jump back (
-11 in this case) to the address of the point at which
while was seen; causing reevaluation of the predicate expression (
COLOR sensor RED =). It also patches the placeholder address for the
if to skip over the body (
+10 in this case). Notice that this means that FlashForth can be compiled in one pass. Also, by the way, notice that
-127 automatically becomes
It may sound like a funny name to call conditional branch on false,
if. This comes from Forth and is because of the forms for which this instruction is used. It's essentially a branch on false over the body - meaning
if true, fall through into the body.
Reading Flash Codes
In OzoBlockly, programs are transmitted to the robot through the color sensor by flashing colors on the screen (or, an interesting idea is to flash an LED at it, or even to have one Ozobot flash to another!). These are the kinds of ideas that can be explored once the protocol is known and can be used outside of the, nice but very sandboxed, OzoBlockly.
- Colors: White, R, G, B, C, M, Y, K (black)
- Framerate: 20Hz (50ms apart)
- No repeating colors (robot detects change in color rather than timing)
Scrubbing through a high-frame-rate video, it's clear that the colors being used are primary Red, Green, Blue, Cyan, Magenta, Yellow, plus White and Black. Presumably it uses an RGB sensor and these are all composed of full on/off RGB channels - very easy to detect. Further analyzing the video (and logging a capture with
FlashReader) it is clearly a 20Hz frame rate. A further observation is that colors never repeat. That is, a different color is shown every 50ms.
By experimenting with values being sent we glean the numbering scheme. It appears to be a base-7 encoding (due to no repeats within set of 8 colors) and appears to line up on byte-sized boundaries encoded as sets of three colors. In BGR space, the colors are just 3-bit values:
- 0 000 Black
- 1 001 Red
- 2 010 Green
- 3 011 Yellow (R+G)
- 4 100 Blue
- 5 101 Magenta (R+B)
- 6 110 Cyan (G+B)
White (111) isn't used as a value. Instead it signifies "repeat last color". For example KWK is just 000.
Programs are "framed" by:
CRY CYM CRW ... CMW
The first three "words" are CRY CYM CRW followed by a sequence of encoded bytes and finally by CMW. These framing words starting with cyan (C) decode to values outside of a single byte range (hex 130, 140, 12E and 14E). Everything between however seems to always decode to bytes. We will consider this a "framing" protocol; just a sequence the robot listens for to switch into "programming" mode.
The bytes within frames appear to be in the form:
VV UU XX YY ZZ ... CK
Giving a version, length and checksum. Bytes within this "envelope" are program instructions.
The Ozobot Bit seems to use
01 03, while firmware 1.4 on Evo seems to use
UU may be a version number? They have been observed to be 1, 3 when loading the Bit and 1, 7 when loading the Evo currently.
ZZ have to do with the length of the program.
It's not fully understood yet, but
ZZ (probably combined with
YY) appears to be the length of the program instructions (up to the checksum).
XX is, for some reason that's still a mystery, always 219-length for Bit and 199-length for Evo.
YY has only been observed to be zero, but likely it's the high bits of
ZZ when programs longer than 255 (
FF) are sent.
CK is a checksum to detect misreading. It is constructed from the whole payload up to the checksum - version, length and program bytes.
After a bit of experimentation, the checksum has been found to be simply a single-byte (underflowed) running difference between the bytes of the envelope, starting with zero. That is, subtract the first byte from zero, subtract the second byte from this, the third from that, and so on; keeping a running value. The result is a byte. For example, this payload:
01 03 CE 00 0D C7 2D 24 93 00 00 00 B8 00 1E 93 00 AE
It appears to be a stack machine with operands sent before operations. For example, the instruction to "set LED color" is
B8 and takes three arguments for red, green and blue values.
7F 00 00 B8 sets the LED to red. The "wait N x 10ms" instruction is
9B and takes a single argument (the number of centiseconds).
64 9B waits for one second (
64 hex = 100 dec). These can be composed:
7F 00 00 B8 64 9B 00 7F 00 B8 64 9B 00 00 7F B8 64 9B
This program fragment blinks red, then green, then blue, with one-second pauses.
Values less than 128 are considered literals and pushed to the stack. Values of 128 or higher are instructions. You may notice that in OzoBlockly negative values are supported. This is done by emitting a positive value (one less than desired) followed by a
8b hex) instruction. This gives a range of -128 to +127. FlashForth converts negative literals for you.
Differences with Evo
OzoBlockly just (26 APR 2017) released flash codes for the Ozobot Evo. There seem to be some differenced in code generation.
- Version number is
01 07(seems to switch back to
01 03for Bit)
- The magic length value is
219(see Length section above)
- Mystery instructions - programs are prepended with
2d 28 set(2d 28 93) for some reason (only for Evo)
- Program ends with
03 end(03 ae), while reverting to
00 endfor Bit.
- The known values for
02= idle. It's unknown what
03does (seems to idle).
- The known values for
Going through the various constructs in OzoBlockly, here is the bytecode to which they compile.
Move distance D mm speed S mm/s is a single bytecode (
9e) of two parameters
D S move.
Rotate angle D deg speed S mm/s is also a single bytecode (
98) of two parameters
D S turn.
Set wheel speeds: left (mm/s) L right (mm/s) R as well is a single bytecode (
9f) of two parameters
L R wheels.
Stop motors is really just
0 0 wheels.
Move forward at speed S mm/s until line is found, and then follow the line is not primitive at all. It actually becomes a whole little program fragment.
S dup dup wheels ac 08 sensor if -8 97 96 00 00 wheels c6 01 a0 ac ad 9a 10 = if -3 97 00 a0 01 25 93 // TODO: Figure this out!
get surface color and
surface color C as used with a comparison operator (
= for example) is
COLOR sensor C =. FlashForth has constants for the single-byte values used for
C (see the help tab there).
None of these are primitive.
Follow line to next intersection or line end:
call 00 09 00 end 01 a0 ac ad 9a 10 = if fd 00 a0 01 25 93 ; // TODO: Figure this out!
Pick direction D:
D call 00 0b 96 00 end dup 10 92 81 not b7 25 92 not b7 1f 93 01 a0 ad 9a 14 = if fd 00 a0 00 25 93 ; // TODO: Figure this out!
RIGHT (4), or
This is way D:
LINE sensor D 81 // TODO: Figure out what 81 is
RIGHT (4), or
Set line-following speed S mm/s:
S 18 93 // TODO: Figure this out!
Get line-following speed:
Get intersection/line-end color:
Intersection/line-end color C is the same as
Surface color C above. The same FlashForth single-byte values may be used.
Set LED color Red R Green G Blue B is a single bytecode taking three parameters:
R G B led
Turn LED off is just
0 0 0 led followed by
1e 93. // TODO: Figure out what
1e 93 means!
Set random light color isn't primative:
7f 00 rand 7f 00 rand 7f 00 rand led
Lights on Evo
Evo has some extra lights and a new instruction (
c9) for setting them. Parameters appear to be an unknown value (observed to be
00), a bit mask of which LEDs to control, followed by red, green, blue values. For example:
00 3f 7f 00 00 c9`
This sets all LEDs to red.
The bit mask appears to be in the form
E are the LEDs across the front and
T is the top LED.
Wait T x 10 ms is
Wait S.T second(s) is not primitive. If only
T centiseconds are chosen then it's just
T wait. If
S seconds are chosen (even though 1 second could be done as a simple
wait), it becomes a loop:
S 100 wait 1 - dup 0 > not if -8 97 96 // TODO: Figure out what 96 is for
T are chosen, then it becomes a loop followed by another
S 100 wait 1 - dup 00 > not if -8 97 96 T wait // TODO: Figure out what 96 is for
Terminate program on Bit and turn Ozobot off is
00 end (
OFF in FlashForth)
Terminate program and continue line following is
01 end (
FOLLOW in FlashForth)
Terminate program and switch to idle is
02 end (
IDLE in FLashForth)
IDLE mode, by the way, it is ready to accept a new program. If we can find a way to cause programs to execute without double pressing power then we could have a very nice, interactive experience with the Ozobot sitting on a tablet while being programmed from another tablet or laptop. Send a command and immediately see the result. Send another. And so on without load/run manual steps.
If P do A .... For example
If TRUE do Set light color RED then Set light color GREEN:
TRUE if +10 97 127 0 0 led jump +3 97 0 127 0 led
if instruction consumes a predicate result (boolean) and branches on false over the body of the block (
+10 in this case). It's not clear yet what the
97 bytecode does, but apparently the body
jumps over this to the following code (
0 127 0 led).
If P do A else B ... (for example
If TRUE do Set light color RED else Set light color BLUE then Set light color GREEN becomes:
TRUE if +10 97 127 0 0 led jump +7 97 0 0 127 led 0 127 0 led
if jumps over the main body (into the
else clause in this case). However, this time the
jump in the main body skips over the
else clause. Maybe that's the purpose - the OzoBlockly compiler blindly puts in this jump even without an
else. Meaningless, but harmless in that case.
If P do A else if Q do B ... is no different; just nested:
TRUE if +10 97 127 0 0 led jump +14 97 TRUE if +10 97 0 0 127 led jump +3 97 0 127 0 led
The FlashForth form for this is normal Forth-like
P if A then ... or
P if A else B then ... or
P if A else Q if B then then .... TODO: Document these macros
The OzoBlockly form
test P if true T if false F which results in a value (as opposed to
If P do A else B) becomes:
P if +7 97 T jump +4 97 F // TODO: Figure out what 97 is for
In FlashForth, there is no distinction between expresions and statements. This is just
P if T else F then as usual. The distinction in OzoBlockly is inconsistent anyway. For example, why can't you have an expression yielding a color and use that in
Set light color ...?
Boolean & Comparison
The primitive boolean operations are
8a). These appear to be separate from the bitwise operations. A normal Forth would have avoided this by useing -1 (all bits set) for
TRUE. Ozobot appears to use non-zero, like C. Poor design?
The primitive comparison operations are
9d). The others are composed of these and
8a). Not equal is
= not, less-than is
>= not and less-or-equal is
> not. There are
<= macros that expand to these in FlashForth.
Repeat forever do ... becomes:
... jump -X 97
This could be accomplished in FlashForth with
while TRUE do ... loop, but it saves five bytes to use
forever ... continue which emits exactle the above with no conditional.
Repeat N times do ... becomes:
N dup 0 > if +X 97 ... 1 - jump -Y 97 drop
+X is a relative jump to just past the
jump (to the
97 byte) and
-Y is a relative jump back to the predicate (the
dup). Equivalently in FlashForth:
N while dup 0 > do ... 1 - loop drop
Or more, idomatically (but different byte order):
N while dup 1 - 0 >= do ... loop drop
There is a
repeat macro that expands to
while dup 1 - 0 >= do and a
again macro expanding to
loop drop, making it simply:
N repeat ... again
Arithemetic operations are all of the form
Y X [op] where
0x89). The verbose
remainder of X / Y in OzoBlockly is just
mod of course.
The unary operations are of the form
X [op] where
absolute value (
negate sign (
X is odd becomes
X 2 mod
X is even becomes
X 2 mod not
X is positive becomes
X 0 >
X is negative becomes
0 X >
X is divisible by Y becomes
X Y mod not
Constrain X low L high H becomes:
H L X a9 drop a9 aa drop // TODO: figure out what `a9` and `aa` do
Random integer from N to M becomes
N M rand (
Change V by N gives a clue into how variables work (and how
sensor may actually be sampling a "virtual variable"?):
V sensor 1 + V 93
Set V to X becomes
X V set (
V begins at
Set foo to X Set bar to Y Set baz to Z becomes
X 25 set Y 26 set Z 27 set with increasing variable numbers assigned (starting with
By the way, it turns out that the
2d 24 93 we see at the start of every program from OzoBlockly is setting variable
2d for some reason. A mystery still... Programs seem to behave fine without it and for now FlashForth doesn't emit this.
Get V becomes
25 get (
0x92). This instruction is also given the name
sensor because, apparently, sensor values are store in variables. For example the
COLOR sensor is variable
LINE sensor is
The bytecode includes
; (return -
call instruction takes the next two bytes as an address.
An example pair of functions and calls,
To R Set light color RED, To G Set light color GREEN, do R, do G translates to:
call 00 0b call 00 10 00 end 7f 00 00 led ; 00 7f 00 led ;
So the two functions are packed at the end (
7f 00 00 led ; and
00 7f 00 led ;) with returns after each. The program then begins with
calls to these by address (
0010). Notice that there must be a termination (
00 end) or else the program would fall through into the first function!
3 2 1 call 00 0d 3 pop OFF end 0 pick 1 pick 2 pick led ;
The architecture is a strange one. Rather than just allow the stack to be shared across functions, there are instructions to
put to and
pop from the caller's stack frame. Why are there stack frames at all? I don't know...
In the above,
1 literals are pushed to the stack followed by a
call to a function (
000d). The function being called
picks these values from the caller's stack frame. In this case, they're being
picked in the same order that they were already on the stack. Normally, stack machines have operations such a
over, etc. to manipulate the order. In the case of Ozobot, you're free to reach in and
pick in whatever order you like. The argument to
pick is the stack depth (e.g.
0 for the top,
1 for the second value, etc.). Upon returning, the original literals are still on the stack (not consumed by
pick) and so
3 pop discards them. Note that
pop takes an argument indicating how many values to discard, while
drop always discards one. That is,
1 pop is equivalent to
It turns out that Ozobot doesn't appear to use the stack frame for return addresses and such; only data. That is good design, although I don't know what's up with all the
pop stuff. This program would also work:
3 2 1 call 00 0d OFF end led ;
The literals left by the caller are already in the correct order for
led in the the callee to use. They're consumed and so
3 pop is also not needed. In the case of arguments being out of order, it may be necessary to use
pick as we've not yet discovered a
roll, or other stack manipulation primitives.
Return values are weird no matter what you do. A normal stack machine would allow for return values to simple be left on the stack for callers to use. It seems that Ozobot has some kind of stack frames and enforces this by resetting the top-of-stack pointer upon return (
Here is the same program, only with the function returning
3 2 1 call 00 0d 2 pop OFF end 0 pick 1 pick 2 pick led 7 2 put ;
What is happening here is
1 are placed on the stack, the function is called. These values are
picked to the callee's stack frame (in the same order; could have been simplified). The callee consumes these with
led. Finally, a
7 is pushed to the stack, but we cannot simply return because the top-of-stack pointer will be reset and the caller won't have access to this. So, the
put into the third (
2) value of the caller's stackframe. Upon returning, the top two (
2) values are
popped to discard them, leaving the returned value on top.
I really wish there were a way around this top-of-stack reset. It's preventing simple returns. In fact, when calling a function taking no arguments and returning something, you have to push a dummy value to your stack for the callee to
put into. Very strange and limiting calling convention.
01 03 >= 00 3f 2d 24 set 2d call 00 0a drop 00 end 2c ~ 00 pick turn 0a 00 pick move 02 dup 00 > if 19 97 5a 00 pick turn 14 00 pick move 59 ~ 00 pick turn 14 00 pick move 01 - jump e7 97 drop 5a 00 pick turn 0a 00 pick move 2c ~ 00 pick turn ;
|0x97||?||Unknown purpose. Used in while loops.|
|0xc7||kill||Causes starter pack Ozobots to die|
This Forth-like code to blink red, green, blue with one-second delays:
some_stuff // ignore x7F 0 0 led 100 wait 0 x7F 0 led 100 wait 0 0 x7F led 100 wait off
2D 24 93 // ignore 7F 00 00 B8 64 9B 00 7F 00 B8 64 9B 00 00 7F B8 64 9B 00 AE
The version (
01 03) is prepended along with the length bytes (
C4 00 17), becomming:
01 03 C4 00 17 2D 24 93 7F 00 00 B8 64 9B 00 7F 00 B8 64 9B 00 00 7F B8 64 9B 00 AE
The checksum of this is
01 03 C4 00 17 2D 24 93 7F 00 00 B8 64 9B 00 7F 00 B8 64 9B 00 00 7F B8 64 9B 00 AE ED
This is framed within
130 140 12E ... 14E:
130 140 12E 01 03 C4 00 17 2D 24 93 7F 00 00 B8 64 9B 00 7F 00 B8 64 9B 00 00 7F B8 64 9B 00 AE ED 14E
And encodes directly to color values:
But the robots expects different colors for each frame (no repeats), so replacing repeats with white:
This can be sent to the robot with
FlashWriter and viola!