| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283 |
- use std::convert::TryInto;
- use std::sync::mpsc;
- use std::thread;
- use std::thread::sleep;
- use std::time::{Duration, Instant};
-
- use embedded_nrf24l01::{Configuration, CrcMode, DataRate, RxMode, NRF24L01};
- use linux_embedded_hal::spidev::{SpiModeFlags, Spidev, SpidevOptions};
- use linux_embedded_hal::sysfs_gpio::Direction;
- use linux_embedded_hal::sysfs_gpio::Error as GpioError;
- use linux_embedded_hal::Pin;
- use log::{error, info};
- use protocol::{Location, Packet, Value};
- use rand::rngs::ThreadRng;
- use rand::Rng;
-
- use crate::spi::EmbeddedHalSpidev;
- use crate::Error;
- use crate::{SensorData, SensorUpdate};
-
- const DISPLAY_ID: u8 = 0x20;
- const DISPLAY_KEY: [u8; 16] = include!("../../../common/display_key.txt");
- const WEATHER_STATION_0_ID: u8 = 0x30;
- const WEATHER_STATION_0_KEY: [u8; 16] = include!("../../../common/weather_station_0_key.txt");
-
- pub fn start(updates: mpsc::Sender<SensorUpdate>) {
- // TODO: Channels for sensor readings.
- thread::spawn(move || {
- // Whenever a error occurs, reinitialize the radio module shortly later.
- loop {
- match radio_thread(&updates) {
- Ok(()) => {}
- Err(e) => {
- error!("Radio error: {:?}", e);
- }
- }
- thread::sleep(Duration::from_secs(3));
- }
- });
- }
-
- fn radio_thread(updates: &mpsc::Sender<SensorUpdate>) -> Result<(), Error> {
- let mut radio = Radio::init(updates)?;
- radio.run_loop()?;
- Ok(())
- }
-
- struct Radio<'a> {
- rx: Option<RxMode<NRF24L01<GpioError, Pin, Pin, EmbeddedHalSpidev>>>,
- updates: &'a mpsc::Sender<SensorUpdate>,
- rng: ThreadRng,
- }
-
- impl<'a> Radio<'a> {
- fn init(updates: &'a mpsc::Sender<SensorUpdate>) -> Result<Radio<'a>, Error> {
- info!("Initializing radio...");
-
- // The NRF module is connected as follows:
- // - CE: PA1
- // - CS: PG8
- // - IRQ: PG9
- // - MOSI: PC0
- // - MISO: PC1
- // - SCK: PC2
-
- // Configure SPI.
- let mut spi = Spidev::open("/dev/spidev0.0").unwrap();
- let options = SpidevOptions::new()
- .bits_per_word(8)
- .max_speed_hz(500_000)
- .mode(SpiModeFlags::SPI_MODE_0)
- .build();
- spi.configure(&options).unwrap();
- let spi = EmbeddedHalSpidev::from(spi);
-
- // Configure the GPIOs.
- let ce_nr = get_pin_number('A', 1);
- let ce = Pin::new(ce_nr);
- ce.export().unwrap();
- ce.set_direction(Direction::Out).unwrap();
- let cs_nr = get_pin_number('G', 8);
- let cs = Pin::new(cs_nr);
- cs.export().unwrap();
- cs.set_direction(Direction::Out).unwrap();
- let irq_nr = get_pin_number('G', 9);
- let irq = Pin::new(irq_nr);
- irq.export().unwrap();
- irq.set_direction(Direction::In).unwrap();
-
- // Initialize the radio module.
- let mut nrf24 = NRF24L01::new(ce, cs, spi)?;
- nrf24.set_frequency(0x32)?;
- nrf24.set_rf(DataRate::R2Mbps, 3)?;
- nrf24.set_crc(Some(CrcMode::OneByte))?;
- nrf24.set_auto_retransmit(250, 3)?;
- nrf24.set_pipes_rx_enable(&[true, false, false, false, false, false])?; // TODO enable pipe 0 once the base station receives messages
- nrf24
- .set_rx_addr(0, &[0xB3, 0xB3, 0xB3, 0xB3, 0x00])
- .unwrap();
- nrf24.flush_rx().unwrap();
- nrf24.flush_tx().unwrap();
- nrf24.set_auto_ack(&[true; 6]).unwrap();
- info!("auto ack: {:?}", nrf24.get_auto_ack().unwrap());
- nrf24.set_pipes_rx_lengths(&[Some(32); 6]).unwrap();
-
- info!("width: {}", nrf24.get_address_width().unwrap());
-
- let rx = nrf24.rx().unwrap();
-
- Ok(Radio {
- rx: Some(rx),
- updates,
- rng: rand::thread_rng(),
- })
- }
-
- fn run_loop(&mut self) -> Result<(), Error> {
- let mut start = Instant::now();
-
- // Receive data.
- info!("Starting to receive:");
- loop {
- sleep(Duration::from_millis(1));
- let rx = self.rx.as_mut().unwrap();
- if let Some(pipe) = rx.can_read().unwrap() {
- let payload = rx.read().unwrap();
- info!(
- "packet received on pipe {}: {:x?}, {}",
- pipe,
- payload.as_ref(),
- payload.len()
- );
- if payload.len() != 32 {
- continue;
- }
-
- let mut payload: [u8; 32] = payload.as_ref().try_into().unwrap();
- self.handle_packet(&mut payload)?;
-
- let end = Instant::now();
- let elapsed = end.duration_since(start);
- info!("Debug: {:?}", elapsed);
- start = end;
- }
- }
- }
-
- fn handle_packet(&mut self, payload: &mut [u8]) -> Result<(), Error> {
- // Get the key of the device.
- let device_id = payload[0];
- let key = match get_key(device_id) {
- Some(k) => k,
- None => {
- info!("packet from unknown device {:02x}", device_id);
- return Ok(());
- }
- };
-
- // Decode the packet.
- let packet = match Packet::decrypt_and_decode(key, payload) {
- None => {
- info!("invalid packet from device {:02x}", device_id);
- return Ok(());
- }
- Some(p) => p,
- };
- // TODO: For all packets except for salt requests, check whether the
- // salt was incremented to prevent replay attacks.
- // TODO: Also check whether the device bit in the salt is 0.
-
- info!("packet from {}: {:?}", device_id, packet);
-
- match packet {
- Packet::GetSalt => {
- self.send_salt(device_id)?;
- }
- Packet::Salt(_) => {
- error!("received Salt packet from device.");
- }
- Packet::Report(payload) => {
- let location = get_location(device_id);
- let count = payload.count;
- for i in 0..count {
- match payload.values[i as usize] {
- Value::Invalid => {}
- Value::Time(_) => {
- error!("device tried to report a time");
- }
- Value::Temperature(temperature) => {
- let temperature = temperature as f32 / 10.0;
- info!("{:?} temperature: {} °C", location, temperature);
- }
- Value::Pressure(pressure) => {
- let pressure = pressure as f32 / 100.0;
- info!("{:?} pressure: {} HPa", location, pressure);
- }
- Value::Humidity(humidity) => {
- let humidity = humidity as f32 / 100.0;
- info!("{:?} humidity: {}%", location, humidity);
- }
- }
- }
- // TODO: Send values via MQTT
- /*
- updates
- .send(SensorUpdate {
- location: 0,
- data: vec![
- SensorData::Temperature(temperature),
- SensorData::Pressure(pressure),
- SensorData::Humidity(humidity),
- ],
- })
- .unwrap();
- */
- }
- Packet::GetValues(_payload) => {
- error!("GetValues not yet implemented.");
- // TODO
- }
- Packet::Values(_payload) => {
- error!("received Values packet from device.");
- }
- }
- Ok(())
- }
-
- fn send_salt(&mut self, device_id: u8) -> Result<(), Error> {
- let salt = (self.rng.gen::<u64>() & !0xff & !(1 << 63)) | device_id as u64;
- let packet = Packet::Salt(salt);
- self.send_packet(device_id, packet)
- }
-
- fn send_packet(&mut self, device_id: u8, packet: Packet) -> Result<(), Error> {
- /*let salt = self.rng.gen::<u64>();
- let mut tx = self
- .rx
- .take()
- .unwrap()
- .standby()
- .tx()
- .map_err(|(_, e)| Error::Radio(e))?;
- let mut encoded = [0u8; 32];
- let key = get_key(device_id);
- if packet.encode_and_encrypt(key.unwrap(), salt, &mut encoded) {
- tx.set_tx_addr(&[0xB3, 0xB3, 0xB3, 0xB3, device_id])
- .unwrap();
- tx.set_rx_addr(0, &[0xB3, 0xB3, 0xB3, 0xB3, device_id])
- .unwrap();
- tx.send(&encoded).unwrap();
- // TODO: Check whether the packet arrived.
- } else {
- info!("could not encode packet {:?}", packet);
- }
- self.rx = Some(
- tx.standby()
- .map_err(Error::Radio)?
- .rx()
- .map_err(|(_, e)| Error::Radio(e))?,
- );*/
- Ok(())
- }
- }
-
- fn get_location(device_id: u8) -> Location {
- match device_id {
- DISPLAY_ID => Location::Bedroom,
- WEATHER_STATION_0_ID => Location::Livingroom,
- _ => Location::Livingroom,
- }
- }
-
- fn get_key(device_id: u8) -> Option<&'static [u8]> {
- match device_id {
- DISPLAY_ID => Some(&DISPLAY_KEY),
- WEATHER_STATION_0_ID => Some(&WEATHER_STATION_0_KEY),
- _ => None,
- }
- }
-
- fn get_pin_number(c: char, n: u64) -> u64 {
- (c as u64 - 'A' as u64) * 32 + n
- }
|