Animating Graph on Azure IoT DevKit

In the previous post I have shown how an image can be generated by an Azure Function and sent to IoT DevKit to display it.

In this post, I will show how that given image can be animated on the device.

The goal is to animate the graph from left to right, so it shows the measurements first closer to the y-axis, and the one further (to the right) only at a later time. I am not going to propose a generic animation algorithm, but rather an efficient and specific to this problem.

DeviceAnimation

Exploring options

Option 1: Create animation with Azure Function

Though it seems convenient to generate subsequent images in the Azure Function and sending them to the device, it requires a huge memory on the device (to buffer the images) and we would also send a lot more data to the device over the network. This results the function costing more (money), as it runs longer, as well as the device would probably need a clever way to handle messages as well as displaying them at the same time.

Because of these issues, this does not seem a viable solution for the above animation.

Option 2: Animation on the device

On the screen each row consists of 128 columns. A column is 8 pixels high represented by a single byte, where each bit represents a pixel of the column. A byte array represents the image to be drawn on the screen. When the byte array is 128*8 (the device has 8 rows) we draw a full screen image as the below figure shows below.

Raw data to screen

In case we draw only to the half of the screen (halved vertically), we would need to put 64 bytes only to a row in the data array. When the given row is read (64 byte in this case) by Screen.draw a new row is started to be drawn from the continuation of the unread data in the array.

Zero out

The first idea might be to have the byte array, and zero out the bytes where we don't want to draw yet. Zero-ing less-and-less columns will create the draw animation.The problem with this solution is that we need to create a new large temporary array to copy the bytes over that we want to display. This solution could be optimized further.

Draw to

In the second solution, we could use the Screen.draw(x0, y0, x1, y1, data); to iteratively increment the x1 boundary value, from 1 until the end of the screen is reached.The problem with this solution is that the shape of the data array (the length of a single row drawn) is very different from the previous iteration. The temporary array to be display would have a different size in every iteration. We need a new array allocation and a clever copy for this solution.

Final Solution

In order to find a more efficient solution, one idea could be to draw only a column at a time, which will result the animation effect. This is possible because the image is stable: previously drawn pixel do not need to be overwritten later in a single frame of the animation. The only difficulty is the undocumented behavior of Screen.draw function: it cannot start drawing on odd x0 column values.

With this limitation, we can draw two columns at a time. In my case, I need to draw 6 rows [2,8). This is represented by a fixed 12-byte long byte array. In an iteration from the 0th screen column to the one before the last, I populate this column array by copying over the bytes from the corresponding rows of the message. The process is shown on the figure below:

Animation by columns

The code for this solution can be found below. In order to have the animation effect, a small delay is added in each iteration.

static void ShowHistoricalDataAnimatedCallback(const char *msg, int msgLength)
{
  if (app_status != 6 || msg == NULL || msgLength != 6 * 128)
  {
    return;
  }
  Screen.clean();
  DrawTitle("Environment");
  unsigned char column[12];

  for (int i = 0; i < 127; i += 2)
  {
    for (int j = 0; j < 6; j++)
    {
      column[j * 2] = msg[128 * j + i];
      column[j * 2 + 1] = msg[128 * j + i + 1];
    }
    Screen.draw(i, 2, i + 2, 8, column);
    delay(10);
  }
}