Reading an image bitmap file from the web using ESP8266 and C++

There are a couple of different ways to do it, but I wanted to do it after a simple image example, to understand a bit better how reading a stream from the web to get as far as the pixel information and send it to a display. As a reference then I started with /GxEPD Library :

There are a couple of basic things to understand when dealing with streams of information. One is that the information comes on chunks specially if there is a large file, then buffering whatever is coming is essential if you want to read from it. The first examples I did without buffering just filled the 7.5 E-ink display of separated lines, that only resembled part of the web screenshot I was going to send it.

So how comes an image you request from the web then ?

First of all like any other web content there is a request made to an endpoint to whatever script or API that delivers the image. This part of the code is well reflected here:

String request;
  request  = "GET " + image + " HTTP/1.1\r\n";
  request += "Accept: */*\r\n";
  request += "Host: " + host + "\r\n";
  request += "Connection: close\r\n";
  request += "\r\n";
  Serial.println(request);

  if (! client.connect(host, 80)) {
    Serial.println("connection failed");
    client.stop();
    return;
  }
  client.print(request); //send the http request to the server
  client.flush();

In this case is a get Request. Then in the case of reading a Windows BMP image that is one of the easiest formats to read, the first thing is to check for the starting bits, that for a .bmp image file are represented by 2 bytes represented by HEX 0x4D42

But before that, when you send a Request and the server replies with a Response, it comes with the headers. For example it looks something like this:

HTTP/1.1 200 OK
Host:display.local
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:61.0)
Accept: text/html

(And some more that I will spare here ending at the end with an empty line only with “\r” known as Carriage return)

Then after this the image should start. So there are two choices:

1 To make something that loops reading the first lines discarding the headers and then attempts to read the 2 starting bytes of the image

2 To read from the start including the headers and scan this 2 bytes until we find 4D42 that represent the start of the image

Between the two I prefer the first since it looks cleaner. If we where to take the second one for this image it will look like this, note is a 4-bit bmp:

5448 5054 312F 312E 3220 3030 4F20 D4B 440A 7461 3A65 5720 6465 202C 3130 4120
6775 3220 3130 2038 3131 343A 3A38 3334 4720 544D A0D 6553 7672 7265 203A 7041
6361 6568 322F 342E 312E 2036 4128 616D 6F7A 296E 4F20 6570 536E 4C53 312F 302E
312E 2D65 6966 7370 5020 5048 372F 302E 332E D30 580A 502D 776F 7265 6465 422D
3A79 5020 5048 372F 302E 332E D30 430A 6E6F 656E 7463 6F69 3A6E 6320 6F6C 6573
A0D 7254 6E61 6673 7265 452D 636E 646F 6E69 3A67 6320 7568 6B6E 6465 A0D 6F43
746E 6E65 2D74 7954 6570 203A 6D69 6761 2F65 6D62 D70 D0A 310A 3065 3637 A0D
4D42 ->BMP starts here. File size: 122998
Image Offset: 118
Header size: 40
Width * Height: 640 x 384 / Bit Depth: 4
Planes: 1
Format: 0
Bytes read:122912

Then as we can see in this example they come as the starting bits the image headers itself that are readed with this part of code:

// BMP signature
if (bmp == 0x4D42)
{
    uint32_t fileSize = read32();
    uint32_t creatorBytes = read32();
    uint32_t imageOffset = read32(); // Start of image data
    uint32_t headerSize = read32();
    uint32_t width  = read32();
    uint32_t height = read32();
    uint16_t planes = read16();
    uint16_t depth = read16(); // bits per pixel
    uint32_t format = read32();
}
uint16_t read16()
{
  // Reads 2 bytes and returns then
  uint16_t result;
  ((uint8_t *)&result)[0] = client.read(); // LSB
  ((uint8_t *)&result)[1] = client.read(); // MSB
  return result;
}

uint32_t read32()there
{
  // Reads 4 fucking bytes
  uint32_t result;
  ((uint8_t *)&result)[0] = client.read(); // LSB
  ((uint8_t *)&result)[1] = client.read();
  ((uint8_t *)&result)[2] = client.read();
  ((uint8_t *)&result)[3] = client.read(); // MSB
  return result;
}

In there comes a very important 2 bytes of information and without it is impossible or I just couldn’t find out how to read the pixels, and that’s Image Offset: 118 which means at byte 118 the image information starts. Also Depth that represents how many bits represents one single pixel. So in 1 bit, we can store a black and white image, and if we want full RGB then we need 24 bits per pixel, also 1 byte for each color (Red, Green and Blue)
Our dear Wikipedia says about this:

