diff --git a/hosts/dacbert/default.nix b/hosts/dacbert/default.nix index 0ec31498..eb4489d0 100644 --- a/hosts/dacbert/default.nix +++ b/hosts/dacbert/default.nix @@ -56,6 +56,15 @@ mapHqHosts = true; hq.interface = "eth0"; hq.statistics.enable = true; + pi-sensors = [ { + type = "dht22"; + pin = 17; + location = "Tisch"; + } { + type = "dht22"; + pin = 23; + location = "Tisch2"; + } ]; }; nix = { diff --git a/lib/default.nix b/lib/default.nix index 9ad321b3..b5d68582 100644 --- a/lib/default.nix +++ b/lib/default.nix @@ -32,6 +32,7 @@ in { ./stats.nix ./openwebrx.nix ./audio-server + ./pi-sensors.nix ]; options.c3d2 = with lib; diff --git a/lib/pi-sensors.nix b/lib/pi-sensors.nix new file mode 100644 index 00000000..5c3baa3c --- /dev/null +++ b/lib/pi-sensors.nix @@ -0,0 +1,40 @@ +{ pkgs, config, lib, ... }: +{ + options.c3d2.pi-sensors = lib.mkOption { + default = []; + type = with lib.types; listOf (submodule ({ ... }: { + options = { + type = lib.mkOption { + description = "Sensor type"; + type = enum ["dht22"]; + }; + pin = lib.mkOption { + description = "GPIO pin"; + type = int; + }; + location = lib.mkOption { + description = "Sensor location"; + type = str; + }; + }; + })); + }; + + config = lib.mkIf (config.c3d2.pi-sensors != []) { + # GPIO requires access to /dev/mem + security.wrappers.pi-sensors = { + setuid = true; + owner = "root"; + group = "root"; + source = "${pkgs.pi-sensors}/bin/pi-sensors"; + }; + + services.collectd.plugins.exec = '' + Exec "${config.services.collectd.user}" "/run/wrappers/bin/pi-sensors" "10"${lib.concatMapStrings (s: " \"${s}\"") ( + lib.concatMap ({ type, pin, location }: + [ type (toString pin) location ] + ) config.c3d2.pi-sensors + )} + ''; + }; +} diff --git a/overlay/default.nix b/overlay/default.nix index 171b710f..8eeeff24 100644 --- a/overlay/default.nix +++ b/overlay/default.nix @@ -8,4 +8,6 @@ final: prev: openwebrx = prev.python3Packages.callPackage ./openwebrx.nix { }; dump1090_sdrplus = prev.callPackage ./dump1090.nix { }; + + pi-sensors = prev.callPackage ./pi-sensors { }; } diff --git a/overlay/pi-sensors/Cargo.lock b/overlay/pi-sensors/Cargo.lock new file mode 100644 index 00000000..dae458be --- /dev/null +++ b/overlay/pi-sensors/Cargo.lock @@ -0,0 +1,113 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "dht-hal-drv" +version = "0.2.1" +source = "git+https://github.com/voteblake/dht-hal-drv.git#ed6ea0177573b977545016036fa10ff3b52fd16f" +dependencies = [ + "embedded-hal", +] + +[[package]] +name = "embedded-hal" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e36cfb62ff156596c892272f3015ef952fe1525e85261fa3a7f327bd6b384ab9" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8f7255a17a627354f321ef0055d63b898c6fb27eff628af4d1b66b7331edf6" + +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.0.0", +] + +[[package]] +name = "nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "546c37ac5d9e56f55e73b677106873d9d9f5190605e41a856503623648488cae" + +[[package]] +name = "once_cell" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "692fcb63b64b1758029e0a96ee63e049ce8c5948587f2f7208df04625e5f6b56" + +[[package]] +name = "pi-sensors" +version = "0.0.0" +dependencies = [ + "dht-hal-drv", + "embedded-hal", + "rppal", + "spin_sleep", + "void", +] + +[[package]] +name = "rppal" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3225d01a8b7ae304645b4aa38f30061ee08539a1fc6458c68e2b55c803323679" +dependencies = [ + "lazy_static", + "libc", +] + +[[package]] +name = "spin_sleep" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a98101bdc3833e192713c2af0b0dd2614f50d1cf1f7a97c5221b7aac052acc7" +dependencies = [ + "once_cell", + "winapi", +] + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/overlay/pi-sensors/Cargo.toml b/overlay/pi-sensors/Cargo.toml new file mode 100644 index 00000000..cddd8726 --- /dev/null +++ b/overlay/pi-sensors/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "pi-sensors" +version = "0.0.0" +edition = "2018" + +[dependencies] +rppal = "0.13" +embedded-hal = "0.2" +void = "1" +dht-hal-drv = "0.2" +spin_sleep = "1" + +[patch.crates-io] +dht-hal-drv = { git = "https://github.com/voteblake/dht-hal-drv.git" } diff --git a/overlay/pi-sensors/default.nix b/overlay/pi-sensors/default.nix new file mode 100644 index 00000000..0987e8be --- /dev/null +++ b/overlay/pi-sensors/default.nix @@ -0,0 +1,8 @@ +{ rustPlatform }: + +rustPlatform.buildRustPackage { + name = "pi-sensors"; + version = "0.0.0"; + src = ./.; + cargoSha256 = "0pihg88jx61a3bxm6n6h0fila34xgfnpgqrsdk4bw165bwmp5laq"; +} diff --git a/overlay/pi-sensors/src/dht.rs b/overlay/pi-sensors/src/dht.rs new file mode 100644 index 00000000..1be20cf9 --- /dev/null +++ b/overlay/pi-sensors/src/dht.rs @@ -0,0 +1,47 @@ +use std::rc::Rc; +use std::time::Duration; +use dht_hal_drv::{dht_split_init, dht_split_read, DhtType}; +use spin_sleep; +use rppal::gpio::IoPin; + +use crate::open_pin::OpenPin; +use crate::{Measurement, Sensor}; + +pub struct DHT22 { + location: Rc, + pin: OpenPin, +} + +impl DHT22 { + pub fn new(pin: IoPin, location: String) -> Self { + DHT22 { + location: Rc::new(location), + pin: OpenPin::new(pin), + } + } +} + +impl Sensor for DHT22 { + fn read(&mut self) -> Vec { + let mut delay_us = |d: u16| { + // We are using here more accurate delays than in std library + spin_sleep::sleep(Duration::from_micros(d as u64)); + }; + + self.pin.switch_output(); + if let Err(_) = dht_split_init(&mut self.pin, &mut delay_us) { + return vec![]; + } + + self.pin.switch_input(); + // match dht_read(DhtType::DHT22, &mut opin, &mut delay_us) { + match dht_split_read(DhtType::DHT22, &mut self.pin, &mut delay_us) { + Ok(readings) => vec![ + Measurement::new(self.location.clone(), "temperature", readings.temperature()), + Measurement::new(self.location.clone(), "humidity", readings.humidity()), + ], + Err(_) => + vec![] + } + } +} diff --git a/overlay/pi-sensors/src/main.rs b/overlay/pi-sensors/src/main.rs new file mode 100644 index 00000000..3a63a65c --- /dev/null +++ b/overlay/pi-sensors/src/main.rs @@ -0,0 +1,82 @@ +use std::rc::Rc; +use std::time::Duration; +use rppal::gpio::{Gpio, Mode}; + +mod open_pin; +mod dht; + +#[derive(Debug, Clone)] +pub struct Measurement { + pub location: Rc, + pub type_: &'static str, + pub value: f64, +} + +impl Measurement { + pub fn new>( + location: Rc, + type_: &'static str, + value: V, + ) -> Self { + Measurement { + location, type_, + value: value.into(), + } + } +} + +pub trait Sensor { + fn read(&mut self) -> Vec; +} + +fn main() { + let hostname = std::fs::read("/proc/sys/kernel/hostname") + .expect("hostname"); + let hostname = String::from_utf8_lossy(&hostname); + let hostname = hostname.trim(); + let gpio = Gpio::new().expect("Can not init Gpio structure"); + + let mut args = std::env::args().skip(1); + let interval: u64 = args.next() + .expect("interval argument") + .parse() + .expect("interval integer"); + let interval = Duration::from_secs(interval); + + let mut sensors: Vec> = vec![]; + while let Some(kind) = args.next() { + match &kind[..] { + "dht22" => { + let pin: u8 = args.next() + .expect("pin argument") + .parse() + .expect("pin integer"); + let location = args.next() + .expect("location"); + let iopin = gpio + .get(pin) + .expect("Was not able to get Pin") + .into_io(Mode::Input); + let dht = dht::DHT22::new(iopin, location); + sensors.push(Box::new(dht)); + } + _ => panic!("Unknown sensor kind: {}", kind), + } + } + + loop { + for sensor in &mut sensors { + for m in sensor.read() { + println!( + "PUTVAL {}/{}-{}/{}-{} interval={} N:{}", + hostname, + "sensors", "sensors-pi", + m.type_, m.location, + interval.as_secs(), + m.value + ); + } + } + std::thread::sleep(interval); + } +} diff --git a/overlay/pi-sensors/src/open_pin.rs b/overlay/pi-sensors/src/open_pin.rs new file mode 100644 index 00000000..50f33113 --- /dev/null +++ b/overlay/pi-sensors/src/open_pin.rs @@ -0,0 +1,66 @@ +use embedded_hal::digital::v2::{InputPin, OutputPin}; +use rppal::gpio::{IoPin, Mode}; +use void; + +/** + * Raspberry pi does not have open drain pins so we have to emulate it. + */ +pub struct OpenPin { + iopin: IoPin, + mode: Mode, +} + +impl OpenPin { + pub fn new(mut pin: IoPin) -> OpenPin { + pin.set_mode(Mode::Input); + OpenPin { + iopin: pin, + mode: Mode::Input, + } + } + + pub fn switch_input(&mut self) { + if self.mode != Mode::Input { + self.mode = Mode::Input; + self.iopin.set_mode(Mode::Input); + } + } + + pub fn switch_output(&mut self) { + if self.mode != Mode::Output { + self.mode = Mode::Output; + self.iopin.set_mode(Mode::Output); + } + } +} + +// Current rppal implementation does not support embedded_hal::gpio::v2 pins API. +impl InputPin for OpenPin { + type Error = void::Void; + + fn is_high(&self) -> Result { + Ok(self.iopin.is_high()) + } + + /// Is the input pin low? + fn is_low(&self) -> Result { + Ok(self.iopin.is_low()) + } +} + +// Current rppal implementation does not support embedded_hal::gpio::v2 pins API. +impl OutputPin for OpenPin { + type Error = void::Void; + + fn set_low(&mut self) -> Result<(), Self::Error> { + self.switch_output(); + self.iopin.set_low(); + Ok(()) + } + + fn set_high(&mut self) -> Result<(), Self::Error> { + self.iopin.set_high(); + self.switch_input(); + Ok(()) + } +}