Using C to write to LCD 20x4 Bricklet

For this project we are assuming, that you have a C development environment set up and that you have a rudimentary understanding of the C language.

If you are totally new to C itself you should start here. If you are new to the Tinkerforge API, you should start here.

Goals

We are setting the following goals for this project:

  • Temperature, ambient light, humidity and air pressure should be shown on the LCD 20x4 Bricklet,
  • the measured values should be updated automatically when they change and
  • the measured values should be formated to be easily readable.

Since this project will likely run 24/7, we will also make sure that the application is as robust towards external influences as possible. The application should still work when

  • Bricklets are exchanged (i.e. we don't rely on UIDs),
  • Brick Daemon isn't running or is restarted,
  • WIFI Extension is out of range or
  • Weather Station is restarted (power loss or accidental USB removal).

In the following we will show step-by-step how this can be achieved.

Step 1: Discover Bricks and Bricklets

To start off, we need to define where our program should connect to:

#define HOST "localhost"
#define PORT 4223

If the WIFI Extension is used or if the Brick Daemon is running on a different PC, you have to exchange "localhost" with the IP address or hostname of the WIFI Extension or PC.

When the program is started, we need to register the IPCON_CALLBACK_ENUMERATE callback and the IPCON_CALLBACK_CONNECTED callback and trigger a first enumerate:

typedef struct {
    IPConnection ipcon;
} WeatherStation;

int main() {
    WeatherStation ws;
    ipcon_create(&ws.ipcon);
    ipcon_connect(&ws.ipcon, HOST, PORT);

    ipcon_register_callback(&ws.ipcon,
                            IPCON_CALLBACK_ENUMERATE,
                            (void *)cb_enumerate,
                            (void *)&ws);
    ipcon_register_callback(&ws.ipcon,
                            IPCON_CALLBACK_CONNECTED,
                            (void *)cb_connected,
                            (void *)&ws);

    ipcon_enumerate(&ws.ipcon);
    return 0;
}

The enumerate callback is triggered if a Brick gets connected over USB or if the ipcon_enumerate function is called. This allows to discover the Bricks and Bricklets in a stack without knowing their types or UIDs beforehand.

The connected callback is triggered if the connection to the WIFI Extension or to the Brick Daemon got established. In this callback we need to trigger the enumerate again, if the reason is an auto reconnect:

void cb_connected(uint8_t connected_reason, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(connected_reason == IPCON_CONNECT_REASON_AUTO_RECONNECT) {
        ipcon_enumerate(&ws->ipcon);
    }
}

An auto reconnect means, that the connection to the WIFI Extension or to the Brick Daemon was lost and could subsequently be established again. In this case the Bricklets may have lost their configurations and we have to reconfigure them. Since the configuration is done during the enumeration process (see below), we have to trigger another enumeration.

Step 1 put together:

typedef struct {
    IPConnection ipcon;
} WeatherStation;

void cb_connected(uint8_t connected_reason, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(connected_reason == IPCON_CONNECT_REASON_AUTO_RECONNECT) {
        ipcon_enumerate(&ws->ipcon);
    }
}

int main() {
    WeatherStation ws;
    ipcon_create(&ws.ipcon);
    ipcon_connect(&ws.ipcon, HOST, PORT);

    ipcon_register_callback(&ws.ipcon,
                            IPCON_CALLBACK_ENUMERATE,
                            (void *)cb_enumerate,
                            (void *)&ws);
    ipcon_register_callback(&ws.ipcon,
                            IPCON_CALLBACK_CONNECTED,
                            (void *)cb_connected,
                            (void *)&ws);

    ipcon_enumerate(&ws.ipcon);
    return 0;
}

Step 2: Initialize Bricklets on Enumeration

During the enumeration we want to configure all of the weather measuring Bricklets. Doing this during the enumeration ensures that Bricklets get reconfigured if the stack was disconnected or there was a power loss.

The configurations should be performed on first startup (IPCON_ENUMERATION_TYPE_CONNECTED) as well as whenever the enumeration is triggered externally by us (IPCON_ENUMERATION_TYPE_AVAILABLE):

void cb_enumerate(const char *uid, const char *connected_uid,
                  char position, uint8_t hardware_version[3],
                  uint8_t firmware_version[3], uint16_t device_identifier,
                  uint8_t enumeration_type, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(enumeration_type == IPCON_ENUMERATION_TYPE_CONNECTED ||
       enumeration_type == IPCON_ENUMERATION_TYPE_AVAILABLE) {

The LCD 20x4 configuration is simple, we want the current text cleared and we want the backlight on:

if(device_identifier == LCD_20X4_DEVICE_IDENTIFIER) {
    lcd_20x4_create(&ws->lcd, uid, &ws->ipcon);
    lcd_20x4_clear_display(&ws->lcd);
    lcd_20x4_backlight_on(&ws->lcd);
}

We configure the Ambient Light, Humidity and Barometer Bricklet to return their respective measurements continuously with a period of 1000ms (1s):

  else if(device_identifier == AMBIENT_LIGHT_DEVICE_IDENTIFIER) {
    ambient_light_create(&ws->ambient_light, uid, &ws->ipcon);
    ambient_light_set_illuminance_callback_period(&ws->ambient_light, 1000);
    ambient_light_register_callback(&ws->ambient_light,
                                    AMBIENT_LIGHT_CALLBACK_ILLUMINANCE,
                                    (void *)cb_illuminance,
                                    (void *)ws);
} else if(device_identifier == HUMIDITY_DEVICE_IDENTIFIER) {
    humidity_create(&ws->humidity, uid, &ws->ipcon);
    humidity_set_humidity_callback_period(&ws->humidity, 1000);
    humidity_register_callback(&ws->humidity,
                               HUMIDITY_CALLBACK_HUMIDITY,
                               (void *)cb_humidity,
                               (void *)ws);
} else if(device_identifier == BAROMETER_DEVICE_IDENTIFIER) {
    barometer_create(&ws->barometer, uid, &ws->ipcon);
    barometer_set_air_pressure_callback_period(&ws->barometer, 1000);
    barometer_register_callback(&ws->barometer,
                                BAROMETER_CALLBACK_AIR_PRESSURE,
                                (void *)cb_air_pressure,
                                (void *)ws);
}

This means that the Bricklets will call the cb_illuminance, cb_humidity and cb_air_pressure callback functions whenever the value has changed, but with a maximum period of 1000ms.

Step 2 put together:

void cb_enumerate(const char *uid, const char *connected_uid,
                  char position, uint8_t hardware_version[3],
                  uint8_t firmware_version[3], uint16_t device_identifier,
                  uint8_t enumeration_type, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(enumeration_type == IPCON_ENUMERATION_TYPE_CONNECTED ||
       enumeration_type == IPCON_ENUMERATION_TYPE_AVAILABLE) {
        if(device_identifier == LCD_20X4_DEVICE_IDENTIFIER) {
            lcd_20x4_create(&ws->lcd, uid, &ws->ipcon);
            lcd_20x4_clear_display(&ws->lcd);
            lcd_20x4_backlight_on(&ws->lcd);
        } else if(device_identifier == AMBIENT_LIGHT_DEVICE_IDENTIFIER) {
            ambient_light_create(&ws->ambient_light, uid, &ws->ipcon);
            ambient_light_set_illuminance_callback_period(&ws->ambient_light, 1000);
            ambient_light_register_callback(&ws->ambient_light,
                                            AMBIENT_LIGHT_CALLBACK_ILLUMINANCE,
                                            (void *)cb_illuminance,
                                            (void *)ws);
        } else if(device_identifier == HUMIDITY_DEVICE_IDENTIFIER) {
            humidity_create(&ws->humidity, uid, &ws->ipcon);
            humidity_set_humidity_callback_period(&ws->humidity, 1000);
            humidity_register_callback(&ws->humidity,
                                       HUMIDITY_CALLBACK_HUMIDITY,
                                       (void *)cb_humidity,
                                       (void *)ws);
        } else if(device_identifier == BAROMETER_DEVICE_IDENTIFIER) {
            barometer_create(&ws->barometer, uid, &ws->ipcon);
            barometer_set_air_pressure_callback_period(&ws->barometer, 1000);
            barometer_register_callback(&ws->barometer,
                                        BAROMETER_CALLBACK_AIR_PRESSURE,
                                        (void *)cb_air_pressure,
                                        (void *)ws);
        }
    }
}

Step 3: Show measurements on display

We want a neat arrangement of the measurements on the display, such as:

Illuminanc 137.39 lx
Humidity    34.10 %
Air Press  987.70 mb
Temperature 22.64 °C

The decimal marks and the units should be below each other. To achieve this we use two characters for the unit, two decimal places and crop the name to use the maximum characters that are left. That's why "Illuminanc" is missing its final "e".

sprintf(text, "%6.2f", value);

The code above converts a floating point value to a string according to the given format specification. The result will be at least 6 characters long with 2 decimal places, filled up with spaces from the left if it would be shorter than 6 characters otherwise.

void cb_illuminance(uint16_t illuminance, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;
    char text[30] = {'\0'};

    sprintf(text, "Illuminanc %6.2f lx", illuminance/10.0);
    lcd_20x4_write_line(&ws->lcd, 0, 0, text);
}

void cb_humidity(uint16_t humidity, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;
    char text[30] = {'\0'};

    sprintf(text, "Humidity   %6.2f %%", humidity/10.0);
    lcd_20x4_write_line(&ws->lcd, 1, 0, text);
}

void cb_air_pressure(int32_t air_pressure, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;
    char text[30] = {'\0'};

    sprintf(text, "Air Press %7.2f mb", air_pressure/1000.0);
    lcd_20x4_write_line(&ws->lcd, 2, 0, text);
}

We are still missing the temperature. The Barometer Bricklet can measure temperature, but it doesn't have a callback for it. As a simple workaround we can retrieve the temperature in the cb_air_pressure callback function:

void cb_air_pressure(int32_t air_pressure, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;
    char text[30] = {'\0'};

    sprintf(text, "Air Press %7.2f mb", air_pressure/1000.0);
    lcd_20x4_write_line(&ws->lcd, 2, 0, text);

    int16_t temperature;
    barometer_get_chip_temperature(&ws->barometer, &temperature);

    memset(text, '\0', sizeof(text));
    sprintf(text, "Temperature %5.2f %cC", temperature/100.0, 0xDF);
    lcd_20x4_write_line(&ws->lcd, 3, 0, text);
}

Step 3 put together:

void cb_illuminance(uint16_t illuminance, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;
    char text[30] = {'\0'};

    sprintf(text, "Illuminanc %6.2f lx", illuminance/10.0);
    lcd_20x4_write_line(&ws->lcd, 0, 0, text);
}

void cb_humidity(uint16_t humidity, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;
    char text[30] = {'\0'};

    sprintf(text, "Humidity   %6.2f %%", humidity/10.0);
    lcd_20x4_write_line(&ws->lcd, 1, 0, text);
}

void cb_air_pressure(int32_t air_pressure, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;
    char text[30] = {'\0'};

    sprintf(text, "Air Press %7.2f mb", air_pressure/1000.0);
    lcd_20x4_write_line(&ws->lcd, 2, 0, text);

    int16_t temperature;
    barometer_get_chip_temperature(&ws->barometer, &temperature);

    memset(text, '\0', sizeof(text));
    // 0xDF == ° on LCD 20x4 charset
    sprintf(text, "Temperature %5.2f %cC", temperature/100.0, 0xDF);
    lcd_20x4_write_line(&ws->lcd, 3, 0, text);
}

That's it. If we would copy these three steps together in one file and execute it, we would have a working Weather Station!

There are some obvious ways to make the output better. The name could be cropped according to the exact space that is available (depending on the number of digits of the measured value). Also, reading the temperature in the cb_air_pressure callback function is suboptimal. If the air pressure doesn't change, we won't update the temperature. It would be better to read the temperature in a different thread in an endless loop with a one second sleep after each read. But we want to keep this code as simple as possible.

However, we do not meet all of our goals yet. The program is not yet robust enough. What happens if it can't connect on startup? What happens if the enumerate after an auto reconnect doesn't work?

What we need is error handling!

Step 4: Error handling and Logging

On startup, we need to try to connect until the connection works:

while(true) {
    int rc = ipcon_connect(&ws.ipcon, HOST, PORT);
    if(rc < 0) {
        fprintf(stderr, "Could not connect to brickd: %d\n", rc);
        // TODO: sleep 1s
        continue;
    }
    break;
}

and we need to try enumerating until the message goes through:

while(true) {
    int rc = ipcon_enumerate(&ws.ipcon);
    if(rc < 0) {
        fprintf(stderr, "Could not enumerate: %d\n", rc);
        // TODO: sleep 1s
        continue;
    }
    break;
}

There is no portable sleep function in C. On Windows windows.h declares a Sleep function that takes the duration in milliseconds. On POSIX systems such as Linux and macOS there is a sleep function declared in unistd.h that takes the duration in seconds.

With these changes it is now possible to first start the program and connect the Weather Station afterwards.

We also need to make sure, that we only write to the LCD if it is already initialized:

void cb_illuminance(uint16_t illuminance, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(ws->lcd_created) {
        char text[30] = {'\0'};
        sprintf(text, "Illuminanc %6.2f lx", illuminance/10.0);
        lcd_20x4_write_line(&ws->lcd, 0, 0, text);
        printf("Write to line 0: %s\n", text);
    }
}

and that we have to deal with errors during the initialization:

if(device_identifier == AMBIENT_LIGHT_DEVICE_IDENTIFIER) {
    ambient_light_create(&ws->ambient_light, uid, &ws->ipcon);
    ambient_light_register_callback(&ws->ambient_light,
                                    AMBIENT_LIGHT_CALLBACK_ILLUMINANCE,
                                    (void *)cb_illuminance,
                                    (void *)ws);

    int rc = ambient_light_set_illuminance_callback_period(&ws->ambient_light, 1000);
    if(rc < 0) {
        fprintf(stderr, "Ambient Light init failed: %d\n", rc);
    } else {
        printf("Ambient Light initialized\n");
    }
}

Additionally we added some logging. With the logging we can later find out what exactly caused a problem, if the Weather Station failed for some time period.

For example, if we connect to the Weather Station via Wi-Fi and we have regular auto reconnects, it likely means that the Wi-Fi connection is not very stable.

Step 5: Everything put together

That's it! We are already done with our Weather Station and all of the goals should be met.

Now all of the above put together (download):

#include <stdio.h>

#include "ip_connection.h"
#include "bricklet_lcd_20x4.h"
#include "bricklet_ambient_light.h"
#include "bricklet_ambient_light_v2.h"
#include "bricklet_ambient_light_v3.h"
#include "bricklet_humidity.h"
#include "bricklet_humidity_v2.h"
#include "bricklet_barometer.h"
#include "bricklet_barometer_v2.h"

#define HOST "localhost"
#define PORT 4223

typedef struct {
    IPConnection ipcon;
    LCD20x4 lcd;
    bool lcd_created;
    AmbientLight ambient_light;
    AmbientLightV2 ambient_light_v2;
    AmbientLightV3 ambient_light_v3;
    Humidity humidity;
    HumidityV2 humidity_v2;
    Barometer barometer;
    BarometerV2 barometer_v2;
} WeatherStation;

void cb_illuminance(uint16_t illuminance, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(ws->lcd_created) {
        char text[30] = {'\0'};

        sprintf(text, "Illuminanc %6.2f lx", illuminance / 10.0);
        lcd_20x4_write_line(&ws->lcd, 0, 0, text);
        printf("Write to line 0: %s\n", text);
    }
}

void cb_illuminance_v2(uint32_t illuminance, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(ws->lcd_created) {
        char text[30] = {'\0'};

        sprintf(text, "Illumina %8.2f lx", illuminance / 100.0);
        lcd_20x4_write_line(&ws->lcd, 0, 0, text);
        printf("Write to line 0: %s\n", text);
    }
}

void cb_illuminance_v3(uint32_t illuminance, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(ws->lcd_created) {
        char text[30] = {'\0'};

        sprintf(text, "Illumina %8.2f lx", illuminance / 100.0);
        lcd_20x4_write_line(&ws->lcd, 0, 0, text);
        printf("Write to line 0: %s\n", text);
    }
}

void cb_humidity(uint16_t humidity, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(ws->lcd_created) {
        char text[30] = {'\0'};

        sprintf(text, "Humidity   %6.2f %%", humidity/10.0);
        lcd_20x4_write_line(&ws->lcd, 1, 0, text);
        printf("Write to line 1: %s\n", text);
    }
}

void cb_humidity_v2(uint16_t humidity, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(ws->lcd_created) {
        char text[30] = {'\0'};

        sprintf(text, "Humidity   %6.2f %%", humidity/100.0);
        lcd_20x4_write_line(&ws->lcd, 1, 0, text);
        printf("Write to line 1: %s\n", text);
    }
}

void cb_air_pressure(int32_t air_pressure, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(ws->lcd_created) {
        char text[30] = {'\0'};

        sprintf(text, "Air Press %7.2f mb", air_pressure / 1000.0);
        lcd_20x4_write_line(&ws->lcd, 2, 0, text);
        printf("Write to line 2: %s\n", text);

        int16_t temperature;
        int rc = barometer_get_chip_temperature(&ws->barometer, &temperature);

        if(rc < 0) {
            fprintf(stderr, "Could not get temperature: %d\n", rc);

            return;
        }

        memset(text, '\0', sizeof(text));

        // 0xDF == ° on LCD 20x4 charset.
        sprintf(text, "Temperature %5.2f %cC", temperature / 100.0, 0xDF);

        lcd_20x4_write_line(&ws->lcd, 3, 0, text);
        sprintf(text, "Temperature %5.2f °C", temperature / 100.0);
        printf("Write to line 3: %s\n", text);
    }
}

void cb_air_pressure_v2(int32_t air_pressure, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(ws->lcd_created) {
        char text[30] = {'\0'};

        sprintf(text, "Air Press %7.2f mb", air_pressure / 1000.0);
        lcd_20x4_write_line(&ws->lcd, 2, 0, text);
        printf("Write to line 2: %s\n", text);

        int32_t temperature;
        int rc = barometer_v2_get_temperature(&ws->barometer, &temperature);

        if(rc < 0) {
            fprintf(stderr, "Could not get temperature: %d\n", rc);
            return;
        }

        memset(text, '\0', sizeof(text));

        // 0xDF == ° on LCD 20x4 charset.
        sprintf(text, "Temperature %5.2f %cC", temperature / 100.0, 0xDF);

        lcd_20x4_write_line(&ws->lcd, 3, 0, text);
        sprintf(text, "Temperature %5.2f °C", temperature / 100.0);
        printf("Write to line 3: %s\n", text);
    }
}

void cb_connected(uint8_t connected_reason, void *user_data) {
    WeatherStation *ws = (WeatherStation *)user_data;

    if(connected_reason == IPCON_CONNECT_REASON_AUTO_RECONNECT) {
        printf("Auto Reconnect\n");

        while(true) {
            int rc = ipcon_enumerate(&ws->ipcon);

            if(rc < 0) {
                fprintf(stderr, "Could not enumerate: %d\n", rc);

                // TODO: sleep 1s.

                continue;
            }
            break;
        }
    }
}

void cb_enumerate(const char *uid, const char *connected_uid,
                  char position, uint8_t hardware_version[3],
                  uint8_t firmware_version[3], uint16_t device_identifier,
                  uint8_t enumeration_type, void *user_data) {
    int rc;
    WeatherStation *ws = (WeatherStation *)user_data;

    // Avoid unused parameter warning.
    (void)position;
    (void)connected_uid;
    (void)hardware_version;
    (void)firmware_version;

    if(enumeration_type == IPCON_ENUMERATION_TYPE_CONNECTED ||
       enumeration_type == IPCON_ENUMERATION_TYPE_AVAILABLE) {
        if(device_identifier == LCD_20X4_DEVICE_IDENTIFIER) {
            lcd_20x4_create(&ws->lcd, uid, &ws->ipcon);
            lcd_20x4_clear_display(&ws->lcd);
            lcd_20x4_backlight_on(&ws->lcd);
            ws->lcd_created = true;
            printf("LCD 20x4 initialized\n");
        } else if(device_identifier == AMBIENT_LIGHT_DEVICE_IDENTIFIER) {
            ambient_light_create(&ws->ambient_light, uid, &ws->ipcon);
            ambient_light_register_callback(&ws->ambient_light,
                                            AMBIENT_LIGHT_CALLBACK_ILLUMINANCE,
                                            (void (*)(void))cb_illuminance,
                                            (void *)ws);
            rc = ambient_light_set_illuminance_callback_period(&ws->ambient_light, 1000);

            if(rc < 0) {
                fprintf(stderr, "Ambient Light init failed: %d\n", rc);
            } else {
                printf("Ambient Light initialized\n");
            }
        } else if(device_identifier == AMBIENT_LIGHT_V2_DEVICE_IDENTIFIER) {
            ambient_light_v2_create(&ws->ambient_light_v2, uid, &ws->ipcon);
            ambient_light_v2_register_callback(&ws->ambient_light_v2,
                                               AMBIENT_LIGHT_V2_CALLBACK_ILLUMINANCE,
                                               (void (*)(void))cb_illuminance_v2,
                                               (void *)ws);
            rc = ambient_light_v2_set_configuration(&ws->ambient_light_v2,
                                                    AMBIENT_LIGHT_V2_ILLUMINANCE_RANGE_64000LUX,
                                                    AMBIENT_LIGHT_V2_INTEGRATION_TIME_200MS);

            if(rc < 0) {
                fprintf(stderr, "Ambient Light 2.0 init step 1 failed: %d\n", rc);
            } else {
                rc = ambient_light_v2_set_illuminance_callback_period(&ws->ambient_light_v2, 1000);

                if(rc < 0) {
                    fprintf(stderr, "Ambient Light 2.0 init step 2 failed: %d\n", rc);
                } else {
                    printf("Ambient Light 2.0 initialized\n");
                }
            }
        } else if(device_identifier == AMBIENT_LIGHT_V3_DEVICE_IDENTIFIER) {
            ambient_light_v3_create(&ws->ambient_light_v3, uid, &ws->ipcon);
            ambient_light_v3_register_callback(&ws->ambient_light_v3,
                                               AMBIENT_LIGHT_V3_CALLBACK_ILLUMINANCE,
                                               (void (*)(void))cb_illuminance_v3,
                                               (void *)ws);
            rc = ambient_light_v3_set_configuration(&ws->ambient_light_v3,
                                                    AMBIENT_LIGHT_V3_ILLUMINANCE_RANGE_64000LUX,
                                                    AMBIENT_LIGHT_V3_INTEGRATION_TIME_200MS);

            if(rc < 0) {
                fprintf(stderr, "Ambient Light 3.0 init step 1 failed: %d\n", rc);
            } else {
                rc = ambient_light_v3_set_illuminance_callback_configuration(&ws->ambient_light_v3, 1000, false, 'x', 0, 0);

                if(rc < 0) {
                    fprintf(stderr, "Ambient Light 3.0 init step 2 failed: %d\n", rc);
                } else {
                    printf("Ambient Light 3.0 initialized\n");
                }
            }
        } else if(device_identifier == HUMIDITY_DEVICE_IDENTIFIER) {
            humidity_create(&ws->humidity, uid, &ws->ipcon);
            humidity_register_callback(&ws->humidity,
                                       HUMIDITY_CALLBACK_HUMIDITY,
                                       (void (*)(void))cb_humidity,
                                       (void *)ws);
            rc = humidity_set_humidity_callback_period(&ws->humidity, 1000);

            if(rc < 0) {
                fprintf(stderr, "Humidity init failed: %d\n", rc);
            } else {
                printf("Humidity initialized\n");
            }
        } else if(device_identifier == HUMIDITY_V2_DEVICE_IDENTIFIER) {
            humidity_v2_create(&ws->humidity_v2, uid, &ws->ipcon);
            humidity_v2_register_callback(&ws->humidity_v2,
                                          HUMIDITY_V2_CALLBACK_HUMIDITY,
                                          (void (*)(void))cb_humidity_v2,
                                          (void *)ws);
            rc = humidity_v2_set_humidity_callback_configuration(&ws->humidity_v2, 1000, true, 'x', 0, 0);

            if(rc < 0) {
                fprintf(stderr, "Humidity 2.0 init failed: %d\n", rc);
            } else {
                printf("Humidity 2.0 initialized\n");
            }
        } else if(device_identifier == BAROMETER_DEVICE_IDENTIFIER) {
            barometer_create(&ws->barometer, uid, &ws->ipcon);
            barometer_register_callback(&ws->barometer,
                                        BAROMETER_CALLBACK_AIR_PRESSURE,
                                        (void (*)(void))cb_air_pressure,
                                        (void *)ws);
            rc = barometer_set_air_pressure_callback_period(&ws->barometer, 1000);

            if(rc < 0) {
                fprintf(stderr, "Barometer init failed: %d\n", rc);
            } else {
                printf("Barometer initialized\n");
            }
        } else if(device_identifier == BAROMETER_V2_DEVICE_IDENTIFIER) {
            barometer_v2_create(&ws->barometer, uid, &ws->ipcon);
            barometer_v2_register_callback(&ws->barometer,
                                           BAROMETER_V2_CALLBACK_AIR_PRESSURE,
                                           (void (*)(void))cb_air_pressure_v2,
                                           (void *)ws);
            rc = barometer_v2_set_air_pressure_callback_configuration(&ws->barometer, 1000, false, 'x', 0, 0);

            if(rc < 0) {
                fprintf(stderr, "Barometer 2.0 init failed: %d\n", rc);
            } else {
                printf("Barometer 2.0 initialized\n");
            }
        }
    }
}

int main() {
    WeatherStation ws;

    ws.lcd_created = false;
    ipcon_create(&ws.ipcon);

    while(true) {
        int rc = ipcon_connect(&ws.ipcon, HOST, PORT);

        if(rc < 0) {
            fprintf(stderr, "Could not connect to brickd: %d\n", rc);

            // TODO: sleep 1s

            continue;
        }

        break;
    }

    ipcon_register_callback(&ws.ipcon,
                            IPCON_CALLBACK_ENUMERATE,
                            (void (*)(void))cb_enumerate,
                            (void *)&ws);

    ipcon_register_callback(&ws.ipcon,
                            IPCON_CALLBACK_CONNECTED,
                            (void (*)(void))cb_connected,
                            (void *)&ws);

    while(true) {
        int rc = ipcon_enumerate(&ws.ipcon);

        if(rc < 0) {
            fprintf(stderr, "Could not enumerate: %d\n", rc);

            // TODO: sleep 1s

            continue;
        }

        break;
    }

    printf("Press key to exit\n");
    getchar();
    ipcon_destroy(&ws.ipcon);

    return 0;
}