For an uncompressed, packed within rows, bitmap, such as is stored in Microsoft BMP file format, a lower bound on storage size for a n-bit-per-pixel (2n colors) bitmap, in bytes, can be calculated as:

size = width • height • n/8, where height and width are given in pixels.

So there we have then the Image Offset: 118, but to get to read this headers, we already got from the client 32 bytes. Then we need to make the difference and start reading the image:

// Attempt to move pointer where image starts
client.readBytes(buffer, imageOffset-bytesRead);

That should be it, then we need to read every row up to the reported width in our example 640, inside of a height loop of 384 pixels. And then read each pixel taking in account the pixel depth. In the code example this looks a bit rough around the corners:

    if ((planes == 1) && (format == 0 || format == 3)) { // uncompressed is handled
      // Attempt to move pointer where image starts
      client.readBytes(buffer, imageOffset-bytesRead);
      size_t buffidx = sizeof(buffer); // force buffer load

      for (uint16_t row = 0; row < height; row++) // for each line
      {
        uint8_t bits;
        for (uint16_t col = 0; col = sizeof(buffer))
          {
            client.readBytes(buffer, sizeof(buffer));
            buffidx = 0; // Set index to beginning
          }
          switch (depth)
          {
            case 1: // one bit per pixel b/w format
              {
                if (0 == col % 8)
                {
                  bits = buffer[buffidx++];
                  bytesRead++;
                }
                uint16_t bw_color = bits & 0x80 ? GxEPD_BLACK : GxEPD_WHITE;
                display.drawPixel(col, displayHeight-row, bw_color);
                bits <<= 1;
              }
              break;

            case 4: // was a hard word to get here
              {
                if (0 == col % 2) {
                  bits = buffer[buffidx++];
                  bytesRead++;
                }
                bits <<= 1;
                bits < 0x80 ? GxEPD_WHITE : GxEPD_BLACK;
                display.drawPixel(col, displayHeight-row, bw_color);
                bits <<= 1;
                bits < 0xFF  / 2) ? GxEPD_WHITE : GxEPD_BLACK;
                display.drawPixel(col, displayHeight-row, bw_color);
                bytesRead = bytesRead +3;
              }
          }
        } // end pixel
      } // end line

And I still have an issue that still didn't found why it does not work. This code works good and I can see images in 4-bits and 24-bits but it hangs on 1-bit image.
It's something about the headers, using the point 1 described before, also discarding headers the 1-bit image works. But not the other depths (4/24)
It's maybe some basic thing about how the byte stream comes that I'm not getting or I'm simply missing something stupid enough not to get around it.
There are other better examples on ZinggJM Repositories that deal much better with the buffering and other aspects, where the BMP reading truly works. But sometimes I like to understand the stuff and fight with it, before implementing something, since it's only way to learn how stuff works.
Have you ever though how far we are that every OS and every Browser have the resources to read almost any existing Image or Video format ? How many Megabytes of software is that ? ;)
That's what I love about coding simple examples in C++ on the Espressif chips. That you need to go deep, there is no such a thing of ready made json_decode or do-whatever libraries as in PHP. You need to read it from the bits. But the cool thing is that if you get around it, then you have a grasp of what is need to be done to read in this case a very simple Bitmap Format image. I cannot imagine how to read a compressed JPG or a PNG, I think for that yes, I will put my head down and use some library.
UPDATE: I found out after about 4 hours fight why it is. And it's the fact that I'm reading the bytes in chunks of 2. Reading them one by one and adding lastByte in the comparison to check them then it works for both 1 and 4 bits images. I can post the solution here if someone is interested, but if not, I will keep it as is to avoid making it a boring long read.

E-Paper driver to make your electronics smaller

Got from Waveshare some of this E-Paper ESP8266 driver boards:
e-paper esp8266 driver-board

I’m trying to make the electronics of my displays as small as possible and to avoid the wire-chaos inside the case with the goal of having more space for the battery. This will basically spare the need of the driver HAT and additional cables, something that is still present in my first prototype:

A bit messy uh ?

The only issue I see and I’m still sorting out with Waveshare is that the cable is not coming with the RAW display. But I think it will be not a show-stopper.

E-Paper ESP8266 driver board example developed by Waveshare

  • Supports Floyd-Steinberg dithering algorithm, more color combinations, better shadow rendering for the original image
  • Supports popular image formats: BMP, JPEG, GIF, PNG, etc.

