Files
KAT/src/protocols/scher_khan.rs
T
2026-02-17 19:47:57 -05:00

211 lines
7.0 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! 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()
}
}