diff --git a/README.md b/README.md index fed7d7b..60f37e4 100644 --- a/README.md +++ b/README.md @@ -76,3 +76,4 @@ The scope of this setting is per TCP session. | `load` | Restore configuration from EEPROM | | `save` | Save configuration to EEPROM | | `reset` | Reset the device | +| `ipv4 ` | Configure IPv4 address | diff --git a/src/command_parser.rs b/src/command_parser.rs index c93545a..3352d17 100644 --- a/src/command_parser.rs +++ b/src/command_parser.rs @@ -135,6 +135,7 @@ pub enum Command { Load, Save, Reset, + Ipv4([u8; 4]), Show(ShowCommand), Reporting(bool), /// PWM parameter setting @@ -181,6 +182,19 @@ fn whitespace(input: &[u8]) -> IResult<&[u8], ()> { fold_many1(char(' '), (), |(), _| ())(input) } +fn unsigned(input: &[u8]) -> IResult<&[u8], Result> { + take_while1(is_digit)(input) + .map(|(input, digits)| { + let result = + from_utf8(digits) + .map_err(|e| e.into()) + .and_then(|digits| u32::from_str_radix(digits, 10) + .map_err(|e| e.into()) + ); + (input, result) + }) +} + fn float(input: &[u8]) -> IResult<&[u8], Result> { let (input, sign) = opt(is_a("-"))(input)?; let negative = sign.is_some(); @@ -415,11 +429,30 @@ fn postfilter(input: &[u8]) -> IResult<&[u8], Result> { ))(input) } +fn ipv4(input: &[u8]) -> IResult<&[u8], Result> { + let (input, _) = tag("ipv4")(input)?; + let (input, _) = whitespace(input)?; + let (input, a) = unsigned(input)?; + let (input, _) = tag(".")(input)?; + let (input, b) = unsigned(input)?; + let (input, _) = tag(".")(input)?; + let (input, c) = unsigned(input)?; + let (input, _) = tag(".")(input)?; + let (input, d) = unsigned(input)?; + end(input)?; + + let result = a.and_then(|a| b.and_then(|b| c.and_then(|c| d.map(|d| + Command::Ipv4([a as u8, b as u8, c as u8, d as u8]) + )))); + Ok((input, result)) +} + fn command(input: &[u8]) -> IResult<&[u8], Result> { alt((value(Ok(Command::Quit), tag("quit")), value(Ok(Command::Load), tag("load")), value(Ok(Command::Save), tag("save")), value(Ok(Command::Reset), tag("reset")), + ipv4, map(report, Ok), pwm, center_point, @@ -464,6 +497,12 @@ mod test { assert_eq!(command, Ok(Command::Save)); } + #[test] + fn parse_ipv4() { + let command = Command::parse(b"ipv4 192.168.1.26"); + assert_eq!(command, Ok(Command::Ipv4([192, 168, 1, 26]))); + } + #[test] fn parse_report() { let command = Command::parse(b"report"); diff --git a/src/config.rs b/src/config.rs index 1e9d2eb..4e39bd1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -1,5 +1,6 @@ use postcard::{from_bytes, to_slice}; use serde::{Serialize, Deserialize}; +use smoltcp::wire::Ipv4Address; use stm32f4xx_hal::i2c; use uom::si::{ electric_potential::volt, @@ -40,15 +41,17 @@ impl From for Error { #[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Config { channels: [ChannelConfig; CHANNELS], + pub ipv4_address: [u8; 4], } impl Config { - pub fn new(channels: &mut Channels) -> Self { + pub fn new(channels: &mut Channels, ipv4_address: Ipv4Address) -> Self { Config { channels: [ ChannelConfig::new(channels, 0), ChannelConfig::new(channels, 1), ], + ipv4_address: ipv4_address.0, } } @@ -191,6 +194,7 @@ impl PwmLimits { #[cfg(test)] mod test { use super::*; + use crate::DEFAULT_IPV4_ADDRESS; #[test] fn test_fit_eeprom() { @@ -211,6 +215,7 @@ mod test { channel_config.clone(), channel_config.clone(), ], + ipv4_address: DEFAULT_IPV4_ADDRESS.0, }; let mut buffer = [0; EEPROM_SIZE]; @@ -237,6 +242,7 @@ mod test { channel_config.clone(), channel_config.clone(), ], + ipv4_address: DEFAULT_IPV4_ADDRESS.0, }; let mut buffer = [0; EEPROM_SIZE]; diff --git a/src/main.rs b/src/main.rs index f4c83ee..f4bf40c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -24,7 +24,7 @@ use stm32f4xx_hal::{ use smoltcp::{ time::Instant, socket::TcpSocket, - wire::EthernetAddress, + wire::{EthernetAddress, Ipv4Address}, }; use uom::{ si::{ @@ -76,6 +76,7 @@ const WATCHDOG_INTERVAL: u32 = 30_000; pub const EEPROM_PAGE_SIZE: usize = 8; pub const EEPROM_SIZE: usize = 128; +pub const DEFAULT_IPV4_ADDRESS: Ipv4Address = Ipv4Address([192, 168, 1, 26]); const TCP_PORT: u16 = 23; @@ -159,10 +160,15 @@ fn main() -> ! { usb::State::setup(usb); + let mut ipv4_address = DEFAULT_IPV4_ADDRESS; let mut channels = Channels::new(pins); let _ = Config::load(&mut eeprom) - .map(|config| config.apply(&mut channels)) + .map(|config| { + config.apply(&mut channels); + ipv4_address = Ipv4Address::from_bytes(&config.ipv4_address); + }) .map_err(|e| warn!("error loading config: {:?}", e)); + info!("IPv4 address: {}", ipv4_address); // EEPROM ships with a read-only EUI-48 identifier let mut eui48 = [0; 6]; @@ -170,7 +176,8 @@ fn main() -> ! { let hwaddr = EthernetAddress(eui48); info!("EEPROM MAC address: {}", hwaddr); - net::run(clocks, dp.ETHERNET_MAC, dp.ETHERNET_DMA, eth_pins, hwaddr, |iface| { + net::run(clocks, dp.ETHERNET_MAC, dp.ETHERNET_DMA, eth_pins, hwaddr, ipv4_address, |iface| { + let mut new_ipv4_address = None; Server::::run(iface, |server| { leds.r1.off(); @@ -339,20 +346,25 @@ fn main() -> ! { } Command::Load => { match Config::load(&mut eeprom) { - Ok(config) => - config.apply(&mut channels), + Ok(config) => { + config.apply(&mut channels); + new_ipv4_address = Some(Ipv4Address::from_bytes(&config.ipv4_address)); + } Err(e) => error!("unable to load eeprom config: {:?}", e), } } Command::Save => { - let config = Config::new(&mut channels); + let config = Config::new(&mut channels, ipv4_address); match config.save(&mut eeprom) { Ok(()) => {}, Err(e) => error!("unable to save eeprom config: {:?}", e), } } + Command::Ipv4(address) => { + new_ipv4_address = Some(Ipv4Address::from_bytes(&address)); + } Command::Reset => { for i in 0..CHANNELS { channels.power_down(i); @@ -375,6 +387,11 @@ fn main() -> ! { } }); + // Apply new IPv4 address + new_ipv4_address.map(|ipv4_address| + server.set_ipv4_address(ipv4_address) + ); + // Update watchdog wd.feed(); diff --git a/src/net.rs b/src/net.rs index 5914373..031eee4 100644 --- a/src/net.rs +++ b/src/net.rs @@ -8,7 +8,7 @@ use stm32f4xx_hal::{ rcc::Clocks, stm32::{interrupt, Peripherals, ETHERNET_MAC, ETHERNET_DMA}, }; -use smoltcp::wire::{EthernetAddress, IpAddress, IpCidr}; +use smoltcp::wire::{EthernetAddress, IpCidr, Ipv4Address}; use smoltcp::iface::{NeighborCache, EthernetInterfaceBuilder, EthernetInterface}; use stm32_eth::{Eth, RingEntry, PhyAddress, RxDescriptor, TxDescriptor}; use crate::pins::EthernetPins; @@ -29,7 +29,9 @@ pub fn run( clocks: Clocks, ethernet_mac: ETHERNET_MAC, ethernet_dma: ETHERNET_DMA, eth_pins: EthernetPins, - ethernet_addr: EthernetAddress, f: F + ethernet_addr: EthernetAddress, + local_addr: Ipv4Address, + f: F ) where F: FnOnce(EthernetInterface<&mut stm32_eth::Eth<'static, 'static>>), { @@ -50,8 +52,9 @@ pub fn run( eth_dev.enable_interrupt(); // IP stack - let local_addr = IpAddress::v4(192, 168, 1, 26); - let mut ip_addrs = [IpCidr::new(local_addr, 24)]; + // Netmask 0 means we expect any IP address on the local segment. + // No routing. + let mut ip_addrs = [IpCidr::new(local_addr.into(), 0)]; let mut neighbor_storage = [None; 16]; let neighbor_cache = NeighborCache::new(&mut neighbor_storage[..]); let iface = EthernetInterfaceBuilder::new(&mut eth_dev) diff --git a/src/server.rs b/src/server.rs index 07a2402..d3ac975 100644 --- a/src/server.rs +++ b/src/server.rs @@ -3,6 +3,7 @@ use smoltcp::{ iface::EthernetInterface, socket::{SocketSet, SocketHandle, TcpSocket, TcpSocketBuffer, SocketRef}, time::Instant, + wire::{IpCidr, Ipv4Address, Ipv4Cidr}, }; @@ -83,4 +84,21 @@ impl<'a, 'b, S: Default> Server<'a, 'b, S> { callback(socket, &mut state.state); } } + + pub fn set_ipv4_address(&mut self, ipv4_address: Ipv4Address) { + self.net.update_ip_addrs(|addrs| { + for addr in addrs.iter_mut() { + match addr { + IpCidr::Ipv4(_) => { + *addr = IpCidr::Ipv4(Ipv4Cidr::new(ipv4_address, 0)); + // done + break + } + _ => { + // skip + } + } + } + }); + } }