The Emulator is a simulation environment designed to compile and run C programs.
This document provides details on the communication protocol used between the Emulator and your C program.
Emulator: The application which simulates your code.
User Program: The C program you and your software groupmates will be writing. It may consist of multiple files, possibly within subfolders.
Command: An instruction to the Emulator.
Component: Any motor, sensor, or mechanism. Each component has a unique ID in the emulator.
GM: Grabbing Mechanism.
TM: Throwing Mechanism.
Turn: A cycle of information exchange (I/O) between the Emulator and User Program. Equivalent to one iteration of loop()
.
At the beginning, the Emulator sends an ACK
signal to the User Program. Then the User Program is allowed to perform a setup procedure.
Once setup is finished, the User Program and Emulator communicate in turns. Within one turn, the following occurs:
Note that simulation is kept separate from communication, in a separate thread.
The concept of requesting data and reading data is clarified in the following section.
This feature was introduced in v1.3.0.
This section introduces the new UI.
You may download the starter kit here.
The starter kit contains a compact library for Emulator I/O. It also abstracts away the main function (similar to Arduino), so that you don't need to bother yourself with the most basic level of program flow.
You are not allowed to modify certain files (main.c
, emulator.h
, emulator,c
) but are allowed to make your own functions to substitute emwrite
or emread
-series, for example. You are also allowed to create additional .h and .c files to better organise your code.
emwrite
and emread
-series FunctionsThe most basic Emulator I/O functions. They are, however, not necessarily "good" implementations. Here are some examples of using them:
emwrite
. Accidentally passing an int
instead of a float
can cause undefined behaviour. Read the Commands section to learn about what types to pass.
Note that you need to request for input first, before being able to read the input. You'll only be able to read input on the next turn, after requesting input.
The input will be sent in the same order as the requests.
The user program communicates to the Emulator by printing a series of commands to standard output. These are described in detail below.
Component IDs are listed at the end.
Placeholder command. The rest of the commands in the current batch will be ignored.
Code: 0
Format: WAIT
Parameters: (none)
Initialises a component (i.e. motor, sensor, or mechanism) with a given id.
You should initialise components at the start of your program in the setup()
function.
Code: 1
Type: Any
Format: INIT id
Parameters:
Places a component at an offset relative to the centre of the robot.
You should configure components at the start of your program in the setup()
function.
Code: 2
Type: Sensor, Mechanism, Robot
Format: CONFIG id x y angle
Parameters:
The robot is configured by taking its centre relative to the field. For example, configuring the robot to (0, 0, ) will position the robot's centre at the top left of the field, facing south. The default configuration of the robot is (50, 50, ).
Components, on the other hand, are configured at an offset relative to the robot's centre. Attempting to configure components at an offset outside the robot will result in a "Configuration out of range" error.
This command was introduced in v1.1.0.
A specialised configuration command to set different camera attributes.
You should configure the camera at the start of your program in the setup()
function.
Code: 13
Type: Camera
Format: CONFIGCAM attr value
Parameters:
attribute-id | attribute | min | max |
---|---|---|---|
0 | Height (metres) | 0 | 2.0 |
1 | Angle of Depression (radians) | 1.25π/8 | 2.95π/8 |
Code: 3
Type: Motors
Format: SET id attr value
Parameters:
# | attribute | value |
---|---|---|
0 | Motor Voltage | min: -24.0V, max: +24.0V |
Code: 4
Type: Sensors
Format: REQUEST id
Parameters:
emread
function.Code: 5
Format: REQUESTVAR var
Parameters:
# | variable |
---|---|
10 | GrabCode → int[3] |
Since v1.2.0, requesting GrabCode will now buffer three ints from standard input, one int for each grab slot. If a slot is unoccupied, a 0 is buffered. These are not necessarily aligned. The ints will be pushed forward as objects are released/thrown.
A new helper function, emread_grab_code()
, has been provided. It works similarly to emread_int()
except it receives an int[3]
instead of an int*
.
GrabCode | The object currently grabbed |
---|---|
0 | Nothing |
1 | Tennis Ball |
2 | Table-Tennis Ball |
3 | Basket |
Code: 10
Format: GRAB target
Parameters:
# Low / High | target |
---|---|
0 / 8 | None (void) |
1 / 9 | Tennis |
2 / 10 | Table-Tennis |
3 / 11 | Basket |
This command was introduced in v1.2.0.
Code: 11
Format: PLACE target
Parameters:
This command was introduced in v1.2.0.
Code: 20
Format: TMLOAD strength
Parameters:
This command loads the TM, after which it cannot be interacted with for 2 turns. On the third turn after, the TM will be ready to fire (with TMTHROW).
For the command to succeed, it has to be called in the correct state (when the TM is idle) and a tennis ball has to be available from the GM.
See Throwing Mechanism for more details.
This command was introduced in v1.2.0.
Code: 21
Format: TMTHROW
Parameters: (none)
This activates the TM and launches a tennis ball. The ball should have been loaded using the TMLOAD command. After a throw, the TM will take 1 turn to rest and can only be loaded on the turn after.
See Throwing Mechanism for more details.
In this context, a component is a motor, sensor, or mechanism. Each component has an ID.
The sections below further elaborate on each component.
The following table lists the IDs of the available components and their corresponding kind and type.
ID | Kind | Type | Colour |
---|---|---|---|
5 | Sensor | Camera | Dark Green |
6-9 | Sensor | Magnetic | Yellow |
10-14 | Sensor | Line | Light Blue |
15-19 | Sensor | IR | Dark Blue (Light Green since v1.2) |
100-101 | Motor | Wheel | Black |
200 | Mechanism | Grabbing | Magenta |
201 | Mechanism | Throwing | - |
Read Format: CAMERA id
Parameters:
The camera uses a 2D perspective, only flat image data is simulated.
When requested, image data will be saved to camera.bmp, in the same folder as the application executable. In macOS, the image will be inside the app bundle, in Contents/macOS/.
Note that some graphics drawn on the window are not copied to the image (e.g. the magnetic lines).
Read Format: MAGNETIC id value
Parameters:
For the Emulator, the angular configuration of the magnetic sensors will not affect the output value.
Read Format: LINE id value
Parameters:
Read Format: IR id value
Parameters:
In the Emulator, the retrieve baskets are fixed to the ground.
The GM allows grabbing of multiple objects. Here is a brief description of what it can and cannot do.
It can:
But it cannot:
Grabbing can be done by repeatedly using the GRAB and PLACE commands. However, the Emulator will emulate only one of these each turn.
For example, if your team would like to grab 2 ping-pong balls, you would call emwrite(10, 2)
twice across two iterations of loop()
. Similarly, to place 2 ping-pong balls, you would call emwrite(12, 2)
twice across two iterations of loop()
.
This mechanism was introduced in v1.2.0.
The TM operates in 3 states: Idle, Load, or Shoot. The TM may only be loaded (TMLOAD) in the Idle state. It can only be shot (TMTHROW) in the Load state.
Like the GM, the Emulator will emulate only one of either TMLOAD or TMTHROW each turn. Both commands need some extra turns to buffer. TMLOAD requires 2 turns to load. On the third turn after, TMTHROW can be called. TMTHROW requires 1 turn to reset.
The following table shows an optimal throwing sequence. Waiting extra turns before loading/throwing is allowed.
Turn | Throw Action |
---|---|
100 | TMLOAD |
101 | - |
102 | - |
103 | TMTHROW |
104 | - |
105 | TMLOAD |
106 | - |
… | … |
For your reference, this is what the Tennis Ball looks like when thrown:
This feature was introduced in v1.1.0.
To select the chosen basket or to generate motor/sensor noise, our Emulator will be using random number generators. RNGs are great, but suppose we want to replicate certain random situations, then we would need to repeat the same sequence of numbers.
We can do this by using a pseudo-random number generator (PRNG), which generates numbers based on a seed. The sequence of numbers generated will be the same for a given seed.
You can use this feature to replicate and debug situations where your robot failed or where improvements could be made.
By default, the seed is 0. You can input your own seed if you wish or press the refresh-seed button (↺) to let the Emulator generate one randomly. The seed should be a 32-bit unsigned int.
Every time you press the reset button (⏪), the PRNG will reseed itself with the given seed (or 0 if none was provided).
emwrite()
, this shouldn't be a problem as formatting is handled.loop()
.setup()
.CONFIGCAM
).There is no actual delay function in the Emulator. It will timeout if no output is provided within 500ms. If, for some reason, you'd like to delay for—say 3 seconds—you'd need to WAIT
across multiple turns.
This example uses clock_t
and clock()
from the time.h library. clock_t
is an integral type (i.e. similar to int
and long
) used to store units of clock time. We use CLOCKS_PER_SEC
to convert between clock time and seconds.
Note that the delay wouldn't be exact.