diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 9694544..43da4c0 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -9,6 +9,7 @@ members = [ "weather", "dvp-ov", "glyph-mapping", + "term-server", ] [patch.crates-io] diff --git a/rust/term-server/.gitignore b/rust/term-server/.gitignore new file mode 100644 index 0000000..aa682e9 --- /dev/null +++ b/rust/term-server/.gitignore @@ -0,0 +1,4 @@ +/target +**/*.rs.bk +# Do not check in secrets file +config.rs diff --git a/rust/term-server/Cargo.toml b/rust/term-server/Cargo.toml new file mode 100644 index 0000000..23de055 --- /dev/null +++ b/rust/term-server/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "term-server" +version = "0.1.0" +authors = ["W.J. van der Laan "] +edition = "2018" + +[dependencies] +embedded-hal = { version = "0.2.1", features = ["unproven"] } +nb = "0.1.1" +riscv-rt = "0.6.0" +k210-hal = "0.1.0" +riscv = "0.5" +k210-shared = { path = "../k210-shared" } +k210-console = { path = "../k210-console" } +esp8266at = { path = "../esp8266at", default-features = false } diff --git a/rust/term-server/README.md b/rust/term-server/README.md new file mode 100644 index 0000000..41a9e56 --- /dev/null +++ b/rust/term-server/README.md @@ -0,0 +1,15 @@ +# `weather` + +Uses the ESP8285 WiFi chip of the Maix Go to fetch weather data from +[wttr.in](https://wttr.in) and print it to the display using `k210-console`. + +As it needs to connect to an access point first, this needs configuration of one +to connect to in `src/config.rs`: + +```bash +cp src/config.rs.example src/config.rs +vim src/config.rs # ... +``` + +Set `` and `` accordingly. Do not check in `src/config.rs` ! +(gitignore settings should prevent this) diff --git a/rust/term-server/src/config.rs.example b/rust/term-server/src/config.rs.example new file mode 100644 index 0000000..66208fe --- /dev/null +++ b/rust/term-server/src/config.rs.example @@ -0,0 +1,8 @@ +/** Secrets */ + +/** Access point name */ +pub const APNAME: &str = ""; + +/** Access point password */ +pub const APPASS: &str = ""; + diff --git a/rust/term-server/src/main.rs b/rust/term-server/src/main.rs new file mode 100644 index 0000000..ab3598b --- /dev/null +++ b/rust/term-server/src/main.rs @@ -0,0 +1,231 @@ +#![allow(dead_code)] +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +#![no_std] +#![no_main] + +use core::str; +use embedded_hal::serial; +use esp8266at::handler::{NetworkEvent, SerialNetworkHandler}; +use esp8266at::response::{parse, ParseResult}; +use esp8266at::traits; +use k210_hal::Peripherals; +use k210_hal::prelude::*; +use k210_hal::stdout::Stdout; +use k210_shared::board::def::io; +use k210_shared::board::lcd::{self, LCD, LCDHL}; +use k210_shared::soc::fpioa; +use k210_shared::soc::gpio; +use k210_shared::soc::gpiohs; +use k210_shared::soc::sleep::usleep; +use k210_shared::soc::spi::SPIExt; +use k210_shared::soc::sysctl; +use nb::block; +use riscv::register::mcycle; +use riscv_rt::entry; +use k210_console::console::{Console, ScreenImage, DISP_HEIGHT, DISP_WIDTH}; + +mod config; + +const DEFAULT_BAUD: u32 = 115_200; +const TIMEOUT: usize = 390_000_000 * 40 / 115200; + +struct WriteAdapter<'a, TX> +where + TX: serial::Write, +{ + tx: &'a mut TX, +} +impl<'a, TX> WriteAdapter<'a, TX> +where + TX: serial::Write, + TX::Error: core::fmt::Debug, +{ + fn new(tx: &'a mut TX) -> Self { + Self { tx } + } +} +impl<'a, TX> traits::Write for WriteAdapter<'a, TX> +where + TX: serial::Write, + TX::Error: core::fmt::Debug, +{ + type Error = TX::Error; + + fn write_all(&mut self, buf: &[u8]) -> Result<(), Self::Error> { + for ch in buf { + block!(self.tx.write(*ch))?; + } + Ok(()) + } +} + +/** Connect pins to internal functions */ +fn io_init() { + /* Init SPI IO map and function settings */ + fpioa::set_function( + io::LCD_RST, + fpioa::function::gpiohs(lcd::RST_GPIONUM), + ); + fpioa::set_io_pull(io::LCD_RST, fpioa::pull::DOWN); // outputs must be pull-down + fpioa::set_function(io::LCD_DC, fpioa::function::gpiohs(lcd::DCX_GPIONUM)); + fpioa::set_io_pull(io::LCD_DC, fpioa::pull::DOWN); + fpioa::set_function(io::LCD_CS, fpioa::function::SPI0_SS3); + fpioa::set_function(io::LCD_WR, fpioa::function::SPI0_SCLK); + + sysctl::set_spi0_dvp_data(true); + + /* Set dvp and spi pin to 1.8V */ + sysctl::set_power_mode(sysctl::power_bank::BANK6, sysctl::io_power_mode::V18); + sysctl::set_power_mode(sysctl::power_bank::BANK7, sysctl::io_power_mode::V18); +} + +#[entry] +fn main() -> ! { + let p = Peripherals::take().unwrap(); + let clocks = k210_hal::clock::Clocks::new(); + + usleep(200000); + io_init(); + + // Configure UARTHS (→host) + let serial = p.UARTHS.configure((p.pins.pin5, p.pins.pin4), DEFAULT_BAUD.bps(), &clocks); + let (mut tx, mut _rx) = serial.split(); + let mut debug = Stdout(&mut tx); + + // Configure UART1 (→WIFI) + sysctl::clock_enable(sysctl::clock::UART1); + sysctl::reset(sysctl::reset::UART1); + fpioa::set_function(io::WIFI_EN, fpioa::function::GPIOHS8); + fpioa::set_io_pull(io::WIFI_EN, fpioa::pull::DOWN); + gpiohs::set_pin(8, true); + gpiohs::set_direction(8, gpio::direction::OUTPUT); + let wifi_serial = p.UART1.configure((p.pins.pin7, p.pins.pin6), DEFAULT_BAUD.bps(), &clocks); + let (mut wtx, mut wrx) = wifi_serial.split(); + + let mut wa = WriteAdapter::new(&mut wtx); + let mut sh = SerialNetworkHandler::new(&mut wa, config::APNAME.as_bytes(), config::APPASS.as_bytes()); + + // LCD ini + let spi = p.SPI0.constrain(); + let mut lcd = LCD::new(spi); + lcd.init(); + lcd.set_direction(lcd::direction::YX_LRUD); + let mut console: Console = Console::new(); + + writeln!(console, "\x1b[48;2;128;192;255;38;5;0m TERMINAL \x1b[0m \x1b[38;2;128;128;128m\x1b[0m").unwrap(); + + // Start off connection process state machine + sh.start(false).unwrap(); + writeln!(console, "∙ Connecting to AP").unwrap(); + + let mut serial_buf = [0u8; 8192]; + let mut ofs: usize = 0; + + loop { + if console.dirty { + let mut image: ScreenImage = [0; DISP_WIDTH * DISP_HEIGHT / 2]; + console.render(&mut image); + lcd.draw_picture(0, 0, DISP_WIDTH as u16, DISP_HEIGHT as u16, &image); + console.dirty = false; + } + + // Receive into buffer + let mut lastrecv = mcycle::read(); + while ofs < serial_buf.len() { + // Read until we stop receiving for a certain duration + // This is a hack around the fact that in the time that the parser runs, + // more than one FIFO full of characters can be received so characters could be + // lost. The right way would be to receive in an interrupt handler, but, + // we don't have that yet. + if let Ok(ch) = wrx.read() { + serial_buf[ofs] = ch; + ofs += 1; + lastrecv = mcycle::read(); + } else if (mcycle::read().wrapping_sub(lastrecv)) >= TIMEOUT { + break; + } + } + //writeln!(debug, "ofs: {} received {} chars {:?}", ofs0, ofs - ofs0, + // &serial_buf[ofs0..ofs]).unwrap(); + + // Loop as long as there's something in the buffer to parse, starting at the + // beginning + let mut start = 0; + while start < ofs { + // try parsing + let tail = &serial_buf[start..ofs]; + let erase = match parse(tail) { + ParseResult::Ok(offset, resp) => { + sh.message(&resp, |port, ev, _debug| { + match ev { + NetworkEvent::Ready => { + writeln!(console, "∙ Connected to AP").unwrap(); + port.listen(33445).unwrap(); + } + NetworkEvent::Error => { + writeln!(console, "∙ Could not connect to AP").unwrap(); + } + NetworkEvent::ListenSuccess(ip, port) => { + writeln!(console, "∙ Listening on {}.{}.{}.{}:{}", + ip[0], ip[1], ip[2], ip[3], port).unwrap(); + } + NetworkEvent::ConnectionEstablished(_link) => { + } + NetworkEvent::Data(_link, data) => { + // write!(debug, "{}", str::from_utf8(data).unwrap()); + console.puts(str::from_utf8(data).unwrap_or("???")); + } + NetworkEvent::ConnectionClosed(_link) => { + } + _ => { } + } + }, &mut debug).unwrap(); + + offset + } + ParseResult::Incomplete => { + // Incomplete, ignored, just retry after a new receive + 0 + } + ParseResult::Err => { + if tail.len() > 100 { + writeln!(debug, "err: Error([too long ...])").unwrap(); + } else { + writeln!(debug, "err: {:?}", tail).unwrap(); + } + // Erase unparseable data to next line, if line is complete + if let Some(ofs) = tail.iter().position(|&x| x == b'\n') { + ofs + 1 + } else { + // If not, retry next time + 0 + } + } + }; + + if erase == 0 { + // End of input or remainder unparseable + break; + } + start += erase; + } + // Erase everything before new starting offset + for i in start..ofs { + serial_buf[i - start] = serial_buf[i]; + } + ofs -= start; + + // If the buffer is full and we can't parse *anything*, clear it and start over + if ofs == serial_buf.len() { + writeln!(debug, "Error: buffer was unparseable, dropping buffer").unwrap(); + ofs = 0; + } + + /* + if let Ok(ch) = rx.read() { + let _res = block!(wtx.write(ch)); + } + */ + } +}