Rust Thermometer – Part 2

Thermometer adventure continuous with some progress over the last weekend. Little bit of refactoring, rewiring, X.org, matchbox, Piston, … and some ugly (but working!) UI 🙂

Rewiring

My inside DS18B20 thermometers have three wires. But I managed to buy the outside one with two wires only. Had to rewire everything to parasite power mode …

… and add pullup=1 to RESIN_HOST_CONFIG_dtoverlay (w1-gpio,pullup=1). As a bonus, it’s much simpler and nicer.

BTW I’m not very good at these things, maybe, it can be wired without parasite power mode somehow, don’t know.

Refactoring

Here’s the short list of important changes I made. Not all of them are covered.

Configuration

Hard coded thermometer device identifiers removed and config module introduced. Also decided to use two thermometers instead of three – one for inside temperature and second one for outside temperature. Thermometer can be configured via environment variables or command line arguments. Issue following command to see the complete list of them:

cargo run -- --help

Version 0.0.2 gives you following output:

thermometer 0.0.2
Raspberry Pi Thermometer

USAGE:
    thermometer --inside-thermometer <INSIDE_THERMOMETER>
        --max-fps <MAX_FPS> 
        --outside-thermometer <OUTSIDE_THERMOMETER>
        --temperature-interval <TEMPERATURE_INTERVAL>
        --temperature-units <TEMPERATURE_UNITS>

FLAGS:
    -h, --help       Prints help information
    -V, --version    Prints version information

OPTIONS:
        --inside-thermometer <INSIDE_THERMOMETER>
            Inside W1 thermometer device ID [env: INSIDE_THERMOMETER=]
            [default: 28-000009e8f6e7]

        --max-fps <MAX_FPS>
            Max frames per second [env: MAX_FPS=]
            [default: 2]

        --outside-thermometer <OUTSIDE_THERMOMETER>
            Outside W1 thermometer device ID [env: OUTSIDE_THERMOMETER=]
            [default: 28-000009d4dffc]

        --temperature-interval <TEMPERATURE_INTERVAL>
            Interval in which temperatures are read from sensors (ms)
            [env: TEMPERATURE_INTERVAL=] [default: 500]            

        --temperature-units <TEMPERATURE_UNITS>
            Temperature units [env: TEMPERATURE_UNITS=]
            [default: celsius]  [possible values: celsius, fahrenheit]

Each device in the fleet can have different configuration now:

TIMEZONE environment variable is not related to the Rust thermometer application. But it’s important and will be explained later in this post.

Local push

local-push.env file introduced. It contains configuration options for the local-push.sh script. GitHub repository contains values for the Raspberry Pi 3 B+:

export RESIN_MACHINE_NAME=raspberrypi3
export RESIN_MACHINE_TARGET=armv7-unknown-linux-gnueabihf

Do you have different device? Just modify these values and local-push.sh should work for you. Other changes:

  • removed resin-sync.yml and moved the before part to the local-push.sh (-b argument) script,
  • removed hard coded DEVICE,
  • run local-push.sh without arguments to discover devices to deploy to,
  • run local-push.sh with device name (first argument) to deploy to specific device.

Everything should be covered in the documentation.

Module w1

Temperature parsing was moved to the ds18b20.rs module. It’s DS18B20 specific. Couple of other changes like temperature units added, from / to string, etc.

Documentation

Exhaustive documentation is in the code itself. Frankly, it’s over documented, but should help people new to the Rust language and Piston.

User Interface

Kiosk mode and launch scripts

Decided to go with X.org kiosk mode and took inspiration from the electron-rpi-quick-start repository. It means couple of new packages in the Dockerfile and new launch script. Launch script configures time zone, disables DPMS, screen blanking, launches matchbox window manager and our thermometer.

Remember TIMEZONE from the device configuration section? We want our application to display correct date & time (local one, not the UTC).

My Raspberry Pi 3 B+ is standing on the desk, case is upside down and the only thing I had to do was to set RESIN_HOST_CONFIG_lcd_rotate variable to 2 (to rotate it upside down).

Piston window

Couple of graphics libraries do exist for the Rust language. I prefer piston_window for simple applications. If you’re new to the Piston, don’t look to other crates, just stick with the piston_window. It’s more than enough and you’ll not be puzzled. Trust me 🙂

If you’ve never heard about Piston before, check the Getting Started guide or Games Made With Piston page.

Code organization

As I already said, code is extensively documented, so, I’m not going to cover it in detail. Just some organization notes.

  • src/w1 – W1 related stuff (devices – thermometers)
  • src/app – UI application based on the piston_window crate
  • src/state.rs – global application state (just temperatures for now)
  • src/config.rs – application configuration
  • src/temperature.rs – temperature readers threads
  • src/main.rs – entry point which spawns temperature readers threads and launches thermometer UI

Temperature simulation

You can try to run the thermometer application on your computer. It works, but you’ll see N/A instead of temperature values. Sensors are connected to the Raspberry Pi, right? Rust thermometer contains feature simulate-temperature, which can be enabled in this way:

cargo run --features simulate-temperature

It simulates temperatures on local machine, so, we can play with the thermometer on computer as well. Here’re the differences:

Read The Manifest Format to learn more about features.

Conclusion

Honestly, I thought it will be much harder task to make it working on Raspberry Pi & resinOS. But it was piece of cake. Little bit of Googling, checking sample projects and connecting dots together.

Thermometer on Raspberry Pi. Yeah, it’s a mug with coffee. Was testing if the sensor works 🙂 Don’t worry, it’s the outside sensor = waterproof.

And thermometer running on macOS with simulate-temperature feature enabled.

Pretty ugly UI, but good enough for now. We’ve just tested that it works and we can work on some nice UI later.

What’s next? Open / closed terrace door check and I’ll probably replace synchronous temperature readers with some asynchronous ones. We don’t need separate thread per sensor.

Previous parts