integrate ethmac/smoltcp (timestamp missing), add HTTP server

pull/1/head
Sebastien Bourdeauducq 2017-08-05 15:51:54 +08:00
parent 98f116e226
commit 648b4da9da
7 changed files with 347 additions and 3 deletions

View File

@ -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]
}

151
firmware/src/http.rs Normal file
View File

@ -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<bool, &'static str> {
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<bool, &'static str> {
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")
}

50
firmware/src/index.html Normal file
View File

@ -0,0 +1,50 @@
<!DOCTYPE html>
<html>
<title>ionpak</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css">
<body>
<!-- Sidebar -->
<div class="w3-sidebar w3-light-grey w3-bar-block" style="width:15%">
<h3 class="w3-bar-item"><img src="logo.svg"></h3>
<a href="/" class="w3-bar-item w3-button">Measure</a>
<a href="/gauge_settings.html" class="w3-bar-item w3-button">Gauge settings</a>
<a href="/network_settings.html" class="w3-bar-item w3-button">Network settings</a>
</div>
<!-- Page Content --->
<div style="margin-left:15%">
<div class="w3-container w3-teal">
<h1>Measure</h1>
</div>
<div class="w3-container">
<p>Pressure:</p>
</div>
<div class="w3-container w3-xxlarge">
2.3×10<sup>-7</sup> mbar
</div>
<div class="w3-container">
<p>
At local time:
<script language="Javascript">
var date = new Date();
var n = date.toDateString();
var time = date.toLocaleString();
document.write(time);
</script>
</p>
</div>
</div>
</body>
</html>
</body>
</html>

BIN
firmware/src/logo.svg.gz Normal file

Binary file not shown.

View File

@ -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<Cell<u64>> = 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 => {

27
firmware/src/pages.rs Normal file
View File

@ -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();
}
}
}

BIN
firmware/src/style.css.gz Normal file

Binary file not shown.