diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ae822d --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +/target +**/*.rs.bk +.idea \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock new file mode 100644 index 0000000..26bb8dd --- /dev/null +++ b/Cargo.lock @@ -0,0 +1,48 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "cc2500" +version = "0.1.0" +dependencies = [ + "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)", + "rppal 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cfg-if" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "lazy_static" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "libc" +version = "0.2.62" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "log" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rppal" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[metadata] +"checksum cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)" = "b486ce3ccf7ffd79fdeb678eac06a9e6c09fc88d33836340becb8fffe87c5e33" +"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14" +"checksum libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)" = "34fcd2c08d2f832f376f4173a231990fa5aef4e99fb569867318a227ef4c06ba" +"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" +"checksum rppal 0.11.3 (registry+https://github.com/rust-lang/crates.io-index)" = "137dbba1fb867daa27cda4c3cd6a11bca5bb5a1551f034cf9319b994846ddbe1" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..e4a1ff7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,35 @@ +[package] +name = "cc2500" +version = "0.1.0" +authors = ["tschl"] +edition = "2018" +target = "armv7-unknown-linux-gnueabihf" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rppal = "0.11.3" +log = "0.4.8" + +[target.armv7-unknown-linux-gnueabihf] +linker = "arm-linux-gnueabihf-gcc-8" +ar = "arm-linux-gnueabihf-ar" + +[tasks.compile] +command = "cargo" +args = ["build", "--target", "armv7-unknown-linux-gnueabihf"] + +[tasks.upload] +command = "scp" +args = ["./target/armv7-unknown-linux-gnueabihf/debug/cc2500", "pi@xxx.xxx.xxx.xxx:"] + +[tasks.gdb] +command = "ssh" +args = ["pi@xxx.xxx.xxx.xxx", "gdbserver 0.0.0.0:2345 ./cc2500"] + +[tasks.debug-raspberry] +dependencies = [ + "compile", + "upload", +# "gdb" +] \ No newline at end of file diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..4cd4bdf --- /dev/null +++ b/readme.md @@ -0,0 +1,66 @@ +# Ansluta Rust controller + +This project is a rust application compiled for an raspberry pi to control IKEA lamps by faking an Ansluta remote. +Thanks to [NDBCK](https://github.com/NDBCK/Ansluta-Remote-Controller) for his great research and example code in C++. +In this project I used the first time ever rust language so please don't judge about code style :D. + + +### Requirements + +* [Raspberry PI 3 (€33,99 at Amazon)](https://amzn.to/2oiGepb) +* [CC2500 2.4 GHz chip (€3 at Amazon)](https://amzn.to/2mbaRwk) +* [Breadboard & Jumper cables (€8.99 at Amazon)](https://amzn.to/2mRyfiG) +* [Rust](https://www.rust-lang.org/tools/install) +* [Cargo make](https://github.com/sagiegurari/cargo-make#installation) +* [Cargo watch](https://github.com/passcod/cargo-watch#install) +* Linux Subsystem if you are on windows +* GCC for Raspberry ARM processor + +### Setup + +**1)** Open linux shell (`bash.exe` for linux subsystem on windows) + +**2)** Install rustup, rust and cargo +```bash +$ curl https://sh.rustup.rs -sSf | sh +``` + +**3)** Install cargo-make +``` +$ cargo install --force cargo-make +``` + +**4)** Install cargo-make +``` +$ cargo install --force cargo-watch +``` + +**5)** Install ARM gcc to cross compile for raspberry pi. +*Note: If you choose another gcc version you have to update the linker used in the Cargo.toml file.* +``` +$ apt-get install gcc-8-multilib-arm-linux-gnueabihf +``` + +**6)** Add target in rustup +``` +$ rustup target add armv7-unknown-linux-gnueabihf +``` + +**7)** Update ip in Cargo.toml to directly upload to raspberry pi on build +``` +$ sed -i 's/xxx.xxx.xxx.xxx/your-ip/g' ./Cargo.toml +``` + +### Run + +**Build**: Compile and upload to raspberry pi +```bash +$ cargo make --makefile Cargo.toml debug-raspberry +``` + +**Watch**: Compile and upload on every file change +```bash +cargo watch -x "make --makefile Cargo.toml debug-raspberry" +``` + +**NOTE**: Seems like there is no remote debug functionality at all. I didn't managed it to have breakpoints working. Neither CLion with GDBRemote nor CLI worked and I just found bugs / issues raised by other users complaining about the same. \ No newline at end of file diff --git a/src/cc2500.rs b/src/cc2500.rs new file mode 100644 index 0000000..d325eaf --- /dev/null +++ b/src/cc2500.rs @@ -0,0 +1,28 @@ +use crate::cc2500::chip::CC2500; +use rppal::spi::Spi; + +mod chip; +mod constants; + +#[derive(Debug, Copy, Clone)] +pub struct Address(pub u8, pub u8); + +pub enum STATE { + SIDLE = 0x36, // Exit RX / TX + STX = 0x35, // Enable TX. If in RX state, only enable TX if CCA passes + SFTX = 0x3B, // Flush the TX FIFO buffer. Only issue SFTX in IDLE or TXFIFO_UNDERFLOW states + SRES = 0x30, // Reset chip + SRX = 0x34, // Enable RX. Perform calibration if enabled + SFRX = 0x3A, // Flush the RX FIFO buffer. Only issue SFRX in IDLE or RXFIFO_OVERFLOW states +} + +pub enum COMMAND { + LightOff = 0x01, // Command to turn the light off + LightOn50 = 0x02, // Command to turn the light on 50% + LightOn100 = 0x03, // Command to turn the light on 100% + PAIR = 0xFF, // Command to pair a remote to the light +} + +pub fn new(spi: Spi) -> CC2500 { + CC2500 { spi, address: None } +} diff --git a/src/cc2500/chip.rs b/src/cc2500/chip.rs new file mode 100644 index 0000000..6284fcb --- /dev/null +++ b/src/cc2500/chip.rs @@ -0,0 +1,243 @@ +use super::constants::*; +use super::COMMAND; +use super::STATE; +use super::Address; +use rppal::gpio::Gpio; +use rppal::spi::{Polarity}; +use rppal::spi::{Segment, Spi}; +use std::thread; + +pub struct CC2500 { + pub spi: Spi, + pub address: Option
, +} + +/** + * Block execution until MISO is set to high and chip is ready for receiving data + */ +fn wait_for_miso() { + let gpio = Gpio::new().expect("Couldn't iunit GPIO"); + let miso = gpio + .get(9) + .expect("Could not attach to MISO pin") + .into_output(); + + while miso.is_set_high() {} +} + +/** + * Create list of segments for the list of bytes + */ +fn bytes_to_segments(bytes: &[u8], delay: u16) -> Vec { + let segments = bytes.iter().map(|byte| { + let mut segment = Segment::with_write(std::slice::from_ref(byte)); + segment.set_delay(delay); + segment + }); + return segments.collect(); +} + +impl CC2500 { + + /** + * Write spi register on CC2500 chip + */ + fn write_reg(&mut self, reg: u8, value: u8) { + self.spi.set_ss_polarity(Polarity::ActiveLow).expect("Couldn't set polarity"); + wait_for_miso(); + + let bytes = [reg, value]; + let segments = bytes_to_segments(&bytes, 200); + self.spi + .transfer_segments(&segments) + .expect("Could not write"); + + self.spi.set_ss_polarity(Polarity::ActiveHigh).expect("Couldn't set polarity"); + + thread::sleep(DELAY_200); + } + + /** + * Read spi register from CC2500 chip + */ + fn read_reg(&mut self, addr: u8) -> u8 { + self.spi.set_ss_polarity(Polarity::ActiveLow).expect("Couldn't set polarity"); + wait_for_miso(); + + let buffer = [addr + 0x80]; + let mut write = Segment::with_write(&buffer); + write.set_delay(200); + + let mut buffer: [u8; 1] = [1]; + let mut read = Segment::with_read(&mut buffer); + read.set_delay(200); + + self.spi + .transfer_segments(&[write, read]) + .expect("Could not write"); + + self.spi.set_ss_polarity(Polarity::ActiveHigh).expect("Couldn't set polarity"); + + return buffer[0]; + } + + /** + * Send a strobe of 1 byte to the CC2500 + */ + pub fn strobe(&mut self, state: STATE) { + self.spi.set_ss_polarity(Polarity::ActiveLow).expect("Couldn't set polarity"); + wait_for_miso(); + + self.spi + .write(&[state as u8]) + .expect("Could not transfer to SPI"); + + self.spi.set_ss_polarity(Polarity::ActiveHigh).expect("Couldn't set polarity"); + + thread::sleep(DELAY_20000); + } + + /** + * Initialize the registers of CC2500 chip + */ + pub fn init(&mut self) { + self.strobe(STATE::SRES); + + self.write_reg(REG_IOCFG2, VAL_IOCFG2); + self.write_reg(REG_IOCFG0, VAL_IOCFG0); + self.write_reg(REG_PKTLEN, VAL_PKTLEN); + self.write_reg(REG_PKTCTRL1, VAL_PKTCTRL1); + self.write_reg(REG_PKTCTRL0, VAL_PKTCTRL0); + self.write_reg(REG_ADDR, VAL_ADDR); + self.write_reg(REG_CHANNR, VAL_CHANNR); + self.write_reg(REG_FSCTRL1, VAL_FSCTRL1); + self.write_reg(REG_FSCTRL0, VAL_FSCTRL0); + self.write_reg(REG_FREQ2, VAL_FREQ2); + self.write_reg(REG_FREQ1, VAL_FREQ1); + self.write_reg(REG_FREQ0, VAL_FREQ0); + self.write_reg(REG_MDMCFG4, VAL_MDMCFG4); + self.write_reg(REG_MDMCFG3, VAL_MDMCFG3); + self.write_reg(REG_MDMCFG2, VAL_MDMCFG2); + self.write_reg(REG_MDMCFG1, VAL_MDMCFG1); + self.write_reg(REG_MDMCFG0, VAL_MDMCFG0); + self.write_reg(REG_DEVIATN, VAL_DEVIATN); + self.write_reg(REG_MCSM2, VAL_MCSM2); + self.write_reg(REG_MCSM1, VAL_MCSM1); + self.write_reg(REG_MCSM0, VAL_MCSM0); + self.write_reg(REG_FOCCFG, VAL_FOCCFG); + self.write_reg(REG_BSCFG, VAL_BSCFG); + self.write_reg(REG_AGCCTRL2, VAL_AGCCTRL2); + self.write_reg(REG_AGCCTRL1, VAL_AGCCTRL1); + self.write_reg(REG_AGCCTRL0, VAL_AGCCTRL0); + self.write_reg(REG_WOREVT1, VAL_WOREVT1); + self.write_reg(REG_WOREVT0, VAL_WOREVT0); + self.write_reg(REG_WORCTRL, VAL_WORCTRL); + self.write_reg(REG_FREND1, VAL_FREND1); + self.write_reg(REG_FREND0, VAL_FREND0); + self.write_reg(REG_FSCAL3, VAL_FSCAL3); + self.write_reg(REG_FSCAL2, VAL_FSCAL2); + self.write_reg(REG_FSCAL1, VAL_FSCAL1); + self.write_reg(REG_FSCAL0, VAL_FSCAL0); + self.write_reg(REG_RCCTRL1, VAL_RCCTRL1); + self.write_reg(REG_RCCTRL0, VAL_RCCTRL0); + self.write_reg(REG_FSTEST, VAL_FSTEST); + self.write_reg(REG_TEST2, VAL_TEST2); + self.write_reg(REG_TEST1, VAL_TEST1); + self.write_reg(REG_TEST0, VAL_TEST0); + self.write_reg(REG_DAFUQ, VAL_DAFUQ); + // Set power level to max + self.write_reg(0x3E, 0xFF); + } + + /** + * Send command with CC2500 to Ansluta. Repeating the message 50 times to ensure it was consumed + */ + pub fn command(&mut self, cmd: COMMAND) { + let cmd_address = cmd as u8; + match self.address { + None => panic!("Can not send command to unknown address!"), + Some(address) => { + for _i in 0..50 { + self.strobe(STATE::SIDLE); + self.strobe(STATE::SFTX); + + self.spi.set_ss_polarity(Polarity::ActiveLow).expect("Couldn't set polarity"); + wait_for_miso(); + + let bytes = vec![ + 0x7F, + 0x06, + 0x55, + 0x01, + address.0, + address.1, + cmd_address, + 0xAA, + 0xFF, + ]; + + let segments: Vec<_> = bytes_to_segments(&bytes, 0); + self.spi + .transfer_segments(&segments) + .expect("Could not write"); + + self.spi.set_ss_polarity(Polarity::ActiveHigh).expect("Couldn't set polarity"); + + self.strobe(STATE::STX); + thread::sleep(DELAY_10); + } + } + } + } + + /** + * Debugger function to read the address of the original ansulta remote. + * Once it's read it will print it. In future this should be persisted for other calls + */ + pub fn read_address(&mut self) -> Option
{ + loop { + self.strobe(STATE::SRX); + self.write_reg(REG_IOCFG1, 0x01); // Switch MISO to output if packet has been received + thread::sleep(DELAY_200); + + let packet_length = self.read_reg(REG_FIFO) as usize; + let mut packet = vec![0; packet_length as usize]; + + if packet_length > 8 { + println!("Received packet is to big {}", packet_length); + } + // Read packet from FIFO buffer + if packet_length > 0 && packet_length <= 8 { + println!("Received packet"); + + for i in 0..packet_length { + packet[i] = self.read_reg(REG_FIFO); + } + println!("Packet {:x?}", packet); + + let start = packet.iter().position(|&byte| byte != 0x55).unwrap(); + + // Check if the packet is from IKEA remote + if packet[start] == 0x01 && packet[start + 4] == 0xAA { + println!("Address found: {} {}", packet[start + 1], packet[start + 2]); + self.address = Some(Address(packet[start + 1], packet[start + 2])); + + self.strobe(STATE::SIDLE); + self.strobe(STATE::SFRX); + + return self.address.clone(); + } + } + + self.strobe(STATE::SIDLE); + self.strobe(STATE::SFRX); + } + } + + /** + * Set address of original ansluta + */ + pub fn set_address(&mut self, address: Address) { + self.address = Some(address.clone()); + } +} diff --git a/src/cc2500/constants.rs b/src/cc2500/constants.rs new file mode 100644 index 0000000..069d2ca --- /dev/null +++ b/src/cc2500/constants.rs @@ -0,0 +1,116 @@ +#![allow(dead_code)] + +use std::time::Duration; + +pub const DELAY_1: Duration = Duration::from_micros(1); +pub const DELAY_20000: Duration = Duration::from_micros(20000); +pub const DELAY_10: Duration = Duration::from_micros(10); +pub const DELAY_0: Duration = Duration::from_micros(0); +pub const DELAY_200: Duration = Duration::from_micros(200); + +pub const REG_IOCFG2: u8 = 0x00; +pub const REG_IOCFG1: u8 = 0x01; +pub const REG_IOCFG0: u8 = 0x02; +pub const REG_FIFOTHR: u8 = 0x03; +pub const REG_SYNC1: u8 = 0x04; +pub const REG_SYNC0: u8 = 0x05; +pub const REG_PKTLEN: u8 = 0x06; +pub const REG_PKTCTRL1: u8 = 0x07; +pub const REG_PKTCTRL0: u8 = 0x08; +pub const REG_ADDR: u8 = 0x09; +pub const REG_CHANNR: u8 = 0x0A; +pub const REG_FSCTRL1: u8 = 0x0B; +pub const REG_FSCTRL0: u8 = 0x0C; +pub const REG_FREQ2: u8 = 0x0D; +pub const REG_FREQ1: u8 = 0x0E; +pub const REG_FREQ0: u8 = 0x0F; +pub const REG_MDMCFG4: u8 = 0x10; +pub const REG_MDMCFG3: u8 = 0x11; +pub const REG_MDMCFG2: u8 = 0x12; +pub const REG_MDMCFG1: u8 = 0x13; +pub const REG_MDMCFG0: u8 = 0x14; +pub const REG_DEVIATN: u8 = 0x15; +pub const REG_MCSM2: u8 = 0x16; +pub const REG_MCSM1: u8 = 0x17; +pub const REG_MCSM0: u8 = 0x18; +pub const REG_FOCCFG: u8 = 0x19; +pub const REG_BSCFG: u8 = 0x1A; +pub const REG_AGCCTRL2: u8 = 0x1B; +pub const REG_AGCCTRL1: u8 = 0x1C; +pub const REG_AGCCTRL0: u8 = 0x1D; +pub const REG_WOREVT1: u8 = 0x1E; +pub const REG_WOREVT0: u8 = 0x1F; +pub const REG_WORCTRL: u8 = 0x20; +pub const REG_FREND1: u8 = 0x21; +pub const REG_FREND0: u8 = 0x22; +pub const REG_FSCAL3: u8 = 0x23; +pub const REG_FSCAL2: u8 = 0x24; +pub const REG_FSCAL1: u8 = 0x25; +pub const REG_FSCAL0: u8 = 0x26; +pub const REG_RCCTRL1: u8 = 0x27; +pub const REG_RCCTRL0: u8 = 0x28; +pub const REG_FSTEST: u8 = 0x29; +pub const REG_PTEST: u8 = 0x2A; +pub const REG_AGCTEST: u8 = 0x2B; +pub const REG_TEST2: u8 = 0x2C; +pub const REG_TEST1: u8 = 0x2D; +pub const REG_TEST0: u8 = 0x2E; +pub const REG_PARTNUM: u8 = 0x30; +pub const REG_VERSION: u8 = 0x31; +pub const REG_FREQEST: u8 = 0x32; +pub const REG_LQI: u8 = 0x33; +pub const REG_RSSI: u8 = 0x34; +pub const REG_MARCSTATE: u8 = 0x35; +pub const REG_WORTIME1: u8 = 0x36; +pub const REG_WORTIME0: u8 = 0x37; +pub const REG_PKTSTATUS: u8 = 0x38; +pub const REG_VCO_VC_DAC: u8 = 0x39; +pub const REG_TXBYTES: u8 = 0x3A; +pub const REG_RXBYTES: u8 = 0x3B; +pub const REG_RCCTRL1_STATUS: u8 = 0x3C; +pub const REG_RCCTRL0_STATUS: u8 = 0x3D; +pub const REG_FIFO: u8 = 0x3F; // TX and RX FIFO +pub const REG_DAFUQ: u8 = 0x7E; + +pub const VAL_IOCFG2: u8 = 0x29; +pub const VAL_IOCFG0: u8 = 0x06; +pub const VAL_PKTLEN: u8 = 0xFF; +pub const VAL_PKTCTRL1: u8 = 0x04; +pub const VAL_PKTCTRL0: u8 = 0x05; +pub const VAL_ADDR: u8 = 0x01; +pub const VAL_CHANNR: u8 = 0x10; +pub const VAL_FSCTRL1: u8 = 0x09; +pub const VAL_FSCTRL0: u8 = 0x00; +pub const VAL_FREQ2: u8 = 0x5D; +pub const VAL_FREQ1: u8 = 0x93; +pub const VAL_FREQ0: u8 = 0xB1; +pub const VAL_MDMCFG4: u8 = 0x2D; +pub const VAL_MDMCFG3: u8 = 0x3B; +pub const VAL_MDMCFG2: u8 = 0x73; //MSK, No Manchester +pub const VAL_MDMCFG1: u8 = 0xA2; +pub const VAL_MDMCFG0: u8 = 0xF8; +pub const VAL_DEVIATN: u8 = 0x01; +pub const VAL_MCSM2: u8 = 0x07; +pub const VAL_MCSM1: u8 = 0x30; +pub const VAL_MCSM0: u8 = 0x18; +pub const VAL_FOCCFG: u8 = 0x1D; +pub const VAL_BSCFG: u8 = 0x1C; +pub const VAL_AGCCTRL2: u8 = 0xC7; +pub const VAL_AGCCTRL1: u8 = 0x00; +pub const VAL_AGCCTRL0: u8 = 0xB2; +pub const VAL_WOREVT1: u8 = 0x87; +pub const VAL_WOREVT0: u8 = 0x6B; +pub const VAL_WORCTRL: u8 = 0xF8; +pub const VAL_FREND1: u8 = 0xB6; +pub const VAL_FREND0: u8 = 0x10; +pub const VAL_FSCAL3: u8 = 0xEA; +pub const VAL_FSCAL2: u8 = 0x0A; +pub const VAL_FSCAL1: u8 = 0x00; +pub const VAL_FSCAL0: u8 = 0x11; +pub const VAL_RCCTRL1: u8 = 0x41; +pub const VAL_RCCTRL0: u8 = 0x00; +pub const VAL_FSTEST: u8 = 0x59; +pub const VAL_TEST2: u8 = 0x88; +pub const VAL_TEST1: u8 = 0x31; +pub const VAL_TEST0: u8 = 0x0B; +pub const VAL_DAFUQ: u8 = 0xFF; diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..73cde32 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,64 @@ +use crate::cc2500::{COMMAND, Address}; +use rppal::spi::{BitOrder, Bus, Mode, SlaveSelect, Spi}; +use std::env; +use std::fs; + +mod cc2500; + +const ADDRESS_FILE: &str = "./.ansluta-address"; + +/** + * Load 2 byte address of original ansluta + */ +fn load_address() -> Option
{ + match fs::read(ADDRESS_FILE) { + Ok(address) => Some(Address(address[0], address[1])), + Err(_) => None + } +} + +/** + * Save 2 byte address of original ansluta for later use + */ +fn save_address(address: Address) { + if let Err(error) = fs::write(ADDRESS_FILE, [address.0, address.1]) { + panic!("Couldn't save ansluta address to {} because {}", ADDRESS_FILE, error); + } +} + +/** + * Initialize CC2500 chip registers, ensure we have a ansulta address and execute one of the commands off, 50 or 100 + */ +fn main() { + let spi = Spi::new(Bus::Spi0, SlaveSelect::Ss0, 6_000_000, Mode::Mode0) + .expect("Couldn't start SPI connection"); + spi.set_bit_order(BitOrder::MsbFirst) + .expect("Could not set bit order on SPI"); + + let mut cc2500 = cc2500::new(spi); + cc2500.init(); + // Read address from original ansluta if none is known yet + match load_address() { + Some(address) => cc2500.set_address(address), + None => { + match cc2500.read_address() { + Some(address) => save_address(address), + None => panic!("Could not find address!") + } + } + } + + let args: Vec = env::args().collect(); + let level = &args[1]; + let command = { + match level.as_str() { + "off" => COMMAND::LightOff, + "50" => COMMAND::LightOn50, + "100" => COMMAND::LightOn100, + _ => panic!("Unknown level. Use off, 50 or 100") + } + }; + + println!("Light {}", level); + cc2500.command(command); +}