mirror of
https://github.com/laanwj/k210-sdk-stuff.git
synced 2024-11-22 01:16:20 +04:00
rust: Add term-server demo
This commit is contained in:
parent
d4b2b6bbbd
commit
6cd6168038
@ -9,6 +9,7 @@ members = [
|
||||
"weather",
|
||||
"dvp-ov",
|
||||
"glyph-mapping",
|
||||
"term-server",
|
||||
]
|
||||
|
||||
[patch.crates-io]
|
||||
|
4
rust/term-server/.gitignore
vendored
Normal file
4
rust/term-server/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
# Do not check in secrets file
|
||||
config.rs
|
15
rust/term-server/Cargo.toml
Normal file
15
rust/term-server/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "term-server"
|
||||
version = "0.1.0"
|
||||
authors = ["W.J. van der Laan <laanwj@protonmail.com>"]
|
||||
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 }
|
15
rust/term-server/README.md
Normal file
15
rust/term-server/README.md
Normal file
@ -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 `<ap name>` and `<ap password>` accordingly. Do not check in `src/config.rs` !
|
||||
(gitignore settings should prevent this)
|
8
rust/term-server/src/config.rs.example
Normal file
8
rust/term-server/src/config.rs.example
Normal file
@ -0,0 +1,8 @@
|
||||
/** Secrets */
|
||||
|
||||
/** Access point name */
|
||||
pub const APNAME: &str = "<ap name>";
|
||||
|
||||
/** Access point password */
|
||||
pub const APPASS: &str = "<ap password>";
|
||||
|
231
rust/term-server/src/main.rs
Normal file
231
rust/term-server/src/main.rs
Normal file
@ -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<u8>,
|
||||
{
|
||||
tx: &'a mut TX,
|
||||
}
|
||||
impl<'a, TX> WriteAdapter<'a, TX>
|
||||
where
|
||||
TX: serial::Write<u8>,
|
||||
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<u8>,
|
||||
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));
|
||||
}
|
||||
*/
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user