diff --git a/firmware/src/board.rs b/firmware/src/board.rs index 0d3fb9c..08f9fb4 100644 --- a/firmware/src/board.rs +++ b/firmware/src/board.rs @@ -320,3 +320,13 @@ pub fn init() { adc0.actss.write(|w| w.asen0().bit(true)); }); } + +pub fn get_mac_address() -> [u8; 6] { + let (userreg0, userreg1) = cortex_m::interrupt::free(|cs| { + let flashctl = tm4c129x::FLASH_CTRL.borrow(cs); + (flashctl.userreg0.read().bits(), + flashctl.userreg1.read().bits()) + }); + [userreg0 as u8, (userreg0 >> 8) as u8, (userreg0 >> 16) as u8, + userreg1 as u8, (userreg1 >> 8) as u8, (userreg1 >> 16) as u8] +} diff --git a/firmware/src/http.rs b/firmware/src/http.rs new file mode 100644 index 0000000..4ec7a84 --- /dev/null +++ b/firmware/src/http.rs @@ -0,0 +1,151 @@ +use core::fmt; + +const MAX_PATH: usize = 128; + +#[derive(Debug,Clone,Copy,PartialEq,Eq)] +enum State { + WaitG, + WaitE, + WaitT, + WaitSpace, + GetPath, + WaitCR1, + WaitLF1, + WaitCR2, + WaitLF2, + Finished +} + +pub struct Request { + state: State, + path_idx: usize, + path: [u8; MAX_PATH] +} + +impl Request { + pub fn new() -> Request { + Request { + state: State::WaitG, + path_idx: 0, + path: [0; MAX_PATH] + } + } + + pub fn reset(&mut self) { + self.state = State::WaitG; + self.path_idx = 0; + } + + pub fn input_char(&mut self, c: u8) -> Result { + match self.state { + State::WaitG => { + if c == b'G' { + self.state = State::WaitE; + } else { + return Err("invalid character in method") + } + } + State::WaitE => { + if c == b'E' { + self.state = State::WaitT; + } else { + return Err("invalid character in method") + } + } + State::WaitT => { + if c == b'T' { + self.state = State::WaitSpace; + } else { + return Err("invalid character in method") + } + } + State::WaitSpace => { + if c == b' ' { + self.state = State::GetPath; + } else { + return Err("invalid character in method") + } + } + State::GetPath => { + if c == b'\r' || c == b'\n' { + return Err("GET ended prematurely") + } else if c == b' ' { + if self.path_idx == 0 { + return Err("path is empty") + } else { + self.state = State::WaitCR1; + } + } else { + if self.path_idx >= self.path.len() { + return Err("path is too long") + } else { + self.path[self.path_idx] = c; + self.path_idx += 1; + } + } + } + State::WaitCR1 => { + if c == b'\r' { + self.state = State::WaitLF1; + } + } + State::WaitLF1 => { + if c == b'\n' { + self.state = State::WaitCR2; + } else { + self.state = State::WaitCR1; + } + } + State::WaitCR2 => { + if c == b'\r' { + self.state = State::WaitLF2; + } else { + self.state = State::WaitCR1; + } + } + State::WaitLF2 => { + if c == b'\n' { + self.state = State::Finished; + return Ok(true) + } else { + self.state = State::WaitCR1; + } + } + State::Finished => return Err("trailing characters") + } + Ok(false) + } + + pub fn input(&mut self, buf: &[u8]) -> Result { + let mut result = Ok(false); + for c in buf.iter() { + result = self.input_char(*c); + if result.is_err() { + return result; + } + } + result + } + + pub fn get_path<'a>(&'a self) -> Result<&'a [u8], &'static str> { + if self.state != State::Finished { + return Err("request is not finished") + } + Ok(&self.path[..self.path_idx]) + } +} + +pub fn write_reply_header(output: &mut fmt::Write, status: u16, content_type: &str, gzip: bool) -> fmt::Result { + let status_text = match status { + 200 => "OK", + 404 => "Not Found", + 500 => "Internal Server Error", + _ => return Err(fmt::Error) + }; + write!(output, "HTTP/1.1 {} {}\r\nContent-Type: {}\r\n", + status, status_text, content_type)?; + if gzip { + write!(output, "Content-Encoding: gzip\r\n")?; + } + write!(output, "\r\n") +} diff --git a/firmware/src/index.html b/firmware/src/index.html new file mode 100644 index 0000000..a75e298 --- /dev/null +++ b/firmware/src/index.html @@ -0,0 +1,50 @@ + + +ionpak + + + + + + + + +
+ +
+

