#![doc = include_str!("../README.md")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
pub mod prelude {
pub use crate::{
AsyncMinitelBaudrateControl, AsyncMinitelRead, AsyncMinitelReadWrite,
AsyncMinitelReadWriteBaudrate, AsyncMinitelWrite,
};
}
pub mod stum;
#[cfg(feature = "axum")]
pub mod axum;
#[cfg(feature = "futures")]
pub mod futures;
#[cfg(any(feature = "esp", feature = "espdoc"))]
pub mod esp;
#[cfg(feature = "ratatui")]
pub mod ratatui;
use std::io::{Error, ErrorKind, Result};
use stum::{
protocol::{
Baudrate, FunctionMode, Pro1, Pro2, Pro2Resp, Pro3Resp, ProtocolMessage, Rom, RoutingRx,
RoutingTx,
},
videotex::{FunctionKey, UserInput, C0, C1, G0, G2},
};
pub trait MinitelMessage {
fn message(self) -> Vec<u8>;
}
#[allow(async_fn_in_trait)]
pub trait AsyncMinitelRead {
async fn read(&mut self, data: &mut [u8]) -> Result<()>;
#[inline(always)]
async fn read_byte(&mut self) -> Result<u8> {
let mut data = [0];
self.read(&mut data).await?;
Ok(data[0])
}
async fn read_s0_stroke(&mut self) -> Result<UserInput> {
let b = self.read_byte().await?;
if let Ok(g0) = G0::try_from(b) {
return Ok(UserInput::Char(g0.into()));
}
let c0 = C0::from(b);
match c0 {
C0::ESC => {
let c1 = C1::from(self.read_byte().await?);
Ok(UserInput::C1(c1))
}
C0::Sep => {
let fct = FunctionKey::from(self.read_byte().await?);
Ok(UserInput::FunctionKey(fct))
}
C0::SS2 => {
let g2 = G2::from(self.read_byte().await?);
if let Some(diacritics) = g2.unicode_diacritic() {
let char: char = self.read_byte().await?.into();
let char = unicode_normalization::char::compose(char, diacritics).ok_or(
Error::new(ErrorKind::InvalidData, "Invalid diacritic composition"),
)?;
Ok(UserInput::Char(char))
} else {
Ok(UserInput::Char(g2.char()))
}
}
_ => Ok(UserInput::C0(c0)),
}
}
#[inline(always)]
async fn wait_for(&mut self, byte: impl Into<u8> + Copy) -> Result<()> {
for _ in 0..10 {
if self.read_byte().await? == byte.into() {
return Ok(());
}
}
Err(ErrorKind::TimedOut.into())
}
#[inline(always)]
async fn expect_read(&mut self, byte: impl Into<u8> + Copy) -> Result<()> {
let got = self.read_byte().await?;
if got != byte.into() {
return Err(ErrorKind::InvalidData.into());
}
Ok(())
}
#[inline(always)]
async fn read_pro2(&mut self, expected_ack: Pro2Resp) -> Result<u8> {
self.wait_for(C0::ESC).await?;
self.expect_read(C1::Pro2).await?;
self.expect_read(expected_ack).await?;
self.read_byte().await
}
#[inline(always)]
async fn read_pro3(&mut self, expected_ack: Pro3Resp) -> Result<(u8, u8)> {
self.wait_for(C0::ESC).await?;
self.expect_read(C1::Pro3).await?;
self.expect_read(expected_ack).await?;
Ok((self.read_byte().await?, self.read_byte().await?))
}
}
#[allow(async_fn_in_trait)]
pub trait AsyncMinitelWrite {
async fn write(&mut self, data: &[u8]) -> Result<()>;
async fn flush(&mut self) -> Result<()>;
async fn send(&mut self, message: impl MinitelMessage) -> Result<()> {
self.write(&message.message()).await
}
}
#[allow(async_fn_in_trait)]
pub trait AsyncMinitelBaudrateControl {
fn set_baudrate(&mut self, baudrate: Baudrate) -> Result<()>;
fn read_byte_blocking(&mut self) -> Result<u8>;
}
#[allow(async_fn_in_trait)]
pub trait AsyncMinitelReadWrite: AsyncMinitelRead + AsyncMinitelWrite {
#[inline(always)]
async fn read_rom(&mut self) -> Result<Rom> {
self.send(ProtocolMessage::Pro1(Pro1::EnqRom)).await?;
self.wait_for(C0::SOH).await?;
let mut rom = [0; 3];
self.read(&mut rom).await?;
self.expect_read(C0::EOL).await?;
Ok(rom.into())
}
#[inline(always)]
async fn get_pos(&mut self) -> Result<(u8, u8)> {
self.send(C1::EnqCursor).await?;
self.wait_for(C0::US).await?;
let mut position = [0; 2];
self.read(&mut position).await?;
Ok((position[1] - 0x40 - 1, position[0] - 0x40 - 1))
}
#[inline(always)]
async fn set_function_mode(&mut self, mode: FunctionMode, enable: bool) -> Result<()> {
self.send(ProtocolMessage::function_mode(mode, enable))
.await?;
let _status = self.read_pro2(Pro2Resp::RepStatus).await?;
Ok(())
}
#[inline(always)]
async fn set_routing(
&mut self,
enable: bool,
recepter: RoutingRx,
emitter: RoutingTx,
) -> Result<()> {
self.send(ProtocolMessage::aiguillage(enable, emitter, recepter))
.await?;
let (_recepter, _status) = self.read_pro3(Pro3Resp::RoutingFrom).await?;
Ok(())
}
#[inline(always)]
async fn get_speed(&mut self) -> Result<Baudrate> {
self.send(ProtocolMessage::Pro1(Pro1::EnqSpeed)).await?;
let code = self.read_pro2(Pro2Resp::QuerySpeedAnswer).await?;
Baudrate::try_from(code).map_err(|_| ErrorKind::InvalidData.into())
}
}
#[allow(async_fn_in_trait)]
pub trait AsyncMinitelReadWriteBaudrate:
AsyncMinitelReadWrite + AsyncMinitelBaudrateControl
{
async fn search_speed(&mut self) -> Result<Baudrate> {
for baudrate in [
Baudrate::B1200,
Baudrate::B9600,
Baudrate::B300,
Baudrate::B4800,
] {
log::info!("Trying baudrate: {}", baudrate);
self.flush().await?;
self.set_baudrate(baudrate)?;
self.send(ProtocolMessage::Pro1(Pro1::EnqSpeed)).await?;
if let Ok(speed) = self.get_speed_blocking() {
log::info!("Found baudrate: {}", speed);
return Ok(speed);
}
}
Err(ErrorKind::NotFound.into())
}
fn get_speed_blocking(&mut self) -> Result<Baudrate> {
for _ in 0..10 {
if let Ok(C0::ESC) = self.read_byte_blocking().map(C0::from) {
if let Ok(C1::Pro2) = self.read_byte_blocking().map(C1::from) {
if let Ok(Ok(Pro2Resp::QuerySpeedAnswer)) =
self.read_byte_blocking().map(Pro2Resp::try_from)
{
let code = self.read_byte_blocking()?;
return Baudrate::try_from(code).map_err(|_| ErrorKind::InvalidData.into());
}
}
}
}
Err(ErrorKind::NotFound.into())
}
#[inline(always)]
async fn set_speed(&mut self, baudrate: Baudrate) -> Result<Baudrate> {
self.send(ProtocolMessage::Pro2(Pro2::Prog, baudrate.code()))
.await?;
self.flush().await?;
self.set_baudrate(baudrate)?;
let speed_code = self.read_pro2(Pro2Resp::QuerySpeedAnswer).await?;
let baudrate = Baudrate::try_from(speed_code).map_err(|_| ErrorKind::InvalidData)?;
Ok(baudrate)
}
}
impl<T> AsyncMinitelReadWrite for T where T: AsyncMinitelRead + AsyncMinitelWrite {}
impl<T> AsyncMinitelReadWriteBaudrate for T where
T: AsyncMinitelRead + AsyncMinitelWrite + AsyncMinitelBaudrateControl
{
}
#[cfg(test)]
#[cfg(feature = "futures")]
mod tests {
use ::futures::io::Cursor;
use stum::videotex::StringMessage;
use super::*;
#[tokio::test]
async fn read_stroke() {
let seq: Vec<_> = "He?! ".bytes().collect();
let mut minitel = Cursor::new(seq);
assert_eq!(
minitel.read_s0_stroke().await.unwrap(),
UserInput::Char('H')
);
assert_eq!(
minitel.read_s0_stroke().await.unwrap(),
UserInput::Char('e')
);
assert_eq!(
minitel.read_s0_stroke().await.unwrap(),
UserInput::Char('?')
);
assert_eq!(
minitel.read_s0_stroke().await.unwrap(),
UserInput::Char('!')
);
assert_eq!(
minitel.read_s0_stroke().await.unwrap(),
UserInput::Char(' ')
);
let seq: Vec<_> = vec![0x20, 0x13, 0x41, 0x13, 0x49, 0x20, 0x1B, 0x54];
let mut minitel = Cursor::new(seq);
assert_eq!(
minitel.read_s0_stroke().await.unwrap(),
UserInput::Char(' ')
);
assert_eq!(
minitel.read_s0_stroke().await.unwrap(),
UserInput::FunctionKey(FunctionKey::Envoi)
);
assert_eq!(
minitel.read_s0_stroke().await.unwrap(),
UserInput::FunctionKey(FunctionKey::ConnexionFin)
);
assert_eq!(
minitel.read_s0_stroke().await.unwrap(),
UserInput::Char(' ')
);
assert_eq!(
minitel.read_s0_stroke().await.unwrap(),
UserInput::C1(C1::BgBlue)
);
let seq: Vec<_> = vec![0x19, 0x42, 0x65, 0x19, 0x3D]; let mut minitel = Cursor::new(seq);
assert_eq!(
minitel.read_s0_stroke().await.unwrap(),
UserInput::Char('é')
);
assert_eq!(
minitel.read_s0_stroke().await.unwrap(),
UserInput::Char('½')
);
}
#[tokio::test]
async fn write_str() {
let seq: Vec<u8> = Vec::new();
let mut minitel = Cursor::new(seq);
minitel
.send(StringMessage("Hé½".to_string()))
.await
.unwrap();
let written = minitel.into_inner();
assert_eq!(written, vec![0x48, 0x19, 0x42, 0x65, 0x19, 0x3D]); }
}