211 lines
7.0 KiB
Rust
211 lines
7.0 KiB
Rust
//! 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<DecodedSignal> {
|
||
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<Vec<LevelDuration>> {
|
||
None // Scher-Khan decode-only in protopirate
|
||
}
|
||
}
|
||
|
||
impl Default for ScherKhanDecoder {
|
||
fn default() -> Self {
|
||
Self::new()
|
||
}
|
||
}
|