Measure

+
+ +
+

Pressure:

+
+
+2.3×10-7 mbar +
+ +
+

+At local time: + +

+
+ +
+ + + + + + + + diff --git a/firmware/src/logo.svg.gz b/firmware/src/logo.svg.gz new file mode 100644 index 0000000..135b4b1 Binary files /dev/null and b/firmware/src/logo.svg.gz differ diff --git a/firmware/src/main.rs b/firmware/src/main.rs index 8dc35f6..36224aa 100644 --- a/firmware/src/main.rs +++ b/firmware/src/main.rs @@ -12,6 +12,11 @@ use cortex_m::exception::Handlers as ExceptionHandlers; use cortex_m::interrupt::Mutex; use tm4c129x::interrupt::Interrupt; use tm4c129x::interrupt::Handlers as InterruptHandlers; +use smoltcp::Error; +use smoltcp::wire::{EthernetAddress, IpAddress}; +use smoltcp::iface::{ArpCache, SliceArpCache, EthernetInterface}; +use smoltcp::socket::{AsSocket, SocketSet}; +use smoltcp::socket::{TcpSocket, TcpSocketBuffer}; #[macro_export] macro_rules! print { @@ -34,6 +39,8 @@ mod pid; mod loop_anode; mod loop_cathode; mod electrometer; +mod http; +mod pages; static TIME: Mutex> = Mutex::new(Cell::new(0)); @@ -68,6 +75,26 @@ impl fmt::Write for UART0 { } } +const TCP_RX_BUFFER_SIZE: usize = 256; +const TCP_TX_BUFFER_SIZE: usize = 8192; + + +macro_rules! create_socket_storage { + ($rx_storage:ident, $tx_storage:ident) => ( + let mut $rx_storage = [0; TCP_RX_BUFFER_SIZE]; + let mut $tx_storage = [0; TCP_TX_BUFFER_SIZE]; + ) +} + +macro_rules! create_socket { + ($set:ident, $rx_storage:ident, $tx_storage:ident, $target:ident) => ( + let tcp_rx_buffer = TcpSocketBuffer::new(&mut $rx_storage[..]); + let tcp_tx_buffer = TcpSocketBuffer::new(&mut $tx_storage[..]); + let tcp_socket = TcpSocket::new(tcp_rx_buffer, tcp_tx_buffer); + let $target = $set.add(tcp_socket); + ) +} + fn main() { // Enable the FPU unsafe { @@ -132,17 +159,95 @@ fn main_with_fpu() { |_|\___/|_| |_| .__/ \__,_|_|\_\ | | |_| -Ready."#); +"#); + + let hardware_addr = EthernetAddress(board::get_mac_address()); + let mut protocol_addrs = [IpAddress::v4(192, 168, 69, 1)]; + println!("MAC {} IP {}", hardware_addr, protocol_addrs[0]); + let mut arp_cache_entries: [_; 8] = Default::default(); + let mut arp_cache = SliceArpCache::new(&mut arp_cache_entries[..]); + ethmac::init(hardware_addr.0); + let mut device = ethmac::EthernetDevice; + let mut iface = EthernetInterface::new( + &mut device, &mut arp_cache as &mut ArpCache, + hardware_addr, &mut protocol_addrs[..]); + + create_socket_storage!(tcp_rx_storage0, tcp_tx_storage0); + create_socket_storage!(tcp_rx_storage1, tcp_tx_storage1); + create_socket_storage!(tcp_rx_storage2, tcp_tx_storage2); + create_socket_storage!(tcp_rx_storage3, tcp_tx_storage3); + create_socket_storage!(tcp_rx_storage4, tcp_tx_storage4); + create_socket_storage!(tcp_rx_storage5, tcp_tx_storage5); + create_socket_storage!(tcp_rx_storage6, tcp_tx_storage6); + create_socket_storage!(tcp_rx_storage7, tcp_tx_storage7); + + let mut socket_set_entries: [_; 8] = Default::default(); + let mut sockets = SocketSet::new(&mut socket_set_entries[..]); + + create_socket!(sockets, tcp_rx_storage0, tcp_tx_storage0, tcp_handle0); + create_socket!(sockets, tcp_rx_storage1, tcp_tx_storage1, tcp_handle1); + create_socket!(sockets, tcp_rx_storage2, tcp_tx_storage2, tcp_handle2); + create_socket!(sockets, tcp_rx_storage3, tcp_tx_storage3, tcp_handle3); + create_socket!(sockets, tcp_rx_storage4, tcp_tx_storage4, tcp_handle4); + create_socket!(sockets, tcp_rx_storage5, tcp_tx_storage5, tcp_handle5); + create_socket!(sockets, tcp_rx_storage6, tcp_tx_storage6, tcp_handle6); + create_socket!(sockets, tcp_rx_storage7, tcp_tx_storage7, tcp_handle7); + + let mut sessions = [ + (http::Request::new(), tcp_handle0), + (http::Request::new(), tcp_handle1), + (http::Request::new(), tcp_handle2), + (http::Request::new(), tcp_handle3), + (http::Request::new(), tcp_handle4), + (http::Request::new(), tcp_handle5), + (http::Request::new(), tcp_handle6), + (http::Request::new(), tcp_handle7), + ]; let mut next_blink = 0; let mut next_info = 0; let mut led_state = true; let mut latch_reset_time = None; loop { - board::process_errors(); - let time = get_time(); + for &mut(ref mut request, ref tcp_handle) in sessions.iter_mut() { + let socket: &mut TcpSocket = sockets.get_mut(*tcp_handle).as_socket(); + if !socket.is_open() { + socket.listen(80).unwrap() + } + + if socket.may_recv() { + let request_status = { + let data = socket.recv(TCP_RX_BUFFER_SIZE).unwrap(); + request.input(data) + }; + match request_status { + Ok(true) => { + if socket.can_send() { + pages::serve(socket, &request); + } + request.reset(); + socket.close(); + } + Ok(false) => (), + Err(err) => { + println!("failed HTTP request: {}", err); + request.reset(); + socket.close(); + } + } + } else if socket.may_send() { + request.reset(); + socket.close(); + } + } + let timestamp_ms = 0; // TODO + match iface.poll(&mut sockets, timestamp_ms) { + Ok(()) | Err(Error::Exhausted) => (), + Err(e) => println!("poll error: {}", e) + } + if time > next_blink { led_state = !led_state; next_blink = time + 1000; @@ -170,6 +275,7 @@ Ready."#); next_info = next_info + 3000; } + board::process_errors(); if board::error_latched() { match latch_reset_time { None => { diff --git a/firmware/src/pages.rs b/firmware/src/pages.rs new file mode 100644 index 0000000..cf2a162 --- /dev/null +++ b/firmware/src/pages.rs @@ -0,0 +1,27 @@ +use core::fmt::Write; +use smoltcp::socket::TcpSocket; +use http; + +pub fn serve(output: &mut TcpSocket, request: &http::Request) { + match request.get_path().unwrap() { + b"/" => { + let data = include_str!("index.html"); + http::write_reply_header(output, 200, "text/html; charset=utf-8", false).unwrap(); + output.write_str(data).unwrap(); + }, + b"/style.css" => { + let data = include_bytes!("style.css.gz"); + http::write_reply_header(output, 200, "text/css", true).unwrap(); + output.send_slice(data).unwrap(); + }, + b"/logo.svg" => { + let data = include_bytes!("logo.svg.gz"); + http::write_reply_header(output, 200, "image/svg+xml", true).unwrap(); + output.send_slice(data).unwrap(); + }, + _ => { + http::write_reply_header(output, 404, "text/plain", false).unwrap(); + write!(output, "Not found").unwrap(); + } + } +} diff --git a/firmware/src/style.css.gz b/firmware/src/style.css.gz new file mode 100644 index 0000000..49da7f8 Binary files /dev/null and b/firmware/src/style.css.gz differ