minitel_stum/
lib.rs

1//! Spéfications Techniques d'Utilisation du Minitel
2//!
3//! This module defines the general constants extracted from the STUM1B specification.
4//! Reference: <https://jbellue.github.io/stum1b/>
5
6pub mod protocol;
7pub mod videotex;
8
9use crate::{
10    protocol::{
11        Baudrate, FunctionMode, Pro1, Pro2, Pro2Resp, Pro3, Pro3Resp, Protocol, Rom, RoutingRx,
12        RoutingTx,
13    },
14    videotex::{C0, C1, G2},
15};
16
17use videotex::{FunctionKey, SIChar, UserInput, G0};
18
19use std::io::{Error, ErrorKind, Result};
20
21/// Types that can be converted into a sequence of bytes in the
22/// minitel serial protocol
23pub trait IntoSequence<const N: usize> {
24    /// Sequence of bytes, including the escape sequence
25    fn sequence(self) -> [u8; N];
26}
27
28impl<T, const N: usize> IntoSequence<N> for &[T; N]
29where
30    T: Into<u8> + Copy,
31{
32    fn sequence(self) -> [u8; N] {
33        self.map(|x| x.into())
34    }
35}
36
37/// Ability to read from a minitel
38pub trait MinitelRead {
39    /// Read a sequence of bytes from the minitel, blocking
40    fn read(&mut self, data: &mut [u8]) -> Result<()>;
41}
42
43/// Ability to write to a minitel
44pub trait MinitelWrite {
45    /// Send a sequence of bytes to the minitel
46    fn send(&mut self, data: &[u8]) -> Result<()>;
47    /// Flush the serial port
48    fn flush(&mut self) -> Result<()>;
49}
50
51/// Ability to change the baudrate of the serial port
52pub trait MinitelBaudrateControl {
53    /// Change the baudrate of the serial port
54    fn set_baudrate(&mut self, baudrate: Baudrate) -> Result<()>;
55}
56
57/// Minitel interface, the entry point to the library
58///
59/// This struct wraps a serial port (websocket, physical, file, ...) and provides
60/// methods to interact with the device.
61///
62/// This struct can be initialized using the `ws_minitel`, `esp_minitel` or `esp_minitel_uart2`
63/// functions, depending on the target platform and enabled features. It can also operate on any
64/// std::io::Read and/or std::io::Write object.
65pub struct Minitel<S> {
66    pub port: S,
67}
68
69impl<S> Minitel<S> {
70    pub fn new(port: S) -> Self {
71        Self { port }
72    }
73}
74
75impl<S: MinitelRead + MinitelWrite> Minitel<S> {
76    #[inline(always)]
77    pub fn clear_screen(&mut self) -> Result<()> {
78        self.write_byte(C0::FF)
79    }
80
81    #[inline(always)]
82    pub fn read_rom(&mut self) -> Result<Rom> {
83        self.pro1(Pro1::EnqRom)?;
84        self.wait_for(C0::SOH)?;
85        let mut rom = [0; 3];
86        self.read_bytes(&mut rom)?;
87        self.expect_read(C0::EOL)?;
88        Ok(rom.into())
89    }
90
91    #[inline(always)]
92    pub fn get_pos(&mut self) -> Result<(u8, u8)> {
93        self.c1(C1::EnqCursor)?;
94        self.wait_for(C0::US)?;
95        let mut position = [0; 2];
96        self.read_bytes(&mut position)?;
97        Ok((position[1] - 0x40 - 1, position[0] - 0x40 - 1))
98    }
99
100    #[inline(always)]
101    pub fn set_function_mode(&mut self, mode: FunctionMode, enable: bool) -> Result<()> {
102        let start_stop = if enable { Pro2::Start } else { Pro2::Stop };
103        self.pro2(start_stop, mode)?;
104        let _status = self.read_pro2(Pro2Resp::RepStatus)?;
105        Ok(())
106    }
107
108    #[inline(always)]
109    pub fn set_routing(
110        &mut self,
111        enable: bool,
112        recepter: RoutingRx,
113        emitter: RoutingTx,
114    ) -> Result<()> {
115        let cmd = if enable {
116            Pro3::RoutingOn
117        } else {
118            Pro3::RoutingOff
119        };
120        self.pro3(cmd, recepter, emitter)?;
121        let (_recepter, _status) = self.read_pro3(Pro3Resp::RoutingFrom)?;
122        Ok(())
123    }
124
125    #[inline(always)]
126    pub fn get_speed(&mut self) -> Result<Baudrate> {
127        self.pro1(Pro1::EnqSpeed)?;
128        let code = self.read_pro2(Pro2Resp::QuerySpeedAnswer)?;
129        Baudrate::try_from(code).map_err(|_| ErrorKind::InvalidData.into())
130    }
131
132    #[inline(always)]
133    pub fn set_rouleau(&mut self, enable: bool) -> Result<()> {
134        self.set_function_mode(FunctionMode::Rouleau, enable)
135    }
136
137    #[inline(always)]
138    pub fn set_procedure(&mut self, enable: bool) -> Result<()> {
139        self.set_function_mode(FunctionMode::Procedure, enable)
140    }
141
142    #[inline(always)]
143    pub fn set_minuscule(&mut self, enable: bool) -> Result<()> {
144        self.set_function_mode(FunctionMode::Minuscule, enable)
145    }
146}
147
148impl<S: MinitelRead> Minitel<S> {
149    /// Read a raw sequence from the minitel
150    #[inline(always)]
151    pub fn read_bytes(&mut self, data: &mut [u8]) -> Result<()> {
152        self.port.read(data)
153    }
154
155    /// Read a single byte from the minitel
156    #[inline(always)]
157    pub fn read_byte(&mut self) -> Result<u8> {
158        let mut data = [0];
159        self.read_bytes(&mut data)?;
160        Ok(data[0])
161    }
162
163    /// Read a key stroke from the minitel assuming it is in S0 (text) mode.
164    ///
165    /// G0 and G2 characters are returned as unicode characters.
166    pub fn read_s0_stroke(&mut self) -> Result<UserInput> {
167        let b = self.read_byte()?;
168        if let Ok(g0) = G0::try_from(b) {
169            return Ok(UserInput::Char(g0.into()));
170        }
171        let c0 = C0::try_from(b).map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
172        match c0 {
173            C0::ESC => {
174                // ESC code, C1 special char
175                let c1 = C1::try_from(self.read_byte()?)
176                    .map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
177                Ok(UserInput::C1(c1))
178            }
179            C0::Sep => {
180                // SEP code, function key
181                let fct = FunctionKey::try_from(self.read_byte()?)
182                    .map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
183                Ok(UserInput::FunctionKey(fct))
184            }
185            C0::SS2 => {
186                // SS2 code, G2 char, returned as unicode char
187                let g2 = G2::try_from(self.read_byte()?)
188                    .map_err(|e| Error::new(ErrorKind::InvalidData, e))?;
189
190                if let Some(diacritics) = g2.unicode_diacritic() {
191                    // With diacritics, read one more byte for the base char
192                    let char: char = self.read_byte()?.into();
193                    let char = unicode_normalization::char::compose(char, diacritics).ok_or(
194                        Error::new(ErrorKind::InvalidData, "Invalid diacritic composition"),
195                    )?;
196                    Ok(UserInput::Char(char))
197                } else {
198                    // Without diacritic, return the char directly
199                    Ok(UserInput::Char(g2.char()))
200                }
201            }
202            _ => Ok(UserInput::C0(c0)),
203        }
204    }
205
206    #[inline(always)]
207    pub fn wait_for(&mut self, byte: impl Into<u8> + Copy) -> Result<()> {
208        for _ in 0..10 {
209            if self.read_byte()? == byte.into() {
210                return Ok(());
211            }
212        }
213        Err(ErrorKind::TimedOut.into())
214    }
215
216    #[inline(always)]
217    pub fn expect_read(&mut self, byte: impl Into<u8> + Copy) -> Result<()> {
218        let got = self.read_byte()?;
219        if got != byte.into() {
220            return Err(ErrorKind::InvalidData.into());
221        }
222        Ok(())
223    }
224
225    #[inline(always)]
226    pub fn read_pro2(&mut self, expected_ack: Pro2Resp) -> Result<u8> {
227        self.wait_for(C0::ESC)?;
228        self.expect_read(Protocol::Pro2)?;
229        self.expect_read(expected_ack)?;
230        self.read_byte()
231    }
232
233    #[inline(always)]
234    pub fn read_pro3(&mut self, expected_ack: Pro3Resp) -> Result<(u8, u8)> {
235        self.wait_for(C0::ESC)?;
236        self.expect_read(Protocol::Pro3)?;
237        self.expect_read(expected_ack)?;
238        Ok((self.read_byte()?, self.read_byte()?))
239    }
240}
241
242impl<S: MinitelWrite> Minitel<S> {
243    /// Write a raw sequence to the minitel
244    #[inline(always)]
245    pub fn write_bytes(&mut self, data: &[u8]) -> Result<()> {
246        self.port.send(data)
247    }
248
249    /// Write a single byte to the minitel
250    #[inline(always)]
251    pub fn write_byte<T: Into<u8> + Copy>(&mut self, byte: T) -> Result<()> {
252        self.write_bytes(&[byte.into()])
253    }
254
255    /// Flush the serial port
256    #[inline(always)]
257    pub fn flush(&mut self) -> Result<()> {
258        self.port.flush()
259    }
260
261    #[inline(always)]
262    pub fn write_sequence<const N: usize>(&mut self, sequence: impl IntoSequence<N>) -> Result<()> {
263        self.write_bytes(sequence.sequence().as_ref())
264    }
265
266    #[inline(always)]
267    pub fn c1(&mut self, c1: C1) -> Result<()> {
268        self.write_sequence(c1)
269    }
270
271    #[inline(always)]
272    pub fn write_g2(&mut self, g2: G2) -> Result<()> {
273        self.write_sequence(g2)
274    }
275
276    #[inline(always)]
277    pub fn show_cursor(&mut self) -> Result<()> {
278        self.write_byte(C0::Con)
279    }
280
281    #[inline(always)]
282    pub fn hide_cursor(&mut self) -> Result<()> {
283        self.write_byte(C0::Coff)
284    }
285
286    #[inline(always)]
287    pub fn set_pos(&mut self, x: u8, y: u8) -> Result<()> {
288        self.write_bytes(&[C0::US.into(), 0x40 + y, 0x40 + x + 1]) // allow access to y 0, not x 0
289    }
290
291    #[inline(always)]
292    pub fn cursor_down(&mut self) -> Result<()> {
293        self.write_bytes(&[C0::LF.into()])
294    }
295
296    #[inline(always)]
297    pub fn cursor_up(&mut self) -> Result<()> {
298        self.write_byte(C0::VT)
299    }
300
301    #[inline(always)]
302    pub fn cursor_right(&mut self) -> Result<()> {
303        self.write_byte(C0::HT)
304    }
305
306    #[inline(always)]
307    pub fn cursor_left(&mut self) -> Result<()> {
308        self.write_byte(C0::BS)
309    }
310
311    #[inline(always)]
312    pub fn start_zone(&mut self, funcs: &[C1]) -> Result<()> {
313        for func in funcs {
314            self.c1(*func)?;
315        }
316        self.zone_delimiter()?;
317        Ok(())
318    }
319
320    #[inline(always)]
321    pub fn zone_delimiter(&mut self) -> Result<()> {
322        self.write_byte(0x20)
323    }
324
325    pub fn write_char(&mut self, c: char) -> Result<()> {
326        if let Ok(c) = SIChar::try_from(c) {
327            self.si_char(c)?;
328            return Ok(());
329        }
330        Err(ErrorKind::InvalidData.into())
331    }
332
333    pub fn si_char(&mut self, c: SIChar) -> Result<()> {
334        match c {
335            SIChar::G0(g0) => self.write_byte(g0)?,
336            SIChar::G0Diacritic(g0, g2) => {
337                self.write_g2(g2)?;
338                self.write_byte(g0)?;
339            }
340            SIChar::G2(g2) => self.write_g2(g2)?,
341        }
342        Ok(())
343    }
344
345    #[inline(always)]
346    pub fn write_str(&mut self, s: &str) -> Result<()> {
347        for c in s.chars() {
348            self.write_char(c)?;
349        }
350        Ok(())
351    }
352
353    #[inline(always)]
354    pub fn writeln(&mut self, s: &str) -> Result<()> {
355        let mut s = s.to_string();
356        s.push_str("\r\n");
357        self.write_str(&s)
358    }
359
360    /// Protocol command with a single argument
361    #[inline(always)]
362    pub fn pro1(&mut self, x: Pro1) -> Result<()> {
363        self.write_bytes(&Protocol::pro1(x))?;
364        Ok(())
365    }
366
367    /// Protocol command with two arguments
368    #[inline(always)]
369    pub fn pro2(&mut self, x: Pro2, y: impl Into<u8> + Copy) -> Result<()> {
370        self.write_bytes(&Protocol::pro2(x, y))?;
371        Ok(())
372    }
373
374    /// Protocol command with three arguments
375    #[inline(always)]
376    pub fn pro3(
377        &mut self,
378        x: Pro3,
379        y: impl Into<u8> + Copy,
380        z: impl Into<u8> + Copy,
381    ) -> Result<()> {
382        self.write_bytes(&Protocol::pro3(x, y, z))?;
383        Ok(())
384    }
385}
386
387/// Ability to communicate with a minitel through a serial port with baudrate control
388impl<S: MinitelRead + MinitelWrite + MinitelBaudrateControl> Minitel<S> {
389    pub fn search_speed(&mut self) -> Result<Baudrate> {
390        for baudrate in [
391            Baudrate::B1200,
392            Baudrate::B300,
393            Baudrate::B4800,
394            Baudrate::B9600,
395        ] {
396            log::debug!("Trying baudrate: {}", baudrate);
397            self.port.flush()?;
398            self.port.set_baudrate(baudrate)?;
399            //self.port.flush()?;
400            if self.get_speed().is_ok() {
401                log::debug!("Found baudrate: {}", baudrate);
402                return Ok(baudrate);
403            }
404        }
405        Err(ErrorKind::NotFound.into())
406    }
407
408    #[inline(always)]
409    pub fn set_speed(&mut self, baudrate: Baudrate) -> Result<Baudrate> {
410        self.pro2(Pro2::Prog, baudrate.code())?;
411        self.port.flush()?;
412        self.port.set_baudrate(baudrate)?;
413
414        let speed_code = self.read_pro2(Pro2Resp::QuerySpeedAnswer)?;
415        let baudrate = Baudrate::try_from(speed_code).map_err(|_| ErrorKind::InvalidData)?;
416        Ok(baudrate)
417    }
418}
419
420impl<T> MinitelRead for T
421where
422    T: std::io::Read,
423{
424    fn read(&mut self, data: &mut [u8]) -> Result<()> {
425        self.read_exact(data)
426    }
427}
428
429impl<T> MinitelWrite for T
430where
431    T: std::io::Write,
432{
433    fn send(&mut self, data: &[u8]) -> Result<()> {
434        self.write_all(data)
435    }
436
437    fn flush(&mut self) -> Result<()> {
438        self.flush()
439    }
440}
441
442#[cfg(test)]
443mod tests {
444    use super::*;
445    #[test]
446    pub fn read_stroke() {
447        let seq: Vec<_> = "He?! ".bytes().collect();
448        let mut minitel = Minitel::new(std::io::Cursor::new(seq));
449        assert_eq!(minitel.read_s0_stroke().unwrap(), UserInput::Char('H'));
450        assert_eq!(minitel.read_s0_stroke().unwrap(), UserInput::Char('e'));
451        assert_eq!(minitel.read_s0_stroke().unwrap(), UserInput::Char('?'));
452        assert_eq!(minitel.read_s0_stroke().unwrap(), UserInput::Char('!'));
453        assert_eq!(minitel.read_s0_stroke().unwrap(), UserInput::Char(' '));
454
455        let seq: Vec<_> = vec![0x20, 0x13, 0x41, 0x13, 0x49, 0x20, 0x1B, 0x54];
456        let mut minitel = Minitel::new(std::io::Cursor::new(seq));
457        assert_eq!(minitel.read_s0_stroke().unwrap(), UserInput::Char(' '));
458        assert_eq!(
459            minitel.read_s0_stroke().unwrap(),
460            UserInput::FunctionKey(FunctionKey::Envoi)
461        );
462        assert_eq!(
463            minitel.read_s0_stroke().unwrap(),
464            UserInput::FunctionKey(FunctionKey::ConnexionFin)
465        );
466        assert_eq!(minitel.read_s0_stroke().unwrap(), UserInput::Char(' '));
467        assert_eq!(minitel.read_s0_stroke().unwrap(), UserInput::C1(C1::BgBlue));
468
469        let seq: Vec<_> = vec![0x19, 0x42, 0x65, 0x19, 0x3D]; // SS2, ', e, SS2, ½
470        let mut minitel = Minitel::new(std::io::Cursor::new(seq));
471        assert_eq!(minitel.read_s0_stroke().unwrap(), UserInput::Char('é'));
472        assert_eq!(minitel.read_s0_stroke().unwrap(), UserInput::Char('½'));
473    }
474
475    #[test]
476    pub fn write_str() {
477        let seq: Vec<u8> = Vec::new();
478        let mut minitel = Minitel::new(std::io::Cursor::new(seq));
479        minitel.write_str("Hé½").unwrap();
480        let written = minitel.port.into_inner();
481        assert_eq!(written, vec![0x48, 0x19, 0x42, 0x65, 0x19, 0x3D]); // H, SS2, ', e, SS2, ½
482    }
483}