Specifications

  • WiFi protocol: 802.11b/g/n
  • Interface: 3-wire SPI, 4-wire SPI (default)
  • Operating voltage: 5V
  • Operating current: 50mA ~ 100mA
  • Outline dimension: 29.57mm x 48.26mm
  • Mounting holes size: 2.9mm

When I find the time will post about the first tests and specially about consumption. Currently I’m using a 7.5 B/w only display with a 1200mA battery for our office. My only wish is that Waveshare electronics makes bigger E-paper displays so we can make more visible charts for our clients (10.3″ or 13 inches)

UPDATE from Waveshare

The sales people replied me, the cable is included when you buy the driver, it’s on Package contents Tab on Waveshare website:

  1. e-Paper ESP8266 Driver Board x1
  2. e-Paper Adapter x1
  3. 24PIN FFC x1
    1
    2+3-> 24 pins cable is included.
    So that’s it, you just need this driver, and to buy one RAW display. So it will run using a core of only 3 elements: Driver, Cable and E-paper display

E-Paper driver board specifications

 

Wemos D1 consumption and VCC pins

So this 6 min boring video was just an experiment to make a small consumption table comparing how the Wemos D1 mini behaves when receiving power through the 5v and 3v3  pins.

5v:
75.5 avg mA only Wifi
126.5 mA with relay on
2.5 mA on deep sleep mode

3v3:
75.4 avg mA only Wifi
119.5 mA with relay on
2.4 mA on deep sleep mode

All code examples will be published in this repository:
https://github.com/martinberlin/esp

Starting again with Arduino and 3D design

About a month ago I joined the amazing community of Thingiverse and started uploading my first designs made on Blender.

One of the first projects is to control automatically the curtain blinds to ensure we don’t have to get up to turn them manually. Actually it’s just a great excuse to learn more about arduino and stepper motor controlling.

This is the basic schema to control light detection:

Then I just connected some leds to the digital output:


int ledPinMin = 2;
void setup() {
Serial.begin(9600);
pinMode(ledPinMin, OUTPUT); // declare the ledPin as an OUTPUT
pinMode(ledPinMin+1, OUTPUT);
pinMode(ledPinMin+2, OUTPUT);
pinMode(ledPinMin+3, OUTPUT);
pinMode(ledPinMin+4, OUTPUT);
pinMode(ledPinMin+5, OUTPUT);
}
void loop() {
int sensorValue = analogRead(A5);
int ledPin = sensorValue/50 +2;
digitalWrite(ledPin, 1);
delay(sensorValue);
digitalWrite(ledPin, LOW);
delay(100);
}

I really like the projects like arduino and the power that brings 3D printing to the table. It’s a great combination. Anything can be done you just need to have an idea and be creative enough to make it happen.

Friction powered Longboard

This is a redesign of the Rotor Flettner project. The effect works after testing it mounted on a windsurf board but it has a potential flaw: Magnus effect has a smaller force than the wind pulling you to the side. The result is that you go more in the wind direction than in the direction that you really want to go. The equivalent to having a very small sail.

But anyways it was a lot of fun to make it and having already the engine and the structure to mount it I thought, why not to try doing something with it :)

And it works, went about 2 kilometers back and forth after discovering I made some design errors and that friction is not my friend. Engineering lessons learnt:
1.- Control cables of the engine should not be touching the floor
2.- Engine should transmit power using some similar material as the wheel
3.- Friction generates heat and 50% of the battery or more is wasted (maybe good for the winter)

Skate and position detail

Wheel coupling with engine

New project: Portable Flettner-Rotor (Longboard)

flettner_longboard
3D model available on tridiv.com

The idea come while reading “Symmetry of Sailing” by Ross Garret. The german engineer Anton Flettner was the first to use this physical principle as propulsion engine for a ship in 1922.

The still difficult part and open points to discuss that I have in this Winter project are:

  • Material to use in the main structure. The goal is that is light, when possible less than 10 Kilos,  but resistant since it needs to provide lift.
  • Motor weight: 170 g  Speed controller: 230 g  . Cables + Battery: Aprox.  2 K. Making a total of 2.5 Kilos weight.
  • Still open to discuss: At what speed needs the cilinder to roll and what would be the ideal diameter to provide enough momentum to move 70 Kg in a Longboard (I thought about 40 cm diameter).
    The chain relation between the motor and the cilinder to have the right speed and force. Ex: 10 spin motor: 1 spin cilinder
  • And to end the question: What kind of lift will this generate on a 10 Knots wind, coming from the optimal point, let’s say 90 degrees left or right from the cilinder.

