forked from M-Labs/ionpak-thermostat
integrate ethmac/smoltcp (timestamp missing), add HTTP server
This commit is contained in:
parent
98f116e226
commit
648b4da9da
|
@ -320,3 +320,13 @@ pub fn init() {
|
||||||
adc0.actss.write(|w| w.asen0().bit(true));
|
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]
|
||||||
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
|
@ -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>
|
Binary file not shown.
|
@ -12,6 +12,11 @@ use cortex_m::exception::Handlers as ExceptionHandlers;
|
||||||
use cortex_m::interrupt::Mutex;
|
use cortex_m::interrupt::Mutex;
|
||||||
use tm4c129x::interrupt::Interrupt;
|
use tm4c129x::interrupt::Interrupt;
|
||||||
use tm4c129x::interrupt::Handlers as InterruptHandlers;
|
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_export]
|
||||||
macro_rules! print {
|
macro_rules! print {
|
||||||
|
@ -34,6 +39,8 @@ mod pid;
|
||||||
mod loop_anode;
|
mod loop_anode;
|
||||||
mod loop_cathode;
|
mod loop_cathode;
|
||||||
mod electrometer;
|
mod electrometer;
|
||||||
|
mod http;
|
||||||
|
mod pages;
|
||||||
|
|
||||||
static TIME: Mutex<Cell<u64>> = Mutex::new(Cell::new(0));
|
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() {
|
fn main() {
|
||||||
// Enable the FPU
|
// Enable the FPU
|
||||||
unsafe {
|
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_blink = 0;
|
||||||
let mut next_info = 0;
|
let mut next_info = 0;
|
||||||
let mut led_state = true;
|
let mut led_state = true;
|
||||||
let mut latch_reset_time = None;
|
let mut latch_reset_time = None;
|
||||||
loop {
|
loop {
|
||||||
board::process_errors();
|
|
||||||
|
|
||||||
let time = get_time();
|
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 {
|
if time > next_blink {
|
||||||
led_state = !led_state;
|
led_state = !led_state;
|
||||||
next_blink = time + 1000;
|
next_blink = time + 1000;
|
||||||
|
@ -170,6 +275,7 @@ Ready."#);
|
||||||
next_info = next_info + 3000;
|
next_info = next_info + 3000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
board::process_errors();
|
||||||
if board::error_latched() {
|
if board::error_latched() {
|
||||||
match latch_reset_time {
|
match latch_reset_time {
|
||||||
None => {
|
None => {
|
||||||
|
|
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Binary file not shown.
Loading…
Reference in New Issue