Pirrigator, part 6: In for a penny...
Tags: rust, raspberrypi, sideprojects
Published: 2019-05-12 14:35:15

---
title: Pirrigator, part 6: In for a penny...
published: true
description: Automating my greenhouse irrigation with Rust and Raspberry Pi
tags: #rust #raspberrypi #sideprojects
series: Watering tomatoes with a Raspberry Pi
---

In my [last post](https://dev.to/neilgall/pirrigator-part-5-41an) I outlined how all the data gets gathered, stored and served over a JSON API. I turned to look at making a front end for viewing the data - the idea in my head the whole time was "draw pretty graphs!" - and had to venture into web development, an area which I'm really a novice at. I did do six years of mobile apps professionally, so I'm familiar with some kinds of front-end development, I've written a few toy things with React, I've dabbled with Elm, I can make really simple changes to the Angular + Typescript UI at my present day-job, but other than that I've shied away from web development for years.

The big question therefore was what technology could I build a front-end in. I thought again about my original goal of learning Rust and the well-known fact that Rust compiles to WebAssembly and can run in the browser, but I had no idea if any of this technology was ready for use. I googled "rust web front-end" and came across [this Reddit discussion](https://www.reddit.com/r/rust/comments/a7h53y/considering_rust_for_frontend/) on the subject. That in turn linked to [Markus Kohlhase](https://github.com/flosse)'s excellent [Rust web framework comparison](https://github.com/flosse/rust-web-framework-comparison#frontend-frameworks-wasm) document.

Unsurprisingly there's a lot of activity in this area. Knowing I wanted to draw graphs I looked through the front-end frameworks for one with an SVG example, and the only one was [Draco](https://github.com/utkarshkukreti/draco). I spent an evening going through the examples and got as far as serving the SVG clock example from the Iron webserver.

I made a possibly interesting choice here not to add a static file mount to the webserver, but to build the binary blobs of the three files I needed right into the server binary. I have two reasons for this. First, deployment is simple as there's always a single file to copy to the device. And second, while I'm not sharing the address widely as it's running on a really low-end device with a poor wifi connection 20 metres from my house, the port is open to the internet via my router. Limiting the data the server can access to only what Rust code can generate seems a good way to maximise the benefits of Rust's safety and security claims. A handy crate for this was [lazy-static-include](https://lib.rs/crates/lazy-static-include) which builds binary blobs into release builds but loads them from the source files at runtime in debug builds. I do build the app on my development laptop with hardware access disabled so I can test out the database and server side of things, so this gives me the best of both worlds.

With the success of serving the SVG clock from the Iron webserver, I set about replacing this UI with my own, and quickly ran into trouble. On loading the WASM blob the web console showed a link error for some `__wbg_error` handler. I googled this, read more of the [wasm-bindgen guide](https://rustwasm.github.io/docs/wasm-bindgen/) and even browsed the source but came up short. It seems Draco just hasn't reached a maturity level to address things like easy debugging yet.

Back at Markus's comparison document I looked at the _popularity_ of the individual web frameworks, realising it was a bit of schoolboy error to ignore this in the first place. The most starred framework is [Dodrio](https://github.com/fitzgen/dodrio) but the example code looks too low-level for my liking. I definitely want something that gives the kind of abstraction that Elm and React offer.

The next most popular framework is [Seed](https://seed-rs.org/) and I have to say I'm really impressed. The documentation is great, introducing concepts in the right order (although not all of it is completely up to date), and the API is pretty nice to use. It was a really straightforward translation from the small amount of Draco-based code I'd written to use Seed instead. Quickly I had a simple UI to query blocks of historical sensor data from the server and show it in tables. Then I set about writing an SVG chart component to draw 2D line graphs. A few hours later I had rudimentary charts!

![example chart showing weather history](https://thepracticaldev.s3.amazonaws.com/i/o6y9mctne8d45bi322bd.png)

I really like the way the UI can be decomposed with Seed. Roughly following the Elm architecture, at the root a UI is a model, a view function which turns a model into a virtual DOM tree, and an update function which updates the model from a message. To decompose into smaller units I really want those units to work in terms of their own model and messages, and Seed provides the tools to make this easy.

I separated the main UI into Weather and Sensors sub-modules. The main model becomes
```rust
struct Model {
    weather: weather::Model,
    sensors: sensors::Model
}
```

The top-level event type just wraps the lower layer events that are possible:
```rust
enum Message {
    Weather(weather::Message),
    Sensors(sensors::Message),
}
```

The view for this just renders the sub-modules views, and crucially, maps the sub-module's event types to the higher layer's using the Message instance constructors.
```rust
fn view(model: &Model) -> El<Message> {
    div![
        h1!["Pirrigator"],
        model.weather.render().map_message(Message::Weather),
        model.sensors.render().map_message(Message::Sensors)
    ]
}
```

The update works in reverse, unwrapping the top-layer messages, passing the underlying message through the sub-modules, then wrapping the result back up in the top layer structure. Fundamentally it's an `fmap`:
```rust
fn update(msg: Message, model: &mut Model) -> Update<Message> {
    match msg {
        Message::Weather(msg) => model.weather.update(msg).map(Message::Weather),
        Message::Sensors(msg) => model.sensors.update(msg).map(Message::Sensors)
    }
}
```

This is great, as it lets you decompose into self-contained units, and can even instantiate multiple instances of these units into the larger UI. Perfect for tables, grids and the like.

Because the data comes from the server, the weather model is itself an enum representing the states this data can be in.
```rust
pub enum Model {
    NotLoaded,
    Loading,
    Loaded(Vec<WeatherRow>),
    Failed(String)
}
```

The render function shows a heading, some buttons to select the time range of data to load, and the rest depends on the data state: 
```rust
pub fn render(&self) -> El<Message> {
    div![
        h2!["Weather"],
        button![simple_ev(Ev::Click, Message::Fetch(HOUR)), "Last Hour"],
        button![simple_ev(Ev::Click, Message::Fetch(DAY)), "Last Day"],
        button![simple_ev(Ev::Click, Message::Fetch(WEEK)), "Last Week"],
        button![simple_ev(Ev::Click, Message::Fetch(MONTH)), "Last Month"],
        match self {
            Model::NotLoaded =>
                p!["Select a time range"],
            Model::Loading =>
                p!["Loading..."],
            Model::Failed(e) =>
                p![e],
            Model::Loaded(data) =>
                div![
                    h3!["Temperature"],
                    chart(data, true, &|r| r.temperature).render().map_message(|_| Message::Fetch(HOUR)),
                    h3!["Humidity"],
                    chart(data, true, &|r| r.humidity).render().map_message(|_| Message::Fetch(HOUR)),
                    h3!["Barometric Pressure"],
                    chart(data, false, &|r| r.pressure).render().map_message(|_| Message::Fetch(HOUR))
                ]
        }
    ]
}
```
The best bit about this code is it's hard to see where you could write bugs. You can only render data when in the state where there is data to render. You can only show an error in the error state. How many UIs get this wrong by failing to [make impossible states unrepresentable](https://www.youtube.com/watch?v=IcgmSRJHu_8)?.

The chart function uses the provided lambda to pull one axis out of the weather data and build a `Chart` sub-model, which is then immediately rendered. As with the top-layer the Chart module's message is then mapped into a Weather module message. Right now my chart module doesn't actually produce any messages but when it does it's already plumbed all the way up to the root.
```rust
fn chart(data: &Vec<WeatherRow>, y_origin_zero: bool, f: &Fn(&WeatherRow) -> f64) -> chart::Chart {
    chart::Chart {
        width: 600,
        height: 200,
        y_origin_zero,
        data: data.iter().map(|r| chart::DataPoint { time: r.timestamp, value: f(r) }).collect()
    }
}
```

I'm not going to go through the chart code. It was fun to write but is fairly self-explanatory as it just builds a bunch of SVG elements. There was a little arithmetic involved to scale the data to SVG coordinates (and also to deal with the fact that my X axis consists of `SystemTime` objects, but the [chrono](https://github.com/chronotope/chrono) crate came in very useful here).

The weather sub-module's update function is interesting as it updates the current state. To do this it's necessary to dereference the `&mut self` reference:
```rust
pub fn update(&mut self, msg: Message) -> Update<Message> {
    match msg {
        Message::Fetch(t) => {
            *self = Model::Loading;
            Update::with_future_msg(self.fetch(t)).skip()
        }
        Message::Fetched(rows) => {
            *self = if rows.is_empty() { Model::NotLoaded } else { Model::Loaded(rows) };
            Render.into()
        }
        Message::Failed(e) => {
            *self = Model::Failed(e.as_string().unwrap_or("Unknown error".to_string()));
            Render.into()
        }
    }
}
```

The sensors module works very similarly but has the added twist that it needs to fetch the list of sensor names with one API call, and then the data for each one separately. Following the same pattern I encoded all these states into the model.

After my false start with Draco this has been a joy to work on. UIs are inherently stateful and being able to think about and explicitly encode the complete set of states your code can be in is such a massive improvement from where we came from. Apart from a couple of bugs with scaling the data to the graph, on the whole this has been a "if it compiles it works" scenario.

There are only two major components left before this is up and running. I have three sensors but only two water valves as one of the three I bought doesn't work. So I'll need some additional configuration to define irrigation zones, how they map to sensors and valves, and some parameters for how the water should be added (e.g. after 6pm every day until the moisture reads a certain percentage). Then a final back-end module to execute this logic and store irrigation events in the database, and a UI addition to show when water has been added in each zone.

The final component to install will be the tomato plants. It's mid-May, I better get a crack on!

[GitHub](https://github.com/neilgall/pirrigator/)