Peggy version 2.0 Led panel receiving serial data

This is a modification we did to a Peggy2 led panel.

SAMSUNG DIGITAL CAMERA
JP1 y JP2 enable the “serial hack” (Map at the footer of this post)

The steps to follow if you want to try this:

  1. Install this program in the Peggy through USB using Arduino software. This little program is just a loop that renders in the led panel putting Peggy in “Serial mode”
  2. Make the electrical brige like shown in the picture
    Originally the bridge is from P2 to JP3 and JP4 so enabling Peggy2 to be programmed. To make this point clear, after changing the bridge you will not be able to install a program in Peggy, you will just send serial data through the USB
  3. Download processing and try out programs designed to send data to Peggy
    There are some examples at the end of this entry

This is the hardware modification explained by Carlos Fasani

Originally Peggy 2 has 2 bridges (0 ohm resistances) in jumpers JP3 and JP4.

With this connection the lines A_Sel and B_Sel of the multiplexor 74HC154 (U2) that controls rows 0 to 14 are connected to RXD (pin 2) and TXD (pin 3) of the microprocessor ATmega (U1).
But as this communication legs are needed to recevied serial data we take out JP3 and JP4 leaving the serial line desconected from 74HC154 (U2).
And then we bridge JP1 and JP2 connecting A_Sel and B_Sel from multiplexor 74HC154 (U2) to SDA (pin 27) and SCL (pin 28).

This way Peggy is ready to receive data and light the ROWS at the same time.

*UPDATE*
Windell, the Chief Scientist of Evil Mad Scientist replied to our email pointing out some important details that can be useful to make this even better:

 It  looks like you’ve run the two jumpers as follows:

  * First jumper: From the left side of JP2 to the right side of JP4, and
  * Second jumper: From the left side of JP1 to the right side of JP3.
 If I’m seeing this correctly, then your modification is exactly equivalent to putting the two jumpers in locations JP1 and JP2.  (See the attached screenshot for verification of how this is wired.)  And if so, you *should* still be able to reprogram the board, even after making the modification.

Thanks a lot for your clarification. I still didn’t tried this out since I forgot my soldier equipment but for sure it will work out.
Windell also pointed out that the latest Peggy 2 versions are already capable of receiving serial data and operating the full display at the same time, so long as you have the SER option selected on the board, and you are using our Peggy2Serial library and its RecvSerial.pde sketch
Peggy2serial

ATTACHED:
https://github.com/martinberlin/sketchbook
Electronic design of Peggy2 (pdf)

Jay’s projects. Jay was one of the first ones to send serial data and video to Peggy2

Modificación para hacer funcionar en serie la Peggy2

Esta es una modificación que hicimos para poder enviar datos seriales a través de USB al panel de Leds Peggy2.

SAMSUNG DIGITAL CAMERA
JP1 y JP2 habilitan el “serial hack” (Mirar plano adjunto en esta entrada)

Los pasos a seguir para poder hacer que esta modificación y enviar datos serie son:

  1. Instalar este programa en el Peggy a través del software de Arduino. Este programa la peggy en modo “Recibir datos por serie”
    Basicamente si le echan un vistazo a el codigo, es un loop eterno que recibe los datos serie y los imprime row por row en la placa de Leds. 
  2. Hacer el puente este como esta indicado en la foto (JP1 y JP2 a P2)
    Originalmente los puentes están desde P2 a JP3 y JP4 que son para tener una exacta compatibilidad con Peggy2. Para dejar en claro este punto, al hacer este cambio la placa Peggy queda fijada en modo de recepción serie con lo cual no se puede programar mas desde Arduino sino que le enviaremos datos por Processing.
  3. Descargar el processing.org y meter programas preparados para funcionar con la peggy en modo “serie”

ADJUNTOS:
https://github.com/martinberlin/sketchbook
Repositiorio en Github con ejemplos para probar esta modificación

Descargar Peggy2 plano electronico (pdf)

Jay’s projects. Jay fue uno de los primeros en enviar datos por serie a la Peggy2

Fe de erratas para Peggy2 mapa: Populate ONLY  (JP1 and JP2) OR (JP3 and JP4).  Select: JP1 and JP2 to enable the “serial hack” or Select: JP3 and JP4 for exact compatibility with Peggy 2.0.

Una cosa interesante que he descubierto intentando subir mis codigos es que WordPress.com no te permite hacer upload de cualquier fichero, con lo cual a partir de ahora subiré todos los codigos en Github.