//! Kia V1 protocol decoder/encoder //! //! Aligned with ProtoPirate reference: `REFERENCES/ProtoPirate/protocols/kia_v1.c`. //! Decode/encode logic (Manchester, CRC4, preamble, field layout) matches reference. //! //! Protocol characteristics: //! - Manchester encoding: 800/1600µs timing //! - 57 bits total (32 serial + 8 button + 12 counter + 4 CRC) //! - Long preamble of ~90 long pairs //! - CRC4 checksum (XOR of nibbles + offset 1, 7 bytes including cnt_high) use super::{ProtocolDecoder, ProtocolTiming, DecodedSignal}; use crate::radio::demodulator::LevelDuration; use crate::duration_diff; const TE_SHORT: u32 = 800; const TE_LONG: u32 = 1600; const TE_DELTA: u32 = 200; const MIN_COUNT_BIT: usize = 57; /// Manchester decoder states (matches protopirate kia_v1 Manchester state machine) #[derive(Debug, Clone, Copy, PartialEq)] enum ManchesterState { Mid0, Mid1, Start0, Start1, } /// Decoder states (matches protopirate's KiaV1DecoderStep) #[derive(Debug, Clone, Copy, PartialEq)] enum DecoderStep { Reset, CheckPreamble, DecodeData, } /// Kia V1 protocol decoder pub struct KiaV1Decoder { step: DecoderStep, te_last: u32, header_count: u16, decode_data: u64, decode_count_bit: usize, manchester_state: ManchesterState, } impl KiaV1Decoder { pub fn new() -> Self { Self { step: DecoderStep::Reset, te_last: 0, header_count: 0, decode_data: 0, decode_count_bit: 0, manchester_state: ManchesterState::Mid1, } } /// CRC4 for Kia V1 (matches kia_v1.c: XOR nibbles + offset) fn crc4(bytes: &[u8], offset: u8) -> u8 { let mut crc: u8 = 0; for &byte in bytes { crc ^= (byte & 0x0F) ^ (byte >> 4); } (crc.wrapping_add(offset)) & 0x0F } /// Manchester state machine (Flipper convention: level ? ShortLow : ShortHigh) fn manchester_advance(&mut self, is_short: bool, level: bool) -> Option { // C: event = level ? ManchesterEventShortLow : ManchesterEventShortHigh (inverted) let event = match (is_short, level) { (true, true) => 0, // level=true → ShortLow (true, false) => 1, // level=false → ShortHigh (false, true) => 2, // level=true → LongLow (false, false) => 3, // level=false → LongHigh }; let (new_state, output) = match (self.manchester_state, event) { (ManchesterState::Mid0, 0) | (ManchesterState::Mid1, 0) => (ManchesterState::Start0, None), (ManchesterState::Mid0, 1) | (ManchesterState::Mid1, 1) => (ManchesterState::Start1, None), (ManchesterState::Start1, 0) => (ManchesterState::Mid1, Some(true)), (ManchesterState::Start1, 2) => (ManchesterState::Start0, Some(true)), (ManchesterState::Start0, 1) => (ManchesterState::Mid0, Some(false)), (ManchesterState::Start0, 3) => (ManchesterState::Start1, Some(false)), _ => (ManchesterState::Mid1, None), }; self.manchester_state = new_state; output } /// Parse decoded data fn parse_data(&self) -> DecodedSignal { let data = self.decode_data; // Field layout matches kia_v1.c: serial(32) | button(8) | cnt_low(8) | cnt_high(4) | crc(4) let serial = (data >> 24) as u32; let button = ((data >> 16) & 0xFF) as u8; let cnt_low = ((data >> 8) & 0xFF) as u16; let cnt_high = ((data >> 4) & 0x0F) as u16; let counter = (cnt_high << 8) | cnt_low; let received_crc = (data & 0x0F) as u8; // Calculate CRC let mut char_data = [0u8; 7]; char_data[0] = ((serial >> 24) & 0xFF) as u8; char_data[1] = ((serial >> 16) & 0xFF) as u8; char_data[2] = ((serial >> 8) & 0xFF) as u8; char_data[3] = (serial & 0xFF) as u8; char_data[4] = button; char_data[5] = (counter & 0xFF) as u8; // CRC4: always 7 bytes with offset 1 (matches updated ProtoPirate kia_v1.c) char_data[6] = cnt_high as u8; let crc = Self::crc4(&char_data, 1); DecodedSignal { serial: Some(serial), button: Some(button), counter: Some(counter), crc_valid: received_crc == crc, data, data_count_bit: MIN_COUNT_BIT, encoder_capable: true, extra: None, protocol_display_name: None, } } } impl ProtocolDecoder for KiaV1Decoder { fn name(&self) -> &'static str { "Kia V1" } 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] { &[315_000_000, 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; self.manchester_state = ManchesterState::Mid1; } fn feed(&mut self, level: bool, duration: u32) -> Option { let is_short = duration_diff!(duration, TE_SHORT) < TE_DELTA; let is_long = duration_diff!(duration, TE_LONG) < TE_DELTA; match self.step { DecoderStep::Reset => { if level && is_long { self.step = DecoderStep::CheckPreamble; self.te_last = duration; self.header_count = 0; self.decode_data = 0; self.decode_count_bit = 0; self.manchester_state = ManchesterState::Mid1; } } DecoderStep::CheckPreamble => { if !level { if is_long && duration_diff!(self.te_last, TE_LONG) < TE_DELTA { self.header_count += 1; self.te_last = duration; } else { self.step = DecoderStep::Reset; } } if self.header_count > 70 { if !level && is_short && duration_diff!(self.te_last, TE_LONG) < TE_DELTA { // C: decode_count_bit=1, then add_bit(1) increments to 2 self.decode_count_bit = 2; self.decode_data = 1; self.header_count = 0; self.step = DecoderStep::DecodeData; } } } DecoderStep::DecodeData => { if is_short { if let Some(bit) = self.manchester_advance(true, level) { self.decode_data = (self.decode_data << 1) | (bit as u64); self.decode_count_bit += 1; } } else if is_long { if let Some(bit) = self.manchester_advance(false, level) { self.decode_data = (self.decode_data << 1) | (bit as u64); self.decode_count_bit += 1; } } else { self.step = DecoderStep::Reset; return None; } if self.decode_count_bit == MIN_COUNT_BIT { let result = self.parse_data(); self.step = DecoderStep::Reset; return Some(result); } } } None } fn supports_encoding(&self) -> bool { true } fn encode(&self, decoded: &DecodedSignal, button: u8) -> Option> { let serial = decoded.serial?; let counter = decoded.counter.unwrap_or(0); // Calculate CRC let cnt_high = ((counter >> 8) & 0x0F) as u8; let mut char_data = [0u8; 7]; char_data[0] = ((serial >> 24) & 0xFF) as u8; char_data[1] = ((serial >> 16) & 0xFF) as u8; char_data[2] = ((serial >> 8) & 0xFF) as u8; char_data[3] = (serial & 0xFF) as u8; char_data[4] = button; char_data[5] = (counter & 0xFF) as u8; // CRC4: always 7 bytes with offset 1 (matches updated ProtoPirate kia_v1.c) char_data[6] = cnt_high; let crc = Self::crc4(&char_data, 1); // Build data let data: u64 = ((serial as u64) << 24) | ((button as u64) << 16) | (((counter & 0xFF) as u64) << 8) | ((cnt_high as u64) << 4) | (crc as u64); let mut signal = Vec::with_capacity(600); // Generate 3 bursts (matches protopirate kia_v1 encode) for burst in 0..3 { if burst > 0 { signal.push(LevelDuration::new(false, 25000)); } // Preamble: 90 long pairs for _ in 0..90 { signal.push(LevelDuration::new(false, TE_LONG)); signal.push(LevelDuration::new(true, TE_LONG)); } // Short gap before data signal.push(LevelDuration::new(false, TE_SHORT)); // Data: 57 bits Manchester encoded, MSB first for bit_num in (1..MIN_COUNT_BIT).rev() { let bit = ((data >> (bit_num - 1)) & 1) == 1; if bit { signal.push(LevelDuration::new(true, TE_SHORT)); signal.push(LevelDuration::new(false, TE_SHORT)); } else { signal.push(LevelDuration::new(false, TE_SHORT)); signal.push(LevelDuration::new(true, TE_SHORT)); } } } Some(signal) } } impl Default for KiaV1Decoder { fn default() -> Self { Self::new() } }