# A Detailed Guide to the Emulator ## Overview 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. ## Terms **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()`. ## General Flow ![Beatiful drawing here.](https://i.imgur.com/orWGkiu.png) At the beginning, the Emulator sends an `ACK` signal to the User Program. Then the User Program is allowed to perform a setup procedure. 1. [Emulator] -> [User Program] ACK 2. [User Program] -> [Emulator] Setup (INIT, CONFIG) 3. [User Program] -> [Emulator] Finish Setup (STOPINIT) Once setup is finished, the User Program and Emulator communicate in *turns*. Within one turn, the following occurs: 1. [User Program] (Optional) Read Data 2. [User Program] (Optional) Logic and Calculations 3. [User Program] -> [Emulator] Send Commands * Write Data (SET), * Request Data (REQUEST, REQUESTVAR), and/or * Actions (WAIT, GRAB, THROW) 6. [Emulator] Process Commands 7. [Emulator] -> [User Program] Return Requested Data 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. ## Demo Mode :::info This feature was introduced in v1.3.0. ::: This section introduces the new UI. ![](https://i.imgur.com/5QHL1Xh.png) 1. A new switch has been added. Use this to toggle between Debug Mode (the original UI) and Demo Mode. 2. A table replaces debugging information. This is where important game events will be logged (e.g. when points are gained). * Timestamps are formatted as mm:ss.zzz (minutes, seconds, milliseconds) followed by the *number of turns* after the slash. One turn is defined as one input/output cycle between the Emulator and your program. * Table tennis balls placed in baskets only score points if the basket containing them is deposited on the rack. * If your team has achieved the End Game condition, it will be logged as a separate event. 3. Some events may have long messages. Hover your mouse over its description to see a box pop up with the full sentence. 4. Your team's points are shown here. 5. Seeding functionality and the reset button are disabled in Demo Mode. * The Run button (▶️) will automatically refresh the seed and reset the game state. 7. The version number has been moved to the bottom right corner, in case you were wondering. ## Starter Kit You may download the starter kit [here](https://github.com/HKUST-Robocon/Emulator-Release/). 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 Functions The most basic Emulator I/O functions. They are, however, not necessarily "good" implementations. Here are some examples of using them: #### Example: Writing Output ```c emwrite(1, 5); // Initialise the camera. emwrite(1, 100); // Initialise the motors. emwrite(1, 101); emwrite(2, 5, 0, 20, 0.0); // Position the camera at an offset of (0, 20) cm and with no angular offset. emwrite(3, 20, 0, 24.0); // Set wheel motor 0 voltage to 24V. emwrite(3, 21, 0, 12.0); // Set wheel motor 1 voltage to 12V. emwrite(4, 5); // Request camera input on the next iteration. emwrite(10, 0); // Release the grabber. emwrite(10, 1); // Grab a tennis ball. emwrite(11); // Throw the ball. ``` :::warning :warning: Pay attention to the value of the types you pass into `emwrite`. Accidentally passing an `int` instead of a `float` can cause undefined behaviour. Read the [Commands](#Emulator-Commands) section to learn about what types to pass. ::: #### Example: Reading Input 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. ```c // Read RGB565 camera data into a 32x32 buffer. BMP* bmp; if (!emread_camera(&bmp)) { emdebug("Failed to read camera data!\n"); return; } else { // Process image... BMP_Free(bmp); // Free bmp after usage. } // Read 1-bit data from a line-tracking sensor. bool is_line; if (emread_line_sensor(&id, &is_line)) { emdebug("Read Line Sensor (id=%d): %s\n", id, (is_line ? "true" : "false")); } ``` #### Example: Requesting Data ```c bool is_data_requested = false; void loop() { // On the first turn, is_data_requested will be // false, so this block wouldn't be executed. // But on the second turn and after, is_data_requested // will be true, so we can safely read the data. if (is_data_requested) { int id; bool is_line; if (emread_line_sensor(&id, &is_line)) emdebug("Line sensor: %s\n", is_line ? "true" : "false"); } // Request data for a line sensor. emwrite(4, 10); // The data WON'T be immediately returned. // It will only be available on the next turn. is_data_requested = true; } ``` ## Emulator Commands 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. ### WAIT Placeholder command. The rest of the commands in the current batch will be ignored. **Code**: 0 **Format**: `WAIT` **Parameters**: (none) ### INIT 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**: * id: int. The **id** of the component to initialise. ### CONFIG 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**: * id: int. The **id** of the component to configure. Passing 0 would configure the robot. * x: int. The x offset, in centimetres. * y: int. The y offset, in centimetres. * angle: float. The angular offset, in radians. :::warning :warning: Configuring the robot itself (id = 0) will reset **all** components. So it is suggested that you CONFIG the robot first before initialising your desired components. ::: The robot is configured by taking its centre *relative to the field*. For example, configuring the robot to (0, 0, $\pi$) will position the robot's centre at the top left of the field, facing south. The default configuration of the robot is (50, 50, $\frac{\pi}{2}$). 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. ### CONFIGCAM :::info 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**: * attr: int. The camera's **attribute-id** to configure. * value: float. A value, appropriate to the specified attribute. | attribute-id | attribute | min | max | | ------------ | ----------------------------- | ------- | ------- | | 0 | Height (metres) | 0 | 2.0 | | 1 | Angle of Depression (radians) | 1.25π/8 | 2.95π/8 | ### SET **Code**: 3 **Type**: Motors **Format**: `SET id attr value` **Parameters**: * id: int. The **id** of the motor to set. * attr: int. The motor **attribute** to set. * value: double. The value of the **attribute** to set. | # | attribute | value | | --- | ------------- | ------------------------ | | 0 | Motor Voltage | min: -24.0V, max: +24.0V | ### REQUEST **Code**: 4 **Type**: Sensors **Format**: `REQUEST id` **Parameters**: * id: int. The **id** of the sensor to request data from. Data from requests will be returned in the same order they were requested. Be sure to use the appropriate `emread` function. ### REQUESTVAR **Code**: 5 **Format**: `REQUESTVAR var` **Parameters**: * var: int. The **variable** to request. | # | variable | | --- | --------------------- | | 10 | **GrabCode** → int[3] | <!-- For **GrabCode**, an int will be pushed to standard input on the next loop. You can read this int using `emread_int()`. --> 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 | ### GRAB **Code**: 10 **Format**: `GRAB target` **Parameters**: * target: int. The **target** to grab. Only targets 1–3 and 9–11 are meaningful. Targets 0 and 8 have been deprecated since v1.2.0. Note that targets 8–11 will be simulated as if the grabber was at a higher spot. This is only useful for placing (see [PLACE](#PLACE)). To clarify, for **grabbing**, only 1–3 should be used. For **placing**, 1–3 and 9–11 should be used. | # Low / High | target | | ------------ | ------------ | | 0 / 8 | None (void) | | 1 / 9 | Tennis | | 2 / 10 | Table-Tennis | | 3 / 11 | Basket | ### PLACE :::info This command was introduced in v1.2.0. ::: **Code**: 11 **Format**: `PLACE target` **Parameters**: * target: int. The **target** to grab. Values are the same as those in the [GRAB](#GRAB) command. ### TMLOAD :::info This command was introduced in v1.2.0. ::: **Code**: 20 **Format**: `TMLOAD strength` **Parameters**: * strength: float. How much power to load the TM with. Valid range is [0.0, 24.0]. The higher the value, the farther the ball will "fly". This command loads the [TM](#Terms), after which it cannot be interacted with for 2 turns. On the third turn after, the TM will be ready to fire (with [TMTHROW](#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](#Throwing-Mechanism) for more details. ### TMTHROW :::info 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](#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](#Throwing-Mechanism) for more details. ## Components In this context, a component is a motor, sensor, or mechanism. Each component has an ID. The sections below further elaborate on each component. ### Components IDs and Overview 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 | - | ### Camera **Read Format**: `CAMERA id` **Parameters**: * id: int. The **id** of the camera. This will always be 5. 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). ### Magnetic Sensors **Read Format**: `MAGNETIC id value` **Parameters**: * id: int. The **id** of the sensor associated with the following data. * value: int. A value in the range 0 to 255, indicating the strength of the magnetic field detected. <sub>[v1.0.2+]</sub> For the Emulator, the angular configuration of the magnetic sensors will not affect the output value. ### Line Sensors **Read Format**: `LINE id value` **Parameters**: * id: int. The **id** of the sensor associated with the following data. * value: bool. 0 or 1. Indicating whether the sensor detects a black surface. ### IR Sensors **Read Format**: `IR id value` **Parameters**: * id: int. The **id** of the sensor associated with the following data. * value: bool. 0 or 1. Indicating whether an obstacle can be detected in front of the IR. ### Grabbing Mechanism In the Emulator, the retrieve baskets are fixed to the ground. <!-- Currently only one object can be grabbed at a time. That is, the mechanism can only grab one of: * 1 Tennis Ball, * 1 Table-Tennis Ball, or * 1 Placing Basket. --> The GM allows grabbing of multiple objects. Here is a brief description of what it *can* and *cannot* do. It can: * Grab up to 3 balls (from any combination of Tennis balls and Ping-Pong balls), * Grab up to 1 basket, and * Expend grabbed balls in any order (implementation defined). But it can**not**: * Grab basket(s) *and* balls at the same instance, * Grab more than 3 balls * Grab more than 1 basket #### Usage Grabbing can be done by repeatedly using the [GRAB](#GRAB) and [PLACE](#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()`. ### Throwing Mechanism :::info 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](#TMLOAD)) in the Idle state. It can only be shot ([TMTHROW](#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: ![](https://i.imgur.com/dZBIsQf.png) ## Seeding :::info This feature was introduced in v1.1.0. ::: ### What dis? 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. ### Usage 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). :::danger :heavy_exclamation_mark: Note that using the same seed does **not** guarantee that two simulations will be perfectly replicated. Other factors in play (e.g. physics) cannot be controlled by the seed. ::: ## Error Messages * "Timeout after XXXms of inactivity.": To put a time constraint, the Emulator limits each turn to at most 500ms. If your program does not output anything within 500ms of the previous output, then emulation will stop. * "Parse error!": Error parsing your output. If you're using `emwrite()`, this shouldn't be a problem as formatting is handled. * "Not enough arguments!": Insufficient arguments for a given command. * "Component not found!": A component with the given id could not be found. * "Component not enabled!": A component with the given id was found, but wasn't enabled. * "This command can only be emitted from the setup() function.": Whatever command this message is referring to, **don't** call it from `loop()`. * "This command can only be emitted from the loop() function.": Whatever command this message is referring to, **don't** call it from `setup()`. * "GM was already used this turn.": You're attempting to use a GM command twice in one turn. * "TM was already used this turn.": You're attempting to use a TM command twice in one turn. * "Configuration out of range!": Emitted when trying to configure a component outside the body of the robot. * "Unknown option encountered!": Emitted when an unknown option is passed (e.g. to `CONFIGCAM`). * "Bad sequencing.": When the TM was not expecting a state (e.g. loading when already loaded, throwing when not loaded). * "No tennis balls.": When the TM cannot load any tennis balls. * "TM still loading.": When you attempt to throw while the TM is loading. ## More Examples #### Example: Delaying 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. ```cpp const uint64_t msec_to_wait = 10000; // Amount of time to wait. static int iter = 0; static int finished = 0; void loop(void) { static clock_t start = 0; if (start == 0) { // Set a reference point with the current clock time. start = clock(); } // Convert ms to clock time. const uint64_t clocktime_to_wait = msec_to_wait * (CLOCKS_PER_SEC / 1000); // Retrieve the current clock time and compare it with `start`. // If the elapsed time exceeds our target wait time, then we're finished waiting. const clock_t now = clock(); if (now - start > clocktime_to_wait) { // Finished waiting. // Do stuff... printf("Finished!\n"); finished = 1; } else { emwrite(0); // Send a WAIT command, so that the Emulator is aware that our // program is still alive. } } ``` Note that the delay wouldn't be exact. ## FAQ * [Q&A](https://docs.google.com/document/d/1AqXYctp2MiOqkPS6WZT-kyLZbZt8r-DA7BN0-aEfdwA/edit#heading=h.leauutjmwkph) * [Ask Here: Question Form](https://forms.gle/f5MxZNEdJwPJecTS6) (Although asking through WhatsApp is faster.)