Fixed barrier/gate/alarm menu
This commit is contained in:
+95
-5
@@ -7,7 +7,7 @@ use std::sync::mpsc::{self, Receiver, Sender};
|
|||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::capture::{ButtonCommand, Capture};
|
use crate::capture::{ButtonCommand, Capture};
|
||||||
use crate::protocols::ProtocolRegistry;
|
use crate::protocols::{is_keeloq_non_car, ProtocolRegistry};
|
||||||
use crate::radio::{HackRfController, LevelDuration, RtlSdrController};
|
use crate::radio::{HackRfController, LevelDuration, RtlSdrController};
|
||||||
use crate::storage::Storage;
|
use crate::storage::Storage;
|
||||||
|
|
||||||
@@ -151,6 +151,7 @@ pub enum ExportFormat {
|
|||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum SignalAction {
|
pub enum SignalAction {
|
||||||
Replay,
|
Replay,
|
||||||
|
SendNextCode,
|
||||||
Lock,
|
Lock,
|
||||||
Unlock,
|
Unlock,
|
||||||
Trunk,
|
Trunk,
|
||||||
@@ -161,6 +162,7 @@ pub enum SignalAction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SignalAction {
|
impl SignalAction {
|
||||||
|
/// All actions (car keyfob menu). Barrier/alarm menu is built separately to include SendNextCode only there.
|
||||||
pub const ALL: [SignalAction; 8] = [
|
pub const ALL: [SignalAction; 8] = [
|
||||||
SignalAction::Replay,
|
SignalAction::Replay,
|
||||||
SignalAction::Lock,
|
SignalAction::Lock,
|
||||||
@@ -175,6 +177,7 @@ impl SignalAction {
|
|||||||
pub fn label(&self) -> &'static str {
|
pub fn label(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
SignalAction::Replay => "Replay",
|
SignalAction::Replay => "Replay",
|
||||||
|
SignalAction::SendNextCode => "Send next code",
|
||||||
SignalAction::Lock => "TX Lock",
|
SignalAction::Lock => "TX Lock",
|
||||||
SignalAction::Unlock => "TX Unlock",
|
SignalAction::Unlock => "TX Unlock",
|
||||||
SignalAction::Trunk => "TX Trunk",
|
SignalAction::Trunk => "TX Trunk",
|
||||||
@@ -884,6 +887,76 @@ impl App {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Transmit the next KeeLoq rolling code for a barrier/alarm capture (same button, counter+1).
|
||||||
|
pub fn transmit_next_code(&mut self, id: u32) -> Result<()> {
|
||||||
|
use crate::protocols::DecodedSignal;
|
||||||
|
|
||||||
|
if let Some(ref radio) = self.radio {
|
||||||
|
if !radio.supports_tx() {
|
||||||
|
self.last_error = Some("Transmit not available – RTL-SDR is receive-only".to_string());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
self.last_error = Some("No radio device connected".to_string());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let capture = match self.captures.iter().find(|c| c.id == id) {
|
||||||
|
Some(c) => c.clone(),
|
||||||
|
None => {
|
||||||
|
self.last_error = Some(format!("Capture {} not found", id));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if capture.protocol.is_none() {
|
||||||
|
self.last_error = Some("Cannot transmit: unknown protocol".to_string());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let protocol_name = capture.protocol.as_ref().unwrap();
|
||||||
|
let protocol = match self.protocols.get(protocol_name) {
|
||||||
|
Some(p) => p,
|
||||||
|
None => {
|
||||||
|
self.last_error = Some(format!("Protocol {} not supported for encoding", protocol_name));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !protocol.supports_encoding() {
|
||||||
|
self.last_error = Some("Protocol does not support encoding".to_string());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let button = capture.button.unwrap_or(0);
|
||||||
|
let decoded = DecodedSignal {
|
||||||
|
serial: capture.serial,
|
||||||
|
button: capture.button,
|
||||||
|
counter: capture.counter,
|
||||||
|
crc_valid: capture.crc_valid,
|
||||||
|
data: capture.data,
|
||||||
|
data_count_bit: capture.data_count_bit,
|
||||||
|
encoder_capable: true,
|
||||||
|
extra: capture.data_extra,
|
||||||
|
protocol_display_name: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let signal = match protocol.encode(&decoded, button) {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
self.last_error = Some("Failed to encode next code".to_string());
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(ref mut radio) = self.radio {
|
||||||
|
radio.transmit(&signal, capture.frequency)?;
|
||||||
|
self.status_message = Some(format!("Sent next code for capture {} (button {})", id, button));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Delete the currently selected capture (if any). No-op if none selected or list empty.
|
/// Delete the currently selected capture (if any). No-op if none selected or list empty.
|
||||||
pub fn delete_selected_capture(&mut self) -> Result<()> {
|
pub fn delete_selected_capture(&mut self) -> Result<()> {
|
||||||
let id = match self.selected_capture {
|
let id = match self.selected_capture {
|
||||||
@@ -1027,14 +1100,17 @@ impl App {
|
|||||||
// -- Signal Action Menu helpers --
|
// -- Signal Action Menu helpers --
|
||||||
|
|
||||||
/// Signal actions shown in the menu. With HackRF: Replay always; Lock/Unlock/Trunk/Panic only when
|
/// Signal actions shown in the menu. With HackRF: Replay always; Lock/Unlock/Trunk/Panic only when
|
||||||
/// the selected capture is encoder-capable (unknown or decoded-only signals get Replay only).
|
/// the selected capture is encoder-capable and not a barrier/gate/garage or alarm (KeeLoq barrier
|
||||||
/// Without TX (e.g. RTL-SDR): only export and delete.
|
/// and alarm protocols get Replay + export + delete only). Without TX (e.g. RTL-SDR): only export and delete.
|
||||||
pub fn available_signal_actions(&self) -> Vec<SignalAction> {
|
pub fn available_signal_actions(&self) -> Vec<SignalAction> {
|
||||||
let has_tx = self.radio.as_ref().map_or(false, |r| r.supports_tx());
|
let has_tx = self.radio.as_ref().map_or(false, |r| r.supports_tx());
|
||||||
let encoder_capable = self
|
let selected = self
|
||||||
.selected_capture
|
.selected_capture
|
||||||
.and_then(|idx| self.captures.get(idx))
|
.and_then(|idx| self.captures.get(idx));
|
||||||
|
let encoder_capable = selected
|
||||||
.map_or(false, |c| c.status == crate::capture::CaptureStatus::EncoderCapable);
|
.map_or(false, |c| c.status == crate::capture::CaptureStatus::EncoderCapable);
|
||||||
|
let is_non_car_keeloq = selected
|
||||||
|
.map_or(false, |c| is_keeloq_non_car(c.protocol_name()));
|
||||||
|
|
||||||
if !has_tx {
|
if !has_tx {
|
||||||
return SignalAction::ALL
|
return SignalAction::ALL
|
||||||
@@ -1049,6 +1125,17 @@ impl App {
|
|||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Barrier/gate/garage or alarm: Replay, Send next code (encoder next rolling code), export + delete
|
||||||
|
if encoder_capable && is_non_car_keeloq {
|
||||||
|
return vec![
|
||||||
|
SignalAction::Replay,
|
||||||
|
SignalAction::SendNextCode,
|
||||||
|
SignalAction::ExportFob,
|
||||||
|
SignalAction::ExportFlipper,
|
||||||
|
SignalAction::Delete,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
if encoder_capable {
|
if encoder_capable {
|
||||||
SignalAction::ALL.to_vec()
|
SignalAction::ALL.to_vec()
|
||||||
} else {
|
} else {
|
||||||
@@ -1088,6 +1175,9 @@ impl App {
|
|||||||
SignalAction::Replay => {
|
SignalAction::Replay => {
|
||||||
self.replay_capture(capture_id)?;
|
self.replay_capture(capture_id)?;
|
||||||
}
|
}
|
||||||
|
SignalAction::SendNextCode => {
|
||||||
|
self.transmit_next_code(capture_id)?;
|
||||||
|
}
|
||||||
SignalAction::Lock => {
|
SignalAction::Lock => {
|
||||||
let id_str = capture_id.to_string();
|
let id_str = capture_id.to_string();
|
||||||
self.transmit_command(Some(&&*id_str.as_str()), ButtonCommand::Lock)?;
|
self.transmit_command(Some(&&*id_str.as_str()), ButtonCommand::Lock)?;
|
||||||
|
|||||||
@@ -0,0 +1,240 @@
|
|||||||
|
//! KeeLoq barrier/gate/garage and alarm manufacturer names.
|
||||||
|
//! When a decode matches one of these, the signal action menu shows only Replay (and export/delete),
|
||||||
|
//! not TX Lock/Unlock/Trunk/Panic (those are for car keyfobs).
|
||||||
|
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::sync::OnceLock;
|
||||||
|
|
||||||
|
fn normalize(s: &str) -> String {
|
||||||
|
s.chars()
|
||||||
|
.filter(|c| c.is_ascii_alphanumeric())
|
||||||
|
.flat_map(|c| c.to_lowercase())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Barrier/gate/garage door manufacturers (normalized names).
|
||||||
|
/// Covers Flipper/ProtoPirate-style names and common variants (e.g. NICE_MHOUSE, Guard_RF-311A).
|
||||||
|
const BARRIER_NAMES: &[&str] = &[
|
||||||
|
"airforce",
|
||||||
|
"allmatic",
|
||||||
|
"alutechat4n",
|
||||||
|
"ansonic",
|
||||||
|
"aprimatic",
|
||||||
|
"beninca",
|
||||||
|
"benincavaaes128",
|
||||||
|
"bett",
|
||||||
|
"bft",
|
||||||
|
"cameatomo",
|
||||||
|
"cameatomotop44rbn",
|
||||||
|
"camespace",
|
||||||
|
"camestatic",
|
||||||
|
"cameweetwin",
|
||||||
|
"chamberlaincode",
|
||||||
|
"clemsa",
|
||||||
|
"comunello",
|
||||||
|
"deamio",
|
||||||
|
"dickertmahs",
|
||||||
|
"doitrand",
|
||||||
|
"doorhan",
|
||||||
|
"dooya",
|
||||||
|
"dtmneo",
|
||||||
|
"ecostar",
|
||||||
|
"elmespoland",
|
||||||
|
"elplast",
|
||||||
|
"faacrctx",
|
||||||
|
"faacslh",
|
||||||
|
"faacslhspa",
|
||||||
|
"feron",
|
||||||
|
"gatetx",
|
||||||
|
"gbidi",
|
||||||
|
"geniusbravo",
|
||||||
|
"geniusbravoecho",
|
||||||
|
"gibidi",
|
||||||
|
"gsn",
|
||||||
|
"guardrf311a",
|
||||||
|
"gangqi",
|
||||||
|
"hay21",
|
||||||
|
"hollarm",
|
||||||
|
"holtek",
|
||||||
|
"holtekht12x",
|
||||||
|
"honeywell",
|
||||||
|
"honeywellwdb",
|
||||||
|
"hormannbisecur",
|
||||||
|
"hormannhsm",
|
||||||
|
"ido",
|
||||||
|
"intertechnov3",
|
||||||
|
"ironlogic",
|
||||||
|
"jcmtech",
|
||||||
|
"jollymotors",
|
||||||
|
"kingatesstylo4k",
|
||||||
|
"legrand",
|
||||||
|
"linear",
|
||||||
|
"lineardelta3",
|
||||||
|
"magellan",
|
||||||
|
"marantec",
|
||||||
|
"marantec24",
|
||||||
|
"mastercode",
|
||||||
|
"megacode",
|
||||||
|
"merlin",
|
||||||
|
"monarch",
|
||||||
|
"motorline",
|
||||||
|
"mutancode",
|
||||||
|
"mutancomutancode",
|
||||||
|
"neroradio",
|
||||||
|
"nerosketch",
|
||||||
|
"niceflo",
|
||||||
|
"niceflorsone",
|
||||||
|
"nicemhouse",
|
||||||
|
"nicesmilo",
|
||||||
|
"novoferm",
|
||||||
|
"pecninin",
|
||||||
|
"pecnin",
|
||||||
|
"phoenixv2",
|
||||||
|
"powersmart",
|
||||||
|
"prastel",
|
||||||
|
"princeton",
|
||||||
|
"reversrb2",
|
||||||
|
"roger",
|
||||||
|
"rosh",
|
||||||
|
"rossi",
|
||||||
|
"sea",
|
||||||
|
"secplusv1v2",
|
||||||
|
"smc5326",
|
||||||
|
"somfykeytis",
|
||||||
|
"somfytelis",
|
||||||
|
"steelmate",
|
||||||
|
"stilmatic",
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Alarm (car alarm / aftermarket) manufacturers (normalized names).
|
||||||
|
/// Same menu behaviour as barriers: Replay + export + delete only.
|
||||||
|
const ALARM_NAMES: &[&str] = &[
|
||||||
|
"a2a4",
|
||||||
|
"sla2a4",
|
||||||
|
"a6a9",
|
||||||
|
"sla6a9",
|
||||||
|
"alligator",
|
||||||
|
"alligators275",
|
||||||
|
"aps1100",
|
||||||
|
"aps2550",
|
||||||
|
"aps1100aps2550",
|
||||||
|
"b6b9",
|
||||||
|
"slb6b9dop",
|
||||||
|
"cenmaxst5",
|
||||||
|
"cenmaxst7",
|
||||||
|
"cenmax",
|
||||||
|
"cfm",
|
||||||
|
"faraon",
|
||||||
|
"harpoon",
|
||||||
|
"jaguar",
|
||||||
|
"kgd",
|
||||||
|
"leopard",
|
||||||
|
"mongoose",
|
||||||
|
"panteraclk",
|
||||||
|
"panteraxsjaguar",
|
||||||
|
"partisanrx",
|
||||||
|
"pro1",
|
||||||
|
"pro2",
|
||||||
|
"pandorapro2",
|
||||||
|
"reff",
|
||||||
|
"sheriff",
|
||||||
|
"tomahawk9010",
|
||||||
|
"tomahawkzx35",
|
||||||
|
"tomahawktz9030",
|
||||||
|
"tz9030",
|
||||||
|
"starline",
|
||||||
|
"zx730",
|
||||||
|
"zx750",
|
||||||
|
"zx755",
|
||||||
|
"zx930",
|
||||||
|
"zx940",
|
||||||
|
"zx1070",
|
||||||
|
"zx1090",
|
||||||
|
"zx7307501055",
|
||||||
|
];
|
||||||
|
|
||||||
|
fn barrier_set() -> &'static HashSet<String> {
|
||||||
|
static BARRIER_SET: OnceLock<HashSet<String>> = OnceLock::new();
|
||||||
|
BARRIER_SET.get_or_init(|| BARRIER_NAMES.iter().map(|s| (*s).to_string()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn alarm_set() -> &'static HashSet<String> {
|
||||||
|
static ALARM_SET: OnceLock<HashSet<String>> = OnceLock::new();
|
||||||
|
ALARM_SET.get_or_init(|| ALARM_NAMES.iter().map(|s| (*s).to_string()).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the protocol string is KeeLoq with a barrier/gate/garage door manufacturer.
|
||||||
|
/// Protocol is typically "KeeLoq (ManufacturerName)" e.g. "KeeLoq (BFT)" or "KeeLoq (NICE_MHOUSE)".
|
||||||
|
pub fn is_keeloq_barrier(protocol: &str) -> bool {
|
||||||
|
let protocol = protocol.trim();
|
||||||
|
let open = protocol.find(" (");
|
||||||
|
let Some(open_idx) = open else { return false };
|
||||||
|
if !protocol[..open_idx].eq_ignore_ascii_case("keeloq") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let rest = protocol[open_idx + 2..].trim_start();
|
||||||
|
let close = rest.rfind(')');
|
||||||
|
let Some(close_idx) = close else { return false };
|
||||||
|
let inner = rest[..close_idx].trim();
|
||||||
|
if inner.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
barrier_set().contains(&normalize(inner))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true if the protocol string is KeeLoq with an alarm (car alarm / aftermarket) manufacturer.
|
||||||
|
/// Same menu behaviour as barriers: Replay + export + delete only, no TX Lock/Unlock/Trunk/Panic.
|
||||||
|
pub fn is_keeloq_alarm(protocol: &str) -> bool {
|
||||||
|
let protocol = protocol.trim();
|
||||||
|
let open = protocol.find(" (");
|
||||||
|
let Some(open_idx) = open else { return false };
|
||||||
|
if !protocol[..open_idx].eq_ignore_ascii_case("keeloq") {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let rest = protocol[open_idx + 2..].trim_start();
|
||||||
|
let close = rest.rfind(')');
|
||||||
|
let Some(close_idx) = close else { return false };
|
||||||
|
let inner = rest[..close_idx].trim();
|
||||||
|
if inner.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
alarm_set().contains(&normalize(inner))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if this KeeLoq protocol should hide car-style TX actions (barrier/gate or alarm).
|
||||||
|
pub fn is_keeloq_non_car(protocol: &str) -> bool {
|
||||||
|
is_keeloq_barrier(protocol) || is_keeloq_alarm(protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn barrier_detection() {
|
||||||
|
assert!(is_keeloq_barrier("KeeLoq (BFT)"));
|
||||||
|
assert!(is_keeloq_barrier("KeeLoq (DoorHan)"));
|
||||||
|
assert!(is_keeloq_barrier("KeeLoq (NICE_MHOUSE)"));
|
||||||
|
assert!(is_keeloq_barrier("KeeLoq (Guard_RF-311A)"));
|
||||||
|
assert!(is_keeloq_barrier("KeeLoq (Stilmatic)"));
|
||||||
|
assert!(is_keeloq_barrier("KeeLoq (Motorline)"));
|
||||||
|
assert!(!is_keeloq_barrier("KeeLoq (KIAV5)"));
|
||||||
|
assert!(!is_keeloq_barrier("KeeLoq (Star Line)"));
|
||||||
|
assert!(!is_keeloq_barrier("Ford V0"));
|
||||||
|
assert!(!is_keeloq_barrier("Unknown"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alarm_detection() {
|
||||||
|
assert!(is_keeloq_alarm("KeeLoq (Star Line)"));
|
||||||
|
assert!(is_keeloq_alarm("KeeLoq (Pantera_CLK)"));
|
||||||
|
assert!(is_keeloq_alarm("KeeLoq (Sheriff)"));
|
||||||
|
assert!(is_keeloq_alarm("KeeLoq (Alligator_S-275)"));
|
||||||
|
assert!(is_keeloq_alarm("KeeLoq (Harpoon)"));
|
||||||
|
assert!(is_keeloq_alarm("KeeLoq (Partisan_RX)"));
|
||||||
|
assert!(is_keeloq_alarm("KeeLoq (Cenmax_St-7)"));
|
||||||
|
assert!(is_keeloq_alarm("KeeLoq (Reff)"));
|
||||||
|
assert!(!is_keeloq_alarm("KeeLoq (KIAV5)"));
|
||||||
|
assert!(!is_keeloq_alarm("Ford V0"));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,8 @@
|
|||||||
mod common;
|
mod common;
|
||||||
pub mod keeloq_common;
|
pub mod keeloq_common;
|
||||||
mod keeloq;
|
mod keeloq;
|
||||||
|
mod keeloq_barriers;
|
||||||
|
pub use keeloq_barriers::is_keeloq_non_car;
|
||||||
mod keeloq_generic;
|
mod keeloq_generic;
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub mod aut64;
|
pub mod aut64;
|
||||||
|
|||||||
Reference in New Issue
Block a user