use std::fmt::{self, Display, Formatter}; use crate::{ppu::PaletteAlpha, serial::SerialDevice, warnln}; const PRINTER_PALETTE: PaletteAlpha = [ [0xff, 0xff, 0xff, 0xff], [0xaa, 0xaa, 0xaa, 0xff], [0x55, 0x55, 0x55, 0xff], [0x00, 0x00, 0x00, 0xff], ]; #[derive(Clone, Copy, PartialEq, Eq)] enum PrinterState { MagicBytes1 = 0x00, MagicBytes2 = 0x01, Identification = 0x02, Compression = 0x03, LengthLow = 0x04, LengthHigh = 0x05, Data = 0x06, ChecksumLow = 0x07, ChecksumHigh = 0x08, KeepAlive = 0x09, Status = 0x0a, Other = 0xff, } impl PrinterState { pub fn description(&self) -> &'static str { match self { PrinterState::MagicBytes1 => "Magic Bytes 1", PrinterState::MagicBytes2 => "Magic Bytes 2", PrinterState::Identification => "Identification", PrinterState::Compression => "Compression", PrinterState::LengthLow => "Length Low", PrinterState::LengthHigh => "Length High", PrinterState::Data => "Data", PrinterState::ChecksumLow => "Checksum Low", PrinterState::ChecksumHigh => "Checksum High", PrinterState::KeepAlive => "Keep Alive", PrinterState::Status => "Status", PrinterState::Other => "Other", } } fn from_u8(value: u8) -> Self { match value { 0x00 => PrinterState::MagicBytes1, 0x01 => PrinterState::MagicBytes2, 0x02 => PrinterState::Identification, 0x03 => PrinterState::Compression, 0x04 => PrinterState::LengthLow, 0x05 => PrinterState::LengthHigh, 0x06 => PrinterState::Data, 0x07 => PrinterState::ChecksumLow, 0x08 => PrinterState::ChecksumHigh, 0x09 => PrinterState::KeepAlive, 0x0a => PrinterState::Status, _ => PrinterState::Other, } } } impl Display for PrinterState { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.description()) } } #[derive(Clone, Copy, PartialEq, Eq)] enum PrinterCommand { Init = 0x01, Print = 0x02, Data = 0x04, Status = 0x0f, Other = 0xff, } impl PrinterCommand { pub fn description(&self) -> &'static str { match self { PrinterCommand::Init => "Init", PrinterCommand::Print => "Print", PrinterCommand::Data => "Data", PrinterCommand::Status => "Status", PrinterCommand::Other => "Other", } } fn from_u8(value: u8) -> Self { match value { 0x01 => PrinterCommand::Init, 0x02 => PrinterCommand::Print, 0x04 => PrinterCommand::Data, 0x0f => PrinterCommand::Status, _ => PrinterCommand::Other, } } } impl Display for PrinterCommand { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { write!(f, "{}", self.description()) } } pub struct PrinterDevice { state: PrinterState, command: PrinterCommand, compression: bool, command_length: u16, length_left: u16, checksum: u16, status: u8, byte_out: u8, data: [u8; 0x280], image: [u8; 160 * 200], image_offset: u16, callback: fn(image_buffer: &Vec<u8>), } impl PrinterDevice { pub fn new() -> Self { Self { state: PrinterState::MagicBytes1, command: PrinterCommand::Other, compression: false, command_length: 0, length_left: 0, checksum: 0x0, status: 0x0, byte_out: 0x0, data: [0x00; 0x280], image: [0x00; 160 * 200], image_offset: 0, callback: |_| {}, } } pub fn reset(&mut self) { self.state = PrinterState::MagicBytes1; self.command = PrinterCommand::Other; self.compression = false; self.command_length = 0; self.length_left = 0; self.checksum = 0x0; self.status = 0x0; self.byte_out = 0x0; self.data = [0x00; 0x280]; self.image = [0x00; 160 * 200]; self.image_offset = 0; } pub fn set_callback(&mut self, callback: fn(image_buffer: &Vec<u8>)) { self.callback = callback; } fn run_command(&mut self, command: PrinterCommand) { match command { PrinterCommand::Init => { self.status = 0x00; self.byte_out = self.status; self.image_offset = 0; } PrinterCommand::Print => { let mut image_buffer = Vec::new(); let palette_index = self.data[2]; for index in 0..self.image_offset { let value = self.image[index as usize]; let pixel_offset = (palette_index >> (value << 1)) & 0x03; let pixel = PRINTER_PALETTE[pixel_offset as usize]; image_buffer.push(pixel[0]); image_buffer.push(pixel[1]); image_buffer.push(pixel[2]); image_buffer.push(pixel[3]); } (self.callback)(&image_buffer); self.byte_out = self.status; self.status = 0x06; } PrinterCommand::Data => { if self.command_length == 0x280 { self.flush_image(); } // in case the command is of size 0 we assume this is // an EOF and we ignore this data operation else if self.command_length == 0x0 { } else { warnln!( "Printer: Wrong size for data: {:04x} bytes", self.command_length ); } self.status = 0x08; self.byte_out = self.status; } PrinterCommand::Status => { self.byte_out = self.status; // in case the current status is printing let's // mark it as done, resetting the status back to // the original value if self.status == 0x06 { self.status = 0x00; } } PrinterCommand::Other => { warnln!("Printer: Invalid command: {:02x}", self.state as u8); } } } fn flush_image(&mut self) { // sets the initial value of the index that will point to // the data that is going to be copied to the image buffer let mut index = 0; // iterates over the two rows that are going to be printed // keep in mind that the printer only allows 2 lines at each // time to be printed for _ in 0..2 { for col in 0..20 { for y in 0..8 { let mut first = self.data[index]; let mut second = self.data[index + 1]; for x in 0..8 { let offset = self.image_offset as usize + (col * 8) + (y * 160) + x; self.image[offset] = (first >> 7) | ((second >> 6) & 0x02); first <<= 1; second <<= 1; } index += 2 } } self.image_offset += 160 * 8; } } } impl SerialDevice for PrinterDevice { fn send(&mut self) -> u8 { self.byte_out } fn receive(&mut self, byte: u8) { self.byte_out = 0x00; match self.state { PrinterState::MagicBytes1 => { if byte != 0x88 { warnln!("Printer: Invalid magic byte 1: {:02x}", byte); return; } self.command = PrinterCommand::Other; self.command_length = 0; } PrinterState::MagicBytes2 => { if byte != 0x33 { if byte != 0x88 { self.state = PrinterState::MagicBytes1; } warnln!("Printer: Invalid magic byte 2: {:02x}", byte); return; } } PrinterState::Identification => self.command = PrinterCommand::from_u8(byte), PrinterState::Compression => { self.compression = byte & 0x01 == 0x01; if self.compression { warnln!("Printer: Using compressed data, currently unsupported"); } } PrinterState::LengthLow => self.length_left = byte as u16, PrinterState::LengthHigh => self.length_left |= (byte as u16) << 8, PrinterState::Data => { self.data[self.command_length as usize] = byte; self.command_length += 1; self.length_left -= 1; } PrinterState::ChecksumLow => self.checksum = byte as u16, PrinterState::ChecksumHigh => { self.checksum |= (byte as u16) << 8; self.byte_out = 0x81; } PrinterState::KeepAlive => { self.run_command(self.command); } PrinterState::Status => { self.state = PrinterState::MagicBytes1; return; } PrinterState::Other => { warnln!("Printer: Invalid state: {:02x}", self.state as u8); return; } } if self.state != PrinterState::Data { self.state = PrinterState::from_u8(self.state as u8 + 1); } if self.state == PrinterState::Data && self.length_left == 0 { self.state = PrinterState::from_u8(self.state as u8 + 1); } } fn allow_slave(&self) -> bool { false } fn description(&self) -> String { format!("Printer [{}]", self.command) } fn state(&self) -> String { self.command.to_string() } } impl Default for PrinterDevice { fn default() -> Self { Self::new() } }