Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding static IP configuration support #240

Merged
merged 8 commits into from
Oct 12, 2022
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
* Support for different ethernet daughterboards using the ENC424J00 has been added.
* Support for static IP + netmask and gateway configuration

### Changed
* Removed custom `mutex` implementation in favor of leveraging `shared-bus`
Expand Down
16 changes: 9 additions & 7 deletions src/hardware/net_interface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,6 @@ use super::external_mac::SmoltcpDevice;
/// The number of TCP sockets supported in the network stack.
const NUM_TCP_SOCKETS: usize = 4;

/// Static storage for Smoltcp sockets and network state.
static mut NETWORK_STORAGE: NetStorage = NetStorage::new();

/// Containers for smoltcp-related network configurations
struct NetStorage {
pub ip_addrs: [smoltcp::wire::IpCidr; 1],
Expand Down Expand Up @@ -73,11 +70,14 @@ pub fn setup(
device: SmoltcpDevice<'static>,
settings: &BoosterSettings,
) -> smoltcp::iface::Interface<'static, SmoltcpDevice<'static>> {
// Note(unsafe): This function may only be called once to initialize the network storage.
let net_store = unsafe { &mut NETWORK_STORAGE };
let net_store = cortex_m::singleton!(: NetStorage = NetStorage::new()).unwrap();

let ip_address = settings.ip_address();
net_store.ip_addrs[0] = ip_address;

let mut interface = {
let routes = smoltcp::iface::Routes::new(&mut net_store.routes_cache[..]);
let mut routes = smoltcp::iface::Routes::new(&mut net_store.routes_cache[..]);
routes.add_default_ipv4_route(settings.gateway()).unwrap();

let neighbor_cache = smoltcp::iface::NeighborCache::new(&mut net_store.neighbor_cache[..]);

Expand All @@ -100,7 +100,9 @@ pub fn setup(
interface.add_socket(tcp_socket);
}

interface.add_socket(smoltcp::socket::Dhcpv4Socket::new());
if ip_address.address().is_unspecified() {
interface.add_socket(smoltcp::socket::Dhcpv4Socket::new());
}

interface
}
61 changes: 51 additions & 10 deletions src/serial_terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use super::{
BoosterSettings,
};
use bbqueue::BBBuffer;
use core::convert::{TryFrom, TryInto};
use heapless::{String, Vec};
use logos::Logos;
use usbd_serial::UsbError;
Expand Down Expand Up @@ -46,6 +47,15 @@ enum Token {
#[token("broker-address")]
BrokerAddress,

#[token("gateway")]
Gateway,

#[token("netmask")]
Netmask,

#[token("ip-address")]
StaticIpAddress,

#[token("fan")]
FanSpeed,

Expand All @@ -65,6 +75,9 @@ pub enum Property {
BrokerAddress,
Identifier,
FanSpeed,
IpAddress,
Netmask,
Gateway,
}

pub enum Request {
Expand All @@ -78,12 +91,33 @@ pub enum Request {
WriteFanSpeed(f32),
}

impl TryFrom<Token> for Property {
type Error = ();
fn try_from(token: Token) -> Result<Property, ()> {
let property = match token {
Token::Mac => Property::Mac,
Token::BrokerAddress => Property::BrokerAddress,
Token::Identifier => Property::Identifier,
Token::FanSpeed => Property::FanSpeed,
Token::StaticIpAddress => Property::IpAddress,
Token::Gateway => Property::Gateway,
Token::Netmask => Property::Netmask,
_ => return Err(()),
};

Ok(property)
}
}

fn get_property_string(prop: Property, settings: &BoosterSettings) -> String<128> {
let mut msg = String::<128>::new();
match prop {
Property::Identifier => writeln!(&mut msg, "{}", settings.id()).unwrap(),
Property::Mac => writeln!(&mut msg, "{}", settings.mac()).unwrap(),
Property::BrokerAddress => writeln!(&mut msg, "{}", settings.broker()).unwrap(),
Property::Gateway => writeln!(&mut msg, "{}", settings.gateway()).unwrap(),
Property::IpAddress => writeln!(&mut msg, "{}", settings.ip_address()).unwrap(),
Property::Netmask => writeln!(&mut msg, "{}", settings.netmask()).unwrap(),
Property::FanSpeed => writeln!(&mut msg, "{:.2} %", settings.fan_speed() * 100.0).unwrap(),
};
msg
Expand Down Expand Up @@ -212,6 +246,9 @@ impl SerialTerminal {

Request::WriteIpAddress(prop, addr) => match prop {
Property::BrokerAddress => self.settings.set_broker(addr),
Property::Gateway => self.settings.set_gateway(addr),
Property::Netmask => self.settings.set_netmask(addr),
Property::IpAddress => self.settings.set_ip_address(addr),
_ => self.write("Invalid property write\n".as_bytes()),
},

Expand Down Expand Up @@ -365,9 +402,13 @@ impl SerialTerminal {
* `reset` - Resets the device
* `service` - Read the service information. Service infromation clears once read.
* `dfu` - Resets the device to DFU mode
* `read <PROP>` - Reads the value of <PROP>. <PROP> may be [broker-address, mac, id, fan]
* `write [broker-address <IP> | id <ID> | fan <DUTY>]`
- Writes the value of <IP> to the broker address. <IP> must be an IP address (e.g. 192.168.1.1)
* `read <PROP>` - Reads the value of <PROP>. <PROP> may be:
- [broker-address, mac, id, fan, ip-address, netmask, gateway]
* `write [broker-address <IP> | gateway <IP> | ip-address <IP> | netmask <IP> | id <ID> | fan <DUTY>]
- Writes the value of <IP> to the broker address, static IP, or gateway.
* An `ip-address` of 0.0.0.0 will use DHCP
* <IP> must be an IP address (e.g. 192.168.1.1)
* Netmasks must contain only leading data. E.g. 255.255.0.0
- Write the MQTT client ID of the device. <ID> must be 23 or less ASCII characters.
- Write the <DUTY> default fan speed duty cycle, which is specified [0, 1.0].
"
Expand Down Expand Up @@ -400,11 +441,8 @@ impl SerialTerminal {
let property_token = lex.next().ok_or("Malformed command")?;

// Check that the property is acceptable for a read.
let property = match property_token {
Token::Mac => Property::Mac,
Token::BrokerAddress => Property::BrokerAddress,
Token::Identifier => Property::Identifier,
Token::FanSpeed => Property::FanSpeed,
let property = match property_token.try_into() {
Ok(prop) => prop,
_ => return Err("Invalid property read"),
};

Expand All @@ -413,9 +451,12 @@ impl SerialTerminal {
Token::Write => {
// Check that the property is acceptable for a write.
match lex.next().ok_or("Malformed command")? {
Token::BrokerAddress => {
token @ Token::BrokerAddress
| token @ Token::StaticIpAddress
| token @ Token::Gateway
| token @ Token::Netmask => {
if let Token::IpAddress(addr) = lex.next().ok_or("Malformed address")? {
Request::WriteIpAddress(Property::BrokerAddress, addr)
Request::WriteIpAddress(token.try_into().unwrap(), addr)
} else {
return Err("Invalid property");
}
Expand Down
158 changes: 103 additions & 55 deletions src/settings/global_settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ use crate::{hardware::Eeprom, Error};
use heapless::String;
use minimq::embedded_nal::Ipv4Addr;
use serde::{Deserialize, Serialize};
use smoltcp_nal::smoltcp;

use crate::hardware::chassis_fans::DEFAULT_FAN_SPEED;

Expand All @@ -25,10 +26,6 @@ const EXPECTED_VERSION: SemVersion = SemVersion {
patch: 0,
};

fn array_to_addr(addr: &[u8; 4]) -> Ipv4Addr {
Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3])
}

fn identifier_is_valid(id: &str) -> bool {
id.len() <= 23 && id.chars().all(|x| x.is_alphanumeric() || x == '-')
}
Expand All @@ -37,13 +34,10 @@ fn identifier_is_valid(id: &str) -> bool {
#[derive(Serialize, Deserialize)]
struct BoosterMainBoardData {
version: SemVersion,

// Note: The IP address, gateway, and netmask are unused, but left here to maintain backwards compatibility with
// settings version v1.0.0
_unused_ip_address: [u8; 4],
ip_address: [u8; 4],
broker_address: [u8; 4],
_unused_gateway_address: [u8; 4],
_unused_netmask: [u8; 4],
gateway: [u8; 4],
netmask: [u8; 4],
identifier: [u8; 23],
id_size: usize,
fan_speed: f32,
Expand All @@ -68,10 +62,10 @@ impl BoosterMainBoardData {

Self {
version: EXPECTED_VERSION,
_unused_ip_address: [10, 0, 0, 1],
ip_address: [0, 0, 0, 0],
broker_address: [10, 0, 0, 2],
_unused_gateway_address: [10, 0, 0, 0],
_unused_netmask: [255, 255, 255, 0],
gateway: [0, 0, 0, 0],
netmask: [0, 0, 0, 0],
identifier: id,
id_size: name.len(),
fan_speed: DEFAULT_FAN_SPEED,
Expand Down Expand Up @@ -116,6 +110,11 @@ impl BoosterMainBoardData {
Ok((config, modified))
}

/// Get the MQTT identifier of Booster.
pub fn id(&self) -> &[u8] {
&self.identifier[..self.id_size]
}

/// Serialize the booster config into a sinara configuration for storage into EEPROM.
///
/// # Args
Expand All @@ -125,41 +124,6 @@ impl BoosterMainBoardData {
let serialized = postcard::to_slice(self, &mut buffer).unwrap();
config.board_data[..serialized.len()].copy_from_slice(serialized);
}

/// Get the MQTT broker address of Booster.
pub fn broker(&self) -> Ipv4Addr {
array_to_addr(&self.broker_address)
}

/// Get the MQTT identifier of Booster.
pub fn id(&self) -> &[u8] {
&self.identifier[..self.id_size]
}

/// Set the MQTT ID of Booster.
///
/// # Args
/// * `id` - The new MQTT id. This must conform with MQTT identifier standards. That means that
/// it must be 23 characters or shorter and contain only alphanumeric values.
///
/// # Returns
/// Ok if the update was successful. Otherwise, returns an error.
pub fn set_id(&mut self, id: &str) -> Result<(), Error> {
if !identifier_is_valid(id) {
return Err(Error::Invalid);
}

let len = id.as_bytes().len();
self.identifier[..len].copy_from_slice(id.as_bytes());
self.id_size = len;

Ok(())
}

/// Update the MQTT broker IP address of Booster.
pub fn set_broker(&mut self, addr: Ipv4Addr) {
self.broker_address = addr.octets();
}
}

/// Booster device-wide configurable settings.
Expand Down Expand Up @@ -254,7 +218,8 @@ impl BoosterSettings {

/// Get the MQTT broker IP address.
pub fn broker(&self) -> Ipv4Addr {
self.board_data.broker()
let addr = &self.board_data.broker_address;
Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3])
}

/// Check if current settings differ from active (executing) settings.
Expand All @@ -265,15 +230,98 @@ impl BoosterSettings {
/// Update the broker IP address.
pub fn set_broker(&mut self, addr: Ipv4Addr) {
self.dirty = true;
self.board_data.set_broker(addr);
self.board_data.broker_address = addr.octets();
self.save();
}

/// Update the booster MQTT client identifier.
/// Set the MQTT ID of Booster.
///
/// # Args
/// * `id` - The new MQTT id. This must conform with MQTT identifier standards. That means that
/// it must be 23 characters or shorter and contain only alphanumeric values.
///
/// # Returns
/// Ok if the update was successful. Otherwise, returns an error.
pub fn set_id(&mut self, id: &str) -> Result<(), Error> {
self.board_data.set_id(id).map(|_| {
self.dirty = true;
self.save()
})
if !identifier_is_valid(id) {
return Err(Error::Invalid);
}

let len = id.as_bytes().len();
self.board_data.identifier[..len].copy_from_slice(id.as_bytes());
self.board_data.id_size = len;

self.dirty = true;
self.save();

Ok(())
}

/// Get the IP address of the device.
///
/// # Note
/// The IP address will be unspecified if DHCP is to be used.
pub fn ip_address(&self) -> smoltcp::wire::IpCidr {
let ip_addr = smoltcp::wire::Ipv4Address::from_bytes(&self.board_data.ip_address);

let prefix = if !ip_addr.is_unspecified() {
let netmask = smoltcp::wire::IpAddress::Ipv4(smoltcp::wire::Ipv4Address::from_bytes(
&self.board_data.netmask,
));

netmask.prefix_len().unwrap_or_else(|| {
log::error!("Invalid netmask found. Assuming no mask.");
0
})
} else {
0
};

smoltcp::wire::IpCidr::new(smoltcp::wire::IpAddress::Ipv4(ip_addr), prefix)
}

/// Set the static IP address of the device.
///
/// # Args
/// * `addr` - The address to set
pub fn set_ip_address(&mut self, addr: Ipv4Addr) {
self.board_data.ip_address = addr.octets();
self.dirty = true;
self.save();
}

/// Get the netmask of the device.
pub fn netmask(&self) -> smoltcp::wire::Ipv4Address {
smoltcp::wire::Ipv4Address::from_bytes(&self.board_data.netmask)
}

/// Set the netmask of the static IP address.
pub fn set_netmask(&mut self, mask: Ipv4Addr) {
let netmask = smoltcp::wire::IpAddress::Ipv4(smoltcp::wire::Ipv4Address::from_bytes(
&mask.octets()[..],
));

if netmask.prefix_len().is_none() {
log::error!("Netmask is invalid. Ignoring");
return;
}

self.board_data.netmask = mask.octets();
self.dirty = true;
self.save();
}

/// Get the default gateway IP address for the interface.
pub fn gateway(&self) -> smoltcp::wire::Ipv4Address {
smoltcp::wire::Ipv4Address::from_bytes(&self.board_data.gateway)
}

/// Set the gateway of the device.
///
/// # Note
pub fn set_gateway(&mut self, gateway: Ipv4Addr) {
self.board_data.gateway = gateway.octets();
self.dirty = true;
self.save();
}
}