Pirrigator, part 5.
Tags: rust, raspberrypi, sideprojects
Published: 2019-05-02 23:39:51

---
title: Pirrigator, part 5.
published: true
description: I actually have some code now!
tags: #rust #raspberrypi #sideprojects
series: Watering tomatoes with a Raspberry Pi
---

![Integration testing my hardware](https://thepracticaldev.s3.amazonaws.com/i/2mdbnxlja9q563f1xbww.jpg)

In my earlier posts I outlined my plan to automate watering the tomatoes in my greenhouse with a Raspberry Pi and Rust. The first few posts mostly outlined the hardware and my procrastination at writing proper Rust code for the first time. But we're now into May and the plants will be going in soon, so I really need this working pretty quickly. The good news is much of the core code concepts are in place.

## Configuration
I stumbled across the [config](https://crates.io/crates/config) crate in my news feed one morning and it was exactly what I need. It deserialises from various sources and formats into a data structure, so I can keep the hardware configuration and detail out of the main code.

```rust
#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct Database {
	pub path: String
}

#[derive(Debug, Deserialize, PartialEq, Eq)]
pub struct Settings {
	pub database: Database,
	pub weather: Option<WeatherSensorSettings>,
	pub adc: Option<ADCSettings>,
	pub moisture: Vec<MoistureSensorSettings>,
	pub buttons: Vec<ButtonSettings>,
	pub valves: Vec<ValveSettings>
}

impl Settings {
	pub fn new() -> Result<Self, ConfigError> {
		let mut s = Config::new();

		s.merge(File::with_name("Settings"))?;
		s.merge(Environment::with_prefix("PIRRIGATOR"))?;

		s.try_into()
	}
}
```

A central setup chunk of code takes the settings read from a file and constructs the various objects I need. So I can do various tests on my development laptop I made all the hardware modules optional so I can configure the system with just a database and web server.

```rust
impl Pirrigator {
	pub fn new(s: settings::Settings) -> Result<Pirrigator, Box<Error>> {
		let (tx, rx) = mpsc::channel();

		let weather = traverse(&s.weather, &|w|
			weather::WeatherSensor::new(&w, tx.clone())
		)?;

		let moisture = traverse(&s.adc, &|adc|
			moisture::MoistureSensor::new(&adc, &s.moisture, tx.clone())
		)?;

		let buttons = button::Buttons::new(&s.buttons, tx.clone())?;
		let valves = valve::Valves::new(&s.valves)?;

		let db = database::Database::new(Path::new(&s.database.path))?;

		let mut controller = Controller {
			database: db.clone(),
			weather,
			moisture,
			buttons,
			valves
		};

		let thread = spawn(move || controller.run(rx));

		return Ok(Pirrigator { 
			thread,
			database: db
		})
	}
```

The `traverse` feels like the wrong way to do things but I'm still learning how the `Option` and `Result` combinators all work. I'll sort it out at some point. It's a standard traverse function like in Haskell:

```rust
fn traverse<T, U, E>(t: &Option<T>, f: &Fn(&T) -> Result<U, E>) -> Result<Option<U>, E> {
	match t {
		None => Ok(None),
		Some(t) => f(t).map(Some)
	}
}
```

## Sending data from sensors

As I mentioned in an earlier post my grand idea was to run each sensor on its own thread and post events to a controller which dispatches these events to the places they need to go. For now, I store sensor data in an sqlite database, and act on the button presses, which are a manual on/off for the water valve for now.

The sensor modules all follow a similar pattern. `new()` spawns a thread which mostly sleeps but wakes up periodically, reads data from the hardware and posts an event to the multi-producer-single-consumer channel.

```rust
impl WeatherSensor {
	pub fn new(settings: &WeatherSensorSettings, channel: Sender<Event>) -> Result<Self, Box<Error>> {
		let device = Bme280Device::new(&settings.device, settings.address)?;
		let period = Duration::from_secs(settings.update);
		let thread = spawn(move || { main(device, channel, period) });
		Ok(WeatherSensor { thread })
	}
}
```

The local `main()` function for a sensor thread is just a polling loop:

```rust
fn main(mut device: Bme280Device, channel: Sender<Event>, period: Duration) {
	loop {
		match device.read() {
			Ok(data) => send_event(data, &channel),
			Err(e) => error(e)
		};
		sleep(period);
	}
}
```

And `send_event()` just translates away from the hardware module's data structure, adds a timestamp, and sends it over the channel.

```rust
fn send_event(data: Bme280Data, channel: &Sender<Event>) {
	let event = WeatherEvent {
		timestamp: SystemTime::now(),
		temperature: data.temperature,
		humidity: data.humidity,
		pressure: data.pressure
	};

	 match channel.send(Event::WeatherEvent(event)) {
		Ok(_) => {},
		Err(e) => error(e)
	};
}
```

## Receiving data from sensors

The controller's `run()` is the main dispatcher loop. Nothing too fancy.
```rust
		loop {
			let event = rx.recv()
				.expect("receive error");

			self.database.store_event(&event)
				.expect("database store error");

			match event {
				event::Event::ButtonEvent(b) => self.button_event(&b),
				_ => {}
			}
		}
```

For the database I chose [rusqlite](https://crates.io/crates/rusqlite). I don't need an ORM or anything - there are no relations in my data yet so I'm just doing SQL by hand. I do want the controller's receiver thread and web server handler threads to be able to access the database however, so there's an [r2d2](https://crates.io/crates/r2d2) connection pool in there. The `Database` abstraction provides methods like

```rust
	fn store_weather(&self, event: &weather::WeatherEvent) -> Result<(), Error> {
		self.conn().execute(
			"INSERT INTO weather (time, temperature, humidity, pressure) VALUES (?1, ?2, ?3, ?4)",
			&[&to_seconds(&event.timestamp) as &ToSql, &event.temperature, &event.humidity, &event.pressure]
		)?;
		Ok(())
	}
```

Dates are always a nightmare so I'm just translating the time to 32-bit Unix seconds. If I'm still using this in 2038 I'll be amazed. I had to cross-compile the sqlite C library for ARMv6 but this turned out to be really simple - just download the autoconf package, configure with `--target=arm-unknown-linux-eabihf`, make, and copy the resulting libraries to the Rust `deps` directory. It looks like Cargo can be configured to do all this, but it's not my main concern right now.

## Web Server
Primarily it's an automation system but I do want to be able to check in, and of course with all that collected data we want graphs! So the final component so far is a web server. I looked at a few Rust web frameworks. [Rocket](https://rocket.rs/) looks great, similar in feel to [Flask](http://flask.pocoo.org/) but I feel the developers have made a gigantic mistake requiring the Rust nightly compiler. Back in the early days of Swift I had a terrible time with code unable to compile across releases. Maybe Rust's change management is better than Apple's was in the Swift 2 to 3 days, but the unstable language base meant an instant rejection for Rocket.

Some of the other web frameworks looked far too removed from anything I've used before. But [Iron](http://ironframework.io/) seemed simple to get started and quite extensible so that's my choice for now. Following [this example](https://github.com/DavidBM/rust-webserver-example-with-iron-diesel-r2d2-serde) I managed to build a middleware to plug my database abstraction into requests:

```rust
struct DbMiddleware {
	database: Database
}

impl typemap::Key for DbMiddleware {
	type Value = Database;
}

impl BeforeMiddleware for DbMiddleware {
	fn before(&self, req: &mut Request) -> IronResult<()> {
		req.extensions.insert::<DbMiddleware>(self.database.clone());
		Ok(())
	}
}

pub trait DbRequestExtension {
	fn get_database(&self) -> Database;
}

impl <'a, 'b>DbRequestExtension for Request<'a, 'b> {
	fn get_database(&self) -> Database {
		let database = self.extensions.get::<DbMiddleware>().unwrap();
		database.clone()
	}
}
```

Thus in any request handler I can do `req.get_database()` and invoke methods on the database abstraction. The middleware also plugs in a [JSON response handler](https://crates.io/crates/iron-json-response) and a [logger](https://crates.io/crates/logger). All useful things that you need in almost any project.

So far the web server has a `/status` route which just reports that the app is running, and a `/weather` route which reports the latest recorded weather conditions.
```rust
fn status(_: &mut Request) -> IronResult<Response> {
	Ok(Response::with((status::Ok, "running")))
}

fn weather(req: &mut Request) -> IronResult<Response> {
	let weather = req.get_database().get_latest_weather();

	let mut response = Response::new();
	match weather {
		Ok(w) => {
			response.set_mut(JsonResponse::json(w)).set_mut(status::Ok);
			Ok(response)
		}
		Err(e) => {
			let err = IronError::new(e, status::NotFound);
			Err(err)
		}
	}
}

pub fn run(database: Database) {
	let mut router = Router::new();
	router.get("/status", status, "status");
	router.get("/weather", weather, "weather");

	Iron::new(middleware::insert(router, database))
		.http("0.0.0.0:5000")
		.unwrap();
}
```

Again I'm sure there are more succinct and idiomatic translations between error types but I'll just have to learn these as I go. I was emphatic in an earlier post that you just have to do what works when learning, and improve it as you go.

After all that, I can GET a JSON blob with the current greenhouse weather conditions! For fun I exposed the port on my router and threw together an Alexa skill to query the weather and read it out. We've had fun asking Alexa what the weather in the greenhouse is like.

## Still to do
Loads has come together in the last week or two, but there's tons to do. There's still no automation of course - I'll need a final thread which runs a state machine and some logic deciding when the plants need water. I haven't really thought much about this yet, but the infrastructure now available allows it to be modular and gives me options.

I'll also need to expand the web server to expose more data suitable for drawing graphs - the last hour, day, week of weather and moisture data for example. Since Rust has a WebAssembly target I'm starting to wonder if I could even build a front-end in Rust?

There are other odds and ends - there's a camera module hanging off the Raspberry Pi, so I want to be able to at least serve up a still image every few hours. Remote override of the water might be an idea; if I'm away from home and everything looks a bit dry because the automation isn't behaving correctly I could at least water the plants manually.

## The experience
The first Rust code I wrote was to read the hardware sensors and collect the data into structures. It felt like C for the most part. Since then I've moved up the stack to accessing a database and serving web content, and Rust no longer feels like C. I've had few ownership issues and have always had a notion of "who is responsible for this object" when programming, so I'm actually really enjoying making this explicit in Rust. No exceptions is great - I think they result in terrible code - but finding the right way to use Rust's tools like `Option` and `Result` together while keeping the code simple and clean is tricky. I feel I'm doing too many `unwrap()`s and not enough passing errors up the call stack. All part of the learning process, which is ultimately what this project is about. If you want to learn a language do something real in it.

The full code is on [github](https://github.com/neilgall/pirrigator/), as ever.