//! Scher-Khan protocol decoder (decode-only) //! //! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/scher_khan.c`. //! Decode logic (PWM preamble/sync, short=0/long=1, variable bit count) matches reference. //! No encoder in protopirate. //! //! Protocol characteristics: //! - PWM encoding: 750µs = 0, 1100µs = 1; preamble uses 2× short then alternating //! - Variable bit count (35, 51, 57, 63, 64, 81, 82); only 51-bit format parsed for serial/button/counter //! //! References: https://phreakerclub.com/72 use super::{DecodedSignal, ProtocolDecoder, ProtocolTiming}; use crate::duration_diff; use crate::radio::demodulator::LevelDuration; const TE_SHORT: u32 = 750; const TE_LONG: u32 = 1100; const TE_DELTA: u32 = 160; const MIN_COUNT_BIT: usize = 35; /// Decoder states (matches protopirate's ScherKhanDecoderStep) #[derive(Debug, Clone, Copy, PartialEq)] enum DecoderStep { Reset, CheckPreamble, SaveDuration, CheckDuration, } /// Scher-Khan protocol decoder pub struct ScherKhanDecoder { step: DecoderStep, te_last: u32, header_count: u16, decode_data: u64, decode_count_bit: usize, } impl ScherKhanDecoder { pub fn new() -> Self { Self { step: DecoderStep::Reset, te_last: 0, header_count: 0, decode_data: 0, decode_count_bit: 0, } } /// Parse payload by bit count; 51-bit format yields serial/button/counter (matches scher_khan.c) fn parse_data(data: u64, bit_count: usize) -> DecodedSignal { let (serial, btn, cnt) = match bit_count { 51 => { // 51-bit "MAGIC CODE" / Dynamic format: serial(28) | button(4) | counter(16) — matches reference let serial = ((data >> 24) & 0xFFFFFF0) as u32 | ((data >> 20) & 0x0F) as u32; let btn = ((data >> 24) & 0x0F) as u8; let cnt = (data & 0xFFFF) as u16; (Some(serial), Some(btn), Some(cnt)) } _ => (None, None, None), }; DecodedSignal { serial, button: btn, counter: cnt, crc_valid: true, data, data_count_bit: bit_count, encoder_capable: false, extra: None, protocol_display_name: None, } } } impl ProtocolDecoder for ScherKhanDecoder { fn name(&self) -> &'static str { "Scher-Khan" } fn timing(&self) -> ProtocolTiming { ProtocolTiming { te_short: TE_SHORT, te_long: TE_LONG, te_delta: TE_DELTA, min_count_bit: MIN_COUNT_BIT, } } fn supported_frequencies(&self) -> &[u32] { &[433_920_000] } fn reset(&mut self) { self.step = DecoderStep::Reset; self.te_last = 0; self.header_count = 0; self.decode_data = 0; self.decode_count_bit = 0; } fn feed(&mut self, level: bool, duration: u32) -> Option { match self.step { DecoderStep::Reset => { if level && duration_diff!(duration, TE_SHORT * 2) < TE_DELTA { self.step = DecoderStep::CheckPreamble; self.te_last = duration; self.header_count = 0; } } DecoderStep::CheckPreamble => { if level { if duration_diff!(duration, TE_SHORT * 2) < TE_DELTA || duration_diff!(duration, TE_SHORT) < TE_DELTA { self.te_last = duration; } else { self.step = DecoderStep::Reset; } } else if duration_diff!(duration, TE_SHORT * 2) < TE_DELTA || duration_diff!(duration, TE_SHORT) < TE_DELTA { if duration_diff!(self.te_last, TE_SHORT * 2) < TE_DELTA { self.header_count += 1; } else if duration_diff!(self.te_last, TE_SHORT) < TE_DELTA { // Found start bit if self.header_count >= 2 { self.step = DecoderStep::SaveDuration; self.decode_data = 0; self.decode_count_bit = 1; } else { self.step = DecoderStep::Reset; } } else { self.step = DecoderStep::Reset; } } else { self.step = DecoderStep::Reset; } } DecoderStep::SaveDuration => { if level { if duration >= (TE_DELTA * 2 + TE_LONG) { // Found stop bit self.step = DecoderStep::Reset; if self.decode_count_bit >= MIN_COUNT_BIT { let result = Self::parse_data(self.decode_data, self.decode_count_bit); self.decode_data = 0; self.decode_count_bit = 0; return Some(result); } self.decode_data = 0; self.decode_count_bit = 0; } else { self.te_last = duration; self.step = DecoderStep::CheckDuration; } } else { self.step = DecoderStep::Reset; } } DecoderStep::CheckDuration => { if !level { if duration_diff!(self.te_last, TE_SHORT) < TE_DELTA && duration_diff!(duration, TE_SHORT) < TE_DELTA { // Bit 0 self.decode_data = (self.decode_data << 1) | 0; self.decode_count_bit += 1; self.step = DecoderStep::SaveDuration; } else if duration_diff!(self.te_last, TE_LONG) < TE_DELTA && duration_diff!(duration, TE_LONG) < TE_DELTA { // Bit 1 self.decode_data = (self.decode_data << 1) | 1; self.decode_count_bit += 1; self.step = DecoderStep::SaveDuration; } else { self.step = DecoderStep::Reset; } } else { self.step = DecoderStep::Reset; } } } None } fn supports_encoding(&self) -> bool { false } fn encode(&self, _decoded: &DecodedSignal, _button: u8) -> Option> { None // Scher-Khan decode-only in protopirate } } impl Default for ScherKhanDecoder { fn default() -> Self { Self::new() } }