diff --git a/docs/MissingSimpleProtocolCommands.md b/docs/MissingSimpleProtocolCommands.md index 6a7f64b..4b0b76c 100644 --- a/docs/MissingSimpleProtocolCommands.md +++ b/docs/MissingSimpleProtocolCommands.md @@ -1,49 +1,24 @@ -# Simple Protocol commands not wrapped in `src/` +# Simple Protocol coverage audit -The current `TWN4ReaderDevice` wrappers cover the SYS (reset/version), RF tag search, ISO14443 transparent access, and MIFARE Classic/Ultralight helpers. The following Simple Protocol commands from DocRev25 are not surfaced in `src/` and require host-side framing/handling. +> **Status: stale / needs re-audit** +> +> This file was created against an earlier source layout and must not be used as an authoritative command map. Several entries became outdated as additional Simple Protocol wrappers were implemented, and some API/function identifiers in the previous inventory were incorrect. +> +> Use [`DocRev25.txt`](DocRev25.txt) as the protocol reference until this coverage audit has been regenerated from the current `src/` tree. -## API IO (0x01xx) -- **WriteByte** `[0100][Channel][Byte]` → `[00]`. Writes a single byte to a host/reader channel.【11669d†L1184-L1202】 -- **ReadByte** `[0101][Channel]` → `[00][Byte]`. Reads a byte from the channel.【11669d†L1184-L1202】 -- **TestEmpty/TestFull** `[0102|0103][Channel][Dir]` → `[00][Bool]`. Poll channel buffer state for read/write directions.【11669d†L1208-L1246】 -- **GetBufferSize/GetByteCount** `[0104|0105][Channel][Dir]` → `[00][UInt16]` reporting configured buffer length or current fill level.【11669d†L1248-L1260】 -- **SetCOMParameters** `[0106][Byte: Channel][Byte: Dir][UInt32: Baud][Byte: Parity][Byte: StopBits][Byte: DataBits]` → `[00][Bool]` to configure serial-style host links (see DocRev25 for field ordering). Subsequent commands assume the negotiated framing persists.【11669d†L1248-L1260】 -- **GetUSBDeviceState/GetHostChannel** `[0107|0108]` → `[00][Byte]` returning USB state or host channel mapping.【11669d†L1248-L1260】 -- **USBRemoteWakeup** `[0109]` → `[00]` to trigger remote wake.【11669d†L1248-L1260】 -- **WriteBytes/ReadBytes** `[010A|010B][Channel][Len][Data]/[Channel][Len]` → `[00][Ack?][Payload]` for buffered multi-byte transfers; caller must respect buffer sizes/timing noted above.【11669d†L1248-L1260】 +## Verified status for API PERIPH -## API GPIO/Diagnostics (0x02xx) -DocRev25 exposes GPIO drive/LED/beeper frames beyond the basic `SetGpio*` helpers in `src/`: -- **GPIOConfigureOutputs/Inputs** `[0200|0201][Byte: Mask]` → `[00]` to set output vs. input direction.【18d50a†L47-L53】 -- **GPIOSetBits/GPIOClearBits/GPIOToggleBits** `[0202|0203|0204][Byte: Mask]` → `[00]` for bitwise manipulation; **GPIOBlinkBits** `[0205][Mask][OnTime][OffTime][Repeat]` → `[00]` handles timed blinking.【18d50a†L47-L53】 -- **GPIOTestBit** `[0206][Bit]` → `[00][Bool]` reads a single pin.【18d50a†L47-L53】 -- **Beep/BeepOn/BeepOff** `[0207][Duration]` or `[0213|0214]` → `[00]` for one-shot or continuous tones; timing parameters are milliseconds.【18d50a†L53-L58】 -- **DiagLEDOn/Off/Toggle/IsOn** `[0208|0209|020A|020B]` manage the diagnostic LED; `IsOn` returns `[00][Bool]`.【18d50a†L105-L109】 -- **SendWiegand/SendOmron** `[020C|020D][Byte: BitLen][VarData]` → `[00][Bool]` emit interface frames after optional busy-time delays noted in DocRev25.【18d50a†L109-L114】 -- **LEDInit/On/Off/Toggle/Blink** `[020E-0212]` configure and drive RGB panel LEDs; blink uses on/off times and repeat counters.【18d50a†L111-L115】 +Issue #24 triggered a full check of the PERIPH group against DocRev25. -## API TILF LF/HF (0x03xx) -All TILF low-/high-frequency read/program/lock commands (selective page reads, special lock/write variants) lack wrappers. Each frame starts with `0x03` plus the operation code and carries page numbers, address ranges, optional passwords, and payload bytes; responses are `[00][Bool/Data...]` depending on the variant.【765278†L1-L25】 +- Simple Protocol API PERIPH is `0x04xx`. +- The documented PERIPH commands `0x0400` through `0x0417` (excluding undocumented/reserved function numbers) are wrapped by `TWN4ReaderDevice`. +- `Beep` is `0x0407`. +- `LEDInit` is `0x0410`; LED on/off/toggle/blink are `0x0411` through `0x0414`. +- `BeepOn` and `BeepOff` are `0x0416` and `0x0417`. +- GPIO outputs must be configured as outputs before set/clear/toggle/blink operations. This GPIO initialization requirement is separate from the dedicated beeper commands. -## API HITAG1S / HITAG2 (0x04xx) -The HITAG commands for block/page access, password set, and halt are not surfaced. They use `[04xx][Page/Block][Len][Data]` request frames with `[00][Bool/Data]` responses and rely on prior tag selection via RF.【765278†L25-L41】 +Some functions present in the TWN4 App/API `SYSFUNC` interface are not commands of the stock Simple Protocol. They must not be assigned Simple Protocol command numbers merely because their App/API function numbers appear similar. -## API SM4X00 (0x050x) -`SM4X00_GenericRaw` and `SM4X00_Generic` support transparent SM4X00 exchanges (`[0500|0501][Byte: Flags][Cmd][Data…][Buf]` → `[00][Bool][Data]`) but are absent from `src/`.【765278†L41-L48】 - -## API I2C host bridge (0x060x) -Reader-side I2C master controls are missing: `I2CInit`, `I2CDeInit`, `I2CMasterStart/Stop`, `Transmit/ReceiveByte`, `BeginWrite/BeginRead`, and `SetAck` with the byte-level request/response frames documented in DocRev25 (`[06xx][Addr/Data...]` → `[00][Bool/Data]`).【765278†L48-L63】 - -## API ISO15693 (0x0Dxx) -No ISO15693 transparent or helper wrappers exist: -- **ISO15693_GenericCommand** `[0D00][Flags][Command][Data…][BufferSize]` → `[00][Bool][Data]` for raw frame exchange.【d0c2f0†L3327-L3345】 -- **GetSystemInformation / GetSystemInformationExt** `[0D01|0D02]` → `[00][Bool][SystemInfo(15 bytes)]`.【d0c2f0†L3347-L3355】【7f9f40†L3355-L3383】 -- **GetTagTypeFromUID/SystemInfo** `[0D03|0D04][UID/SystemInfo]` → `[00][TagType]`.【7f9f40†L3385-L3427】 -- **ReadSingleBlock / ReadSingleBlockExt / WriteSingleBlock / WriteSingleBlockExt** `[0D05-0D08][Block][BufferSize/Data]` → `[00][Bool][Data?]` (extended variants use 16‑bit blocks and optional flags; DocRev25 notes standard ISO15693 timing/CRC expectations).【7f9f40†L3429-L3434】 - -## Additional unwrapped areas -Beyond the categories above, DocRev25 lists many card- or interface-specific APIs not represented in `src/`, including crypto primitives (1.5.13), LEGIC/Desfire derivatives, FeliCa, SLE44xx memory ops, NTAG/Topaz helpers, SPI/BLE stacks, file-system (FS*) calls, and multiple proprietary card families (AT55, EM4150/4305, CTS, SRX, etc.). These follow the same framing pattern—`[API ID][FuncNo][Params…]` with `[00]` status+payload replies—and often note tag-presence timing constraints or buffer-size caps in the command descriptions’ remarks.【18d50a†L213-L757】 - -## Status/ACK behavior -DocRev25 uses `[00]` as the success byte in every response frame, followed by function-specific payloads (booleans, lengths, data arrays). Buffer-size parameters (`BufferSize`, `MaxLen`, `MaxRXByteCnt`) gate how many bytes the reader returns; callers must poll or throttle for channel/transport buffer fullness where documented (notably API IO read/write/test commands). +## Follow-up +A complete command-by-command coverage audit should compare the current `src/Api/Readers/TWN4ReaderDevice/Protocols/` implementation directly with DocRev25 before publishing a new "missing commands" inventory. diff --git a/src/Api/Readers/TWN4ReaderDevice/Protocols/TWN4ReaderDevice.Periph.cs b/src/Api/Readers/TWN4ReaderDevice/Protocols/TWN4ReaderDevice.Periph.cs index 4de53cc..4ed825b 100644 --- a/src/Api/Readers/TWN4ReaderDevice/Protocols/TWN4ReaderDevice.Periph.cs +++ b/src/Api/Readers/TWN4ReaderDevice/Protocols/TWN4ReaderDevice.Periph.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Threading.Tasks; using Elatec.NET.Helpers.ByteArrayHelper.Extensions; @@ -9,255 +9,384 @@ public partial class TWN4ReaderDevice { #region API_PERIPH / Periphery Functions - public static readonly byte API_PERIPH = 2; + /// + /// TWN4 Simple Protocol API identifier for periphery functions (0x04xx). + /// + public static readonly byte API_PERIPH = 0x04; - // Not supported: SYSFUNC(API_PERIPH, 0, bool SysSetGpioConfig(byte bits, byte pull_up_down, byte output_type)) + /// + /// Configure one or more GPIOs as outputs. + /// Simple Protocol command: 0x0400. + /// + public async Task GpioConfigureOutputsAsync(Gpios bits, GpioPullType pullUpDown, GpioOutputType outputType) + { + await CallFunctionAsync(new byte[] { API_PERIPH, 0x00, (byte)bits, (byte)pullUpDown, (byte)outputType }); + } /// - /// Set the polarity and the output type (open-drain or push-pull) of each GPIO pin. + /// Configure one or more GPIOs as inputs. + /// Simple Protocol command: 0x0401. /// - /// GPIO pins to set. This is a bitmask, where the bits represent the GPIO pins, see TwnGpioEnum. - /// Input pin resistors: PullUp, PullDown, or None - /// Output pin types: PushPull or OpenDrain. - /// - /// SYSFUNC(API_PERIPH, 1, bool GpioSetConfig(byte bits, byte pull_up_down, byte output_type)) - public async Task SetGpioConfigAsync(Gpios bits, PullResistor pullUpDown, OutputType outputType) + public async Task GpioConfigureInputsAsync(Gpios bits, GpioPullType pullUpDown) { - await CallFunctionAsync(new byte[] { API_PERIPH, 0, (byte)bits, (byte)pullUpDown, (byte)outputType }); + await CallFunctionAsync(new byte[] { API_PERIPH, 0x01, (byte)bits, (byte)pullUpDown }); } /// - /// Set the pull up/down of each GPIO pin. + /// Set one or more GPIO outputs to logical high. + /// The GPIOs must have been configured as outputs first. + /// Simple Protocol command: 0x0402. /// - /// GPIO pins to set. This is a bitmask, where the bits represent the GPIO pins, see TwnGpioEnum. - /// Input pin resistors: PullUp, PullDown, or None - /// - /// SYSFUNC(API_PERIPH, 2, bool GpioSetPullUpDown(byte bits, byte pull_up_down)) - public async Task SetGpioPullUpDownAsync(Gpios bits, PullResistor pullUpDown) + public async Task GpioSetBitsAsync(Gpios bits) { - await CallFunctionAsync(new byte[] { API_PERIPH, 1, (byte)bits, (byte)pullUpDown }); + await CallFunctionAsync(new byte[] { API_PERIPH, 0x02, (byte)bits }); } /// - /// Set GPIO pins to PushPull. + /// Set one or more GPIO outputs to logical low. + /// The GPIOs must have been configured as outputs first. + /// Simple Protocol command: 0x0403. /// - /// GPIO pins to set. This is a bitmask, where the bits represent the GPIO pins, see TwnGpioEnum. - /// - /// SYSFUNC(API_PERIPH, 3, bool GpioSetPushPull(byte bits)) - public async Task SetGpioPushPullAsync(Gpios bits) + public async Task GpioClearBitsAsync(Gpios bits) { - await CallFunctionAsync(new byte[] { API_PERIPH, 2, (byte)bits }); + await CallFunctionAsync(new byte[] { API_PERIPH, 0x03, (byte)bits }); } /// - /// Set GPIO pins to OpenDrain. + /// Toggle one or more GPIO outputs. + /// The GPIOs must have been configured as outputs first. + /// Simple Protocol command: 0x0404. /// - /// GPIO pins to set. This is a bitmask, where the bits represent the GPIO pins, see TwnGpioEnum. - /// - /// SYSFUNC(API_PERIPH, 4, bool GpioSetOpenDrain(byte bits)) - public async Task SetGpioOpenDrainAsync(Gpios bits) + public async Task GpioToggleBitsAsync(Gpios bits) { - await CallFunctionAsync(new byte[] { API_PERIPH, 3, (byte)bits }); + await CallFunctionAsync(new byte[] { API_PERIPH, 0x04, (byte)bits }); } /// - /// Set the state of the desired GPIO pins. + /// Blink one or more GPIO outputs. + /// The GPIOs must have been configured as outputs first. + /// Simple Protocol command: 0x0405. /// - /// GPIO pins to set. This is a bitmask, where the bits represent the GPIO pins, see TwnGpioEnum. - /// - /// SYSFUNC(API_PERIPH, 5, bool GpioSetBits(byte bits)) - public async Task SetGpioBitsAsync(Gpios bits) + public async Task GpioBlinkBitsAsync(Gpios bits, ushort timeHigh, ushort timeLow) { - await CallFunctionAsync(new byte[] { API_PERIPH, 4, (byte)bits }); + var bytes = new List { API_PERIPH, 0x05, (byte)bits }; + bytes.AddUInt16(timeHigh); + bytes.AddUInt16(timeLow); + await CallFunctionAsync(bytes.ToArray()); } /// - /// Clear the state of the desired GPIO pins. + /// Read the state of a GPIO. + /// Simple Protocol command: 0x0406. /// - /// GPIO pins to clear. This is a bitmask, where the bits represent the GPIO pins, see TwnGpioEnum. - /// - /// SYSFUNC(API_PERIPH, 6, bool GpioClearBits(byte bits)) - public async Task ClearGpioBitsAsync(Gpios bits) + public async Task GpioTestBitAsync(Gpios bit) { - await CallFunctionAsync(new byte[] { API_PERIPH, 5, (byte)bits }); + var parser = await CallFunctionAsync(new byte[] { API_PERIPH, 0x06, (byte)bit }); + return parser.ParseByte() != 0; } /// - /// Set the state of the desired GPIO pins. + /// Switch the diagnostic LED on. + /// Simple Protocol command: 0x0408. /// - /// GPIO pins to set. This is a bitmask, where the bits represent the GPIO pins, see TwnGpioEnum. - /// GPIO pins to clear. This is a bitmask, where the bits represent the GPIO pins, see TwnGpioEnum. - /// GPIO pins to toggle the state of. This is a bitmask, where the bits represent the GPIO pins, see TwnGpioEnum. - /// - /// SYSFUNC(API_PERIPH, 7, bool GpioWriteBits(byte setbits, byte clearbits, byte togglebits)) - public async Task WriteGpioBitsAsync(Gpios setbits, Gpios clearbits, Gpios togglebits) - { - List bytes = new List { API_PERIPH, 6 }; - bytes.Add((byte)setbits); - bytes.Add((byte)clearbits); - bytes.Add((byte)togglebits); - await CallFunctionAsync(bytes.ToArray()); + public async Task DiagLedOnAsync() + { + await CallFunctionAsync(new byte[] { API_PERIPH, 0x08 }); } /// - /// Get the state of the desired GPIO pins. + /// Switch the diagnostic LED off. + /// Simple Protocol command: 0x0409. /// - /// GPIO pin to check. See TwnGpioEnum. - /// Returns true, if the GPIO pin is set, false otherwise. - /// SYSFUNC(API_PERIPH, 8, bool GpioGetBit(byte bit)) - public async Task GetGpioBitAsync(Gpios bit) + public async Task DiagLedOffAsync() { - var parser = await CallFunctionAsync(new byte[] { API_PERIPH, 6, (byte)bit }); - var result = parser.ParseBool(); - return result; + await CallFunctionAsync(new byte[] { API_PERIPH, 0x09 }); } /// - /// This function initializes UART0 in the specified mode. It occupies GPIO pins 1 and 4 (RX and TX). + /// Toggle the diagnostic LED. + /// Simple Protocol command: 0x040A. /// - /// The UART mode selection. See TwnUartModeEnum. - /// UART0 baudrate in Bauds - /// - /// SYSFUNC(API_PERIPH,10, bool SerialSetMode(byte Mode, unsigned int Baudrate)) - public async Task SetSerialModeAsync(SerialMode Mode, uint Baudrate) - { - List bytes = new List { API_PERIPH, 8 }; - bytes.Add((byte)Mode); - bytes.AddUInt32(Baudrate); - await CallFunctionAsync(bytes.ToArray()); + public async Task DiagLedToggleAsync() + { + await CallFunctionAsync(new byte[] { API_PERIPH, 0x0A }); } /// - /// Writes to UART0. + /// Query the diagnostic LED state. + /// Simple Protocol command: 0x040B. /// - /// Data to write to UART0. - /// - /// SYSFUNC(API_PERIPH,11, bool SerialWrite(const byte* Data, int ByteCount)) - public async Task SerialWriteAsync(byte[] Data) - { - List bytes = new List { API_PERIPH, 9 }; - bytes.Add((byte)Data.Length); - bytes.AddRange(Data); + public async Task DiagLedIsOnAsync() + { + var parser = await CallFunctionAsync(new byte[] { API_PERIPH, 0x0B }); + return parser.ParseBool(); + } + + /// + /// Send a Wiegand bit stream. + /// Simple Protocol command: 0x040C. + /// + public async Task SendWiegandAsync(Gpios gpioData0, Gpios gpioData1, ushort pulseTime, ushort intervalTime, byte[] bits, byte bitCount) + { + if (bits == null) + { + throw new ArgumentNullException(nameof(bits)); + } + + if (bits.Length > byte.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(bits), "Simple Protocol Byte Array(Var) fields are limited to 255 bytes."); + } + + var bytes = new List { API_PERIPH, 0x0C, (byte)gpioData0, (byte)gpioData1 }; + bytes.AddUInt16(pulseTime); + bytes.AddUInt16(intervalTime); + bytes.Add((byte)bits.Length); + bytes.AddRange(bits); + bytes.Add(bitCount); await CallFunctionAsync(bytes.ToArray()); } /// - /// Reads from UART0 into a buffer. + /// Send an Omron bit stream. + /// Simple Protocol command: 0x040D. /// - /// Maximum number of bytes to read from UART0. - /// - /// SYSFUNC(API_PERIPH,12, int SerialRead(byte* Data,int MaxByteCount)) - public async Task SerialReadAsync(byte MaxBytes) + public async Task SendOmronAsync(Gpios gpioClock, Gpios gpioData, ushort t1, ushort t2, ushort t3, byte[] bits, byte bitCount) { - List bytes = new List { API_PERIPH, 10 }; - bytes.Add(MaxBytes); - var parser = await CallFunctionAsync(bytes.ToArray()); - var result = parser.ParseFlexByteArray(); - return result; + if (bits == null) + { + throw new ArgumentNullException(nameof(bits)); + } + + if (bits.Length > byte.MaxValue) + { + throw new ArgumentOutOfRangeException(nameof(bits), "Simple Protocol Byte Array(Var) fields are limited to 255 bytes."); + } + + var bytes = new List { API_PERIPH, 0x0D, (byte)gpioClock, (byte)gpioData }; + bytes.AddUInt16(t1); + bytes.AddUInt16(t2); + bytes.AddUInt16(t3); + bytes.Add((byte)bits.Length); + bytes.AddRange(bits); + bytes.Add(bitCount); + await CallFunctionAsync(bytes.ToArray()); } /// - /// Reads and writes to UART0. + /// Initialize GPIOs for LED operation. + /// Simple Protocol command: 0x0410. /// - /// Data to write to UART0. - /// Maximum number of bytes to read from UART0. - /// - /// SYSFUNC(API_PERIPH,13, int SerialReadWrite(const byte* writeData,int writeByteCount,byte* readData,int readMaxByteCount)) - public async Task SerialReadWriteAsync(byte[] writeData, byte readMaxBytes) + public async Task LedInitAsync(Leds leds = Leds.All) { - List bytes = new List { API_PERIPH, 11 }; - bytes.Add((byte)writeData.Length); - bytes.AddRange(writeData); - bytes.Add(readMaxBytes); - var parser = await CallFunctionAsync(bytes.ToArray()); - var result = parser.ParseFlexByteArray(); - return result; + await CallFunctionAsync(new byte[] { API_PERIPH, 0x10, (byte)leds }); } /// - /// Causes the device to emit an acoustic tone using buzzer or speaker for the desired duration. + /// Compatibility alias for . /// - /// Milliseconds of tone output - /// SYSFUNC(API_PERIPH,16, void PlaySound(int duration)) - public async Task PlaySoundAsync(short duration) + public Task InitLedsAsync(Leds leds = Leds.All) { - await CallFunctionAsync(new byte[] { API_PERIPH, 16, (byte)duration }); + return LedInitAsync(leds); } /// - /// Causes the device to emit two acoustic tones using buzzer or speaker for the desired duration. + /// Switch one or more initialized LEDs on. + /// Simple Protocol command: 0x0411. /// - /// Milliseconds of tone output - /// SYSFUNC(API_PERIPH,17, void PlaySound1(int duration)) - public async Task PlaySound1Async(short duration) + public async Task LedOnAsync(Leds leds) { - await CallFunctionAsync(new byte[] { API_PERIPH, 17, (byte)duration }); + await CallFunctionAsync(new byte[] { API_PERIPH, 0x11, (byte)leds }); } /// - /// Causes the device to emit two acoustic tones using buzzer or speaker for the desired duration. + /// Switch one or more initialized LEDs off. + /// Simple Protocol command: 0x0412. /// - /// Milliseconds of tone output - /// SYSFUNC(API_PERIPH,18, void PlaySound2(int duration)) - public async Task PlaySound2Async(short duration) + public async Task LedOffAsync(Leds leds) { - await CallFunctionAsync(new byte[] { API_PERIPH, 18, (byte)duration }); + await CallFunctionAsync(new byte[] { API_PERIPH, 0x12, (byte)leds }); } /// - /// Causes the device to emit an acoustic tone using buzzer or speaker for the desired duration. + /// Toggle one or more initialized LEDs. + /// Simple Protocol command: 0x0413. /// - /// Duration of first tone in milliseconds. - /// Duration of second tone in milliseconds. - /// SYSFUNC(API_PERIPH,19, void PlaySound4(int tone1Duration, int tone2Duration)) - public async Task PlaySound4Async(short tone1Duration, short tone2Duration) + public async Task LedToggleAsync(Leds leds) { - await CallFunctionAsync(new byte[] { API_PERIPH, 19, (byte)tone1Duration, (byte)tone2Duration }); + await CallFunctionAsync(new byte[] { API_PERIPH, 0x13, (byte)leds }); } /// - /// Causes the device to emit a melody. + /// Blink one or more initialized LEDs. + /// Simple Protocol command: 0x0414. /// - /// The desired melody: tones. - /// The desired melody: durations. This represents a percentage with 255=100%. - /// SYSFUNC(API_PERIPH,20, void PlayMelody(const byte* tones,const byte* durations,int Count,int RepeatCount)) - public async Task PlayMelodyAsync(byte[] tones, byte[] durations) - { - List bytes = new List { API_PERIPH, 20 }; - bytes.Add((byte)tones.Length); - bytes.AddRange(tones); - bytes.Add((byte)durations.Length); - bytes.AddRange(durations); + public async Task LedBlinkAsync(Leds leds, ushort onTime, ushort offTime) + { + var bytes = new List { API_PERIPH, 0x14, (byte)leds }; + bytes.AddUInt16(onTime); + bytes.AddUInt16(offTime); await CallFunctionAsync(bytes.ToArray()); } /// - /// Causes the device to emit a melody. + /// Switch the beeper on continuously. + /// Simple Protocol command: 0x0416. /// - /// The desired melody: tones. - /// The desired melody: durations. This represents a percentage with 255=100%. - /// Repeat the melody repeatCount times. - /// SYSFUNC(API_PERIPH,21, void PlayMelody(const byte* tones,const byte* durations,int Count,int RepeatCount)) - public async Task PlayMelodyAsync(byte[] tones, byte[] durations, byte repeatCount) - { - List bytes = new List { API_PERIPH, 20 }; - bytes.Add((byte)tones.Length); - bytes.AddRange(tones); - bytes.Add((byte)durations.Length); - bytes.AddRange(durations); - bytes.Add(repeatCount); + public async Task BeepOnAsync(byte volume, ushort frequency) + { + var bytes = new List { API_PERIPH, 0x16, volume }; + bytes.AddUInt16(frequency); await CallFunctionAsync(bytes.ToArray()); } /// - /// Stops an acoustic tone or melody produced by a previous PlaySound or PlayMelody call. + /// Switch the beeper off. + /// Simple Protocol command: 0x0417. + /// + public async Task BeepOffAsync() + { + await CallFunctionAsync(new byte[] { API_PERIPH, 0x17 }); + } + + #region Compatibility aliases + + /// + /// Compatibility alias for configuring GPIO outputs. + /// + public Task SetGpioConfigAsync(Gpios bits, PullResistor pullUpDown, OutputType outputType) + { + return GpioConfigureOutputsAsync(bits, (GpioPullType)pullUpDown, (GpioOutputType)outputType); + } + + /// + /// Compatibility alias for . + /// + public Task SetGpioBitsAsync(Gpios bits) + { + return GpioSetBitsAsync(bits); + } + + /// + /// Compatibility alias for . + /// + public Task ClearGpioBitsAsync(Gpios bits) + { + return GpioClearBitsAsync(bits); + } + + /// + /// Compatibility alias for . + /// + public Task GetGpioBitAsync(Gpios bit) + { + return GpioTestBitAsync(bit); + } + + [Obsolete("GpioSetPullUpDown is part of the TWN4 App/API SYSFUNC interface and has no equivalent command in the stock Simple Protocol. Use GpioConfigureInputsAsync or GpioConfigureOutputsAsync instead.")] + public Task SetGpioPullUpDownAsync(Gpios bits, PullResistor pullUpDown) + { + return UnsupportedSimpleProtocolCall(nameof(SetGpioPullUpDownAsync)); + } + + [Obsolete("GpioSetPushPull is part of the TWN4 App/API SYSFUNC interface and has no equivalent command in the stock Simple Protocol. Use GpioConfigureOutputsAsync instead.")] + public Task SetGpioPushPullAsync(Gpios bits) + { + return UnsupportedSimpleProtocolCall(nameof(SetGpioPushPullAsync)); + } + + [Obsolete("GpioSetOpenDrain is part of the TWN4 App/API SYSFUNC interface and has no equivalent command in the stock Simple Protocol. Use GpioConfigureOutputsAsync instead.")] + public Task SetGpioOpenDrainAsync(Gpios bits) + { + return UnsupportedSimpleProtocolCall(nameof(SetGpioOpenDrainAsync)); + } + + [Obsolete("GpioWriteBits is part of the TWN4 App/API SYSFUNC interface and has no atomic equivalent in the stock Simple Protocol. Use GpioSetBitsAsync, GpioClearBitsAsync and GpioToggleBitsAsync instead.")] + public Task WriteGpioBitsAsync(Gpios setbits, Gpios clearbits, Gpios togglebits) + { + return UnsupportedSimpleProtocolCall(nameof(WriteGpioBitsAsync)); + } + + [Obsolete("SerialSetMode is part of the TWN4 App/API SYSFUNC interface and is not a stock Simple Protocol PERIPH command. Use the API IO Simple Protocol functions where appropriate.")] + public Task SetSerialModeAsync(SerialMode mode, uint baudrate) + { + return UnsupportedSimpleProtocolCall(nameof(SetSerialModeAsync)); + } + + [Obsolete("SerialWrite is part of the TWN4 App/API SYSFUNC interface and is not a stock Simple Protocol PERIPH command. Use the API IO Simple Protocol functions where appropriate.")] + public Task SerialWriteAsync(byte[] data) + { + return UnsupportedSimpleProtocolCall(nameof(SerialWriteAsync)); + } + + [Obsolete("SerialRead is part of the TWN4 App/API SYSFUNC interface and is not a stock Simple Protocol PERIPH command. Use the API IO Simple Protocol functions where appropriate.")] + public Task SerialReadAsync(byte maxBytes) + { + return UnsupportedSimpleProtocolCall(nameof(SerialReadAsync)); + } + + [Obsolete("SerialReadWrite is part of the TWN4 App/API SYSFUNC interface and is not a stock Simple Protocol PERIPH command. Use the API IO Simple Protocol functions where appropriate.")] + public Task SerialReadWriteAsync(byte[] writeData, byte readMaxBytes) + { + return UnsupportedSimpleProtocolCall(nameof(SerialReadWriteAsync)); + } + + [Obsolete("PlaySound is part of the TWN4 App/API SYSFUNC interface and is not a stock Simple Protocol command. Use BeepAsync instead.")] + public Task PlaySoundAsync(short duration) + { + return UnsupportedSimpleProtocolCall(nameof(PlaySoundAsync)); + } + + [Obsolete("PlaySound1 is part of the TWN4 App/API SYSFUNC interface and is not a stock Simple Protocol command. Use BeepAsync instead.")] + public Task PlaySound1Async(short duration) + { + return UnsupportedSimpleProtocolCall(nameof(PlaySound1Async)); + } + + [Obsolete("PlaySound2 is part of the TWN4 App/API SYSFUNC interface and is not a stock Simple Protocol command. Use BeepAsync instead.")] + public Task PlaySound2Async(short duration) + { + return UnsupportedSimpleProtocolCall(nameof(PlaySound2Async)); + } + + [Obsolete("PlaySound4 is part of the TWN4 App/API SYSFUNC interface and is not a stock Simple Protocol command. Use BeepAsync instead.")] + public Task PlaySound4Async(short tone1Duration, short tone2Duration) + { + return UnsupportedSimpleProtocolCall(nameof(PlaySound4Async)); + } + + [Obsolete("The TWN4 App/API PlayMelody SYSFUNC is not a stock Simple Protocol command. Use the high-level PlayMelody helper instead.")] + public Task PlayMelodyAsync(byte[] tones, byte[] durations) + { + return UnsupportedSimpleProtocolCall(nameof(PlayMelodyAsync)); + } + + [Obsolete("The TWN4 App/API PlayMelody SYSFUNC is not a stock Simple Protocol command. Use the high-level PlayMelody helper instead.")] + public Task PlayMelodyAsync(byte[] tones, byte[] durations, byte repeatCount) + { + return UnsupportedSimpleProtocolCall(nameof(PlayMelodyAsync)); + } + + /// + /// Compatibility alias for stopping a currently active beeper. /// - /// SYSFUNC(API_PERIPH,22, void StopSound()) - public async Task StopSoundAsync() + public Task StopSoundAsync() + { + return BeepOffAsync(); + } + + private static Task UnsupportedSimpleProtocolCall(string methodName) + { + return Task.FromException(new NotSupportedException(methodName + " is not available in the stock TWN4 Simple Protocol.")); + } + + private static Task UnsupportedSimpleProtocolCall(string methodName) { - await CallFunctionAsync(new byte[] { API_PERIPH, 23 }); + return Task.FromException(new NotSupportedException(methodName + " is not available in the stock TWN4 Simple Protocol.")); } #endregion + + #endregion } -} \ No newline at end of file +} diff --git a/src/Api/Readers/TWN4ReaderDevice/TWN4ReaderDevice.Core.cs b/src/Api/Readers/TWN4ReaderDevice/TWN4ReaderDevice.Core.cs index 9c06937..ba97e13 100644 --- a/src/Api/Readers/TWN4ReaderDevice/TWN4ReaderDevice.Core.cs +++ b/src/Api/Readers/TWN4ReaderDevice/TWN4ReaderDevice.Core.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Globalization; using System.Threading; using System.Threading.Tasks; @@ -126,18 +126,19 @@ public async Task PlayMelody(int tempo, List song) /// /// Activate the reader buzzer for a specified duration. + /// Simple Protocol command: 0x0407. /// /// Volume in percent (0-100). /// Tone frequency in hertz. /// Duration in milliseconds the buzzer should be on. /// Duration in milliseconds the buzzer should remain off afterwards. /// - /// The firmware exposes the beeper via the periphery API. This helper packages the parameters and delegates to - /// the low-level call. + /// The stock Simple Protocol exposes Beep as [0407][Volume][Frequency][OnTime][OffTime]. + /// UInt16 values are encoded least-significant byte first. /// public async Task BeepAsync(byte volume, ushort frequency, ushort onTime, ushort offTime) { - var payload = new List { TWN4ReaderDevice.API_PERIPH, 12, volume }; + var payload = new List { TWN4ReaderDevice.API_PERIPH, 0x07, volume }; payload.AddUInt16(frequency); payload.AddUInt16(onTime); payload.AddUInt16(offTime); diff --git a/tests/Elatec.NET.Tests/PeriphProtocolTests.cs b/tests/Elatec.NET.Tests/PeriphProtocolTests.cs new file mode 100644 index 0000000..d046221 --- /dev/null +++ b/tests/Elatec.NET.Tests/PeriphProtocolTests.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Xunit; + +namespace Elatec.NET.Tests +{ + public class PeriphProtocolTests + { + [Fact] + public async Task BeepAsync_UsesDocumented0407Frame() + { + var transport = new FakeReaderTransport("COM24"); + transport.QueueResponseBytes(0x00); + var device = new TWN4ReaderDevice("COM24", _ => transport); + + await device.BeepAsync(100, 2400, 500, 500); + + Assert.Equal(0x04, TWN4ReaderDevice.API_PERIPH); + Assert.Single(transport.WrittenLines, "0407646009F401F401"); + } + + [Fact] + public async Task ReadmeSample_BeepLedAndPlayMelody_UseSimpleProtocolFrames() + { + var transport = new FakeReaderTransport("COM24"); + transport.QueueResponseBytes(0x00); // Beep + transport.QueueResponseBytes(0x00); // LED init + transport.QueueResponseBytes(0x00); // LED blink + transport.QueueResponseBytes(0x00); // Melody tone + + var device = new TWN4ReaderDevice("COM24", _ => transport); + + // Keep this sequence aligned with README.md and c3rebro/Elatec.Net.SampleApp. + await device.BeepAsync(100, 1500, 500, 100); + await device.LedInitAsync(); + await device.LedBlinkAsync(Leds.All, 100, 300); + await device.PlayMelody(90, new List + { + new TWN4ReaderDevice.Tone { Value = 4, Pitch = NotePitch.C3 } + }); + + Assert.Equal(new[] + { + "040764DC05F4016400", + "041007", + "04140764002C01", + "04073C1704A5000000" + }, transport.WrittenLines); + } + + [Fact] + public async Task GpioCommands_UseDocumented0400To0406Frames() + { + var transport = new FakeReaderTransport("COM24"); + for (var i = 0; i < 6; i++) + { + transport.QueueResponseBytes(0x00); + } + transport.QueueResponseBytes(0x00, 0x01); + + var device = new TWN4ReaderDevice("COM24", _ => transport); + + await device.GpioConfigureOutputsAsync(Gpios.GPIO0, GpioPullType.NoPull, GpioOutputType.PushPull); + await device.GpioConfigureInputsAsync(Gpios.GPIO0, GpioPullType.NoPull); + await device.GpioSetBitsAsync(Gpios.GPIO0); + await device.GpioClearBitsAsync(Gpios.GPIO0); + await device.GpioToggleBitsAsync(Gpios.GPIO0); + await device.GpioBlinkBitsAsync(Gpios.GPIO0, 100, 100); + var isSet = await device.GpioTestBitAsync(Gpios.GPIO0); + + Assert.True(isSet); + Assert.Equal(new[] + { + "0400010000", + "04010100", + "040201", + "040301", + "040401", + "04050164006400", + "040601" + }, transport.WrittenLines); + } + + [Fact] + public async Task DiagnosticLedLedAndContinuousBeep_UseDocumentedFrames() + { + var transport = new FakeReaderTransport("COM24"); + transport.QueueResponseBytes(0x00); // Diag LED on + transport.QueueResponseBytes(0x00); // Diag LED off + transport.QueueResponseBytes(0x00); // Diag LED toggle + transport.QueueResponseBytes(0x00, 0x01); // Diag LED is on + transport.QueueResponseBytes(0x00); // LED init + transport.QueueResponseBytes(0x00); // LED on + transport.QueueResponseBytes(0x00); // LED off + transport.QueueResponseBytes(0x00); // LED toggle + transport.QueueResponseBytes(0x00); // LED blink + transport.QueueResponseBytes(0x00); // Beep on + transport.QueueResponseBytes(0x00); // Beep off + + var device = new TWN4ReaderDevice("COM24", _ => transport); + + await device.DiagLedOnAsync(); + await device.DiagLedOffAsync(); + await device.DiagLedToggleAsync(); + var diagLedIsOn = await device.DiagLedIsOnAsync(); + await device.LedInitAsync(); + await device.LedOnAsync(Leds.Red); + await device.LedOffAsync(Leds.Red); + await device.LedToggleAsync(Leds.Red); + await device.LedBlinkAsync(Leds.Red, 500, 500); + await device.BeepOnAsync(100, 2400); + await device.BeepOffAsync(); + + Assert.True(diagLedIsOn); + Assert.Equal(new[] + { + "0408", + "0409", + "040A", + "040B", + "041007", + "041101", + "041201", + "041301", + "041401F401F401", + "0416646009", + "0417" + }, transport.WrittenLines); + } + + [Fact] + public async Task WiegandAndOmron_UseDocumentedFrames() + { + var transport = new FakeReaderTransport("COM24"); + transport.QueueResponseBytes(0x00); + transport.QueueResponseBytes(0x00); + var device = new TWN4ReaderDevice("COM24", _ => transport); + + await device.SendWiegandAsync(Gpios.GPIO3, Gpios.GPIO4, 100, 1000, new byte[] { 0xAA }, 8); + await device.SendOmronAsync(Gpios.GPIO3, Gpios.GPIO4, 500, 500, 500, new byte[] { 0xAA }, 8); + + Assert.Equal(new[] + { + "040C08106400E80301AA08", + "040D0810F401F401F40101AA08" + }, transport.WrittenLines); + } + + [Fact] + public async Task AppApiOnlyPeriphMethods_DoNotSendMisleadingSimpleProtocolFrames() + { + var transport = new FakeReaderTransport("COM24"); + var device = new TWN4ReaderDevice("COM24", _ => transport); + +#pragma warning disable CS0618 + await Assert.ThrowsAsync(() => device.SetSerialModeAsync(SerialMode.Uart, 9600)); + await Assert.ThrowsAsync(() => device.PlaySoundAsync(100)); +#pragma warning restore CS0618 + + Assert.Empty(transport.WrittenLines); + } + } +}