greaseweazle/
lib.rs

1//! A Rust library for controlling a Greaseweazle from the host system.
2//!
3//! A Greaseweazle is a device that provides direct control and raw flux-level access to a floppy
4//! drive; see <https://github.com/keirf/greaseweazle/> for more information.
5//! This project is not maintained by Keir Fraser and is not affiliated with the official
6//! Greaseweazle project in any way.
7//!
8//! # Usage
9//!
10//! The [`Greaseweazle`] object represents a connection to the Greaseweazle. When you create it,
11//! it will establish a serial connection to the device. You can then call its methods to send
12//! commands.
13//!
14//! # Example
15//!
16//! ```no_run
17//! use greaseweazle::Greaseweazle;
18//!
19//! let port_infos = greaseweazle::enumerate()?;
20//! let mut gw = Greaseweazle::new(&port_infos[0].port_name).unwrap();
21//! let info = gw.get_firmware_info().unwrap();
22//! println!("{info:?}");
23//! Ok::<(), greaseweazle::CommandError>(())
24//! ```
25
26use byteorder::{LE, ReadBytesExt, WriteBytesExt as _};
27use num_enum::{FromPrimitive, IntoPrimitive};
28use serialport::{ClearBuffer, SerialPort, SerialPortInfo, SerialPortType, UsbPortInfo};
29use std::{
30    borrow::Cow,
31    error::Error,
32    fmt,
33    io::{self, BufRead, BufReader, Read, Write as _},
34    time::Duration,
35};
36pub use {config::*, flux::*};
37
38mod config;
39mod flux;
40
41/// The connection to the Greaseweazle device.
42///
43/// The Greaseweazle does not handle multiple connections, and creating more than one
44/// `Greaseweazle` object for the same device will probably result in errors.
45#[derive(Debug)]
46pub struct Greaseweazle {
47    serial: BufReader<Box<dyn SerialPort>>,
48    write_buf: Vec<u8>,
49}
50
51impl Greaseweazle {
52    /// Creates a new connection to the Greaseweazle device, using the serial port located at the
53    /// given `serial_path`.
54    ///
55    /// This does not check whether the device connected to the serial port is in fact a
56    /// Greaseweazle. You should call [`get_firmware_info`](Self::get_firmware_info) after creating
57    /// a connection, to ensure that the device accepts Greaseweazle commands and responds as
58    /// expected.
59    pub fn new<'a>(serial_path: impl Into<Cow<'a, str>>) -> Result<Self, io::Error> {
60        let mut serial = serialport::new(serial_path, BaudRate::ClearComms.into())
61            .timeout(Duration::from_secs(60))
62            .dtr_on_open(true)
63            .open()?;
64
65        serial.set_baud_rate(BaudRate::Normal.into())?;
66        serial.clear(ClearBuffer::All)?;
67        serial.write_request_to_send(true)?;
68
69        Ok(Self {
70            serial: BufReader::new(serial),
71            write_buf: Vec::with_capacity(1024),
72        })
73    }
74
75    /// Returns a reference to the internal serial port.
76    #[inline]
77    pub fn serial(&self) -> &dyn SerialPort {
78        self.serial.get_ref().as_ref()
79    }
80
81    /// Returns a mutable reference to the internal serial port. This can be used for raw
82    /// communication with the Greaseweazle.
83    ///
84    /// You can send invalid data this way, which may disrupt future commands and require creating
85    /// a new connection. Use with care.
86    #[inline]
87    pub fn serial_mut(&mut self) -> &mut dyn SerialPort {
88        self.serial.get_mut().as_mut()
89    }
90
91    /// Resets the Greaseweazle to its default state when first powered on.
92    pub fn reset(&mut self) -> Result<(), CommandError> {
93        self.send_command(Command::Reset, |_write| Ok(()))
94    }
95
96    // *** Configuration ***
97
98    /// Returns information about the hardware and firmware of the Greaseweazle device.
99    pub fn get_firmware_info(&mut self) -> Result<FirmwareInfo, CommandError> {
100        self.send_command(Command::GetInfo, |write| {
101            write.write_u8(GetInfo::Firmware.into())?;
102            Ok(())
103        })?;
104
105        let mut buf = [0; 32];
106        self.serial.read_exact(&mut buf)?;
107        FirmwareInfo::read_from(buf.as_slice()).map_err(Into::into)
108    }
109
110    /// Sets the floppy bus type for future commands.
111    pub fn set_bus_type(&mut self, bus_type: BusType) -> Result<(), CommandError> {
112        self.send_command(Command::SetBusType, |write| {
113            write.write_u8(bus_type.into())?;
114            Ok(())
115        })
116    }
117
118    /// Returns information about the specified drive, or the currently selected drive if
119    /// not specified.
120    ///
121    /// You must have called [`set_bus_type`](Self::set_bus_type) first, and `drive_num` must be
122    /// a valid drive number for the selected bus type. If `drive_num` is `None`, you must also
123    /// have called [`select_drive`](Self::select_drive) first.
124    pub fn get_drive_info(&mut self, drive_num: Option<u8>) -> Result<DriveInfo, CommandError> {
125        if let Some(drive_num) = drive_num {
126            let code = u8::from(GetInfo::Drive)
127                .checked_add(drive_num)
128                .ok_or(GreaseweazleError::InvalidDriveNumber)?;
129            self.send_command(Command::GetInfo, |write| {
130                write.write_u8(code)?;
131                Ok(())
132            })?;
133        } else {
134            self.send_command(Command::GetInfo, |write| {
135                write.write_u8(GetInfo::CurrentDrive.into())?;
136                Ok(())
137            })?;
138        }
139
140        let mut buf = [0; 32];
141        self.serial.read_exact(&mut buf)?;
142        DriveInfo::read_from(buf.as_slice()).map_err(Into::into)
143    }
144
145    /// Returns the current delay/timing settings.
146    pub fn get_delays(&mut self) -> Result<Delays, CommandError> {
147        let mut value_count = 8;
148
149        // Older firmware does not support all values, so if an error is returned,
150        // incrementally ask for fewer values.
151        while let Err(err) = self.send_command(Command::GetParams, |write| {
152            write.write_u8(Param::Delays.into())?;
153            write.write_u8(value_count * 2)?;
154            Ok(())
155        }) {
156            if value_count > 5
157                && matches!(
158                    err,
159                    CommandError::GreaseweazleError(GreaseweazleError::BadCommand)
160                )
161            {
162                value_count -= 1;
163            } else {
164                return Err(err);
165            }
166        }
167
168        Ok(Delays {
169            select_us: self.serial.read_u16::<LE>()?,
170            step_us: self.serial.read_u16::<LE>()?,
171            seek_settle_ms: self.serial.read_u16::<LE>()?,
172            motor_ms: self.serial.read_u16::<LE>()?,
173            watchdog_ms: self.serial.read_u16::<LE>()?,
174            pre_write_us: if value_count >= 6 {
175                Some(self.serial.read_u16::<LE>()?)
176            } else {
177                None
178            },
179            post_write_us: if value_count >= 7 {
180                Some(self.serial.read_u16::<LE>()?)
181            } else {
182                None
183            },
184            index_mask_us: if value_count >= 8 {
185                Some(self.serial.read_u16::<LE>()?)
186            } else {
187                None
188            },
189        })
190    }
191
192    /// Sets new delay/timing settings.
193    pub fn set_delays(&mut self, delays: Delays) -> Result<(), CommandError> {
194        self.send_command(Command::SetParams, |write| {
195            write.write_u8(Param::Delays.into())?;
196
197            let Delays {
198                select_us: select,
199                step_us: step,
200                seek_settle_ms: seek_settle,
201                motor_ms: motor,
202                watchdog_ms: watchdog,
203                pre_write_us: pre_write,
204                post_write_us: post_write,
205                index_mask_us: index_mask,
206            } = delays;
207
208            write.write_u16::<LE>(select)?;
209            write.write_u16::<LE>(step)?;
210            write.write_u16::<LE>(seek_settle)?;
211            write.write_u16::<LE>(motor)?;
212            write.write_u16::<LE>(watchdog)?;
213
214            let Some(pre_write) = pre_write else {
215                return Ok(());
216            };
217            write.write_u16::<LE>(pre_write)?;
218
219            let Some(post_write) = post_write else {
220                return Ok(());
221            };
222            write.write_u16::<LE>(post_write)?;
223
224            let Some(index_mask) = index_mask else {
225                return Ok(());
226            };
227            write.write_u16::<LE>(index_mask)?;
228
229            Ok(())
230        })
231    }
232
233    // *** Control ***
234
235    /// Returns whether a pin on the floppy interface is high.
236    pub fn get_pin(&mut self, pin: Pin) -> Result<PinLevel, CommandError> {
237        self.send_command(Command::GetPin, |write| {
238            write.write_u8(pin.into())?;
239            Ok(())
240        })?;
241
242        Ok(self.serial.read_u8()?.into())
243    }
244
245    /// Sets the level of a pin on the floppy interface.
246    pub fn set_pin(&mut self, pin: Pin, level: PinLevel) -> Result<(), CommandError> {
247        self.send_command(Command::SetPin, |write| {
248            write.write_u8(pin.into())?;
249            write.write_u8(level.into())?;
250            Ok(())
251        })
252    }
253
254    /// Selects the drive that future commands should be sent to. If `drive_num` is `None`,
255    /// deselects all drives.
256    ///
257    /// You must have called [`set_bus_type`](Self::set_bus_type) first, and `drive_num` must be
258    /// a valid drive number for the selected bus type.
259    pub fn select_drive(&mut self, drive_num: Option<u8>) -> Result<(), CommandError> {
260        if let Some(drive_num) = drive_num {
261            self.send_command(Command::Select, |write| {
262                write.write_u8(drive_num)?;
263                Ok(())
264            })
265        } else {
266            self.send_command(Command::Deselect, |_write| Ok(()))
267        }
268    }
269
270    /// Selects which head on the drive should be used for future read/write commands.
271    ///
272    /// Head `0` is the bottom head, head `1` is the top head. Other values will probably result in
273    /// an error.
274    pub fn select_head(&mut self, head: u8) -> Result<(), CommandError> {
275        self.send_command(Command::Head, |write| {
276            write.write_u8(head)?;
277            Ok(())
278        })
279    }
280
281    /// Sets the state of the drive motor for the drive.
282    ///
283    /// You must have called [`set_bus_type`](Self::set_bus_type) first, and `drive_num` must be
284    /// a valid drive number for the selected bus type.
285    pub fn set_drive_motor(&mut self, drive_num: u8, on: bool) -> Result<(), CommandError> {
286        self.send_command(Command::Motor, |write| {
287            write.write_u8(drive_num)?;
288            write.write_u8(on.into())?;
289            Ok(())
290        })
291    }
292
293    /// Seeks the head to the specified cylinder.
294    ///
295    /// You must have called [`set_bus_type`](Self::set_bus_type) and
296    /// [`select_drive`](Self::select_drive) first. Negative numbers for `cylinder` are supported
297    /// for "flippy" drives. For normal drives, you should use only non-negative values.
298    ///
299    /// After seeking, you can call [`get_pin`](Self::get_pin) with [`Pin::Track0`] to ensure that
300    /// the track 0 indicator has the expected state.
301    ///
302    /// # Notes on Greaseweazle record-keeping
303    ///
304    /// The Greaseweazle internally keeps a record of the cylinder that the head is currently on,
305    /// and updates it when seeking. However, this is not perfect, and can be disrupted if you seek
306    /// to an invalid cylinder (beyond the capabilities of the drive), if the Greaseweazle loses
307    /// contact with the drive, or if the drive seeks on its own without being told to
308    /// (some drives do this when first powered on).
309    ///
310    /// When first powered on, the Greaseweazle will always seek down on each drive until the
311    /// drive indicates track 0. If, during operation, the Greaseweazle has lost track of
312    /// the head position, you can call [`reset`](Self::reset) to let it recalibrate.
313    pub fn seek(&mut self, cylinder: i16) -> Result<(), CommandError> {
314        if let Ok(cylinder) = i8::try_from(cylinder) {
315            self.send_command(Command::Seek, |write| {
316                write.write_i8(cylinder)?;
317                Ok(())
318            })
319        } else {
320            self.send_command(Command::Seek, |write| {
321                write.write_i16::<LE>(cylinder)?;
322                Ok(())
323            })
324        }
325    }
326
327    /// Seeks the head to cylinder 0, then attempts to step down by one cylinder.
328    ///
329    /// For most drives, this is equivalent to simply seeking to cylinder 0 directly, as stepping
330    /// down from cylinder 0 is not possible. However, some flippy-modded drives erroneously do not
331    /// indicate track 0 when they step upwards from cylinder -1. This command exists to correct
332    /// for those.
333    pub fn no_click_step(&mut self) -> Result<(), CommandError> {
334        self.send_command(Command::NoClickStep, |_write| Ok(()))
335    }
336
337    // *** Flux ***
338
339    /// Reads flux from the currently selected drive, head and cylinder.
340    /// The drive motor must be on for this to work.
341    ///
342    /// Reading continues for the specified duration or the specified number of index pulses,
343    /// whichever limit is reached first.
344    /// Specifying `0` for either of these values means no limit is set.
345    pub fn read_flux(
346        &mut self,
347        duration: Ticks,
348        indexes: u16,
349    ) -> Result<EncodedFlux, CommandError> {
350        self.send_command(Command::ReadFlux, |write| {
351            write.write_u32::<LE>(duration.into())?;
352            write.write_u16::<LE>(indexes)?;
353            Ok(())
354        })?;
355
356        let mut buf = Vec::new();
357        self.serial.read_until(0, &mut buf)?;
358        self.send_command(Command::GetFluxStatus, |_write| Ok(()))?;
359
360        Ok(EncodedFlux::from_encoded_bytes(buf))
361    }
362
363    /// Writes flux to the currently selected drive, head and cylinder.
364    /// The drive motor must be on for this to work.
365    ///
366    /// If `start_at_index` is `true`, writing starts when the next index pulse occurs,
367    /// otherwise writing starts immediately. Writing ends when all the provided flux is written.
368    /// If `stop_at_index` is `true`, writing ends earlier if an index pulse occurs first, and
369    /// the remaining flux is discarded.
370    ///
371    /// `hard_sector_period`, if not `0`, specifies that the disk is hard-sectored, and the
372    /// Greaseweazle should expect sector pulses at roughly this interval.
373    /// This helps the Greaseweazle distinguish between sector holes and index holes.
374    pub fn write_flux(
375        &mut self,
376        flux: &EncodedFlux,
377        start_at_index: bool,
378        stop_at_index: bool,
379        hard_sector_period: Ticks,
380    ) -> Result<(), CommandError> {
381        if hard_sector_period.0 == 0 {
382            self.send_command(Command::WriteFlux, |write| {
383                write.write_u8(start_at_index.into())?;
384                write.write_u8(stop_at_index.into())?;
385                Ok(())
386            })?;
387        } else {
388            self.send_command(Command::WriteFlux, |write| {
389                write.write_u8(start_at_index.into())?;
390                write.write_u8(stop_at_index.into())?;
391                write.write_u32::<LE>(hard_sector_period.into())?;
392                Ok(())
393            })?;
394        }
395
396        self.serial.get_mut().write_all(flux.as_bytes())?;
397        self.serial.read_u8()?; // wait for Greaseweazle to finish
398        self.send_command(Command::GetFluxStatus, |_write| Ok(()))?;
399
400        Ok(())
401    }
402
403    /// Erases flux on the currently selected drive, head and cylinder, for the specified duration.
404    /// The drive motor must be on for this to work.
405    pub fn erase_flux(&mut self, duration: Ticks) -> Result<(), CommandError> {
406        self.send_command(Command::EraseFlux, |write| {
407            write.write_u32::<LE>(duration.into())?;
408            Ok(())
409        })?;
410
411        self.serial.read_u8()?; // wait for Greaseweazle to finish
412        self.send_command(Command::GetFluxStatus, |_write| Ok(()))?;
413
414        Ok(())
415    }
416
417    // *** Bandwidth ***
418
419    /// Receives randomly generated bytes from the Greaseweazle.
420    ///
421    /// This is used for the [`get_bandwidth_stats`](Self::get_bandwidth_stats) command.
422    pub fn source_bytes(&mut self, buf: &mut [u8], seed: u32) -> Result<(), CommandError> {
423        self.send_command(Command::SourceBytes, |write| {
424            write.write_u32::<LE>(buf.len().try_into().expect("buffer was too long"))?;
425            write.write_u32::<LE>(seed)?;
426            Ok(())
427        })?;
428
429        self.serial.read_exact(buf).map_err(Into::into)
430    }
431
432    /// Sends randomly generated bytes to the Greaseweazle.
433    ///
434    /// This is used for the [`get_bandwidth_stats`](Self::get_bandwidth_stats) command.
435    pub fn sink_bytes(&mut self, buf: &[u8], seed: u32) -> Result<(), CommandError> {
436        self.send_command(Command::SinkBytes, |write| {
437            write.write_u32::<LE>(buf.len().try_into().expect("buffer was too long"))?;
438            write.write_u32::<LE>(seed)?;
439            Ok(())
440        })?;
441
442        self.serial.get_mut().write_all(buf)?;
443        let ack_code = self.serial.read_u8()?;
444
445        if ack_code != 0 {
446            return Err(GreaseweazleError::from(ack_code).into());
447        }
448
449        Ok(())
450    }
451
452    /// Gets bandwidth statistics after calling [`source_bytes`](Self::source_bytes) and/or
453    /// [`sink_bytes`](Self::sink_bytes).
454    pub fn get_bandwidth_stats(&mut self) -> Result<BandwidthStats, CommandError> {
455        self.send_command(Command::GetInfo, |write| {
456            write.write_u8(GetInfo::BandwidthStats.into())?;
457            Ok(())
458        })?;
459
460        let mut buf = [0; 32];
461        self.serial.read_exact(&mut buf)?;
462        BandwidthStats::read_from(buf.as_slice()).map_err(Into::into)
463    }
464
465    fn send_command(
466        &mut self,
467        command: Command,
468        write_data: impl FnOnce(&mut Vec<u8>) -> Result<(), io::Error>,
469    ) -> Result<(), CommandError> {
470        let sent_command = u8::from(command);
471
472        self.write_buf.clear();
473        self.write_buf.write_u8(sent_command)?;
474        self.write_buf.write_u8(0)?;
475        write_data(&mut self.write_buf)?;
476        self.write_buf[1] = self
477            .write_buf
478            .len()
479            .try_into()
480            .expect("command was too long");
481
482        let serial = self.serial.get_mut();
483        serial.write_all(&self.write_buf)?;
484        serial.flush()?;
485
486        #[cfg(feature = "log")]
487        log::trace!("Sent command `{command:?}` with data: {:?}", self.write_buf);
488
489        let mut response = [0; 2];
490        self.serial.read_exact(&mut response)?;
491        let mut response = response.as_slice();
492        let received_command = response.read_u8()?;
493
494        let result = match response.read_u8()? {
495            0 => {
496                #[cfg(feature = "log")]
497                log::trace!("Received command `{received_command:?}` response `Ok`");
498                Ok(())
499            }
500            code => {
501                let err = GreaseweazleError::from(code);
502                #[cfg(feature = "log")]
503                log::trace!("Received command `{received_command:?}` response `{err:?}`");
504                Err(err.into())
505            }
506        };
507
508        if received_command != sent_command {
509            return Err(CommandError::ResponseCommandMismatch {
510                sent_command,
511                received_command,
512            });
513        }
514
515        result
516    }
517}
518
519/// Error that can occur when executing a Greaseweazle command.
520#[derive(Debug)]
521#[non_exhaustive]
522pub enum CommandError {
523    /// An I/O error occurred while sending/receiving data.
524    IoError(io::Error),
525
526    /// The Greaseweazle responded with an error code.
527    GreaseweazleError(GreaseweazleError),
528
529    /// The command code returned by the Greaseweazle does not match the code of the command that
530    /// was sent.
531    ///
532    /// This may indicate that the communication with the Greaseweazle has gone out of sync,
533    /// and requires recreating the connection. It may also happen if the connected device is not
534    /// actually a Greaseweazle.
535    ResponseCommandMismatch {
536        /// The command code that was sent to the Greaseweazle.
537        sent_command: u8,
538
539        /// The command code that was received back from the Greaseweazle.
540        received_command: u8,
541    },
542}
543
544impl fmt::Display for CommandError {
545    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
546        match self {
547            Self::IoError(_) => {
548                write!(f, "an I/O error occurred while sending/receiving data")
549            }
550            Self::GreaseweazleError(_) => {
551                write!(f, "the Greaseweazle responded with an error code")
552            }
553            Self::ResponseCommandMismatch { .. } => {
554                write!(
555                    f,
556                    "the command code returned by the Greaseweazle does not match the code of the \
557                    command that was sent."
558                )
559            }
560        }
561    }
562}
563
564impl Error for CommandError {
565    fn source(&self) -> Option<&(dyn Error + 'static)> {
566        match self {
567            Self::IoError(error) => Some(error),
568            Self::GreaseweazleError(error) => Some(error),
569            Self::ResponseCommandMismatch { .. } => None,
570        }
571    }
572}
573
574impl From<io::Error> for CommandError {
575    fn from(value: io::Error) -> Self {
576        Self::IoError(value)
577    }
578}
579
580impl From<GreaseweazleError> for CommandError {
581    fn from(value: GreaseweazleError) -> Self {
582        Self::GreaseweazleError(value)
583    }
584}
585
586/// An error code that can be returned by the Greaseweazle.
587#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
588#[non_exhaustive]
589#[repr(u8)]
590pub enum GreaseweazleError {
591    /// Bad command.
592    BadCommand = 1,
593
594    /// No index.
595    NoIndex = 2,
596
597    /// Track 0 not found.
598    NoTrack0 = 3,
599
600    /// Flux overflow.
601    FluxOverflow = 4,
602
603    /// Flux underflow.
604    FluxUnderflow = 5,
605
606    /// Disk is write protected.
607    WriteProtected = 6,
608
609    /// No drive selected
610    /// (using [`select_drive`](Greaseweazle::select_drive)).
611    NoDriveSelected = 7,
612
613    /// No bus type selected
614    /// (using [`set_bus_type`](Greaseweazle::set_bus_type)).
615    NoBusTypeSelected = 8,
616
617    /// Invalid drive number.
618    InvalidDriveNumber = 9,
619
620    /// Invalid pin.
621    InvalidPin = 10,
622
623    /// Invalid cylinder.
624    InvalidCylinder = 11,
625
626    /// Out of SRAM.
627    OutOfSram = 12,
628
629    /// Out of flash.
630    OutOfFlash = 13,
631
632    /// Unknown error.
633    #[num_enum(catch_all)]
634    Unknown(u8),
635}
636
637/// Returns a list of all Greaseweazle devices that can be found on the system.
638///
639/// # Linux note
640///
641/// On Linux, this uses standard system APIs, but may give better results by using the `udev`
642/// library. To use it, enable the `libudev` feature on this crate.
643pub fn enumerate() -> Result<Vec<SerialPortInfo>, io::Error> {
644    let mut infos = serialport::available_ports()?;
645
646    infos.retain(|info| {
647        let SerialPortType::UsbPort(info) = &info.port_type else {
648            return false;
649        };
650
651        let &UsbPortInfo {
652            vid,
653            pid,
654            ref serial_number,
655            ref manufacturer,
656            ref product,
657        } = info;
658
659        if vid == 0x1209 {
660            if pid == 0x4d69 {
661                return true;
662            }
663
664            if pid == 0x0001
665                && serial_number
666                    .as_deref()
667                    .is_some_and(|serial_number| serial_number.starts_with("GW"))
668            {
669                return true;
670            }
671        }
672
673        if let Some(product) = product {
674            if product == "Greaseweazle" && manufacturer.as_deref() == Some("Keir Fraser") {
675                return true;
676            }
677
678            if product.to_ascii_lowercase().contains("gw-compat") {
679                return true;
680            }
681        }
682
683        false
684    });
685
686    Ok(infos)
687}
688
689/// A time duration expressed in Greaseweazle sample ticks.
690///
691/// A sample tick is a time unit equal to the period of a sample.
692/// There are [`FirmwareInfo::sample_freq`] sample ticks in a second, and one sample tick is
693/// `1.0 / sample_freq` seconds.
694#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
695#[repr(transparent)]
696pub struct Ticks(pub u32);
697
698impl Ticks {
699    /// Converts `duration` to sample ticks, with the provided sample frequency.
700    ///
701    /// Panics if overflow occurs.
702    #[inline]
703    pub fn from_duration(duration: Duration, sample_freq: u32) -> Self {
704        Self::try_from_duration(duration, sample_freq).expect("overflow occurred")
705    }
706
707    /// Tries to convert `duration` to sample ticks, with the provided sample frequency.
708    ///
709    /// Returns `None` if the value is too big to fit in a `u32`. The conversion is lossless if
710    /// `duration` is an exact multiple of the tick duration for `sample_freq`.
711    #[inline]
712    pub fn try_from_duration(duration: Duration, sample_freq: u32) -> Option<Self> {
713        Some(Self(
714            u32::try_from(duration.saturating_mul(sample_freq).as_secs()).ok()?,
715        ))
716    }
717
718    /// Converts sample ticks to a `Duration`, with the provided sample frequency.
719    ///
720    /// Rounding errors may occur during division.
721    #[inline]
722    pub fn to_duration(self, sample_freq: u32) -> Duration {
723        Duration::from_secs(self.0.into()) / sample_freq
724    }
725}
726
727impl From<u32> for Ticks {
728    #[inline]
729    fn from(value: u32) -> Self {
730        Self(value)
731    }
732}
733
734impl From<Ticks> for u32 {
735    #[inline]
736    fn from(value: Ticks) -> Self {
737        value.0
738    }
739}
740
741impl fmt::Display for GreaseweazleError {
742    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
743        match self {
744            Self::BadCommand => write!(f, "bad command"),
745            Self::NoIndex => write!(f, "no index"),
746            Self::NoTrack0 => write!(f, "track 0 not found"),
747            Self::FluxOverflow => write!(f, "flux overflow"),
748            Self::FluxUnderflow => write!(f, "flux underflow"),
749            Self::WriteProtected => write!(f, "disk is write protected"),
750            Self::NoDriveSelected => write!(f, "no drive selected"),
751            Self::NoBusTypeSelected => write!(f, "no bus type selected"),
752            Self::InvalidDriveNumber => write!(f, "invalid drive number"),
753            Self::InvalidPin => write!(f, "invalid pin"),
754            Self::InvalidCylinder => write!(f, "invalid cylinder"),
755            Self::OutOfSram => write!(f, "out of SRAM"),
756            Self::OutOfFlash => write!(f, "out of flash"),
757            Self::Unknown(code) => write!(f, "unknown error {code}"),
758        }
759    }
760}
761
762impl Error for GreaseweazleError {}
763
764/// A pin on the floppy interface of the Greaseweazle.
765///
766/// This type contains only pins that can be used with [`get_pin`](Greaseweazle::get_pin) and
767/// [`set_pin`](Greaseweazle::set_pin). Some pins have two or more incompatible functions,
768/// depending on the drive and how it's configured, and there may be more obscure drives with yet
769/// other functions not mentioned here.
770#[derive(Debug, Clone, Copy, FromPrimitive, IntoPrimitive)]
771#[non_exhaustive]
772#[repr(u8)]
773pub enum Pin {
774    /// Can be set.
775    ///
776    /// - **Density select**:
777    ///   - [`Low`] = High density.
778    ///   - [`High`] = Low density.
779    /// - **Head load**: This function is used on some early floppy drives.
780    ///   - [`Low`] = The head is pressed down onto the disk, ready for reading/writing.
781    ///   - [`High`] = The head is released from the disk.
782    ///
783    /// [`Low`]: PinLevel::Low
784    /// [`High`]: PinLevel::High
785    DensitySelect = 2,
786
787    /// Can be set.
788    ///
789    /// - **Head load**:
790    ///   - [`Low`] = The head is pressed down onto the disk, ready for reading/writing.
791    ///   - [`High`] = The head is released from the disk.
792    /// - **In use**:
793    ///   - [`Low`] = The drive is prepared for reading/writing.
794    ///     Some drives may lock the door/eject mechanism, but on others, this signal only makes
795    ///     the drive light turn on.
796    ///
797    /// [`Low`]: PinLevel::Low
798    /// [`High`]: PinLevel::High
799    #[allow(non_camel_case_types)]
800    HeadLoad_InUse = 4,
801
802    /// Can be set.
803    ///
804    /// - **Drive select 3**: This function is used on most floppy drives, but is not present on
805    ///   the IBM PC floppy controller.
806    ///   - [`Low`] = Selects drive 3. This probably should not be used with the Greaseweazle.
807    /// - **Ready**: This function is used on a few early floppy drives.
808    ///   - [`Low`] = Disk is inserted and spinning.
809    ///
810    /// [`Low`]: PinLevel::Low
811    #[allow(non_camel_case_types)]
812    DriveSelect3 = 6,
813
814    /// Read-only, cannot be set.
815    ///
816    /// - **Index**:
817    ///   - [`Low`] = The index hole is over the index sensor.
818    ///
819    /// [`Low`]: PinLevel::Low
820    Index = 8,
821
822    /// Read-only, cannot be set.
823    ///
824    /// - **Track 0**:
825    ///   - [`Low`] = The head is positioned above cylinder 0.
826    ///
827    /// [`Low`]: PinLevel::Low
828    Track0 = 26,
829
830    /// Read-only, cannot be set.
831    ///
832    /// - **Write protect**:
833    ///   - [`Low`] = The disk is write-protected.
834    ///
835    /// [`Low`]: PinLevel::Low
836    WriteProtect = 28,
837
838    /// Read-only, cannot be set.
839    ///
840    /// - **Disk change**: This function is used on the IBM PC.
841    ///   - [`Low`] = Disk has been changed since the last access.
842    /// - **Ready**: This function is used on some non-IBM PC drives.
843    ///   - [`Low`] = The disk is inserted and spinning, ready to load the head.
844    /// - **In use**: This function is used on a few early floppy drives.
845    ///   - [`Low`] = The drive is prepared for reading/writing.
846    ///     Some drives may lock the door/eject mechanism, but on others, this signal only makes
847    ///     the drive light turn on.
848    ///
849    /// [`Low`]: PinLevel::Low
850    #[allow(non_camel_case_types)]
851    DiskChange_Ready = 34,
852
853    /// Other pin.
854    ///
855    /// This is provided for future extensions of the Greaseweazle.
856    #[num_enum(catch_all)]
857    Other(u8),
858}
859
860/// Possible levels for a pin.
861#[derive(Debug, Clone, Copy, PartialEq, Eq, FromPrimitive, IntoPrimitive)]
862#[repr(u8)]
863pub enum PinLevel {
864    /// Logic low, or 0.
865    Low = 0,
866
867    /// Logic high, or 1.
868    #[num_enum(default)]
869    High = 1,
870}
871
872/// Bandwidth statistics.
873///
874/// Returned by the [`get_bandwidth_stats`](Greaseweazle::get_bandwidth_stats) command.
875#[derive(Debug, Clone, Copy)]
876#[non_exhaustive]
877pub struct BandwidthStats {
878    /// The minimum reported bandwidth.
879    pub min: Bandwidth,
880
881    /// The maximum reported bandwidth.
882    pub max: Bandwidth,
883}
884
885impl BandwidthStats {
886    pub(crate) fn read_from(mut read: impl Read) -> Result<Self, io::Error> {
887        Ok(Self {
888            min: Bandwidth::read_from(&mut read)?,
889            max: Bandwidth::read_from(&mut read)?,
890        })
891    }
892}
893
894/// Bandwidth value.
895#[derive(Debug, Clone, Copy)]
896#[non_exhaustive]
897pub struct Bandwidth {
898    /// Number of bytes transferred.
899    pub bytes: u32,
900
901    /// The number of microseconds it took to transfer `bytes` bytes.
902    pub per_us: u32,
903}
904
905impl Bandwidth {
906    fn read_from(mut read: impl Read) -> Result<Self, io::Error> {
907        Ok(Self {
908            bytes: read.read_u32::<LE>()?,
909            per_us: read.read_u32::<LE>()?,
910        })
911    }
912
913    /// Returns the bandwidth in bits per second.
914    #[inline]
915    pub const fn as_bits_per_sec(&self) -> f64 {
916        self.bytes as f64 / self.per_us as f64 * 8_000_000.0
917    }
918}
919
920#[derive(IntoPrimitive)]
921#[repr(u32)]
922pub(crate) enum BaudRate {
923    Normal = 9600,
924    ClearComms = 10000,
925}
926
927#[derive(Debug, Clone, Copy, IntoPrimitive)]
928#[non_exhaustive]
929#[repr(u8)]
930pub(crate) enum Command {
931    GetInfo = 0,
932    #[allow(dead_code)]
933    Update = 1,
934    Seek = 2,
935    Head = 3,
936    SetParams = 4,
937    GetParams = 5,
938    Motor = 6,
939    ReadFlux = 7,
940    WriteFlux = 8,
941    GetFluxStatus = 9,
942    #[allow(dead_code)]
943    SwitchFwMode = 11,
944    Select = 12,
945    Deselect = 13,
946    SetBusType = 14,
947    SetPin = 15,
948    Reset = 16,
949    EraseFlux = 17,
950    SourceBytes = 18,
951    SinkBytes = 19,
952    GetPin = 20,
953    #[allow(dead_code)]
954    TestMode = 21,
955    NoClickStep = 22,
956}