mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-05-14 05:25:26 +00:00
Compare commits
27 Commits
experiments
..
1.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
| e419b9865a | |||
| a89cb55529 | |||
| efa653c7cf | |||
| 07957617e5 | |||
| 903104239b | |||
| 291c5320bb | |||
| edbc2f291e | |||
| c32ee61a4f | |||
| 0995609391 | |||
| 29fef56be1 | |||
| 6a348dd304 | |||
| 32a96e580d | |||
| 54f03a39c2 | |||
| a55189e2a4 | |||
| 14d10c0794 | |||
| 27818ccb1f | |||
| 0ebf26eff4 | |||
| ac620e2b0e | |||
| 46115cdf6c | |||
| f465c6edbb | |||
| ad795ae7ef | |||
| efff8d2f2e | |||
| c9c9c74117 | |||
| dc0f30dad9 | |||
| 38f261e23b | |||
| cb1daaa4f1 | |||
| b318b3e9ff |
@@ -8,25 +8,62 @@ on:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
concurrency:
|
||||
group: release
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Get latest semantic version tag
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
git fetch --tags
|
||||
|
||||
LAST_TAG=$(git tag --sort=-v:refname | grep -E '^[0-9]+\.[0-9]+\.[0-9]+$' | head -n 1 || true)
|
||||
|
||||
if [ -z "$LAST_TAG" ]; then
|
||||
NEW_TAG="1.0.0"
|
||||
else
|
||||
IFS='.' read -r MAJOR MINOR PATCH <<< "$LAST_TAG"
|
||||
|
||||
PATCH=$((PATCH + 1))
|
||||
|
||||
NEW_TAG="$MAJOR.$MINOR.$PATCH"
|
||||
fi
|
||||
|
||||
echo "Latest tag: $LAST_TAG"
|
||||
echo "New tag: $NEW_TAG"
|
||||
|
||||
echo "NEW_TAG=$NEW_TAG" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Git tag
|
||||
shell: bash
|
||||
run: |
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||
|
||||
git tag ${{ steps.version.outputs.NEW_TAG }}
|
||||
git push origin ${{ steps.version.outputs.NEW_TAG }}
|
||||
|
||||
- name: Build firmware
|
||||
shell: bash
|
||||
run: |
|
||||
export DIST_SUFFIX=Flipper-ARF
|
||||
chmod +x fbt
|
||||
./fbt COMPACT=1 DEBUG=0 updater_package
|
||||
|
||||
- name: Generate tag name
|
||||
id: tag
|
||||
run: echo "TAG=dev-$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Detect firmware updater
|
||||
id: firmware
|
||||
shell: bash
|
||||
run: |
|
||||
DIR=$(ls -d dist/f7-* | head -n 1)
|
||||
FILE="$DIR/flipper-z-f7-update-Flipper-ARF.tgz"
|
||||
@@ -36,13 +73,18 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Found firmware: $FILE"
|
||||
|
||||
echo "FILE=$FILE" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Create Release
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
tag_name: ${{ steps.tag.outputs.TAG }}
|
||||
name: Dev Build ${{ steps.tag.outputs.TAG }}
|
||||
files: ${{ steps.firmware.outputs.FILE }}
|
||||
tag_name: ${{ steps.version.outputs.NEW_TAG }}
|
||||
name: Release ${{ steps.version.outputs.NEW_TAG }}
|
||||
generate_release_notes: true
|
||||
make_latest: true
|
||||
files: |
|
||||
${{ steps.firmware.outputs.FILE }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
+9
-1
@@ -81,4 +81,12 @@ node_modules/
|
||||
|
||||
#companion app
|
||||
/companion
|
||||
/Flipper-Android-App
|
||||
/Flipper-Android-App
|
||||
|
||||
#WIP not ready to push protocols
|
||||
|
||||
lib/subghz/protocols/subghz_protocol_honda_pandora.c
|
||||
lib/subghz/protocols/honda_rolling.c
|
||||
lib/subghz/protocols/honda_rolling.h
|
||||
lib/subghz/protocols/honda_pandora.c
|
||||
lib/subghz/protocols/honda_pandora.h
|
||||
|
||||
@@ -22,6 +22,7 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
- [Contribution Policy](#contribution-policy)
|
||||
- [Citations & References](#citations--references)
|
||||
- [Disclaimer](#disclaimer)
|
||||
- [Special Thanks](#special-thanks-to-everyone-who-contributes-to-this-project)
|
||||
|
||||
---
|
||||
|
||||
@@ -48,8 +49,9 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
| Porsche | Porsche AG | 433/868 MHz | AM | Yes | Yes | No |
|
||||
| PSA (Peugeot/Citroën/DS) | PSA GROUP | 433 MHz | AM/FM | Yes | Yes | Yes |
|
||||
| Ford | Ford V0 | 315/433 MHz | AM | Yes | Yes | Yes |
|
||||
| Ford | Ford V1 | 315/433 MHz | FM | Yes | Yes | Yes |
|
||||
| Fiat | Fiat SpA | 433 MHz | AM | Yes | Yes | Yes |
|
||||
| Fiat | Marelli/Delphi | 433 MHz | AM | No | Yes | No |
|
||||
| Fiat | Marelli/Delphi | 433 MHz | AM | No | Yes | Yes |
|
||||
| Renault (old models) | Marelli | 433 MHz | AM | No | Yes | No|
|
||||
| Mazda | Siemens (5WK49365D) | 315/433 MHz | AM/FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | KIA/HYU V0 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
@@ -58,13 +60,17 @@ This project may incorporate, adapt, or build upon **other open-source projects*
|
||||
| Kia/Hyundai | KIA/HYU V3/V4 | 315/433 MHz | AM/FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | KIA/HYU V5 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | KIA/HYU V6 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Kia/Hyundai | KIA V7 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Subaru | Subaru | 433 MHz | AM | Yes | Yes | No |
|
||||
| Suzuki | Suzuki | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Mitsubishi | Mitsubishi V0 | 868 MHz | FM | Yes | Yes | No |
|
||||
| Honda | Honda Type A/B | 433 MHz | FM (custom) | Yes | Yes | No |
|
||||
| Starline | Star Line | 433 MHz | AM | Yes | Yes | No |
|
||||
| Scher-Khan | Scher-Khan | 433 MHz | FM | Yes | Yes | No |
|
||||
| Scher-Khan | Magic Code PRO1/PRO2 | 433 MHz | FM | Yes | Yes | Yes |
|
||||
| Sheriff | Sheriff CFM (ZX-750/930) | 433 MHz | AM | Yes | Yes | No |
|
||||
| Chrysler/Dodge/Jeep | FOBIK GQ43VT | 315/433 MHz | AM | Yes | Yes | No |
|
||||
| Honda | Honda Static | 433 MHz | AM | Yes | Yes | No |
|
||||
|
||||
### Gate / Access Protocols
|
||||
|
||||
@@ -343,3 +349,36 @@ THIS SOFTWARE IS PROVIDED **"AS IS,"** WITHOUT ANY WARRANTIES OF ANY KIND, EXPRE
|
||||
IN NO EVENT SHALL THE AUTHORS, COPYRIGHT HOLDERS, OR CONTRIBUTORS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT, OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR ITS USE.
|
||||
|
||||
**ALL RISKS FROM THE USE OR PERFORMANCE OF THIS SOFTWARE REMAIN WITH THE USER.**
|
||||
|
||||
|
||||
---
|
||||
|
||||
## Special thanks to everyone who contributes to this project:
|
||||
|
||||
## Contributors (GitHub)
|
||||
|
||||
<a href="https://github.com/d4c1-labs/Flipper-ARF/graphs/contributors">
|
||||
<img src="https://contrib.rocks/image?repo=d4c1-labs/Flipper-ARF"/>
|
||||
</a>
|
||||
|
||||
## Special Thanks
|
||||
|
||||
<table align="center">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="https://github.com/whatthefxck">
|
||||
<img src="https://avatars.githubusercontent.com/whatthefxck?s=80" width="80" height="80" alt="whatthefxck"/>
|
||||
</a>
|
||||
</td>
|
||||
<td align="center">
|
||||
<a href="https://github.com/zero-mega">
|
||||
<img src="https://avatars.githubusercontent.com/zero-mega?s=80" width="80" height="80" alt="zero-mega"/>
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<p align="center">
|
||||
Special thanks to everyone who contributed code, testing, reversing,
|
||||
research, ideas, captures and documentation.
|
||||
</p>
|
||||
|
||||
@@ -119,3 +119,13 @@ 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: Honda1
|
||||
Custom_preset_module: CC1101
|
||||
# G2 G3 G4 D L0 L1 L2
|
||||
Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 36 10 69 15 32 18 18 19 16 1D 91 1C 00 1B 07 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00
|
||||
|
||||
Custom_preset_name: Honda2
|
||||
Custom_preset_module: CC1101
|
||||
# G2 G3 G4 D L0 L1 L2
|
||||
Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 07 11 36 10 E9 15 32 18 18 19 16 1D 92 1C 40 1B 03 20 FB 22 10 21 56 00 00 C0 00 00 00 00 00 00 00
|
||||
|
||||
@@ -0,0 +1,568 @@
|
||||
/**
|
||||
* auto_rke_protocols.c
|
||||
* Additional automotive RKE protocols — ported from Pandora DXL 5000 firmware
|
||||
* Target: Flipper Zero
|
||||
*
|
||||
* Protocols included (all found in firmware string table 0x0000ecc4-0x0000ee00):
|
||||
* - Subaru (ID 0x06, 433.92 MHz)
|
||||
* - Hyundai/KiaRIO (ID 0x11, 433.92 MHz)
|
||||
* - Mazda Siemens (ID 0x15, 433.92 MHz)
|
||||
* - VAG -2004 (ID 0x19, 433.92 MHz) [VW/Audi/Seat/Skoda pre-2004]
|
||||
* - SantaFe 13-16 (ID 0x1A, 433.92 MHz) [Hyundai Santa Fe 2013-2016]
|
||||
*
|
||||
* All use OOK AM modulation. Timing constants extracted from firmware
|
||||
* FUN_000007cc (period calculator) and FUN_00000840 (timer init).
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
/* =========================================================================
|
||||
* Common helpers
|
||||
* ========================================================================= */
|
||||
|
||||
typedef struct {
|
||||
int32_t pulses[512];
|
||||
uint32_t count;
|
||||
} RawBuf;
|
||||
|
||||
static void raw_push(RawBuf *b, int32_t v)
|
||||
{
|
||||
if (b->count < 512) b->pulses[b->count++] = v;
|
||||
}
|
||||
static void raw_pair(RawBuf *b, uint32_t hi, uint32_t lo)
|
||||
{
|
||||
raw_push(b, (int32_t)hi);
|
||||
raw_push(b, -(int32_t)lo);
|
||||
}
|
||||
static bool in_range(int32_t m, uint32_t ref, uint32_t tol_pct)
|
||||
{
|
||||
int32_t r = (int32_t)ref, d = m - r;
|
||||
if (d < 0) d = -d;
|
||||
return (d * 100) <= (r * (int32_t)tol_pct);
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* 1. SUBARU RKE (firmware case 0x06, protocol type 6, iVar7=0x18)
|
||||
*
|
||||
* Subaru legacy fob (Impreza/Forester/Legacy ~2000-2010):
|
||||
* Freq : 433.92 MHz
|
||||
* Bits : 48 (LSB-first)
|
||||
* Layout: [47:16] 32-bit fixed ID [15:8] rolling counter [7:0] button+cksum
|
||||
* PWM : period 800 µs; 1 = 600 µs HI + 200 µs LO; 0 = 200 µs HI + 600 µs LO
|
||||
* Sync : 8000 µs LOW preamble, then 600 µs HI start-bit
|
||||
* Repeat: 3×
|
||||
* ========================================================================= */
|
||||
|
||||
#define SUBARU_FREQ_HZ 433920000ul
|
||||
#define SUBARU_BITS 48u
|
||||
#define SUBARU_REPEAT 3u
|
||||
#define SUBARU_SYNC_US 8000u
|
||||
#define SUBARU_TOL_PCT 15u
|
||||
#define SUBARU_BIT1_HI_US 600u
|
||||
#define SUBARU_BIT1_LO_US 200u
|
||||
#define SUBARU_BIT0_HI_US 200u
|
||||
#define SUBARU_BIT0_LO_US 600u
|
||||
|
||||
#define SUBARU_BTN_LOCK 0x1u
|
||||
#define SUBARU_BTN_UNLOCK 0x2u
|
||||
#define SUBARU_BTN_TRUNK 0x4u
|
||||
#define SUBARU_BTN_PANIC 0x8u
|
||||
|
||||
typedef struct {
|
||||
uint32_t fixed_id;
|
||||
uint8_t counter;
|
||||
uint8_t button;
|
||||
bool valid;
|
||||
} SubaruFrame;
|
||||
|
||||
static uint8_t subaru_cksum(uint32_t id, uint8_t ctr, uint8_t btn)
|
||||
{
|
||||
/* Simple nibble-XOR checksum (derived from analysis of firmware data loop) */
|
||||
uint8_t c = 0;
|
||||
for (int i = 0; i < 4; i++) c ^= (id >> (i * 8)) & 0xFFu;
|
||||
c ^= ctr ^ (btn & 0xFu);
|
||||
return c & 0xFFu;
|
||||
}
|
||||
|
||||
void subaru_encode(const SubaruFrame *f, RawBuf *buf)
|
||||
{
|
||||
buf->count = 0;
|
||||
uint8_t ck = subaru_cksum(f->fixed_id, f->counter, f->button);
|
||||
|
||||
/* Pack 48 bits LSB-first: fixed_id[31:0] | counter[7:0] | (btn<<4)|ck */
|
||||
uint64_t word = (uint64_t)f->fixed_id |
|
||||
((uint64_t)f->counter << 32) |
|
||||
((uint64_t)((f->button << 4) | ck) << 40);
|
||||
|
||||
for (uint32_t rep = 0; rep < SUBARU_REPEAT; rep++) {
|
||||
/* Sync gap then start burst */
|
||||
raw_push(buf, -(int32_t)SUBARU_SYNC_US);
|
||||
raw_pair(buf, SUBARU_BIT1_HI_US, SUBARU_BIT1_LO_US); /* start bit=1 */
|
||||
|
||||
for (uint32_t b = 0; b < SUBARU_BITS; b++) {
|
||||
bool bit = (word >> b) & 1u;
|
||||
raw_pair(buf,
|
||||
bit ? SUBARU_BIT1_HI_US : SUBARU_BIT0_HI_US,
|
||||
bit ? SUBARU_BIT1_LO_US : SUBARU_BIT0_LO_US);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool subaru_decode(const RawBuf *buf, SubaruFrame *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
for (uint32_t i = 0; i + 1 < buf->count; i++) {
|
||||
/* Sync: long LOW */
|
||||
if (!in_range(-buf->pulses[i], SUBARU_SYNC_US, SUBARU_TOL_PCT)) continue;
|
||||
/* Start bit: long HI */
|
||||
if (i + 1 >= buf->count) continue;
|
||||
if (!in_range(buf->pulses[i + 1], SUBARU_BIT1_HI_US, SUBARU_TOL_PCT)) continue;
|
||||
|
||||
uint32_t j = i + 2; /* skip LOW half of start bit */
|
||||
if (j + 1 >= buf->count) continue;
|
||||
j++; /* skip the LOW portion already in pair */
|
||||
if (j + SUBARU_BITS * 2 > buf->count) continue;
|
||||
|
||||
uint64_t word = 0;
|
||||
bool ok = true;
|
||||
for (uint32_t b = 0; b < SUBARU_BITS; b++) {
|
||||
int32_t hi = buf->pulses[j];
|
||||
int32_t lo = -buf->pulses[j + 1];
|
||||
j += 2;
|
||||
if (in_range(hi, SUBARU_BIT1_HI_US, SUBARU_TOL_PCT) &&
|
||||
in_range(lo, SUBARU_BIT1_LO_US, SUBARU_TOL_PCT)) {
|
||||
word |= (uint64_t)1 << b;
|
||||
} else if (in_range(hi, SUBARU_BIT0_HI_US, SUBARU_TOL_PCT) &&
|
||||
in_range(lo, SUBARU_BIT0_LO_US, SUBARU_TOL_PCT)) {
|
||||
/* bit 0 */
|
||||
} else { ok = false; break; }
|
||||
}
|
||||
if (!ok) continue;
|
||||
|
||||
frame->fixed_id = (uint32_t)(word & 0xFFFFFFFFu);
|
||||
frame->counter = (uint8_t)((word >> 32) & 0xFFu);
|
||||
frame->button = (uint8_t)((word >> 44) & 0xFu);
|
||||
uint8_t rx_ck = (uint8_t)((word >> 40) & 0xFu);
|
||||
frame->valid = (rx_ck == (subaru_cksum(frame->fixed_id, frame->counter, frame->button) & 0xFu));
|
||||
if (frame->valid) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* 2. HYUNDAI / KIA RIO (firmware case 0x11, iVar7=1, 14-bit display)
|
||||
*
|
||||
* Early Hyundai/Kia fobs (Accent, Rio, Elantra ~2001-2008):
|
||||
* Freq : 433.92 MHz
|
||||
* Bits : 64 (MSB-first), plain fixed-code (no rolling — very old fobs)
|
||||
* Layout: [63:32] 32-bit serial [31:16] 16-bit button mask repeated
|
||||
* [15:0] ~16-bit checksum (XOR block)
|
||||
* PWM : period 1040 µs; 1 = 728 µs HI + 312 µs LO; 0 = 312 µs HI + 728 µs LO
|
||||
* Sync : 312 µs HI + 10400 µs LO
|
||||
* Repeat: 3×
|
||||
* ========================================================================= */
|
||||
|
||||
#define HKR_BITS 64u
|
||||
#define HKR_REPEAT 3u
|
||||
#define HKR_SYNC_HI_US 312u
|
||||
#define HKR_SYNC_LO_US 10400u
|
||||
#define HKR_BIT1_HI_US 728u
|
||||
#define HKR_BIT1_LO_US 312u
|
||||
#define HKR_BIT0_HI_US 312u
|
||||
#define HKR_BIT0_LO_US 728u
|
||||
#define HKR_TOL_PCT 15u
|
||||
#define HKR_GAP_US 10000u
|
||||
|
||||
#define HKR_BTN_LOCK 0x0100u
|
||||
#define HKR_BTN_UNLOCK 0x0200u
|
||||
#define HKR_BTN_TRUNK 0x0400u
|
||||
#define HKR_BTN_PANIC 0x0800u
|
||||
|
||||
typedef struct {
|
||||
uint32_t serial;
|
||||
uint16_t button_mask;
|
||||
bool valid;
|
||||
} HKRFrame;
|
||||
|
||||
static uint16_t hkr_cksum(uint32_t serial, uint16_t btn)
|
||||
{
|
||||
uint16_t c = (uint16_t)(serial ^ (serial >> 16));
|
||||
c ^= btn;
|
||||
return ~c;
|
||||
}
|
||||
|
||||
void hkr_encode(const HKRFrame *f, RawBuf *buf)
|
||||
{
|
||||
buf->count = 0;
|
||||
uint16_t ck = hkr_cksum(f->serial, f->button_mask);
|
||||
uint64_t word = ((uint64_t)f->serial << 32) |
|
||||
((uint64_t)f->button_mask << 16) |
|
||||
(uint64_t)ck;
|
||||
|
||||
for (uint32_t rep = 0; rep < HKR_REPEAT; rep++) {
|
||||
raw_pair(buf, HKR_SYNC_HI_US, HKR_SYNC_LO_US);
|
||||
for (int b = 63; b >= 0; b--) {
|
||||
bool bit = (word >> b) & 1u;
|
||||
raw_pair(buf,
|
||||
bit ? HKR_BIT1_HI_US : HKR_BIT0_HI_US,
|
||||
bit ? HKR_BIT1_LO_US : HKR_BIT0_LO_US);
|
||||
}
|
||||
raw_push(buf, -(int32_t)HKR_GAP_US);
|
||||
}
|
||||
}
|
||||
|
||||
bool hkr_decode(const RawBuf *buf, HKRFrame *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
for (uint32_t i = 0; i + 1 < buf->count; i++) {
|
||||
if (!in_range( buf->pulses[i], HKR_SYNC_HI_US, HKR_TOL_PCT)) continue;
|
||||
if (!in_range(-buf->pulses[i + 1], HKR_SYNC_LO_US, HKR_TOL_PCT)) continue;
|
||||
uint32_t j = i + 2;
|
||||
if (j + HKR_BITS * 2 > buf->count) continue;
|
||||
uint64_t word = 0;
|
||||
bool ok = true;
|
||||
for (int b = 63; b >= 0; b--) {
|
||||
int32_t hi = buf->pulses[j];
|
||||
int32_t lo = -buf->pulses[j + 1];
|
||||
j += 2;
|
||||
if (in_range(hi, HKR_BIT1_HI_US, HKR_TOL_PCT) && in_range(lo, HKR_BIT1_LO_US, HKR_TOL_PCT)) word |= (uint64_t)1 << b;
|
||||
else if (in_range(hi, HKR_BIT0_HI_US, HKR_TOL_PCT) && in_range(lo, HKR_BIT0_LO_US, HKR_TOL_PCT)) { /* 0 */ }
|
||||
else { ok = false; break; }
|
||||
}
|
||||
if (!ok) continue;
|
||||
frame->serial = (uint32_t)(word >> 32);
|
||||
frame->button_mask = (uint16_t)((word >> 16) & 0xFFFFu);
|
||||
uint16_t rx_ck = (uint16_t)(word & 0xFFFFu);
|
||||
frame->valid = (rx_ck == hkr_cksum(frame->serial, frame->button_mask));
|
||||
if (frame->valid) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* 3. MAZDA SIEMENS RKE (firmware case 0x15, iVar7=5, 13-bit display)
|
||||
*
|
||||
* Mazda 3/6/CX-7 with Siemens VDO fob (~2003-2009):
|
||||
* Freq : 433.92 MHz
|
||||
* Bits : 72 (MSB-first), Siemens rolling code
|
||||
* Layout: [71:40] 32-bit hop (Siemens proprietary cipher)
|
||||
* [39:16] 24-bit serial
|
||||
* [15:8] 8-bit counter (low byte)
|
||||
* [7:4] 4-bit button [3:0] 4-bit checksum
|
||||
* PWM : 1 = 450 µs HI + 1350 µs LO; 0 = 450 µs HI + 450 µs LO
|
||||
* Sync : 450 µs HI + 14400 µs LO
|
||||
* Repeat: 2×
|
||||
* ========================================================================= */
|
||||
|
||||
#define MAZ_BITS 72u
|
||||
#define MAZ_REPEAT 2u
|
||||
#define MAZ_SYNC_HI_US 450u
|
||||
#define MAZ_SYNC_LO_US 14400u
|
||||
#define MAZ_BIT1_HI_US 450u
|
||||
#define MAZ_BIT1_LO_US 1350u
|
||||
#define MAZ_BIT0_HI_US 450u
|
||||
#define MAZ_BIT0_LO_US 450u
|
||||
#define MAZ_GAP_US 20000u
|
||||
#define MAZ_TOL_PCT 15u
|
||||
|
||||
#define MAZ_BTN_LOCK 0x1u
|
||||
#define MAZ_BTN_UNLOCK 0x2u
|
||||
#define MAZ_BTN_TRUNK 0x4u
|
||||
|
||||
typedef struct {
|
||||
uint32_t hop; /* Siemens encrypted hopping word — decrypt separately */
|
||||
uint32_t serial; /* 24-bit */
|
||||
uint8_t counter;
|
||||
uint8_t button;
|
||||
bool valid;
|
||||
} MazdaFrame;
|
||||
|
||||
static uint8_t maz_cksum(uint32_t hop, uint32_t serial, uint8_t ctr, uint8_t btn)
|
||||
{
|
||||
uint8_t c = 0;
|
||||
for (int i = 0; i < 4; i++) c ^= (hop >> (i * 8)) & 0xFFu;
|
||||
for (int i = 0; i < 3; i++) c ^= (serial >> (i * 8)) & 0xFFu;
|
||||
c ^= ctr ^ (btn & 0xFu);
|
||||
return c & 0xFu;
|
||||
}
|
||||
|
||||
void mazda_encode(const MazdaFrame *f, RawBuf *buf)
|
||||
{
|
||||
buf->count = 0;
|
||||
uint8_t ck = maz_cksum(f->hop, f->serial, f->counter, f->button);
|
||||
/* Pack 72 bits into 9 bytes, MSB of hop is bit 71 */
|
||||
uint8_t pkt[9];
|
||||
pkt[0] = (f->hop >> 24) & 0xFFu;
|
||||
pkt[1] = (f->hop >> 16) & 0xFFu;
|
||||
pkt[2] = (f->hop >> 8) & 0xFFu;
|
||||
pkt[3] = f->hop & 0xFFu;
|
||||
pkt[4] = (f->serial >> 16) & 0xFFu;
|
||||
pkt[5] = (f->serial >> 8) & 0xFFu;
|
||||
pkt[6] = f->serial & 0xFFu;
|
||||
pkt[7] = f->counter;
|
||||
pkt[8] = (uint8_t)((f->button << 4) | ck);
|
||||
|
||||
for (uint32_t rep = 0; rep < MAZ_REPEAT; rep++) {
|
||||
raw_pair(buf, MAZ_SYNC_HI_US, MAZ_SYNC_LO_US);
|
||||
for (int byte = 0; byte < 9; byte++) {
|
||||
for (int bit = 7; bit >= 0; bit--) {
|
||||
bool b = (pkt[byte] >> bit) & 1u;
|
||||
raw_pair(buf,
|
||||
b ? MAZ_BIT1_HI_US : MAZ_BIT0_HI_US,
|
||||
b ? MAZ_BIT1_LO_US : MAZ_BIT0_LO_US);
|
||||
}
|
||||
}
|
||||
raw_push(buf, -(int32_t)MAZ_GAP_US);
|
||||
}
|
||||
}
|
||||
|
||||
bool mazda_decode(const RawBuf *buf, MazdaFrame *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
for (uint32_t i = 0; i + 1 < buf->count; i++) {
|
||||
if (!in_range( buf->pulses[i], MAZ_SYNC_HI_US, MAZ_TOL_PCT)) continue;
|
||||
if (!in_range(-buf->pulses[i + 1], MAZ_SYNC_LO_US, MAZ_TOL_PCT)) continue;
|
||||
uint32_t j = i + 2;
|
||||
if (j + MAZ_BITS * 2 > buf->count) continue;
|
||||
uint8_t pkt[9] = {0};
|
||||
bool ok = true;
|
||||
for (uint32_t bt = 0; bt < MAZ_BITS; bt++) {
|
||||
int32_t hi = buf->pulses[j];
|
||||
int32_t lo = -buf->pulses[j + 1];
|
||||
j += 2;
|
||||
bool b;
|
||||
if (in_range(lo, MAZ_BIT1_LO_US, MAZ_TOL_PCT)) b = true;
|
||||
else if (in_range(lo, MAZ_BIT0_LO_US, MAZ_TOL_PCT)) b = false;
|
||||
else { ok = false; break; }
|
||||
(void)hi;
|
||||
if (b) pkt[bt / 8] |= (uint8_t)(1u << (7 - (bt % 8)));
|
||||
}
|
||||
if (!ok) continue;
|
||||
frame->hop = ((uint32_t)pkt[0]<<24)|((uint32_t)pkt[1]<<16)|((uint32_t)pkt[2]<<8)|pkt[3];
|
||||
frame->serial = ((uint32_t)pkt[4]<<16)|((uint32_t)pkt[5]<<8)|pkt[6];
|
||||
frame->counter = pkt[7];
|
||||
frame->button = pkt[8] >> 4;
|
||||
uint8_t rx_ck = pkt[8] & 0xFu;
|
||||
frame->valid = (rx_ck == maz_cksum(frame->hop, frame->serial, frame->counter, frame->button));
|
||||
if (frame->valid) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* 4. VAG -2004 (firmware case 0x19, Princeton-style, ID 0x19)
|
||||
*
|
||||
* VW/Audi/Seat/Skoda fobs before 2004 (ID48 era, 3-button):
|
||||
* Freq : 433.92 MHz
|
||||
* Bits : 64 (MSB-first), simple rolling code (16-bit counter)
|
||||
* Layout: [63:32] 32-bit fixed transponder ID
|
||||
* [31:16] 16-bit counter
|
||||
* [15:8] 8-bit button+flags
|
||||
* [7:0] 8-bit checksum (sum of all prior bytes mod 256, inverted)
|
||||
* PWM : period 800 µs; 1 = 550 µs HI + 250 µs LO; 0 = 250 µs HI + 550 µs LO
|
||||
* Sync : 550 µs HI + 11000 µs LO
|
||||
* Repeat: 3×
|
||||
* ========================================================================= */
|
||||
|
||||
#define VAG_BITS 64u
|
||||
#define VAG_REPEAT 3u
|
||||
#define VAG_SYNC_HI_US 550u
|
||||
#define VAG_SYNC_LO_US 11000u
|
||||
#define VAG_BIT1_HI_US 550u
|
||||
#define VAG_BIT1_LO_US 250u
|
||||
#define VAG_BIT0_HI_US 250u
|
||||
#define VAG_BIT0_LO_US 550u
|
||||
#define VAG_GAP_US 9000u
|
||||
#define VAG_TOL_PCT 15u
|
||||
|
||||
#define VAG_BTN_LOCK 0x01u
|
||||
#define VAG_BTN_UNLOCK 0x02u
|
||||
#define VAG_BTN_TRUNK 0x04u
|
||||
#define VAG_BTN_PANIC 0x08u
|
||||
|
||||
typedef struct {
|
||||
uint32_t transponder_id;
|
||||
uint16_t counter;
|
||||
uint8_t button;
|
||||
bool valid;
|
||||
} VAGFrame;
|
||||
|
||||
static uint8_t vag_cksum(uint32_t tid, uint16_t ctr, uint8_t btn)
|
||||
{
|
||||
uint8_t s = 0;
|
||||
s += (tid >> 24) & 0xFFu;
|
||||
s += (tid >> 16) & 0xFFu;
|
||||
s += (tid >> 8) & 0xFFu;
|
||||
s += tid & 0xFFu;
|
||||
s += (ctr >> 8) & 0xFFu;
|
||||
s += ctr & 0xFFu;
|
||||
s += btn;
|
||||
return (uint8_t)(~s);
|
||||
}
|
||||
|
||||
void vag_encode(const VAGFrame *f, RawBuf *buf)
|
||||
{
|
||||
buf->count = 0;
|
||||
uint8_t ck = vag_cksum(f->transponder_id, f->counter, f->button);
|
||||
uint64_t word = ((uint64_t)f->transponder_id << 32) |
|
||||
((uint64_t)f->counter << 16) |
|
||||
((uint64_t)f->button << 8) |
|
||||
ck;
|
||||
|
||||
for (uint32_t rep = 0; rep < VAG_REPEAT; rep++) {
|
||||
raw_pair(buf, VAG_SYNC_HI_US, VAG_SYNC_LO_US);
|
||||
for (int b = 63; b >= 0; b--) {
|
||||
bool bit = (word >> b) & 1u;
|
||||
raw_pair(buf,
|
||||
bit ? VAG_BIT1_HI_US : VAG_BIT0_HI_US,
|
||||
bit ? VAG_BIT1_LO_US : VAG_BIT0_LO_US);
|
||||
}
|
||||
raw_push(buf, -(int32_t)VAG_GAP_US);
|
||||
}
|
||||
}
|
||||
|
||||
bool vag_decode(const RawBuf *buf, VAGFrame *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
for (uint32_t i = 0; i + 1 < buf->count; i++) {
|
||||
if (!in_range( buf->pulses[i], VAG_SYNC_HI_US, VAG_TOL_PCT)) continue;
|
||||
if (!in_range(-buf->pulses[i + 1], VAG_SYNC_LO_US, VAG_TOL_PCT)) continue;
|
||||
uint32_t j = i + 2;
|
||||
if (j + VAG_BITS * 2 > buf->count) continue;
|
||||
uint64_t word = 0;
|
||||
bool ok = true;
|
||||
for (int b = 63; b >= 0; b--) {
|
||||
int32_t hi = buf->pulses[j];
|
||||
int32_t lo = -buf->pulses[j + 1];
|
||||
j += 2;
|
||||
if (in_range(hi, VAG_BIT1_HI_US, VAG_TOL_PCT) && in_range(lo, VAG_BIT1_LO_US, VAG_TOL_PCT)) word |= (uint64_t)1 << b;
|
||||
else if (in_range(hi, VAG_BIT0_HI_US, VAG_TOL_PCT) && in_range(lo, VAG_BIT0_LO_US, VAG_TOL_PCT)) {}
|
||||
else { ok = false; break; }
|
||||
}
|
||||
if (!ok) continue;
|
||||
frame->transponder_id = (uint32_t)(word >> 32);
|
||||
frame->counter = (uint16_t)((word >> 16) & 0xFFFFu);
|
||||
frame->button = (uint8_t) ((word >> 8) & 0xFFu);
|
||||
uint8_t rx_ck = (uint8_t) (word & 0xFFu);
|
||||
frame->valid = (rx_ck == vag_cksum(frame->transponder_id, frame->counter, frame->button));
|
||||
if (frame->valid) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* 5. HYUNDAI SANTA FE 2013-2016 (firmware case 0x1A, paired with HU Solaris)
|
||||
*
|
||||
* Hyundai Santa Fe / Solaris RKE (TRW fob variant):
|
||||
* Freq : 433.92 MHz
|
||||
* Bits : 80 (MSB-first)
|
||||
* Layout: [79:48] 32-bit rolling code (Hitag2 derived)
|
||||
* [47:24] 24-bit serial
|
||||
* [23:16] 8-bit counter
|
||||
* [15:8] 8-bit button flags
|
||||
* [7:0] 8-bit CRC8 (poly 0x31, init 0xFF)
|
||||
* PWM : period 500 µs; 1 = 375 µs HI + 125 µs LO; 0 = 125 µs HI + 375 µs LO
|
||||
* Sync : 375 µs HI + 12000 µs LO
|
||||
* Repeat: 3×
|
||||
* ========================================================================= */
|
||||
|
||||
#define SFE_BITS 80u
|
||||
#define SFE_REPEAT 3u
|
||||
#define SFE_SYNC_HI_US 375u
|
||||
#define SFE_SYNC_LO_US 12000u
|
||||
#define SFE_BIT1_HI_US 375u
|
||||
#define SFE_BIT1_LO_US 125u
|
||||
#define SFE_BIT0_HI_US 125u
|
||||
#define SFE_BIT0_LO_US 375u
|
||||
#define SFE_GAP_US 15000u
|
||||
#define SFE_TOL_PCT 15u
|
||||
|
||||
#define SFE_BTN_LOCK 0x01u
|
||||
#define SFE_BTN_UNLOCK 0x02u
|
||||
#define SFE_BTN_TRUNK 0x04u
|
||||
#define SFE_BTN_PANIC 0x08u
|
||||
|
||||
typedef struct {
|
||||
uint32_t rolling; /* Hitag2-derived ciphertext — decrypt separately */
|
||||
uint32_t serial; /* 24-bit */
|
||||
uint8_t counter;
|
||||
uint8_t button;
|
||||
bool valid;
|
||||
} SantaFeFrame;
|
||||
|
||||
static uint8_t sfe_crc8(const uint8_t *data, uint32_t len)
|
||||
{
|
||||
uint8_t crc = 0xFFu;
|
||||
for (uint32_t i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
for (int b = 0; b < 8; b++) {
|
||||
if (crc & 0x80u) crc = (uint8_t)((crc << 1) ^ 0x31u);
|
||||
else crc = (uint8_t) (crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
void santafe_encode(const SantaFeFrame *f, RawBuf *buf)
|
||||
{
|
||||
buf->count = 0;
|
||||
uint8_t pkt[10];
|
||||
pkt[0] = (f->rolling >> 24) & 0xFFu;
|
||||
pkt[1] = (f->rolling >> 16) & 0xFFu;
|
||||
pkt[2] = (f->rolling >> 8) & 0xFFu;
|
||||
pkt[3] = f->rolling & 0xFFu;
|
||||
pkt[4] = (f->serial >> 16) & 0xFFu;
|
||||
pkt[5] = (f->serial >> 8) & 0xFFu;
|
||||
pkt[6] = f->serial & 0xFFu;
|
||||
pkt[7] = f->counter;
|
||||
pkt[8] = f->button;
|
||||
pkt[9] = sfe_crc8(pkt, 9);
|
||||
|
||||
for (uint32_t rep = 0; rep < SFE_REPEAT; rep++) {
|
||||
raw_pair(buf, SFE_SYNC_HI_US, SFE_SYNC_LO_US);
|
||||
for (int byte = 0; byte < 10; byte++) {
|
||||
for (int bit = 7; bit >= 0; bit--) {
|
||||
bool b = (pkt[byte] >> bit) & 1u;
|
||||
raw_pair(buf,
|
||||
b ? SFE_BIT1_HI_US : SFE_BIT0_HI_US,
|
||||
b ? SFE_BIT1_LO_US : SFE_BIT0_LO_US);
|
||||
}
|
||||
}
|
||||
raw_push(buf, -(int32_t)SFE_GAP_US);
|
||||
}
|
||||
}
|
||||
|
||||
bool santafe_decode(const RawBuf *buf, SantaFeFrame *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
for (uint32_t i = 0; i + 1 < buf->count; i++) {
|
||||
if (!in_range( buf->pulses[i], SFE_SYNC_HI_US, SFE_TOL_PCT)) continue;
|
||||
if (!in_range(-buf->pulses[i + 1], SFE_SYNC_LO_US, SFE_TOL_PCT)) continue;
|
||||
uint32_t j = i + 2;
|
||||
if (j + SFE_BITS * 2 > buf->count) continue;
|
||||
uint8_t pkt[10] = {0};
|
||||
bool ok = true;
|
||||
for (uint32_t bt = 0; bt < SFE_BITS; bt++) {
|
||||
int32_t hi = buf->pulses[j];
|
||||
int32_t lo = -buf->pulses[j + 1];
|
||||
j += 2;
|
||||
bool b;
|
||||
if (in_range(lo, SFE_BIT1_LO_US, SFE_TOL_PCT)) b = true;
|
||||
else if (in_range(lo, SFE_BIT0_LO_US, SFE_TOL_PCT)) b = false;
|
||||
else { ok = false; break; }
|
||||
(void)hi;
|
||||
if (b) pkt[bt / 8] |= (uint8_t)(1u << (7 - (bt % 8)));
|
||||
}
|
||||
if (!ok) continue;
|
||||
frame->rolling = ((uint32_t)pkt[0]<<24)|((uint32_t)pkt[1]<<16)|((uint32_t)pkt[2]<<8)|pkt[3];
|
||||
frame->serial = ((uint32_t)pkt[4]<<16)|((uint32_t)pkt[5]<<8)|pkt[6];
|
||||
frame->counter = pkt[7];
|
||||
frame->button = pkt[8];
|
||||
uint8_t rx_crc = pkt[9];
|
||||
frame->valid = (rx_crc == sfe_crc8(pkt, 9));
|
||||
if (frame->valid) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
#pragma once
|
||||
/**
|
||||
* auto_rke_protocols.h
|
||||
* Additional automotive RKE protocols — Pandora DXL 5000 → Flipper Zero port
|
||||
*
|
||||
* Protocols:
|
||||
* - Subaru (ID 0x06) | 433.92 MHz | 48-bit | OOK PWM
|
||||
* - Hyundai/KiaRIO (ID 0x11) | 433.92 MHz | 64-bit | OOK PWM
|
||||
* - Mazda Siemens (ID 0x15) | 433.92 MHz | 72-bit | OOK PWM
|
||||
* - VAG -2004 (ID 0x19) | 433.92 MHz | 64-bit | OOK PWM
|
||||
* - SantaFe 13-16 (ID 0x1A) | 433.92 MHz | 80-bit | OOK PWM
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* =========================================================================
|
||||
* Shared raw pulse buffer
|
||||
* All encode functions write into RawBuf.
|
||||
* All decode functions read from RawBuf.
|
||||
* positive value = HIGH duration in µs
|
||||
* negative value = LOW duration in µs
|
||||
* ========================================================================= */
|
||||
typedef struct {
|
||||
int32_t pulses[512];
|
||||
uint32_t count;
|
||||
} RawBuf;
|
||||
|
||||
/* =========================================================================
|
||||
* 1. SUBARU (firmware ID 0x06)
|
||||
* ========================================================================= */
|
||||
|
||||
#define SUBARU_FREQ_HZ 433920000ul
|
||||
#define SUBARU_BITS 48u
|
||||
#define SUBARU_REPEAT 3u
|
||||
#define SUBARU_SYNC_US 8000u
|
||||
#define SUBARU_BIT1_HI_US 600u
|
||||
#define SUBARU_BIT1_LO_US 200u
|
||||
#define SUBARU_BIT0_HI_US 200u
|
||||
#define SUBARU_BIT0_LO_US 600u
|
||||
#define SUBARU_TOL_PCT 15u
|
||||
|
||||
#define SUBARU_BTN_LOCK 0x1u
|
||||
#define SUBARU_BTN_UNLOCK 0x2u
|
||||
#define SUBARU_BTN_TRUNK 0x4u
|
||||
#define SUBARU_BTN_PANIC 0x8u
|
||||
|
||||
/** Subaru RKE frame (Impreza/Forester/Legacy ~2000-2010) */
|
||||
typedef struct {
|
||||
uint32_t fixed_id; /**< 32-bit fixed fob ID */
|
||||
uint8_t counter; /**< 8-bit rolling counter */
|
||||
uint8_t button; /**< SUBARU_BTN_* */
|
||||
bool valid; /**< true after decode if checksum matched */
|
||||
} SubaruFrame;
|
||||
|
||||
void subaru_encode(const SubaruFrame *frame, RawBuf *buf);
|
||||
bool subaru_decode(const RawBuf *buf, SubaruFrame *frame);
|
||||
|
||||
/* =========================================================================
|
||||
* 2. HYUNDAI / KIA RIO (firmware ID 0x11)
|
||||
* ========================================================================= */
|
||||
|
||||
#define HKR_FREQ_HZ 433920000ul
|
||||
#define HKR_BITS 64u
|
||||
#define HKR_REPEAT 3u
|
||||
#define HKR_SYNC_HI_US 312u
|
||||
#define HKR_SYNC_LO_US 10400u
|
||||
#define HKR_BIT1_HI_US 728u
|
||||
#define HKR_BIT1_LO_US 312u
|
||||
#define HKR_BIT0_HI_US 312u
|
||||
#define HKR_BIT0_LO_US 728u
|
||||
#define HKR_GAP_US 10000u
|
||||
#define HKR_TOL_PCT 15u
|
||||
|
||||
#define HKR_BTN_LOCK 0x0100u
|
||||
#define HKR_BTN_UNLOCK 0x0200u
|
||||
#define HKR_BTN_TRUNK 0x0400u
|
||||
#define HKR_BTN_PANIC 0x0800u
|
||||
|
||||
/** Hyundai/Kia RIO RKE frame (Accent/Rio/Elantra ~2001-2008, fixed code) */
|
||||
typedef struct {
|
||||
uint32_t serial; /**< 32-bit fixed serial */
|
||||
uint16_t button_mask; /**< HKR_BTN_* bitmask */
|
||||
bool valid; /**< true after decode if checksum matched */
|
||||
} HKRFrame;
|
||||
|
||||
void hkr_encode(const HKRFrame *frame, RawBuf *buf);
|
||||
bool hkr_decode(const RawBuf *buf, HKRFrame *frame);
|
||||
|
||||
/* =========================================================================
|
||||
* 3. MAZDA SIEMENS (firmware ID 0x15)
|
||||
* ========================================================================= */
|
||||
|
||||
#define MAZ_FREQ_HZ 433920000ul
|
||||
#define MAZ_BITS 72u
|
||||
#define MAZ_REPEAT 2u
|
||||
#define MAZ_SYNC_HI_US 450u
|
||||
#define MAZ_SYNC_LO_US 14400u
|
||||
#define MAZ_BIT1_HI_US 450u
|
||||
#define MAZ_BIT1_LO_US 1350u
|
||||
#define MAZ_BIT0_HI_US 450u
|
||||
#define MAZ_BIT0_LO_US 450u
|
||||
#define MAZ_GAP_US 20000u
|
||||
#define MAZ_TOL_PCT 15u
|
||||
|
||||
#define MAZ_BTN_LOCK 0x1u
|
||||
#define MAZ_BTN_UNLOCK 0x2u
|
||||
#define MAZ_BTN_TRUNK 0x4u
|
||||
|
||||
/**
|
||||
* Mazda Siemens VDO RKE frame (Mazda 3/6/CX-7 ~2003-2009).
|
||||
* hop is the raw Siemens ciphertext — inner cipher is proprietary.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t hop; /**< 32-bit Siemens encrypted hopping word */
|
||||
uint32_t serial; /**< 24-bit fixed serial */
|
||||
uint8_t counter; /**< 8-bit rolling counter */
|
||||
uint8_t button; /**< MAZ_BTN_* */
|
||||
bool valid; /**< true after decode if checksum matched */
|
||||
} MazdaFrame;
|
||||
|
||||
void mazda_encode(const MazdaFrame *frame, RawBuf *buf);
|
||||
bool mazda_decode(const RawBuf *buf, MazdaFrame *frame);
|
||||
|
||||
/* =========================================================================
|
||||
* 4. VAG -2004 (firmware ID 0x19)
|
||||
* ========================================================================= */
|
||||
|
||||
#define VAG_FREQ_HZ 433920000ul
|
||||
#define VAG_BITS 64u
|
||||
#define VAG_REPEAT 3u
|
||||
#define VAG_SYNC_HI_US 550u
|
||||
#define VAG_SYNC_LO_US 11000u
|
||||
#define VAG_BIT1_HI_US 550u
|
||||
#define VAG_BIT1_LO_US 250u
|
||||
#define VAG_BIT0_HI_US 250u
|
||||
#define VAG_BIT0_LO_US 550u
|
||||
#define VAG_GAP_US 9000u
|
||||
#define VAG_TOL_PCT 15u
|
||||
|
||||
#define VAG_BTN_LOCK 0x01u
|
||||
#define VAG_BTN_UNLOCK 0x02u
|
||||
#define VAG_BTN_TRUNK 0x04u
|
||||
#define VAG_BTN_PANIC 0x08u
|
||||
|
||||
/** VW/Audi/Seat/Skoda pre-2004 RKE frame */
|
||||
typedef struct {
|
||||
uint32_t transponder_id; /**< 32-bit fixed transponder ID */
|
||||
uint16_t counter; /**< 16-bit rolling counter */
|
||||
uint8_t button; /**< VAG_BTN_* */
|
||||
bool valid; /**< true after decode if checksum matched */
|
||||
} VAGFrame;
|
||||
|
||||
void vag_encode(const VAGFrame *frame, RawBuf *buf);
|
||||
bool vag_decode(const RawBuf *buf, VAGFrame *frame);
|
||||
|
||||
/** Counter window validation — VAG accepts [stored+1, stored+255] */
|
||||
static inline bool vag_counter_valid(uint16_t stored, uint16_t received) {
|
||||
uint16_t delta = (uint16_t)(received - stored);
|
||||
return (delta >= 1u && delta <= 255u);
|
||||
}
|
||||
|
||||
/* =========================================================================
|
||||
* 5. HYUNDAI SANTA FE 2013-2016 (firmware ID 0x1A)
|
||||
* ========================================================================= */
|
||||
|
||||
#define SFE_FREQ_HZ 433920000ul
|
||||
#define SFE_BITS 80u
|
||||
#define SFE_REPEAT 3u
|
||||
#define SFE_SYNC_HI_US 375u
|
||||
#define SFE_SYNC_LO_US 12000u
|
||||
#define SFE_BIT1_HI_US 375u
|
||||
#define SFE_BIT1_LO_US 125u
|
||||
#define SFE_BIT0_HI_US 125u
|
||||
#define SFE_BIT0_LO_US 375u
|
||||
#define SFE_GAP_US 15000u
|
||||
#define SFE_TOL_PCT 15u
|
||||
|
||||
#define SFE_BTN_LOCK 0x01u
|
||||
#define SFE_BTN_UNLOCK 0x02u
|
||||
#define SFE_BTN_TRUNK 0x04u
|
||||
#define SFE_BTN_PANIC 0x08u
|
||||
|
||||
/**
|
||||
* Hyundai Santa Fe / Solaris RKE frame (TRW fob ~2013-2016).
|
||||
* rolling is Hitag2-derived ciphertext.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t rolling; /**< 32-bit Hitag2-derived encrypted word */
|
||||
uint32_t serial; /**< 24-bit fixed serial */
|
||||
uint8_t counter; /**< 8-bit rolling counter */
|
||||
uint8_t button; /**< SFE_BTN_* */
|
||||
bool valid; /**< true after decode if CRC8 matched */
|
||||
} SantaFeFrame;
|
||||
|
||||
void santafe_encode(const SantaFeFrame *frame, RawBuf *buf);
|
||||
bool santafe_decode(const RawBuf *buf, SantaFeFrame *frame);
|
||||
|
||||
/** Counter window validation — SantaFe accepts [stored+1, stored+32] */
|
||||
static inline bool santafe_counter_valid(uint8_t stored, uint8_t received) {
|
||||
uint8_t delta = (uint8_t)(received - stored);
|
||||
return (delta >= 1u && delta <= 32u);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,305 @@
|
||||
#include "bmw_cas4.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#define TAG "BmwCas4"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_bmw_cas4_const = {
|
||||
.te_short = 500,
|
||||
.te_long = 1000,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
#define BMW_CAS4_PREAMBLE_PULSE_MIN 300u
|
||||
#define BMW_CAS4_PREAMBLE_PULSE_MAX 700u
|
||||
#define BMW_CAS4_PREAMBLE_MIN 10u
|
||||
#define BMW_CAS4_DATA_BITS 64u
|
||||
#define BMW_CAS4_GAP_MIN 1800u
|
||||
#define BMW_CAS4_BYTE0_MARKER 0x30u
|
||||
#define BMW_CAS4_BYTE6_MARKER 0xC5u
|
||||
|
||||
struct SubGhzProtocolDecoderBmwCas4 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
ManchesterState manchester_state;
|
||||
uint8_t decoder_state;
|
||||
uint16_t preamble_count;
|
||||
uint8_t raw_data[8];
|
||||
uint8_t bit_count;
|
||||
uint32_t te_last;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderBmwCas4 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
BmwCas4DecoderStepReset = 0,
|
||||
BmwCas4DecoderStepPreamble,
|
||||
BmwCas4DecoderStepData,
|
||||
} BmwCas4DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_bmw_cas4_decoder = {
|
||||
.alloc = subghz_protocol_decoder_bmw_cas4_alloc,
|
||||
.free = subghz_protocol_decoder_bmw_cas4_free,
|
||||
.feed = subghz_protocol_decoder_bmw_cas4_feed,
|
||||
.reset = subghz_protocol_decoder_bmw_cas4_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_bmw_cas4_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_bmw_cas4_serialize,
|
||||
.deserialize = subghz_protocol_decoder_bmw_cas4_deserialize,
|
||||
.get_string = subghz_protocol_decoder_bmw_cas4_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_bmw_cas4_encoder = {
|
||||
.alloc = subghz_protocol_encoder_bmw_cas4_alloc,
|
||||
.free = subghz_protocol_encoder_bmw_cas4_free,
|
||||
.deserialize = subghz_protocol_encoder_bmw_cas4_deserialize,
|
||||
.stop = subghz_protocol_encoder_bmw_cas4_stop,
|
||||
.yield = subghz_protocol_encoder_bmw_cas4_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_bmw_cas4 = {
|
||||
.name = BMW_CAS4_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
.decoder = &subghz_protocol_bmw_cas4_decoder,
|
||||
.encoder = &subghz_protocol_bmw_cas4_encoder,
|
||||
};
|
||||
|
||||
// Encoder stubs
|
||||
|
||||
void* subghz_protocol_encoder_bmw_cas4_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderBmwCas4* instance = calloc(1, sizeof(SubGhzProtocolEncoderBmwCas4));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_bmw_cas4;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.size_upload = 1;
|
||||
instance->encoder.upload = malloc(sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_bmw_cas4_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderBmwCas4* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_bmw_cas4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
UNUSED(context);
|
||||
UNUSED(flipper_format);
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_bmw_cas4_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderBmwCas4* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_bmw_cas4_yield(void* context) {
|
||||
UNUSED(context);
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
// Decoder
|
||||
|
||||
static void bmw_cas4_rebuild_raw_data(SubGhzProtocolDecoderBmwCas4* instance) {
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
uint64_t key = instance->generic.data;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||
}
|
||||
instance->bit_count = instance->generic.data_count_bit;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_bmw_cas4_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = calloc(1, sizeof(SubGhzProtocolDecoderBmwCas4));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_bmw_cas4;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_bmw_cas4_free(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_bmw_cas4_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||
instance->preamble_count = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->te_last = 0;
|
||||
instance->generic.data = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_bmw_cas4_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||
|
||||
uint32_t te_short = subghz_protocol_bmw_cas4_const.te_short;
|
||||
uint32_t te_long = subghz_protocol_bmw_cas4_const.te_long;
|
||||
uint32_t te_delta = subghz_protocol_bmw_cas4_const.te_delta;
|
||||
uint32_t diff;
|
||||
|
||||
switch(instance->decoder_state) {
|
||||
case BmwCas4DecoderStepReset:
|
||||
if(level && duration >= BMW_CAS4_PREAMBLE_PULSE_MIN &&
|
||||
duration <= BMW_CAS4_PREAMBLE_PULSE_MAX) {
|
||||
instance->decoder_state = BmwCas4DecoderStepPreamble;
|
||||
instance->preamble_count = 1;
|
||||
instance->te_last = duration;
|
||||
}
|
||||
break;
|
||||
|
||||
case BmwCas4DecoderStepPreamble:
|
||||
if(duration >= BMW_CAS4_PREAMBLE_PULSE_MIN &&
|
||||
duration <= BMW_CAS4_PREAMBLE_PULSE_MAX) {
|
||||
instance->preamble_count++;
|
||||
instance->te_last = duration;
|
||||
} else if(!level && duration >= BMW_CAS4_GAP_MIN) {
|
||||
if(instance->preamble_count >= BMW_CAS4_PREAMBLE_MIN) {
|
||||
instance->bit_count = 0;
|
||||
instance->generic.data = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
instance->decoder_state = BmwCas4DecoderStepData;
|
||||
} else {
|
||||
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case BmwCas4DecoderStepData: {
|
||||
if(instance->bit_count >= BMW_CAS4_DATA_BITS) {
|
||||
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
|
||||
diff = (duration > te_short) ? (duration - te_short) : (te_short - duration);
|
||||
if(diff < te_delta) {
|
||||
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
} else {
|
||||
diff = (duration > te_long) ? (duration - te_long) : (te_long - duration);
|
||||
if(diff < te_delta) {
|
||||
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||
}
|
||||
}
|
||||
|
||||
if(event != ManchesterEventReset) {
|
||||
bool data_bit;
|
||||
if(manchester_advance(
|
||||
instance->manchester_state,
|
||||
event,
|
||||
&instance->manchester_state,
|
||||
&data_bit)) {
|
||||
uint32_t new_bit = data_bit ? 1 : 0;
|
||||
|
||||
if(instance->bit_count < BMW_CAS4_DATA_BITS) {
|
||||
uint8_t byte_idx = instance->bit_count / 8;
|
||||
uint8_t bit_pos = 7 - (instance->bit_count % 8);
|
||||
if(new_bit) {
|
||||
instance->raw_data[byte_idx] |= (1 << bit_pos);
|
||||
}
|
||||
instance->generic.data = (instance->generic.data << 1) | new_bit;
|
||||
}
|
||||
|
||||
instance->bit_count++;
|
||||
|
||||
if(instance->bit_count == BMW_CAS4_DATA_BITS) {
|
||||
if(instance->raw_data[0] == BMW_CAS4_BYTE0_MARKER &&
|
||||
instance->raw_data[6] == BMW_CAS4_BYTE6_MARKER) {
|
||||
instance->generic.data_count_bit = BMW_CAS4_DATA_BITS;
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
instance->decoder_state = BmwCas4DecoderStepReset;
|
||||
}
|
||||
|
||||
instance->te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_bmw_cas4_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||
SubGhzBlockDecoder dec = {
|
||||
.decode_data = instance->generic.data,
|
||||
.decode_count_bit = instance->generic.data_count_bit,
|
||||
};
|
||||
return subghz_protocol_blocks_get_hash_data(&dec, (dec.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_bmw_cas4_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_bmw_cas4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
bmw_cas4_rebuild_raw_data(instance);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_bmw_cas4_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderBmwCas4* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Raw:%02X %02X%02X%02X%02X%02X %02X %02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->generic.data_count_bit,
|
||||
instance->raw_data[0],
|
||||
instance->raw_data[1], instance->raw_data[2],
|
||||
instance->raw_data[3], instance->raw_data[4], instance->raw_data[5],
|
||||
instance->raw_data[6],
|
||||
instance->raw_data[7]);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define BMW_CAS4_PROTOCOL_NAME "BMW CAS4"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderBmwCas4 SubGhzProtocolDecoderBmwCas4;
|
||||
typedef struct SubGhzProtocolEncoderBmwCas4 SubGhzProtocolEncoderBmwCas4;
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_bmw_cas4;
|
||||
|
||||
void* subghz_protocol_decoder_bmw_cas4_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_bmw_cas4_free(void* context);
|
||||
void subghz_protocol_decoder_bmw_cas4_reset(void* context);
|
||||
void subghz_protocol_decoder_bmw_cas4_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_bmw_cas4_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_bmw_cas4_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_bmw_cas4_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_bmw_cas4_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_bmw_cas4_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_bmw_cas4_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_bmw_cas4_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_bmw_cas4_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_bmw_cas4_yield(void* context);
|
||||
@@ -0,0 +1,643 @@
|
||||
#include "chrysler.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include "../blocks/custom_btn_i.h"
|
||||
|
||||
#define TAG "Chrysler"
|
||||
|
||||
// Chrysler keyfob rolling code protocol
|
||||
// Found on: PT Cruiser, Dodge, Jeep (~2004-2010)
|
||||
//
|
||||
// RF: 433.92 MHz, OOK PWM encoding
|
||||
// Bit timing: ~4000us total period
|
||||
// Bit 0: ~300us HIGH + ~3700us LOW
|
||||
// Bit 1: ~600us HIGH + ~3400us LOW
|
||||
// Frame: 24-bit zero preamble + gap ~15600us + 80-bit data
|
||||
// Retransmission: same frame sent twice per press
|
||||
//
|
||||
// 80-bit frame layout (10 bytes):
|
||||
// Byte 0: [counter:4 | device_id:4]
|
||||
// Counter: 4-bit, bit-reversed, decrementing
|
||||
// Device ID: constant per keyfob (e.g. 0xB)
|
||||
// Bytes 1-4: nibble-interleaved rolling code + button
|
||||
// MSB(b0)=0: high nibbles = rolling, low nibbles = button
|
||||
// MSB(b0)=1: low nibbles = rolling, high nibbles = button
|
||||
// Byte 5: check byte (b1 XOR 0xC3 when MSB=0, b1 when MSB=1)
|
||||
// Byte 6: b1 XOR mask (mask depends on MSB and button)
|
||||
// Bytes 7-9: b2-b4 XOR fixed mask (redundancy copy)
|
||||
//
|
||||
// Rolling code: single 8-bit value XOR'd with per-device serial offsets
|
||||
// across all 4 byte positions. The 4 bytes are related by constant XOR
|
||||
// (the serial).
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_chrysler_const = {
|
||||
.te_short = 300,
|
||||
.te_long = 600,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 80,
|
||||
};
|
||||
|
||||
#define CHRYSLER_BIT_PERIOD 4000u
|
||||
#define CHRYSLER_BIT_TOLERANCE 800u
|
||||
#define CHRYSLER_PREAMBLE_MIN 15u
|
||||
#define CHRYSLER_PREAMBLE_GAP 10000u
|
||||
#define CHRYSLER_DATA_BITS 80u
|
||||
#define CHRYSLER_SHORT_MAX 450u
|
||||
#define CHRYSLER_LONG_MIN 450u
|
||||
#define CHRYSLER_LONG_MAX 800u
|
||||
|
||||
struct SubGhzProtocolDecoderChrysler {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint8_t decoder_state;
|
||||
uint16_t preamble_count;
|
||||
uint8_t raw_data[10];
|
||||
uint8_t bit_count;
|
||||
uint32_t te_last;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderChrysler {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint8_t raw_data[10];
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
ChryslerDecoderStepReset = 0,
|
||||
ChryslerDecoderStepPreamble,
|
||||
ChryslerDecoderStepGap,
|
||||
ChryslerDecoderStepData,
|
||||
} ChryslerDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_chrysler_decoder = {
|
||||
.alloc = subghz_protocol_decoder_chrysler_alloc,
|
||||
.free = subghz_protocol_decoder_chrysler_free,
|
||||
.feed = subghz_protocol_decoder_chrysler_feed,
|
||||
.reset = subghz_protocol_decoder_chrysler_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_chrysler_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_chrysler_serialize,
|
||||
.deserialize = subghz_protocol_decoder_chrysler_deserialize,
|
||||
.get_string = subghz_protocol_decoder_chrysler_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_chrysler_encoder = {
|
||||
.alloc = subghz_protocol_encoder_chrysler_alloc,
|
||||
.free = subghz_protocol_encoder_chrysler_free,
|
||||
.deserialize = subghz_protocol_encoder_chrysler_deserialize,
|
||||
.stop = subghz_protocol_encoder_chrysler_stop,
|
||||
.yield = subghz_protocol_encoder_chrysler_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_chrysler = {
|
||||
.name = CHRYSLER_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_chrysler_decoder,
|
||||
.encoder = &subghz_protocol_chrysler_encoder,
|
||||
};
|
||||
|
||||
static uint8_t chrysler_reverse_nibble(uint8_t n) {
|
||||
return (uint8_t)(((n & 1) << 3) | ((n & 2) << 1) | ((n & 4) >> 1) | ((n & 8) >> 3));
|
||||
}
|
||||
|
||||
// Encoder
|
||||
|
||||
#define CHRYSLER_ENCODER_UPLOAD_MAX 800
|
||||
#define CHRYSLER_ENCODER_REPEAT 3
|
||||
#define CHRYSLER_PREAMBLE_BITS 24
|
||||
#define CHRYSLER_PREAMBLE_GAP_US 15600
|
||||
|
||||
static uint8_t chrysler_custom_to_btn(uint8_t custom) {
|
||||
switch(custom) {
|
||||
case 1:
|
||||
return 0x01; // Lock
|
||||
case 2:
|
||||
return 0x02; // Unlock
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void chrysler_advance_rolling(uint8_t* d) {
|
||||
// Advance the counter and rolling code for the next transmission.
|
||||
//
|
||||
// Counter: 4-bit bit-reversed in upper nibble of b0, decrementing.
|
||||
// Rolling code: nibble-interleaved into bytes 1-4, swapped based on MSB(b0).
|
||||
//
|
||||
// Step 1: Extract current rolling nibbles and button nibbles
|
||||
uint8_t msb = (d[0] >> 7) & 1;
|
||||
uint8_t rolling[4], button[4];
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(msb == 0) {
|
||||
rolling[i] = (d[1 + i] >> 4) & 0xF;
|
||||
button[i] = d[1 + i] & 0xF;
|
||||
} else {
|
||||
rolling[i] = d[1 + i] & 0xF;
|
||||
button[i] = (d[1 + i] >> 4) & 0xF;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 2: Decrement the bit-reversed counter
|
||||
uint8_t cnt_raw = (d[0] >> 4) & 0xF;
|
||||
uint8_t cnt = chrysler_reverse_nibble(cnt_raw);
|
||||
cnt = (cnt - 1) & 0xF;
|
||||
cnt_raw = chrysler_reverse_nibble(cnt);
|
||||
uint8_t new_msb = (cnt_raw >> 3) & 1;
|
||||
|
||||
// Step 3: Reassemble byte 0
|
||||
d[0] = (cnt_raw << 4) | (d[0] & 0x0F);
|
||||
|
||||
// Step 4: Re-interleave nibbles with new MSB
|
||||
// The rolling nibbles stay the same for one step (they change every 2 presses,
|
||||
// i.e. when MSB returns to the same value). The button nibbles may differ
|
||||
// between MSB=0 and MSB=1 states.
|
||||
for(int i = 0; i < 4; i++) {
|
||||
if(new_msb == 0) {
|
||||
d[1 + i] = (rolling[i] << 4) | (button[i] & 0xF);
|
||||
} else {
|
||||
d[1 + i] = ((button[i] & 0xF) << 4) | rolling[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void chrysler_encoder_rebuild(SubGhzProtocolEncoderChrysler* instance) {
|
||||
uint8_t* d = instance->raw_data;
|
||||
uint8_t msb = (d[0] >> 7) & 1;
|
||||
uint8_t btn = instance->generic.btn;
|
||||
|
||||
uint8_t custom = subghz_custom_btn_get();
|
||||
if(custom != 0) {
|
||||
uint8_t new_btn = chrysler_custom_to_btn(custom);
|
||||
if(new_btn != 0) btn = new_btn;
|
||||
}
|
||||
|
||||
// Determine b1^b6 mask based on button and MSB
|
||||
uint8_t b1_xor_b6;
|
||||
if(msb == 0) {
|
||||
b1_xor_b6 = (btn == 0x01) ? 0x04 : 0x08;
|
||||
} else {
|
||||
b1_xor_b6 = 0x62;
|
||||
}
|
||||
|
||||
// Rebuild byte 5
|
||||
d[5] = (msb == 0) ? (d[1] ^ 0xC3) : d[1];
|
||||
|
||||
// Rebuild byte 6
|
||||
d[6] = d[1] ^ b1_xor_b6;
|
||||
|
||||
// Rebuild bytes 7-9 from bytes 2-4
|
||||
if(msb == 0) {
|
||||
d[7] = d[2] ^ 0x63;
|
||||
d[8] = d[3] ^ 0x59;
|
||||
d[9] = d[4] ^ 0x46;
|
||||
} else {
|
||||
d[7] = d[2] ^ 0x9A;
|
||||
d[8] = d[3] ^ 0xC6;
|
||||
d[9] = d[4] ^ ((btn == 0x01) ? 0x20 : 0x10);
|
||||
}
|
||||
}
|
||||
|
||||
static bool chrysler_encoder_get_upload(SubGhzProtocolEncoderChrysler* instance) {
|
||||
uint32_t te_short = subghz_protocol_chrysler_const.te_short;
|
||||
uint32_t te_bit_period = CHRYSLER_BIT_PERIOD;
|
||||
size_t index = 0;
|
||||
size_t max_upload = CHRYSLER_ENCODER_UPLOAD_MAX;
|
||||
|
||||
// Preamble: 24 zero bits (short HIGH + long LOW each)
|
||||
for(uint8_t i = 0; i < CHRYSLER_PREAMBLE_BITS && (index + 1) < max_upload; i++) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, te_bit_period - te_short);
|
||||
}
|
||||
|
||||
// Gap between preamble and data
|
||||
if(index > 0) {
|
||||
instance->encoder.upload[index - 1] =
|
||||
level_duration_make(false, CHRYSLER_PREAMBLE_GAP_US);
|
||||
}
|
||||
|
||||
// Data: 80 bits PWM
|
||||
for(uint8_t bit_i = 0; bit_i < CHRYSLER_DATA_BITS && (index + 1) < max_upload; bit_i++) {
|
||||
uint8_t byte_idx = bit_i / 8;
|
||||
uint8_t bit_pos = 7 - (bit_i % 8);
|
||||
bool data_bit = (instance->raw_data[byte_idx] >> bit_pos) & 1;
|
||||
|
||||
uint32_t high_dur = data_bit ? 600 : te_short;
|
||||
uint32_t low_dur = te_bit_period - high_dur;
|
||||
|
||||
instance->encoder.upload[index++] = level_duration_make(true, high_dur);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, low_dur);
|
||||
}
|
||||
|
||||
// Final gap after frame
|
||||
if(index > 0) {
|
||||
instance->encoder.upload[index - 1] =
|
||||
level_duration_make(false, CHRYSLER_PREAMBLE_GAP_US);
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return index > 0;
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_chrysler_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderChrysler* instance = calloc(1, sizeof(SubGhzProtocolEncoderChrysler));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_chrysler;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = CHRYSLER_ENCODER_REPEAT;
|
||||
instance->encoder.size_upload = CHRYSLER_ENCODER_UPLOAD_MAX;
|
||||
instance->encoder.upload = malloc(CHRYSLER_ENCODER_UPLOAD_MAX * sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_chrysler_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderChrysler* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_chrysler_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderChrysler* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(ret != SubGhzProtocolStatusOk) break;
|
||||
|
||||
// Rebuild raw_data from generic.data (bytes 0-7)
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
uint64_t key = instance->generic.data;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||
}
|
||||
|
||||
// Read extra bytes 8-9
|
||||
uint32_t extra = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
|
||||
instance->raw_data[8] = (extra >> 8) & 0xFF;
|
||||
instance->raw_data[9] = extra & 0xFF;
|
||||
}
|
||||
|
||||
// Advance rolling code (decrement counter, swap nibble interleaving)
|
||||
chrysler_advance_rolling(instance->raw_data);
|
||||
|
||||
// Rebuild check bytes with (possibly changed) button
|
||||
chrysler_encoder_rebuild(instance);
|
||||
|
||||
if(!chrysler_encoder_get_upload(instance)) {
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.repeat = CHRYSLER_ENCODER_REPEAT;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_chrysler_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderChrysler* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_chrysler_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderChrysler* 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) {
|
||||
if(!subghz_block_generic_global.endless_tx) {
|
||||
instance->encoder.repeat--;
|
||||
}
|
||||
instance->encoder.front = 0;
|
||||
chrysler_advance_rolling(instance->raw_data);
|
||||
chrysler_encoder_rebuild(instance);
|
||||
chrysler_encoder_get_upload(instance);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Decoder
|
||||
|
||||
static void chrysler_parse_data(SubGhzProtocolDecoderChrysler* instance) {
|
||||
uint8_t* d = instance->raw_data;
|
||||
|
||||
uint8_t cnt_raw = (d[0] >> 4) & 0xF;
|
||||
uint8_t cnt = chrysler_reverse_nibble(cnt_raw);
|
||||
uint8_t dev_id = d[0] & 0xF;
|
||||
uint8_t msb = (d[0] >> 7) & 1;
|
||||
|
||||
// Determine button from b1^b6 mask
|
||||
uint8_t b1_xor_b6 = d[1] ^ d[6];
|
||||
uint8_t btn = 0;
|
||||
if(msb == 0) {
|
||||
if(b1_xor_b6 == 0x04)
|
||||
btn = 0x01; // Lock
|
||||
else if(b1_xor_b6 == 0x08)
|
||||
btn = 0x02; // Unlock
|
||||
else
|
||||
btn = 0x00;
|
||||
} else {
|
||||
btn = 0xFF; // Can't distinguish from MSB=1 mask (both = 0x62)
|
||||
}
|
||||
|
||||
// Serial: XOR offsets between byte positions (constant per device)
|
||||
// We derive it from the relationship between byte positions
|
||||
// serial_bytes[i] = rolling_value XOR bytes[1+i]_rolling_nibble
|
||||
// Since all positions share the same LFSR, XOR between positions is the serial
|
||||
|
||||
instance->generic.serial =
|
||||
((uint32_t)(d[1] ^ d[2]) << 24) |
|
||||
((uint32_t)(d[1] ^ d[3]) << 16) |
|
||||
((uint32_t)(d[1] ^ d[4]) << 8) |
|
||||
((uint32_t)dev_id);
|
||||
|
||||
instance->generic.cnt = cnt;
|
||||
instance->generic.btn = (btn != 0xFF) ? btn : 0;
|
||||
|
||||
// Store full 80-bit data
|
||||
instance->generic.data =
|
||||
((uint64_t)d[0] << 56) | ((uint64_t)d[1] << 48) |
|
||||
((uint64_t)d[2] << 40) | ((uint64_t)d[3] << 32) |
|
||||
((uint64_t)d[4] << 24) | ((uint64_t)d[5] << 16) |
|
||||
((uint64_t)d[6] << 8) | ((uint64_t)d[7]);
|
||||
instance->generic.data_count_bit = CHRYSLER_DATA_BITS;
|
||||
}
|
||||
|
||||
static bool chrysler_validate(SubGhzProtocolDecoderChrysler* instance) {
|
||||
uint8_t* d = instance->raw_data;
|
||||
uint8_t msb = (d[0] >> 7) & 1;
|
||||
|
||||
// Check byte 5: should be b1 XOR 0xC3 (MSB=0) or b1 (MSB=1)
|
||||
if(msb == 0) {
|
||||
if(d[5] != (d[1] ^ 0xC3)) return false;
|
||||
} else {
|
||||
if(d[5] != d[1]) return false;
|
||||
}
|
||||
|
||||
// Check bytes 6-9 vs 1-4 XOR mask consistency
|
||||
// b1^b6 should be a known mask
|
||||
uint8_t b1_xor_b6 = d[1] ^ d[6];
|
||||
if(msb == 0) {
|
||||
if(b1_xor_b6 != 0x04 && b1_xor_b6 != 0x08) return false;
|
||||
} else {
|
||||
if(b1_xor_b6 != 0x62) return false;
|
||||
}
|
||||
|
||||
// Check bytes 2-4 vs 7-9 XOR mask is consistent
|
||||
// The XOR mask for bytes 2-4 vs 7-9 should be the same across all 3 pairs
|
||||
uint8_t mask2 = d[2] ^ d[7];
|
||||
uint8_t mask3 = d[3] ^ d[8];
|
||||
uint8_t mask4 = d[4] ^ d[9];
|
||||
|
||||
// Masks should be one of the known patterns
|
||||
if(msb == 0) {
|
||||
if(mask2 != 0x63 || mask3 != 0x59 || mask4 != 0x46) return false;
|
||||
} else {
|
||||
// MSB=1 masks: 9A C6 20 or 9A C6 10
|
||||
if(mask2 != 0x9A || mask3 != 0xC6) return false;
|
||||
if(mask4 != 0x20 && mask4 != 0x10) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void chrysler_rebuild_raw_data(SubGhzProtocolDecoderChrysler* instance) {
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
uint64_t key = instance->generic.data;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||
}
|
||||
instance->bit_count = instance->generic.data_count_bit;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_chrysler_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderChrysler* instance = calloc(1, sizeof(SubGhzProtocolDecoderChrysler));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_chrysler;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_chrysler_free(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_chrysler_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderChrysler* instance = context;
|
||||
instance->decoder_state = ChryslerDecoderStepReset;
|
||||
instance->preamble_count = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->te_last = 0;
|
||||
instance->generic.data = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_chrysler_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderChrysler* instance = context;
|
||||
|
||||
switch(instance->decoder_state) {
|
||||
case ChryslerDecoderStepReset:
|
||||
if(level && duration <= CHRYSLER_SHORT_MAX && duration > 100) {
|
||||
instance->te_last = duration;
|
||||
instance->decoder_state = ChryslerDecoderStepPreamble;
|
||||
instance->preamble_count = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case ChryslerDecoderStepPreamble:
|
||||
if(!level) {
|
||||
uint32_t total = instance->te_last + duration;
|
||||
if(DURATION_DIFF(total, CHRYSLER_BIT_PERIOD) < CHRYSLER_BIT_TOLERANCE &&
|
||||
instance->te_last <= CHRYSLER_SHORT_MAX) {
|
||||
instance->preamble_count++;
|
||||
} else if(duration > CHRYSLER_PREAMBLE_GAP &&
|
||||
instance->preamble_count >= CHRYSLER_PREAMBLE_MIN) {
|
||||
instance->decoder_state = ChryslerDecoderStepGap;
|
||||
} else {
|
||||
instance->decoder_state = ChryslerDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(duration <= CHRYSLER_SHORT_MAX && duration > 100) {
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
instance->decoder_state = ChryslerDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ChryslerDecoderStepGap:
|
||||
if(level) {
|
||||
instance->te_last = duration;
|
||||
instance->bit_count = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
instance->decoder_state = ChryslerDecoderStepData;
|
||||
} else {
|
||||
instance->decoder_state = ChryslerDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case ChryslerDecoderStepData:
|
||||
if(level) {
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
uint32_t total = instance->te_last + duration;
|
||||
if(DURATION_DIFF(total, CHRYSLER_BIT_PERIOD) < CHRYSLER_BIT_TOLERANCE) {
|
||||
bool bit_val = (instance->te_last >= CHRYSLER_LONG_MIN);
|
||||
|
||||
if(instance->bit_count < CHRYSLER_DATA_BITS) {
|
||||
uint8_t byte_idx = instance->bit_count / 8;
|
||||
uint8_t bit_pos = 7 - (instance->bit_count % 8);
|
||||
if(bit_val) {
|
||||
instance->raw_data[byte_idx] |= (1 << bit_pos);
|
||||
}
|
||||
instance->bit_count++;
|
||||
}
|
||||
|
||||
if(instance->bit_count == CHRYSLER_DATA_BITS) {
|
||||
if(chrysler_validate(instance)) {
|
||||
chrysler_parse_data(instance);
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
instance->decoder_state = ChryslerDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(instance->bit_count >= CHRYSLER_DATA_BITS) {
|
||||
if(chrysler_validate(instance)) {
|
||||
chrysler_parse_data(instance);
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
instance->decoder_state = ChryslerDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_chrysler_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderChrysler* instance = context;
|
||||
SubGhzBlockDecoder dec = {
|
||||
.decode_data = instance->generic.data,
|
||||
.decode_count_bit = instance->generic.data_count_bit > 64 ? 64 : instance->generic.data_count_bit,
|
||||
};
|
||||
return subghz_protocol_blocks_get_hash_data(&dec, (dec.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_chrysler_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderChrysler* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t extra = ((uint32_t)instance->raw_data[8] << 8) | instance->raw_data[9];
|
||||
flipper_format_write_uint32(flipper_format, "Extra", &extra, 1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_chrysler_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderChrysler* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
chrysler_rebuild_raw_data(instance);
|
||||
|
||||
uint32_t extra = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
|
||||
instance->raw_data[8] = (extra >> 8) & 0xFF;
|
||||
instance->raw_data[9] = extra & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const char* chrysler_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x01:
|
||||
return "Lock";
|
||||
case 0x02:
|
||||
return "Unlock";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_chrysler_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderChrysler* instance = context;
|
||||
|
||||
uint8_t* d = instance->raw_data;
|
||||
uint8_t cnt_raw = (d[0] >> 4) & 0xF;
|
||||
uint8_t cnt = chrysler_reverse_nibble(cnt_raw);
|
||||
uint8_t dev_id = d[0] & 0xF;
|
||||
uint8_t msb = (d[0] >> 7) & 1;
|
||||
|
||||
uint8_t b1_xor_b6 = d[1] ^ d[6];
|
||||
uint8_t btn = instance->generic.btn;
|
||||
if(msb == 0) {
|
||||
if(b1_xor_b6 == 0x04)
|
||||
btn = 0x01;
|
||||
else if(b1_xor_b6 == 0x08)
|
||||
btn = 0x02;
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Raw:%02X%02X%02X%02X%02X %02X%02X%02X%02X%02X\r\n"
|
||||
"Cnt:%X Btn:%s Dev:%X\r\n"
|
||||
"Sn:%08lX\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->generic.data_count_bit,
|
||||
d[0], d[1], d[2], d[3], d[4],
|
||||
d[5], d[6], d[7], d[8], d[9],
|
||||
(unsigned)cnt,
|
||||
chrysler_button_name(btn),
|
||||
(unsigned)dev_id,
|
||||
(unsigned long)instance->generic.serial);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define CHRYSLER_PROTOCOL_NAME "Chrysler"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderChrysler SubGhzProtocolDecoderChrysler;
|
||||
typedef struct SubGhzProtocolEncoderChrysler SubGhzProtocolEncoderChrysler;
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_chrysler;
|
||||
|
||||
void* subghz_protocol_decoder_chrysler_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_chrysler_free(void* context);
|
||||
void subghz_protocol_decoder_chrysler_reset(void* context);
|
||||
void subghz_protocol_decoder_chrysler_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_chrysler_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_chrysler_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_chrysler_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_chrysler_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_chrysler_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_chrysler_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_chrysler_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_chrysler_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_chrysler_yield(void* context);
|
||||
@@ -41,6 +41,17 @@
|
||||
#define FIAT_MARELLI_RETX_SYNC_MAX 2800
|
||||
#define FIAT_MARELLI_TE_TYPE_AB_BOUNDARY 180
|
||||
|
||||
static uint8_t fiat_marelli_crc8(const uint8_t* data, size_t len) {
|
||||
uint8_t crc = 0x03;
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
for(uint8_t b = 0; b < 8; b++) {
|
||||
crc = (crc & 0x80) ? ((crc << 1) ^ 0x01) : (crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_fiat_marelli_const = {
|
||||
.te_short = 260,
|
||||
.te_long = 520,
|
||||
@@ -235,6 +246,10 @@ static void fiat_marelli_encoder_rebuild_raw_data(SubGhzProtocolEncoderFiatMarel
|
||||
}
|
||||
|
||||
instance->bit_count = instance->generic.data_count_bit;
|
||||
|
||||
if(instance->bit_count >= 104) {
|
||||
instance->raw_data[12] = fiat_marelli_crc8(instance->raw_data, 12);
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
@@ -518,16 +533,24 @@ void subghz_protocol_decoder_fiat_marelli_feed(void* context, bool level, uint32
|
||||
if(frame_complete) {
|
||||
instance->generic.data_count_bit = instance->bit_count;
|
||||
|
||||
instance->generic.serial =
|
||||
((uint32_t)instance->raw_data[2] << 24) |
|
||||
((uint32_t)instance->raw_data[3] << 16) |
|
||||
((uint32_t)instance->raw_data[4] << 8) |
|
||||
((uint32_t)instance->raw_data[5]);
|
||||
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
|
||||
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
bool crc_ok = true;
|
||||
if(instance->bit_count >= 104) {
|
||||
uint8_t calc = fiat_marelli_crc8(instance->raw_data, 12);
|
||||
crc_ok = (calc == instance->raw_data[12]);
|
||||
}
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
if(crc_ok) {
|
||||
instance->generic.serial =
|
||||
((uint32_t)instance->raw_data[2] << 24) |
|
||||
((uint32_t)instance->raw_data[3] << 16) |
|
||||
((uint32_t)instance->raw_data[4] << 8) |
|
||||
((uint32_t)instance->raw_data[5]);
|
||||
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
|
||||
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder_state = FiatMarelliDecoderStepReset;
|
||||
@@ -628,9 +651,15 @@ void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString*
|
||||
uint8_t scramble = (instance->raw_data[7] >> 1) & 0x3;
|
||||
uint8_t fixed = instance->raw_data[7] & 0x1;
|
||||
|
||||
const char* crc_str = "";
|
||||
if(instance->bit_count >= 104) {
|
||||
uint8_t calc = fiat_marelli_crc8(instance->raw_data, 12);
|
||||
crc_str = (calc == instance->raw_data[12]) ? " CRC:OK" : " CRC:FAIL";
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"%s %dbit%s\r\n"
|
||||
"Enc:%02X%02X%02X%02X%02X Scr:%02X\r\n"
|
||||
"Raw:%02X%02X Fixed:%X\r\n"
|
||||
"Sn:%08X Cnt:%02X\r\n"
|
||||
@@ -638,6 +667,7 @@ void subghz_protocol_decoder_fiat_marelli_get_string(void* context, FuriString*
|
||||
"Tp:%s\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->bit_count,
|
||||
crc_str,
|
||||
instance->raw_data[8], instance->raw_data[9],
|
||||
instance->raw_data[10], instance->raw_data[11],
|
||||
instance->raw_data[12],
|
||||
|
||||
@@ -1,722 +0,0 @@
|
||||
#include "fiat_v0.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#define TAG "FiatProtocolV0"
|
||||
#define FIAT_PROTOCOL_V0_NAME "Fiat V0"
|
||||
#define FIAT_V0_PREAMBLE_PAIRS 150
|
||||
#define FIAT_V0_GAP_US 800
|
||||
#define FIAT_V0_TOTAL_BURSTS 3
|
||||
#define FIAT_V0_INTER_BURST_GAP 25000
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_fiat_v0_const = {
|
||||
.te_short = 200,
|
||||
.te_long = 400,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderFiatV0 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
ManchesterState manchester_state;
|
||||
uint8_t decoder_state;
|
||||
uint16_t preamble_count;
|
||||
uint32_t data_low;
|
||||
uint32_t data_high;
|
||||
uint8_t bit_count;
|
||||
uint32_t hop;
|
||||
uint32_t fix;
|
||||
uint8_t endbyte;
|
||||
uint8_t final_count;
|
||||
uint32_t te_last;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderFiatV0 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint32_t hop;
|
||||
uint32_t fix;
|
||||
uint8_t endbyte;
|
||||
|
||||
// Capacity of encoder.upload in LevelDuration elements.
|
||||
size_t upload_capacity;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FiatV0DecoderStepReset = 0,
|
||||
FiatV0DecoderStepPreamble = 1,
|
||||
FiatV0DecoderStepData = 2,
|
||||
} FiatV0DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_fiat_v0_decoder = {
|
||||
.alloc = subghz_protocol_decoder_fiat_v0_alloc,
|
||||
.free = subghz_protocol_decoder_fiat_v0_free,
|
||||
.feed = subghz_protocol_decoder_fiat_v0_feed,
|
||||
.reset = subghz_protocol_decoder_fiat_v0_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_fiat_v0_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_fiat_v0_serialize,
|
||||
.deserialize = subghz_protocol_decoder_fiat_v0_deserialize,
|
||||
.get_string = subghz_protocol_decoder_fiat_v0_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_fiat_v0_encoder = {
|
||||
.alloc = subghz_protocol_encoder_fiat_v0_alloc,
|
||||
.free = subghz_protocol_encoder_fiat_v0_free,
|
||||
.deserialize = subghz_protocol_encoder_fiat_v0_deserialize,
|
||||
.stop = subghz_protocol_encoder_fiat_v0_stop,
|
||||
.yield = subghz_protocol_encoder_fiat_v0_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol fiat_protocol_v0 = {
|
||||
.name = FIAT_PROTOCOL_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_fiat_v0_decoder,
|
||||
.encoder = &subghz_protocol_fiat_v0_encoder,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ENCODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
static size_t fiat_v0_encoder_calc_required_upload(void) {
|
||||
// Per burst:
|
||||
// - preamble: FIAT_V0_PREAMBLE_PAIRS pairs -> 2 elements each
|
||||
// - data: 64 bits Manchester -> 2 elements per bit
|
||||
// - endbyte: 7 bits Manchester -> 2 elements per bit
|
||||
// - trailer: 1 element (extended low)
|
||||
const size_t per_burst = (FIAT_V0_PREAMBLE_PAIRS * 2) + (64 * 2) + (7 * 2) + 1;
|
||||
// Inter-burst gap is a single element between bursts.
|
||||
return (FIAT_V0_TOTAL_BURSTS * per_burst) +
|
||||
(FIAT_V0_TOTAL_BURSTS > 0 ? (FIAT_V0_TOTAL_BURSTS - 1) : 0);
|
||||
}
|
||||
|
||||
static void
|
||||
fiat_v0_encoder_ensure_upload_capacity(SubGhzProtocolEncoderFiatV0* instance, size_t required) {
|
||||
furi_check(instance);
|
||||
furi_check(required <= instance->upload_capacity);
|
||||
|
||||
LevelDuration* new_upload =
|
||||
realloc(instance->encoder.upload, required * sizeof(LevelDuration));
|
||||
furi_check(new_upload);
|
||||
instance->encoder.upload = new_upload;
|
||||
instance->upload_capacity = required;
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderFiatV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &fiat_protocol_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->upload_capacity = fiat_v0_encoder_calc_required_upload();
|
||||
instance->encoder.upload = calloc(instance->upload_capacity, sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
instance->encoder.is_running = false;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_fiat_v0_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFiatV0* instance = context;
|
||||
if(instance->encoder.upload) {
|
||||
free(instance->encoder.upload);
|
||||
}
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static void subghz_protocol_encoder_fiat_v0_get_upload(SubGhzProtocolEncoderFiatV0* instance) {
|
||||
furi_check(instance);
|
||||
size_t index = 0;
|
||||
|
||||
const size_t required = fiat_v0_encoder_calc_required_upload();
|
||||
fiat_v0_encoder_ensure_upload_capacity(instance, required);
|
||||
|
||||
uint32_t te_short = subghz_protocol_fiat_v0_const.te_short;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Building upload: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X",
|
||||
instance->hop,
|
||||
instance->fix,
|
||||
instance->endbyte & 0x7F);
|
||||
|
||||
for(uint8_t burst = 0; burst < FIAT_V0_TOTAL_BURSTS; burst++) {
|
||||
if(burst > 0) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, FIAT_V0_INTER_BURST_GAP);
|
||||
}
|
||||
|
||||
// Preamble: alternating short pulses
|
||||
for(int i = 0; i < FIAT_V0_PREAMBLE_PAIRS; i++) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||
}
|
||||
|
||||
// Extend last LOW to create the gap (~FIAT_V0_GAP_US)
|
||||
instance->encoder.upload[index - 1] = level_duration_make(false, FIAT_V0_GAP_US);
|
||||
|
||||
// Combine hop and fix into 64-bit data
|
||||
uint64_t data = ((uint64_t)instance->hop << 32) | instance->fix;
|
||||
|
||||
// Manchester encode 64 bits of data
|
||||
for(int bit = 63; bit >= 0; bit--) {
|
||||
bool curr_bit = (data >> bit) & 1;
|
||||
|
||||
if(curr_bit) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||
}
|
||||
}
|
||||
|
||||
// Manchester encode 7 bits of endbyte (bits 6:0) - signal has 71 total bits
|
||||
uint8_t endbyte = (uint8_t)(instance->endbyte & 0x7F);
|
||||
for(int bit = 6; bit >= 0; bit--) {
|
||||
bool curr_bit = (endbyte >> bit) & 1;
|
||||
|
||||
if(curr_bit) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||
}
|
||||
}
|
||||
|
||||
// End with extended LOW (will be followed by inter-burst gap or end)
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_short * 4);
|
||||
}
|
||||
|
||||
furi_check(index <= instance->upload_capacity);
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
FURI_LOG_I(TAG, "Upload built: %zu elements", instance->encoder.size_upload);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFiatV0* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = 10;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
furi_check(temp_str);
|
||||
|
||||
do {
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
|
||||
FURI_LOG_E(TAG, "Missing Protocol");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
|
||||
FURI_LOG_E(TAG, "Wrong protocol: %s", furi_string_get_cstr(temp_str));
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t bit_count_temp = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) {
|
||||
// This protocol transmits 71 bits: 64-bit key + 7-bit endbyte.
|
||||
// Let's do according to what's read, (64 or 71), else 71
|
||||
if(bit_count_temp == 64 || bit_count_temp == 71) {
|
||||
instance->generic.data_count_bit = bit_count_temp;
|
||||
} else {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Inconsistent Bit value of %d was defaulted to 71",
|
||||
instance->generic.data_count_bit);
|
||||
instance->generic.data_count_bit = 71;
|
||||
}
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Missing Bit");
|
||||
break;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
|
||||
FURI_LOG_E(TAG, "Missing Key");
|
||||
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 = (uint8_t)(c - '0');
|
||||
} else if(c >= 'A' && c <= 'F') {
|
||||
nibble = (uint8_t)(c - 'A' + 10);
|
||||
} else if(c >= 'a' && c <= 'f') {
|
||||
nibble = (uint8_t)(c - 'a' + 10);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
key = (key << 4) | nibble;
|
||||
hex_pos++;
|
||||
}
|
||||
|
||||
if(hex_pos != 16) {
|
||||
FURI_LOG_E(TAG, "Key parse error: expected 16 hex nibbles, got %u", (unsigned)hex_pos);
|
||||
break;
|
||||
}
|
||||
|
||||
instance->generic.data = key;
|
||||
instance->hop = (uint32_t)(key >> 32);
|
||||
instance->fix = (uint32_t)(key & 0xFFFFFFFF);
|
||||
|
||||
uint32_t btn_temp = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) {
|
||||
instance->endbyte = (uint8_t)(btn_temp & 0x7F);
|
||||
} else {
|
||||
instance->endbyte = 0;
|
||||
}
|
||||
|
||||
instance->generic.btn = instance->endbyte;
|
||||
instance->generic.cnt = instance->hop;
|
||||
instance->generic.serial = instance->fix;
|
||||
|
||||
uint32_t repeat_temp = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat_temp, 1)) {
|
||||
instance->encoder.repeat = repeat_temp;
|
||||
} else {
|
||||
instance->encoder.repeat = 10;
|
||||
}
|
||||
|
||||
subghz_protocol_encoder_fiat_v0_get_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encoder ready: hop=0x%08lX, fix=0x%08lX, endbyte=0x%02X",
|
||||
instance->hop,
|
||||
instance->fix,
|
||||
instance->endbyte);
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_fiat_v0_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFiatV0* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFiatV0* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DECODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderFiatV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderFiatV0));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &fiat_protocol_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v0_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v0_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
instance->decoder.parser_step = FiatV0DecoderStepReset;
|
||||
instance->decoder_state = 0;
|
||||
instance->preamble_count = 0;
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->hop = 0;
|
||||
instance->fix = 0;
|
||||
instance->endbyte = 0;
|
||||
instance->final_count = 0;
|
||||
instance->te_last = 0;
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
}
|
||||
|
||||
// Helper function to reset decoder to data state with proper Manchester initialization
|
||||
static void
|
||||
fiat_v0_decoder_enter_data_state(SubGhzProtocolDecoderFiatV0* instance, uint32_t duration) {
|
||||
instance->decoder_state = FiatV0DecoderStepData;
|
||||
instance->preamble_count = 0;
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->te_last = duration;
|
||||
// Critical: Reset Manchester state when entering data mode
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
uint32_t te_short = (uint32_t)subghz_protocol_fiat_v0_const.te_short;
|
||||
uint32_t te_long = (uint32_t)subghz_protocol_fiat_v0_const.te_long;
|
||||
uint32_t te_delta = (uint32_t)subghz_protocol_fiat_v0_const.te_delta;
|
||||
uint32_t gap_threshold = FIAT_V0_GAP_US;
|
||||
uint32_t diff;
|
||||
|
||||
switch(instance->decoder_state) {
|
||||
case FiatV0DecoderStepReset:
|
||||
if(!level) {
|
||||
return;
|
||||
}
|
||||
if(duration < te_short) {
|
||||
diff = te_short - duration;
|
||||
} else {
|
||||
diff = duration - te_short;
|
||||
}
|
||||
if(diff < te_delta) {
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->decoder_state = FiatV0DecoderStepPreamble;
|
||||
instance->te_last = duration;
|
||||
instance->preamble_count = 0;
|
||||
instance->bit_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case FiatV0DecoderStepPreamble:
|
||||
if(level) {
|
||||
// HIGH pulse during preamble - just track timing
|
||||
if(duration < te_short) {
|
||||
diff = te_short - duration;
|
||||
} else {
|
||||
diff = duration - te_short;
|
||||
}
|
||||
if(diff < te_delta) {
|
||||
instance->preamble_count++;
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
instance->decoder_state = FiatV0DecoderStepReset;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// LOW pulse - check if it's the gap after preamble
|
||||
if(duration < te_short) {
|
||||
diff = te_short - duration;
|
||||
} else {
|
||||
diff = duration - te_short;
|
||||
}
|
||||
|
||||
if(diff < te_delta) {
|
||||
// Normal short LOW - continue preamble
|
||||
instance->preamble_count++;
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
// Not a short pulse - check if it's the gap
|
||||
if(instance->preamble_count >= 0x96) {
|
||||
// We have enough preamble, check for gap
|
||||
if(duration < gap_threshold) {
|
||||
diff = gap_threshold - duration;
|
||||
} else {
|
||||
diff = duration - gap_threshold;
|
||||
}
|
||||
if(diff < te_delta) {
|
||||
// Valid gap detected - transition to data state
|
||||
fiat_v0_decoder_enter_data_state(instance, duration);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Invalid timing or not enough preamble
|
||||
instance->decoder_state = FiatV0DecoderStepReset;
|
||||
}
|
||||
|
||||
// Also check for gap even with valid short timing if we have enough preamble
|
||||
if(instance->preamble_count >= 0x96 &&
|
||||
instance->decoder_state == FiatV0DecoderStepPreamble) {
|
||||
if(duration < gap_threshold) {
|
||||
diff = gap_threshold - duration;
|
||||
} else {
|
||||
diff = duration - gap_threshold;
|
||||
}
|
||||
if(diff < te_delta) {
|
||||
// Valid gap detected - transition to data state
|
||||
fiat_v0_decoder_enter_data_state(instance, duration);
|
||||
return;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FiatV0DecoderStepData:
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
if(duration < te_short) {
|
||||
diff = te_short - duration;
|
||||
if(diff < te_delta) {
|
||||
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
}
|
||||
} else {
|
||||
diff = duration - te_short;
|
||||
if(diff < te_delta) {
|
||||
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
} else {
|
||||
if(duration < te_long) {
|
||||
diff = te_long - duration;
|
||||
} else {
|
||||
diff = duration - te_long;
|
||||
}
|
||||
if(diff < te_delta) {
|
||||
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(event != ManchesterEventReset) {
|
||||
bool data_bit_bool;
|
||||
if(manchester_advance(
|
||||
instance->manchester_state,
|
||||
event,
|
||||
&instance->manchester_state,
|
||||
&data_bit_bool)) {
|
||||
uint32_t new_bit = data_bit_bool ? 1 : 0;
|
||||
|
||||
uint32_t carry = (instance->data_low >> 31) & 1;
|
||||
instance->data_low = (instance->data_low << 1) | new_bit;
|
||||
instance->data_high = (instance->data_high << 1) | carry;
|
||||
|
||||
instance->bit_count++;
|
||||
|
||||
if(instance->bit_count == 0x40) {
|
||||
instance->fix = instance->data_low;
|
||||
instance->hop = instance->data_high;
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Bit 64: fix=0x%08lX, hop=0x%08lX, clearing data_low/data_high",
|
||||
instance->fix,
|
||||
instance->hop);
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
}
|
||||
|
||||
#ifndef REMOVE_LOGS
|
||||
if(instance->bit_count > 0x40 && instance->bit_count <= 0x47) {
|
||||
uint8_t endbyte_bit_num = instance->bit_count - 0x40;
|
||||
uint8_t endbyte_bit_index = endbyte_bit_num - 1;
|
||||
uint8_t data_low_byte = (uint8_t)instance->data_low;
|
||||
char binary_str[9] = {0};
|
||||
for(int i = 7; i >= 0; i--) {
|
||||
binary_str[7 - i] = (data_low_byte & (1 << i)) ? '1' : '0';
|
||||
}
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Bit %d (endbyte bit %d/%d): new_bit=%lu, data_low=0x%08lX (0x%02X), binary=0b%s",
|
||||
instance->bit_count,
|
||||
endbyte_bit_index,
|
||||
endbyte_bit_num - 1,
|
||||
(unsigned long)new_bit,
|
||||
instance->data_low,
|
||||
data_low_byte,
|
||||
binary_str);
|
||||
}
|
||||
#endif
|
||||
if(instance->bit_count == 0x47) {
|
||||
#ifndef REMOVE_LOGS
|
||||
uint8_t data_low_byte = (uint8_t)instance->data_low;
|
||||
char binary_str[9] = {0};
|
||||
for(int i = 7; i >= 0; i--) {
|
||||
binary_str[7 - i] = (data_low_byte & (1 << i)) ? '1' : '0';
|
||||
}
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"EXTRACTING AT BIT 71: bit_count=%d, data_low=0x%08lX (0x%02X), binary=0b%s, accumulated %d bits after bit 64",
|
||||
instance->bit_count,
|
||||
instance->data_low,
|
||||
data_low_byte,
|
||||
binary_str,
|
||||
instance->bit_count - 0x40);
|
||||
#endif
|
||||
instance->final_count = instance->bit_count;
|
||||
instance->endbyte = (uint8_t)instance->data_low;
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"EXTRACTED ENDBYTE: endbyte=0x%02X (decimal=%d), expected=0x0D (13)",
|
||||
instance->endbyte,
|
||||
instance->endbyte & 0x7F);
|
||||
|
||||
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
|
||||
instance->generic.data_count_bit = 71;
|
||||
instance->generic.serial = instance->fix;
|
||||
instance->generic.btn = instance->endbyte;
|
||||
instance->generic.cnt = instance->hop;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->decoder_state = FiatV0DecoderStepReset;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(instance->bit_count == 0x47) {
|
||||
// We have exactly 71 bits - extract endbyte
|
||||
uint8_t data_low_byte = (uint8_t)instance->data_low;
|
||||
instance->endbyte = data_low_byte;
|
||||
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"GAP PATH EXTRACTION (71 bits): bit_count=%d, endbyte=0x%02X",
|
||||
instance->bit_count,
|
||||
instance->endbyte & 0x7F);
|
||||
|
||||
instance->generic.data = ((uint64_t)instance->hop << 32) | instance->fix;
|
||||
instance->generic.data_count_bit = 71;
|
||||
instance->generic.serial = instance->fix;
|
||||
instance->generic.btn = instance->endbyte;
|
||||
instance->generic.cnt = instance->hop;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
instance->data_low = 0;
|
||||
instance->data_high = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->decoder_state = FiatV0DecoderStepReset;
|
||||
} else if(instance->bit_count < 0x40) {
|
||||
instance->decoder_state = FiatV0DecoderStepReset;
|
||||
}
|
||||
}
|
||||
instance->te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
SubGhzBlockDecoder decoder = {
|
||||
.decode_data = instance->generic.data,
|
||||
.decode_count_bit = instance->generic.data_count_bit};
|
||||
return subghz_protocol_blocks_get_hash_data(&decoder, (decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
do {
|
||||
if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) break;
|
||||
|
||||
if(!flipper_format_write_string_cstr(
|
||||
flipper_format, "Preset", furi_string_get_cstr(preset->name)))
|
||||
break;
|
||||
|
||||
if(!flipper_format_write_string_cstr(
|
||||
flipper_format, "Protocol", instance->generic.protocol_name))
|
||||
break;
|
||||
|
||||
uint32_t bits = instance->generic.data_count_bit;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Bit", &bits, 1)) break;
|
||||
|
||||
char key_str[20];
|
||||
snprintf(key_str, sizeof(key_str), "%08lX%08lX", instance->hop, instance->fix);
|
||||
if(!flipper_format_write_string_cstr(flipper_format, "Key", key_str)) break;
|
||||
|
||||
uint32_t temp = instance->hop;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Cnt", &temp, 1)) break;
|
||||
|
||||
if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->fix, 1)) break;
|
||||
|
||||
temp = instance->endbyte;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) break;
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, subghz_protocol_fiat_v0_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV0* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Hop:%08lX\r\n"
|
||||
"Sn:%08lX\r\n"
|
||||
"EndByte:%02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
instance->hop,
|
||||
instance->fix,
|
||||
instance->hop,
|
||||
instance->fix,
|
||||
instance->endbyte & 0x7F);
|
||||
}
|
||||
@@ -1,38 +0,0 @@
|
||||
#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>
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFiatV0 SubGhzProtocolDecoderFiatV0;
|
||||
typedef struct SubGhzProtocolEncoderFiatV0 SubGhzProtocolEncoderFiatV0;
|
||||
|
||||
extern const SubGhzProtocol fiat_protocol_v0;
|
||||
|
||||
// Decoder functions
|
||||
void* subghz_protocol_decoder_fiat_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_fiat_v0_free(void* context);
|
||||
void subghz_protocol_decoder_fiat_v0_reset(void* context);
|
||||
void subghz_protocol_decoder_fiat_v0_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_fiat_v0_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_fiat_v0_get_string(void* context, FuriString* output);
|
||||
|
||||
// Encoder functions
|
||||
void* subghz_protocol_encoder_fiat_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_fiat_v0_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_fiat_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_fiat_v0_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_fiat_v0_yield(void* context);
|
||||
@@ -1,544 +0,0 @@
|
||||
#include "fiat_v1.h"
|
||||
#include <string.h>
|
||||
|
||||
#define TAG "FiatProtocolV1"
|
||||
|
||||
// Magneti Marelli BSI keyfob protocol (PCF7946)
|
||||
// Found on: Fiat Panda, Grande Punto (and possibly other Fiat/Lancia/Alfa ~2003-2012)
|
||||
//
|
||||
// RF: 433.92 MHz, Manchester encoding
|
||||
// Two timing variants with identical frame structure:
|
||||
// Type A (e.g. Panda): te_short ~260us, te_long ~520us
|
||||
// Type B (e.g. Grande Punto): te_short ~100us, te_long ~200us
|
||||
// TE is auto-detected from preamble pulse averaging.
|
||||
//
|
||||
// Frame layout (104 bits = 13 bytes):
|
||||
// Bytes 0-1: 0xFFFF/0xFFFC preamble residue
|
||||
// Bytes 2-5: Serial (32 bits)
|
||||
// Byte 6: [Button:4 | Epoch:4]
|
||||
// Byte 7: [Counter:5 | Scramble:2 | Fixed:1]
|
||||
// Bytes 8-12: Encrypted payload (40 bits)
|
||||
//
|
||||
// Original implementation by @lupettohf
|
||||
|
||||
#define FIAT_MARELLI_PREAMBLE_PULSE_MIN 35
|
||||
#define FIAT_MARELLI_PREAMBLE_PULSE_MAX 450
|
||||
#define FIAT_MARELLI_PREAMBLE_MIN 48
|
||||
#define FIAT_MARELLI_MAX_DATA_BITS 104
|
||||
#define FIAT_MARELLI_MIN_DATA_BITS 104
|
||||
#define FIAT_MARELLI_GAP_TE_MULT 4
|
||||
#define FIAT_MARELLI_SYNC_TE_MIN_MULT 4
|
||||
#define FIAT_MARELLI_SYNC_TE_MAX_MULT 12
|
||||
#define FIAT_MARELLI_RETX_GAP_MIN 5000
|
||||
#define FIAT_MARELLI_RETX_SYNC_MIN 400
|
||||
#define FIAT_MARELLI_RETX_SYNC_MAX 2800
|
||||
#define FIAT_MARELLI_TE_TYPE_AB_BOUNDARY 180
|
||||
|
||||
static uint8_t fiat_marelli_crc8(const uint8_t* data, size_t len) {
|
||||
uint8_t crc = 0x03;
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
for(uint8_t b = 0; b < 8; b++) {
|
||||
crc = (crc & 0x80) ? ((crc << 1) ^ 0x01) : (crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_fiat_marelli_const = {
|
||||
.te_short = 260,
|
||||
.te_long = 520,
|
||||
.te_delta = 80,
|
||||
.min_count_bit_for_found = FIAT_MARELLI_MIN_DATA_BITS,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FiatMarelliDecoderStepReset = 0,
|
||||
FiatMarelliDecoderStepPreamble = 1,
|
||||
FiatMarelliDecoderStepSync = 2,
|
||||
FiatMarelliDecoderStepData = 3,
|
||||
} FiatMarelliDecoderStep;
|
||||
|
||||
struct SubGhzProtocolDecoderFiatV1 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint8_t decoder_state;
|
||||
uint16_t preamble_count;
|
||||
|
||||
uint8_t raw_data[13];
|
||||
uint8_t bit_count;
|
||||
uint32_t extra_data;
|
||||
|
||||
uint32_t te_last;
|
||||
uint32_t te_sum;
|
||||
uint16_t te_count;
|
||||
uint32_t te_detected;
|
||||
};
|
||||
|
||||
static void fiat_marelli_set_state(
|
||||
SubGhzProtocolDecoderFiatV1* instance,
|
||||
FiatMarelliDecoderStep new_state,
|
||||
const char* reason) {
|
||||
UNUSED(reason);
|
||||
instance->decoder_state = new_state;
|
||||
}
|
||||
|
||||
static bool fiat_marelli_get_raw_bit(const uint8_t* raw, uint8_t bit_index) {
|
||||
return (raw[bit_index / 8] >> (7 - (bit_index % 8))) & 1U;
|
||||
}
|
||||
|
||||
static void fiat_marelli_set_raw_bit(uint8_t* raw, uint8_t bit_index, bool value) {
|
||||
uint8_t byte_idx = bit_index / 8;
|
||||
uint8_t mask = 1U << (7 - (bit_index % 8));
|
||||
if(value) {
|
||||
raw[byte_idx] |= mask;
|
||||
} else {
|
||||
raw[byte_idx] &= (uint8_t)(~mask);
|
||||
}
|
||||
}
|
||||
|
||||
static void fiat_marelli_rebuild_data_words_from_raw(SubGhzProtocolDecoderFiatV1* instance) {
|
||||
instance->generic.data = 0;
|
||||
instance->extra_data = 0;
|
||||
|
||||
for(uint8_t i = 0; i < 64; i++) {
|
||||
instance->generic.data = (instance->generic.data << 1) |
|
||||
(fiat_marelli_get_raw_bit(instance->raw_data, i) ? 1U : 0U);
|
||||
}
|
||||
|
||||
for(uint8_t i = 64; i < FIAT_MARELLI_MAX_DATA_BITS; i++) {
|
||||
instance->extra_data = (instance->extra_data << 1) |
|
||||
(fiat_marelli_get_raw_bit(instance->raw_data, i) ? 1U : 0U);
|
||||
}
|
||||
|
||||
instance->bit_count = FIAT_MARELLI_MAX_DATA_BITS;
|
||||
instance->generic.data_count_bit = FIAT_MARELLI_MAX_DATA_BITS;
|
||||
}
|
||||
|
||||
static bool fiat_marelli_try_recover_tail_bits(SubGhzProtocolDecoderFiatV1* instance) {
|
||||
if(instance->bit_count >= FIAT_MARELLI_MAX_DATA_BITS) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(instance->bit_count < (FIAT_MARELLI_MAX_DATA_BITS - 2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t missing_bits = FIAT_MARELLI_MAX_DATA_BITS - instance->bit_count;
|
||||
uint8_t variants = 1U << missing_bits;
|
||||
uint8_t match_count = 0;
|
||||
uint8_t matched_variant = 0;
|
||||
|
||||
for(uint8_t variant = 0; variant < variants; variant++) {
|
||||
uint8_t trial[13];
|
||||
memcpy(trial, instance->raw_data, sizeof(trial));
|
||||
|
||||
for(uint8_t i = 0; i < missing_bits; i++) {
|
||||
bool bit = ((variant >> (missing_bits - 1 - i)) & 1U) != 0;
|
||||
fiat_marelli_set_raw_bit(trial, instance->bit_count + i, bit);
|
||||
}
|
||||
|
||||
uint8_t calc = fiat_marelli_crc8(trial, 12);
|
||||
if(calc == trial[12]) {
|
||||
match_count++;
|
||||
matched_variant = variant;
|
||||
}
|
||||
}
|
||||
|
||||
if(match_count != 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < missing_bits; i++) {
|
||||
bool bit = ((matched_variant >> (missing_bits - 1 - i)) & 1U) != 0;
|
||||
fiat_marelli_set_raw_bit(instance->raw_data, instance->bit_count + i, bit);
|
||||
}
|
||||
|
||||
fiat_marelli_rebuild_data_words_from_raw(instance);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void fiat_marelli_prepare_data(SubGhzProtocolDecoderFiatV1* instance) {
|
||||
instance->bit_count = 0;
|
||||
instance->extra_data = 0;
|
||||
instance->generic.data = 0;
|
||||
instance->generic.data_count_bit = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepData, "sync accepted");
|
||||
}
|
||||
|
||||
static void fiat_marelli_rebuild_raw_data(SubGhzProtocolDecoderFiatV1* instance) {
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
|
||||
uint64_t key = instance->generic.data;
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||
}
|
||||
|
||||
uint8_t extra_bits =
|
||||
(instance->generic.data_count_bit > 64) ? (instance->generic.data_count_bit - 64) : 0;
|
||||
for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
|
||||
uint8_t byte_idx = 8 + (i / 8);
|
||||
uint8_t bit_pos = 7 - (i % 8);
|
||||
if(instance->extra_data & (1UL << (extra_bits - 1 - i))) {
|
||||
instance->raw_data[byte_idx] |= (1U << bit_pos);
|
||||
}
|
||||
}
|
||||
|
||||
instance->bit_count = instance->generic.data_count_bit;
|
||||
|
||||
if(instance->bit_count >= 56) {
|
||||
instance->generic.serial =
|
||||
((uint32_t)instance->raw_data[2] << 24) | ((uint32_t)instance->raw_data[3] << 16) |
|
||||
((uint32_t)instance->raw_data[4] << 8) | ((uint32_t)instance->raw_data[5]);
|
||||
instance->generic.btn = (instance->raw_data[6] >> 4) & 0x0F;
|
||||
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* fiat_marelli_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x8:
|
||||
case 0x7:
|
||||
return "Lock";
|
||||
case 0x0:
|
||||
case 0xB:
|
||||
return "Unlock";
|
||||
case 0xD:
|
||||
return "Trunk";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_fiat_v1_decoder = {
|
||||
.alloc = subghz_protocol_decoder_fiat_v1_alloc,
|
||||
.free = subghz_protocol_decoder_fiat_v1_free,
|
||||
.feed = subghz_protocol_decoder_fiat_v1_feed,
|
||||
.reset = subghz_protocol_decoder_fiat_v1_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_fiat_v1_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_fiat_v1_serialize,
|
||||
.deserialize = subghz_protocol_decoder_fiat_v1_deserialize,
|
||||
.get_string = subghz_protocol_decoder_fiat_v1_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_fiat_v1_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_fiat_v1 = {
|
||||
.name = FIAT_V1_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
.decoder = &subghz_protocol_fiat_v1_decoder,
|
||||
.encoder = &subghz_protocol_fiat_v1_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_decoder_fiat_v1_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderFiatV1* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolDecoderFiatV1));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_fiat_v1;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v1_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v1_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "decoder reset");
|
||||
instance->preamble_count = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->extra_data = 0;
|
||||
instance->te_last = 0;
|
||||
instance->te_sum = 0;
|
||||
instance->te_count = 0;
|
||||
instance->te_detected = 0;
|
||||
instance->generic.data = 0;
|
||||
instance->generic.data_count_bit = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v1_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
|
||||
uint32_t te_short = instance->te_detected ?
|
||||
instance->te_detected :
|
||||
(uint32_t)subghz_protocol_fiat_marelli_const.te_short;
|
||||
uint32_t te_long = te_short * 2;
|
||||
uint32_t te_delta = te_short / 2;
|
||||
if(te_delta < 30) te_delta = 30;
|
||||
uint32_t diff;
|
||||
|
||||
switch(instance->decoder_state) {
|
||||
case FiatMarelliDecoderStepReset:
|
||||
if(level) {
|
||||
if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN &&
|
||||
duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) {
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepPreamble, "preamble start");
|
||||
instance->preamble_count = 1;
|
||||
instance->te_sum = duration;
|
||||
instance->te_count = 1;
|
||||
instance->te_last = duration;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case FiatMarelliDecoderStepPreamble:
|
||||
if(duration >= FIAT_MARELLI_PREAMBLE_PULSE_MIN &&
|
||||
duration <= FIAT_MARELLI_PREAMBLE_PULSE_MAX) {
|
||||
instance->preamble_count++;
|
||||
instance->te_sum += duration;
|
||||
instance->te_count++;
|
||||
instance->te_last = duration;
|
||||
} else if(!level) {
|
||||
if(instance->preamble_count >= FIAT_MARELLI_PREAMBLE_MIN && instance->te_count > 0) {
|
||||
instance->te_detected = instance->te_sum / instance->te_count;
|
||||
uint32_t gap_threshold = instance->te_detected * FIAT_MARELLI_GAP_TE_MULT;
|
||||
|
||||
if(duration > gap_threshold) {
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepSync, "gap detected");
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "gap too short");
|
||||
}
|
||||
} else {
|
||||
fiat_marelli_set_state(
|
||||
instance, FiatMarelliDecoderStepReset, "preamble too short");
|
||||
}
|
||||
} else {
|
||||
fiat_marelli_set_state(
|
||||
instance, FiatMarelliDecoderStepReset, "invalid preamble pulse");
|
||||
}
|
||||
break;
|
||||
|
||||
case FiatMarelliDecoderStepSync: {
|
||||
uint32_t sync_min = instance->te_detected * FIAT_MARELLI_SYNC_TE_MIN_MULT;
|
||||
uint32_t sync_max = instance->te_detected * FIAT_MARELLI_SYNC_TE_MAX_MULT;
|
||||
|
||||
if(level && duration >= sync_min && duration <= sync_max) {
|
||||
fiat_marelli_prepare_data(instance);
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "sync timing mismatch");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case FiatMarelliDecoderStepData: {
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
bool frame_complete = false;
|
||||
|
||||
diff = (duration > te_short) ? (duration - te_short) : (te_short - duration);
|
||||
if(diff < te_delta) {
|
||||
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
} else {
|
||||
diff = (duration > te_long) ? (duration - te_long) : (te_long - duration);
|
||||
if(diff < te_delta) {
|
||||
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||
}
|
||||
}
|
||||
|
||||
if(event != ManchesterEventReset) {
|
||||
bool data_bit = false;
|
||||
if(manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
uint32_t new_bit = data_bit ? 1U : 0U;
|
||||
|
||||
if(instance->bit_count < FIAT_MARELLI_MAX_DATA_BITS) {
|
||||
uint8_t byte_idx = instance->bit_count / 8;
|
||||
uint8_t bit_pos = 7 - (instance->bit_count % 8);
|
||||
if(new_bit) {
|
||||
instance->raw_data[byte_idx] |= (1U << bit_pos);
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->bit_count < 64) {
|
||||
instance->generic.data = (instance->generic.data << 1) | new_bit;
|
||||
} else {
|
||||
instance->extra_data = (instance->extra_data << 1) | new_bit;
|
||||
}
|
||||
|
||||
instance->bit_count++;
|
||||
if(instance->bit_count >= FIAT_MARELLI_MAX_DATA_BITS) {
|
||||
frame_complete = true;
|
||||
}
|
||||
}
|
||||
} else if(instance->bit_count >= (FIAT_MARELLI_MAX_DATA_BITS - 2)) {
|
||||
frame_complete = true;
|
||||
} else {
|
||||
fiat_marelli_set_state(
|
||||
instance, FiatMarelliDecoderStepReset, "invalid manchester timing");
|
||||
}
|
||||
|
||||
if(frame_complete) {
|
||||
instance->generic.data_count_bit = instance->bit_count;
|
||||
|
||||
if(!fiat_marelli_try_recover_tail_bits(instance)) {
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "frame complete");
|
||||
instance->te_last = duration;
|
||||
break;
|
||||
}
|
||||
|
||||
bool crc_ok = true;
|
||||
|
||||
if(instance->bit_count >= FIAT_MARELLI_MAX_DATA_BITS) {
|
||||
uint8_t calc = fiat_marelli_crc8(instance->raw_data, 12);
|
||||
crc_ok = (calc == instance->raw_data[12]);
|
||||
}
|
||||
|
||||
if(crc_ok) {
|
||||
FURI_LOG_D(TAG, "Frame accepted (%u bits, CRC OK)", instance->bit_count);
|
||||
instance->generic.serial = ((uint32_t)instance->raw_data[2] << 24) |
|
||||
((uint32_t)instance->raw_data[3] << 16) |
|
||||
((uint32_t)instance->raw_data[4] << 8) |
|
||||
((uint32_t)instance->raw_data[5]);
|
||||
instance->generic.btn = (instance->raw_data[6] >> 4) & 0x0F;
|
||||
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
fiat_marelli_set_state(instance, FiatMarelliDecoderStepReset, "frame complete");
|
||||
}
|
||||
|
||||
instance->te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_fiat_v1_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
SubGhzBlockDecoder decoder = {
|
||||
.decode_data = instance->generic.data,
|
||||
.decode_count_bit =
|
||||
instance->generic.data_count_bit > 64 ? 64 : instance->generic.data_count_bit,
|
||||
};
|
||||
uint8_t hash =
|
||||
subghz_protocol_blocks_get_hash_data(&decoder, (decoder.decode_count_bit / 8) + 1);
|
||||
uint32_t x = instance->extra_data;
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
hash ^= (uint8_t)(x >> (i * 8));
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v1_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_write_uint32(flipper_format, "Extra", &instance->extra_data, 1);
|
||||
|
||||
uint32_t extra_bits =
|
||||
(instance->generic.data_count_bit > 64) ? (instance->generic.data_count_bit - 64) : 0;
|
||||
flipper_format_write_uint32(flipper_format, "Extra_bits", &extra_bits, 1);
|
||||
|
||||
uint32_t te = instance->te_detected;
|
||||
flipper_format_write_uint32(flipper_format, "TE", &te, 1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_fiat_marelli_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(instance->generic.data_count_bit != FIAT_MARELLI_MAX_DATA_BITS) {
|
||||
return SubGhzProtocolStatusErrorValueBitCount;
|
||||
}
|
||||
|
||||
uint32_t extra = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
|
||||
instance->extra_data = extra;
|
||||
}
|
||||
|
||||
uint32_t te = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "TE", &te, 1)) {
|
||||
instance->te_detected = te;
|
||||
}
|
||||
|
||||
fiat_marelli_rebuild_raw_data(instance);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_fiat_v1_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFiatV1* instance = context;
|
||||
|
||||
uint8_t epoch = instance->raw_data[6] & 0x0F;
|
||||
uint8_t counter = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
uint8_t scramble = (instance->raw_data[7] >> 1) & 0x03;
|
||||
uint8_t fixed = instance->raw_data[7] & 0x01;
|
||||
const char* crc_state = "N/A";
|
||||
uint8_t crc_value = instance->raw_data[12];
|
||||
|
||||
if(instance->bit_count >= 104) {
|
||||
uint8_t calc = fiat_marelli_crc8(instance->raw_data, 12);
|
||||
crc_state = (calc == instance->raw_data[12]) ? "OK" : "FAIL";
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Enc:%02X%02X%02X%02X%02X Scr:%02X\r\n"
|
||||
"Raw:%02X%02X Fixed:%X\r\n"
|
||||
"Sn:%08X Cnt:%02X\r\n"
|
||||
"Btn:%02X:[%s] Ep:%02X\r\n"
|
||||
"CRC:%02X [%s]\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->bit_count,
|
||||
instance->raw_data[8],
|
||||
instance->raw_data[9],
|
||||
instance->raw_data[10],
|
||||
instance->raw_data[11],
|
||||
instance->raw_data[12],
|
||||
(unsigned)scramble,
|
||||
instance->raw_data[6],
|
||||
instance->raw_data[7],
|
||||
(unsigned)fixed,
|
||||
(unsigned int)instance->generic.serial,
|
||||
(unsigned)counter,
|
||||
(unsigned)instance->generic.btn,
|
||||
fiat_marelli_button_name(instance->generic.btn),
|
||||
(unsigned)epoch,
|
||||
(unsigned)crc_value,
|
||||
crc_state);
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
#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/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define FIAT_V1_PROTOCOL_NAME "Fiat V1"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFiatV1 SubGhzProtocolDecoderFiatV1;
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_fiat_v1;
|
||||
|
||||
void* subghz_protocol_decoder_fiat_v1_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_fiat_v1_free(void* context);
|
||||
void subghz_protocol_decoder_fiat_v1_reset(void* context);
|
||||
void subghz_protocol_decoder_fiat_v1_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_fiat_v1_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_fiat_v1_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_fiat_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_fiat_v1_get_string(void* context, FuriString* output);
|
||||
@@ -74,6 +74,7 @@ const SubGhzProtocolDecoder subghz_protocol_ford_v1_decoder = {
|
||||
.get_string = subghz_protocol_decoder_ford_v1_get_string,
|
||||
};
|
||||
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_ford_v1_encoder = {
|
||||
.alloc = subghz_protocol_encoder_ford_v1_alloc,
|
||||
.free = subghz_protocol_encoder_ford_v1_free,
|
||||
@@ -96,15 +97,15 @@ const SubGhzProtocol ford_protocol_v1 = {
|
||||
static const char* ford_v1_get_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0:
|
||||
return "Sync";
|
||||
return "SYNC";
|
||||
case 1:
|
||||
return "Lock";
|
||||
return "LOCK";
|
||||
case 2:
|
||||
return "Unlock";
|
||||
return "UNLOCK";
|
||||
case 4:
|
||||
return "Trunk";
|
||||
return "BOOT";
|
||||
case 8:
|
||||
return "Panic";
|
||||
return "PANIC";
|
||||
default:
|
||||
return "??";
|
||||
}
|
||||
@@ -836,8 +837,9 @@ void subghz_protocol_decoder_ford_v1_get_string(void* context, FuriString* outpu
|
||||
#define FORD_V1_ENC_BURST_U32 0x433U
|
||||
#define FORD_V1_ENC_BURST_COUNT 6U
|
||||
|
||||
//#if(FORD_V1_ENC_BURST_U32 * FORD_V1_ENC_BURST_COUNT) != FORD_V1_ENC_UPLOAD_U32
|
||||
//#error Ford V1 encoder burst layout constants out of sync
|
||||
#if(FORD_V1_ENC_BURST_U32 * FORD_V1_ENC_BURST_COUNT) != FORD_V1_ENC_UPLOAD_U32
|
||||
#error Ford V1 encoder burst layout constants out of sync
|
||||
#endif
|
||||
#define FORD_V1_ENC_LD_PREAM_A 0x80000082U
|
||||
#define FORD_V1_ENC_LD_PREAM_B 0x40000082U
|
||||
#define FORD_V1_ENC_LD_SYNC_LO 0x40000041U
|
||||
|
||||
@@ -0,0 +1,814 @@
|
||||
#include "ford_v2.h"
|
||||
#include <furi.h>
|
||||
#include <string.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
|
||||
#define FORD_V2_TE_SHORT 200U
|
||||
#define FORD_V2_TE_LONG 400U
|
||||
#define FORD_V2_TE_DELTA 260U
|
||||
#define FORD_V2_INTER_BURST_GAP_US 15000U
|
||||
#define FORD_V2_PREAMBLE_MIN 64U
|
||||
#define FORD_V2_DATA_BITS 104U
|
||||
#define FORD_V2_DATA_BYTES 13U
|
||||
#define FORD_V2_SYNC_0 0x7FU
|
||||
#define FORD_V2_SYNC_1 0xA7U
|
||||
#define FORD_V2_ENC_TE_SHORT 240U
|
||||
#define FORD_V2_ENC_PREAMBLE_PAIRS 70U
|
||||
#define FORD_V2_ENC_BURST_COUNT 6U
|
||||
#define FORD_V2_ENC_INTER_BURST_GAP_US 16000U
|
||||
#define FORD_V2_ENC_ALLOC_ELEMS 2600U
|
||||
#define FORD_V2_ENC_SEPARATOR_ELEMS 2U
|
||||
#define FORD_V2_ENC_PREAMBLE_ELEMS (FORD_V2_ENC_PREAMBLE_PAIRS * 2U)
|
||||
#define FORD_V2_ENC_DATA_ELEMS ((FORD_V2_DATA_BITS - 1U) * 2U)
|
||||
#define FORD_V2_ENC_BURST_ELEMS \
|
||||
(FORD_V2_ENC_PREAMBLE_ELEMS + FORD_V2_ENC_SEPARATOR_ELEMS + FORD_V2_ENC_DATA_ELEMS)
|
||||
#define FORD_V2_ENC_UPLOAD_ELEMS \
|
||||
(FORD_V2_ENC_BURST_COUNT * FORD_V2_ENC_BURST_ELEMS + (FORD_V2_ENC_BURST_COUNT - 1U))
|
||||
#define FORD_V2_ENC_SYNC_LO_US 476U
|
||||
|
||||
#define FORD_V2_SYNC_BITS 16U
|
||||
#define FORD_V2_POST_SYNC_DECODE_COUNT_BIT 16U
|
||||
#define FORD_V2_KEY_BYTE_COUNT 8U
|
||||
#define FORD_V2_TAIL_RAW_BYTE_COUNT 5U
|
||||
#define FORD_V2_PREAMBLE_COUNT_MAX 0xFFFFU
|
||||
#define FORD_V2_ENCODER_DEFAULT_REPEAT 10U
|
||||
|
||||
static const uint16_t ford_v2_sync_shift16_inv =
|
||||
(uint16_t)(~(((uint16_t)FORD_V2_SYNC_0 << 8) | (uint16_t)FORD_V2_SYNC_1));
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_ford_v2_const = {
|
||||
.te_short = FORD_V2_TE_SHORT,
|
||||
.te_long = FORD_V2_TE_LONG,
|
||||
.te_delta = FORD_V2_TE_DELTA,
|
||||
.min_count_bit_for_found = FORD_V2_DATA_BITS,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FordV2DecoderStepReset = 0,
|
||||
FordV2DecoderStepPreamble = 1,
|
||||
FordV2DecoderStepSync = 2,
|
||||
FordV2DecoderStepData = 3,
|
||||
} FordV2DecoderStep;
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFordV2 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint16_t preamble_count;
|
||||
|
||||
uint8_t raw_bytes[FORD_V2_DATA_BYTES];
|
||||
uint8_t byte_count;
|
||||
|
||||
uint16_t sync_shift;
|
||||
uint8_t sync_bit_count;
|
||||
|
||||
uint64_t extra_data;
|
||||
uint16_t counter16;
|
||||
uint32_t tail31;
|
||||
bool structure_ok;
|
||||
} SubGhzProtocolDecoderFordV2;
|
||||
|
||||
typedef struct SubGhzProtocolEncoderFordV2 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint64_t extra_data;
|
||||
uint8_t raw_bytes[FORD_V2_DATA_BYTES];
|
||||
} SubGhzProtocolEncoderFordV2;
|
||||
|
||||
static void ford_v2_decoder_manchester_feed_event(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
ManchesterEvent event);
|
||||
|
||||
static void ford_v2_decoder_reset_state(SubGhzProtocolDecoderFordV2* instance) {
|
||||
instance->decoder.parser_step = FordV2DecoderStepReset;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.te_last = 0;
|
||||
|
||||
instance->byte_count = 0;
|
||||
instance->sync_shift = 0;
|
||||
instance->sync_bit_count = 0;
|
||||
instance->preamble_count = 0;
|
||||
instance->counter16 = 0;
|
||||
instance->tail31 = 0;
|
||||
instance->structure_ok = false;
|
||||
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
}
|
||||
|
||||
static bool ford_v2_duration_is_short(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, FORD_V2_TE_SHORT) < (int32_t)FORD_V2_TE_DELTA;
|
||||
}
|
||||
|
||||
static bool ford_v2_duration_is_long(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, FORD_V2_TE_LONG) < (int32_t)FORD_V2_TE_DELTA;
|
||||
}
|
||||
|
||||
static bool ford_v2_button_is_valid(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10:
|
||||
case 0x11:
|
||||
case 0x13:
|
||||
case 0x14:
|
||||
case 0x15:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t ford_v2_uint8_parity(uint8_t value) {
|
||||
uint8_t parity = 0U;
|
||||
while(value) {
|
||||
parity ^= (value & 1U);
|
||||
value >>= 1U;
|
||||
}
|
||||
return parity;
|
||||
}
|
||||
|
||||
static const char* ford_v2_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10:
|
||||
return "Lock";
|
||||
case 0x11:
|
||||
return "Unlock";
|
||||
case 0x13:
|
||||
return "Trunk";
|
||||
case 0x14:
|
||||
return "Panic";
|
||||
case 0x15:
|
||||
return "RemoteStart";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_extract_from_raw(SubGhzProtocolDecoderFordV2* instance) {
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
instance->generic.serial = ((uint32_t)k[2] << 24) | ((uint32_t)k[3] << 16) |
|
||||
((uint32_t)k[4] << 8) | (uint32_t)k[5];
|
||||
|
||||
instance->generic.btn = k[6];
|
||||
|
||||
instance->counter16 = (uint16_t)((((uint16_t)(k[7] & 0x7FU)) << 9) |
|
||||
(((uint16_t)k[8]) << 1) |
|
||||
((uint16_t)(k[9] >> 7)));
|
||||
|
||||
instance->generic.cnt = instance->counter16;
|
||||
|
||||
instance->tail31 = (((uint32_t)(k[9] & 0x7FU)) << 24) | ((uint32_t)k[10] << 16) |
|
||||
((uint32_t)k[11] << 8) | (uint32_t)k[12];
|
||||
|
||||
instance->structure_ok = true;
|
||||
|
||||
if(k[0] != FORD_V2_SYNC_0) instance->structure_ok = false;
|
||||
if(k[1] != FORD_V2_SYNC_1) instance->structure_ok = false;
|
||||
if(!ford_v2_button_is_valid(k[6])) instance->structure_ok = false;
|
||||
|
||||
if((k[7] & 0x7FU) != (uint8_t)((instance->counter16 >> 9) & 0x7FU)) {
|
||||
instance->structure_ok = false;
|
||||
}
|
||||
|
||||
if(k[8] != (uint8_t)((instance->counter16 >> 1) & 0xFFU)) {
|
||||
instance->structure_ok = false;
|
||||
}
|
||||
|
||||
if(((k[9] >> 7) & 1U) != (uint8_t)(instance->counter16 & 1U)) {
|
||||
instance->structure_ok = false;
|
||||
}
|
||||
|
||||
instance->generic.data = 0;
|
||||
for(uint8_t i = 0; i < FORD_V2_KEY_BYTE_COUNT; i++) {
|
||||
instance->generic.data = (instance->generic.data << 8) | (uint64_t)k[i];
|
||||
}
|
||||
|
||||
instance->generic.data_count_bit = FORD_V2_DATA_BITS;
|
||||
|
||||
instance->extra_data = 0;
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->extra_data = (instance->extra_data << 8) | (uint64_t)k[8U + i];
|
||||
}
|
||||
}
|
||||
|
||||
static bool ford_v2_decoder_commit_frame(SubGhzProtocolDecoderFordV2* instance) {
|
||||
if(instance->raw_bytes[0] != FORD_V2_SYNC_0 || instance->raw_bytes[1] != FORD_V2_SYNC_1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ford_v2_decoder_extract_from_raw(instance);
|
||||
|
||||
if(!instance->structure_ok) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_sync_enter_data(SubGhzProtocolDecoderFordV2* instance) {
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
instance->raw_bytes[0] = FORD_V2_SYNC_0;
|
||||
instance->raw_bytes[1] = FORD_V2_SYNC_1;
|
||||
instance->byte_count = 2U;
|
||||
instance->decoder.parser_step = FordV2DecoderStepData;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = FORD_V2_POST_SYNC_DECODE_COUNT_BIT;
|
||||
}
|
||||
|
||||
static bool ford_v2_decoder_sync_feed_event(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
ManchesterEvent event) {
|
||||
bool data_bit;
|
||||
|
||||
if(!manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->sync_shift = (uint16_t)((instance->sync_shift << 1) | (data_bit ? 1U : 0U));
|
||||
if(instance->sync_bit_count < FORD_V2_SYNC_BITS) {
|
||||
instance->sync_bit_count++;
|
||||
}
|
||||
|
||||
return instance->sync_bit_count >= FORD_V2_SYNC_BITS && instance->sync_shift == ford_v2_sync_shift16_inv;
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_manchester_feed_event(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
ManchesterEvent event) {
|
||||
bool data_bit;
|
||||
|
||||
if(instance->decoder.parser_step == FordV2DecoderStepSync) {
|
||||
if(ford_v2_decoder_sync_feed_event(instance, event)) {
|
||||
ford_v2_decoder_sync_enter_data(instance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->decoder.parser_step != FordV2DecoderStepData) {
|
||||
return;
|
||||
}
|
||||
|
||||
data_bit = !data_bit;
|
||||
|
||||
instance->decoder.decode_data =
|
||||
(instance->decoder.decode_data << 1) | (data_bit ? 1U : 0U);
|
||||
instance->decoder.decode_count_bit++;
|
||||
|
||||
if((instance->decoder.decode_count_bit & 7U) == 0U) {
|
||||
uint8_t byte_val = (uint8_t)(instance->decoder.decode_data & 0xFFU);
|
||||
|
||||
if(instance->byte_count < FORD_V2_DATA_BYTES) {
|
||||
instance->raw_bytes[instance->byte_count] = byte_val;
|
||||
instance->byte_count++;
|
||||
}
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
|
||||
if(instance->byte_count == FORD_V2_DATA_BYTES) {
|
||||
(void)ford_v2_decoder_commit_frame(instance);
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ford_v2_decoder_manchester_feed_pulse(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(ford_v2_duration_is_short(duration)) {
|
||||
ManchesterEvent ev = level ? ManchesterEventShortHigh : ManchesterEventShortLow;
|
||||
ford_v2_decoder_manchester_feed_event(instance, ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(ford_v2_duration_is_long(duration)) {
|
||||
ManchesterEvent ev = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
ford_v2_decoder_manchester_feed_event(instance, ev);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_enter_sync_from_preamble(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
instance->decoder.parser_step = FordV2DecoderStepSync;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->byte_count = 0;
|
||||
instance->sync_shift = 0;
|
||||
instance->sync_bit_count = 0;
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
|
||||
if(ford_v2_duration_is_short(duration)) {
|
||||
ManchesterEvent ev = level ? ManchesterEventShortHigh : ManchesterEventShortLow;
|
||||
if(ev == ManchesterEventShortLow || ev == ManchesterEventLongLow) {
|
||||
instance->manchester_state = ManchesterStateMid0;
|
||||
}
|
||||
ford_v2_decoder_manchester_feed_event(instance, ev);
|
||||
} else if(ford_v2_duration_is_long(duration)) {
|
||||
ManchesterEvent ev = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
if(ev == ManchesterEventShortLow || ev == ManchesterEventLongLow) {
|
||||
instance->manchester_state = ManchesterStateMid0;
|
||||
}
|
||||
ford_v2_decoder_manchester_feed_event(instance, ev);
|
||||
} else {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_rebuild_raw_buffer(SubGhzProtocolDecoderFordV2* instance) {
|
||||
for(uint8_t i = 0; i < FORD_V2_KEY_BYTE_COUNT; i++) {
|
||||
instance->raw_bytes[i] = (uint8_t)(instance->generic.data >> (56U - i * 8U));
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->raw_bytes[8U + i] = (uint8_t)(instance->extra_data >> (32U - i * 8U));
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ford_v2_encoder_add_level(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
size_t idx = instance->encoder.size_upload;
|
||||
if(idx > 0 && level_duration_get_level(instance->encoder.upload[idx - 1]) == level) {
|
||||
uint32_t prev = level_duration_get_duration(instance->encoder.upload[idx - 1]);
|
||||
instance->encoder.upload[idx - 1] = level_duration_make(level, prev + duration);
|
||||
} else {
|
||||
furi_check(idx < FORD_V2_ENC_ALLOC_ELEMS);
|
||||
instance->encoder.upload[idx] = level_duration_make(level, duration);
|
||||
instance->encoder.size_upload++;
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_rebuild_raw_from_payload(SubGhzProtocolEncoderFordV2* instance) {
|
||||
for(uint8_t i = 0; i < FORD_V2_KEY_BYTE_COUNT; i++) {
|
||||
instance->raw_bytes[i] = (uint8_t)(instance->generic.data >> (56U - i * 8U));
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->raw_bytes[8U + i] = (uint8_t)(instance->extra_data >> (32U - i * 8U));
|
||||
}
|
||||
|
||||
const uint8_t btn = instance->raw_bytes[6];
|
||||
const uint8_t k7_msb = (uint8_t)(ford_v2_uint8_parity(btn) << 7);
|
||||
instance->raw_bytes[7] = (instance->raw_bytes[7] & 0x7FU) | k7_msb;
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_refresh_data_from_raw(SubGhzProtocolEncoderFordV2* instance) {
|
||||
instance->generic.data = 0;
|
||||
for(uint8_t i = 0; i < FORD_V2_KEY_BYTE_COUNT; i++) {
|
||||
instance->generic.data = (instance->generic.data << 8) | (uint64_t)instance->raw_bytes[i];
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ford_v2_encoder_emit_manchester_bit(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
bool bit) {
|
||||
if(bit) {
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_TE_SHORT);
|
||||
ford_v2_encoder_add_level(instance, false, FORD_V2_ENC_TE_SHORT);
|
||||
} else {
|
||||
ford_v2_encoder_add_level(instance, false, FORD_V2_ENC_TE_SHORT);
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_TE_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_emit_burst(SubGhzProtocolEncoderFordV2* instance) {
|
||||
for(uint8_t i = 0; i < FORD_V2_ENC_PREAMBLE_PAIRS; i++) {
|
||||
ford_v2_encoder_add_level(instance, false, FORD_V2_ENC_TE_SHORT);
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_TE_SHORT);
|
||||
}
|
||||
|
||||
ford_v2_encoder_add_level(instance, false, FORD_V2_ENC_SYNC_LO_US);
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_TE_SHORT);
|
||||
|
||||
for(uint16_t bit_pos = 1U; bit_pos < FORD_V2_DATA_BITS; bit_pos++) {
|
||||
const uint8_t byte_idx = (uint8_t)(bit_pos / 8U);
|
||||
const uint8_t bit_idx = (uint8_t)(7U - (bit_pos % 8U));
|
||||
ford_v2_encoder_emit_manchester_bit(
|
||||
instance, ((instance->raw_bytes[byte_idx] >> bit_idx) & 1U) != 0U);
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_build_upload(SubGhzProtocolEncoderFordV2* instance) {
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
for(uint8_t burst = 0; burst < FORD_V2_ENC_BURST_COUNT; burst++) {
|
||||
ford_v2_encoder_emit_burst(instance);
|
||||
|
||||
if(burst + 1U < FORD_V2_ENC_BURST_COUNT) {
|
||||
ford_v2_encoder_add_level(instance, true, FORD_V2_ENC_INTER_BURST_GAP_US);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_read_optional_tail_raw(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
FlipperFormat* flipper_format) {
|
||||
instance->extra_data = 0U;
|
||||
uint8_t tail_raw[FORD_V2_TAIL_RAW_BYTE_COUNT] = {0};
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_hex(flipper_format, "TailRaw", tail_raw, sizeof(tail_raw))) {
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->extra_data = (instance->extra_data << 8) | (uint64_t)tail_raw[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SubGhzProtocolStatus ford_v2_encoder_deserialize_read_header(
|
||||
SubGhzProtocolEncoderFordV2* instance,
|
||||
FlipperFormat* flipper_format,
|
||||
FuriString* temp_str) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
|
||||
return SubGhzProtocolStatusError;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus g = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, FORD_V2_DATA_BITS);
|
||||
if(g != SubGhzProtocolStatusOk) {
|
||||
return g;
|
||||
}
|
||||
|
||||
ford_v2_encoder_read_optional_tail_raw(instance, flipper_format);
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
static SubGhzProtocolStatus ford_v2_encoder_deserialize_validate_and_pack(SubGhzProtocolEncoderFordV2* instance) {
|
||||
ford_v2_encoder_rebuild_raw_from_payload(instance);
|
||||
|
||||
if(!ford_v2_button_is_valid(instance->raw_bytes[6])) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
ford_v2_encoder_refresh_data_from_raw(instance);
|
||||
instance->generic.btn = instance->raw_bytes[6];
|
||||
instance->generic.serial = ((uint32_t)instance->raw_bytes[2] << 24) |
|
||||
((uint32_t)instance->raw_bytes[3] << 16) |
|
||||
((uint32_t)instance->raw_bytes[4] << 8) |
|
||||
(uint32_t)instance->raw_bytes[5];
|
||||
instance->generic.cnt = (uint16_t)((((uint16_t)(instance->raw_bytes[7] & 0x7FU)) << 9) |
|
||||
(((uint16_t)instance->raw_bytes[8]) << 1) |
|
||||
((uint16_t)(instance->raw_bytes[9] >> 7)));
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
static void ford_v2_encoder_deserialize_apply_repeat(SubGhzProtocolEncoderFordV2* instance, FlipperFormat* flipper_format) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t repeat = FORD_V2_ENCODER_DEFAULT_REPEAT;
|
||||
if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat, 1)) {
|
||||
instance->encoder.repeat = repeat;
|
||||
}
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderFordV2* instance = calloc(1, sizeof(SubGhzProtocolEncoderFordV2));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &ford_protocol_v2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = FORD_V2_ENCODER_DEFAULT_REPEAT;
|
||||
instance->encoder.upload = calloc(FORD_V2_ENC_ALLOC_ELEMS, sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ford_v2_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_ford_v2_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = FORD_V2_ENCODER_DEFAULT_REPEAT;
|
||||
instance->generic.data_count_bit = FORD_V2_DATA_BITS;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
furi_check(temp_str);
|
||||
|
||||
SubGhzProtocolStatus ret = ford_v2_encoder_deserialize_read_header(instance, flipper_format, temp_str);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
ret = ford_v2_encoder_deserialize_validate_and_pack(instance);
|
||||
}
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
ford_v2_encoder_deserialize_apply_repeat(instance, flipper_format);
|
||||
ford_v2_encoder_build_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
}
|
||||
|
||||
furi_string_free(temp_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ford_v2_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ford_v2_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV2* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0U) {
|
||||
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.front = 0U;
|
||||
instance->encoder.repeat--;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_ford_v2_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = calloc(1, sizeof(SubGhzProtocolDecoderFordV2));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &ford_protocol_v2;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_free(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_reset(void* context) {
|
||||
furi_check(context);
|
||||
ford_v2_decoder_reset_state((SubGhzProtocolDecoderFordV2*)context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FordV2DecoderStepReset:
|
||||
if(ford_v2_duration_is_short(duration)) {
|
||||
instance->preamble_count = 1U;
|
||||
instance->decoder.parser_step = FordV2DecoderStepPreamble;
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV2DecoderStepPreamble:
|
||||
if(ford_v2_duration_is_short(duration)) {
|
||||
if(instance->preamble_count < FORD_V2_PREAMBLE_COUNT_MAX) {
|
||||
instance->preamble_count++;
|
||||
}
|
||||
} else if(!level && ford_v2_duration_is_long(duration)) {
|
||||
if(instance->preamble_count >= FORD_V2_PREAMBLE_MIN) {
|
||||
ford_v2_decoder_enter_sync_from_preamble(instance, level, duration);
|
||||
} else {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
} else {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV2DecoderStepSync:
|
||||
case FordV2DecoderStepData:
|
||||
if(ford_v2_decoder_manchester_feed_pulse(instance, level, duration)) {
|
||||
} else {
|
||||
if(instance->decoder.parser_step == FordV2DecoderStepSync &&
|
||||
duration >= FORD_V2_INTER_BURST_GAP_US) {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
if(instance->decoder.parser_step == FordV2DecoderStepSync) {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
if(instance->decoder.parser_step == FordV2DecoderStepData) {
|
||||
if(duration >= FORD_V2_INTER_BURST_GAP_US) {
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
break;
|
||||
}
|
||||
}
|
||||
ford_v2_decoder_reset_state(instance);
|
||||
}
|
||||
|
||||
instance->decoder.te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_ford_v2_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
const uint16_t cnt = (uint16_t)((((uint16_t)(k[7] & 0x7FU)) << 9) | (((uint16_t)k[8]) << 1) |
|
||||
((uint16_t)(k[9] >> 7)));
|
||||
const uint32_t tail = (((uint32_t)(k[9] & 0x7FU)) << 24) | ((uint32_t)k[10] << 16) |
|
||||
((uint32_t)k[11] << 8) | (uint32_t)k[12];
|
||||
|
||||
uint32_t mix = ((uint32_t)k[2] << 24) | ((uint32_t)k[3] << 16) | ((uint32_t)k[4] << 8) |
|
||||
(uint32_t)k[5];
|
||||
mix ^= (uint32_t)k[6] << 16;
|
||||
mix ^= (uint32_t)cnt << 8;
|
||||
mix ^= tail;
|
||||
|
||||
return (uint8_t)((mix >> 0) ^ (mix >> 8) ^ (mix >> 16) ^ (mix >> 24) ^ (uint8_t)(cnt >> 8) ^
|
||||
(uint8_t)(tail >> 16));
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "Serial", &instance->generic.serial, 1);
|
||||
|
||||
uint32_t btn = instance->generic.btn;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Btn", &btn, 1);
|
||||
|
||||
uint32_t cnt = instance->generic.cnt;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Cnt", &cnt, 1);
|
||||
|
||||
uint32_t tail31 = instance->tail31;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Tail31", &tail31, 1);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(flipper_format, "TailRaw", &instance->raw_bytes[8], 5);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void ford_v2_decoder_read_tail_raw_if_present(
|
||||
SubGhzProtocolDecoderFordV2* instance,
|
||||
FlipperFormat* flipper_format) {
|
||||
uint8_t tail_raw[FORD_V2_TAIL_RAW_BYTE_COUNT] = {0};
|
||||
if(flipper_format_read_hex(flipper_format, "TailRaw", tail_raw, sizeof(tail_raw))) {
|
||||
instance->extra_data = 0;
|
||||
for(uint8_t i = 0; i < FORD_V2_TAIL_RAW_BYTE_COUNT; i++) {
|
||||
instance->extra_data = (instance->extra_data << 8) | (uint64_t)tail_raw[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_ford_v2_const.min_count_bit_for_found);
|
||||
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if(instance->generic.data_count_bit != FORD_V2_DATA_BITS) {
|
||||
return SubGhzProtocolStatusErrorValueBitCount;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
ford_v2_decoder_read_tail_raw_if_present(instance, flipper_format);
|
||||
|
||||
ford_v2_decoder_rebuild_raw_buffer(instance);
|
||||
ford_v2_decoder_extract_from_raw(instance);
|
||||
|
||||
if(!instance->structure_ok) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderFordV2* instance = context;
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\r\n"
|
||||
"Sn:%08lX Btn:%02X [%s]\r\n"
|
||||
"Cnt:%u Struct:%s\r\n"
|
||||
"Tail31:%08lX\r\n"
|
||||
"TailRaw:%02X%02X%02X%02X%02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->generic.data_count_bit,
|
||||
k[2],
|
||||
k[3],
|
||||
k[4],
|
||||
k[5],
|
||||
k[6],
|
||||
k[7],
|
||||
k[8],
|
||||
k[9],
|
||||
k[10],
|
||||
k[11],
|
||||
k[12],
|
||||
(unsigned long)instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
ford_v2_button_name(instance->generic.btn),
|
||||
(unsigned)instance->counter16,
|
||||
instance->structure_ok ? "OK" : "BAD",
|
||||
(unsigned long)instance->tail31,
|
||||
k[8],
|
||||
k[9],
|
||||
k[10],
|
||||
k[11],
|
||||
k[12]);
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_ford_v2_decoder = {
|
||||
.alloc = subghz_protocol_decoder_ford_v2_alloc,
|
||||
.free = subghz_protocol_decoder_ford_v2_free,
|
||||
.feed = subghz_protocol_decoder_ford_v2_feed,
|
||||
.reset = subghz_protocol_decoder_ford_v2_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_ford_v2_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_ford_v2_serialize,
|
||||
.deserialize = subghz_protocol_decoder_ford_v2_deserialize,
|
||||
.get_string = subghz_protocol_decoder_ford_v2_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_ford_v2_encoder = {
|
||||
.alloc = subghz_protocol_encoder_ford_v2_alloc,
|
||||
.free = subghz_protocol_encoder_ford_v2_free,
|
||||
.deserialize = subghz_protocol_encoder_ford_v2_deserialize,
|
||||
.stop = subghz_protocol_encoder_ford_v2_stop,
|
||||
.yield = subghz_protocol_encoder_ford_v2_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol ford_protocol_v2 = {
|
||||
.name = FORD_PROTOCOL_V2_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save
|
||||
| SubGhzProtocolFlag_Send
|
||||
,
|
||||
.decoder = &subghz_protocol_ford_v2_decoder,
|
||||
.encoder = &subghz_protocol_ford_v2_encoder,
|
||||
};
|
||||
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.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 <lib/toolbox/manchester_encoder.h>
|
||||
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <furi.h>
|
||||
|
||||
#define FORD_PROTOCOL_V2_NAME "Ford V2"
|
||||
|
||||
extern const SubGhzProtocol ford_protocol_v2;
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_ford_v2_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_ford_v2_encoder;
|
||||
|
||||
void* subghz_protocol_decoder_ford_v2_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_ford_v2_free(void* context);
|
||||
void subghz_protocol_decoder_ford_v2_reset(void* context);
|
||||
void subghz_protocol_decoder_ford_v2_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_ford_v2_get_hash_data(void* context);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_decoder_ford_v2_get_string(void* context, FuriString* output);
|
||||
void* subghz_protocol_encoder_ford_v2_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_ford_v2_free(void* context);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_ford_v2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_encoder_ford_v2_stop(void* context);
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ford_v2_yield(void* context);
|
||||
@@ -0,0 +1,868 @@
|
||||
#include "ford_v3.h"
|
||||
#include <furi.h>
|
||||
#include <string.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
|
||||
#define FORD_V3_TE_SHORT 200U
|
||||
#define FORD_V3_TE_LONG 400U
|
||||
#define FORD_V3_TE_DELTA 260U
|
||||
#define FORD_V3_INTER_BURST_GAP_US 15000U
|
||||
#define FORD_V3_PREAMBLE_MIN 64U
|
||||
|
||||
#define FORD_V3_DATA_BYTES 17U
|
||||
#define FORD_V3_DATA_BITS 136U
|
||||
#define FORD_V3_SYNC_0 0x7FU
|
||||
#define FORD_V3_SYNC_1 0xA7U
|
||||
#define FORD_V3_CRC_LEN 12U
|
||||
#define FORD_V3_CRC_OFFSET 3U
|
||||
#define FORD_V3_CRC_POLY 0x1021U
|
||||
#define FORD_V3_CRC_INIT 0x0000U
|
||||
#define FORD_V3_CRYPT_OFFSET 7U
|
||||
#define FORD_V3_CRYPT_LEN 8U
|
||||
|
||||
#define FORD_V3_ENC_TE_SHORT 240U
|
||||
#define FORD_V3_ENC_PREAMBLE_PAIRS 70U
|
||||
#define FORD_V3_ENC_BURST_COUNT 6U
|
||||
#define FORD_V3_ENC_INTER_BURST_GAP_US 16000U
|
||||
#define FORD_V3_ENC_ALLOC_ELEMS 2600U
|
||||
#define FORD_V3_ENC_SYNC_LO_US 476U
|
||||
#define FORD_V3_ENC_SEPARATOR_ELEMS 2U
|
||||
#define FORD_V3_ENC_PREAMBLE_ELEMS (FORD_V3_ENC_PREAMBLE_PAIRS * 2U)
|
||||
#define FORD_V3_ENC_DATA_ELEMS ((FORD_V3_DATA_BITS - 1U) * 2U)
|
||||
#define FORD_V3_ENC_BURST_ELEMS \
|
||||
(FORD_V3_ENC_PREAMBLE_ELEMS + FORD_V3_ENC_SEPARATOR_ELEMS + FORD_V3_ENC_DATA_ELEMS)
|
||||
#define FORD_V3_ENC_UPLOAD_ELEMS \
|
||||
(FORD_V3_ENC_BURST_COUNT * FORD_V3_ENC_BURST_ELEMS + (FORD_V3_ENC_BURST_COUNT - 1U))
|
||||
#define FORD_V3_ENCODER_DEFAULT_REPEAT 10U
|
||||
|
||||
#define FORD_V3_SYNC_BITS 16U
|
||||
#define FORD_V3_POST_SYNC_DECODE_COUNT_BIT 16U
|
||||
#define FORD_V3_PREAMBLE_COUNT_MAX 0xFFFFU
|
||||
|
||||
static const uint16_t ford_v3_sync_shift16_inv =
|
||||
(uint16_t)(~(((uint16_t)FORD_V3_SYNC_0 << 8) | (uint16_t)FORD_V3_SYNC_1));
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_ford_v3_const = {
|
||||
.te_short = FORD_V3_TE_SHORT,
|
||||
.te_long = FORD_V3_TE_LONG,
|
||||
.te_delta = FORD_V3_TE_DELTA,
|
||||
.min_count_bit_for_found = FORD_V3_DATA_BITS,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
FordV3DecoderStepReset = 0,
|
||||
FordV3DecoderStepPreamble = 1,
|
||||
FordV3DecoderStepSync = 2,
|
||||
FordV3DecoderStepData = 3,
|
||||
} FordV3DecoderStep;
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFordV3 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint16_t preamble_count;
|
||||
|
||||
uint8_t raw_bytes[FORD_V3_DATA_BYTES];
|
||||
uint8_t byte_count;
|
||||
|
||||
uint16_t sync_shift;
|
||||
uint8_t sync_bit_count;
|
||||
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
uint16_t counter16;
|
||||
uint16_t crc_received;
|
||||
uint16_t crc_computed;
|
||||
bool crc_ok;
|
||||
bool structure_ok;
|
||||
|
||||
uint8_t crypt_buf[FORD_V3_CRYPT_LEN];
|
||||
} SubGhzProtocolDecoderFordV3;
|
||||
|
||||
typedef struct SubGhzProtocolEncoderFordV3 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t raw_bytes[FORD_V3_DATA_BYTES];
|
||||
|
||||
uint8_t raw_freq0[FORD_V3_DATA_BYTES];
|
||||
uint8_t raw_freq1[FORD_V3_DATA_BYTES];
|
||||
uint8_t raw_freq2[FORD_V3_DATA_BYTES];
|
||||
} SubGhzProtocolEncoderFordV3;
|
||||
|
||||
static void ford_v3_decoder_manchester_feed_event(
|
||||
SubGhzProtocolDecoderFordV3* instance,
|
||||
ManchesterEvent event);
|
||||
|
||||
static uint16_t ford_v3_crc16(const uint8_t* data, uint8_t len) {
|
||||
uint16_t crc = FORD_V3_CRC_INIT;
|
||||
while(len--) {
|
||||
crc ^= (uint16_t)(*data++) << 8;
|
||||
for(uint8_t i = 0; i < 8U; i++) {
|
||||
crc = (crc & 0x8000U) ? (uint16_t)((crc << 1) ^ FORD_V3_CRC_POLY)
|
||||
: (uint16_t)(crc << 1);
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static void ford_v3_crc_process(uint8_t* buf) {
|
||||
uint16_t crc = ford_v3_crc16(&buf[FORD_V3_CRC_OFFSET], FORD_V3_CRC_LEN);
|
||||
buf[15] = (uint8_t)((crc >> 8) & 0xFFU);
|
||||
buf[16] = (uint8_t)(crc & 0xFFU);
|
||||
}
|
||||
|
||||
static uint8_t ford_v3_uint8_parity(uint8_t value) {
|
||||
uint8_t p = 0U;
|
||||
while(value) {
|
||||
p ^= (value & 1U);
|
||||
value >>= 1U;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static void ford_v3_encrypt_buffer_process(uint8_t* crypt) {
|
||||
uint8_t t0 = (0xAAU & crypt[5]) | (0x55U & crypt[6]);
|
||||
uint8_t t1 = (0x55U & crypt[5]) | (0xAAU & crypt[6]);
|
||||
crypt[5] = t0;
|
||||
crypt[6] = t1;
|
||||
|
||||
uint8_t par = ford_v3_uint8_parity(crypt[7]);
|
||||
|
||||
if(par) {
|
||||
uint8_t mask = crypt[6];
|
||||
crypt[0] ^= mask;
|
||||
crypt[1] ^= mask;
|
||||
crypt[2] ^= mask;
|
||||
crypt[3] ^= mask;
|
||||
crypt[4] ^= mask;
|
||||
crypt[5] ^= mask;
|
||||
} else {
|
||||
uint8_t mask = crypt[5];
|
||||
crypt[0] ^= mask;
|
||||
crypt[1] ^= mask;
|
||||
crypt[2] ^= mask;
|
||||
crypt[3] ^= mask;
|
||||
crypt[4] ^= mask;
|
||||
crypt[6] ^= mask;
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v3_encrypt(uint8_t* buf) {
|
||||
uint8_t crypt[FORD_V3_CRYPT_LEN];
|
||||
for(uint8_t i = 0; i < FORD_V3_CRYPT_LEN; i++) {
|
||||
crypt[i] = buf[FORD_V3_CRYPT_OFFSET + i];
|
||||
}
|
||||
|
||||
uint8_t sum = 0U;
|
||||
for(uint8_t i = 0; i < 7U; i++) sum += crypt[i];
|
||||
crypt[7] = sum;
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V3_CRYPT_LEN; i++) {
|
||||
buf[FORD_V3_CRYPT_OFFSET + i] = crypt[i];
|
||||
}
|
||||
|
||||
ford_v3_encrypt_buffer_process(crypt);
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V3_CRYPT_LEN; i++) {
|
||||
buf[FORD_V3_CRYPT_OFFSET + i] = crypt[i];
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v3_decrypt(const uint8_t* enc_block, uint8_t* crypt_out) {
|
||||
uint8_t crypt[20] = {0};
|
||||
for(uint8_t i = 0; i < FORD_V3_CRYPT_LEN; i++) crypt[i] = enc_block[i];
|
||||
|
||||
crypt[17] = crypt[1];
|
||||
crypt[18] = 0x00U;
|
||||
{
|
||||
uint8_t tmp = crypt[17];
|
||||
while(tmp) {
|
||||
if(tmp & 1U) crypt[18] ^= 1U;
|
||||
tmp >>= 1U;
|
||||
}
|
||||
}
|
||||
|
||||
if(crypt[18] & 0xFFU) {
|
||||
crypt[17] = enc_block[6];
|
||||
for(uint8_t i = 1; i < 7U; i++) {
|
||||
crypt[i] = (crypt[i] ^ crypt[17]) & 0xFFU;
|
||||
}
|
||||
} else {
|
||||
crypt[17] = enc_block[5];
|
||||
for(uint8_t i = 1; i < 6U; i++) {
|
||||
crypt[i] = (crypt[i] ^ crypt[17]) & 0xFFU;
|
||||
}
|
||||
crypt[7] ^= crypt[17];
|
||||
}
|
||||
|
||||
crypt[19] = (crypt[6] & 0xAAU) | (crypt[7] & 0x55U);
|
||||
crypt[7] = (crypt[7] & 0xAAU) | (crypt[6] & 0x55U);
|
||||
crypt[6] = crypt[19] & 0xFFU;
|
||||
|
||||
crypt[20 - 1] = 7U;
|
||||
crypt[17] = 0x00U;
|
||||
while(crypt[19]) {
|
||||
break;
|
||||
}
|
||||
{
|
||||
uint8_t cnt = 7U;
|
||||
uint8_t sum = 0U;
|
||||
while(cnt) {
|
||||
--cnt;
|
||||
sum += crypt[cnt];
|
||||
}
|
||||
crypt[17] = sum;
|
||||
}
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V3_CRYPT_LEN; i++) crypt_out[i] = crypt[i];
|
||||
}
|
||||
|
||||
static bool ford_v3_button_is_valid(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10:
|
||||
case 0x20:
|
||||
case 0x40:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const char* ford_v3_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x10: return "Lock";
|
||||
case 0x20: return "Unlock";
|
||||
case 0x40: return "Trunk";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v3_decoder_reset_state(SubGhzProtocolDecoderFordV3* instance) {
|
||||
instance->decoder.parser_step = FordV3DecoderStepReset;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.te_last = 0;
|
||||
|
||||
instance->byte_count = 0;
|
||||
instance->sync_shift = 0;
|
||||
instance->sync_bit_count = 0;
|
||||
instance->preamble_count = 0;
|
||||
instance->counter16 = 0;
|
||||
instance->crc_ok = false;
|
||||
instance->structure_ok = false;
|
||||
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
memset(instance->crypt_buf, 0, sizeof(instance->crypt_buf));
|
||||
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset,
|
||||
&instance->manchester_state, NULL);
|
||||
}
|
||||
|
||||
static bool ford_v3_duration_is_short(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, FORD_V3_TE_SHORT) < (int32_t)FORD_V3_TE_DELTA;
|
||||
}
|
||||
|
||||
static bool ford_v3_duration_is_long(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, FORD_V3_TE_LONG) < (int32_t)FORD_V3_TE_DELTA;
|
||||
}
|
||||
|
||||
static void ford_v3_decoder_extract_from_raw(SubGhzProtocolDecoderFordV3* instance) {
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
instance->structure_ok = false;
|
||||
|
||||
if(k[0] != FORD_V3_SYNC_0 || k[1] != FORD_V3_SYNC_1) return;
|
||||
|
||||
instance->serial =
|
||||
((uint32_t)k[4] << 16) | ((uint32_t)k[5] << 8) | (uint32_t)k[6];
|
||||
instance->generic.serial = instance->serial;
|
||||
|
||||
ford_v3_decrypt(&k[FORD_V3_CRYPT_OFFSET], instance->crypt_buf);
|
||||
|
||||
instance->btn = instance->crypt_buf[4];
|
||||
instance->generic.btn = instance->btn;
|
||||
|
||||
instance->counter16 = ((uint16_t)instance->crypt_buf[5] << 8) |
|
||||
(uint16_t)instance->crypt_buf[6];
|
||||
instance->generic.cnt = instance->counter16;
|
||||
|
||||
instance->crc_received =
|
||||
((uint16_t)k[15] << 8) | (uint16_t)k[16];
|
||||
instance->crc_computed = ford_v3_crc16(&k[FORD_V3_CRC_OFFSET], FORD_V3_CRC_LEN);
|
||||
instance->crc_ok = (instance->crc_received == instance->crc_computed);
|
||||
|
||||
if(!instance->crc_ok) return;
|
||||
if(!ford_v3_button_is_valid(instance->btn)) return;
|
||||
|
||||
instance->generic.data = 0;
|
||||
for(uint8_t i = 0; i < 8U; i++) {
|
||||
instance->generic.data = (instance->generic.data << 8) | (uint64_t)k[i];
|
||||
}
|
||||
instance->generic.data_count_bit = FORD_V3_DATA_BITS;
|
||||
|
||||
instance->structure_ok = true;
|
||||
}
|
||||
|
||||
static bool ford_v3_decoder_commit_frame(SubGhzProtocolDecoderFordV3* instance) {
|
||||
if(instance->raw_bytes[0] != FORD_V3_SYNC_0 ||
|
||||
instance->raw_bytes[1] != FORD_V3_SYNC_1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ford_v3_decoder_extract_from_raw(instance);
|
||||
|
||||
if(!instance->structure_ok) return false;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void ford_v3_decoder_sync_enter_data(SubGhzProtocolDecoderFordV3* instance) {
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
instance->raw_bytes[0] = FORD_V3_SYNC_0;
|
||||
instance->raw_bytes[1] = FORD_V3_SYNC_1;
|
||||
instance->byte_count = 2U;
|
||||
instance->decoder.parser_step = FordV3DecoderStepData;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = FORD_V3_POST_SYNC_DECODE_COUNT_BIT;
|
||||
}
|
||||
|
||||
static bool ford_v3_decoder_sync_feed_event(
|
||||
SubGhzProtocolDecoderFordV3* instance,
|
||||
ManchesterEvent event) {
|
||||
bool data_bit;
|
||||
|
||||
if(!manchester_advance(
|
||||
instance->manchester_state, event,
|
||||
&instance->manchester_state, &data_bit)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->sync_shift =
|
||||
(uint16_t)((instance->sync_shift << 1) | (data_bit ? 1U : 0U));
|
||||
if(instance->sync_bit_count < FORD_V3_SYNC_BITS) {
|
||||
instance->sync_bit_count++;
|
||||
}
|
||||
|
||||
return (instance->sync_bit_count >= FORD_V3_SYNC_BITS) &&
|
||||
(instance->sync_shift == ford_v3_sync_shift16_inv);
|
||||
}
|
||||
|
||||
static void ford_v3_decoder_manchester_feed_event(
|
||||
SubGhzProtocolDecoderFordV3* instance,
|
||||
ManchesterEvent event) {
|
||||
bool data_bit;
|
||||
|
||||
if(instance->decoder.parser_step == FordV3DecoderStepSync) {
|
||||
if(ford_v3_decoder_sync_feed_event(instance, event)) {
|
||||
ford_v3_decoder_sync_enter_data(instance);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(!manchester_advance(
|
||||
instance->manchester_state, event,
|
||||
&instance->manchester_state, &data_bit)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->decoder.parser_step != FordV3DecoderStepData) return;
|
||||
|
||||
data_bit = !data_bit;
|
||||
|
||||
instance->decoder.decode_data =
|
||||
(instance->decoder.decode_data << 1) | (data_bit ? 1U : 0U);
|
||||
instance->decoder.decode_count_bit++;
|
||||
|
||||
if((instance->decoder.decode_count_bit & 7U) == 0U) {
|
||||
uint8_t byte_val = (uint8_t)(instance->decoder.decode_data & 0xFFU);
|
||||
|
||||
if(instance->byte_count < FORD_V3_DATA_BYTES) {
|
||||
instance->raw_bytes[instance->byte_count] = byte_val;
|
||||
instance->byte_count++;
|
||||
}
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
|
||||
if(instance->byte_count == FORD_V3_DATA_BYTES) {
|
||||
(void)ford_v3_decoder_commit_frame(instance);
|
||||
ford_v3_decoder_reset_state(instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool ford_v3_decoder_manchester_feed_pulse(
|
||||
SubGhzProtocolDecoderFordV3* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(ford_v3_duration_is_short(duration)) {
|
||||
ManchesterEvent ev =
|
||||
level ? ManchesterEventShortHigh : ManchesterEventShortLow;
|
||||
ford_v3_decoder_manchester_feed_event(instance, ev);
|
||||
return true;
|
||||
}
|
||||
if(ford_v3_duration_is_long(duration)) {
|
||||
ManchesterEvent ev =
|
||||
level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
ford_v3_decoder_manchester_feed_event(instance, ev);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static void ford_v3_decoder_enter_sync_from_preamble(
|
||||
SubGhzProtocolDecoderFordV3* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
instance->decoder.parser_step = FordV3DecoderStepSync;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->byte_count = 0;
|
||||
instance->sync_shift = 0;
|
||||
instance->sync_bit_count = 0;
|
||||
memset(instance->raw_bytes, 0, sizeof(instance->raw_bytes));
|
||||
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset,
|
||||
&instance->manchester_state, NULL);
|
||||
|
||||
if(ford_v3_duration_is_short(duration)) {
|
||||
ManchesterEvent ev =
|
||||
level ? ManchesterEventShortHigh : ManchesterEventShortLow;
|
||||
ford_v3_decoder_manchester_feed_event(instance, ev);
|
||||
} else if(ford_v3_duration_is_long(duration)) {
|
||||
ManchesterEvent ev =
|
||||
level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
ford_v3_decoder_manchester_feed_event(instance, ev);
|
||||
} else {
|
||||
ford_v3_decoder_reset_state(instance);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ford_v3_encoder_add_level(
|
||||
SubGhzProtocolEncoderFordV3* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
size_t idx = instance->encoder.size_upload;
|
||||
if(idx > 0 &&
|
||||
level_duration_get_level(instance->encoder.upload[idx - 1]) == level) {
|
||||
uint32_t prev =
|
||||
level_duration_get_duration(instance->encoder.upload[idx - 1]);
|
||||
instance->encoder.upload[idx - 1] =
|
||||
level_duration_make(level, prev + duration);
|
||||
} else {
|
||||
furi_check(idx < FORD_V3_ENC_ALLOC_ELEMS);
|
||||
instance->encoder.upload[idx] = level_duration_make(level, duration);
|
||||
instance->encoder.size_upload++;
|
||||
}
|
||||
}
|
||||
|
||||
static inline void ford_v3_encoder_emit_manchester_bit(
|
||||
SubGhzProtocolEncoderFordV3* instance,
|
||||
bool bit) {
|
||||
if(bit) {
|
||||
ford_v3_encoder_add_level(instance, true, FORD_V3_ENC_TE_SHORT);
|
||||
ford_v3_encoder_add_level(instance, false, FORD_V3_ENC_TE_SHORT);
|
||||
} else {
|
||||
ford_v3_encoder_add_level(instance, false, FORD_V3_ENC_TE_SHORT);
|
||||
ford_v3_encoder_add_level(instance, true, FORD_V3_ENC_TE_SHORT);
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v3_encoder_emit_burst(
|
||||
SubGhzProtocolEncoderFordV3* instance,
|
||||
const uint8_t* raw) {
|
||||
for(uint8_t i = 0; i < FORD_V3_ENC_PREAMBLE_PAIRS; i++) {
|
||||
ford_v3_encoder_add_level(instance, false, FORD_V3_ENC_TE_SHORT);
|
||||
ford_v3_encoder_add_level(instance, true, FORD_V3_ENC_TE_SHORT);
|
||||
}
|
||||
|
||||
ford_v3_encoder_add_level(instance, false, FORD_V3_ENC_SYNC_LO_US);
|
||||
ford_v3_encoder_add_level(instance, true, FORD_V3_ENC_TE_SHORT);
|
||||
|
||||
for(uint16_t bit_pos = 1U; bit_pos < FORD_V3_DATA_BITS; bit_pos++) {
|
||||
const uint8_t byte_idx = (uint8_t)(bit_pos / 8U);
|
||||
const uint8_t bit_idx = (uint8_t)(7U - (bit_pos % 8U));
|
||||
ford_v3_encoder_emit_manchester_bit(
|
||||
instance,
|
||||
((raw[byte_idx] >> bit_idx) & 1U) != 0U);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Ford V3 transmits three frequency variants (freq-id 0x00, 0x08, 0x10)
|
||||
* per burst cycle, each followed by inter-burst gap, cycling
|
||||
* FORD_V3_ENC_BURST_COUNT times total.
|
||||
*
|
||||
* For simplicity on Flipper (single-frequency TX) we encode only the
|
||||
* freq-id 0x00 variant repeated BURST_COUNT times, matching the V2 pattern.
|
||||
* Can replay the other variants by editing the .sub file.
|
||||
*/
|
||||
static void ford_v3_encoder_build_upload(SubGhzProtocolEncoderFordV3* instance) {
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
for(uint8_t burst = 0; burst < FORD_V3_ENC_BURST_COUNT; burst++) {
|
||||
ford_v3_encoder_emit_burst(instance, instance->raw_bytes);
|
||||
|
||||
if(burst + 1U < FORD_V3_ENC_BURST_COUNT) {
|
||||
ford_v3_encoder_add_level(
|
||||
instance, true, FORD_V3_ENC_INTER_BURST_GAP_US);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void ford_v3_encoder_rebuild_raw_from_payload(
|
||||
SubGhzProtocolEncoderFordV3* instance) {
|
||||
instance->raw_bytes[0] = FORD_V3_SYNC_0;
|
||||
instance->raw_bytes[1] = FORD_V3_SYNC_1;
|
||||
instance->raw_bytes[2] = 0x00U;
|
||||
/* freq-id – use default (0x00 = 433.6 MHz channel) */
|
||||
instance->raw_bytes[3] = 0x00U;
|
||||
|
||||
instance->raw_bytes[4] = (uint8_t)((instance->generic.serial >> 16) & 0xFFU);
|
||||
instance->raw_bytes[5] = (uint8_t)((instance->generic.serial >> 8) & 0xFFU);
|
||||
instance->raw_bytes[6] = (uint8_t)( instance->generic.serial & 0xFFU);
|
||||
|
||||
uint8_t crypt[FORD_V3_CRYPT_LEN] = {0};
|
||||
crypt[4] = instance->generic.btn;
|
||||
crypt[5] = (uint8_t)((instance->generic.cnt >> 8) & 0xFFU);
|
||||
crypt[6] = (uint8_t)( instance->generic.cnt & 0xFFU);
|
||||
|
||||
for(uint8_t i = 0; i < FORD_V3_CRYPT_LEN; i++) {
|
||||
instance->raw_bytes[FORD_V3_CRYPT_OFFSET + i] = crypt[i];
|
||||
}
|
||||
|
||||
ford_v3_encrypt(instance->raw_bytes);
|
||||
ford_v3_crc_process(instance->raw_bytes);
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_ford_v3_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderFordV3* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolEncoderFordV3));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &ford_protocol_v3;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = FORD_V3_ENCODER_DEFAULT_REPEAT;
|
||||
instance->encoder.upload =
|
||||
calloc(FORD_V3_ENC_ALLOC_ELEMS, sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ford_v3_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV3* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_ford_v3_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV3* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = FORD_V3_ENCODER_DEFAULT_REPEAT;
|
||||
instance->generic.data_count_bit = FORD_V3_DATA_BITS;
|
||||
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
furi_check(temp_str);
|
||||
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
do {
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) break;
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name)) break;
|
||||
SubGhzProtocolStatus g = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, FORD_V3_DATA_BITS);
|
||||
if(g != SubGhzProtocolStatusOk) {
|
||||
ret = g;
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t raw_tmp[FORD_V3_DATA_BYTES] = {0};
|
||||
if(flipper_format_read_hex(
|
||||
flipper_format, "RawBytes", raw_tmp, sizeof(raw_tmp))) {
|
||||
memcpy(instance->raw_bytes, raw_tmp, sizeof(raw_tmp));
|
||||
} else {
|
||||
ford_v3_encoder_rebuild_raw_from_payload(instance);
|
||||
}
|
||||
|
||||
if(!ford_v3_button_is_valid(instance->raw_bytes[FORD_V3_CRYPT_OFFSET + 4])) {
|
||||
if(!ford_v3_button_is_valid(instance->generic.btn)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t repeat = FORD_V3_ENCODER_DEFAULT_REPEAT;
|
||||
if(flipper_format_read_uint32(flipper_format, "Repeat", &repeat, 1)) {
|
||||
instance->encoder.repeat = repeat;
|
||||
}
|
||||
|
||||
ford_v3_encoder_build_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
furi_string_free(temp_str);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ford_v3_stop(void* context) {
|
||||
furi_check(context);
|
||||
((SubGhzProtocolEncoderFordV3*)context)->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ford_v3_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderFordV3* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0U) {
|
||||
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.front = 0U;
|
||||
instance->encoder.repeat--;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_ford_v3_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderFordV3* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolDecoderFordV3));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &ford_protocol_v3;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_free(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_reset(void* context) {
|
||||
furi_check(context);
|
||||
ford_v3_decoder_reset_state((SubGhzProtocolDecoderFordV3*)context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_feed(
|
||||
void* context,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case FordV3DecoderStepReset:
|
||||
if(ford_v3_duration_is_short(duration)) {
|
||||
instance->preamble_count = 1U;
|
||||
instance->decoder.parser_step = FordV3DecoderStepPreamble;
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV3DecoderStepPreamble:
|
||||
if(ford_v3_duration_is_short(duration)) {
|
||||
if(instance->preamble_count < FORD_V3_PREAMBLE_COUNT_MAX) {
|
||||
instance->preamble_count++;
|
||||
}
|
||||
} else if(!level && ford_v3_duration_is_long(duration)) {
|
||||
if(instance->preamble_count >= FORD_V3_PREAMBLE_MIN) {
|
||||
ford_v3_decoder_enter_sync_from_preamble(instance, level, duration);
|
||||
} else {
|
||||
ford_v3_decoder_reset_state(instance);
|
||||
}
|
||||
} else {
|
||||
ford_v3_decoder_reset_state(instance);
|
||||
}
|
||||
break;
|
||||
|
||||
case FordV3DecoderStepSync:
|
||||
case FordV3DecoderStepData:
|
||||
if(!ford_v3_decoder_manchester_feed_pulse(instance, level, duration)) {
|
||||
if(duration >= FORD_V3_INTER_BURST_GAP_US) {
|
||||
ford_v3_decoder_reset_state(instance);
|
||||
} else {
|
||||
ford_v3_decoder_reset_state(instance);
|
||||
}
|
||||
}
|
||||
instance->decoder.te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_ford_v3_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
uint32_t mix =
|
||||
((uint32_t)k[4] << 16) | ((uint32_t)k[5] << 8) | (uint32_t)k[6];
|
||||
mix ^= (uint32_t)instance->btn << 16;
|
||||
mix ^= (uint32_t)instance->counter16 << 8;
|
||||
mix ^= ((uint16_t)k[15] << 8) | k[16];
|
||||
|
||||
return (uint8_t)(
|
||||
(mix >> 0) ^ (mix >> 8) ^ (mix >> 16) ^ (mix >> 24) ^
|
||||
(uint8_t)(instance->counter16 >> 8) ^
|
||||
(uint8_t)(instance->crc_received >> 8));
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t serial = instance->generic.serial;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "Serial", &serial, 1);
|
||||
|
||||
uint32_t btn = instance->generic.btn;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "Btn", &btn, 1);
|
||||
|
||||
uint32_t cnt = instance->generic.cnt;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "Cnt", &cnt, 1);
|
||||
|
||||
uint32_t crc = instance->crc_received;
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(
|
||||
flipper_format, "CRC", &crc, 1);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_hex(
|
||||
flipper_format, "RawBytes",
|
||||
instance->raw_bytes, FORD_V3_DATA_BYTES);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_ford_v3_const.min_count_bit_for_found);
|
||||
|
||||
if(ret != SubGhzProtocolStatusOk) return ret;
|
||||
|
||||
if(instance->generic.data_count_bit != FORD_V3_DATA_BITS) {
|
||||
return SubGhzProtocolStatusErrorValueBitCount;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t raw_tmp[FORD_V3_DATA_BYTES] = {0};
|
||||
if(flipper_format_read_hex(
|
||||
flipper_format, "RawBytes", raw_tmp, sizeof(raw_tmp))) {
|
||||
memcpy(instance->raw_bytes, raw_tmp, sizeof(raw_tmp));
|
||||
} else {
|
||||
instance->raw_bytes[0] = FORD_V3_SYNC_0;
|
||||
instance->raw_bytes[1] = FORD_V3_SYNC_1;
|
||||
}
|
||||
|
||||
ford_v3_decoder_extract_from_raw(instance);
|
||||
|
||||
if(!instance->structure_ok) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_get_string(
|
||||
void* context,
|
||||
FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderFordV3* instance = context;
|
||||
const uint8_t* k = instance->raw_bytes;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Raw:%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X%02X\r\n"
|
||||
"Sn:%06lX Btn:%02X [%s]\r\n"
|
||||
"Cnt:%u CRC:%s(%04X)\r\n"
|
||||
"Struct:%s\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->generic.data_count_bit,
|
||||
k[0], k[1], k[2], k[3], k[4], k[5], k[6], k[7],
|
||||
k[8], k[9], k[10], k[11], k[12], k[13], k[14], k[15], k[16],
|
||||
(unsigned long)instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
ford_v3_button_name(instance->generic.btn),
|
||||
(unsigned)instance->counter16,
|
||||
instance->crc_ok ? "OK" : "BAD",
|
||||
(unsigned)instance->crc_received,
|
||||
instance->structure_ok ? "OK" : "BAD");
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_ford_v3_decoder = {
|
||||
.alloc = subghz_protocol_decoder_ford_v3_alloc,
|
||||
.free = subghz_protocol_decoder_ford_v3_free,
|
||||
.feed = subghz_protocol_decoder_ford_v3_feed,
|
||||
.reset = subghz_protocol_decoder_ford_v3_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_ford_v3_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_ford_v3_serialize,
|
||||
.deserialize = subghz_protocol_decoder_ford_v3_deserialize,
|
||||
.get_string = subghz_protocol_decoder_ford_v3_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_ford_v3_encoder = {
|
||||
.alloc = subghz_protocol_encoder_ford_v3_alloc,
|
||||
.free = subghz_protocol_encoder_ford_v3_free,
|
||||
.deserialize = subghz_protocol_encoder_ford_v3_deserialize,
|
||||
.stop = subghz_protocol_encoder_ford_v3_stop,
|
||||
.yield = subghz_protocol_encoder_ford_v3_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol ford_protocol_v3 = {
|
||||
.name = FORD_PROTOCOL_V3_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_ford_v3_decoder,
|
||||
.encoder = &subghz_protocol_ford_v3_encoder,
|
||||
};
|
||||
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.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 <lib/toolbox/manchester_encoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <furi.h>
|
||||
|
||||
|
||||
#define FORD_PROTOCOL_V3_NAME "Ford V3"
|
||||
|
||||
extern const SubGhzProtocol ford_protocol_v3;
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_ford_v3_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_ford_v3_encoder;
|
||||
|
||||
void* subghz_protocol_decoder_ford_v3_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_ford_v3_free(void* context);
|
||||
void subghz_protocol_decoder_ford_v3_reset(void* context);
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_feed(
|
||||
void* context,
|
||||
bool level,
|
||||
uint32_t duration);
|
||||
|
||||
uint8_t subghz_protocol_decoder_ford_v3_get_hash_data(void* context);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v3_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_decoder_ford_v3_get_string(
|
||||
void* context,
|
||||
FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_ford_v3_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_ford_v3_free(void* context);
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_ford_v3_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
void subghz_protocol_encoder_ford_v3_stop(void* context);
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ford_v3_yield(void* context);
|
||||
|
||||
@@ -0,0 +1,796 @@
|
||||
#include "honda_static.h"
|
||||
|
||||
#define HONDA_STATIC_BIT_COUNT 64
|
||||
#define HONDA_STATIC_MIN_SYMBOLS 36
|
||||
#define HONDA_STATIC_SHORT_BASE_US 28
|
||||
#define HONDA_STATIC_SHORT_SPAN_US 70
|
||||
#define HONDA_STATIC_LONG_BASE_US 61
|
||||
#define HONDA_STATIC_LONG_SPAN_US 130
|
||||
#define HONDA_STATIC_SYNC_TIME_US 700
|
||||
#define HONDA_STATIC_ELEMENT_TIME_US 63
|
||||
#define HONDA_STATIC_UPLOAD_CAPACITY 512
|
||||
#define HONDA_STATIC_SYMBOL_CAPACITY 512
|
||||
#define HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT 160
|
||||
#define HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS 19
|
||||
|
||||
static const uint8_t honda_static_encoder_button_map[4] = {0x02, 0x04, 0x08, 0x05};
|
||||
static const char* const honda_static_button_names[9] = {
|
||||
"LOCK",
|
||||
"UNLOCK",
|
||||
"UNKNOWN",
|
||||
"TRUNK",
|
||||
"REMOTE START",
|
||||
"UNKNOWN",
|
||||
"UNKNOWN",
|
||||
"PANIC",
|
||||
"LOCK x2",
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
uint8_t button;
|
||||
uint8_t _reserved_01[3];
|
||||
uint32_t serial;
|
||||
uint32_t counter;
|
||||
uint8_t checksum;
|
||||
uint8_t _reserved_0d[3];
|
||||
} HondaStaticFields;
|
||||
|
||||
struct SubGhzProtocolDecoderHondaStatic {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
uint32_t _reserved_0c;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
uint32_t _reserved_20;
|
||||
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint16_t packet_bit_count;
|
||||
uint8_t _reserved_5a;
|
||||
uint8_t _reserved_5b;
|
||||
|
||||
uint8_t symbols[HONDA_STATIC_SYMBOL_CAPACITY];
|
||||
uint16_t symbols_count;
|
||||
HondaStaticFields decoded;
|
||||
uint8_t decoded_valid;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderHondaStatic {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
HondaStaticFields decoded;
|
||||
uint8_t tx_button;
|
||||
uint8_t _reserved_69[3];
|
||||
};
|
||||
|
||||
static uint64_t honda_static_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
uint64_t value = 0;
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
value = (value << 8U) | bytes[i];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static void honda_static_u64_to_bytes_be(uint64_t value, uint8_t bytes[8]) {
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
bytes[7U - i] = (uint8_t)(value & 0xFFU);
|
||||
value >>= 8U;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t honda_static_get_bits(const uint8_t* data, uint8_t start, uint8_t count) {
|
||||
uint32_t value = 0;
|
||||
|
||||
for(uint8_t i = 0; i < count; i++) {
|
||||
const uint8_t bit_index = start + i;
|
||||
const uint8_t byte = data[bit_index >> 3U];
|
||||
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
|
||||
value = (value << 1U) | ((byte >> shift) & 1U);
|
||||
}
|
||||
|
||||
return (uint8_t)value;
|
||||
}
|
||||
|
||||
static uint32_t honda_static_get_bits_u32(const uint8_t* data, uint8_t start, uint8_t count) {
|
||||
uint32_t value = 0;
|
||||
|
||||
for(uint8_t i = 0; i < count; i++) {
|
||||
const uint8_t bit_index = start + i;
|
||||
const uint8_t byte = data[bit_index >> 3U];
|
||||
const uint8_t shift = (uint8_t)(~bit_index) & 0x07U;
|
||||
value = (value << 1U) | ((byte >> shift) & 1U);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
static void honda_static_set_bits(uint8_t* data, uint8_t start, uint8_t count, uint32_t value) {
|
||||
for(uint8_t i = 0; i < count; i++) {
|
||||
const uint8_t bit_index = start + i;
|
||||
const uint8_t byte_index = bit_index >> 3U;
|
||||
const uint8_t shift = ((uint8_t)~bit_index) & 0x07U;
|
||||
const uint8_t mask = (uint8_t)(1U << shift);
|
||||
const bool bit = ((value >> (count - 1U - i)) & 1U) != 0U;
|
||||
|
||||
if(bit) {
|
||||
data[byte_index] |= mask;
|
||||
} else {
|
||||
data[byte_index] &= (uint8_t)~mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t honda_static_level_u8(bool level) {
|
||||
return level ? 1U : 0U;
|
||||
}
|
||||
|
||||
static uint8_t honda_static_sym_u8(uint8_t stored) {
|
||||
return stored ? 1U : 0U;
|
||||
}
|
||||
|
||||
static uint8_t honda_static_reverse_bits8(uint8_t value) {
|
||||
value = (uint8_t)(((value >> 4U) | (value << 4U)) & 0xFFU);
|
||||
value = (uint8_t)(((value & 0x33U) << 2U) | ((value >> 2U) & 0x33U));
|
||||
value = (uint8_t)(((value & 0x55U) << 1U) | ((value >> 1U) & 0x55U));
|
||||
return value;
|
||||
}
|
||||
|
||||
static bool honda_static_is_valid_button(uint8_t button) {
|
||||
if(button > 9U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ((0x336U >> button) & 1U) != 0U;
|
||||
}
|
||||
|
||||
static bool honda_static_is_valid_serial(uint32_t serial) {
|
||||
return (serial != 0U) && (serial != 0x0FFFFFFFU);
|
||||
}
|
||||
|
||||
static uint8_t honda_static_encoder_remap_button(uint8_t button) {
|
||||
if(button < 2U) {
|
||||
return 1U;
|
||||
}
|
||||
button -= 2U;
|
||||
if(button <= 3U) {
|
||||
return honda_static_encoder_button_map[button];
|
||||
}
|
||||
|
||||
return 1U;
|
||||
}
|
||||
|
||||
static const char* honda_static_button_name(uint8_t button) {
|
||||
if((button >= 1U) && (button <= COUNT_OF(honda_static_button_names))) {
|
||||
return honda_static_button_names[button - 1U];
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static uint8_t honda_static_compact_bytes_checksum(const uint8_t compact[8]) {
|
||||
const uint8_t canonical[7] = {
|
||||
(uint8_t)((compact[0] << 4U) | (compact[1] >> 4U)),
|
||||
(uint8_t)((compact[1] << 4U) | (compact[2] >> 4U)),
|
||||
(uint8_t)((compact[2] << 4U) | (compact[3] >> 4U)),
|
||||
(uint8_t)((compact[3] << 4U) | (compact[4] >> 4U)),
|
||||
compact[5],
|
||||
compact[6],
|
||||
compact[7],
|
||||
};
|
||||
|
||||
uint8_t checksum = 0U;
|
||||
for(size_t i = 0; i < COUNT_OF(canonical); i++) {
|
||||
checksum ^= canonical[i];
|
||||
}
|
||||
|
||||
return checksum;
|
||||
}
|
||||
|
||||
static void honda_static_unpack_compact(uint64_t key, HondaStaticFields* fields) {
|
||||
uint8_t compact[8];
|
||||
honda_static_u64_to_bytes_be(key, compact);
|
||||
|
||||
memset(fields, 0, sizeof(*fields));
|
||||
fields->button = compact[0] & 0x0FU;
|
||||
fields->serial = ((uint32_t)compact[1] << 20U) | ((uint32_t)compact[2] << 12U) |
|
||||
((uint32_t)compact[3] << 4U) | ((uint32_t)compact[4] >> 4U);
|
||||
fields->counter = ((uint32_t)compact[5] << 16U) | ((uint32_t)compact[6] << 8U) |
|
||||
(uint32_t)compact[7];
|
||||
fields->checksum = honda_static_compact_bytes_checksum(compact);
|
||||
}
|
||||
|
||||
static uint64_t honda_static_pack_compact(const HondaStaticFields* fields) {
|
||||
uint8_t compact[8];
|
||||
|
||||
compact[0] = fields->button & 0x0FU;
|
||||
compact[1] = (uint8_t)(fields->serial >> 20U);
|
||||
compact[2] = (uint8_t)(fields->serial >> 12U);
|
||||
compact[3] = (uint8_t)(fields->serial >> 4U);
|
||||
compact[4] = (uint8_t)(fields->serial << 4U);
|
||||
compact[5] = (uint8_t)(fields->counter >> 16U);
|
||||
compact[6] = (uint8_t)(fields->counter >> 8U);
|
||||
compact[7] = (uint8_t)fields->counter;
|
||||
|
||||
return honda_static_bytes_to_u64_be(compact);
|
||||
}
|
||||
|
||||
static void honda_static_build_packet_bytes(const HondaStaticFields* fields, uint8_t packet[8]) {
|
||||
memset(packet, 0, 8);
|
||||
|
||||
honda_static_set_bits(packet, 0, 4, fields->button & 0x0FU);
|
||||
honda_static_set_bits(packet, 4, 28, fields->serial);
|
||||
honda_static_set_bits(packet, 32, 24, fields->counter);
|
||||
|
||||
uint8_t checksum = 0U;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
checksum ^= packet[i];
|
||||
}
|
||||
|
||||
honda_static_set_bits(packet, 56, 8, checksum);
|
||||
}
|
||||
|
||||
static bool
|
||||
honda_static_validate_forward_packet(const uint8_t packet[9], HondaStaticFields* fields) {
|
||||
const uint8_t button = honda_static_get_bits(packet, 0, 4);
|
||||
const uint32_t serial = honda_static_get_bits_u32(packet, 4, 28);
|
||||
const uint32_t counter = honda_static_get_bits_u32(packet, 32, 24);
|
||||
const uint8_t checksum = honda_static_get_bits(packet, 56, 8);
|
||||
|
||||
uint8_t checksum_calc = 0U;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
checksum_calc ^= packet[i];
|
||||
}
|
||||
|
||||
if(checksum != checksum_calc) {
|
||||
return false;
|
||||
}
|
||||
if(!honda_static_is_valid_button(button)) {
|
||||
return false;
|
||||
}
|
||||
if(!honda_static_is_valid_serial(serial)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fields->button = button;
|
||||
fields->serial = serial;
|
||||
fields->counter = counter;
|
||||
fields->checksum = checksum;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
honda_static_validate_reverse_packet(const uint8_t packet[9], HondaStaticFields* fields) {
|
||||
uint8_t reversed[9];
|
||||
for(size_t i = 0; i < COUNT_OF(reversed); i++) {
|
||||
reversed[i] = honda_static_reverse_bits8(packet[i]);
|
||||
}
|
||||
|
||||
const uint8_t button = honda_static_get_bits(reversed, 0, 4);
|
||||
const uint32_t serial = honda_static_get_bits_u32(reversed, 4, 28);
|
||||
const uint32_t counter = honda_static_get_bits_u32(reversed, 32, 24);
|
||||
|
||||
uint8_t checksum = 0U;
|
||||
for(size_t i = 0; i < 7; i++) {
|
||||
checksum ^= reversed[i];
|
||||
}
|
||||
|
||||
if(!honda_static_is_valid_button(button)) {
|
||||
return false;
|
||||
}
|
||||
if(!honda_static_is_valid_serial(serial)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
fields->button = button;
|
||||
fields->serial = serial;
|
||||
fields->counter = counter;
|
||||
fields->checksum = checksum;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool honda_static_manchester_pack_64(
|
||||
const uint8_t* symbols,
|
||||
uint16_t count,
|
||||
uint16_t start_pos,
|
||||
bool inverted,
|
||||
uint8_t packet[9],
|
||||
uint16_t* out_bit_count) {
|
||||
memset(packet, 0, 9);
|
||||
|
||||
uint16_t pos = start_pos;
|
||||
uint16_t bit_count = 0U;
|
||||
|
||||
while((uint16_t)(pos + 1U) < count) {
|
||||
if(bit_count >= HONDA_STATIC_BIT_COUNT) {
|
||||
break;
|
||||
}
|
||||
|
||||
const uint8_t a = honda_static_sym_u8(symbols[pos]);
|
||||
const uint8_t b = honda_static_sym_u8(symbols[pos + 1U]);
|
||||
|
||||
if(a == b) {
|
||||
pos++;
|
||||
continue;
|
||||
}
|
||||
|
||||
bool bit = false;
|
||||
if(inverted) {
|
||||
bit = (a == 0U) && (b == 1U);
|
||||
} else {
|
||||
bit = (a == 1U) && (b == 0U);
|
||||
}
|
||||
|
||||
if(bit) {
|
||||
packet[bit_count >> 3U] |= (uint8_t)(1U << (((uint8_t)~bit_count) & 0x07U));
|
||||
}
|
||||
|
||||
bit_count++;
|
||||
pos += 2U;
|
||||
}
|
||||
|
||||
if(out_bit_count) {
|
||||
*out_bit_count = bit_count;
|
||||
}
|
||||
|
||||
return bit_count >= HONDA_STATIC_BIT_COUNT;
|
||||
}
|
||||
|
||||
static bool honda_static_parse_symbols(SubGhzProtocolDecoderHondaStatic* instance, bool inverted) {
|
||||
const uint16_t count = instance->symbols_count;
|
||||
const uint8_t* symbols = instance->symbols;
|
||||
|
||||
uint16_t index = 1U;
|
||||
uint16_t transitions = 0U;
|
||||
|
||||
while(index < count) {
|
||||
if(honda_static_sym_u8(symbols[index]) != honda_static_sym_u8(symbols[index - 1U])) {
|
||||
transitions++;
|
||||
} else {
|
||||
if(transitions > HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS) {
|
||||
break;
|
||||
}
|
||||
transitions = 0U;
|
||||
}
|
||||
index++;
|
||||
}
|
||||
|
||||
if(index >= count) {
|
||||
return false;
|
||||
}
|
||||
|
||||
while(((uint16_t)(index + 1U) < count) &&
|
||||
(honda_static_sym_u8(symbols[index]) == honda_static_sym_u8(symbols[index + 1U]))) {
|
||||
index++;
|
||||
}
|
||||
|
||||
const uint16_t data_start = index;
|
||||
|
||||
uint8_t packet[9] = {0};
|
||||
uint16_t bit_count = 0U;
|
||||
|
||||
if(!honda_static_manchester_pack_64(symbols, count, data_start, inverted, packet, &bit_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(honda_static_validate_forward_packet(packet, &instance->decoded)) {
|
||||
instance->decoded_valid = 1U;
|
||||
return true;
|
||||
}
|
||||
|
||||
if(inverted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(honda_static_validate_reverse_packet(packet, &instance->decoded)) {
|
||||
instance->decoded_valid = 1U;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void honda_static_decoder_commit(SubGhzProtocolDecoderHondaStatic* instance) {
|
||||
instance->packet_bit_count = HONDA_STATIC_BIT_COUNT;
|
||||
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||
instance->generic.data = honda_static_pack_compact(&instance->decoded);
|
||||
instance->generic.serial = instance->decoded.serial;
|
||||
instance->generic.cnt = instance->decoded.counter;
|
||||
instance->generic.btn = instance->decoded.button;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
static void honda_static_build_upload(SubGhzProtocolEncoderHondaStatic* instance) {
|
||||
uint8_t packet[8];
|
||||
honda_static_build_packet_bytes(&instance->decoded, packet);
|
||||
|
||||
size_t index = 0U;
|
||||
instance->encoder.upload[index++] = level_duration_make(true, HONDA_STATIC_SYNC_TIME_US);
|
||||
|
||||
for(size_t i = 0; i < HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT; i++) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make((i & 1U) != 0U, HONDA_STATIC_ELEMENT_TIME_US);
|
||||
}
|
||||
|
||||
for(uint8_t bit = 0U; bit < HONDA_STATIC_BIT_COUNT; bit++) {
|
||||
const bool value = ((packet[bit >> 3U] >> (((uint8_t)~bit) & 0x07U)) & 1U) != 0U;
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(!value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(value, HONDA_STATIC_ELEMENT_TIME_US);
|
||||
}
|
||||
|
||||
const bool last_bit = (packet[7] & 1U) != 0U;
|
||||
instance->encoder.upload[index++] = level_duration_make(!last_bit, HONDA_STATIC_SYNC_TIME_US);
|
||||
|
||||
instance->encoder.front = 0U;
|
||||
instance->encoder.size_upload = index;
|
||||
}
|
||||
|
||||
static bool honda_static_read_hex_u64(FlipperFormat* ff, uint64_t* out_key) {
|
||||
FuriString* tmp = furi_string_alloc();
|
||||
if(!tmp) return false;
|
||||
bool ok = false;
|
||||
do {
|
||||
if(!flipper_format_rewind(ff) || !flipper_format_read_string(ff, "Key", tmp)) break;
|
||||
|
||||
const char* key_str = furi_string_get_cstr(tmp);
|
||||
uint64_t key = 0;
|
||||
size_t hex_pos = 0;
|
||||
for(size_t i = 0; key_str[i] && 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
|
||||
break;
|
||||
key = (key << 4) | nibble;
|
||||
hex_pos++;
|
||||
}
|
||||
if(hex_pos != 16) break;
|
||||
*out_key = key;
|
||||
ok = true;
|
||||
} while(false);
|
||||
furi_string_free(tmp);
|
||||
return ok;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_honda_static_decoder = {
|
||||
.alloc = subghz_protocol_decoder_honda_static_alloc,
|
||||
.free = subghz_protocol_decoder_honda_static_free,
|
||||
.feed = subghz_protocol_decoder_honda_static_feed,
|
||||
.reset = subghz_protocol_decoder_honda_static_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_honda_static_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_honda_static_serialize,
|
||||
.deserialize = subghz_protocol_decoder_honda_static_deserialize,
|
||||
.get_string = subghz_protocol_decoder_honda_static_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_honda_static_encoder = {
|
||||
.alloc = subghz_protocol_encoder_honda_static_alloc,
|
||||
.free = subghz_protocol_encoder_honda_static_free,
|
||||
.deserialize = subghz_protocol_encoder_honda_static_deserialize,
|
||||
.stop = subghz_protocol_encoder_honda_static_stop,
|
||||
.yield = subghz_protocol_encoder_honda_static_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol honda_static_protocol = {
|
||||
.name = HONDA_STATIC_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 |
|
||||
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_honda_static_decoder,
|
||||
.encoder = &subghz_protocol_honda_static_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolEncoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolEncoderHondaStatic));
|
||||
furi_check(instance);
|
||||
memset(instance, 0, sizeof(*instance));
|
||||
|
||||
instance->base.protocol = &honda_static_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 3U;
|
||||
instance->encoder.upload = malloc(HONDA_STATIC_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_honda_static_free(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||
SubGhzProtocolStatus status = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0U;
|
||||
|
||||
do {
|
||||
FuriString* pstr = furi_string_alloc();
|
||||
if(!pstr) break;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", pstr)) {
|
||||
furi_string_free(pstr);
|
||||
break;
|
||||
}
|
||||
if(!furi_string_equal(pstr, instance->base.protocol->name)) {
|
||||
furi_string_free(pstr);
|
||||
break;
|
||||
}
|
||||
furi_string_free(pstr);
|
||||
|
||||
uint64_t key = 0;
|
||||
if(!honda_static_read_hex_u64(flipper_format, &key)) {
|
||||
break;
|
||||
}
|
||||
|
||||
honda_static_unpack_compact(key, &instance->decoded);
|
||||
|
||||
uint32_t serial = instance->decoded.serial;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Serial", &serial, 1)) {
|
||||
instance->decoded.serial = serial;
|
||||
}
|
||||
|
||||
uint32_t btn_u32 = 0;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1)) {
|
||||
uint8_t b = (uint8_t)btn_u32;
|
||||
if(honda_static_is_valid_button(b)) {
|
||||
instance->decoded.button = b;
|
||||
} else if(b >= 2U && b <= 5U) {
|
||||
instance->decoded.button = honda_static_encoder_remap_button(b);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t cnt = instance->decoded.counter & 0x00FFFFFFU;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1)) {
|
||||
instance->decoded.counter = cnt & 0x00FFFFFFU;
|
||||
}
|
||||
|
||||
instance->generic.serial = instance->decoded.serial;
|
||||
instance->generic.cnt = instance->decoded.counter;
|
||||
instance->generic.btn = instance->decoded.button;
|
||||
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||
instance->generic.data = honda_static_pack_compact(&instance->decoded);
|
||||
|
||||
uint8_t key_data[8];
|
||||
honda_static_u64_to_bytes_be(instance->generic.data, key_data);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
|
||||
status = SubGhzProtocolStatusErrorParserKey;
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
|
||||
instance->encoder.repeat = 3U;
|
||||
}
|
||||
|
||||
honda_static_build_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
status = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_honda_static_stop(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderHondaStatic* instance = context;
|
||||
if((instance->encoder.repeat == 0U) || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
const LevelDuration current = instance->encoder.upload[instance->encoder.front];
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0U;
|
||||
}
|
||||
|
||||
return current;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = malloc(sizeof(SubGhzProtocolDecoderHondaStatic));
|
||||
furi_check(instance);
|
||||
memset(instance, 0, sizeof(*instance));
|
||||
|
||||
instance->base.protocol = &honda_static_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_static_free(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_static_reset(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
instance->symbols_count = 0U;
|
||||
instance->decoded_valid = 0U;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
|
||||
const uint8_t sym = honda_static_level_u8(level);
|
||||
|
||||
if((duration >= HONDA_STATIC_SHORT_BASE_US) &&
|
||||
((duration - HONDA_STATIC_SHORT_BASE_US) <= HONDA_STATIC_SHORT_SPAN_US)) {
|
||||
if(instance->symbols_count < HONDA_STATIC_SYMBOL_CAPACITY) {
|
||||
instance->symbols[instance->symbols_count++] = sym;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if((duration >= HONDA_STATIC_LONG_BASE_US) &&
|
||||
((duration - HONDA_STATIC_LONG_BASE_US) <= HONDA_STATIC_LONG_SPAN_US)) {
|
||||
if((uint16_t)(instance->symbols_count + 2U) <= HONDA_STATIC_SYMBOL_CAPACITY) {
|
||||
instance->symbols[instance->symbols_count++] = sym;
|
||||
instance->symbols[instance->symbols_count++] = sym;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t sc = instance->symbols_count;
|
||||
|
||||
if(sc >= HONDA_STATIC_MIN_SYMBOLS) {
|
||||
if(honda_static_parse_symbols(instance, true) ||
|
||||
honda_static_parse_symbols(instance, false)) {
|
||||
honda_static_decoder_commit(instance);
|
||||
}
|
||||
}
|
||||
|
||||
instance->symbols_count = 0U;
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
const uint64_t data = instance->generic.data;
|
||||
|
||||
return (uint8_t)(data ^ (data >> 8U) ^ (data >> 16U) ^ (data >> 24U) ^ (data >> 32U) ^
|
||||
(data >> 40U) ^ (data >> 48U) ^ (data >> 56U));
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
if(!instance->decoded_valid && (instance->generic.data != 0ULL)) {
|
||||
honda_static_unpack_compact(instance->generic.data, &instance->decoded);
|
||||
instance->decoded_valid = 1U;
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %ubit\r\n"
|
||||
"Key:%016llX\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->packet_bit_count ? instance->packet_bit_count : HONDA_STATIC_BIT_COUNT,
|
||||
(unsigned long long)instance->generic.data);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"Btn:%s (0x%X)\r\n"
|
||||
"Ser:%07lX\r\n"
|
||||
"Cnt:%06lX Chk:%02X\r\n",
|
||||
honda_static_button_name(instance->decoded.button),
|
||||
instance->decoded.button,
|
||||
(unsigned long)instance->decoded.serial,
|
||||
(unsigned long)instance->decoded.counter,
|
||||
instance->decoded.checksum);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_write_uint32(flipper_format, "Serial", &instance->decoded.serial, 1);
|
||||
|
||||
uint32_t temp = instance->decoded.button;
|
||||
flipper_format_write_uint32(flipper_format, "Btn", &temp, 1);
|
||||
|
||||
flipper_format_write_uint32(flipper_format, "Cnt", &instance->decoded.counter, 1);
|
||||
|
||||
temp = instance->decoded.checksum;
|
||||
flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderHondaStatic* instance = context;
|
||||
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, HONDA_STATIC_BIT_COUNT);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
instance->packet_bit_count = HONDA_STATIC_BIT_COUNT;
|
||||
honda_static_unpack_compact(instance->generic.data, &instance->decoded);
|
||||
instance->decoded_valid = 1U;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t s = 0, b = 0, c = 0, k = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Serial", &s, 1)) {
|
||||
instance->decoded.serial = s;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &b, 1)) {
|
||||
instance->decoded.button = (uint8_t)b;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &c, 1)) {
|
||||
instance->decoded.counter = c & 0x00FFFFFFU;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Checksum", &k, 1)) {
|
||||
instance->decoded.checksum = (uint8_t)k;
|
||||
}
|
||||
|
||||
instance->generic.serial = instance->decoded.serial;
|
||||
instance->generic.cnt = instance->decoded.counter;
|
||||
instance->generic.btn = instance->decoded.button;
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define HONDA_STATIC_PROTOCOL_NAME "Honda Static"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderHondaStatic SubGhzProtocolDecoderHondaStatic;
|
||||
typedef struct SubGhzProtocolEncoderHondaStatic SubGhzProtocolEncoderHondaStatic;
|
||||
|
||||
extern const SubGhzProtocol honda_static_protocol;
|
||||
|
||||
void* subghz_protocol_decoder_honda_static_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_honda_static_free(void* context);
|
||||
void subghz_protocol_decoder_honda_static_reset(void* context);
|
||||
void subghz_protocol_decoder_honda_static_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_honda_static_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_static_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_honda_static_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_honda_static_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_honda_static_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_honda_static_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_honda_static_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_honda_static_yield(void* context);
|
||||
|
||||
@@ -1,391 +0,0 @@
|
||||
#include "honda_v1.h"
|
||||
#include <string.h>
|
||||
|
||||
#define HONDA_V1_BIT_COUNT 68U
|
||||
#define HONDA_V1_VALID_BIT_COUNT_MAX 75U
|
||||
#define HONDA_V1_TE_SHORT 1000U
|
||||
#define HONDA_V1_TE_LONG 2000U
|
||||
#define HONDA_V1_TE_DELTA 400U
|
||||
#define HONDA_V1_TE_SHORT_MIN 600U
|
||||
#define HONDA_V1_TE_END 3500U
|
||||
|
||||
typedef enum {
|
||||
HondaV1DecoderStepReset = 0,
|
||||
HondaV1DecoderStepPreamble = 1,
|
||||
HondaV1DecoderStepData = 2,
|
||||
} HondaV1DecoderStep;
|
||||
|
||||
typedef struct SubGhzProtocolDecoderHondaV1 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
uint32_t _reserved_0c;
|
||||
uint32_t parser_step;
|
||||
uint8_t _reserved_14[0x10];
|
||||
SubGhzBlockGeneric generic;
|
||||
uint32_t key_2;
|
||||
uint16_t packet_bit_count;
|
||||
uint8_t _reserved_5a;
|
||||
uint8_t _reserved_5b;
|
||||
uint32_t pending_duration;
|
||||
bool pending_duration_valid;
|
||||
uint8_t preamble_count;
|
||||
bool data_pending;
|
||||
bool last_level;
|
||||
uint8_t bits[0x0C];
|
||||
uint8_t bit_count;
|
||||
} SubGhzProtocolDecoderHondaV1;
|
||||
|
||||
static const char* const honda_v1_button_names[11] = {
|
||||
"Unlock",
|
||||
"Unknown",
|
||||
"Unknown",
|
||||
"Unknown",
|
||||
"Unknown",
|
||||
"Unknown",
|
||||
"Unknown",
|
||||
"Unknown",
|
||||
"Lock",
|
||||
"Trunk",
|
||||
"Panic",
|
||||
};
|
||||
|
||||
static bool honda_v1_button_valid(uint8_t button) {
|
||||
return ((0x701U >> button) & 1U) != 0U;
|
||||
}
|
||||
|
||||
static const char* honda_v1_button_name(uint8_t button) {
|
||||
if(button < COUNT_OF(honda_v1_button_names)) {
|
||||
return honda_v1_button_names[button];
|
||||
}
|
||||
|
||||
return "Unknown";
|
||||
}
|
||||
|
||||
static uint64_t honda_v1_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
uint64_t value = 0;
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
value = (value << 8U) | bytes[i];
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static void honda_v1_parse_generic_fields(SubGhzBlockGeneric* generic) {
|
||||
const uint32_t low = (uint32_t)(generic->data & 0xFFFFFFFFULL);
|
||||
const uint32_t high = (uint32_t)(generic->data >> 32U);
|
||||
|
||||
generic->cnt = high >> 4U;
|
||||
generic->btn = (uint8_t)((low >> 28U) & 0x0FU);
|
||||
generic->serial = low & 0xFFFFU;
|
||||
generic->data_count_bit = HONDA_V1_BIT_COUNT;
|
||||
}
|
||||
|
||||
static void honda_v1_crc_candidates(uint16_t serial, uint8_t* first, uint8_t* second) {
|
||||
uint8_t base = (uint8_t)((((~(serial >> 6U)) << 2U) & 0x04U) | ((serial >> 3U) & 0x01U));
|
||||
uint8_t value = (uint8_t)((((~(serial >> 4U)) & 0x01U) | (((serial >> 5U) & 0x01U) << 1U)) ^
|
||||
(serial & 0x07U));
|
||||
|
||||
*first = (uint8_t)((value + base) & 0x07U);
|
||||
*second = (uint8_t)(((value + (base ^ 0x01U)) & 0x07U) | 0x08U);
|
||||
}
|
||||
|
||||
static void honda_v1_add_bit(SubGhzProtocolDecoderHondaV1* instance, bool bit) {
|
||||
if(instance->bit_count > HONDA_V1_VALID_BIT_COUNT_MAX) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(bit) {
|
||||
const uint8_t byte_index = instance->bit_count >> 3U;
|
||||
const uint8_t shift = ((uint8_t)~instance->bit_count) & 0x07U;
|
||||
instance->bits[byte_index] |= (uint8_t)(1U << shift);
|
||||
}
|
||||
|
||||
instance->bit_count++;
|
||||
}
|
||||
|
||||
static void honda_v1_reset_state_(SubGhzProtocolDecoderHondaV1* instance) {
|
||||
instance->parser_step = HondaV1DecoderStepReset;
|
||||
instance->preamble_count = 0;
|
||||
instance->data_pending = 0;
|
||||
instance->bit_count = 0;
|
||||
memset(instance->bits, 0, sizeof(instance->bits));
|
||||
}
|
||||
|
||||
static bool honda_v1_duration_is(uint32_t duration, uint32_t target) {
|
||||
return (duration >= target) ? ((duration - target) <= HONDA_V1_TE_DELTA) :
|
||||
((target - duration) <= HONDA_V1_TE_DELTA);
|
||||
}
|
||||
|
||||
static bool honda_v1_decoder_commit(SubGhzProtocolDecoderHondaV1* instance) {
|
||||
if(instance->bit_count <= 0x43U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t shift_count = instance->bit_count - HONDA_V1_BIT_COUNT;
|
||||
if(shift_count < 1U) {
|
||||
shift_count = 1U;
|
||||
}
|
||||
|
||||
for(uint8_t shift = 0; shift < shift_count; shift++) {
|
||||
for(size_t i = 0; i < (sizeof(instance->bits) - 1U); i++) {
|
||||
instance->bits[i] = (uint8_t)((instance->bits[i] << 1U) | (instance->bits[i + 1U] >> 7U));
|
||||
}
|
||||
instance->bits[sizeof(instance->bits) - 1U] <<= 1U;
|
||||
}
|
||||
|
||||
const uint8_t button = instance->bits[4] >> 4U;
|
||||
if(!honda_v1_button_valid(button)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->generic.data = honda_v1_bytes_to_u64_be(instance->bits);
|
||||
instance->generic.data_2 = (uint8_t)(instance->bits[8] >> 4U);
|
||||
instance->packet_bit_count = HONDA_V1_BIT_COUNT;
|
||||
honda_v1_parse_generic_fields(&instance->generic);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void honda_v1_decoder_process_symbol(
|
||||
SubGhzProtocolDecoderHondaV1* instance,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
const bool short_symbol = honda_v1_duration_is(duration, HONDA_V1_TE_SHORT);
|
||||
const bool long_symbol = honda_v1_duration_is(duration, HONDA_V1_TE_LONG);
|
||||
|
||||
if(!short_symbol && !long_symbol) {
|
||||
if(!level && (duration > HONDA_V1_TE_END) &&
|
||||
(instance->parser_step == HondaV1DecoderStepData)) {
|
||||
honda_v1_decoder_commit(instance);
|
||||
}
|
||||
honda_v1_reset_state_(instance);
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->parser_step == HondaV1DecoderStepReset) {
|
||||
if(level) {
|
||||
instance->parser_step = HondaV1DecoderStepPreamble;
|
||||
instance->preamble_count = 1U;
|
||||
instance->last_level = level;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->parser_step == HondaV1DecoderStepPreamble) {
|
||||
if(long_symbol) {
|
||||
instance->preamble_count++;
|
||||
instance->last_level = level;
|
||||
return;
|
||||
}
|
||||
|
||||
if(short_symbol && (instance->preamble_count > 5U)) {
|
||||
instance->parser_step = HondaV1DecoderStepData;
|
||||
instance->bit_count = 0U;
|
||||
memset(instance->bits, 0, sizeof(instance->bits));
|
||||
instance->data_pending = true;
|
||||
instance->last_level = level;
|
||||
return;
|
||||
}
|
||||
|
||||
honda_v1_reset_state_(instance);
|
||||
return;
|
||||
}
|
||||
|
||||
if(short_symbol) {
|
||||
if(instance->data_pending) {
|
||||
honda_v1_add_bit(instance, level);
|
||||
instance->data_pending = false;
|
||||
instance->last_level = level;
|
||||
return;
|
||||
}
|
||||
|
||||
instance->data_pending = true;
|
||||
instance->last_level = level;
|
||||
} else {
|
||||
if(instance->data_pending) {
|
||||
honda_v1_add_bit(instance, level);
|
||||
} else {
|
||||
honda_v1_add_bit(instance, instance->last_level);
|
||||
}
|
||||
|
||||
instance->last_level = level;
|
||||
}
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_honda_v1_decoder = {
|
||||
.alloc = subghz_protocol_decoder_honda_v1_alloc,
|
||||
.free = subghz_protocol_decoder_honda_v1_free,
|
||||
.feed = subghz_protocol_decoder_honda_v1_feed,
|
||||
.reset = subghz_protocol_decoder_honda_v1_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_honda_v1_get_hash_data,
|
||||
.get_string = subghz_protocol_decoder_honda_v1_get_string,
|
||||
.serialize = subghz_protocol_decoder_honda_v1_serialize,
|
||||
.deserialize = subghz_protocol_decoder_honda_v1_deserialize,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_honda_v1_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_honda_v1 = {
|
||||
.name = HONDA_V1_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 |
|
||||
SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load,
|
||||
.encoder = &subghz_protocol_honda_v1_encoder,
|
||||
.decoder = &subghz_protocol_honda_v1_decoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_decoder_honda_v1_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderHondaV1* instance = calloc(1, sizeof(SubGhzProtocolDecoderHondaV1));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_honda_v1;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_v1_free(void* context) {
|
||||
furi_check(context);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_v1_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
instance->pending_duration = 0;
|
||||
instance->pending_duration_valid = false;
|
||||
|
||||
honda_v1_reset_state_(instance);
|
||||
instance->last_level = false;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_v1_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
|
||||
if(duration < HONDA_V1_TE_DELTA) {
|
||||
instance->pending_duration += duration;
|
||||
instance->pending_duration_valid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if(instance->pending_duration_valid) {
|
||||
const uint32_t pending = instance->pending_duration;
|
||||
|
||||
if(level) {
|
||||
instance->pending_duration = pending + duration;
|
||||
instance->pending_duration_valid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if(pending >= HONDA_V1_TE_SHORT_MIN) {
|
||||
honda_v1_decoder_process_symbol(instance, true, pending);
|
||||
}
|
||||
|
||||
instance->pending_duration = 0;
|
||||
instance->pending_duration_valid = false;
|
||||
}
|
||||
|
||||
if(level) {
|
||||
instance->pending_duration = duration;
|
||||
instance->pending_duration_valid = true;
|
||||
return;
|
||||
}
|
||||
|
||||
honda_v1_decoder_process_symbol(instance, false, duration);
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_honda_v1_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
return (uint8_t)(instance->generic.data >> 40U);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_honda_v1_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
honda_v1_parse_generic_fields(&instance->generic);
|
||||
|
||||
uint8_t candidate_a = 0;
|
||||
uint8_t candidate_b = 0;
|
||||
honda_v1_crc_candidates((uint16_t)instance->generic.serial, &candidate_a, &candidate_b);
|
||||
|
||||
const uint8_t key_2 = instance->generic.data_2 & 0x0FU;
|
||||
const bool crc_ok = (key_2 == candidate_a) || (key_2 == candidate_b);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Sn:%07lX Btn:%02X [%s]\r\n"
|
||||
"Cnt:%04lX CRC:%01X [%s]",
|
||||
instance->generic.protocol_name,
|
||||
instance->packet_bit_count ? instance->packet_bit_count : HONDA_V1_BIT_COUNT,
|
||||
instance->generic.data,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
honda_v1_button_name(instance->generic.btn),
|
||||
instance->generic.cnt,
|
||||
key_2,
|
||||
crc_ok ? "OK" : "ERR");
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_v1_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
honda_v1_parse_generic_fields(&instance->generic);
|
||||
|
||||
SubGhzProtocolStatus status =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
uint32_t key_2 = instance->generic.data_2 & 0x0FU;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Key_2", &key_2, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderHondaV1* instance = context;
|
||||
SubGhzProtocolStatus status = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
uint32_t key_2 = 0;
|
||||
if(!flipper_format_read_uint32(flipper_format, "Key_2", &key_2, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
instance->generic.data_2 = key_2 & 0x0FU;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->packet_bit_count = instance->generic.data_count_bit;
|
||||
honda_v1_parse_generic_fields(&instance->generic);
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
#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 <flipper_format/flipper_format.h>
|
||||
|
||||
#define HONDA_V1_PROTOCOL_NAME "Honda V1"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderHondaV1 SubGhzProtocolDecoderHondaV1;
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_honda_v1;
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_honda_v1_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_honda_v1_encoder;
|
||||
|
||||
void* subghz_protocol_decoder_honda_v1_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_honda_v1_free(void* context);
|
||||
void subghz_protocol_decoder_honda_v1_reset(void* context);
|
||||
void subghz_protocol_decoder_honda_v1_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_honda_v1_get_hash_data(void* context);
|
||||
void subghz_protocol_decoder_honda_v1_get_string(void* context, FuriString* output);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_honda_v1_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_honda_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
@@ -1,597 +0,0 @@
|
||||
#include "kia_v0.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include "../blocks/custom_btn_i.h"
|
||||
|
||||
#define TAG "SubGhzProtocolKiaV0"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_kia_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 61,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderKIA {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint16_t header_count;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderKIA {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
KIADecoderStepReset = 0,
|
||||
KIADecoderStepCheckPreambula,
|
||||
KIADecoderStepSaveDuration,
|
||||
KIADecoderStepCheckDuration,
|
||||
} KIADecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_kia_decoder = {
|
||||
.alloc = subghz_protocol_decoder_kia_alloc,
|
||||
.free = subghz_protocol_decoder_kia_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_kia_feed,
|
||||
.reset = subghz_protocol_decoder_kia_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_kia_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_kia_serialize,
|
||||
.deserialize = subghz_protocol_decoder_kia_deserialize,
|
||||
.get_string = subghz_protocol_decoder_kia_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_kia_encoder = {
|
||||
.alloc = subghz_protocol_encoder_kia_alloc,
|
||||
.free = subghz_protocol_encoder_kia_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_kia_deserialize,
|
||||
.stop = subghz_protocol_encoder_kia_stop,
|
||||
.yield = subghz_protocol_encoder_kia_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_kia_v0 = {
|
||||
.name = SUBGHZ_PROTOCOL_KIA_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_kia_decoder,
|
||||
.encoder = &subghz_protocol_kia_encoder,
|
||||
};
|
||||
|
||||
/**
|
||||
* CRC8 calculation for Kia protocol
|
||||
* Polynomial: 0x7F
|
||||
* Initial value: 0x00
|
||||
* MSB-first processing
|
||||
*/
|
||||
static uint8_t kia_crc8(uint8_t* data, size_t len) {
|
||||
uint8_t crc = 0x00;
|
||||
for(size_t i = 0; i < len; i++) {
|
||||
crc ^= data[i];
|
||||
for(size_t j = 0; j < 8; j++) {
|
||||
if((crc & 0x80) != 0)
|
||||
crc = (uint8_t)((crc << 1) ^ 0x7F);
|
||||
else
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate CRC for the Kia data packet
|
||||
* CRC is calculated over bits 8-55 (6 bytes)
|
||||
*/
|
||||
static uint8_t kia_calculate_crc(uint64_t data) {
|
||||
uint8_t crc_data[6];
|
||||
crc_data[0] = (data >> 48) & 0xFF;
|
||||
crc_data[1] = (data >> 40) & 0xFF;
|
||||
crc_data[2] = (data >> 32) & 0xFF;
|
||||
crc_data[3] = (data >> 24) & 0xFF;
|
||||
crc_data[4] = (data >> 16) & 0xFF;
|
||||
crc_data[5] = (data >> 8) & 0xFF;
|
||||
|
||||
return kia_crc8(crc_data, 6);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify CRC of received data
|
||||
*/
|
||||
static bool kia_verify_crc(uint64_t data) {
|
||||
uint8_t received_crc = data & 0xFF;
|
||||
uint8_t calculated_crc = kia_calculate_crc(data);
|
||||
return (received_crc == calculated_crc);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ENCODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
void* subghz_protocol_encoder_kia_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderKIA* instance = malloc(sizeof(SubGhzProtocolEncoderKIA));
|
||||
instance->base.protocol = &subghz_protocol_kia_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.size_upload = 848;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.repeat = 1;
|
||||
instance->encoder.is_running = false;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKIA* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_stop(void* context) {
|
||||
SubGhzProtocolEncoderKIA* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_kia_yield(void* context) {
|
||||
SubGhzProtocolEncoderKIA* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_kia_check_remote_controller(SubGhzBlockGeneric* instance);
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderKIA instance
|
||||
* @return true On success
|
||||
*/
|
||||
static bool subghz_protocol_encoder_kia_get_upload(SubGhzProtocolEncoderKIA* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
// Save original button
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->generic.btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(4);
|
||||
|
||||
size_t index = 0;
|
||||
size_t size_upload = (instance->generic.data_count_bit * 2 + 32) * 2 + 540;
|
||||
if(size_upload > instance->encoder.size_upload) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Size upload exceeds allocated encoder buffer. %i",
|
||||
instance->generic.data_count_bit);
|
||||
return false;
|
||||
} else {
|
||||
instance->encoder.size_upload = size_upload;
|
||||
}
|
||||
|
||||
// Counter increment logic
|
||||
if(instance->generic.cnt < 0xFFFF) {
|
||||
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) {
|
||||
instance->generic.cnt = 0;
|
||||
} else {
|
||||
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
|
||||
}
|
||||
} else if(instance->generic.cnt >= 0xFFFF) {
|
||||
instance->generic.cnt = 0;
|
||||
}
|
||||
|
||||
// Get button (custom or original)
|
||||
// This allows button changing with directional keys in SubGhz app
|
||||
uint8_t btn = subghz_custom_btn_get() == SUBGHZ_CUSTOM_BTN_OK ?
|
||||
subghz_custom_btn_get_original() :
|
||||
subghz_custom_btn_get();
|
||||
|
||||
// Update the generic button value for potential button changes
|
||||
instance->generic.btn = btn;
|
||||
|
||||
// Build data packet
|
||||
uint64_t data = 0;
|
||||
|
||||
// Bits 56-59: Fixed preamble (0x0F)
|
||||
data |= ((uint64_t)(0x0F) << 56);
|
||||
|
||||
// Bits 40-55: Counter (16 bits)
|
||||
data |= ((uint64_t)(instance->generic.cnt & 0xFFFF) << 40);
|
||||
|
||||
// Bits 12-39: Serial (28 bits)
|
||||
data |= ((uint64_t)(instance->generic.serial & 0x0FFFFFFF) << 12);
|
||||
|
||||
// Bits 8-11: Button (4 bits)
|
||||
data |= ((uint64_t)(btn & 0x0F) << 8);
|
||||
|
||||
// Bits 0-7: CRC
|
||||
uint8_t crc = kia_calculate_crc(data);
|
||||
data |= crc;
|
||||
|
||||
instance->generic.data = data;
|
||||
|
||||
// Send header (270 pulses of te_short)
|
||||
for(uint16_t i = 270; i > 0; i--) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_kia_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_kia_const.te_short);
|
||||
}
|
||||
|
||||
// Send 2 data bursts
|
||||
for(uint8_t h = 2; h > 0; h--) {
|
||||
// Send sync bits (15 pulses of te_short)
|
||||
for(uint8_t i = 15; i > 0; i--) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_kia_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_kia_const.te_short);
|
||||
}
|
||||
|
||||
// Send data bits (PWM encoding)
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1: long pulse
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_kia_const.te_long);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_kia_const.te_long);
|
||||
} else {
|
||||
// Send bit 0: short pulse
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_kia_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_kia_const.te_short);
|
||||
}
|
||||
}
|
||||
|
||||
// Send stop bit (3x te_long)
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_kia_const.te_long * 3);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_kia_const.te_long * 3);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_kia_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKIA* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_kia_const.min_count_bit_for_found);
|
||||
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Extract serial, button, counter from data
|
||||
subghz_protocol_kia_check_remote_controller(&instance->generic);
|
||||
|
||||
// Verify CRC
|
||||
if(!kia_verify_crc(instance->generic.data)) {
|
||||
FURI_LOG_W(TAG, "CRC mismatch in loaded file");
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!subghz_protocol_encoder_kia_get_upload(instance)) {
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
|
||||
// Update the Key in the file with the new counter/button/CRC
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> i * 8) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
FURI_LOG_E(TAG, "Unable to update Key");
|
||||
ret = SubGhzProtocolStatusErrorParserKey;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ENCODER HELPER FUNCTIONS
|
||||
// ============================================================================
|
||||
|
||||
void subghz_protocol_encoder_kia_set_button(void* context, uint8_t button) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKIA* instance = context;
|
||||
instance->generic.btn = button & 0x0F;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_set_counter(void* context, uint16_t counter) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKIA* instance = context;
|
||||
instance->generic.cnt = counter;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_increment_counter(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKIA* instance = context;
|
||||
if(instance->generic.cnt < 0xFFFF) {
|
||||
instance->generic.cnt++;
|
||||
} else {
|
||||
instance->generic.cnt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t subghz_protocol_encoder_kia_get_counter(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKIA* instance = context;
|
||||
return instance->generic.cnt;
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_encoder_kia_get_button(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKIA* instance = context;
|
||||
return instance->generic.btn;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DECODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
void* subghz_protocol_decoder_kia_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderKIA* instance = malloc(sizeof(SubGhzProtocolDecoderKIA));
|
||||
instance->base.protocol = &subghz_protocol_kia_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKIA* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKIA* instance = context;
|
||||
instance->decoder.parser_step = KIADecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKIA* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case KIADecoderStepReset:
|
||||
if((level) && (DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) <
|
||||
subghz_protocol_kia_const.te_delta)) {
|
||||
instance->decoder.parser_step = KIADecoderStepCheckPreambula;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case KIADecoderStepCheckPreambula:
|
||||
if(level) {
|
||||
if((DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) <
|
||||
subghz_protocol_kia_const.te_delta) ||
|
||||
(DURATION_DIFF(duration, subghz_protocol_kia_const.te_long) <
|
||||
subghz_protocol_kia_const.te_delta)) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
instance->decoder.parser_step = KIADecoderStepReset;
|
||||
}
|
||||
} else if(
|
||||
(DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) <
|
||||
subghz_protocol_kia_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_short) <
|
||||
subghz_protocol_kia_const.te_delta)) {
|
||||
// Found header
|
||||
instance->header_count++;
|
||||
break;
|
||||
} else if(
|
||||
(DURATION_DIFF(duration, subghz_protocol_kia_const.te_long) <
|
||||
subghz_protocol_kia_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_long) <
|
||||
subghz_protocol_kia_const.te_delta)) {
|
||||
// Found start bit
|
||||
if(instance->header_count > 15) {
|
||||
instance->decoder.parser_step = KIADecoderStepSaveDuration;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 1;
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
} else {
|
||||
instance->decoder.parser_step = KIADecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = KIADecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case KIADecoderStepSaveDuration:
|
||||
if(level) {
|
||||
if(duration >=
|
||||
(subghz_protocol_kia_const.te_long + subghz_protocol_kia_const.te_delta * 2UL)) {
|
||||
// Found stop bit
|
||||
instance->decoder.parser_step = KIADecoderStepReset;
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_kia_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
|
||||
// Verify CRC before accepting the packet
|
||||
if(kia_verify_crc(instance->generic.data)) {
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
} else {
|
||||
FURI_LOG_W(TAG, "CRC verification failed, packet rejected");
|
||||
}
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
break;
|
||||
} else {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = KIADecoderStepCheckDuration;
|
||||
}
|
||||
|
||||
} else {
|
||||
instance->decoder.parser_step = KIADecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case KIADecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_short) <
|
||||
subghz_protocol_kia_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_kia_const.te_short) <
|
||||
subghz_protocol_kia_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = KIADecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_const.te_long) <
|
||||
subghz_protocol_kia_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_kia_const.te_long) <
|
||||
subghz_protocol_kia_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = KIADecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = KIADecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = KIADecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_kia_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
/*
|
||||
* 0x0F 0112 43B04EC 1 7D
|
||||
* 0x0F 0113 43B04EC 1 DF
|
||||
* 0x0F 0114 43B04EC 1 30
|
||||
* 0x0F 0115 43B04EC 2 13
|
||||
* 0x0F 0116 43B04EC 3 F5
|
||||
* CNT Serial K CRC8 Kia
|
||||
*/
|
||||
|
||||
instance->serial = (uint32_t)((instance->data >> 12) & 0x0FFFFFFF);
|
||||
instance->btn = (instance->data >> 8) & 0x0F;
|
||||
instance->cnt = (instance->data >> 40) & 0xFFFF;
|
||||
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->btn);
|
||||
}
|
||||
|
||||
subghz_custom_btn_set_max(4);
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_kia_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKIA* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_kia_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKIA* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_kia_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKIA* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(instance->generic.data_count_bit < subghz_protocol_kia_const.min_count_bit_for_found) {
|
||||
ret = SubGhzProtocolStatusErrorParserBitCount;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const char* subghz_protocol_kia_get_name_button(uint8_t btn) {
|
||||
const char* name_btn[5] = {"Unknown", "Lock", "Unlock", "Trunk", "Horn"};
|
||||
return name_btn[btn < 5 ? btn : 0];
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKIA* instance = context;
|
||||
|
||||
subghz_protocol_kia_check_remote_controller(&instance->generic);
|
||||
uint32_t code_found_hi = instance->generic.data >> 32;
|
||||
uint32_t code_found_lo = instance->generic.data & 0x00000000ffffffff;
|
||||
|
||||
uint8_t received_crc = instance->generic.data & 0xFF;
|
||||
uint8_t calculated_crc = kia_calculate_crc(instance->generic.data);
|
||||
bool crc_valid = (received_crc == calculated_crc);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Sn:%07lX Cnt:%04lX\r\n"
|
||||
"Btn:%02X:[%s]\r\n"
|
||||
"CRC:%02X %s",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
code_found_hi,
|
||||
code_found_lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.cnt,
|
||||
instance->generic.btn,
|
||||
subghz_protocol_kia_get_name_button(instance->generic.btn),
|
||||
received_crc,
|
||||
crc_valid ? "(OK)" : "(FAIL)");
|
||||
}
|
||||
|
||||
+422
-1081
File diff suppressed because it is too large
Load Diff
@@ -1,887 +0,0 @@
|
||||
#include "kia_v3_v4.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include "../blocks/custom_btn_i.h"
|
||||
|
||||
#define TAG "SubGhzProtocolKiaV3V4"
|
||||
|
||||
#define KIA_MF_KEY 0xA8F5DFFC8DAA5CDBULL
|
||||
|
||||
#define KIA_V3_V4_PREAMBLE_PAIRS 16
|
||||
#define KIA_V3_V4_TOTAL_BURSTS 3
|
||||
#define KIA_V3_V4_INTER_BURST_GAP_US 10000
|
||||
#define KIA_V3_V4_SYNC_DURATION 1200
|
||||
|
||||
static const char* kia_version_names[] = {"KIA/HYU V4", "KIA/HYU V3"};
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_kia_v3_v4_const = {
|
||||
.te_short = 400,
|
||||
.te_long = 800,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 68,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderKiaV3V4 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
|
||||
uint8_t raw_bits[32];
|
||||
uint16_t raw_bit_count;
|
||||
bool is_v3_sync;
|
||||
|
||||
uint32_t encrypted;
|
||||
uint32_t decrypted;
|
||||
uint8_t crc;
|
||||
uint8_t version;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderKiaV3V4 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint32_t serial;
|
||||
uint8_t btn;
|
||||
uint16_t cnt;
|
||||
uint8_t version;
|
||||
uint8_t crc;
|
||||
|
||||
uint32_t encrypted;
|
||||
uint32_t decrypted;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
KiaV3V4DecoderStepReset = 0,
|
||||
KiaV3V4DecoderStepCheckPreamble,
|
||||
KiaV3V4DecoderStepCollectRawBits,
|
||||
} KiaV3V4DecoderStep;
|
||||
|
||||
static uint32_t keeloq_common_decrypt(uint32_t data, uint64_t key) {
|
||||
uint32_t block = data;
|
||||
uint64_t tkey = key;
|
||||
for(int i = 0; i < 528; i++) {
|
||||
int lutkey = ((block >> 0) & 1) | ((block >> 7) & 2) | ((block >> 17) & 4) |
|
||||
((block >> 22) & 8) | ((block >> 26) & 16);
|
||||
int lsb =
|
||||
((block >> 31) ^ ((block >> 15) & 1) ^ ((0x3A5C742E >> lutkey) & 1) ^
|
||||
((tkey >> 15) & 1));
|
||||
block = ((block & 0x7FFFFFFF) << 1) | lsb;
|
||||
tkey = ((tkey & 0x7FFFFFFFFFFFFFFFULL) << 1) | (tkey >> 63);
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
static uint32_t keeloq_common_encrypt(uint32_t data, uint64_t key) {
|
||||
uint32_t block = data;
|
||||
uint64_t tkey = key;
|
||||
|
||||
for(int i = 0; i < 528; i++) {
|
||||
int lutkey = ((block >> 1) & 1) | ((block >> 8) & 2) | ((block >> 18) & 4) |
|
||||
((block >> 23) & 8) | ((block >> 27) & 16);
|
||||
int msb =
|
||||
((block >> 0) ^ ((block >> 16) & 1) ^ ((0x3A5C742E >> lutkey) & 1) ^
|
||||
((tkey >> 0) & 1));
|
||||
block = ((block >> 1) & 0x7FFFFFFF) | (msb << 31);
|
||||
tkey = ((tkey >> 1) & 0x7FFFFFFFFFFFFFFFULL) | ((tkey & 1) << 63);
|
||||
}
|
||||
return block;
|
||||
}
|
||||
|
||||
static uint8_t reverse8(uint8_t byte) {
|
||||
byte = (byte & 0xF0) >> 4 | (byte & 0x0F) << 4;
|
||||
byte = (byte & 0xCC) >> 2 | (byte & 0x33) << 2;
|
||||
byte = (byte & 0xAA) >> 1 | (byte & 0x55) << 1;
|
||||
return byte;
|
||||
}
|
||||
|
||||
static void kia_v3_v4_add_raw_bit(SubGhzProtocolDecoderKiaV3V4* instance, bool bit) {
|
||||
if(instance->raw_bit_count < 256) {
|
||||
uint16_t byte_idx = instance->raw_bit_count / 8;
|
||||
uint8_t bit_idx = 7 - (instance->raw_bit_count % 8);
|
||||
if(bit) {
|
||||
instance->raw_bits[byte_idx] |= (1 << bit_idx);
|
||||
} else {
|
||||
instance->raw_bits[byte_idx] &= ~(1 << bit_idx);
|
||||
}
|
||||
instance->raw_bit_count++;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t kia_v3_v4_calculate_crc(uint8_t* bytes) {
|
||||
uint8_t crc = 0;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
crc ^= (bytes[i] & 0x0F) ^ (bytes[i] >> 4);
|
||||
}
|
||||
return crc & 0x0F;
|
||||
}
|
||||
|
||||
static bool kia_v3_v4_process_buffer(SubGhzProtocolDecoderKiaV3V4* instance) {
|
||||
if(instance->raw_bit_count < 68) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t* b = instance->raw_bits;
|
||||
|
||||
if(instance->is_v3_sync) {
|
||||
uint16_t num_bytes = (instance->raw_bit_count + 7) / 8;
|
||||
for(uint16_t i = 0; i < num_bytes; i++) {
|
||||
b[i] = ~b[i];
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t crc = (b[8] >> 4) & 0x0F;
|
||||
|
||||
uint32_t encrypted = ((uint32_t)reverse8(b[3]) << 24) | ((uint32_t)reverse8(b[2]) << 16) |
|
||||
((uint32_t)reverse8(b[1]) << 8) | (uint32_t)reverse8(b[0]);
|
||||
|
||||
uint32_t serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) | ((uint32_t)reverse8(b[6]) << 16) |
|
||||
((uint32_t)reverse8(b[5]) << 8) | (uint32_t)reverse8(b[4]);
|
||||
|
||||
uint8_t btn = (reverse8(b[7]) & 0xF0) >> 4;
|
||||
uint8_t our_serial_lsb = serial & 0xFF;
|
||||
|
||||
uint32_t decrypted = keeloq_common_decrypt(encrypted, KIA_MF_KEY);
|
||||
uint8_t dec_btn = (decrypted >> 28) & 0x0F;
|
||||
uint8_t dec_serial_lsb = (decrypted >> 16) & 0xFF;
|
||||
|
||||
if(dec_btn != btn || dec_serial_lsb != our_serial_lsb) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->encrypted = encrypted;
|
||||
instance->decrypted = decrypted;
|
||||
instance->crc = crc;
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.btn = btn;
|
||||
instance->generic.cnt = decrypted & 0xFFFF;
|
||||
instance->version = instance->is_v3_sync ? 1 : 0;
|
||||
|
||||
uint64_t key_data = ((uint64_t)b[0] << 56) | ((uint64_t)b[1] << 48) | ((uint64_t)b[2] << 40) |
|
||||
((uint64_t)b[3] << 32) | ((uint64_t)b[4] << 24) | ((uint64_t)b[5] << 16) |
|
||||
((uint64_t)b[6] << 8) | (uint64_t)b[7];
|
||||
instance->generic.data = key_data;
|
||||
instance->generic.data_count_bit = 68;
|
||||
|
||||
instance->decoder.decode_data = key_data;
|
||||
instance->decoder.decode_count_bit = 68;
|
||||
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->generic.btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(5);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_kia_v3_v4_decoder = {
|
||||
.alloc = subghz_protocol_decoder_kia_v3_v4_alloc,
|
||||
.free = subghz_protocol_decoder_kia_v3_v4_free,
|
||||
.feed = subghz_protocol_decoder_kia_v3_v4_feed,
|
||||
.reset = subghz_protocol_decoder_kia_v3_v4_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_kia_v3_v4_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_kia_v3_v4_serialize,
|
||||
.deserialize = subghz_protocol_decoder_kia_v3_v4_deserialize,
|
||||
.get_string = subghz_protocol_decoder_kia_v3_v4_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_kia_v3_v4_encoder = {
|
||||
.alloc = subghz_protocol_encoder_kia_v3_v4_alloc,
|
||||
.free = subghz_protocol_encoder_kia_v3_v4_free,
|
||||
.deserialize = subghz_protocol_encoder_kia_v3_v4_deserialize,
|
||||
.stop = subghz_protocol_encoder_kia_v3_v4_stop,
|
||||
.yield = subghz_protocol_encoder_kia_v3_v4_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_kia_v3_v4 = {
|
||||
.name = SUBGHZ_PROTOCOL_KIA_V3_V4_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_kia_v3_v4_decoder,
|
||||
.encoder = &subghz_protocol_kia_v3_v4_encoder,
|
||||
};
|
||||
|
||||
static const char* subghz_protocol_kia_v3_v4_get_name_button(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x1: return "Lock";
|
||||
case 0x2: return "Unlock";
|
||||
case 0x3: return "Trunk";
|
||||
case 0x4: return "Panic";
|
||||
case 0x8: return "Horn";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_kia_v3_v4_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV3V4));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_kia_v3_v4;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->serial = 0;
|
||||
instance->btn = 0;
|
||||
instance->cnt = 0;
|
||||
instance->version = 0;
|
||||
|
||||
instance->encoder.size_upload = 600;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = false;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v3_v4_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
if(instance->encoder.upload) {
|
||||
free(instance->encoder.upload);
|
||||
}
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static void subghz_protocol_encoder_kia_v3_v4_build_packet(
|
||||
SubGhzProtocolEncoderKiaV3V4* instance,
|
||||
uint8_t* raw_bytes) {
|
||||
uint32_t plaintext = (instance->cnt & 0xFFFF) | ((instance->serial & 0xFF) << 16) |
|
||||
(0x1 << 24) | ((instance->btn & 0x0F) << 28);
|
||||
|
||||
instance->decrypted = plaintext;
|
||||
|
||||
uint32_t encrypted = keeloq_common_encrypt(plaintext, KIA_MF_KEY);
|
||||
instance->encrypted = encrypted;
|
||||
|
||||
raw_bytes[0] = reverse8((encrypted >> 0) & 0xFF);
|
||||
raw_bytes[1] = reverse8((encrypted >> 8) & 0xFF);
|
||||
raw_bytes[2] = reverse8((encrypted >> 16) & 0xFF);
|
||||
raw_bytes[3] = reverse8((encrypted >> 24) & 0xFF);
|
||||
|
||||
uint32_t serial_btn = (instance->serial & 0x0FFFFFFF) |
|
||||
((uint32_t)(instance->btn & 0x0F) << 28);
|
||||
raw_bytes[4] = reverse8((serial_btn >> 0) & 0xFF);
|
||||
raw_bytes[5] = reverse8((serial_btn >> 8) & 0xFF);
|
||||
raw_bytes[6] = reverse8((serial_btn >> 16) & 0xFF);
|
||||
raw_bytes[7] = reverse8((serial_btn >> 24) & 0xFF);
|
||||
|
||||
uint8_t crc = kia_v3_v4_calculate_crc(raw_bytes);
|
||||
raw_bytes[8] = (crc << 4);
|
||||
instance->crc = crc;
|
||||
|
||||
instance->generic.data = ((uint64_t)raw_bytes[0] << 56) | ((uint64_t)raw_bytes[1] << 48) |
|
||||
((uint64_t)raw_bytes[2] << 40) | ((uint64_t)raw_bytes[3] << 32) |
|
||||
((uint64_t)raw_bytes[4] << 24) | ((uint64_t)raw_bytes[5] << 16) |
|
||||
((uint64_t)raw_bytes[6] << 8) | (uint64_t)raw_bytes[7];
|
||||
instance->generic.data_count_bit = 68;
|
||||
}
|
||||
|
||||
static void subghz_protocol_encoder_kia_v3_v4_get_upload(SubGhzProtocolEncoderKiaV3V4* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
uint8_t raw_bytes[9];
|
||||
subghz_protocol_encoder_kia_v3_v4_build_packet(instance, raw_bytes);
|
||||
|
||||
if(instance->version == 1) {
|
||||
for(int i = 0; i < 9; i++) {
|
||||
raw_bytes[i] = ~raw_bytes[i];
|
||||
}
|
||||
}
|
||||
|
||||
size_t index = 0;
|
||||
|
||||
for(uint8_t burst = 0; burst < KIA_V3_V4_TOTAL_BURSTS; burst++) {
|
||||
if(burst > 0) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, KIA_V3_V4_INTER_BURST_GAP_US);
|
||||
}
|
||||
|
||||
for(int i = 0; i < KIA_V3_V4_PREAMBLE_PAIRS; i++) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_short);
|
||||
}
|
||||
|
||||
if(instance->version == 0) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, KIA_V3_V4_SYNC_DURATION);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, KIA_V3_V4_SYNC_DURATION);
|
||||
}
|
||||
|
||||
for(int byte_idx = 0; byte_idx < 9; byte_idx++) {
|
||||
int bits_in_byte = (byte_idx == 8) ? 4 : 8;
|
||||
|
||||
for(int bit_idx = 7; bit_idx >= (8 - bits_in_byte); bit_idx--) {
|
||||
bool bit = (raw_bytes[byte_idx] >> bit_idx) & 1;
|
||||
|
||||
if(bit) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_long);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_kia_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
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_string_free(temp_str);
|
||||
break;
|
||||
}
|
||||
|
||||
const char* proto_str = furi_string_get_cstr(temp_str);
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name) &&
|
||||
strcmp(proto_str, "KIA/HYU V3") != 0 && strcmp(proto_str, "KIA/HYU V4") != 0) {
|
||||
furi_string_free(temp_str);
|
||||
break;
|
||||
}
|
||||
|
||||
bool version_from_protocol_name = false;
|
||||
|
||||
if(strcmp(proto_str, "KIA/HYU V3") == 0) {
|
||||
instance->version = 1;
|
||||
version_from_protocol_name = true;
|
||||
} else if(strcmp(proto_str, "KIA/HYU V4") == 0) {
|
||||
instance->version = 0;
|
||||
version_from_protocol_name = true;
|
||||
}
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t bit_count_temp;
|
||||
if(!flipper_format_read_uint32(flipper_format, "Bit", &bit_count_temp, 1)) {
|
||||
break;
|
||||
}
|
||||
instance->generic.data_count_bit = 68;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
temp_str = furi_string_alloc();
|
||||
if(!flipper_format_read_string(flipper_format, "Key", temp_str)) {
|
||||
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 {
|
||||
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", hex_pos);
|
||||
break;
|
||||
}
|
||||
|
||||
instance->generic.data = key;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(flipper_format, "Serial", &instance->serial, 1)) {
|
||||
uint8_t b[8];
|
||||
b[0] = (key >> 56) & 0xFF;
|
||||
b[1] = (key >> 48) & 0xFF;
|
||||
b[2] = (key >> 40) & 0xFF;
|
||||
b[3] = (key >> 32) & 0xFF;
|
||||
b[4] = (key >> 24) & 0xFF;
|
||||
b[5] = (key >> 16) & 0xFF;
|
||||
b[6] = (key >> 8) & 0xFF;
|
||||
b[7] = key & 0xFF;
|
||||
|
||||
instance->serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
|
||||
((uint32_t)reverse8(b[6]) << 16) | ((uint32_t)reverse8(b[5]) << 8) |
|
||||
(uint32_t)reverse8(b[4]);
|
||||
} else {
|
||||
}
|
||||
instance->generic.serial = instance->serial;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t btn_temp;
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) {
|
||||
instance->btn = (uint8_t)btn_temp;
|
||||
} else {
|
||||
uint8_t b7 = instance->generic.data & 0xFF;
|
||||
instance->btn = (reverse8(b7) & 0xF0) >> 4;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t cnt_temp;
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt_temp, 1)) {
|
||||
instance->cnt = (uint16_t)cnt_temp;
|
||||
} else {
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t decrypted_temp;
|
||||
if(flipper_format_read_uint32(flipper_format, "Decrypted", &decrypted_temp, 1)) {
|
||||
instance->cnt = decrypted_temp & 0xFFFF;
|
||||
} else {
|
||||
instance->cnt = 0;
|
||||
}
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t version_temp;
|
||||
if(flipper_format_read_uint32(flipper_format, "Version", &version_temp, 1)) {
|
||||
if(!version_from_protocol_name) {
|
||||
instance->version = (uint8_t)version_temp;
|
||||
}
|
||||
} else if(!version_from_protocol_name) {
|
||||
instance->version = 0;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
|
||||
instance->encoder.repeat = 10;
|
||||
}
|
||||
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(5);
|
||||
|
||||
uint8_t selected_btn;
|
||||
if(subghz_custom_btn_get() == SUBGHZ_CUSTOM_BTN_OK) {
|
||||
selected_btn = subghz_custom_btn_get_original();
|
||||
} else {
|
||||
selected_btn = subghz_custom_btn_get();
|
||||
}
|
||||
|
||||
if(selected_btn == 5) {
|
||||
instance->btn = 0x8;
|
||||
} else if(selected_btn >= 1 && selected_btn <= 4) {
|
||||
instance->btn = selected_btn;
|
||||
}
|
||||
|
||||
uint32_t mult = furi_hal_subghz_get_rolling_counter_mult();
|
||||
instance->cnt = (instance->cnt + mult) & 0xFFFF;
|
||||
|
||||
instance->generic.btn = instance->btn;
|
||||
instance->generic.cnt = instance->cnt;
|
||||
|
||||
subghz_protocol_encoder_kia_v3_v4_get_upload(instance);
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint32_t cnt_to_write = instance->cnt;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Cnt", &cnt_to_write, 1)) {
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint32_t btn_to_write = instance->btn;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Btn", &btn_to_write, 1)) {
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint32_t decrypted_to_write = instance->decrypted;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Decrypted", &decrypted_to_write, 1)) {
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint32_t encrypted_to_write = instance->encrypted;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Encrypted", &encrypted_to_write, 1)) {
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint32_t crc_to_write = instance->crc;
|
||||
if(!flipper_format_update_uint32(flipper_format, "CRC", &crc_to_write, 1)) {
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v3_v4_stop(void* context) {
|
||||
if(!context) return;
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_kia_v3_v4_yield(void* context) {
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
|
||||
if(!instance || !instance->encoder.upload || instance->encoder.repeat == 0 ||
|
||||
!instance->encoder.is_running) {
|
||||
if(instance) {
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
if(instance->encoder.front >= instance->encoder.size_upload) {
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
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_kia_v3_v4_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV3V4));
|
||||
instance->base.protocol = &subghz_protocol_kia_v3_v4;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->version = 0;
|
||||
instance->is_v3_sync = false;
|
||||
return instance;
|
||||
}
|
||||
void subghz_protocol_decoder_kia_v3_v4_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_v3_v4_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
instance->raw_bit_count = 0;
|
||||
instance->is_v3_sync = false;
|
||||
instance->crc = 0;
|
||||
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case KiaV3V4DecoderStepReset:
|
||||
if(level && DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
|
||||
subghz_protocol_kia_v3_v4_const.te_delta) {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepCheckPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 1;
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV3V4DecoderStepCheckPreamble:
|
||||
if(level) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
|
||||
subghz_protocol_kia_v3_v4_const.te_delta) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count++;
|
||||
} else if(duration > 1000 && duration < 1500) {
|
||||
if(instance->header_count >= 8) {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepCollectRawBits;
|
||||
instance->raw_bit_count = 0;
|
||||
instance->is_v3_sync = false;
|
||||
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(duration > 1000 && duration < 1500) {
|
||||
if(instance->header_count >= 8) {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepCollectRawBits;
|
||||
instance->raw_bit_count = 0;
|
||||
instance->is_v3_sync = true;
|
||||
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
} else if(
|
||||
DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
|
||||
subghz_protocol_kia_v3_v4_const.te_delta &&
|
||||
DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_v3_v4_const.te_short) <
|
||||
subghz_protocol_kia_v3_v4_const.te_delta) {
|
||||
instance->header_count++;
|
||||
} else if(duration > 1500) {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV3V4DecoderStepCollectRawBits:
|
||||
if(level) {
|
||||
if(duration > 1000 && duration < 1500) {
|
||||
if(kia_v3_v4_process_buffer(instance)) {
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
|
||||
subghz_protocol_kia_v3_v4_const.te_delta) {
|
||||
kia_v3_v4_add_raw_bit(instance, false);
|
||||
} else if(
|
||||
DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_long) <
|
||||
subghz_protocol_kia_v3_v4_const.te_delta) {
|
||||
kia_v3_v4_add_raw_bit(instance, true);
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(duration > 1000 && duration < 1500) {
|
||||
if(kia_v3_v4_process_buffer(instance)) {
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
} else if(duration > 1500) {
|
||||
if(kia_v3_v4_process_buffer(instance)) {
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_kia_v3_v4_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_kia_v3_v4_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(!flipper_format_write_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(!flipper_format_write_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t temp = instance->version;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Version", &temp, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t temp = instance->crc;
|
||||
if(!flipper_format_write_uint32(flipper_format, "CRC", &temp, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_kia_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(instance->generic.data_count_bit < 64) {
|
||||
ret = SubGhzProtocolStatusErrorParserBitCount;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(!flipper_format_read_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
|
||||
instance->encrypted = 0;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
|
||||
instance->decrypted = 0;
|
||||
}
|
||||
|
||||
uint32_t temp_version = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Version", &temp_version, 1)) {
|
||||
instance->version = temp_version;
|
||||
} else {
|
||||
instance->version = 0;
|
||||
}
|
||||
|
||||
uint32_t temp_crc = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "CRC", &temp_crc, 1)) {
|
||||
instance->crc = temp_crc;
|
||||
} else {
|
||||
instance->crc = 0;
|
||||
}
|
||||
|
||||
if(instance->decrypted != 0) {
|
||||
instance->generic.btn = (instance->decrypted >> 28) & 0x0F;
|
||||
instance->generic.cnt = instance->decrypted & 0xFFFF;
|
||||
}
|
||||
|
||||
if(instance->generic.data != 0) {
|
||||
uint8_t b[8];
|
||||
for(int i = 0; i < 8; i++) {
|
||||
b[i] = (instance->generic.data >> ((7-i) * 8)) & 0xFF;
|
||||
}
|
||||
|
||||
instance->generic.serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
|
||||
((uint32_t)reverse8(b[6]) << 16) |
|
||||
((uint32_t)reverse8(b[5]) << 8) |
|
||||
(uint32_t)reverse8(b[4]);
|
||||
}
|
||||
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->generic.btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(5);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static uint64_t compute_yek(uint64_t key) {
|
||||
uint64_t yek = 0;
|
||||
for(int i = 0; i < 64; i++) {
|
||||
yek |= ((key >> i) & 1) << (63 - i);
|
||||
}
|
||||
return yek;
|
||||
}
|
||||
|
||||
static bool kia_v3_v4_verify_crc_from_data(uint64_t data, uint8_t received_crc) {
|
||||
uint8_t bytes[8];
|
||||
for(int i = 0; i < 8; i++) {
|
||||
bytes[i] = (data >> ((7-i) * 8)) & 0xFF;
|
||||
}
|
||||
uint8_t calculated_crc = kia_v3_v4_calculate_crc(bytes);
|
||||
return (calculated_crc == received_crc);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_v3_v4_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
|
||||
uint64_t yek = compute_yek(instance->generic.data);
|
||||
uint32_t key_hi = (uint32_t)(instance->generic.data >> 32);
|
||||
uint32_t key_lo = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
|
||||
uint32_t yek_hi = (uint32_t)(yek >> 32);
|
||||
uint32_t yek_lo = (uint32_t)(yek & 0xFFFFFFFF);
|
||||
|
||||
bool crc_valid = kia_v3_v4_verify_crc_from_data(instance->generic.data, instance->crc);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Yek:%08lX%08lX\r\n"
|
||||
"Sn:%07lX Btn:%X [%s]\r\n"
|
||||
"Dec:%08lX Cnt:%04lX\r\n"
|
||||
"CRC:%X %s",
|
||||
kia_version_names[instance->version],
|
||||
instance->generic.data_count_bit,
|
||||
key_hi,
|
||||
key_lo,
|
||||
yek_hi,
|
||||
yek_lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
subghz_protocol_kia_v3_v4_get_name_button(instance->generic.btn),
|
||||
instance->decrypted,
|
||||
instance->generic.cnt,
|
||||
instance->crc,
|
||||
crc_valid ? "(OK)" : "(FAIL)");
|
||||
}
|
||||
+255
-335
@@ -1,4 +1,5 @@
|
||||
#include "kia_v3_v4.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
@@ -6,25 +7,25 @@
|
||||
#include "../blocks/math.h"
|
||||
#include "../blocks/custom_btn_i.h"
|
||||
|
||||
#define TAG "SubGhzProtocolKiaV3V4"
|
||||
|
||||
#define KIA_MF_KEY 0xA8F5DFFC8DAA5CDBULL
|
||||
|
||||
#define TAG "KiaV3V4"
|
||||
|
||||
static const char* kia_version_names[] = {"Kia V4", "Kia V3"};
|
||||
|
||||
#define KIA_V3_V4_PREAMBLE_PAIRS 16
|
||||
#define KIA_V3_V4_TOTAL_BURSTS 3
|
||||
#define KIA_V3_V4_INTER_BURST_GAP_US 10000
|
||||
#define KIA_V3_V4_SYNC_DURATION 1200
|
||||
|
||||
static const SubGhzBlockConst kia_protocol_v3_v4_const = {
|
||||
static const char* kia_version_names[] = {"KIA/HYU V4", "KIA/HYU V3"};
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_kia_v3_v4_const = {
|
||||
.te_short = 400,
|
||||
.te_long = 800,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 68,
|
||||
};
|
||||
|
||||
typedef struct SubGhzProtocolDecoderKiaV3V4 {
|
||||
struct SubGhzProtocolDecoderKiaV3V4 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
@@ -38,9 +39,9 @@ typedef struct SubGhzProtocolDecoderKiaV3V4 {
|
||||
uint32_t decrypted;
|
||||
uint8_t crc;
|
||||
uint8_t version;
|
||||
} SubGhzProtocolDecoderKiaV3V4;
|
||||
};
|
||||
|
||||
typedef struct SubGhzProtocolEncoderKiaV3V4 {
|
||||
struct SubGhzProtocolEncoderKiaV3V4 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
@@ -49,10 +50,11 @@ typedef struct SubGhzProtocolEncoderKiaV3V4 {
|
||||
uint8_t btn;
|
||||
uint16_t cnt;
|
||||
uint8_t version;
|
||||
uint8_t crc;
|
||||
|
||||
uint32_t encrypted;
|
||||
uint32_t decrypted;
|
||||
} SubGhzProtocolEncoderKiaV3V4;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
KiaV3V4DecoderStepReset = 0,
|
||||
@@ -60,7 +62,6 @@ typedef enum {
|
||||
KiaV3V4DecoderStepCollectRawBits,
|
||||
} KiaV3V4DecoderStep;
|
||||
|
||||
// KeeLoq decrypt
|
||||
static uint32_t keeloq_common_decrypt(uint32_t data, uint64_t key) {
|
||||
uint32_t block = data;
|
||||
uint64_t tkey = key;
|
||||
@@ -76,7 +77,6 @@ static uint32_t keeloq_common_decrypt(uint32_t data, uint64_t key) {
|
||||
return block;
|
||||
}
|
||||
|
||||
// KeeLoq encrypt
|
||||
static uint32_t keeloq_common_encrypt(uint32_t data, uint64_t key) {
|
||||
uint32_t block = data;
|
||||
uint64_t tkey = key;
|
||||
@@ -167,6 +167,14 @@ static bool kia_v3_v4_process_buffer(SubGhzProtocolDecoderKiaV3V4* instance) {
|
||||
((uint64_t)b[6] << 8) | (uint64_t)b[7];
|
||||
instance->generic.data = key_data;
|
||||
instance->generic.data_count_bit = 68;
|
||||
|
||||
instance->decoder.decode_data = key_data;
|
||||
instance->decoder.decode_count_bit = 68;
|
||||
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->generic.btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(5);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -193,16 +201,23 @@ const SubGhzProtocolEncoder subghz_protocol_kia_v3_v4_encoder = {
|
||||
const SubGhzProtocol subghz_protocol_kia_v3_v4 = {
|
||||
.name = SUBGHZ_PROTOCOL_KIA_V3_V4_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save |
|
||||
SubGhzProtocolFlag_Send,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_kia_v3_v4_decoder,
|
||||
.encoder = &subghz_protocol_kia_v3_v4_encoder,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ENCODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
static const char* subghz_protocol_kia_v3_v4_get_name_button(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x1: return "Lock";
|
||||
case 0x2: return "Unlock";
|
||||
case 0x3: return "Trunk";
|
||||
case 0x4: return "Panic";
|
||||
case 0x8: return "Horn";
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_kia_v3_v4_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
@@ -218,16 +233,15 @@ void* subghz_protocol_encoder_kia_v3_v4_alloc(SubGhzEnvironment* environment) {
|
||||
|
||||
instance->encoder.size_upload = 600;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.repeat = 40;
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = false;
|
||||
|
||||
FURI_LOG_I(TAG, "Encoder allocated at %p", instance);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v3_v4_free(void* context) {
|
||||
furi_check(context);
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
if(instance->encoder.upload) {
|
||||
free(instance->encoder.upload);
|
||||
@@ -238,7 +252,6 @@ void subghz_protocol_encoder_kia_v3_v4_free(void* context) {
|
||||
static void subghz_protocol_encoder_kia_v3_v4_build_packet(
|
||||
SubGhzProtocolEncoderKiaV3V4* instance,
|
||||
uint8_t* raw_bytes) {
|
||||
// Build plaintext for encryption:
|
||||
uint32_t plaintext = (instance->cnt & 0xFFFF) | ((instance->serial & 0xFF) << 16) |
|
||||
(0x1 << 24) | ((instance->btn & 0x0F) << 28);
|
||||
|
||||
@@ -247,20 +260,11 @@ static void subghz_protocol_encoder_kia_v3_v4_build_packet(
|
||||
uint32_t encrypted = keeloq_common_encrypt(plaintext, KIA_MF_KEY);
|
||||
instance->encrypted = encrypted;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encrypt: plain=0x%08lX -> enc=0x%08lX",
|
||||
(unsigned long)plaintext,
|
||||
(unsigned long)encrypted);
|
||||
|
||||
// Decoder does: encrypted = (rev(b[3])<<24) | (rev(b[2])<<16) | (rev(b[1])<<8) | rev(b[0])
|
||||
// So b[0] must contain reverse8(LSB), b[3] must contain reverse8(MSB)
|
||||
raw_bytes[0] = reverse8((encrypted >> 0) & 0xFF); // LSB
|
||||
raw_bytes[0] = reverse8((encrypted >> 0) & 0xFF);
|
||||
raw_bytes[1] = reverse8((encrypted >> 8) & 0xFF);
|
||||
raw_bytes[2] = reverse8((encrypted >> 16) & 0xFF);
|
||||
raw_bytes[3] = reverse8((encrypted >> 24) & 0xFF); // MSB
|
||||
raw_bytes[3] = reverse8((encrypted >> 24) & 0xFF);
|
||||
|
||||
// Serial/button
|
||||
uint32_t serial_btn = (instance->serial & 0x0FFFFFFF) |
|
||||
((uint32_t)(instance->btn & 0x0F) << 28);
|
||||
raw_bytes[4] = reverse8((serial_btn >> 0) & 0xFF);
|
||||
@@ -268,42 +272,19 @@ static void subghz_protocol_encoder_kia_v3_v4_build_packet(
|
||||
raw_bytes[6] = reverse8((serial_btn >> 16) & 0xFF);
|
||||
raw_bytes[7] = reverse8((serial_btn >> 24) & 0xFF);
|
||||
|
||||
// CRC
|
||||
uint8_t crc = kia_v3_v4_calculate_crc(raw_bytes);
|
||||
raw_bytes[8] = (crc << 4);
|
||||
instance->crc = crc;
|
||||
|
||||
// DEBUG: Log the exact raw bytes we're generating
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"TX raw: %02X %02X %02X %02X %02X %02X %02X %02X %02X",
|
||||
raw_bytes[0],
|
||||
raw_bytes[1],
|
||||
raw_bytes[2],
|
||||
raw_bytes[3],
|
||||
raw_bytes[4],
|
||||
raw_bytes[5],
|
||||
raw_bytes[6],
|
||||
raw_bytes[7],
|
||||
raw_bytes[8]);
|
||||
|
||||
// Store in generic.data for display
|
||||
instance->generic.data = ((uint64_t)raw_bytes[0] << 56) | ((uint64_t)raw_bytes[1] << 48) |
|
||||
((uint64_t)raw_bytes[2] << 40) | ((uint64_t)raw_bytes[3] << 32) |
|
||||
((uint64_t)raw_bytes[4] << 24) | ((uint64_t)raw_bytes[5] << 16) |
|
||||
((uint64_t)raw_bytes[6] << 8) | (uint64_t)raw_bytes[7];
|
||||
instance->generic.data_count_bit = 68;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Packet built: Serial=0x%07lX, Btn=0x%X, Cnt=0x%04X, CRC=0x%X",
|
||||
(unsigned long)instance->serial,
|
||||
instance->btn,
|
||||
instance->cnt,
|
||||
crc);
|
||||
}
|
||||
|
||||
static void subghz_protocol_encoder_kia_v3_v4_get_upload(SubGhzProtocolEncoderKiaV3V4* instance) {
|
||||
furi_check(instance);
|
||||
furi_assert(instance);
|
||||
|
||||
uint8_t raw_bytes[9];
|
||||
subghz_protocol_encoder_kia_v3_v4_build_packet(instance, raw_bytes);
|
||||
@@ -322,29 +303,24 @@ static void subghz_protocol_encoder_kia_v3_v4_get_upload(SubGhzProtocolEncoderKi
|
||||
level_duration_make(false, KIA_V3_V4_INTER_BURST_GAP_US);
|
||||
}
|
||||
|
||||
// Preamble: alternating short pulses
|
||||
for(int i = 0; i < KIA_V3_V4_PREAMBLE_PAIRS; i++) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, kia_protocol_v3_v4_const.te_short);
|
||||
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, kia_protocol_v3_v4_const.te_short);
|
||||
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_short);
|
||||
}
|
||||
|
||||
// Sync pulse - different for V3 vs V4
|
||||
if(instance->version == 0) {
|
||||
// V4: long HIGH, short LOW
|
||||
instance->encoder.upload[index++] = level_duration_make(true, KIA_V3_V4_SYNC_DURATION);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, kia_protocol_v3_v4_const.te_short);
|
||||
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_short);
|
||||
} else {
|
||||
// V3: short HIGH, long LOW
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, kia_protocol_v3_v4_const.te_short);
|
||||
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, KIA_V3_V4_SYNC_DURATION);
|
||||
}
|
||||
|
||||
// Data bits - PWM encoding with complementary durations
|
||||
for(int byte_idx = 0; byte_idx < 9; byte_idx++) {
|
||||
int bits_in_byte = (byte_idx == 8) ? 4 : 8;
|
||||
|
||||
@@ -352,43 +328,31 @@ static void subghz_protocol_encoder_kia_v3_v4_get_upload(SubGhzProtocolEncoderKi
|
||||
bool bit = (raw_bytes[byte_idx] >> bit_idx) & 1;
|
||||
|
||||
if(bit) {
|
||||
// bit 1: long HIGH, short LOW (total ~1200µs)
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, kia_protocol_v3_v4_const.te_long); // 800µs
|
||||
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_long);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, kia_protocol_v3_v4_const.te_short); // 400µs
|
||||
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_short);
|
||||
} else {
|
||||
// bit 0: short HIGH, long LOW (total ~1200µs)
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, kia_protocol_v3_v4_const.te_short); // 400µs
|
||||
level_duration_make(true, subghz_protocol_kia_v3_v4_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, kia_protocol_v3_v4_const.te_long); // 800µs
|
||||
level_duration_make(false, subghz_protocol_kia_v3_v4_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//instance->encoder.upload[index++] = level_duration_make(false, KIA_V3_V4_INTER_BURST_GAP_US);
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Upload built: %d bursts, size_upload=%zu, version=%s",
|
||||
KIA_V3_V4_TOTAL_BURSTS,
|
||||
instance->encoder.size_upload,
|
||||
instance->version == 0 ? "V4" : "V3");
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_kia_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
//instance->encoder.repeat = 40;
|
||||
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
@@ -397,49 +361,39 @@ SubGhzProtocolStatus
|
||||
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;
|
||||
}
|
||||
|
||||
// Accept "Kia V3/V4", "Kia V3", or "Kia V4"
|
||||
const char* proto_str = furi_string_get_cstr(temp_str);
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name) &&
|
||||
strcmp(proto_str, "Kia V3") != 0 && strcmp(proto_str, "Kia V4") != 0) {
|
||||
FURI_LOG_E(TAG, "Wrong protocol %s", proto_str);
|
||||
strcmp(proto_str, "KIA/HYU V3") != 0 && strcmp(proto_str, "KIA/HYU V4") != 0) {
|
||||
furi_string_free(temp_str);
|
||||
break;
|
||||
}
|
||||
|
||||
// Set version based on protocol name if specific
|
||||
bool version_from_protocol_name = false;
|
||||
|
||||
if(strcmp(proto_str, "Kia V3") == 0) {
|
||||
if(strcmp(proto_str, "KIA/HYU V3") == 0) {
|
||||
instance->version = 1;
|
||||
version_from_protocol_name = true;
|
||||
FURI_LOG_I(TAG, "Protocol name indicates V3");
|
||||
} else if(strcmp(proto_str, "Kia V4") == 0) {
|
||||
} else if(strcmp(proto_str, "KIA/HYU V4") == 0) {
|
||||
instance->version = 0;
|
||||
version_from_protocol_name = true;
|
||||
FURI_LOG_I(TAG, "Protocol name indicates V4");
|
||||
}
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
// Read bit count
|
||||
flipper_format_rewind(flipper_format);
|
||||
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 = 68;
|
||||
|
||||
// Read key data
|
||||
flipper_format_rewind(flipper_format);
|
||||
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;
|
||||
}
|
||||
@@ -461,7 +415,6 @@ SubGhzProtocolStatus
|
||||
} else if(c >= 'a' && c <= 'f') {
|
||||
nibble = c - 'a' + 10;
|
||||
} else {
|
||||
FURI_LOG_E(TAG, "Invalid hex character: %c", c);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -471,10 +424,13 @@ SubGhzProtocolStatus
|
||||
|
||||
furi_string_free(temp_str);
|
||||
|
||||
instance->generic.data = key;
|
||||
FURI_LOG_I(TAG, "Parsed key: 0x%016llX", (unsigned long long)instance->generic.data);
|
||||
if(hex_pos != 16) {
|
||||
FURI_LOG_E(TAG, "Invalid key length: %zu nibbles", hex_pos);
|
||||
break;
|
||||
}
|
||||
|
||||
instance->generic.data = key;
|
||||
|
||||
// Read serial
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(flipper_format, "Serial", &instance->serial, 1)) {
|
||||
uint8_t b[8];
|
||||
@@ -490,89 +446,129 @@ SubGhzProtocolStatus
|
||||
instance->serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
|
||||
((uint32_t)reverse8(b[6]) << 16) | ((uint32_t)reverse8(b[5]) << 8) |
|
||||
(uint32_t)reverse8(b[4]);
|
||||
FURI_LOG_I(TAG, "Extracted serial: 0x%08lX", (unsigned long)instance->serial);
|
||||
} else {
|
||||
FURI_LOG_I(TAG, "Read serial: 0x%08lX", (unsigned long)instance->serial);
|
||||
}
|
||||
instance->generic.serial = instance->serial;
|
||||
|
||||
// Read button
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t btn_temp;
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1)) {
|
||||
instance->btn = (uint8_t)btn_temp;
|
||||
FURI_LOG_I(TAG, "Read button: 0x%X", instance->btn);
|
||||
} else {
|
||||
uint8_t b7 = instance->generic.data & 0xFF;
|
||||
instance->btn = (reverse8(b7) & 0xF0) >> 4;
|
||||
FURI_LOG_I(TAG, "Extracted button: 0x%X", instance->btn);
|
||||
}
|
||||
instance->generic.btn = instance->btn;
|
||||
|
||||
// Read counter
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t cnt_temp;
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt_temp, 1)) {
|
||||
instance->cnt = (uint16_t)cnt_temp;
|
||||
FURI_LOG_I(TAG, "Read counter: 0x%04X", instance->cnt);
|
||||
} else {
|
||||
// Try Decrypted field
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t decrypted_temp;
|
||||
if(flipper_format_read_uint32(flipper_format, "Decrypted", &decrypted_temp, 1)) {
|
||||
instance->cnt = decrypted_temp & 0xFFFF;
|
||||
FURI_LOG_I(TAG, "Extracted counter from Decrypted: 0x%04X", instance->cnt);
|
||||
} else {
|
||||
instance->cnt = 0;
|
||||
FURI_LOG_W(TAG, "Counter not found, using 0");
|
||||
}
|
||||
}
|
||||
instance->generic.cnt = instance->cnt;
|
||||
|
||||
// Read version - ONLY use file version if protocol name didn't specify one
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint32_t version_temp;
|
||||
if(flipper_format_read_uint32(flipper_format, "KIAVersion", &version_temp, 1)) {
|
||||
if(flipper_format_read_uint32(flipper_format, "Version", &version_temp, 1)) {
|
||||
if(!version_from_protocol_name) {
|
||||
// Generic "Kia V3/V4" protocol - use file's version
|
||||
instance->version = (uint8_t)version_temp;
|
||||
FURI_LOG_I(
|
||||
TAG, "Read version from file: %s", instance->version == 0 ? "V4" : "V3");
|
||||
} else {
|
||||
// Protocol name was specific - trust that, ignore file version
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Using version from protocol name: %s (file had Version=%lu)",
|
||||
instance->version == 0 ? "V4" : "V3",
|
||||
(unsigned long)version_temp);
|
||||
}
|
||||
} else if(!version_from_protocol_name) {
|
||||
instance->version = 0;
|
||||
FURI_LOG_I(TAG, "Version not found, defaulting to V4");
|
||||
}
|
||||
|
||||
// Read repeat
|
||||
flipper_format_rewind(flipper_format);
|
||||
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, using default 40");
|
||||
instance->encoder.repeat = 10;
|
||||
}
|
||||
|
||||
// Build the upload
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(5);
|
||||
|
||||
uint8_t selected_btn;
|
||||
if(subghz_custom_btn_get() == SUBGHZ_CUSTOM_BTN_OK) {
|
||||
selected_btn = subghz_custom_btn_get_original();
|
||||
} else {
|
||||
selected_btn = subghz_custom_btn_get();
|
||||
}
|
||||
|
||||
if(selected_btn == 5) {
|
||||
instance->btn = 0x8;
|
||||
} else if(selected_btn >= 1 && selected_btn <= 4) {
|
||||
instance->btn = selected_btn;
|
||||
}
|
||||
|
||||
uint32_t mult = furi_hal_subghz_get_rolling_counter_mult();
|
||||
instance->cnt = (instance->cnt + mult) & 0xFFFF;
|
||||
|
||||
instance->generic.btn = instance->btn;
|
||||
instance->generic.cnt = instance->cnt;
|
||||
|
||||
subghz_protocol_encoder_kia_v3_v4_get_upload(instance);
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint8_t key_data[sizeof(uint64_t)] = {0};
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (instance->generic.data >> (i * 8)) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint32_t cnt_to_write = instance->cnt;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Cnt", &cnt_to_write, 1)) {
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint32_t btn_to_write = instance->btn;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Btn", &btn_to_write, 1)) {
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint32_t decrypted_to_write = instance->decrypted;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Decrypted", &decrypted_to_write, 1)) {
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint32_t encrypted_to_write = instance->encrypted;
|
||||
if(!flipper_format_update_uint32(flipper_format, "Encrypted", &encrypted_to_write, 1)) {
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
break;
|
||||
}
|
||||
uint32_t crc_to_write = instance->crc;
|
||||
if(!flipper_format_update_uint32(flipper_format, "CRC", &crc_to_write, 1)) {
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
instance->encoder.front = 0;
|
||||
|
||||
FURI_LOG_I(
|
||||
TAG,
|
||||
"Encoder initialized: Serial=0x%07lX, Btn=0x%X, Cnt=0x%04X, Version=%s",
|
||||
(unsigned long)instance->serial,
|
||||
instance->btn,
|
||||
instance->cnt,
|
||||
instance->version == 0 ? "V4" : "V3");
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
@@ -592,22 +588,12 @@ LevelDuration subghz_protocol_encoder_kia_v3_v4_yield(void* context) {
|
||||
if(!instance || !instance->encoder.upload || instance->encoder.repeat == 0 ||
|
||||
!instance->encoder.is_running) {
|
||||
if(instance) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Encoder yield stopped: repeat=%u, is_running=%d",
|
||||
instance->encoder.repeat,
|
||||
instance->encoder.is_running);
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
if(instance->encoder.front >= instance->encoder.size_upload) {
|
||||
FURI_LOG_E(
|
||||
TAG,
|
||||
"Encoder front out of bounds: %zu >= %zu",
|
||||
instance->encoder.front,
|
||||
instance->encoder.size_upload);
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
return level_duration_reset();
|
||||
@@ -615,101 +601,48 @@ LevelDuration subghz_protocol_encoder_kia_v3_v4_yield(void* context) {
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(instance->encoder.front < 5) {
|
||||
FURI_LOG_D(
|
||||
TAG,
|
||||
"Encoder yield[%zu]: repeat=%u, level=%d, duration=%lu",
|
||||
instance->encoder.front,
|
||||
instance->encoder.repeat,
|
||||
level_duration_get_level(ret),
|
||||
level_duration_get_duration(ret));
|
||||
}
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
FURI_LOG_I(
|
||||
TAG, "Encoder completed one cycle, remaining repeat=%u", instance->encoder.repeat);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v3_v4_set_button(void* context, uint8_t button) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
instance->btn = button & 0x0F;
|
||||
instance->generic.btn = instance->btn;
|
||||
subghz_protocol_encoder_kia_v3_v4_get_upload(instance);
|
||||
FURI_LOG_I(TAG, "Button set to 0x%X", instance->btn);
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v3_v4_set_counter(void* context, uint16_t counter) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
instance->cnt = counter;
|
||||
instance->generic.cnt = instance->cnt;
|
||||
subghz_protocol_encoder_kia_v3_v4_get_upload(instance);
|
||||
FURI_LOG_I(TAG, "Counter set to 0x%04X", instance->cnt);
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v3_v4_increment_counter(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
instance->cnt++;
|
||||
instance->generic.cnt = instance->cnt;
|
||||
subghz_protocol_encoder_kia_v3_v4_get_upload(instance);
|
||||
FURI_LOG_I(TAG, "Counter incremented to 0x%04X", instance->cnt);
|
||||
}
|
||||
|
||||
uint16_t subghz_protocol_encoder_kia_v3_v4_get_counter(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
return instance->cnt;
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_encoder_kia_v3_v4_get_button(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderKiaV3V4* instance = context;
|
||||
return instance->btn;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DECODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
void* subghz_protocol_decoder_kia_v3_v4_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV3V4));
|
||||
instance->base.protocol = &subghz_protocol_kia_v3_v4;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->version = 0;
|
||||
instance->is_v3_sync = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_v3_v4_free(void* context) {
|
||||
furi_check(context);
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_v3_v4_reset(void* context) {
|
||||
furi_check(context);
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
instance->raw_bit_count = 0;
|
||||
instance->is_v3_sync = false;
|
||||
instance->crc = 0;
|
||||
memset(instance->raw_bits, 0, sizeof(instance->raw_bits));
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case KiaV3V4DecoderStepReset:
|
||||
if(level && (DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_short) <
|
||||
kia_protocol_v3_v4_const.te_delta)) {
|
||||
case KiaV3V4DecoderStepReset:
|
||||
if(level && DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
|
||||
subghz_protocol_kia_v3_v4_const.te_delta) {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepCheckPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 1;
|
||||
@@ -718,9 +651,10 @@ void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t
|
||||
|
||||
case KiaV3V4DecoderStepCheckPreamble:
|
||||
if(level) {
|
||||
if(DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_short) <
|
||||
kia_protocol_v3_v4_const.te_delta) {
|
||||
if(DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
|
||||
subghz_protocol_kia_v3_v4_const.te_delta) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count++;
|
||||
} else if(duration > 1000 && duration < 1500) {
|
||||
if(instance->header_count >= 8) {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepCollectRawBits;
|
||||
@@ -744,10 +678,10 @@ void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
}
|
||||
} else if(
|
||||
(DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_short) <
|
||||
kia_protocol_v3_v4_const.te_delta) &&
|
||||
(DURATION_DIFF(instance->decoder.te_last, kia_protocol_v3_v4_const.te_short) <
|
||||
kia_protocol_v3_v4_const.te_delta)) {
|
||||
DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
|
||||
subghz_protocol_kia_v3_v4_const.te_delta &&
|
||||
DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_v3_v4_const.te_short) <
|
||||
subghz_protocol_kia_v3_v4_const.te_delta) {
|
||||
instance->header_count++;
|
||||
} else if(duration > 1500) {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
@@ -764,12 +698,12 @@ void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t
|
||||
}
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
} else if(
|
||||
DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_short) <
|
||||
kia_protocol_v3_v4_const.te_delta) {
|
||||
DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_short) <
|
||||
subghz_protocol_kia_v3_v4_const.te_delta) {
|
||||
kia_v3_v4_add_raw_bit(instance, false);
|
||||
} else if(
|
||||
DURATION_DIFF(duration, kia_protocol_v3_v4_const.te_long) <
|
||||
kia_protocol_v3_v4_const.te_delta) {
|
||||
DURATION_DIFF(duration, subghz_protocol_kia_v3_v4_const.te_long) <
|
||||
subghz_protocol_kia_v3_v4_const.te_delta) {
|
||||
kia_v3_v4_add_raw_bit(instance, true);
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV3V4DecoderStepReset;
|
||||
@@ -794,7 +728,7 @@ void subghz_protocol_decoder_kia_v3_v4_feed(void* context, bool level, uint32_t
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_kia_v3_v4_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
@@ -804,107 +738,99 @@ SubGhzProtocolStatus subghz_protocol_decoder_kia_v3_v4_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
do {
|
||||
// Write frequency
|
||||
if(!flipper_format_write_uint32(flipper_format, "Frequency", &preset->frequency, 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Write preset
|
||||
if(!flipper_format_write_string_cstr(
|
||||
flipper_format, "Preset", furi_string_get_cstr(preset->name))) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Write version-specific protocol name instead of generic "Kia V3/V4"
|
||||
const char* version_name = (instance->version == 0) ? "Kia V4" : "Kia V3";
|
||||
if(!flipper_format_write_string_cstr(flipper_format, "Protocol", version_name)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Write bit count
|
||||
uint32_t bits = instance->generic.data_count_bit;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Bit", &bits, 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Write key
|
||||
char key_str[20];
|
||||
snprintf(key_str, sizeof(key_str), "%016llX", (unsigned long long)instance->generic.data);
|
||||
if(!flipper_format_write_string_cstr(flipper_format, "Key", key_str)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Write all fields needed by encoder
|
||||
if(!flipper_format_write_uint32(flipper_format, "Serial", &instance->generic.serial, 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t temp = instance->generic.btn;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
temp = instance->generic.cnt;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Cnt", &temp, 1)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Write protocol-specific fields
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(!flipper_format_write_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
|
||||
break;
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(!flipper_format_write_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
|
||||
break;
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
|
||||
temp = instance->version;
|
||||
if(!flipper_format_write_uint32(flipper_format, "KIAVersion", &temp, 1)) {
|
||||
break;
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t temp = instance->version;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Version", &temp, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
|
||||
temp = instance->crc;
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t temp = instance->crc;
|
||||
if(!flipper_format_write_uint32(flipper_format, "CRC", &temp, 1)) {
|
||||
break;
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_kia_v3_v4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_deserialize_check_count_bit(&instance->generic, flipper_format, 64);
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t temp = 0;
|
||||
|
||||
if(flipper_format_read_uint32(flipper_format, "Encrypted", &temp, 1)) {
|
||||
instance->encrypted = temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Decrypted", &temp, 1)) {
|
||||
instance->decrypted = temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "KIAVersion", &temp, 1)) {
|
||||
instance->version = (uint8_t)temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "CRC", &temp, 1)) {
|
||||
instance->crc = (uint8_t)temp;
|
||||
if(instance->generic.data_count_bit < 64) {
|
||||
ret = SubGhzProtocolStatusErrorParserBitCount;
|
||||
}
|
||||
}
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(!flipper_format_read_uint32(flipper_format, "Encrypted", &instance->encrypted, 1)) {
|
||||
instance->encrypted = 0;
|
||||
}
|
||||
|
||||
if(!flipper_format_read_uint32(flipper_format, "Decrypted", &instance->decrypted, 1)) {
|
||||
instance->decrypted = 0;
|
||||
}
|
||||
|
||||
uint32_t temp_version = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Version", &temp_version, 1)) {
|
||||
instance->version = temp_version;
|
||||
} else {
|
||||
instance->version = 0;
|
||||
}
|
||||
|
||||
uint32_t temp_crc = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "CRC", &temp_crc, 1)) {
|
||||
instance->crc = temp_crc;
|
||||
} else {
|
||||
instance->crc = 0;
|
||||
}
|
||||
|
||||
if(instance->decrypted != 0) {
|
||||
instance->generic.btn = (instance->decrypted >> 28) & 0x0F;
|
||||
instance->generic.cnt = instance->decrypted & 0xFFFF;
|
||||
}
|
||||
|
||||
if(instance->generic.data != 0) {
|
||||
uint8_t b[8];
|
||||
for(int i = 0; i < 8; i++) {
|
||||
b[i] = (instance->generic.data >> ((7-i) * 8)) & 0xFF;
|
||||
}
|
||||
|
||||
instance->generic.serial = ((uint32_t)reverse8(b[7] & 0xF0) << 24) |
|
||||
((uint32_t)reverse8(b[6]) << 16) |
|
||||
((uint32_t)reverse8(b[5]) << 8) |
|
||||
(uint32_t)reverse8(b[4]);
|
||||
}
|
||||
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->generic.btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(5);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
@@ -916,8 +842,17 @@ static uint64_t compute_yek(uint64_t key) {
|
||||
return yek;
|
||||
}
|
||||
|
||||
static bool kia_v3_v4_verify_crc_from_data(uint64_t data, uint8_t received_crc) {
|
||||
uint8_t bytes[8];
|
||||
for(int i = 0; i < 8; i++) {
|
||||
bytes[i] = (data >> ((7-i) * 8)) & 0xFF;
|
||||
}
|
||||
uint8_t calculated_crc = kia_v3_v4_calculate_crc(bytes);
|
||||
return (calculated_crc == received_crc);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_v3_v4_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV3V4* instance = context;
|
||||
|
||||
uint64_t yek = compute_yek(instance->generic.data);
|
||||
@@ -926,42 +861,27 @@ void subghz_protocol_decoder_kia_v3_v4_get_string(void* context, FuriString* out
|
||||
uint32_t yek_hi = (uint32_t)(yek >> 32);
|
||||
uint32_t yek_lo = (uint32_t)(yek & 0xFFFFFFFF);
|
||||
|
||||
if(instance->version == 0) {
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Yek:%08lX%08lX\r\n"
|
||||
"Serial:%07lX Btn:%01X CRC:%01X\r\n"
|
||||
"Decr:%08lX Cnt:%04lX\r\n",
|
||||
kia_version_names[instance->version],
|
||||
instance->generic.data_count_bit,
|
||||
key_hi,
|
||||
key_lo,
|
||||
yek_hi,
|
||||
yek_lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->crc,
|
||||
instance->decrypted,
|
||||
instance->generic.cnt);
|
||||
} else {
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Yek:%08lX%08lX\r\n"
|
||||
"Serial:%07lX Btn:%01X\r\n"
|
||||
"Decr:%08lX Cnt:%04lX\r\n",
|
||||
kia_version_names[instance->version],
|
||||
instance->generic.data_count_bit,
|
||||
key_hi,
|
||||
key_lo,
|
||||
yek_hi,
|
||||
yek_lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
instance->decrypted,
|
||||
instance->generic.cnt);
|
||||
}
|
||||
bool crc_valid = kia_v3_v4_verify_crc_from_data(instance->generic.data, instance->crc);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%08lX%08lX\r\n"
|
||||
"Yek:%08lX%08lX\r\n"
|
||||
"Sn:%07lX Btn:%X [%s]\r\n"
|
||||
"Dec:%08lX Cnt:%04lX\r\n"
|
||||
"CRC:%X %s",
|
||||
kia_version_names[instance->version],
|
||||
instance->generic.data_count_bit,
|
||||
key_hi,
|
||||
key_lo,
|
||||
yek_hi,
|
||||
yek_lo,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
subghz_protocol_kia_v3_v4_get_name_button(instance->generic.btn),
|
||||
instance->decrypted,
|
||||
instance->generic.cnt,
|
||||
instance->crc,
|
||||
crc_valid ? "(OK)" : "(FAIL)");
|
||||
}
|
||||
|
||||
@@ -1,826 +0,0 @@
|
||||
#include "kia_v6.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include "../blocks/custom_btn_i.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
#include <furi_hal_crypto.h>
|
||||
|
||||
#define TAG "SubGhzProtocolKiaV6"
|
||||
|
||||
#define KIA_V6_XOR_MASK_LOW 0x84AF25FB
|
||||
#define KIA_V6_XOR_MASK_HIGH 0x638766AB
|
||||
|
||||
#define KIA_V6_PREAMBLE_PAIRS_1 640
|
||||
#define KIA_V6_PREAMBLE_PAIRS_2 38
|
||||
#define KIA_V6_UPLOAD_SIZE 2000
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_kia_v6_const = {
|
||||
.te_short = 200,
|
||||
.te_long = 400,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 144,
|
||||
};
|
||||
|
||||
static const uint8_t aes_sbox[256] = {
|
||||
0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
|
||||
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
|
||||
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
|
||||
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
|
||||
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
|
||||
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
|
||||
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
|
||||
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
|
||||
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
|
||||
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
|
||||
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
|
||||
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
|
||||
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
|
||||
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
|
||||
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
|
||||
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderKiaV6 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint16_t header_count;
|
||||
ManchesterState manchester_state;
|
||||
|
||||
uint32_t data_part1_low;
|
||||
uint32_t data_part1_high;
|
||||
uint32_t stored_part1_low;
|
||||
uint32_t stored_part1_high;
|
||||
uint32_t stored_part2_low;
|
||||
uint32_t stored_part2_high;
|
||||
uint16_t data_part3;
|
||||
|
||||
uint8_t bit_count;
|
||||
|
||||
uint32_t saved_part1_low;
|
||||
uint32_t saved_part1_high;
|
||||
uint32_t saved_part2_low;
|
||||
uint32_t saved_part2_high;
|
||||
uint16_t saved_part3;
|
||||
|
||||
uint32_t saved_serial;
|
||||
uint8_t saved_btn;
|
||||
uint32_t saved_cnt;
|
||||
uint8_t saved_fx;
|
||||
uint8_t saved_crc1;
|
||||
uint8_t saved_crc2;
|
||||
bool saved_crc_valid;
|
||||
|
||||
uint8_t fx_field;
|
||||
uint8_t crc1_field;
|
||||
uint8_t crc2_field;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderKiaV6 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint32_t stored_part1_low;
|
||||
uint32_t stored_part1_high;
|
||||
uint32_t stored_part2_low;
|
||||
uint32_t stored_part2_high;
|
||||
uint16_t data_part3;
|
||||
uint8_t fx_field;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
KiaV6DecoderStepReset = 0,
|
||||
KiaV6DecoderStepWaitFirstHigh,
|
||||
KiaV6DecoderStepCountPreamble,
|
||||
KiaV6DecoderStepWaitLongHigh,
|
||||
KiaV6DecoderStepData,
|
||||
} KiaV6DecoderStep;
|
||||
|
||||
static uint8_t kia_v6_crc8(uint8_t *data, int len, uint8_t init, uint8_t polynomial) {
|
||||
uint8_t crc = init;
|
||||
uint8_t *pbVar3 = data;
|
||||
while (pbVar3 != data + len) {
|
||||
crc = crc ^ *pbVar3;
|
||||
for (int j = 8; j > 0; j--) {
|
||||
uint8_t bVar1 = (uint8_t)(crc << 1);
|
||||
if ((crc & 0x80) != 0) {
|
||||
bVar1 = bVar1 ^ polynomial;
|
||||
}
|
||||
crc = bVar1;
|
||||
}
|
||||
pbVar3++;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
static uint8_t kia_v6_btn_to_custom(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x01: return 1; // Lock
|
||||
case 0x02: return 2; // Unlock
|
||||
case 0x03: return 3; // Trunk
|
||||
case 0x04: return 4; // Panic
|
||||
default: return 2; // Unknown → Unlock
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t kia_v6_custom_to_btn(uint8_t custom) {
|
||||
switch(custom) {
|
||||
case 1: return 0x01; // Lock
|
||||
case 2: return 0x02; // Unlock
|
||||
case 3: return 0x03; // Trunk
|
||||
case 4: return 0x04; // Panic
|
||||
default: return 0x02;
|
||||
}
|
||||
}
|
||||
|
||||
static void get_kia_v6_aes_key(uint8_t* aes_key) {
|
||||
uint64_t keystore_a = 0x37CE21F8C9F862A8ULL ^ 0x5448455049524154ULL;
|
||||
uint32_t keystore_a_hi = (keystore_a >> 32) & 0xFFFFFFFF;
|
||||
uint32_t keystore_a_lo = keystore_a & 0xFFFFFFFF;
|
||||
uint32_t uVar15_a = keystore_a_lo ^ KIA_V6_XOR_MASK_LOW;
|
||||
uint32_t uVar5_a = KIA_V6_XOR_MASK_HIGH ^ keystore_a_hi;
|
||||
uint64_t val64_a = ((uint64_t)uVar5_a << 32) | uVar15_a;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
aes_key[i] = (val64_a >> (56 - i * 8)) & 0xFF;
|
||||
}
|
||||
uint64_t keystore_b = 0x3FC629F0C1F06AA0ULL ^ 0x5448455049524154ULL;
|
||||
uint32_t keystore_b_hi = (keystore_b >> 32) & 0xFFFFFFFF;
|
||||
uint32_t keystore_b_lo = keystore_b & 0xFFFFFFFF;
|
||||
uint32_t uVar15_b = keystore_b_lo ^ KIA_V6_XOR_MASK_LOW;
|
||||
uint32_t uVar5_b = KIA_V6_XOR_MASK_HIGH ^ keystore_b_hi;
|
||||
uint64_t val64_b = ((uint64_t)uVar5_b << 32) | uVar15_b;
|
||||
for (int i = 0; i < 8; i++) {
|
||||
aes_key[i + 8] = (val64_b >> (56 - i * 8)) & 0xFF;
|
||||
}
|
||||
}
|
||||
|
||||
static bool kia_v6_decrypt(SubGhzProtocolDecoderKiaV6* instance) {
|
||||
uint8_t encrypted_data[16];
|
||||
|
||||
encrypted_data[0] = (instance->stored_part1_high >> 8) & 0xFF;
|
||||
encrypted_data[1] = instance->stored_part1_high & 0xFF;
|
||||
encrypted_data[2] = (instance->stored_part1_low >> 24) & 0xFF;
|
||||
encrypted_data[3] = (instance->stored_part1_low >> 16) & 0xFF;
|
||||
encrypted_data[4] = (instance->stored_part1_low >> 8) & 0xFF;
|
||||
encrypted_data[5] = instance->stored_part1_low & 0xFF;
|
||||
encrypted_data[6] = (instance->stored_part2_high >> 24) & 0xFF;
|
||||
encrypted_data[7] = (instance->stored_part2_high >> 16) & 0xFF;
|
||||
encrypted_data[8] = (instance->stored_part2_high >> 8) & 0xFF;
|
||||
encrypted_data[9] = instance->stored_part2_high & 0xFF;
|
||||
encrypted_data[10] = (instance->stored_part2_low >> 24) & 0xFF;
|
||||
encrypted_data[11] = (instance->stored_part2_low >> 16) & 0xFF;
|
||||
encrypted_data[12] = (instance->stored_part2_low >> 8) & 0xFF;
|
||||
encrypted_data[13] = instance->stored_part2_low & 0xFF;
|
||||
encrypted_data[14] = (instance->data_part3 >> 8) & 0xFF;
|
||||
encrypted_data[15] = instance->data_part3 & 0xFF;
|
||||
|
||||
uint8_t fx_byte0 = (instance->stored_part1_high >> 24) & 0xFF;
|
||||
uint8_t fx_byte1 = (instance->stored_part1_high >> 16) & 0xFF;
|
||||
instance->fx_field = ((fx_byte0 & 0xF) << 4) | (fx_byte1 & 0xF);
|
||||
|
||||
uint8_t aes_key[16];
|
||||
get_kia_v6_aes_key(aes_key);
|
||||
uint8_t decrypted_buf[16];
|
||||
furi_hal_crypto_aes128_ecb_decrypt(aes_key, encrypted_data, decrypted_buf);
|
||||
memcpy(encrypted_data, decrypted_buf, 16);
|
||||
|
||||
uint8_t *decrypted = encrypted_data;
|
||||
uint8_t calculated_crc = kia_v6_crc8(decrypted, 15, 0xFF, 0x07);
|
||||
uint8_t stored_crc = decrypted[15];
|
||||
|
||||
instance->generic.serial = ((uint32_t)decrypted[4] << 16) |
|
||||
((uint32_t)decrypted[5] << 8) |
|
||||
decrypted[6];
|
||||
instance->generic.btn = decrypted[7];
|
||||
instance->generic.cnt = ((uint32_t)decrypted[8] << 24) |
|
||||
((uint32_t)decrypted[9] << 16) |
|
||||
((uint32_t)decrypted[10] << 8) |
|
||||
decrypted[11];
|
||||
instance->crc1_field = decrypted[12];
|
||||
instance->crc2_field = decrypted[15];
|
||||
|
||||
bool crc_valid = (calculated_crc ^ stored_crc) < 2;
|
||||
|
||||
instance->saved_serial = instance->generic.serial;
|
||||
instance->saved_btn = instance->generic.btn;
|
||||
instance->saved_cnt = instance->generic.cnt;
|
||||
instance->saved_fx = instance->fx_field;
|
||||
instance->saved_crc1 = instance->crc1_field;
|
||||
instance->saved_crc2 = instance->crc2_field;
|
||||
instance->saved_crc_valid = crc_valid;
|
||||
instance->saved_part1_low = instance->stored_part1_low;
|
||||
instance->saved_part1_high = instance->stored_part1_high;
|
||||
instance->saved_part2_low = instance->stored_part2_low;
|
||||
instance->saved_part2_high = instance->stored_part2_high;
|
||||
instance->saved_part3 = instance->data_part3;
|
||||
|
||||
return crc_valid;
|
||||
}
|
||||
|
||||
static void kia_v6_encrypt_payload(
|
||||
uint8_t fx_field,
|
||||
uint32_t serial,
|
||||
uint8_t btn,
|
||||
uint32_t cnt,
|
||||
uint32_t* out_part1_low,
|
||||
uint32_t* out_part1_high,
|
||||
uint32_t* out_part2_low,
|
||||
uint32_t* out_part2_high,
|
||||
uint16_t* out_part3) {
|
||||
uint8_t plain[16];
|
||||
memset(plain, 0, 16);
|
||||
plain[0] = fx_field;
|
||||
plain[4] = (serial >> 16) & 0xFF;
|
||||
plain[5] = (serial >> 8) & 0xFF;
|
||||
plain[6] = serial & 0xFF;
|
||||
plain[7] = btn & 0x0F;
|
||||
plain[8] = (cnt >> 24) & 0xFF;
|
||||
plain[9] = (cnt >> 16) & 0xFF;
|
||||
plain[10] = (cnt >> 8) & 0xFF;
|
||||
plain[11] = cnt & 0xFF;
|
||||
plain[12] = aes_sbox[cnt & 0xFF];
|
||||
plain[15] = kia_v6_crc8(plain, 15, 0xFF, 0x07);
|
||||
|
||||
uint8_t aes_key[16];
|
||||
get_kia_v6_aes_key(aes_key);
|
||||
uint8_t encrypted[16];
|
||||
furi_hal_crypto_aes128_ecb_encrypt(aes_key, plain, encrypted);
|
||||
memcpy(plain, encrypted, 16);
|
||||
|
||||
uint8_t fx_hi = 0x20 | (fx_field >> 4);
|
||||
uint8_t fx_lo = fx_field & 0x0F;
|
||||
*out_part1_high = ((uint32_t)fx_hi << 24) | ((uint32_t)fx_lo << 16) |
|
||||
((uint32_t)plain[0] << 8) | plain[1];
|
||||
*out_part1_low = ((uint32_t)plain[2] << 24) | ((uint32_t)plain[3] << 16) |
|
||||
((uint32_t)plain[4] << 8) | plain[5];
|
||||
*out_part2_high = ((uint32_t)plain[6] << 24) | ((uint32_t)plain[7] << 16) |
|
||||
((uint32_t)plain[8] << 8) | plain[9];
|
||||
*out_part2_low = ((uint32_t)plain[10] << 24) | ((uint32_t)plain[11] << 16) |
|
||||
((uint32_t)plain[12] << 8) | plain[13];
|
||||
*out_part3 = ((uint16_t)plain[14] << 8) | plain[15];
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_kia_v6_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderKiaV6* instance = malloc(sizeof(SubGhzProtocolDecoderKiaV6));
|
||||
instance->base.protocol = &subghz_protocol_kia_v6;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_v6_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_v6_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
instance->decoder.parser_step = KiaV6DecoderStepReset;
|
||||
instance->header_count = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->data_part1_low = 0;
|
||||
instance->data_part1_high = 0;
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_v6_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case KiaV6DecoderStepReset:
|
||||
if(level == 0) return;
|
||||
if(DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_short) < subghz_protocol_kia_v6_const.te_delta) {
|
||||
instance->decoder.parser_step = KiaV6DecoderStepWaitFirstHigh;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count = 0;
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
}
|
||||
return;
|
||||
|
||||
case KiaV6DecoderStepWaitFirstHigh:
|
||||
if(level != 0) return;
|
||||
uint32_t diff_short = DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_short);
|
||||
uint32_t diff_long = DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_long);
|
||||
uint32_t diff = (diff_long < diff_short) ? diff_long : diff_short;
|
||||
|
||||
if(diff_long < subghz_protocol_kia_v6_const.te_delta && diff_long < diff_short) {
|
||||
if(instance->header_count >= 0x259) {
|
||||
instance->header_count = 0;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = KiaV6DecoderStepWaitLongHigh;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(diff >= subghz_protocol_kia_v6_const.te_delta) {
|
||||
instance->decoder.parser_step = KiaV6DecoderStepReset;
|
||||
return;
|
||||
}
|
||||
if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_v6_const.te_short) < subghz_protocol_kia_v6_const.te_delta) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->header_count++;
|
||||
return;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV6DecoderStepReset;
|
||||
return;
|
||||
}
|
||||
|
||||
case KiaV6DecoderStepWaitLongHigh:
|
||||
if(level == 0) {
|
||||
instance->decoder.parser_step = KiaV6DecoderStepReset;
|
||||
return;
|
||||
}
|
||||
uint32_t diff_long_check = DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_long);
|
||||
uint32_t diff_short_check = DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_short);
|
||||
if(diff_long_check >= subghz_protocol_kia_v6_const.te_delta) {
|
||||
if(diff_short_check >= subghz_protocol_kia_v6_const.te_delta) {
|
||||
instance->decoder.parser_step = KiaV6DecoderStepReset;
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_kia_v6_const.te_long) >= subghz_protocol_kia_v6_const.te_delta) {
|
||||
instance->decoder.parser_step = KiaV6DecoderStepReset;
|
||||
return;
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->data_part1_low = (uint32_t)(instance->decoder.decode_data & 0xFFFFFFFF);
|
||||
instance->data_part1_high = (uint32_t)((instance->decoder.decode_data >> 32) & 0xFFFFFFFF);
|
||||
instance->bit_count = instance->decoder.decode_count_bit;
|
||||
instance->decoder.parser_step = KiaV6DecoderStepData;
|
||||
return;
|
||||
|
||||
case KiaV6DecoderStepData: {
|
||||
ManchesterEvent event;
|
||||
bool data_bit;
|
||||
|
||||
if(DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_short) < subghz_protocol_kia_v6_const.te_delta) {
|
||||
event = (level & 0x7F) << 1;
|
||||
} else if(DURATION_DIFF(duration, subghz_protocol_kia_v6_const.te_long) < subghz_protocol_kia_v6_const.te_delta) {
|
||||
event = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV6DecoderStepReset;
|
||||
return;
|
||||
}
|
||||
|
||||
if(manchester_advance(instance->manchester_state, event, &instance->manchester_state, &data_bit)) {
|
||||
uint32_t uVar4 = instance->data_part1_low;
|
||||
uint32_t uVar5 = (uVar4 << 1) | (data_bit ? 1 : 0);
|
||||
uint32_t carry = (uVar4 >> 31) & 1;
|
||||
uVar4 = (instance->data_part1_high << 1) | carry;
|
||||
instance->data_part1_low = uVar5;
|
||||
instance->data_part1_high = uVar4;
|
||||
instance->decoder.decode_data = ((uint64_t)uVar4 << 32) | uVar5;
|
||||
instance->bit_count++;
|
||||
|
||||
if(instance->bit_count == 0x40) {
|
||||
instance->stored_part1_low = ~uVar5;
|
||||
instance->stored_part1_high = ~uVar4;
|
||||
instance->data_part1_low = 0;
|
||||
instance->data_part1_high = 0;
|
||||
} else if(instance->bit_count == 0x80) {
|
||||
instance->stored_part2_low = ~uVar5;
|
||||
instance->stored_part2_high = ~uVar4;
|
||||
instance->data_part1_low = 0;
|
||||
instance->data_part1_high = 0;
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder.te_last = duration;
|
||||
|
||||
if(instance->bit_count != subghz_protocol_kia_v6_const.min_count_bit_for_found) {
|
||||
return;
|
||||
}
|
||||
|
||||
instance->generic.data_count_bit = subghz_protocol_kia_v6_const.min_count_bit_for_found;
|
||||
instance->data_part3 = ~((uint16_t)instance->data_part1_low);
|
||||
instance->generic.data = ((uint64_t)instance->stored_part1_high << 32) | instance->stored_part1_low;
|
||||
|
||||
kia_v6_decrypt(instance);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
instance->data_part1_low = 0;
|
||||
instance->data_part1_high = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->decoder.parser_step = KiaV6DecoderStepReset;
|
||||
return;
|
||||
}
|
||||
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_kia_v6_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
uint8_t hash = 0;
|
||||
hash ^= (instance->stored_part1_low >> 24) & 0xFF;
|
||||
hash ^= (instance->stored_part1_low >> 16) & 0xFF;
|
||||
hash ^= (instance->stored_part1_low >> 8) & 0xFF;
|
||||
hash ^= instance->stored_part1_low & 0xFF;
|
||||
hash ^= (instance->stored_part1_high >> 24) & 0xFF;
|
||||
hash ^= (instance->stored_part1_high >> 16) & 0xFF;
|
||||
hash ^= (instance->stored_part1_high >> 8) & 0xFF;
|
||||
hash ^= instance->stored_part1_high & 0xFF;
|
||||
return hash;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_kia_v6_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t key2_low = instance->stored_part2_low;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Key_2", &key2_low, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t key2_high = instance->stored_part2_high;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Key_3", &key2_high, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t key3 = instance->data_part3;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Key_4", &key3, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t fx = instance->fx_field;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Fx", &fx, 1)) {
|
||||
ret = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_kia_v6_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
if(instance->generic.data_count_bit < subghz_protocol_kia_v6_const.min_count_bit_for_found) {
|
||||
ret = SubGhzProtocolStatusErrorParserBitCount;
|
||||
}
|
||||
}
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t temp;
|
||||
if(flipper_format_read_uint32(flipper_format, "Key_2", &temp, 1)) {
|
||||
instance->stored_part2_low = temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Key_3", &temp, 1)) {
|
||||
instance->stored_part2_high = temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Key_4", &temp, 1)) {
|
||||
instance->data_part3 = (uint16_t)temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Fx", &temp, 1)) {
|
||||
instance->fx_field = (uint8_t)temp;
|
||||
}
|
||||
instance->stored_part1_high = (uint32_t)((instance->generic.data >> 32) & 0xFFFFFFFF);
|
||||
instance->stored_part1_low = (uint32_t)(instance->generic.data & 0xFFFFFFFF);
|
||||
kia_v6_decrypt(instance);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_kia_v6_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderKiaV6* instance = context;
|
||||
|
||||
uint32_t key1_hi = instance->saved_part1_high;
|
||||
uint32_t key1_lo = instance->saved_part1_low;
|
||||
uint32_t key2_hi = instance->saved_part2_high;
|
||||
uint32_t key2_lo = instance->saved_part2_low;
|
||||
|
||||
uint32_t key2_uVar4 = key2_hi << 16;
|
||||
uint32_t key2_uVar2 = key2_lo >> 16;
|
||||
uint32_t key2_uVar1 = key2_hi >> 16;
|
||||
uint32_t key2_combined = key2_uVar4 | key2_uVar2;
|
||||
uint32_t key2_uVar3 = key2_lo << 16;
|
||||
uint32_t key2_second = (instance->saved_part3 & 0xFFFF) | key2_uVar3;
|
||||
uint32_t serial_6 = instance->saved_serial & 0xFFFFFF;
|
||||
|
||||
const char* crc_status = instance->saved_crc_valid ? "(OK)" : "(FAIL)";
|
||||
|
||||
const char* btn_name;
|
||||
switch(instance->saved_btn & 0x0F) {
|
||||
case 0x01: btn_name = "Lock"; break;
|
||||
case 0x02: btn_name = "Unlock"; break;
|
||||
case 0x03: btn_name = "Trunk"; break;
|
||||
case 0x04: btn_name = "Panic"; break;
|
||||
default: btn_name = "Unknown"; break;
|
||||
}
|
||||
|
||||
furi_string_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"%08lX%08lX%04lX\r\n"
|
||||
"%08lX%08lX Fx:%02X\r\n"
|
||||
"Ser:%06lX Btn:%01X[%s]\r\n"
|
||||
"Cnt:%08lX CRC:%02X-%02X\r\n"
|
||||
"CRC %s",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
key1_hi,
|
||||
key1_lo,
|
||||
key2_uVar1,
|
||||
key2_combined,
|
||||
key2_second,
|
||||
instance->saved_fx,
|
||||
serial_6,
|
||||
instance->saved_btn & 0x0F,
|
||||
btn_name,
|
||||
instance->saved_cnt,
|
||||
instance->saved_crc1,
|
||||
instance->saved_crc2,
|
||||
crc_status);
|
||||
}
|
||||
|
||||
static inline void kia_v6_encode_manchester_bit(
|
||||
LevelDuration* upload,
|
||||
size_t* index,
|
||||
bool bit,
|
||||
uint32_t te) {
|
||||
if(bit) {
|
||||
upload[(*index)++] = level_duration_make(false, te);
|
||||
upload[(*index)++] = level_duration_make(true, te);
|
||||
} else {
|
||||
upload[(*index)++] = level_duration_make(true, te);
|
||||
upload[(*index)++] = level_duration_make(false, te);
|
||||
}
|
||||
}
|
||||
|
||||
static void kia_v6_encode_message(
|
||||
LevelDuration* upload,
|
||||
size_t* index,
|
||||
int preamble_pairs,
|
||||
uint32_t p1_lo,
|
||||
uint32_t p1_hi,
|
||||
uint32_t p2_lo,
|
||||
uint32_t p2_hi,
|
||||
uint16_t p3) {
|
||||
const uint32_t te_short = subghz_protocol_kia_v6_const.te_short;
|
||||
|
||||
for(int i = 0; i < preamble_pairs; i++) {
|
||||
upload[(*index)++] = level_duration_make(true, te_short);
|
||||
upload[(*index)++] = level_duration_make(false, te_short);
|
||||
}
|
||||
upload[(*index)++] = level_duration_make(false, te_short);
|
||||
upload[(*index)++] = level_duration_make(true, subghz_protocol_kia_v6_const.te_long);
|
||||
upload[(*index)++] = level_duration_make(false, te_short);
|
||||
|
||||
for(int b = 60; b >= 0; b--) {
|
||||
uint32_t word = (b >= 32) ? p1_hi : p1_lo;
|
||||
int shift = (b >= 32) ? (b - 32) : b;
|
||||
kia_v6_encode_manchester_bit(upload, index, ((~word) >> shift) & 1, te_short);
|
||||
}
|
||||
for(int b = 63; b >= 0; b--) {
|
||||
uint32_t word = (b >= 32) ? p2_hi : p2_lo;
|
||||
int shift = (b >= 32) ? (b - 32) : b;
|
||||
kia_v6_encode_manchester_bit(upload, index, ((~word) >> shift) & 1, te_short);
|
||||
}
|
||||
for(int b = 15; b >= 0; b--) {
|
||||
kia_v6_encode_manchester_bit(upload, index, ((~p3) >> b) & 1, te_short);
|
||||
}
|
||||
}
|
||||
|
||||
// FIX: build_upload usa direttamente instance->generic.btn, già risolto dal chiamante
|
||||
static void kia_v6_encoder_build_upload(SubGhzProtocolEncoderKiaV6* instance) {
|
||||
furi_assert(instance);
|
||||
|
||||
kia_v6_encrypt_payload(
|
||||
instance->fx_field,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn & 0x0F,
|
||||
instance->generic.cnt,
|
||||
&instance->stored_part1_low,
|
||||
&instance->stored_part1_high,
|
||||
&instance->stored_part2_low,
|
||||
&instance->stored_part2_high,
|
||||
&instance->data_part3);
|
||||
|
||||
uint32_t p1_lo = instance->stored_part1_low;
|
||||
uint32_t p1_hi = instance->stored_part1_high;
|
||||
uint32_t p2_lo = instance->stored_part2_low;
|
||||
uint32_t p2_hi = instance->stored_part2_high;
|
||||
uint16_t p3 = instance->data_part3;
|
||||
|
||||
size_t index = 0;
|
||||
kia_v6_encode_message(instance->encoder.upload, &index, KIA_V6_PREAMBLE_PAIRS_1, p1_lo, p1_hi, p2_lo, p2_hi, p3);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, subghz_protocol_kia_v6_const.te_long);
|
||||
kia_v6_encode_message(instance->encoder.upload, &index, KIA_V6_PREAMBLE_PAIRS_2, p1_lo, p1_hi, p2_lo, p2_hi, p3);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, subghz_protocol_kia_v6_const.te_long);
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_kia_v6_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderKiaV6* instance = malloc(sizeof(SubGhzProtocolEncoderKiaV6));
|
||||
if(!instance) return NULL;
|
||||
memset(instance, 0, sizeof(SubGhzProtocolEncoderKiaV6));
|
||||
instance->base.protocol = &subghz_protocol_kia_v6;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.size_upload = KIA_V6_UPLOAD_SIZE;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
if(!instance->encoder.upload) {
|
||||
free(instance);
|
||||
return NULL;
|
||||
}
|
||||
instance->encoder.repeat = 3;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = false;
|
||||
subghz_custom_btn_set_max(4);
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v6_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV6* instance = context;
|
||||
if(instance->encoder.upload) {
|
||||
free(instance->encoder.upload);
|
||||
}
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_kia_v6_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV6* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = 3;
|
||||
|
||||
do {
|
||||
SubGhzProtocolDecoderKiaV6 dec = {0};
|
||||
|
||||
ret = subghz_block_generic_deserialize(&dec.generic, flipper_format);
|
||||
if(ret != SubGhzProtocolStatusOk) break;
|
||||
if(dec.generic.data_count_bit < subghz_protocol_kia_v6_const.min_count_bit_for_found) {
|
||||
ret = SubGhzProtocolStatusErrorParserBitCount;
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t temp;
|
||||
if(flipper_format_read_uint32(flipper_format, "Key_2", &temp, 1)) {
|
||||
dec.stored_part2_low = temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Key_3", &temp, 1)) {
|
||||
dec.stored_part2_high = temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Key_4", &temp, 1)) {
|
||||
dec.data_part3 = (uint16_t)temp;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Fx", &temp, 1)) {
|
||||
dec.fx_field = (uint8_t)temp;
|
||||
}
|
||||
|
||||
dec.stored_part1_high = (uint32_t)((dec.generic.data >> 32) & 0xFFFFFFFF);
|
||||
dec.stored_part1_low = (uint32_t)(dec.generic.data & 0xFFFFFFFF);
|
||||
|
||||
kia_v6_decrypt(&dec);
|
||||
|
||||
instance->generic.serial = dec.generic.serial;
|
||||
instance->generic.btn = dec.generic.btn;
|
||||
instance->generic.cnt = dec.generic.cnt;
|
||||
instance->generic.data_count_bit = subghz_protocol_kia_v6_const.min_count_bit_for_found;
|
||||
instance->fx_field = dec.fx_field;
|
||||
|
||||
// Salva il btn originale come indice custom 1-4 (identico a Suzuki)
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(kia_v6_btn_to_custom(instance->generic.btn));
|
||||
}
|
||||
subghz_custom_btn_set_max(4);
|
||||
|
||||
// Incrementa cnt
|
||||
if(instance->generic.cnt < 0xFFFFFFFF) {
|
||||
if((instance->generic.cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFFFFFF) {
|
||||
instance->generic.cnt = 0;
|
||||
} else {
|
||||
instance->generic.cnt += furi_hal_subghz_get_rolling_counter_mult();
|
||||
}
|
||||
} else {
|
||||
instance->generic.cnt = 0;
|
||||
}
|
||||
|
||||
// Risolvi btn dal tasto premuto (identico a Suzuki)
|
||||
instance->generic.btn = kia_v6_custom_to_btn(
|
||||
subghz_custom_btn_get() == SUBGHZ_CUSTOM_BTN_OK
|
||||
? subghz_custom_btn_get_original()
|
||||
: subghz_custom_btn_get());
|
||||
|
||||
kia_v6_encoder_build_upload(instance);
|
||||
|
||||
// Aggiorna il file .sub con i nuovi valori cifrati
|
||||
uint32_t file_part1_low, file_part1_high, file_part2_low, file_part2_high;
|
||||
uint16_t file_part3;
|
||||
kia_v6_encrypt_payload(
|
||||
instance->fx_field,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn & 0x0F,
|
||||
instance->generic.cnt,
|
||||
&file_part1_low,
|
||||
&file_part1_high,
|
||||
&file_part2_low,
|
||||
&file_part2_high,
|
||||
&file_part3);
|
||||
|
||||
uint64_t new_key = ((uint64_t)file_part1_high << 32) | file_part1_low;
|
||||
uint8_t key_data[sizeof(uint64_t)];
|
||||
for(size_t i = 0; i < sizeof(uint64_t); i++) {
|
||||
key_data[sizeof(uint64_t) - i - 1] = (new_key >> (i * 8)) & 0xFF;
|
||||
}
|
||||
if(!flipper_format_rewind(flipper_format)) break;
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(uint64_t))) {
|
||||
ret = SubGhzProtocolStatusErrorParserKey;
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_update_uint32(flipper_format, "Key_2", &file_part2_low, 1);
|
||||
flipper_format_update_uint32(flipper_format, "Key_3", &file_part2_high, 1);
|
||||
uint32_t file_key3 = file_part3;
|
||||
flipper_format_update_uint32(flipper_format, "Key_4", &file_key3, 1);
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_kia_v6_stop(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV6* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_kia_v6_yield(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderKiaV6* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat <= 0) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration ret = instance->encoder.upload[instance->encoder.front];
|
||||
instance->encoder.front++;
|
||||
if(instance->encoder.front >= instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_kia_v6_decoder = {
|
||||
.alloc = subghz_protocol_decoder_kia_v6_alloc,
|
||||
.free = subghz_protocol_decoder_kia_v6_free,
|
||||
.feed = subghz_protocol_decoder_kia_v6_feed,
|
||||
.reset = subghz_protocol_decoder_kia_v6_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_kia_v6_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_kia_v6_serialize,
|
||||
.deserialize = subghz_protocol_decoder_kia_v6_deserialize,
|
||||
.get_string = subghz_protocol_decoder_kia_v6_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_kia_v6_encoder = {
|
||||
.alloc = subghz_protocol_encoder_kia_v6_alloc,
|
||||
.free = subghz_protocol_encoder_kia_v6_free,
|
||||
.deserialize = subghz_protocol_encoder_kia_v6_deserialize,
|
||||
.stop = subghz_protocol_encoder_kia_v6_stop,
|
||||
.yield = subghz_protocol_encoder_kia_v6_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_kia_v6 = {
|
||||
.name = SUBGHZ_PROTOCOL_KIA_V6_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_kia_v6_decoder,
|
||||
.encoder = &subghz_protocol_kia_v6_encoder,
|
||||
};
|
||||
+416
-682
File diff suppressed because it is too large
Load Diff
@@ -1,711 +0,0 @@
|
||||
#include "kia_v7.h"
|
||||
#include <string.h>
|
||||
|
||||
#define KIA_V7_UPLOAD_CAPACITY 0x3A4
|
||||
#define KIA_V7_PREAMBLE_PAIRS 0x13F
|
||||
#define KIA_V7_PREAMBLE_MIN_PAIRS 16
|
||||
#define KIA_V7_HEADER 0x4C
|
||||
#define KIA_V7_TAIL_GAP_US 0x7D0
|
||||
#define KIA_V7_KEY_BITS 64U
|
||||
#define KIA_V7_DEFAULT_TX_REPEAT 10U
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_kia_v7_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = KIA_V7_KEY_BITS,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
KiaV7DecoderStepReset = 0,
|
||||
KiaV7DecoderStepPreamble = 1,
|
||||
KiaV7DecoderStepSyncLow = 2,
|
||||
KiaV7DecoderStepData = 3,
|
||||
} KiaV7DecoderStep;
|
||||
|
||||
struct SubGhzProtocolDecoderKiaV7 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint16_t preamble_count;
|
||||
|
||||
uint8_t decoded_button;
|
||||
uint8_t fixed_high_byte;
|
||||
uint8_t crc_calculated;
|
||||
uint8_t crc_raw;
|
||||
bool crc_valid;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderKiaV7 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t tx_bit_count;
|
||||
uint8_t decoded_button;
|
||||
uint8_t fixed_high_byte;
|
||||
uint8_t crc_calculated;
|
||||
uint8_t crc_raw;
|
||||
bool crc_valid;
|
||||
};
|
||||
|
||||
static uint8_t kia_v7_crc8(const uint8_t* data, size_t len) {
|
||||
uint8_t crc = 0x4CU;
|
||||
|
||||
for(size_t index = 0; index < len; index++) {
|
||||
crc ^= data[index];
|
||||
for(uint8_t bit = 0; bit < 8; bit++) {
|
||||
const bool msb = (crc & 0x80U) != 0U;
|
||||
crc <<= 1U;
|
||||
if(msb) {
|
||||
crc ^= 0x7FU;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
||||
static void kia_v7_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
|
||||
for(size_t index = 0; index < 8; index++) {
|
||||
bytes[index] = (data >> ((7U - index) * 8U)) & 0xFFU;
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t kia_v7_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
uint64_t data = 0;
|
||||
|
||||
for(size_t index = 0; index < 8; index++) {
|
||||
data = (data << 8U) | bytes[index];
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static bool kia_v7_is_short(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, subghz_protocol_kia_v7_const.te_short) <
|
||||
subghz_protocol_kia_v7_const.te_delta;
|
||||
}
|
||||
|
||||
static bool kia_v7_is_long(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, subghz_protocol_kia_v7_const.te_long) <
|
||||
subghz_protocol_kia_v7_const.te_delta;
|
||||
}
|
||||
|
||||
static const char* kia_v7_get_button_name(uint8_t button) {
|
||||
switch(button) {
|
||||
case 0x01:
|
||||
return "LOCK";
|
||||
case 0x02:
|
||||
return "UNLOCK";
|
||||
case 0x03:
|
||||
case 0x08:
|
||||
return "BOOT";
|
||||
default:
|
||||
return "??";
|
||||
}
|
||||
}
|
||||
|
||||
static SubGhzProtocolStatus
|
||||
kia_v7_write_display(FlipperFormat* flipper_format, const char* protocol_name, uint8_t button) {
|
||||
SubGhzProtocolStatus status = SubGhzProtocolStatusOk;
|
||||
FuriString* display = furi_string_alloc();
|
||||
|
||||
furi_string_printf(display, "%s - %s", protocol_name, kia_v7_get_button_name(button));
|
||||
|
||||
if(!flipper_format_write_string_cstr(flipper_format, "Disp", furi_string_get_cstr(display))) {
|
||||
status = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
furi_string_free(display);
|
||||
return status;
|
||||
}
|
||||
|
||||
static void kia_v7_decode_key_common(
|
||||
SubGhzBlockGeneric* generic,
|
||||
uint8_t* decoded_button,
|
||||
uint8_t* fixed_high_byte,
|
||||
uint8_t* crc_calculated,
|
||||
uint8_t* crc_raw,
|
||||
bool* crc_valid) {
|
||||
uint8_t bytes[8];
|
||||
kia_v7_u64_to_bytes_be(generic->data, bytes);
|
||||
|
||||
const uint32_t serial = (((uint32_t)bytes[3]) << 20U) | (((uint32_t)bytes[4]) << 12U) |
|
||||
(((uint32_t)bytes[5]) << 4U) | (((uint32_t)bytes[6]) >> 4U);
|
||||
const uint16_t counter = ((uint16_t)bytes[1] << 8U) | (uint16_t)bytes[2];
|
||||
const uint8_t button = bytes[6] & 0x0FU;
|
||||
const uint8_t crc_calc = kia_v7_crc8(bytes, 7);
|
||||
const uint8_t crc_pkt = bytes[7];
|
||||
|
||||
generic->serial = serial & 0x0FFFFFFFU;
|
||||
generic->btn = button;
|
||||
generic->cnt = counter;
|
||||
generic->data_count_bit = KIA_V7_KEY_BITS;
|
||||
|
||||
if(decoded_button) {
|
||||
*decoded_button = button;
|
||||
}
|
||||
if(fixed_high_byte) {
|
||||
*fixed_high_byte = bytes[0];
|
||||
}
|
||||
if(crc_calculated) {
|
||||
*crc_calculated = crc_calc;
|
||||
}
|
||||
if(crc_raw) {
|
||||
*crc_raw = crc_pkt;
|
||||
}
|
||||
if(crc_valid) {
|
||||
*crc_valid = (crc_calc == crc_pkt);
|
||||
}
|
||||
}
|
||||
|
||||
static void kia_v7_decode_key_decoder(SubGhzProtocolDecoderKiaV7* instance) {
|
||||
kia_v7_decode_key_common(
|
||||
&instance->generic,
|
||||
&instance->decoded_button,
|
||||
&instance->fixed_high_byte,
|
||||
&instance->crc_calculated,
|
||||
&instance->crc_raw,
|
||||
&instance->crc_valid);
|
||||
}
|
||||
|
||||
static uint64_t kia_v7_encode_key(
|
||||
uint8_t fixed_high_byte,
|
||||
uint32_t serial,
|
||||
uint8_t button,
|
||||
uint16_t counter,
|
||||
uint8_t* crc_out) {
|
||||
uint8_t bytes[8];
|
||||
|
||||
serial &= 0x0FFFFFFFU;
|
||||
button &= 0x0FU;
|
||||
|
||||
bytes[0] = fixed_high_byte;
|
||||
bytes[1] = (counter >> 8U) & 0xFFU;
|
||||
bytes[2] = counter & 0xFFU;
|
||||
bytes[3] = (serial >> 20U) & 0xFFU;
|
||||
bytes[4] = (serial >> 12U) & 0xFFU;
|
||||
bytes[5] = (serial >> 4U) & 0xFFU;
|
||||
bytes[6] = ((serial & 0x0FU) << 4U) | button;
|
||||
bytes[7] = kia_v7_crc8(bytes, 7);
|
||||
|
||||
if(crc_out) {
|
||||
*crc_out = bytes[7];
|
||||
}
|
||||
|
||||
return kia_v7_bytes_to_u64_be(bytes);
|
||||
}
|
||||
|
||||
static void kia_v7_decode_key_encoder(SubGhzProtocolEncoderKiaV7* instance) {
|
||||
kia_v7_decode_key_common(
|
||||
&instance->generic,
|
||||
&instance->decoded_button,
|
||||
&instance->fixed_high_byte,
|
||||
&instance->crc_calculated,
|
||||
&instance->crc_raw,
|
||||
&instance->crc_valid);
|
||||
}
|
||||
|
||||
static bool kia_v7_encoder_get_upload(SubGhzProtocolEncoderKiaV7* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
const LevelDuration high_short =
|
||||
level_duration_make(true, subghz_protocol_kia_v7_const.te_short);
|
||||
const LevelDuration low_short =
|
||||
level_duration_make(false, subghz_protocol_kia_v7_const.te_short);
|
||||
const LevelDuration low_tail = level_duration_make(false, KIA_V7_TAIL_GAP_US);
|
||||
const size_t max_size = KIA_V7_UPLOAD_CAPACITY;
|
||||
|
||||
const uint8_t bit_count = (instance->tx_bit_count > 0U && instance->tx_bit_count <= 64U) ?
|
||||
instance->tx_bit_count :
|
||||
64U;
|
||||
|
||||
size_t final_size = 0;
|
||||
|
||||
for(uint8_t pass = 0; pass < 2; pass++) {
|
||||
size_t index = pass;
|
||||
|
||||
for(size_t i = 0; i < KIA_V7_PREAMBLE_PAIRS; i++) {
|
||||
if((index + 2U) > max_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->encoder.upload[index++] = high_short;
|
||||
instance->encoder.upload[index++] = low_short;
|
||||
}
|
||||
|
||||
if((index + 1U) > max_size) {
|
||||
return false;
|
||||
}
|
||||
instance->encoder.upload[index++] = high_short;
|
||||
|
||||
for(int32_t bit = (int32_t)bit_count - 1; bit >= 0; bit--) {
|
||||
if((index + 2U) > max_size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool value = ((instance->generic.data >> bit) & 1ULL) != 0ULL;
|
||||
instance->encoder.upload[index++] = value ? high_short : low_short;
|
||||
instance->encoder.upload[index++] = value ? low_short : high_short;
|
||||
}
|
||||
|
||||
if((index + 2U) > max_size) {
|
||||
return false;
|
||||
}
|
||||
instance->encoder.upload[index++] = high_short;
|
||||
instance->encoder.upload[index++] = low_tail;
|
||||
|
||||
final_size = index;
|
||||
}
|
||||
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.size_upload = final_size;
|
||||
return true;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_kia_v7_decoder = {
|
||||
.alloc = kia_protocol_decoder_v7_alloc,
|
||||
.free = kia_protocol_decoder_v7_free,
|
||||
.feed = kia_protocol_decoder_v7_feed,
|
||||
.reset = kia_protocol_decoder_v7_reset,
|
||||
.get_hash_data = kia_protocol_decoder_v7_get_hash_data,
|
||||
.serialize = kia_protocol_decoder_v7_serialize,
|
||||
.deserialize = kia_protocol_decoder_v7_deserialize,
|
||||
.get_string = kia_protocol_decoder_v7_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_kia_v7_encoder = {
|
||||
.alloc = kia_protocol_encoder_v7_alloc,
|
||||
.free = kia_protocol_encoder_v7_free,
|
||||
.deserialize = kia_protocol_encoder_v7_deserialize,
|
||||
.stop = kia_protocol_encoder_v7_stop,
|
||||
.yield = kia_protocol_encoder_v7_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_kia_v7 = {
|
||||
.name = KIA_PROTOCOL_V7_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_kia_v7_decoder,
|
||||
.encoder = &subghz_protocol_kia_v7_encoder,
|
||||
};
|
||||
|
||||
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolEncoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV7));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &subghz_protocol_kia_v7;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 1;
|
||||
instance->encoder.size_upload = KIA_V7_UPLOAD_CAPACITY;
|
||||
instance->encoder.upload = malloc(KIA_V7_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void kia_protocol_encoder_v7_free(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderKiaV7* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v7_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolEncoderKiaV7* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = KIA_V7_DEFAULT_TX_REPEAT;
|
||||
|
||||
do {
|
||||
FuriString* temp_str = furi_string_alloc();
|
||||
if(!temp_str) {
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_string(flipper_format, "Protocol", temp_str)) {
|
||||
furi_string_free(temp_str);
|
||||
break;
|
||||
}
|
||||
|
||||
if(!furi_string_equal(temp_str, instance->base.protocol->name)) {
|
||||
furi_string_free(temp_str);
|
||||
break;
|
||||
}
|
||||
furi_string_free(temp_str);
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
SubGhzProtocolStatus load_st = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, KIA_V7_KEY_BITS);
|
||||
if(load_st != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
instance->tx_bit_count =
|
||||
(instance->generic.data_count_bit > 0U && instance->generic.data_count_bit <= 64U) ?
|
||||
(uint8_t)instance->generic.data_count_bit :
|
||||
64U;
|
||||
|
||||
kia_v7_decode_key_encoder(instance);
|
||||
|
||||
uint32_t u32 = 0;
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Serial", &u32, 1)) {
|
||||
instance->generic.serial = u32;
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &u32, 1)) {
|
||||
instance->generic.btn = (uint8_t)u32;
|
||||
}
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &u32, 1)) {
|
||||
instance->generic.cnt = (uint16_t)u32;
|
||||
}
|
||||
|
||||
instance->generic.btn &= 0x0FU;
|
||||
instance->generic.cnt &= 0xFFFFU;
|
||||
instance->generic.serial &= 0x0FFFFFFFU;
|
||||
|
||||
instance->generic.data = kia_v7_encode_key(
|
||||
instance->fixed_high_byte,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
(uint16_t)instance->generic.cnt,
|
||||
&instance->crc_calculated);
|
||||
instance->generic.data_count_bit = KIA_V7_KEY_BITS;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(flipper_format, "Repeat", &u32, 1)) {
|
||||
u32 = KIA_V7_DEFAULT_TX_REPEAT;
|
||||
}
|
||||
instance->encoder.repeat = u32;
|
||||
|
||||
if(!kia_v7_encoder_get_upload(instance)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(instance->encoder.size_upload == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t key_data[sizeof(uint64_t)];
|
||||
kia_v7_u64_to_bytes_be(instance->generic.data, key_data);
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void kia_protocol_encoder_v7_stop(void* context) {
|
||||
SubGhzProtocolEncoderKiaV7* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration kia_protocol_encoder_v7_yield(void* context) {
|
||||
SubGhzProtocolEncoderKiaV7* instance = context;
|
||||
|
||||
if(instance->encoder.repeat == 0 || !instance->encoder.is_running) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration duration = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return duration;
|
||||
}
|
||||
|
||||
void* kia_protocol_decoder_v7_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV7));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &subghz_protocol_kia_v7;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v7_free(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v7_reset(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->preamble_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state, ManchesterEventReset, &instance->manchester_state, NULL);
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v7_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
bool data = false;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case KiaV7DecoderStepReset:
|
||||
if(level && kia_v7_is_short(duration)) {
|
||||
instance->decoder.parser_step = KiaV7DecoderStepPreamble;
|
||||
instance->decoder.te_last = duration;
|
||||
instance->preamble_count = 0;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV7DecoderStepPreamble:
|
||||
if(level) {
|
||||
if(kia_v7_is_long(duration) && kia_v7_is_short(instance->decoder.te_last)) {
|
||||
if(instance->preamble_count > (KIA_V7_PREAMBLE_MIN_PAIRS - 1U)) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->preamble_count = 0;
|
||||
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0U);
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1U);
|
||||
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = KiaV7DecoderStepSyncLow;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||
}
|
||||
} else if(kia_v7_is_short(duration)) {
|
||||
instance->decoder.te_last = duration;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(kia_v7_is_short(duration) && kia_v7_is_short(instance->decoder.te_last)) {
|
||||
instance->preamble_count++;
|
||||
} else {
|
||||
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV7DecoderStepSyncLow:
|
||||
if(!level && kia_v7_is_short(duration) && kia_v7_is_long(instance->decoder.te_last)) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = KiaV7DecoderStepData;
|
||||
}
|
||||
break;
|
||||
|
||||
case KiaV7DecoderStepData: {
|
||||
if(kia_v7_is_short(duration)) {
|
||||
event = (ManchesterEvent)((uint8_t)(level & 1U) << 1U);
|
||||
} else if(kia_v7_is_long(duration)) {
|
||||
event = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
} else {
|
||||
event = ManchesterEventReset;
|
||||
}
|
||||
|
||||
if(kia_v7_is_short(duration) || kia_v7_is_long(duration)) {
|
||||
if(manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, data);
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->decoder.decode_count_bit == KIA_V7_KEY_BITS) {
|
||||
const uint64_t candidate = ~instance->decoder.decode_data;
|
||||
const uint8_t hdr = (uint8_t)((candidate >> 56U) & 0xFFU);
|
||||
|
||||
if(hdr == KIA_V7_HEADER) {
|
||||
instance->generic.data = candidate;
|
||||
instance->generic.data_count_bit = KIA_V7_KEY_BITS;
|
||||
kia_v7_decode_key_decoder(instance);
|
||||
|
||||
if(instance->crc_valid) {
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
} else {
|
||||
instance->generic.data = 0;
|
||||
instance->generic.data_count_bit = 0;
|
||||
}
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
} else {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = KiaV7DecoderStepReset;
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t kia_protocol_decoder_v7_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit >> 3U) + 1U);
|
||||
}
|
||||
|
||||
void kia_protocol_decoder_v7_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||
kia_v7_decode_key_decoder(instance);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Sn:%07lX Cnt:%04lX\r\n"
|
||||
"Btn:%01X [%s] CRC:%02X [%s]",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
instance->generic.data,
|
||||
instance->generic.serial & 0x0FFFFFFFU,
|
||||
instance->generic.cnt & 0xFFFFU,
|
||||
instance->decoded_button & 0x0FU,
|
||||
kia_v7_get_button_name(instance->decoded_button),
|
||||
instance->crc_calculated,
|
||||
instance->crc_valid ? "OK" : "ERR");
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v7_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||
kia_v7_decode_key_decoder(instance);
|
||||
|
||||
SubGhzProtocolStatus status =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
uint32_t serial = instance->generic.serial & 0x0FFFFFFFU;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Serial", &serial, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
uint32_t btn_u32 = (uint32_t)(instance->decoded_button & 0x0FU);
|
||||
if(!flipper_format_write_uint32(flipper_format, "Btn", &btn_u32, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
uint32_t cnt_u32 = (uint32_t)(instance->generic.cnt & 0xFFFFU);
|
||||
if(!flipper_format_write_uint32(flipper_format, "Cnt", &cnt_u32, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
uint32_t repeat_u32 = KIA_V7_DEFAULT_TX_REPEAT;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Repeat", &repeat_u32, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
return kia_v7_write_display(
|
||||
flipper_format, instance->generic.protocol_name, instance->decoded_button);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v7_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderKiaV7* instance = context;
|
||||
SubGhzProtocolStatus status = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic, flipper_format, KIA_V7_KEY_BITS);
|
||||
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
kia_v7_decode_key_decoder(instance);
|
||||
|
||||
uint32_t ser_u32 = 0;
|
||||
uint32_t btn_u32 = 0;
|
||||
uint32_t cnt_u32 = 0;
|
||||
bool got_serial = false;
|
||||
bool got_btn = false;
|
||||
bool got_cnt = false;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
got_serial = flipper_format_read_uint32(flipper_format, "Serial", &ser_u32, 1);
|
||||
flipper_format_rewind(flipper_format);
|
||||
got_btn = flipper_format_read_uint32(flipper_format, "Btn", &btn_u32, 1);
|
||||
flipper_format_rewind(flipper_format);
|
||||
got_cnt = flipper_format_read_uint32(flipper_format, "Cnt", &cnt_u32, 1);
|
||||
|
||||
if(got_serial || got_btn || got_cnt) {
|
||||
if(got_serial) {
|
||||
instance->generic.serial = ser_u32 & 0x0FFFFFFFU;
|
||||
}
|
||||
if(got_btn) {
|
||||
instance->generic.btn = (uint8_t)(btn_u32 & 0x0FU);
|
||||
}
|
||||
if(got_cnt) {
|
||||
instance->generic.cnt = (uint16_t)(cnt_u32 & 0xFFFFU);
|
||||
}
|
||||
instance->generic.data = kia_v7_encode_key(
|
||||
instance->fixed_high_byte,
|
||||
instance->generic.serial,
|
||||
instance->generic.btn & 0x0FU,
|
||||
(uint16_t)(instance->generic.cnt & 0xFFFFU),
|
||||
&instance->crc_calculated);
|
||||
kia_v7_decode_key_decoder(instance);
|
||||
}
|
||||
|
||||
return SubGhzProtocolStatusOk;
|
||||
}
|
||||
@@ -9,7 +9,7 @@
|
||||
#define KIA_V7_KEY_BITS 64U
|
||||
#define KIA_V7_DEFAULT_TX_REPEAT 10U
|
||||
|
||||
static const SubGhzBlockConst kia_protocol_v7_const = {
|
||||
static const SubGhzBlockConst subghz_protocol_kia_v7_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
@@ -85,23 +85,24 @@ static uint64_t kia_v7_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
}
|
||||
|
||||
static bool kia_v7_is_short(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, kia_protocol_v7_const.te_short) <
|
||||
kia_protocol_v7_const.te_delta;
|
||||
return DURATION_DIFF(duration, subghz_protocol_kia_v7_const.te_short) <
|
||||
subghz_protocol_kia_v7_const.te_delta;
|
||||
}
|
||||
|
||||
static bool kia_v7_is_long(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, kia_protocol_v7_const.te_long) < kia_protocol_v7_const.te_delta;
|
||||
return DURATION_DIFF(duration, subghz_protocol_kia_v7_const.te_long) <
|
||||
subghz_protocol_kia_v7_const.te_delta;
|
||||
}
|
||||
|
||||
static const char* kia_v7_get_button_name(uint8_t button) {
|
||||
switch(button) {
|
||||
case 0x01:
|
||||
return "Lock";
|
||||
return "LOCK";
|
||||
case 0x02:
|
||||
return "Unlock";
|
||||
return "UNLOCK";
|
||||
case 0x03:
|
||||
case 0x08:
|
||||
return "Trunk";
|
||||
return "BOOT";
|
||||
default:
|
||||
return "??";
|
||||
}
|
||||
@@ -211,8 +212,10 @@ static void kia_v7_decode_key_encoder(SubGhzProtocolEncoderKiaV7* instance) {
|
||||
static bool kia_v7_encoder_get_upload(SubGhzProtocolEncoderKiaV7* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
const LevelDuration high_short = level_duration_make(true, kia_protocol_v7_const.te_short);
|
||||
const LevelDuration low_short = level_duration_make(false, kia_protocol_v7_const.te_short);
|
||||
const LevelDuration high_short =
|
||||
level_duration_make(true, subghz_protocol_kia_v7_const.te_short);
|
||||
const LevelDuration low_short =
|
||||
level_duration_make(false, subghz_protocol_kia_v7_const.te_short);
|
||||
const LevelDuration low_tail = level_duration_make(false, KIA_V7_TAIL_GAP_US);
|
||||
const size_t max_size = KIA_V7_UPLOAD_CAPACITY;
|
||||
|
||||
@@ -263,7 +266,7 @@ static bool kia_v7_encoder_get_upload(SubGhzProtocolEncoderKiaV7* instance) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder kia_protocol_v7_decoder = {
|
||||
const SubGhzProtocolDecoder subghz_protocol_kia_v7_decoder = {
|
||||
.alloc = kia_protocol_decoder_v7_alloc,
|
||||
.free = kia_protocol_decoder_v7_free,
|
||||
.feed = kia_protocol_decoder_v7_feed,
|
||||
@@ -274,7 +277,7 @@ const SubGhzProtocolDecoder kia_protocol_v7_decoder = {
|
||||
.get_string = kia_protocol_decoder_v7_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder kia_protocol_v7_encoder = {
|
||||
const SubGhzProtocolEncoder subghz_protocol_kia_v7_encoder = {
|
||||
.alloc = kia_protocol_encoder_v7_alloc,
|
||||
.free = kia_protocol_encoder_v7_free,
|
||||
.deserialize = kia_protocol_encoder_v7_deserialize,
|
||||
@@ -282,13 +285,13 @@ const SubGhzProtocolEncoder kia_protocol_v7_encoder = {
|
||||
.yield = kia_protocol_encoder_v7_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol kia_protocol_v7 = {
|
||||
const SubGhzProtocol subghz_protocol_kia_v7 = {
|
||||
.name = KIA_PROTOCOL_V7_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &kia_protocol_v7_decoder,
|
||||
.encoder = &kia_protocol_v7_encoder,
|
||||
.decoder = &subghz_protocol_kia_v7_decoder,
|
||||
.encoder = &subghz_protocol_kia_v7_encoder,
|
||||
};
|
||||
|
||||
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment) {
|
||||
@@ -297,7 +300,7 @@ void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment) {
|
||||
SubGhzProtocolEncoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolEncoderKiaV7));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &kia_protocol_v7;
|
||||
instance->base.protocol = &subghz_protocol_kia_v7;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 1;
|
||||
instance->encoder.size_upload = KIA_V7_UPLOAD_CAPACITY;
|
||||
@@ -441,7 +444,7 @@ void* kia_protocol_decoder_v7_alloc(SubGhzEnvironment* environment) {
|
||||
SubGhzProtocolDecoderKiaV7* instance = calloc(1, sizeof(SubGhzProtocolDecoderKiaV7));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &kia_protocol_v7;
|
||||
instance->base.protocol = &subghz_protocol_kia_v7;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
typedef struct SubGhzProtocolDecoderKiaV7 SubGhzProtocolDecoderKiaV7;
|
||||
typedef struct SubGhzProtocolEncoderKiaV7 SubGhzProtocolEncoderKiaV7;
|
||||
|
||||
extern const SubGhzProtocolDecoder kia_protocol_v7_decoder;
|
||||
extern const SubGhzProtocolEncoder kia_protocol_v7_encoder;
|
||||
extern const SubGhzProtocol kia_protocol_v7;
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_kia_v7_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_kia_v7_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_kia_v7;
|
||||
|
||||
void* kia_protocol_decoder_v7_alloc(SubGhzEnvironment* environment);
|
||||
void kia_protocol_decoder_v7_free(void* context);
|
||||
|
||||
@@ -0,0 +1,302 @@
|
||||
/**
|
||||
* landrover_rke.c
|
||||
* Land Rover RKE (Remote Keyless Entry) protocol — ported from Pandora DXL 5000 firmware
|
||||
* Target: Flipper Zero (SubGHz RAW / custom protocol plugin)
|
||||
*
|
||||
* Protocol ID in original firmware: 0x0E
|
||||
* Co-located in firmware with Ford/Jaguar (case 0x0E references 0xed58 "Ford/Jaguar"
|
||||
* then falls through to 0xed64 "Land Rover" — same baseband, different ID range).
|
||||
*
|
||||
* Frequency: 433.92 MHz (EU/RoW) or 315.00 MHz (North America)
|
||||
* Modulation: OOK, Fixed-width PWM (similar to Ford RKE / Microchip KEELOQ derivative)
|
||||
* Carrier: AM
|
||||
*
|
||||
* Frame structure (Land Rover Freelander 2 / Discovery 3-4 / Range Rover Sport ~2004-2013):
|
||||
* Preamble : 20 logic-1 pulses (carrier warmup)
|
||||
* Header : 1 logic-1 + sync gap (~9.6 ms LOW)
|
||||
* Payload : 66 bits, MSB-first, fixed-width PWM
|
||||
* [65:34] 32-bit KeeLoq encrypted hopping code
|
||||
* [33:18] 16-bit fixed serial number (high word)
|
||||
* [17:10] 8-bit fixed serial (low byte)
|
||||
* [9:6] 4-bit button code
|
||||
* 0x1=Lock, 0x2=Unlock, 0x4=Boot/Tailgate, 0x8=Panic
|
||||
* [5:2] 4-bit function bits (repeat count / battery low flags)
|
||||
* [1:0] 2-bit status (0x1=battery low, 0x2=repeat)
|
||||
* Repeated up to 4 times
|
||||
*
|
||||
* PWM timing (from firmware, FUN_000007cc + FUN_00000840 timer init):
|
||||
* Bit period : 1000 µs
|
||||
* Bit-1 : 700 µs HIGH + 300 µs LOW
|
||||
* Bit-0 : 300 µs HIGH + 700 µs LOW
|
||||
* Preamble pulse: 400 µs HIGH + 600 µs LOW
|
||||
* Sync gap : 400 µs HIGH + 9600 µs LOW
|
||||
* Tolerance : ±20%
|
||||
*
|
||||
* KeeLoq note:
|
||||
* The hopping code is encrypted with KeeLoq (Microchip HCS-series algorithm).
|
||||
* Full decryption requires the manufacturer key (not in the firmware binary —
|
||||
* it's provisioned per fob). This file implements the protocol framing layer;
|
||||
* a separate keeloq.c provides the cipher if you have the key.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Timing constants (microseconds)
|
||||
* ------------------------------------------------------------------------- */
|
||||
#define LR_PREAMBLE_HIGH_US 400u
|
||||
#define LR_PREAMBLE_LOW_US 600u
|
||||
#define LR_PREAMBLE_COUNT 20u
|
||||
|
||||
#define LR_SYNC_HIGH_US 400u
|
||||
#define LR_SYNC_LOW_US 9600u
|
||||
|
||||
#define LR_BIT_PERIOD_US 1000u
|
||||
#define LR_BIT1_HIGH_US 700u
|
||||
#define LR_BIT1_LOW_US 300u
|
||||
#define LR_BIT0_HIGH_US 300u
|
||||
#define LR_BIT0_LOW_US 700u
|
||||
|
||||
#define LR_REPEAT_GAP_US 12000u
|
||||
#define LR_REPEAT_COUNT 4u
|
||||
#define LR_TOLERANCE_PCT 20u
|
||||
|
||||
#define LR_FRAME_BITS 66u
|
||||
|
||||
/* Button codes (bits [9:6]) */
|
||||
#define LR_BTN_LOCK 0x1u
|
||||
#define LR_BTN_UNLOCK 0x2u
|
||||
#define LR_BTN_BOOT 0x4u
|
||||
#define LR_BTN_PANIC 0x8u
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Data types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Decoded Land Rover RKE frame.
|
||||
* The hop_code is the raw 32-bit KeeLoq ciphertext — decrypt separately.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t hop_code; /**< 32-bit KeeLoq encrypted hopping word */
|
||||
uint32_t serial; /**< 24-bit fixed serial number (bits [33:10]) */
|
||||
uint8_t button; /**< 4-bit button code (LR_BTN_*) */
|
||||
uint8_t func_bits; /**< 4-bit function/repeat flags */
|
||||
uint8_t status; /**< 2-bit status byte */
|
||||
bool valid; /**< true if frame geometry is correct */
|
||||
} LandRoverFrame;
|
||||
|
||||
/** Raw pulse buffer */
|
||||
typedef struct {
|
||||
int32_t pulses[512];
|
||||
uint32_t count;
|
||||
} LandRoverRawBuf;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Internal helpers
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
static bool lr_in_range(int32_t measured_us, uint32_t ref_us)
|
||||
{
|
||||
int32_t ref = (int32_t)ref_us;
|
||||
int32_t diff = measured_us - ref;
|
||||
if (diff < 0) diff = -diff;
|
||||
return (diff * 100) <= (ref * (int32_t)LR_TOLERANCE_PCT);
|
||||
}
|
||||
|
||||
static void lr_push(LandRoverRawBuf *buf, int32_t val)
|
||||
{
|
||||
if (buf->count < 512) buf->pulses[buf->count++] = val;
|
||||
}
|
||||
|
||||
static void lr_push_pair(LandRoverRawBuf *buf, uint32_t hi, uint32_t lo)
|
||||
{
|
||||
lr_push(buf, (int32_t)hi);
|
||||
lr_push(buf, -(int32_t)lo);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Encode
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* lr_encode() — encode a LandRoverFrame into a Flipper SubGHz RAW buffer.
|
||||
*
|
||||
* The hop_code field must already be KeeLoq-encrypted by the caller.
|
||||
* Emits LR_REPEAT_COUNT repetitions.
|
||||
*/
|
||||
void lr_encode(const LandRoverFrame *frame, LandRoverRawBuf *buf)
|
||||
{
|
||||
buf->count = 0;
|
||||
|
||||
/* Pack the 66-bit payload into a uint8_t array, MSB-first */
|
||||
/* Layout: [65:34]=hop_code [33:10]=serial [9:6]=button */
|
||||
/* [5:2]=func_bits [1:0]=status */
|
||||
uint8_t bits[66];
|
||||
memset(bits, 0, sizeof(bits));
|
||||
|
||||
/* hop_code: bits 65..34 */
|
||||
for (int i = 0; i < 32; i++) {
|
||||
bits[65 - i] = (frame->hop_code >> i) & 1u;
|
||||
}
|
||||
/* serial: bits 33..10 (24 bits) */
|
||||
for (int i = 0; i < 24; i++) {
|
||||
bits[33 - i] = (frame->serial >> i) & 1u;
|
||||
}
|
||||
/* button: bits 9..6 */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
bits[9 - i] = (frame->button >> i) & 1u;
|
||||
}
|
||||
/* func_bits: bits 5..2 */
|
||||
for (int i = 0; i < 4; i++) {
|
||||
bits[5 - i] = (frame->func_bits >> i) & 1u;
|
||||
}
|
||||
/* status: bits 1..0 */
|
||||
bits[1] = (frame->status >> 1) & 1u;
|
||||
bits[0] = frame->status & 1u;
|
||||
|
||||
for (uint32_t rep = 0; rep < LR_REPEAT_COUNT; rep++) {
|
||||
/* Preamble: 20 pulses */
|
||||
for (uint32_t p = 0; p < LR_PREAMBLE_COUNT; p++) {
|
||||
lr_push_pair(buf, LR_PREAMBLE_HIGH_US, LR_PREAMBLE_LOW_US);
|
||||
}
|
||||
/* Sync */
|
||||
lr_push_pair(buf, LR_SYNC_HIGH_US, LR_SYNC_LOW_US);
|
||||
|
||||
/* Data bits, MSB-first (bit 65 first on air) */
|
||||
for (int b = 65; b >= 0; b--) {
|
||||
if (bits[b]) {
|
||||
lr_push_pair(buf, LR_BIT1_HIGH_US, LR_BIT1_LOW_US);
|
||||
} else {
|
||||
lr_push_pair(buf, LR_BIT0_HIGH_US, LR_BIT0_LOW_US);
|
||||
}
|
||||
}
|
||||
|
||||
/* Inter-repetition gap */
|
||||
if (rep < LR_REPEAT_COUNT - 1) {
|
||||
lr_push(buf, -(int32_t)LR_REPEAT_GAP_US);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Decode
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* lr_decode() — decode a raw pulse buffer into a LandRoverFrame.
|
||||
*
|
||||
* Returns true if a geometrically valid frame was found (preamble + sync +
|
||||
* 66 bits all within timing tolerance). The hop_code will need KeeLoq
|
||||
* decryption by the caller to verify authenticity.
|
||||
*/
|
||||
bool lr_decode(const LandRoverRawBuf *buf, LandRoverFrame *frame)
|
||||
{
|
||||
memset(frame, 0, sizeof(*frame));
|
||||
|
||||
for (uint32_t i = 0; i + 1 < buf->count; i++) {
|
||||
/* Look for sync: ~400 µs HIGH + ~9600 µs LOW */
|
||||
if (!lr_in_range( buf->pulses[i], LR_SYNC_HIGH_US)) continue;
|
||||
if (!lr_in_range(-buf->pulses[i + 1], LR_SYNC_LOW_US)) continue;
|
||||
|
||||
uint32_t j = i + 2;
|
||||
if (j + LR_FRAME_BITS * 2 > buf->count) continue;
|
||||
|
||||
uint8_t bits[66];
|
||||
bool ok = true;
|
||||
|
||||
for (uint32_t b = 0; b < LR_FRAME_BITS; b++) {
|
||||
int32_t hi = buf->pulses[j];
|
||||
int32_t lo = -buf->pulses[j + 1];
|
||||
j += 2;
|
||||
|
||||
if (lr_in_range(hi, LR_BIT1_HIGH_US) && lr_in_range(lo, LR_BIT1_LOW_US)) {
|
||||
bits[65 - b] = 1;
|
||||
} else if (lr_in_range(hi, LR_BIT0_HIGH_US) && lr_in_range(lo, LR_BIT0_LOW_US)) {
|
||||
bits[65 - b] = 0;
|
||||
} else {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ok) continue;
|
||||
|
||||
/* Unpack — MSB of each field is highest-indexed bit */
|
||||
frame->hop_code = 0;
|
||||
for (int k = 0; k < 32; k++) {
|
||||
frame->hop_code |= (uint32_t)bits[65 - k] << (31 - k);
|
||||
}
|
||||
frame->serial = 0;
|
||||
for (int k = 0; k < 24; k++) {
|
||||
frame->serial |= (uint32_t)bits[33 - k] << (23 - k);
|
||||
}
|
||||
frame->button = 0;
|
||||
for (int k = 0; k < 4; k++) {
|
||||
frame->button |= (uint8_t)bits[9 - k] << (3 - k);
|
||||
}
|
||||
frame->func_bits = 0;
|
||||
for (int k = 0; k < 4; k++) {
|
||||
frame->func_bits |= (uint8_t)bits[5 - k] << (3 - k);
|
||||
}
|
||||
frame->status = (bits[1] << 1) | bits[0];
|
||||
frame->valid = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* KeeLoq stub
|
||||
*
|
||||
* Land Rover uses a KeeLoq-derived hopping code (same baseband as Ford/Jaguar,
|
||||
* firmware case 0x0E dispatches both via the same path before branching on
|
||||
* the serial-number range).
|
||||
*
|
||||
* To decrypt hop_code you need the 64-bit manufacturer key. Implement
|
||||
* keeloq_decrypt() in a separate keeloq.c (standard NLF cipher, widely
|
||||
* documented in Microchip AN-66265).
|
||||
*
|
||||
* extern uint32_t keeloq_decrypt(uint32_t ciphertext, uint64_t key);
|
||||
*
|
||||
* Typical Land Rover key derivation (normal learning, from public research):
|
||||
* uint64_t man_key = LR_MANUFACTURER_KEY; // provisioned, not in firmware
|
||||
* uint64_t dev_key = keeloq_learn_normal(man_key, serial);
|
||||
* uint32_t plain = keeloq_decrypt(frame.hop_code, dev_key);
|
||||
* // plain[15:0] = 16-bit counter
|
||||
* // plain[19:16] = button code (must match frame.button)
|
||||
* // plain[31:28] = discriminant (0x6 for LR)
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Rolling counter validation
|
||||
* Land Rover receivers accept counter in window [last+1, last+32768]
|
||||
* ------------------------------------------------------------------------- */
|
||||
bool lr_counter_valid(uint16_t stored, uint16_t received)
|
||||
{
|
||||
uint16_t delta = (uint16_t)(received - stored);
|
||||
return (delta >= 1u && delta <= 32768u);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Flipper Zero SubGHz plugin glue — same pattern as honda_rke.c
|
||||
*
|
||||
* void flipper_lr_encode(SubGhzProtocolEncoder *enc, void *ctx) {
|
||||
* LandRoverFrame *f = (LandRoverFrame *)ctx;
|
||||
* LandRoverRawBuf raw;
|
||||
* lr_encode(f, &raw);
|
||||
* // feed raw to SubGHz RAW transmit
|
||||
* }
|
||||
*
|
||||
* bool flipper_lr_decode(SubGhzProtocolDecoder *dec,
|
||||
* const int32_t *pulses, uint32_t count, void *ctx) {
|
||||
* LandRoverRawBuf buf;
|
||||
* buf.count = count > 512 ? 512 : count;
|
||||
* memcpy(buf.pulses, pulses, buf.count * sizeof(int32_t));
|
||||
* LandRoverFrame frame;
|
||||
* return lr_decode(&buf, &frame);
|
||||
* }
|
||||
* ------------------------------------------------------------------------- */
|
||||
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
/**
|
||||
* landrover_rke.h
|
||||
* Land Rover RKE protocol — Pandora DXL 5000 → Flipper Zero port
|
||||
* Protocol ID: 0x0E | 433.92 MHz / 315.00 MHz | OOK PWM | 66-bit KeeLoq frame
|
||||
*
|
||||
* NOTE: hop_code is the raw KeeLoq ciphertext. Use the existing
|
||||
* keeloq.c in the Flipper firmware to decrypt/verify.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Timing constants (microseconds)
|
||||
* ------------------------------------------------------------------------- */
|
||||
#define LR_PREAMBLE_HIGH_US 400u
|
||||
#define LR_PREAMBLE_LOW_US 600u
|
||||
#define LR_PREAMBLE_COUNT 20u
|
||||
#define LR_SYNC_HIGH_US 400u
|
||||
#define LR_SYNC_LOW_US 9600u
|
||||
#define LR_BIT1_HIGH_US 700u
|
||||
#define LR_BIT1_LOW_US 300u
|
||||
#define LR_BIT0_HIGH_US 300u
|
||||
#define LR_BIT0_LOW_US 700u
|
||||
#define LR_REPEAT_GAP_US 12000u
|
||||
#define LR_REPEAT_COUNT 4u
|
||||
#define LR_TOLERANCE_PCT 20u
|
||||
#define LR_FRAME_BITS 66u
|
||||
|
||||
/* Frequency options */
|
||||
#define LR_FREQ_EU_HZ 433920000ul
|
||||
#define LR_FREQ_US_HZ 315000000ul
|
||||
|
||||
/* Button codes — bits [9:6] of frame */
|
||||
#define LR_BTN_LOCK 0x1u
|
||||
#define LR_BTN_UNLOCK 0x2u
|
||||
#define LR_BTN_BOOT 0x4u /**< Boot / tailgate */
|
||||
#define LR_BTN_PANIC 0x8u
|
||||
|
||||
/* Status bits [1:0] */
|
||||
#define LR_STATUS_BATTERY_LOW 0x1u
|
||||
#define LR_STATUS_REPEAT 0x2u
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* Data types
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Land Rover RKE frame.
|
||||
* hop_code must be KeeLoq-encrypted before encode, and can be decrypted
|
||||
* after decode using subghz_protocol_keeloq_decrypt() from the Flipper firmware.
|
||||
*/
|
||||
typedef struct {
|
||||
uint32_t hop_code; /**< 32-bit KeeLoq encrypted hopping word */
|
||||
uint32_t serial; /**< 24-bit fixed fob serial */
|
||||
uint8_t button; /**< 4-bit button code: LR_BTN_* */
|
||||
uint8_t func_bits; /**< 4-bit function/repeat flags */
|
||||
uint8_t status; /**< 2-bit status: LR_STATUS_* */
|
||||
bool valid; /**< true after decode if geometry is correct */
|
||||
} LandRoverFrame;
|
||||
|
||||
/** Raw pulse buffer */
|
||||
typedef struct {
|
||||
int32_t pulses[512];
|
||||
uint32_t count;
|
||||
} LandRoverRawBuf;
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* API
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
/**
|
||||
* Encode a LandRoverFrame into a SubGHz RAW pulse buffer.
|
||||
* hop_code must already be KeeLoq-encrypted by the caller.
|
||||
* Emits LR_REPEAT_COUNT (4) repetitions.
|
||||
*/
|
||||
void lr_encode(const LandRoverFrame *frame, LandRoverRawBuf *buf);
|
||||
|
||||
/**
|
||||
* Decode a raw pulse buffer into a LandRoverFrame.
|
||||
* Sets valid=true if frame geometry (preamble+sync+66 bits) passes timing checks.
|
||||
* Does NOT verify the KeeLoq hop code — do that separately.
|
||||
*/
|
||||
bool lr_decode(const LandRoverRawBuf *buf, LandRoverFrame *frame);
|
||||
|
||||
/**
|
||||
* Validate a received 16-bit KeeLoq counter (extracted from decrypted hop_code)
|
||||
* against the last stored value. Land Rover window: [stored+1, stored+32768].
|
||||
*/
|
||||
bool lr_counter_valid(uint16_t stored, uint16_t received);
|
||||
|
||||
/* -------------------------------------------------------------------------
|
||||
* KeeLoq integration hint
|
||||
*
|
||||
* After lr_decode() succeeds, decrypt and verify like this:
|
||||
*
|
||||
* uint64_t dev_key = keeloq_normal_learning(manufacturer_key, frame.serial);
|
||||
* uint32_t plain = keeloq_decrypt(frame.hop_code, dev_key);
|
||||
* uint16_t counter = plain & 0xFFFFu;
|
||||
* uint8_t btn_chk = (plain >> 16) & 0xFu; // must equal frame.button
|
||||
* uint8_t disc = (plain >> 28) & 0xFu; // 0x6 for Land Rover
|
||||
* ------------------------------------------------------------------------- */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -37,6 +37,7 @@ typedef struct SubGhzProtocolDecoderMazdaV0 {
|
||||
uint32_t count;
|
||||
} SubGhzProtocolDecoderMazdaV0;
|
||||
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
typedef struct SubGhzProtocolEncoderMazdaV0 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
@@ -46,6 +47,7 @@ typedef struct SubGhzProtocolEncoderMazdaV0 {
|
||||
uint8_t button;
|
||||
uint32_t count;
|
||||
} SubGhzProtocolEncoderMazdaV0;
|
||||
//#endif
|
||||
|
||||
typedef enum {
|
||||
MazdaV0DecoderStepReset = 0,
|
||||
@@ -59,6 +61,7 @@ typedef enum {
|
||||
|
||||
static bool mazda_v0_get_event(uint32_t duration, bool level, ManchesterEvent* event);
|
||||
static void mazda_v0_decode_key(SubGhzBlockGeneric* generic);
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint64_t mazda_v0_encode_key(uint32_t serial, uint8_t button, uint32_t counter);
|
||||
static bool mazda_v0_encoder_add_level(
|
||||
SubGhzProtocolEncoderMazdaV0* instance,
|
||||
@@ -68,6 +71,7 @@ static bool mazda_v0_encoder_add_level(
|
||||
static bool
|
||||
mazda_v0_append_byte(SubGhzProtocolEncoderMazdaV0* instance, size_t* index, uint8_t value);
|
||||
static bool mazda_v0_build_upload(SubGhzProtocolEncoderMazdaV0* instance);
|
||||
//#endif
|
||||
static SubGhzProtocolStatus mazda_v0_write_display(
|
||||
FlipperFormat* flipper_format,
|
||||
const char* protocol_name,
|
||||
@@ -88,6 +92,7 @@ const SubGhzProtocolDecoder subghz_protocol_mazda_v0_decoder = {
|
||||
.get_string = subghz_protocol_decoder_mazda_v0_get_string,
|
||||
};
|
||||
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = {
|
||||
.alloc = subghz_protocol_encoder_mazda_v0_alloc,
|
||||
.free = subghz_protocol_encoder_mazda_v0_free,
|
||||
@@ -95,8 +100,17 @@ const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = {
|
||||
.stop = subghz_protocol_encoder_mazda_v0_stop,
|
||||
.yield = subghz_protocol_encoder_mazda_v0_yield,
|
||||
};
|
||||
//#else
|
||||
//const SubGhzProtocolEncoder subghz_protocol_mazda_v0_encoder = {
|
||||
// .alloc = NULL,
|
||||
// .free = NULL,
|
||||
// .deserialize = NULL,
|
||||
// .stop = NULL,
|
||||
// .yield = NULL,
|
||||
//};
|
||||
//#endif
|
||||
|
||||
const SubGhzProtocol mazda_v0_protocol = {
|
||||
const SubGhzProtocol subghz_protocol_mazda_v0 = {
|
||||
.name = MAZDA_PROTOCOL_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
@@ -124,6 +138,7 @@ static void mazda_v0_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
|
||||
}
|
||||
}
|
||||
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint64_t mazda_v0_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
uint64_t data = 0;
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
@@ -131,6 +146,7 @@ static uint64_t mazda_v0_bytes_to_u64_be(const uint8_t bytes[8]) {
|
||||
}
|
||||
return data;
|
||||
}
|
||||
//#endif
|
||||
|
||||
static uint8_t mazda_v0_calculate_checksum(uint32_t serial, uint8_t button, uint32_t counter) {
|
||||
counter &= 0xFFFFFU;
|
||||
@@ -142,13 +158,13 @@ static uint8_t mazda_v0_calculate_checksum(uint32_t serial, uint8_t button, uint
|
||||
static const char* mazda_v0_get_button_name(uint8_t button) {
|
||||
switch(button) {
|
||||
case 0x01:
|
||||
return "Lock";
|
||||
return "LOCK";
|
||||
case 0x02:
|
||||
return "Unlock";
|
||||
return "UNLOCK";
|
||||
case 0x04:
|
||||
return "Trunk";
|
||||
return "BOOT";
|
||||
case 0x08:
|
||||
return "Remote";
|
||||
return "REMOTE";
|
||||
default:
|
||||
return "??";
|
||||
}
|
||||
@@ -197,6 +213,7 @@ static void mazda_v0_decode_key(SubGhzBlockGeneric* generic) {
|
||||
generic->data_count_bit = subghz_protocol_mazda_v0_const.min_count_bit_for_found;
|
||||
}
|
||||
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
static uint64_t mazda_v0_encode_key(uint32_t serial, uint8_t button, uint32_t counter) {
|
||||
uint8_t data[8];
|
||||
|
||||
@@ -313,6 +330,7 @@ static bool mazda_v0_build_upload(SubGhzProtocolEncoderMazdaV0* instance) {
|
||||
|
||||
return true;
|
||||
}
|
||||
//#endif
|
||||
|
||||
static SubGhzProtocolStatus mazda_v0_write_display(
|
||||
FlipperFormat* flipper_format,
|
||||
@@ -335,13 +353,14 @@ static SubGhzProtocolStatus mazda_v0_write_display(
|
||||
// ENCODER
|
||||
// =============================================================================
|
||||
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
void* subghz_protocol_encoder_mazda_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolEncoderMazdaV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderMazdaV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &mazda_v0_protocol;
|
||||
instance->base.protocol = &subghz_protocol_mazda_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 0;
|
||||
@@ -486,6 +505,7 @@ LevelDuration subghz_protocol_encoder_mazda_v0_yield(void* context) {
|
||||
|
||||
return out;
|
||||
}
|
||||
//#endif
|
||||
|
||||
// =============================================================================
|
||||
// DECODER
|
||||
@@ -497,7 +517,7 @@ void* subghz_protocol_decoder_mazda_v0_alloc(SubGhzEnvironment* environment) {
|
||||
SubGhzProtocolDecoderMazdaV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderMazdaV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &mazda_v0_protocol;
|
||||
instance->base.protocol = &subghz_protocol_mazda_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
#include <lib/toolbox/level_duration.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
//#include "../defines.h"
|
||||
|
||||
#define MAZDA_PROTOCOL_V0_NAME "Mazda V0"
|
||||
|
||||
extern const SubGhzProtocol mazda_v0_protocol;
|
||||
extern const SubGhzProtocol subghz_protocol_mazda_v0;
|
||||
|
||||
void* subghz_protocol_decoder_mazda_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_mazda_v0_free(void* context);
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
#include "mitsubishi_v0.h"
|
||||
#include <inttypes.h>
|
||||
|
||||
#define TAG "MitsubishiProtocolV0"
|
||||
#define MITSUBISHI_V0_PREAMBLE_COUNT 100
|
||||
#define MITSUBISHI_V0_BIT_TE 250
|
||||
#define MITSUBISHI_V0_BIT_TE_GAP 500
|
||||
#define MITSUBISHI_V0_BIT_COUNT 96 // 12 bytes * 8 bits
|
||||
#define MITSUBISHI_V0_TOTAL_BURSTS 3
|
||||
#define MITSUBISHI_V0_INTER_BURST_GAP 25000
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_mitsubishi_v0_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 80,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderMitsubishiV0 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint32_t te_last;
|
||||
uint8_t bit_count;
|
||||
uint8_t decode_data[12];
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderMitsubishiV0 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
size_t upload_capacity;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS (Scrambling & Logic)
|
||||
// ============================================================================
|
||||
|
||||
static void mitsubishi_v0_scramble(uint8_t* payload, uint16_t counter) {
|
||||
uint8_t hi = (counter >> 8) & 0xFF;
|
||||
uint8_t lo = counter & 0xFF;
|
||||
uint8_t mask1 = (hi & 0xAA) | (lo & 0x55);
|
||||
uint8_t mask2 = (lo & 0xAA) | (hi & 0x55);
|
||||
uint8_t mask3 = mask1 ^ mask2;
|
||||
|
||||
// Apply scrambling to first 5 bytes (as per sub_ROM_148BE @ 0x148BE)
|
||||
for(int i = 0; i < 5; i++) {
|
||||
payload[i] ^= mask3;
|
||||
}
|
||||
|
||||
// Apply inversion (first 8 bytes) — firmware XORs bytes 1..8 with 0xFF in sub_ROM_151E8
|
||||
for(int i = 0; i < 8; i++) {
|
||||
payload[i] = ~payload[i];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PROTOCOL INTERFACE DEFINITIONS
|
||||
// ============================================================================
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_mitsubishi_v0_decoder = {
|
||||
.alloc = subghz_protocol_decoder_mitsubishi_v0_alloc,
|
||||
.free = subghz_protocol_decoder_mitsubishi_v0_free,
|
||||
.feed = subghz_protocol_decoder_mitsubishi_v0_feed,
|
||||
.reset = subghz_protocol_decoder_mitsubishi_v0_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_mitsubishi_v0_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_mitsubishi_v0_serialize,
|
||||
.deserialize = subghz_protocol_decoder_mitsubishi_v0_deserialize,
|
||||
.get_string = subghz_protocol_decoder_mitsubishi_v0_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_mitsubishi_v0_encoder = {
|
||||
.alloc = subghz_protocol_encoder_mitsubishi_v0_alloc,
|
||||
.free = subghz_protocol_encoder_mitsubishi_v0_free,
|
||||
.deserialize = subghz_protocol_encoder_mitsubishi_v0_deserialize,
|
||||
.stop = subghz_protocol_encoder_mitsubishi_v0_stop,
|
||||
.yield = subghz_protocol_encoder_mitsubishi_v0_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_mitsubishi_v0 = {
|
||||
.name = MITSUBISHI_PROTOCOL_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_868 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_mitsubishi_v0_decoder,
|
||||
.encoder = &subghz_protocol_mitsubishi_v0_encoder,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ENCODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
void* subghz_protocol_encoder_mitsubishi_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderMitsubishiV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderMitsubishiV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &subghz_protocol_mitsubishi_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 5;
|
||||
instance->encoder.size_upload = 0;
|
||||
// Preamble + Sync + (12 bytes * 8 bits * 2 elements) + Gap
|
||||
instance->upload_capacity = (MITSUBISHI_V0_PREAMBLE_COUNT * 2) + 20 + (MITSUBISHI_V0_BIT_COUNT * 2) + 2;
|
||||
instance->encoder.upload = calloc(instance->upload_capacity, sizeof(LevelDuration));
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_mitsubishi_v0_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderMitsubishiV0* instance = context;
|
||||
if(instance->encoder.upload) free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static void subghz_protocol_encoder_mitsubishi_v0_get_upload(SubGhzProtocolEncoderMitsubishiV0* instance) {
|
||||
size_t index = 0;
|
||||
uint8_t payload[12] = {0};
|
||||
|
||||
// Pack data
|
||||
payload[0] = (instance->generic.serial >> 24) & 0xFF;
|
||||
payload[1] = (instance->generic.serial >> 16) & 0xFF;
|
||||
payload[2] = (instance->generic.serial >> 8) & 0xFF;
|
||||
payload[3] = instance->generic.serial & 0xFF;
|
||||
payload[4] = (instance->generic.cnt >> 8) & 0xFF;
|
||||
payload[5] = instance->generic.cnt & 0xFF;
|
||||
payload[6] = instance->generic.btn;
|
||||
payload[9] = 0x5A; // ID byte (firmware: byte_RAM_59 = 0x5A in sub_ROM_151E8 @ 0x15258)
|
||||
payload[10] = 0xFF;
|
||||
payload[11] = 0xFF;
|
||||
|
||||
mitsubishi_v0_scramble(payload, (uint16_t)instance->generic.cnt);
|
||||
|
||||
// Preamble
|
||||
for(int i = 0; i < MITSUBISHI_V0_PREAMBLE_COUNT; i++) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE);
|
||||
}
|
||||
|
||||
// Sync pulses (firmware: 96-iteration loop in sub_ROM_1526C @ 0x152A0)
|
||||
for(int i = 0; i < 95; i++) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE);
|
||||
}
|
||||
|
||||
// Data bits
|
||||
for(int i = 0; i < 12; i++) {
|
||||
for(int bit = 7; bit >= 0; bit--) {
|
||||
bool curr = (payload[i] >> bit) & 1;
|
||||
if(curr) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE_GAP);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE_GAP);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_mitsubishi_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
SubGhzProtocolEncoderMitsubishiV0* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(ret != SubGhzProtocolStatusOk) break;
|
||||
if(!flipper_format_rewind(flipper_format)) break;
|
||||
flipper_format_read_uint32(flipper_format, "Serial", &instance->generic.serial, 1);
|
||||
flipper_format_read_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1);
|
||||
uint32_t btn_temp = 0;
|
||||
flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1);
|
||||
instance->generic.btn = (uint8_t)btn_temp;
|
||||
|
||||
subghz_protocol_encoder_mitsubishi_v0_get_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_mitsubishi_v0_stop(void* context) {
|
||||
((SubGhzProtocolEncoderMitsubishiV0*)context)->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_mitsubishi_v0_yield(void* context) {
|
||||
SubGhzProtocolEncoderMitsubishiV0* instance = context;
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0) 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;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DECODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
void* subghz_protocol_decoder_mitsubishi_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderMitsubishiV0));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_mitsubishi_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_v0_free(void* context) {
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_v0_reset(void* context) {
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = context;
|
||||
instance->bit_count = 0;
|
||||
memset(instance->decode_data, 0, 12);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_v0_feed(void* context, bool level, uint32_t duration) {
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = context;
|
||||
|
||||
// Simplified Pulse Distance/Width Decoder
|
||||
uint32_t te = subghz_protocol_mitsubishi_v0_const.te_short;
|
||||
uint32_t te2 = subghz_protocol_mitsubishi_v0_const.te_long;
|
||||
uint32_t delta = subghz_protocol_mitsubishi_v0_const.te_delta;
|
||||
|
||||
if(!level) {
|
||||
// Logic '1': HIGH 250, LOW 500
|
||||
// Logic '0': HIGH 500, LOW 250
|
||||
if(DURATION_DIFF(instance->te_last, te) < delta && DURATION_DIFF(duration, te2) < delta) {
|
||||
// bit 1
|
||||
instance->decode_data[instance->bit_count / 8] |= (1 << (7 - (instance->bit_count % 8)));
|
||||
instance->bit_count++;
|
||||
} else if(DURATION_DIFF(instance->te_last, te2) < delta && DURATION_DIFF(duration, te) < delta) {
|
||||
// bit 0
|
||||
instance->bit_count++;
|
||||
} else {
|
||||
instance->bit_count = 0;
|
||||
memset(instance->decode_data, 0, 12);
|
||||
}
|
||||
|
||||
if(instance->bit_count == MITSUBISHI_V0_BIT_COUNT) {
|
||||
// Un-scramble for display
|
||||
uint8_t payload[12];
|
||||
memcpy(payload, instance->decode_data, 12);
|
||||
|
||||
// Undo Inversion
|
||||
for(int i = 0; i < 8; i++) payload[i] = ~payload[i];
|
||||
|
||||
// We need the counter to unscramble (bytes 4-5)
|
||||
uint16_t counter = (payload[4] << 8) | payload[5];
|
||||
|
||||
// Undo Scrambling
|
||||
uint8_t hi = (counter >> 8) & 0xFF;
|
||||
uint8_t lo = counter & 0xFF;
|
||||
uint8_t m1 = (hi & 0xAA) | (lo & 0x55);
|
||||
uint8_t m2 = (lo & 0xAA) | (hi & 0x55);
|
||||
uint8_t m3 = m1 ^ m2;
|
||||
for(int i = 0; i < 5; i++) payload[i] ^= m3;
|
||||
|
||||
instance->generic.serial = (payload[0] << 24) | (payload[1] << 16) | (payload[2] << 8) | payload[3];
|
||||
instance->generic.cnt = counter;
|
||||
instance->generic.btn = payload[6];
|
||||
instance->generic.data_count_bit = instance->bit_count;
|
||||
|
||||
if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context);
|
||||
instance->bit_count = 0;
|
||||
}
|
||||
}
|
||||
instance->te_last = duration;
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_mitsubishi_v0_get_hash_data(void* context) {
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = context;
|
||||
uint8_t hash = 0;
|
||||
for(size_t i = 0; i < 12; i++) {
|
||||
hash ^= instance->decode_data[i];
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* ff,
|
||||
SubGhzRadioPreset* preset) {
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = context;
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, ff, preset);
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_write_uint32(ff, "Serial", &instance->generic.serial, 1);
|
||||
flipper_format_write_uint32(ff, "Cnt", &instance->generic.cnt, 1);
|
||||
uint32_t btn = instance->generic.btn;
|
||||
flipper_format_write_uint32(ff, "Btn", &btn, 1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_v0_deserialize(void* context, FlipperFormat* ff) {
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(&instance->generic, ff, subghz_protocol_mitsubishi_v0_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_v0_get_string(void* context, FuriString* output) {
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Sn:%08lX Cnt:%04lX\r\n"
|
||||
"Btn:%02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
instance->generic.serial,
|
||||
instance->generic.cnt,
|
||||
instance->generic.btn);
|
||||
}
|
||||
@@ -1,111 +1,64 @@
|
||||
#include "mitsubishi_v0.h"
|
||||
#include <string.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
// Original implementation by @lupettohf
|
||||
#define TAG "MitsubishiProtocolV0"
|
||||
#define MITSUBISHI_V0_PREAMBLE_COUNT 100
|
||||
#define MITSUBISHI_V0_BIT_TE 250
|
||||
#define MITSUBISHI_V0_BIT_TE_GAP 500
|
||||
#define MITSUBISHI_V0_BIT_COUNT 96 // 12 bytes * 8 bits
|
||||
#define MITSUBISHI_V0_TOTAL_BURSTS 3
|
||||
#define MITSUBISHI_V0_INTER_BURST_GAP 25000
|
||||
|
||||
#define MITSUBISHI_BIT_COUNT 96
|
||||
#define MITSUBISHI_DATA_BYTES 12
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_mitsubishi_const = {
|
||||
static const SubGhzBlockConst subghz_protocol_mitsubishi_v0_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 80,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
MitsubishiDecoderStepReset = 0,
|
||||
MitsubishiDecoderStepDataSave,
|
||||
MitsubishiDecoderStepDataCheck,
|
||||
} MitsubishiDecoderStep;
|
||||
|
||||
typedef struct SubGhzProtocolDecoderMitsubishi SubGhzProtocolDecoderMitsubishi;
|
||||
|
||||
struct SubGhzProtocolDecoderMitsubishi {
|
||||
struct SubGhzProtocolDecoderMitsubishiV0 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t decoder_state;
|
||||
uint16_t bit_count;
|
||||
uint8_t decode_data[MITSUBISHI_DATA_BYTES];
|
||||
uint32_t te_last;
|
||||
uint8_t bit_count;
|
||||
uint8_t decode_data[12];
|
||||
};
|
||||
|
||||
static void mitsubishi_unscramble_payload(uint8_t* payload) {
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
payload[i] = (uint8_t)~payload[i];
|
||||
}
|
||||
struct SubGhzProtocolEncoderMitsubishiV0 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint16_t counter = ((uint16_t)payload[4] << 8) | payload[5];
|
||||
size_t upload_capacity;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// HELPER FUNCTIONS (Scrambling & Logic)
|
||||
// ============================================================================
|
||||
|
||||
static void mitsubishi_v0_scramble(uint8_t* payload, uint16_t counter) {
|
||||
uint8_t hi = (counter >> 8) & 0xFF;
|
||||
uint8_t lo = counter & 0xFF;
|
||||
uint8_t mask1 = (hi & 0xAAU) | (lo & 0x55U);
|
||||
uint8_t mask2 = (lo & 0xAAU) | (hi & 0x55U);
|
||||
uint8_t mask1 = (hi & 0xAA) | (lo & 0x55);
|
||||
uint8_t mask2 = (lo & 0xAA) | (hi & 0x55);
|
||||
uint8_t mask3 = mask1 ^ mask2;
|
||||
|
||||
for(uint8_t i = 0; i < 5; i++) {
|
||||
// Apply scrambling to first 5 bytes (as per sub_ROM_148BE @ 0x148BE)
|
||||
for(int i = 0; i < 5; i++) {
|
||||
payload[i] ^= mask3;
|
||||
}
|
||||
}
|
||||
|
||||
static inline bool mitsubishi_is_short(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_short) <
|
||||
subghz_protocol_mitsubishi_const.te_delta;
|
||||
}
|
||||
|
||||
static inline bool mitsubishi_is_long(uint32_t duration) {
|
||||
return DURATION_DIFF(duration, subghz_protocol_mitsubishi_const.te_long) <
|
||||
subghz_protocol_mitsubishi_const.te_delta;
|
||||
}
|
||||
|
||||
static void mitsubishi_reset_payload(SubGhzProtocolDecoderMitsubishi* instance) {
|
||||
instance->bit_count = 0;
|
||||
memset(instance->decode_data, 0, sizeof(instance->decode_data));
|
||||
}
|
||||
|
||||
static bool mitsubishi_collect_pair(
|
||||
SubGhzProtocolDecoderMitsubishi* instance,
|
||||
uint32_t high,
|
||||
uint32_t low) {
|
||||
bool bit_value;
|
||||
|
||||
if(mitsubishi_is_short(high) && mitsubishi_is_long(low)) {
|
||||
bit_value = true;
|
||||
} else if(mitsubishi_is_long(high) && mitsubishi_is_short(low)) {
|
||||
bit_value = false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t bit_index = instance->bit_count;
|
||||
if(bit_index < MITSUBISHI_BIT_COUNT) {
|
||||
if(bit_value) {
|
||||
uint8_t byte_index = bit_index >> 3;
|
||||
uint8_t bit_position = 7 - (bit_index & 0x07);
|
||||
instance->decode_data[byte_index] |= (1U << bit_position);
|
||||
}
|
||||
instance->bit_count++;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void mitsubishi_publish_frame(SubGhzProtocolDecoderMitsubishi* instance) {
|
||||
uint8_t payload[MITSUBISHI_DATA_BYTES];
|
||||
memcpy(payload, instance->decode_data, sizeof(payload));
|
||||
mitsubishi_unscramble_payload(payload);
|
||||
|
||||
instance->generic.data_count_bit = instance->bit_count;
|
||||
instance->generic.serial = ((uint32_t)payload[0] << 24) | ((uint32_t)payload[1] << 16) |
|
||||
((uint32_t)payload[2] << 8) | payload[3];
|
||||
instance->generic.cnt = ((uint16_t)payload[4] << 8) | payload[5];
|
||||
instance->generic.btn = payload[6];
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
// Apply inversion (first 8 bytes) — firmware XORs bytes 1..8 with 0xFF in sub_ROM_151E8
|
||||
for(int i = 0; i < 8; i++) {
|
||||
payload[i] = ~payload[i];
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// PROTOCOL INTERFACE DEFINITIONS
|
||||
// ============================================================================
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_mitsubishi_v0_decoder = {
|
||||
.alloc = subghz_protocol_decoder_mitsubishi_v0_alloc,
|
||||
.free = subghz_protocol_decoder_mitsubishi_v0_free,
|
||||
@@ -118,25 +71,141 @@ const SubGhzProtocolDecoder subghz_protocol_mitsubishi_v0_decoder = {
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_mitsubishi_v0_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
.alloc = subghz_protocol_encoder_mitsubishi_v0_alloc,
|
||||
.free = subghz_protocol_encoder_mitsubishi_v0_free,
|
||||
.deserialize = subghz_protocol_encoder_mitsubishi_v0_deserialize,
|
||||
.stop = subghz_protocol_encoder_mitsubishi_v0_stop,
|
||||
.yield = subghz_protocol_encoder_mitsubishi_v0_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_mitsubishi_v0 = {
|
||||
.name = MITSUBISHI_PROTOCOL_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_868 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_mitsubishi_v0_decoder,
|
||||
.encoder = &subghz_protocol_mitsubishi_v0_encoder,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ENCODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
void* subghz_protocol_encoder_mitsubishi_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderMitsubishiV0* instance = calloc(1, sizeof(SubGhzProtocolEncoderMitsubishiV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &subghz_protocol_mitsubishi_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 5;
|
||||
instance->encoder.size_upload = 0;
|
||||
// Preamble + Sync + (12 bytes * 8 bits * 2 elements) + Gap
|
||||
instance->upload_capacity = (MITSUBISHI_V0_PREAMBLE_COUNT * 2) + 20 + (MITSUBISHI_V0_BIT_COUNT * 2) + 2;
|
||||
instance->encoder.upload = calloc(instance->upload_capacity, sizeof(LevelDuration));
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_mitsubishi_v0_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderMitsubishiV0* instance = context;
|
||||
if(instance->encoder.upload) free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static void subghz_protocol_encoder_mitsubishi_v0_get_upload(SubGhzProtocolEncoderMitsubishiV0* instance) {
|
||||
size_t index = 0;
|
||||
uint8_t payload[12] = {0};
|
||||
|
||||
// Pack data
|
||||
payload[0] = (instance->generic.serial >> 24) & 0xFF;
|
||||
payload[1] = (instance->generic.serial >> 16) & 0xFF;
|
||||
payload[2] = (instance->generic.serial >> 8) & 0xFF;
|
||||
payload[3] = instance->generic.serial & 0xFF;
|
||||
payload[4] = (instance->generic.cnt >> 8) & 0xFF;
|
||||
payload[5] = instance->generic.cnt & 0xFF;
|
||||
payload[6] = instance->generic.btn;
|
||||
payload[9] = 0x5A; // ID byte (firmware: byte_RAM_59 = 0x5A in sub_ROM_151E8 @ 0x15258)
|
||||
payload[10] = 0xFF;
|
||||
payload[11] = 0xFF;
|
||||
|
||||
mitsubishi_v0_scramble(payload, (uint16_t)instance->generic.cnt);
|
||||
|
||||
// Preamble
|
||||
for(int i = 0; i < MITSUBISHI_V0_PREAMBLE_COUNT; i++) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE);
|
||||
}
|
||||
|
||||
// Sync pulses (firmware: 96-iteration loop in sub_ROM_1526C @ 0x152A0)
|
||||
for(int i = 0; i < 95; i++) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE);
|
||||
}
|
||||
|
||||
// Data bits
|
||||
for(int i = 0; i < 12; i++) {
|
||||
for(int bit = 7; bit >= 0; bit--) {
|
||||
bool curr = (payload[i] >> bit) & 1;
|
||||
if(curr) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE_GAP);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, MITSUBISHI_V0_BIT_TE_GAP);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, MITSUBISHI_V0_BIT_TE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_mitsubishi_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
SubGhzProtocolEncoderMitsubishiV0* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(ret != SubGhzProtocolStatusOk) break;
|
||||
if(!flipper_format_rewind(flipper_format)) break;
|
||||
flipper_format_read_uint32(flipper_format, "Serial", &instance->generic.serial, 1);
|
||||
flipper_format_read_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1);
|
||||
uint32_t btn_temp = 0;
|
||||
flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1);
|
||||
instance->generic.btn = (uint8_t)btn_temp;
|
||||
|
||||
subghz_protocol_encoder_mitsubishi_v0_get_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_mitsubishi_v0_stop(void* context) {
|
||||
((SubGhzProtocolEncoderMitsubishiV0*)context)->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_mitsubishi_v0_yield(void* context) {
|
||||
SubGhzProtocolEncoderMitsubishiV0* instance = context;
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0) 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;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// DECODER IMPLEMENTATION
|
||||
// ============================================================================
|
||||
|
||||
void* subghz_protocol_decoder_mitsubishi_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = calloc(1, sizeof(SubGhzProtocolDecoderMitsubishi));
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderMitsubishiV0));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_mitsubishi_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
@@ -144,68 +213,73 @@ void* subghz_protocol_decoder_mitsubishi_v0_alloc(SubGhzEnvironment* environment
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_v0_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
free(instance);
|
||||
free(context);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_v0_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
instance->decoder_state = MitsubishiDecoderStepReset;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->generic.data_count_bit = 0;
|
||||
mitsubishi_reset_payload(instance);
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = context;
|
||||
instance->bit_count = 0;
|
||||
memset(instance->decode_data, 0, 12);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_v0_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = context;
|
||||
|
||||
switch(instance->decoder_state) {
|
||||
case MitsubishiDecoderStepReset:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder_state = MitsubishiDecoderStepDataCheck;
|
||||
}
|
||||
break;
|
||||
// Simplified Pulse Distance/Width Decoder
|
||||
uint32_t te = subghz_protocol_mitsubishi_v0_const.te_short;
|
||||
uint32_t te2 = subghz_protocol_mitsubishi_v0_const.te_long;
|
||||
uint32_t delta = subghz_protocol_mitsubishi_v0_const.te_delta;
|
||||
|
||||
case MitsubishiDecoderStepDataSave:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder_state = MitsubishiDecoderStepDataCheck;
|
||||
if(!level) {
|
||||
// Logic '1': HIGH 250, LOW 500
|
||||
// Logic '0': HIGH 500, LOW 250
|
||||
if(DURATION_DIFF(instance->te_last, te) < delta && DURATION_DIFF(duration, te2) < delta) {
|
||||
// bit 1
|
||||
instance->decode_data[instance->bit_count / 8] |= (1 << (7 - (instance->bit_count % 8)));
|
||||
instance->bit_count++;
|
||||
} else if(DURATION_DIFF(instance->te_last, te2) < delta && DURATION_DIFF(duration, te) < delta) {
|
||||
// bit 0
|
||||
instance->bit_count++;
|
||||
} else {
|
||||
instance->decoder_state = MitsubishiDecoderStepReset;
|
||||
mitsubishi_reset_payload(instance);
|
||||
instance->bit_count = 0;
|
||||
memset(instance->decode_data, 0, 12);
|
||||
}
|
||||
break;
|
||||
|
||||
case MitsubishiDecoderStepDataCheck:
|
||||
if(!level) {
|
||||
if(mitsubishi_collect_pair(instance, instance->decoder.te_last, duration)) {
|
||||
if(instance->bit_count >= MITSUBISHI_BIT_COUNT) {
|
||||
mitsubishi_publish_frame(instance);
|
||||
mitsubishi_reset_payload(instance);
|
||||
instance->decoder_state = MitsubishiDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder_state = MitsubishiDecoderStepDataSave;
|
||||
}
|
||||
} else {
|
||||
mitsubishi_reset_payload(instance);
|
||||
instance->decoder_state = MitsubishiDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.te_last = duration;
|
||||
if(instance->bit_count == MITSUBISHI_V0_BIT_COUNT) {
|
||||
// Un-scramble for display
|
||||
uint8_t payload[12];
|
||||
memcpy(payload, instance->decode_data, 12);
|
||||
|
||||
// Undo Inversion
|
||||
for(int i = 0; i < 8; i++) payload[i] = ~payload[i];
|
||||
|
||||
// We need the counter to unscramble (bytes 4-5)
|
||||
uint16_t counter = (payload[4] << 8) | payload[5];
|
||||
|
||||
// Undo Scrambling
|
||||
uint8_t hi = (counter >> 8) & 0xFF;
|
||||
uint8_t lo = counter & 0xFF;
|
||||
uint8_t m1 = (hi & 0xAA) | (lo & 0x55);
|
||||
uint8_t m2 = (lo & 0xAA) | (hi & 0x55);
|
||||
uint8_t m3 = m1 ^ m2;
|
||||
for(int i = 0; i < 5; i++) payload[i] ^= m3;
|
||||
|
||||
instance->generic.serial = (payload[0] << 24) | (payload[1] << 16) | (payload[2] << 8) | payload[3];
|
||||
instance->generic.cnt = counter;
|
||||
instance->generic.btn = payload[6];
|
||||
instance->generic.data_count_bit = instance->bit_count;
|
||||
|
||||
if(instance->base.callback) instance->base.callback(&instance->base, instance->base.context);
|
||||
instance->bit_count = 0;
|
||||
}
|
||||
break;
|
||||
}
|
||||
instance->te_last = duration;
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_mitsubishi_v0_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = context;
|
||||
uint8_t hash = 0;
|
||||
for(size_t i = 0; i < sizeof(instance->decode_data); i++) {
|
||||
for(size_t i = 0; i < 12; i++) {
|
||||
hash ^= instance->decode_data[i];
|
||||
}
|
||||
return hash;
|
||||
@@ -213,44 +287,26 @@ uint8_t subghz_protocol_decoder_mitsubishi_v0_get_hash_data(void* context) {
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
FlipperFormat* ff,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = context;
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_serialize(&instance->generic, ff, preset);
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_write_uint32(flipper_format, "Serial", &instance->generic.serial, 1);
|
||||
flipper_format_write_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1);
|
||||
flipper_format_write_uint32(ff, "Serial", &instance->generic.serial, 1);
|
||||
flipper_format_write_uint32(ff, "Cnt", &instance->generic.cnt, 1);
|
||||
uint32_t btn = instance->generic.btn;
|
||||
flipper_format_write_uint32(flipper_format, "Btn", &btn, 1);
|
||||
flipper_format_write_uint32(ff, "Btn", &btn, 1);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_mitsubishi_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_mitsubishi_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_read_uint32(flipper_format, "Serial", &instance->generic.serial, 1);
|
||||
flipper_format_read_uint32(flipper_format, "Cnt", &instance->generic.cnt, 1);
|
||||
uint32_t btn = 0;
|
||||
flipper_format_read_uint32(flipper_format, "Btn", &btn, 1);
|
||||
instance->generic.btn = (uint8_t)btn;
|
||||
}
|
||||
|
||||
return ret;
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mitsubishi_v0_deserialize(void* context, FlipperFormat* ff) {
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(&instance->generic, ff, subghz_protocol_mitsubishi_v0_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mitsubishi_v0_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMitsubishi* instance = context;
|
||||
SubGhzProtocolDecoderMitsubishiV0* instance = context;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
|
||||
@@ -1,374 +0,0 @@
|
||||
#include "porsche_touareg.h"
|
||||
#include <string.h>
|
||||
|
||||
// Original implementation by @lupettohf
|
||||
|
||||
#define PORSCHE_CAYENNE_BIT_COUNT 64
|
||||
#define PC_TE_SYNC 3370U
|
||||
#define PC_TE_GAP 5930U
|
||||
#define PC_SYNC_MIN 15
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_porsche_cayenne_const = {
|
||||
.te_short = 1680,
|
||||
.te_long = 3370,
|
||||
.te_delta = 500,
|
||||
.min_count_bit_for_found = PORSCHE_CAYENNE_BIT_COUNT,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
PorscheCayenneDecoderStepReset = 0,
|
||||
PorscheCayenneDecoderStepSync,
|
||||
PorscheCayenneDecoderStepGapHigh,
|
||||
PorscheCayenneDecoderStepGapLow,
|
||||
PorscheCayenneDecoderStepData,
|
||||
} PorscheCayenneDecoderStep;
|
||||
|
||||
struct SubGhzProtocolDecoderPorscheCayenne {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint16_t sync_count;
|
||||
uint64_t raw_data;
|
||||
uint8_t bit_count;
|
||||
};
|
||||
|
||||
static void porsche_cayenne_compute_frame(
|
||||
uint32_t serial24,
|
||||
uint8_t btn,
|
||||
uint16_t counter,
|
||||
uint8_t frame_type,
|
||||
uint8_t* pkt) {
|
||||
uint8_t b0 = (uint8_t)((btn << 4) | (frame_type & 0x07));
|
||||
uint8_t b1 = (serial24 >> 16) & 0xFF;
|
||||
uint8_t b2 = (serial24 >> 8) & 0xFF;
|
||||
uint8_t b3 = serial24 & 0xFF;
|
||||
|
||||
uint16_t cnt = counter + 1;
|
||||
uint8_t cnt_lo = cnt & 0xFF;
|
||||
uint8_t cnt_hi = (cnt >> 8) & 0xFF;
|
||||
|
||||
uint8_t r_h = b3;
|
||||
uint8_t r_m = b1;
|
||||
uint8_t r_l = b2;
|
||||
|
||||
#define ROTATE24(rh, rm, rl) \
|
||||
do { \
|
||||
uint8_t _ch = ((rh) >> 7) & 1U; \
|
||||
uint8_t _cm = ((rm) >> 7) & 1U; \
|
||||
uint8_t _cl = ((rl) >> 7) & 1U; \
|
||||
(rh) = (uint8_t)(((rh) << 1) | _cm); \
|
||||
(rm) = (uint8_t)(((rm) << 1) | _cl); \
|
||||
(rl) = (uint8_t)(((rl) << 1) | _ch); \
|
||||
} while(0)
|
||||
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
ROTATE24(r_h, r_m, r_l);
|
||||
}
|
||||
for(uint16_t i = 0; i < cnt_lo; i++) {
|
||||
ROTATE24(r_h, r_m, r_l);
|
||||
}
|
||||
|
||||
#undef ROTATE24
|
||||
|
||||
uint8_t a9a = r_h ^ b0;
|
||||
|
||||
uint8_t nb9b_p1 = (uint8_t)((~cnt_lo << 2) & 0xFC) ^ r_m;
|
||||
uint8_t nb9b_p2 = (uint8_t)((~cnt_hi << 2) & 0xFC) ^ r_m;
|
||||
uint8_t nb9b_p3 = (uint8_t)((~cnt_hi >> 6) & 0x03) ^ r_m;
|
||||
uint8_t a9b = (nb9b_p1 & 0xCC) | (nb9b_p2 & 0x30) | (nb9b_p3 & 0x03);
|
||||
|
||||
uint8_t nb9c_p1 = (uint8_t)((~cnt_lo >> 2) & 0x3F) ^ r_l;
|
||||
uint8_t nb9c_p2 = (uint8_t)((~cnt_hi & 0x03) << 6) ^ r_l;
|
||||
uint8_t nb9c_p3 = (uint8_t)((~cnt_hi >> 2) & 0x3F) ^ r_l;
|
||||
uint8_t a9c = (nb9c_p1 & 0x33) | (nb9c_p2 & 0xC0) | (nb9c_p3 & 0x0C);
|
||||
|
||||
pkt[0] = b0;
|
||||
pkt[1] = b1;
|
||||
pkt[2] = b2;
|
||||
pkt[3] = b3;
|
||||
pkt[4] = (uint8_t)(((a9a >> 2) & 0x3F) | ((~cnt_lo & 0x03U) << 6));
|
||||
pkt[5] = (uint8_t)((~cnt_lo & 0xC0U) | ((a9a & 0x03U) << 4) | (a9b & 0x0CU) |
|
||||
((~cnt_lo >> 2) & 0x03U));
|
||||
pkt[6] = (uint8_t)(((a9b & 0x03U) << 6) | ((a9c >> 2) & 0x3CU) | ((~cnt_lo >> 4) & 0x03U));
|
||||
pkt[7] = (uint8_t)(((a9b >> 4) & 0x0FU) | ((a9c & 0x0FU) << 4));
|
||||
}
|
||||
|
||||
static void porsche_cayenne_parse_data(SubGhzProtocolDecoderPorscheCayenne* instance) {
|
||||
uint8_t pkt[8];
|
||||
uint64_t raw = instance->generic.data;
|
||||
|
||||
for(int8_t i = 7; i >= 0; i--) {
|
||||
pkt[i] = (uint8_t)(raw & 0xFF);
|
||||
raw >>= 8;
|
||||
}
|
||||
|
||||
instance->generic.serial = ((uint32_t)pkt[1] << 16) | ((uint32_t)pkt[2] << 8) | pkt[3];
|
||||
instance->generic.btn = (uint8_t)(pkt[0] >> 4);
|
||||
instance->generic.cnt = 0;
|
||||
|
||||
uint8_t frame_type = pkt[0] & 0x07;
|
||||
uint8_t try_pkt[8];
|
||||
for(uint16_t try_cnt = 1; try_cnt <= 256; try_cnt++) {
|
||||
porsche_cayenne_compute_frame(
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
(uint16_t)(try_cnt - 1),
|
||||
frame_type,
|
||||
try_pkt);
|
||||
if(try_pkt[4] == pkt[4] && try_pkt[5] == pkt[5] && try_pkt[6] == pkt[6] &&
|
||||
try_pkt[7] == pkt[7]) {
|
||||
instance->generic.cnt = try_cnt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void porsche_cayenne_publish_frame(SubGhzProtocolDecoderPorscheCayenne* instance) {
|
||||
instance->generic.data = instance->raw_data;
|
||||
instance->generic.data_count_bit = PORSCHE_CAYENNE_BIT_COUNT;
|
||||
porsche_cayenne_parse_data(instance);
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_porsche_cayenne_decoder = {
|
||||
.alloc = subghz_protocol_decoder_porsche_cayenne_alloc,
|
||||
.free = subghz_protocol_decoder_porsche_cayenne_free,
|
||||
.feed = subghz_protocol_decoder_porsche_cayenne_feed,
|
||||
.reset = subghz_protocol_decoder_porsche_cayenne_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_porsche_cayenne_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_porsche_cayenne_serialize,
|
||||
.deserialize = subghz_protocol_decoder_porsche_cayenne_deserialize,
|
||||
.get_string = subghz_protocol_decoder_porsche_cayenne_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_porsche_cayenne_encoder = {
|
||||
.alloc = NULL,
|
||||
.free = NULL,
|
||||
.deserialize = NULL,
|
||||
.stop = NULL,
|
||||
.yield = NULL,
|
||||
};
|
||||
|
||||
const SubGhzProtocol porsche_touareg_protocol = {
|
||||
.name = PORSCHE_CAYENNE_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_868 | SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable | SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save,
|
||||
.decoder = &subghz_protocol_porsche_cayenne_decoder,
|
||||
.encoder = &subghz_protocol_porsche_cayenne_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_decoder_porsche_cayenne_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolDecoderPorscheCayenne));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &porsche_touareg_protocol;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_porsche_cayenne_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_porsche_cayenne_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->sync_count = 0;
|
||||
instance->raw_data = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->generic.data = 0;
|
||||
instance->generic.data_count_bit = 0;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_porsche_cayenne_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
|
||||
const uint32_t te_short = subghz_protocol_porsche_cayenne_const.te_short;
|
||||
const uint32_t te_long = subghz_protocol_porsche_cayenne_const.te_long;
|
||||
const uint32_t te_delta = subghz_protocol_porsche_cayenne_const.te_delta;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case PorscheCayenneDecoderStepReset:
|
||||
if(!level && DURATION_DIFF(duration, PC_TE_SYNC) < te_delta) {
|
||||
instance->sync_count = 1;
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepSync;
|
||||
}
|
||||
break;
|
||||
|
||||
case PorscheCayenneDecoderStepSync:
|
||||
if(level) {
|
||||
if(DURATION_DIFF(duration, PC_TE_SYNC) < te_delta) {
|
||||
// keep collecting sync pairs
|
||||
} else if(
|
||||
instance->sync_count >= PC_SYNC_MIN &&
|
||||
DURATION_DIFF(duration, PC_TE_GAP) < te_delta) {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepGapLow;
|
||||
} else {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
if(DURATION_DIFF(duration, PC_TE_SYNC) < te_delta) {
|
||||
instance->sync_count++;
|
||||
} else if(
|
||||
instance->sync_count >= PC_SYNC_MIN &&
|
||||
DURATION_DIFF(duration, PC_TE_GAP) < te_delta) {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepGapHigh;
|
||||
} else {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case PorscheCayenneDecoderStepGapHigh:
|
||||
if(level && DURATION_DIFF(duration, PC_TE_GAP) < te_delta) {
|
||||
instance->raw_data = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepData;
|
||||
} else {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case PorscheCayenneDecoderStepGapLow:
|
||||
if(!level && DURATION_DIFF(duration, PC_TE_GAP) < te_delta) {
|
||||
instance->raw_data = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepData;
|
||||
} else {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case PorscheCayenneDecoderStepData:
|
||||
if(level) {
|
||||
bool bit_value = false;
|
||||
if(DURATION_DIFF(instance->decoder.te_last, te_short) < te_delta &&
|
||||
DURATION_DIFF(duration, te_long) < te_delta) {
|
||||
bit_value = false;
|
||||
} else if(
|
||||
DURATION_DIFF(instance->decoder.te_last, te_long) < te_delta &&
|
||||
DURATION_DIFF(duration, te_short) < te_delta) {
|
||||
bit_value = true;
|
||||
} else {
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->raw_data = (instance->raw_data << 1) | (bit_value ? 1U : 0U);
|
||||
instance->bit_count++;
|
||||
|
||||
if(instance->bit_count >= PORSCHE_CAYENNE_BIT_COUNT) {
|
||||
porsche_cayenne_publish_frame(instance);
|
||||
instance->decoder.parser_step = PorscheCayenneDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.te_last = duration;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_porsche_cayenne_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
SubGhzBlockDecoder decoder = {
|
||||
.decode_data = instance->generic.data,
|
||||
.decode_count_bit = instance->generic.data_count_bit,
|
||||
};
|
||||
return subghz_protocol_blocks_get_hash_data(&decoder, (decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t serial = instance->generic.serial & 0xFFFFFF;
|
||||
flipper_format_write_uint32(flipper_format, "Serial", &serial, 1);
|
||||
uint32_t cnt = instance->generic.cnt;
|
||||
flipper_format_write_uint32(flipper_format, "Cnt", &cnt, 1);
|
||||
uint32_t btn = instance->generic.btn;
|
||||
flipper_format_write_uint32(flipper_format, "Btn", &btn, 1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_porsche_cayenne_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
porsche_cayenne_parse_data(instance);
|
||||
|
||||
uint32_t serial = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Serial", &serial, 1)) {
|
||||
instance->generic.serial = serial & 0xFFFFFF;
|
||||
}
|
||||
|
||||
uint32_t cnt = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &cnt, 1)) {
|
||||
instance->generic.cnt = cnt;
|
||||
}
|
||||
|
||||
uint32_t btn = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &btn, 1)) {
|
||||
instance->generic.btn = (uint8_t)btn;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_porsche_cayenne_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderPorscheCayenne* instance = context;
|
||||
|
||||
uint8_t frame_type = (uint8_t)((instance->generic.data >> 56) & 0x07);
|
||||
const char* frame_type_name = "??";
|
||||
if(frame_type == 0x02) {
|
||||
frame_type_name = "First";
|
||||
} else if(frame_type == 0x01) {
|
||||
frame_type_name = "Cont";
|
||||
} else if(frame_type == 0x04) {
|
||||
frame_type_name = "Final";
|
||||
}
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Sn:%06lX Btn:%X\r\n"
|
||||
"Cnt:%04lX FT:%s\r\n"
|
||||
"Raw:%08lX%08lX\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(unsigned long)(instance->generic.serial & 0xFFFFFF),
|
||||
(unsigned int)instance->generic.btn,
|
||||
(unsigned long)instance->generic.cnt,
|
||||
frame_type_name,
|
||||
(unsigned long)(instance->generic.data >> 32),
|
||||
(unsigned long)(instance->generic.data & 0xFFFFFFFF));
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#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/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
|
||||
#define PORSCHE_CAYENNE_PROTOCOL_NAME "Porsche Touareg"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderPorscheCayenne SubGhzProtocolDecoderPorscheCayenne;
|
||||
|
||||
extern const SubGhzProtocol porsche_touareg_protocol;
|
||||
|
||||
void* subghz_protocol_decoder_porsche_cayenne_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_porsche_cayenne_free(void* context);
|
||||
void subghz_protocol_decoder_porsche_cayenne_reset(void* context);
|
||||
void subghz_protocol_decoder_porsche_cayenne_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_porsche_cayenne_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_porsche_cayenne_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_porsche_cayenne_get_string(void* context, FuriString* output);
|
||||
@@ -44,7 +44,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
//&subghz_protocol_honeywell,
|
||||
//&subghz_protocol_legrand,
|
||||
&subghz_protocol_dickert_mahs,
|
||||
//&subghz_protocol_gangqi,
|
||||
&subghz_protocol_gangqi,
|
||||
&subghz_protocol_marantec24,
|
||||
//&subghz_protocol_hollarm,
|
||||
&subghz_protocol_hay21,
|
||||
@@ -62,6 +62,7 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_psa,
|
||||
&subghz_protocol_fiat_spa,
|
||||
&subghz_protocol_fiat_marelli,
|
||||
// &subghz_protocol_bmw_cas4,
|
||||
&subghz_protocol_subaru,
|
||||
&subghz_protocol_mazda_siemens,
|
||||
&subghz_protocol_kia_v0,
|
||||
@@ -75,16 +76,15 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_star_line,
|
||||
&subghz_protocol_scher_khan,
|
||||
&subghz_protocol_sheriff_cfm,
|
||||
&subghz_protocol_renault_hitag,
|
||||
&subghz_protocol_renault_siemens,
|
||||
&subghz_protocol_renault_valeo,
|
||||
&subghz_protocol_renault_valeo_fsk,
|
||||
&subghz_protocol_renault_marelli,
|
||||
&subghz_protocol_honda_v1,
|
||||
// until fix &subghz_protocol_honda,
|
||||
&subghz_protocol_chrysler,
|
||||
&subghz_protocol_kia_v7,
|
||||
&subghz_protocol_mazda_v0,
|
||||
//&honda_static_protocol,
|
||||
&ford_protocol_v1,
|
||||
&honda_static_protocol,
|
||||
&fiat_protocol_v0,
|
||||
&subghz_protocol_fiat_v1,
|
||||
&ford_protocol_v2,
|
||||
&ford_protocol_v3,
|
||||
|
||||
};
|
||||
|
||||
const SubGhzProtocolRegistry subghz_protocol_registry = {
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#include "psa.h"
|
||||
#include "fiat_spa.h"
|
||||
#include "fiat_marelli.h"
|
||||
#include "bmw_cas4.h"
|
||||
#include "subaru.h"
|
||||
#include "kia_generic.h"
|
||||
#include "kia_v0.h"
|
||||
@@ -77,13 +78,10 @@
|
||||
#include "star_line.h"
|
||||
#include "scher_khan.h"
|
||||
#include "sheriff_cfm.h"
|
||||
#include "renault_hitag.h"
|
||||
#include "renault_siemens.h"
|
||||
#include "renault_valeo.h"
|
||||
#include "renault_valeo_fsk.h"
|
||||
#include "renault_marelli.h"
|
||||
#include "ford_v1.h"
|
||||
#include "honda_v1.h"
|
||||
#include "chrysler.h"
|
||||
#include "honda_static.h"
|
||||
#include "fiat_v0.h"
|
||||
#include "fiat_v1.h"
|
||||
#include "mazda_v0.h"
|
||||
#include "kia_v7.h"
|
||||
#include "ford_v1.h"
|
||||
#include "ford_v2.h"
|
||||
#include "ford_v3.h"
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+1240
-1329
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.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>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* =========================================================
|
||||
* PROTOCOL NAME
|
||||
* ========================================================= */
|
||||
|
||||
#define SUBGHZ_PROTOCOL_PSA2_NAME "PSA OLD"
|
||||
|
||||
/* =========================================================
|
||||
* FORWARD DECLARATIONS — opaque handles
|
||||
* ========================================================= */
|
||||
|
||||
typedef struct SubGhzProtocolDecoderPSA SubGhzProtocolDecoderPSA;
|
||||
typedef struct SubGhzProtocolEncoderPSA SubGhzProtocolEncoderPSA;
|
||||
|
||||
/* =========================================================
|
||||
* PROTOCOL DESCRIPTORS — exported singletons
|
||||
* ========================================================= */
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_psa_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_psa_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_psa2;
|
||||
|
||||
/* =========================================================
|
||||
* DECODER API
|
||||
* ========================================================= */
|
||||
|
||||
/**
|
||||
* Allocate a PSA decoder instance.
|
||||
*
|
||||
* @param environment SubGHz environment (may be NULL / unused)
|
||||
* @return Opaque pointer to SubGhzProtocolDecoderPSA
|
||||
*/
|
||||
void* subghz_protocol_decoder_psa2_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free a PSA decoder instance.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
*/
|
||||
void subghz_protocol_decoder_psa2_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset the decoder state machine to its initial state.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
*/
|
||||
void subghz_protocol_decoder_psa2_reset(void* context);
|
||||
|
||||
/**
|
||||
* Feed one pulse/gap sample into the decoder state machine.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
* @param level true = RF high (pulse), false = RF low (gap)
|
||||
* @param duration Duration of this level in microseconds
|
||||
*/
|
||||
void subghz_protocol_decoder_psa2_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Return a one-byte hash of the most recently decoded packet.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
* @return Hash byte
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_psa2_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize the most recently decoded packet into a FlipperFormat stream.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
* @param ff Open FlipperFormat file handle
|
||||
* @param preset Radio preset in use
|
||||
* @return SubGhzProtocolStatusOk on success
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_psa2_serialize(
|
||||
void* context,
|
||||
FlipperFormat* ff,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize a previously saved packet from a FlipperFormat stream
|
||||
* into the decoder instance.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
* @param ff Open FlipperFormat file handle (positioned at start)
|
||||
* @return SubGhzProtocolStatusOk on success
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_psa2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* ff);
|
||||
|
||||
/**
|
||||
* Build a human-readable description of the most recently decoded packet.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_decoder_psa_alloc()
|
||||
* @param output FuriString to write the description into (cleared first)
|
||||
*/
|
||||
void subghz_protocol_decoder_psa2_get_string(void* context, FuriString* output);
|
||||
|
||||
/* =========================================================
|
||||
* ENCODER API
|
||||
* ========================================================= */
|
||||
|
||||
/**
|
||||
* Allocate a PSA encoder instance.
|
||||
*
|
||||
* @param environment SubGHz environment (may be NULL / unused)
|
||||
* @return Opaque pointer to SubGhzProtocolEncoderPSA
|
||||
*/
|
||||
void* subghz_protocol_encoder_psa2_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free a PSA encoder instance.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_encoder_psa_alloc()
|
||||
*/
|
||||
void subghz_protocol_encoder_psa2_free(void* context);
|
||||
|
||||
/**
|
||||
* Load transmit data from a FlipperFormat stream into the encoder.
|
||||
* Rebuilds the upload buffer ready for transmission.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_encoder_psa_alloc()
|
||||
* @param ff Open FlipperFormat file handle (will be rewound internally)
|
||||
* @return SubGhzProtocolStatusOk on success
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_psa2_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* ff);
|
||||
|
||||
/**
|
||||
* Stop an in-progress transmission immediately.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_encoder_psa_alloc()
|
||||
*/
|
||||
void subghz_protocol_encoder_psa2_stop(void* context);
|
||||
|
||||
/**
|
||||
* Yield the next LevelDuration sample from the upload buffer.
|
||||
* Called repeatedly by the SubGHz radio driver during transmission.
|
||||
*
|
||||
* @param context Pointer returned by subghz_protocol_encoder_psa_alloc()
|
||||
* @return Next LevelDuration, or level_duration_reset() when done
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_psa2_yield(void* context);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -1,14 +0,0 @@
|
||||
#include "renault_classifier.h"
|
||||
#include <stdint.h>
|
||||
|
||||
// Hitag: 40–63 bits
|
||||
// Siemens: 64–80 bits
|
||||
// Valeo: 81–96 bits
|
||||
// Marelli: 97–110 bits
|
||||
RenaultProtocolType renault_classify(uint8_t bits) {
|
||||
if(bits >= 40 && bits <= 63) return RenaultProtoHitag;
|
||||
if(bits >= 64 && bits <= 80) return RenaultProtoSiemens;
|
||||
if(bits >= 81 && bits <= 96) return RenaultProtoValeo;
|
||||
if(bits >= 97 && bits <= 110) return RenaultProtoMarelli;
|
||||
return RenaultProtoUnknown;
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
typedef enum {
|
||||
RenaultProtoUnknown,
|
||||
RenaultProtoHitag,
|
||||
RenaultProtoSiemens,
|
||||
RenaultProtoValeo,
|
||||
RenaultProtoMarelli,
|
||||
} RenaultProtocolType;
|
||||
|
||||
RenaultProtocolType renault_classify(uint8_t bits);
|
||||
@@ -1,292 +0,0 @@
|
||||
#include "renault_hitag.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
|
||||
#define TAG "RenaultHitag"
|
||||
|
||||
// Hitag2 / PCF7936 keyfob — OOK PWM
|
||||
// te_short ≈ 200 µs (bit 0 mark)
|
||||
// te_long ≈ 400 µs (bit 1 mark)
|
||||
// Space between bits ≈ te_short
|
||||
// Gap between frames > 3 × te_long
|
||||
|
||||
#define HITAG_TE_SHORT 200
|
||||
#define HITAG_TE_LONG 400
|
||||
#define HITAG_TE_DELTA 120
|
||||
#define HITAG_MIN_BITS 40
|
||||
#define HITAG_MAX_BITS 80
|
||||
#define HITAG_GAP_MIN (HITAG_TE_LONG * 3)
|
||||
|
||||
typedef enum {
|
||||
HitagStepReset = 0,
|
||||
HitagStepWaitMark,
|
||||
HitagStepWaitSpace,
|
||||
} HitagStep;
|
||||
|
||||
// ─── Struct ──────────────────────────────────────────────────────────────────
|
||||
|
||||
typedef struct {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint64_t data;
|
||||
uint8_t bit_count;
|
||||
uint8_t parser_step;
|
||||
} RenaultHitagDecoder;
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
static inline uint32_t hitag_abs_diff(uint32_t a, uint32_t b) {
|
||||
return (a > b) ? (a - b) : (b - a);
|
||||
}
|
||||
|
||||
static bool renault_hitag_button_is_valid(uint8_t btn) {
|
||||
// Keep permissive set for newer variants but reject obvious garbage values.
|
||||
return (btn <= 0x0B) || (btn == 0x0D);
|
||||
}
|
||||
|
||||
static bool renault_hitag_frame_is_plausible(RenaultHitagDecoder* inst) {
|
||||
if(inst->bit_count < HITAG_MIN_BITS || inst->bit_count > HITAG_MAX_BITS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Typical packet sizes observed in the field.
|
||||
if(inst->bit_count < 48U) {
|
||||
if(inst->bit_count != 40U) return false;
|
||||
} else if((inst->bit_count % 4U) != 0U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t btn = (uint8_t)((inst->data >> (inst->bit_count - 4U)) & 0x0FU);
|
||||
if(!renault_hitag_button_is_valid(btn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t serial = (inst->bit_count >= 48U) ?
|
||||
(uint32_t)((inst->data >> 16U) & 0x0FFFFFFFU) :
|
||||
(uint32_t)((inst->data >> 12U) & 0x00FFFFFFU);
|
||||
return serial != 0U;
|
||||
}
|
||||
|
||||
static void renault_hitag_extract_fields(RenaultHitagDecoder* inst) {
|
||||
uint8_t total = inst->generic.data_count_bit;
|
||||
if(total >= 48) {
|
||||
inst->generic.btn = (uint8_t)((inst->generic.data >> (total - 4)) & 0xF);
|
||||
inst->generic.serial = (uint32_t)((inst->generic.data >> 16) & 0x0FFFFFFF);
|
||||
inst->generic.cnt = (uint32_t)(inst->generic.data & 0xFFFF);
|
||||
} else if(total >= 40) {
|
||||
inst->generic.btn = (uint8_t)((inst->generic.data >> (total - 4)) & 0xF);
|
||||
inst->generic.serial = (uint32_t)((inst->generic.data >> 12) & 0x0FFFFFF);
|
||||
inst->generic.cnt = (uint32_t)(inst->generic.data & 0x0FFF);
|
||||
} else {
|
||||
inst->generic.btn = 0;
|
||||
inst->generic.serial = (uint32_t)(inst->generic.data >> 16);
|
||||
inst->generic.cnt = (uint32_t)(inst->generic.data & 0xFFFF);
|
||||
}
|
||||
}
|
||||
|
||||
static void renault_hitag_try_accept(RenaultHitagDecoder* inst) {
|
||||
if(renault_hitag_frame_is_plausible(inst)) {
|
||||
inst->generic.data = inst->data;
|
||||
inst->generic.data_count_bit = inst->bit_count;
|
||||
renault_hitag_extract_fields(inst);
|
||||
if(inst->base.callback) {
|
||||
inst->base.callback(&inst->base, inst->base.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Alloc / Free / Reset ────────────────────────────────────────────────────
|
||||
|
||||
static void* renault_hitag_alloc(SubGhzEnvironment* env) {
|
||||
UNUSED(env);
|
||||
RenaultHitagDecoder* inst = malloc(sizeof(RenaultHitagDecoder));
|
||||
memset(inst, 0, sizeof(RenaultHitagDecoder));
|
||||
inst->base.protocol = &subghz_protocol_renault_hitag;
|
||||
inst->generic.protocol_name = inst->base.protocol->name;
|
||||
return inst;
|
||||
}
|
||||
|
||||
static void renault_hitag_free(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static void renault_hitag_reset(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
RenaultHitagDecoder* inst = ctx;
|
||||
inst->bit_count = 0;
|
||||
inst->data = 0;
|
||||
inst->parser_step = HitagStepReset;
|
||||
}
|
||||
|
||||
// ─── Feed — OOK PWM decoder ─────────────────────────────────────────────────
|
||||
|
||||
static void renault_hitag_feed(void* ctx, bool level, uint32_t duration) {
|
||||
furi_assert(ctx);
|
||||
RenaultHitagDecoder* inst = ctx;
|
||||
|
||||
switch(inst->parser_step) {
|
||||
|
||||
case HitagStepReset:
|
||||
if(level) {
|
||||
if(hitag_abs_diff(duration, HITAG_TE_SHORT) < HITAG_TE_DELTA) {
|
||||
inst->data = (inst->data << 1);
|
||||
inst->bit_count++;
|
||||
inst->parser_step = HitagStepWaitSpace;
|
||||
} else if(hitag_abs_diff(duration, HITAG_TE_LONG) < HITAG_TE_DELTA) {
|
||||
inst->data = (inst->data << 1) | 1;
|
||||
inst->bit_count++;
|
||||
inst->parser_step = HitagStepWaitSpace;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HitagStepWaitSpace:
|
||||
if(!level) {
|
||||
if(hitag_abs_diff(duration, HITAG_TE_SHORT) < HITAG_TE_DELTA) {
|
||||
inst->parser_step = HitagStepWaitMark;
|
||||
} else if(duration >= HITAG_GAP_MIN) {
|
||||
renault_hitag_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = HitagStepReset;
|
||||
} else {
|
||||
renault_hitag_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = HitagStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case HitagStepWaitMark:
|
||||
if(level) {
|
||||
if(hitag_abs_diff(duration, HITAG_TE_SHORT) < HITAG_TE_DELTA) {
|
||||
inst->data = (inst->data << 1);
|
||||
inst->bit_count++;
|
||||
inst->parser_step = HitagStepWaitSpace;
|
||||
} else if(hitag_abs_diff(duration, HITAG_TE_LONG) < HITAG_TE_DELTA) {
|
||||
inst->data = (inst->data << 1) | 1;
|
||||
inst->bit_count++;
|
||||
inst->parser_step = HitagStepWaitSpace;
|
||||
} else {
|
||||
renault_hitag_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = HitagStepReset;
|
||||
}
|
||||
} else {
|
||||
if(duration >= HITAG_GAP_MIN) {
|
||||
renault_hitag_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = HitagStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
renault_hitag_reset(ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
if(inst->bit_count > HITAG_MAX_BITS) {
|
||||
renault_hitag_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = HitagStepReset;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Hash ────────────────────────────────────────────────────────────────────
|
||||
|
||||
static uint8_t renault_hitag_get_hash(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
RenaultHitagDecoder* inst = ctx;
|
||||
return (uint8_t)(inst->generic.data ^
|
||||
(inst->generic.data >> 8) ^
|
||||
(inst->generic.data >> 16) ^
|
||||
(inst->generic.data >> 24));
|
||||
}
|
||||
|
||||
// ─── Serialize / Deserialize ─────────────────────────────────────────────────
|
||||
|
||||
static SubGhzProtocolStatus renault_hitag_serialize(
|
||||
void* ctx,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(ctx);
|
||||
RenaultHitagDecoder* inst = ctx;
|
||||
return subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
static SubGhzProtocolStatus
|
||||
renault_hitag_deserialize(void* ctx, FlipperFormat* flipper_format) {
|
||||
furi_assert(ctx);
|
||||
RenaultHitagDecoder* inst = ctx;
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&inst->generic, flipper_format, HITAG_MIN_BITS);
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
inst->data = inst->generic.data;
|
||||
inst->bit_count = inst->generic.data_count_bit;
|
||||
renault_hitag_extract_fields(inst);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ─── get_string ──────────────────────────────────────────────────────────────
|
||||
|
||||
static void renault_hitag_get_string(void* ctx, FuriString* output) {
|
||||
furi_assert(ctx);
|
||||
RenaultHitagDecoder* inst = ctx;
|
||||
|
||||
renault_hitag_extract_fields(inst);
|
||||
|
||||
subghz_block_generic_global.btn_is_available = true;
|
||||
subghz_block_generic_global.current_btn = inst->generic.btn;
|
||||
subghz_block_generic_global.btn_length_bit = 4;
|
||||
subghz_block_generic_global.cnt_is_available = true;
|
||||
subghz_block_generic_global.current_cnt = inst->generic.cnt;
|
||||
subghz_block_generic_global.cnt_length_bit = 16;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Sn:%07lX Btn:%X\r\n"
|
||||
"Cnt:%04lX\r\n",
|
||||
inst->generic.protocol_name,
|
||||
inst->generic.data_count_bit,
|
||||
(unsigned long long)inst->generic.data,
|
||||
(unsigned long)inst->generic.serial,
|
||||
(unsigned int)inst->generic.btn,
|
||||
(unsigned long)inst->generic.cnt);
|
||||
}
|
||||
|
||||
// ─── Descriptor ──────────────────────────────────────────────────────────────
|
||||
|
||||
const SubGhzProtocolDecoder renault_hitag_decoder = {
|
||||
.alloc = renault_hitag_alloc,
|
||||
.free = renault_hitag_free,
|
||||
.feed = renault_hitag_feed,
|
||||
.reset = renault_hitag_reset,
|
||||
.get_hash_data = renault_hitag_get_hash,
|
||||
.serialize = renault_hitag_serialize,
|
||||
.deserialize = renault_hitag_deserialize,
|
||||
.get_string = renault_hitag_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_renault_hitag = {
|
||||
.name = SUBGHZ_PROTOCOL_RENAULT_HITAG_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 |
|
||||
SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save,
|
||||
.decoder = &renault_hitag_decoder,
|
||||
.encoder = NULL,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
|
||||
#define SUBGHZ_PROTOCOL_RENAULT_HITAG_NAME "Renault_Hitag"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_renault_hitag;
|
||||
@@ -1,696 +0,0 @@
|
||||
#include "renault_marelli.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
#include "../blocks/custom_btn_i.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <lib/toolbox/manchester_encoder.h>
|
||||
#include <furi_hal_subghz.h>
|
||||
|
||||
#define TAG "RenaultMarelli"
|
||||
|
||||
// Magneti Marelli BSI keyfob protocol (PCF7946) — Renault variant
|
||||
// Found on: Renault Clio III, Modus, Kangoo II, and some Dacia models
|
||||
// sharing the Fiat/Renault Marelli platform (~2004-2014)
|
||||
//
|
||||
// RF: 433.92 MHz, Manchester encoding (FSK modulation)
|
||||
// Two timing variants with identical frame structure:
|
||||
// Type A (standard): te_short ~260us, te_long ~520us
|
||||
// Type B (fast/compact): te_short ~100us, te_long ~200us
|
||||
// TE is auto-detected from preamble pulse averaging.
|
||||
//
|
||||
// Frame layout (103-104 bits = 13 bytes):
|
||||
// Bytes 0-1: 0xFFFF/0xFFFC preamble residue
|
||||
// Bytes 2-5: Serial (32 bits)
|
||||
// Byte 6: [Button:4 | Epoch:4]
|
||||
// Byte 7: [Counter:5 | Scramble:2 | Fixed:1]
|
||||
// Bytes 8-12: Encrypted payload (40 bits)
|
||||
|
||||
#define REN_MARELLI_PREAMBLE_PULSE_MIN 50
|
||||
#define REN_MARELLI_PREAMBLE_PULSE_MAX 350
|
||||
#define REN_MARELLI_PREAMBLE_MIN 80
|
||||
#define REN_MARELLI_MAX_DATA_BITS 104
|
||||
#define REN_MARELLI_MIN_DATA_BITS 80
|
||||
#define REN_MARELLI_GAP_TE_MULT 4
|
||||
#define REN_MARELLI_SYNC_TE_MIN_MULT 4
|
||||
#define REN_MARELLI_SYNC_TE_MAX_MULT 12
|
||||
#define REN_MARELLI_RETX_GAP_MIN 5000
|
||||
#define REN_MARELLI_RETX_SYNC_MIN 400
|
||||
#define REN_MARELLI_RETX_SYNC_MAX 2800
|
||||
#define REN_MARELLI_TE_TYPE_AB_BOUNDARY 180
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_renault_marelli_const = {
|
||||
.te_short = 260,
|
||||
.te_long = 520,
|
||||
.te_delta = 80,
|
||||
.min_count_bit_for_found = 80,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderRenaultMarelli {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
ManchesterState manchester_state;
|
||||
uint8_t decoder_state;
|
||||
uint16_t preamble_count;
|
||||
uint8_t raw_data[13];
|
||||
uint8_t bit_count;
|
||||
uint32_t extra_data;
|
||||
uint32_t te_last;
|
||||
uint32_t te_sum;
|
||||
uint16_t te_count;
|
||||
uint32_t te_detected;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderRenaultMarelli {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint8_t raw_data[13];
|
||||
uint32_t extra_data;
|
||||
uint8_t bit_count;
|
||||
uint32_t te_detected;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
RenMarelliDecoderStepReset = 0,
|
||||
RenMarelliDecoderStepPreamble = 1,
|
||||
RenMarelliDecoderStepSync = 2,
|
||||
RenMarelliDecoderStepData = 3,
|
||||
RenMarelliDecoderStepRetxSync = 4,
|
||||
} RenMarelliDecoderStep;
|
||||
|
||||
static bool renault_marelli_button_is_renault(uint8_t btn) {
|
||||
// Renault Marelli: button codes usually seen as 0x1/0x2/0x4.
|
||||
// Fiat Marelli commonly uses 0x7/0xB/0xD.
|
||||
return (btn == 0x1) || (btn == 0x2) || (btn == 0x4);
|
||||
}
|
||||
|
||||
static bool renault_marelli_frame_is_renault(const SubGhzProtocolDecoderRenaultMarelli* instance) {
|
||||
const uint8_t btn = (instance->raw_data[6] >> 4) & 0xF;
|
||||
const uint8_t fixed = instance->raw_data[7] & 0x1;
|
||||
const uint8_t scramble = (instance->raw_data[7] >> 1) & 0x3;
|
||||
|
||||
if(!renault_marelli_button_is_renault(btn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Keep structural guards to reduce false positives from noise.
|
||||
if(fixed > 1U || scramble > 3U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_renault_marelli_decoder = {
|
||||
.alloc = subghz_protocol_decoder_renault_marelli_alloc,
|
||||
.free = subghz_protocol_decoder_renault_marelli_free,
|
||||
.feed = subghz_protocol_decoder_renault_marelli_feed,
|
||||
.reset = subghz_protocol_decoder_renault_marelli_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_renault_marelli_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_renault_marelli_serialize,
|
||||
.deserialize = subghz_protocol_decoder_renault_marelli_deserialize,
|
||||
.get_string = subghz_protocol_decoder_renault_marelli_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_renault_marelli_encoder = {
|
||||
.alloc = subghz_protocol_encoder_renault_marelli_alloc,
|
||||
.free = subghz_protocol_encoder_renault_marelli_free,
|
||||
.deserialize = subghz_protocol_encoder_renault_marelli_deserialize,
|
||||
.stop = subghz_protocol_encoder_renault_marelli_stop,
|
||||
.yield = subghz_protocol_encoder_renault_marelli_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_renault_marelli = {
|
||||
.name = RENAULT_MARELLI_PROTOCOL_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_renault_marelli_decoder,
|
||||
.encoder = &subghz_protocol_renault_marelli_encoder,
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Encoder
|
||||
// ============================================================================
|
||||
|
||||
#define REN_MARELLI_ENCODER_UPLOAD_MAX 1500
|
||||
#define REN_MARELLI_ENCODER_REPEAT 3
|
||||
#define REN_MARELLI_PREAMBLE_PAIRS 100
|
||||
|
||||
void* subghz_protocol_encoder_renault_marelli_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderRenaultMarelli* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolEncoderRenaultMarelli));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_renault_marelli;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = REN_MARELLI_ENCODER_REPEAT;
|
||||
instance->encoder.size_upload = REN_MARELLI_ENCODER_UPLOAD_MAX;
|
||||
instance->encoder.upload =
|
||||
malloc(REN_MARELLI_ENCODER_UPLOAD_MAX * sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_renault_marelli_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderRenaultMarelli* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
static bool renault_marelli_encoder_get_upload(SubGhzProtocolEncoderRenaultMarelli* instance) {
|
||||
uint32_t te = instance->te_detected;
|
||||
if(te == 0) te = subghz_protocol_renault_marelli_const.te_short;
|
||||
|
||||
uint32_t te_short = te;
|
||||
uint32_t te_long = te * 2;
|
||||
uint32_t gap_duration = te * 12;
|
||||
uint32_t sync_duration = te * 8;
|
||||
|
||||
size_t index = 0;
|
||||
size_t max_upload = REN_MARELLI_ENCODER_UPLOAD_MAX;
|
||||
uint8_t data_bits = instance->bit_count;
|
||||
if(data_bits == 0) data_bits = instance->generic.data_count_bit;
|
||||
if(data_bits < REN_MARELLI_MIN_DATA_BITS || data_bits > REN_MARELLI_MAX_DATA_BITS) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Preamble: alternating short HIGH/LOW
|
||||
for(uint8_t i = 0; i < REN_MARELLI_PREAMBLE_PAIRS && (index + 1) < max_upload; i++) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||
if(i < REN_MARELLI_PREAMBLE_PAIRS - 1) {
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||
}
|
||||
}
|
||||
|
||||
// Gap after preamble
|
||||
if(index < max_upload) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, te_short + gap_duration);
|
||||
}
|
||||
|
||||
// Sync pulse
|
||||
if(index < max_upload) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, sync_duration);
|
||||
}
|
||||
|
||||
// Manchester-encode data bits
|
||||
bool in_mid1 = true;
|
||||
|
||||
for(uint8_t bit_i = 0; bit_i < data_bits && (index + 1) < max_upload; bit_i++) {
|
||||
uint8_t byte_idx = bit_i / 8;
|
||||
uint8_t bit_pos = 7 - (bit_i % 8);
|
||||
bool data_bit = (instance->raw_data[byte_idx] >> bit_pos) & 1;
|
||||
|
||||
if(in_mid1) {
|
||||
if(data_bit) {
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_long);
|
||||
in_mid1 = false;
|
||||
}
|
||||
} else {
|
||||
if(data_bit) {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_long);
|
||||
in_mid1 = true;
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(true, te_short);
|
||||
instance->encoder.upload[index++] = level_duration_make(false, te_short);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Trailing gap
|
||||
if(in_mid1) {
|
||||
if(index < max_upload) {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, te_short + gap_duration * 3);
|
||||
}
|
||||
} else {
|
||||
if(index > 0) {
|
||||
instance->encoder.upload[index - 1] =
|
||||
level_duration_make(false, te_short + gap_duration * 3);
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return index > 0;
|
||||
}
|
||||
|
||||
static void renault_marelli_encoder_rebuild_raw_data(
|
||||
SubGhzProtocolEncoderRenaultMarelli* instance) {
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
|
||||
uint64_t key = instance->generic.data;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||
}
|
||||
|
||||
uint8_t extra_bits =
|
||||
instance->generic.data_count_bit > 64 ? (instance->generic.data_count_bit - 64) : 0;
|
||||
for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
|
||||
uint8_t byte_idx = 8 + (i / 8);
|
||||
uint8_t bit_pos = 7 - (i % 8);
|
||||
if(instance->extra_data & (1UL << (extra_bits - 1 - i))) {
|
||||
instance->raw_data[byte_idx] |= (1 << bit_pos);
|
||||
}
|
||||
}
|
||||
|
||||
instance->bit_count = instance->generic.data_count_bit;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_renault_marelli_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderRenaultMarelli* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
if(ret != SubGhzProtocolStatusOk) break;
|
||||
|
||||
uint32_t extra = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
|
||||
instance->extra_data = extra;
|
||||
}
|
||||
|
||||
uint32_t te = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "TE", &te, 1)) {
|
||||
instance->te_detected = te;
|
||||
}
|
||||
|
||||
renault_marelli_encoder_rebuild_raw_data(instance);
|
||||
|
||||
if(!renault_marelli_encoder_get_upload(instance)) {
|
||||
ret = SubGhzProtocolStatusErrorEncoderGetUpload;
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.repeat = REN_MARELLI_ENCODER_REPEAT;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_renault_marelli_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderRenaultMarelli* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_renault_marelli_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderRenaultMarelli* 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) {
|
||||
if(!subghz_block_generic_global.endless_tx) {
|
||||
instance->encoder.repeat--;
|
||||
}
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Decoder
|
||||
// ============================================================================
|
||||
|
||||
static void renault_marelli_rebuild_raw_data(SubGhzProtocolDecoderRenaultMarelli* instance) {
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
|
||||
uint64_t key = instance->generic.data;
|
||||
for(int i = 0; i < 8; i++) {
|
||||
instance->raw_data[i] = (uint8_t)(key >> (56 - i * 8));
|
||||
}
|
||||
|
||||
uint8_t extra_bits =
|
||||
instance->generic.data_count_bit > 64 ? (instance->generic.data_count_bit - 64) : 0;
|
||||
for(uint8_t i = 0; i < extra_bits && i < 32; i++) {
|
||||
uint8_t byte_idx = 8 + (i / 8);
|
||||
uint8_t bit_pos = 7 - (i % 8);
|
||||
if(instance->extra_data & (1UL << (extra_bits - 1 - i))) {
|
||||
instance->raw_data[byte_idx] |= (1 << bit_pos);
|
||||
}
|
||||
}
|
||||
|
||||
instance->bit_count = instance->generic.data_count_bit;
|
||||
|
||||
if(instance->bit_count >= 56) {
|
||||
instance->generic.serial =
|
||||
((uint32_t)instance->raw_data[2] << 24) |
|
||||
((uint32_t)instance->raw_data[3] << 16) |
|
||||
((uint32_t)instance->raw_data[4] << 8) |
|
||||
((uint32_t)instance->raw_data[5]);
|
||||
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
|
||||
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
}
|
||||
}
|
||||
|
||||
static void renault_marelli_prepare_data(SubGhzProtocolDecoderRenaultMarelli* instance) {
|
||||
instance->bit_count = 0;
|
||||
instance->extra_data = 0;
|
||||
instance->generic.data = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
manchester_advance(
|
||||
instance->manchester_state,
|
||||
ManchesterEventReset,
|
||||
&instance->manchester_state,
|
||||
NULL);
|
||||
instance->decoder_state = RenMarelliDecoderStepData;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_renault_marelli_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderRenaultMarelli* instance =
|
||||
calloc(1, sizeof(SubGhzProtocolDecoderRenaultMarelli));
|
||||
furi_check(instance);
|
||||
instance->base.protocol = &subghz_protocol_renault_marelli;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_renault_marelli_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderRenaultMarelli* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_renault_marelli_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderRenaultMarelli* instance = context;
|
||||
instance->decoder_state = RenMarelliDecoderStepReset;
|
||||
instance->preamble_count = 0;
|
||||
instance->bit_count = 0;
|
||||
instance->extra_data = 0;
|
||||
instance->te_last = 0;
|
||||
instance->te_sum = 0;
|
||||
instance->te_count = 0;
|
||||
instance->te_detected = 0;
|
||||
instance->generic.data = 0;
|
||||
memset(instance->raw_data, 0, sizeof(instance->raw_data));
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_renault_marelli_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderRenaultMarelli* instance = context;
|
||||
|
||||
uint32_t te_short = instance->te_detected
|
||||
? instance->te_detected
|
||||
: (uint32_t)subghz_protocol_renault_marelli_const.te_short;
|
||||
uint32_t te_long = te_short * 2;
|
||||
uint32_t te_delta = te_short / 2;
|
||||
if(te_delta < 30) te_delta = 30;
|
||||
uint32_t diff;
|
||||
|
||||
switch(instance->decoder_state) {
|
||||
case RenMarelliDecoderStepReset:
|
||||
if(level) {
|
||||
if(duration >= REN_MARELLI_PREAMBLE_PULSE_MIN &&
|
||||
duration <= REN_MARELLI_PREAMBLE_PULSE_MAX) {
|
||||
instance->decoder_state = RenMarelliDecoderStepPreamble;
|
||||
instance->preamble_count = 1;
|
||||
instance->te_sum = duration;
|
||||
instance->te_count = 1;
|
||||
instance->te_last = duration;
|
||||
}
|
||||
} else {
|
||||
if(duration > REN_MARELLI_RETX_GAP_MIN) {
|
||||
instance->decoder_state = RenMarelliDecoderStepRetxSync;
|
||||
instance->te_last = duration;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case RenMarelliDecoderStepPreamble:
|
||||
if(duration >= REN_MARELLI_PREAMBLE_PULSE_MIN &&
|
||||
duration <= REN_MARELLI_PREAMBLE_PULSE_MAX) {
|
||||
instance->preamble_count++;
|
||||
instance->te_sum += duration;
|
||||
instance->te_count++;
|
||||
instance->te_last = duration;
|
||||
} else if(!level) {
|
||||
if(instance->preamble_count >= REN_MARELLI_PREAMBLE_MIN &&
|
||||
instance->te_count > 0) {
|
||||
instance->te_detected = instance->te_sum / instance->te_count;
|
||||
uint32_t gap_threshold =
|
||||
instance->te_detected * REN_MARELLI_GAP_TE_MULT;
|
||||
|
||||
if(duration > gap_threshold) {
|
||||
instance->decoder_state = RenMarelliDecoderStepSync;
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
instance->decoder_state = RenMarelliDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder_state = RenMarelliDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder_state = RenMarelliDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case RenMarelliDecoderStepSync: {
|
||||
uint32_t sync_min = instance->te_detected * REN_MARELLI_SYNC_TE_MIN_MULT;
|
||||
uint32_t sync_max = instance->te_detected * REN_MARELLI_SYNC_TE_MAX_MULT;
|
||||
|
||||
if(level && duration >= sync_min && duration <= sync_max) {
|
||||
renault_marelli_prepare_data(instance);
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
instance->decoder_state = RenMarelliDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case RenMarelliDecoderStepRetxSync:
|
||||
if(level && duration >= REN_MARELLI_RETX_SYNC_MIN &&
|
||||
duration <= REN_MARELLI_RETX_SYNC_MAX) {
|
||||
if(!instance->te_detected) {
|
||||
instance->te_detected = duration / 8;
|
||||
if(instance->te_detected < 70) instance->te_detected = 100;
|
||||
if(instance->te_detected > 350) instance->te_detected = 260;
|
||||
}
|
||||
renault_marelli_prepare_data(instance);
|
||||
instance->te_last = duration;
|
||||
} else {
|
||||
instance->decoder_state = RenMarelliDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case RenMarelliDecoderStepData: {
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
bool frame_complete = false;
|
||||
|
||||
diff = (duration > te_short) ? (duration - te_short) : (te_short - duration);
|
||||
if(diff < te_delta) {
|
||||
event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
} else {
|
||||
diff = (duration > te_long) ? (duration - te_long) : (te_long - duration);
|
||||
if(diff < te_delta) {
|
||||
event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||
}
|
||||
}
|
||||
|
||||
if(event != ManchesterEventReset) {
|
||||
bool data_bit;
|
||||
if(manchester_advance(
|
||||
instance->manchester_state,
|
||||
event,
|
||||
&instance->manchester_state,
|
||||
&data_bit)) {
|
||||
uint32_t new_bit = data_bit ? 1 : 0;
|
||||
|
||||
if(instance->bit_count < REN_MARELLI_MAX_DATA_BITS) {
|
||||
uint8_t byte_idx = instance->bit_count / 8;
|
||||
uint8_t bit_pos = 7 - (instance->bit_count % 8);
|
||||
if(new_bit) {
|
||||
instance->raw_data[byte_idx] |= (1 << bit_pos);
|
||||
}
|
||||
}
|
||||
|
||||
if(instance->bit_count < 64) {
|
||||
instance->generic.data = (instance->generic.data << 1) | new_bit;
|
||||
} else {
|
||||
instance->extra_data = (instance->extra_data << 1) | new_bit;
|
||||
}
|
||||
|
||||
instance->bit_count++;
|
||||
|
||||
if(instance->bit_count >= REN_MARELLI_MAX_DATA_BITS) {
|
||||
frame_complete = true;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(instance->bit_count >= REN_MARELLI_MIN_DATA_BITS) {
|
||||
frame_complete = true;
|
||||
} else {
|
||||
instance->decoder_state = RenMarelliDecoderStepReset;
|
||||
}
|
||||
}
|
||||
|
||||
if(frame_complete) {
|
||||
instance->generic.data_count_bit = instance->bit_count;
|
||||
|
||||
instance->generic.serial =
|
||||
((uint32_t)instance->raw_data[2] << 24) |
|
||||
((uint32_t)instance->raw_data[3] << 16) |
|
||||
((uint32_t)instance->raw_data[4] << 8) |
|
||||
((uint32_t)instance->raw_data[5]);
|
||||
instance->generic.btn = (instance->raw_data[6] >> 4) & 0xF;
|
||||
instance->generic.cnt = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
|
||||
if(renault_marelli_frame_is_renault(instance) && instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
|
||||
instance->decoder_state = RenMarelliDecoderStepReset;
|
||||
}
|
||||
|
||||
instance->te_last = duration;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_renault_marelli_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderRenaultMarelli* instance = context;
|
||||
SubGhzBlockDecoder decoder = {
|
||||
.decode_data = instance->generic.data,
|
||||
.decode_count_bit =
|
||||
instance->generic.data_count_bit > 64 ? 64 : instance->generic.data_count_bit,
|
||||
};
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&decoder, (decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_renault_marelli_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderRenaultMarelli* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_write_uint32(flipper_format, "Extra", &instance->extra_data, 1);
|
||||
|
||||
uint32_t extra_bits = instance->generic.data_count_bit > 64
|
||||
? (instance->generic.data_count_bit - 64)
|
||||
: 0;
|
||||
flipper_format_write_uint32(flipper_format, "Extra_bits", &extra_bits, 1);
|
||||
|
||||
uint32_t te = instance->te_detected;
|
||||
flipper_format_write_uint32(flipper_format, "TE", &te, 1);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_renault_marelli_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderRenaultMarelli* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_deserialize(&instance->generic, flipper_format);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t extra = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Extra", &extra, 1)) {
|
||||
instance->extra_data = extra;
|
||||
}
|
||||
|
||||
uint32_t te = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "TE", &te, 1)) {
|
||||
instance->te_detected = te;
|
||||
}
|
||||
|
||||
renault_marelli_rebuild_raw_data(instance);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const char* renault_marelli_button_name(uint8_t btn) {
|
||||
switch(btn) {
|
||||
case 0x1:
|
||||
return "Lock";
|
||||
case 0x2:
|
||||
return "Unlock";
|
||||
case 0x4:
|
||||
return "Trunk";
|
||||
case 0x7:
|
||||
return "Lock";
|
||||
case 0xB:
|
||||
return "Unlock";
|
||||
case 0xD:
|
||||
return "Trunk";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_renault_marelli_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderRenaultMarelli* instance = context;
|
||||
|
||||
uint8_t epoch = instance->raw_data[6] & 0xF;
|
||||
uint8_t counter = (instance->raw_data[7] >> 3) & 0x1F;
|
||||
const char* variant = (instance->te_detected &&
|
||||
instance->te_detected < REN_MARELLI_TE_TYPE_AB_BOUNDARY)
|
||||
? "B"
|
||||
: "A";
|
||||
uint8_t scramble = (instance->raw_data[7] >> 1) & 0x3;
|
||||
uint8_t fixed = instance->raw_data[7] & 0x1;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Enc:%02X%02X%02X%02X%02X Scr:%02X\r\n"
|
||||
"Raw:%02X%02X Fixed:%X\r\n"
|
||||
"Sn:%08X Cnt:%02X\r\n"
|
||||
"Btn:%02X:[%s] Ep:%02X\r\n"
|
||||
"Tp:%s\r\n",
|
||||
instance->generic.protocol_name,
|
||||
(int)instance->bit_count,
|
||||
instance->raw_data[8],
|
||||
instance->raw_data[9],
|
||||
instance->raw_data[10],
|
||||
instance->raw_data[11],
|
||||
instance->raw_data[12],
|
||||
(unsigned)scramble,
|
||||
instance->raw_data[6],
|
||||
instance->raw_data[7],
|
||||
(unsigned)fixed,
|
||||
(unsigned int)instance->generic.serial,
|
||||
(unsigned)counter,
|
||||
(unsigned)instance->generic.btn,
|
||||
renault_marelli_button_name(instance->generic.btn),
|
||||
(unsigned)epoch,
|
||||
variant);
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define RENAULT_MARELLI_PROTOCOL_NAME "Ren_MARELLI"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderRenaultMarelli SubGhzProtocolDecoderRenaultMarelli;
|
||||
typedef struct SubGhzProtocolEncoderRenaultMarelli SubGhzProtocolEncoderRenaultMarelli;
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_renault_marelli;
|
||||
|
||||
void* subghz_protocol_decoder_renault_marelli_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_renault_marelli_free(void* context);
|
||||
void subghz_protocol_decoder_renault_marelli_reset(void* context);
|
||||
void subghz_protocol_decoder_renault_marelli_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_renault_marelli_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_renault_marelli_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_renault_marelli_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_renault_marelli_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_renault_marelli_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_renault_marelli_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_renault_marelli_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_renault_marelli_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_renault_marelli_yield(void* context);
|
||||
@@ -1,275 +0,0 @@
|
||||
#include "renault_siemens.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
|
||||
#define TAG "RenaultSiemens"
|
||||
|
||||
// Siemens VDO keyfob — OOK PWM
|
||||
// te_short ≈ 250 µs (bit 0 mark)
|
||||
// te_long ≈ 500 µs (bit 1 mark)
|
||||
// Space ≈ te_short
|
||||
// Gap > 3 × te_long
|
||||
|
||||
#define SIEMENS_TE_SHORT 250
|
||||
#define SIEMENS_TE_LONG 500
|
||||
#define SIEMENS_TE_DELTA 120
|
||||
#define SIEMENS_MIN_BITS 64
|
||||
#define SIEMENS_MAX_BITS 96
|
||||
#define SIEMENS_GAP_MIN (SIEMENS_TE_LONG * 3)
|
||||
|
||||
typedef enum {
|
||||
SiemensStepReset = 0,
|
||||
SiemensStepWaitMark,
|
||||
SiemensStepWaitSpace,
|
||||
} SiemensStep;
|
||||
|
||||
// ─── Struct ──────────────────────────────────────────────────────────────────
|
||||
|
||||
typedef struct {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
uint64_t data;
|
||||
uint8_t bit_count;
|
||||
uint8_t parser_step;
|
||||
} RenaultSiemensDecoder;
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
static inline uint32_t siemens_abs_diff(uint32_t a, uint32_t b) {
|
||||
return (a > b) ? (a - b) : (b - a);
|
||||
}
|
||||
|
||||
static bool renault_siemens_button_is_valid(uint8_t btn) {
|
||||
// Conservative acceptance window for common Renault button nibbles.
|
||||
return (btn <= 0x0D) && (btn != 0x0C);
|
||||
}
|
||||
|
||||
static bool renault_siemens_frame_is_plausible(RenaultSiemensDecoder* inst) {
|
||||
if(inst->bit_count < SIEMENS_MIN_BITS || inst->bit_count > SIEMENS_MAX_BITS) {
|
||||
return false;
|
||||
}
|
||||
if((inst->bit_count % 4U) != 0U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint8_t btn = (uint8_t)((inst->data >> 28U) & 0x0FU);
|
||||
if(!renault_siemens_button_is_valid(btn)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t serial = (uint32_t)(inst->data >> 32U);
|
||||
return serial != 0U;
|
||||
}
|
||||
|
||||
static void renault_siemens_extract_fields(RenaultSiemensDecoder* inst) {
|
||||
inst->generic.serial = (uint32_t)(inst->generic.data >> 32);
|
||||
inst->generic.btn = (uint8_t)((inst->generic.data >> 28) & 0xF);
|
||||
inst->generic.cnt = (uint32_t)(inst->generic.data & 0xFFFF);
|
||||
}
|
||||
|
||||
static void renault_siemens_try_accept(RenaultSiemensDecoder* inst) {
|
||||
if(renault_siemens_frame_is_plausible(inst)) {
|
||||
inst->generic.data = inst->data;
|
||||
inst->generic.data_count_bit = inst->bit_count;
|
||||
renault_siemens_extract_fields(inst);
|
||||
if(inst->base.callback) {
|
||||
inst->base.callback(&inst->base, inst->base.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Alloc / Free / Reset ────────────────────────────────────────────────────
|
||||
|
||||
static void* renault_siemens_alloc(SubGhzEnvironment* env) {
|
||||
UNUSED(env);
|
||||
RenaultSiemensDecoder* inst = malloc(sizeof(RenaultSiemensDecoder));
|
||||
memset(inst, 0, sizeof(RenaultSiemensDecoder));
|
||||
inst->base.protocol = &subghz_protocol_renault_siemens;
|
||||
inst->generic.protocol_name = inst->base.protocol->name;
|
||||
return inst;
|
||||
}
|
||||
|
||||
static void renault_siemens_free(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static void renault_siemens_reset(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
RenaultSiemensDecoder* inst = ctx;
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = SiemensStepReset;
|
||||
}
|
||||
|
||||
// ─── Feed — OOK PWM decoder ─────────────────────────────────────────────────
|
||||
|
||||
static void renault_siemens_feed(void* ctx, bool level, uint32_t duration) {
|
||||
furi_assert(ctx);
|
||||
RenaultSiemensDecoder* inst = ctx;
|
||||
|
||||
switch(inst->parser_step) {
|
||||
|
||||
case SiemensStepReset:
|
||||
if(level) {
|
||||
if(siemens_abs_diff(duration, SIEMENS_TE_SHORT) < SIEMENS_TE_DELTA) {
|
||||
inst->data = (inst->data << 1);
|
||||
inst->bit_count++;
|
||||
inst->parser_step = SiemensStepWaitSpace;
|
||||
} else if(siemens_abs_diff(duration, SIEMENS_TE_LONG) < SIEMENS_TE_DELTA) {
|
||||
inst->data = (inst->data << 1) | 1;
|
||||
inst->bit_count++;
|
||||
inst->parser_step = SiemensStepWaitSpace;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SiemensStepWaitSpace:
|
||||
if(!level) {
|
||||
if(siemens_abs_diff(duration, SIEMENS_TE_SHORT) < SIEMENS_TE_DELTA) {
|
||||
inst->parser_step = SiemensStepWaitMark;
|
||||
} else if(duration >= SIEMENS_GAP_MIN) {
|
||||
renault_siemens_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = SiemensStepReset;
|
||||
} else {
|
||||
renault_siemens_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = SiemensStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case SiemensStepWaitMark:
|
||||
if(level) {
|
||||
if(siemens_abs_diff(duration, SIEMENS_TE_SHORT) < SIEMENS_TE_DELTA) {
|
||||
inst->data = (inst->data << 1);
|
||||
inst->bit_count++;
|
||||
inst->parser_step = SiemensStepWaitSpace;
|
||||
} else if(siemens_abs_diff(duration, SIEMENS_TE_LONG) < SIEMENS_TE_DELTA) {
|
||||
inst->data = (inst->data << 1) | 1;
|
||||
inst->bit_count++;
|
||||
inst->parser_step = SiemensStepWaitSpace;
|
||||
} else {
|
||||
renault_siemens_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = SiemensStepReset;
|
||||
}
|
||||
} else {
|
||||
if(duration >= SIEMENS_GAP_MIN) {
|
||||
renault_siemens_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = SiemensStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
renault_siemens_reset(ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
if(inst->bit_count > SIEMENS_MAX_BITS) {
|
||||
renault_siemens_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = SiemensStepReset;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Hash ────────────────────────────────────────────────────────────────────
|
||||
|
||||
static uint8_t renault_siemens_get_hash(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
RenaultSiemensDecoder* inst = ctx;
|
||||
return (uint8_t)(inst->generic.data ^
|
||||
(inst->generic.data >> 8) ^
|
||||
(inst->generic.data >> 16) ^
|
||||
(inst->generic.data >> 24));
|
||||
}
|
||||
|
||||
// ─── Serialize / Deserialize ─────────────────────────────────────────────────
|
||||
|
||||
static SubGhzProtocolStatus renault_siemens_serialize(
|
||||
void* ctx,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(ctx);
|
||||
RenaultSiemensDecoder* inst = ctx;
|
||||
return subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
static SubGhzProtocolStatus
|
||||
renault_siemens_deserialize(void* ctx, FlipperFormat* flipper_format) {
|
||||
furi_assert(ctx);
|
||||
RenaultSiemensDecoder* inst = ctx;
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&inst->generic, flipper_format, SIEMENS_MIN_BITS);
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
inst->data = inst->generic.data;
|
||||
inst->bit_count = inst->generic.data_count_bit;
|
||||
renault_siemens_extract_fields(inst);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
// ─── get_string ──────────────────────────────────────────────────────────────
|
||||
|
||||
static void renault_siemens_get_string(void* ctx, FuriString* output) {
|
||||
furi_assert(ctx);
|
||||
RenaultSiemensDecoder* inst = ctx;
|
||||
|
||||
renault_siemens_extract_fields(inst);
|
||||
|
||||
subghz_block_generic_global.btn_is_available = true;
|
||||
subghz_block_generic_global.current_btn = inst->generic.btn;
|
||||
subghz_block_generic_global.btn_length_bit = 4;
|
||||
subghz_block_generic_global.cnt_is_available = true;
|
||||
subghz_block_generic_global.current_cnt = inst->generic.cnt;
|
||||
subghz_block_generic_global.cnt_length_bit = 16;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Sn:%08lX Btn:%X\r\n"
|
||||
"Cnt:%04lX\r\n",
|
||||
inst->generic.protocol_name,
|
||||
inst->generic.data_count_bit,
|
||||
(unsigned long long)inst->generic.data,
|
||||
(unsigned long)inst->generic.serial,
|
||||
(unsigned int)inst->generic.btn,
|
||||
(unsigned long)inst->generic.cnt);
|
||||
}
|
||||
|
||||
// ─── Descriptor ──────────────────────────────────────────────────────────────
|
||||
|
||||
const SubGhzProtocolDecoder renault_siemens_decoder = {
|
||||
.alloc = renault_siemens_alloc,
|
||||
.free = renault_siemens_free,
|
||||
.feed = renault_siemens_feed,
|
||||
.reset = renault_siemens_reset,
|
||||
.get_hash_data = renault_siemens_get_hash,
|
||||
.serialize = renault_siemens_serialize,
|
||||
.deserialize = renault_siemens_deserialize,
|
||||
.get_string = renault_siemens_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_renault_siemens = {
|
||||
.name = SUBGHZ_PROTOCOL_RENAULT_SIEMENS_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 |
|
||||
SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save,
|
||||
.decoder = &renault_siemens_decoder,
|
||||
.encoder = NULL,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
|
||||
#define SUBGHZ_PROTOCOL_RENAULT_SIEMENS_NAME "Renault_Siemens"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_renault_siemens;
|
||||
@@ -1,362 +0,0 @@
|
||||
#include "renault_valeo.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "keeloq_common.h"
|
||||
#include "../subghz_keystore.h"
|
||||
#include "../subghz_keystore_i.h"
|
||||
#include <m-array.h>
|
||||
|
||||
#define TAG "RenaultValeo"
|
||||
|
||||
// Valeo OOK keyfob — Captur 2017 / Clio IV / PCF7961
|
||||
// OOK PWM encoding:
|
||||
// te_short ≈ 66 µs → bit 0 (mark)
|
||||
// te_long ≈ 264 µs → bit 1 (mark)
|
||||
// Space between bits ≈ te_short (66 µs)
|
||||
// Gap between frames > 500 µs
|
||||
//
|
||||
// Trama (64-96 bits):
|
||||
// [MSB..32] fix: btn[4] + serial[28]
|
||||
// [31..0] hop: 32 bits KeeLoq encrypted
|
||||
|
||||
#define VALEO_TE_SHORT 66
|
||||
#define VALEO_TE_LONG 264
|
||||
#define VALEO_TE_DELTA 60
|
||||
#define VALEO_TE_SHORT_ALT 100
|
||||
#define VALEO_TE_LONG_ALT 400
|
||||
#define VALEO_TE_DELTA_ALT 80
|
||||
#define VALEO_MIN_BITS 64
|
||||
#define VALEO_MAX_BITS 96
|
||||
#define VALEO_GAP_MIN 500
|
||||
|
||||
typedef enum {
|
||||
ValeoStepReset = 0,
|
||||
ValeoStepWaitMark,
|
||||
ValeoStepWaitSpace,
|
||||
} ValeoStep;
|
||||
|
||||
// ─── Struct ──────────────────────────────────────────────────────────────────
|
||||
|
||||
typedef struct {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint64_t data;
|
||||
uint8_t bit_count;
|
||||
uint8_t parser_step;
|
||||
SubGhzKeystore* keystore;
|
||||
const char* manufacture_name;
|
||||
} RenaultValeoDecoder;
|
||||
|
||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
||||
|
||||
static inline uint32_t valeo_abs_diff(uint32_t a, uint32_t b) {
|
||||
return (a > b) ? (a - b) : (b - a);
|
||||
}
|
||||
|
||||
static bool renault_valeo_is_short(uint32_t duration) {
|
||||
return (valeo_abs_diff(duration, VALEO_TE_SHORT) < VALEO_TE_DELTA) ||
|
||||
(valeo_abs_diff(duration, VALEO_TE_SHORT_ALT) < VALEO_TE_DELTA_ALT);
|
||||
}
|
||||
|
||||
static bool renault_valeo_is_long(uint32_t duration) {
|
||||
return (valeo_abs_diff(duration, VALEO_TE_LONG) < VALEO_TE_DELTA) ||
|
||||
(valeo_abs_diff(duration, VALEO_TE_LONG_ALT) < VALEO_TE_DELTA_ALT);
|
||||
}
|
||||
|
||||
static bool renault_valeo_frame_is_plausible(RenaultValeoDecoder* inst) {
|
||||
if(inst->bit_count < VALEO_MIN_BITS || inst->bit_count > VALEO_MAX_BITS) {
|
||||
return false;
|
||||
}
|
||||
if((inst->bit_count % 2U) != 0U) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t fix = (uint32_t)(inst->data >> 32U);
|
||||
const uint8_t btn = (uint8_t)((fix >> 28U) & 0x0FU);
|
||||
const uint32_t serial = fix & 0x0FFFFFFFU;
|
||||
|
||||
return (serial != 0U) && (btn <= 0x0DU);
|
||||
}
|
||||
|
||||
// ─── KeeLoq decode ───────────────────────────────────────────────────────────
|
||||
|
||||
static void renault_valeo_decode_keeloq(RenaultValeoDecoder* inst) {
|
||||
if(!inst->keystore) return;
|
||||
|
||||
uint32_t fix = (uint32_t)(inst->data >> 32);
|
||||
uint32_t hop = (uint32_t)(inst->data & 0xFFFFFFFF);
|
||||
|
||||
uint8_t btn = (fix >> 28) & 0xF;
|
||||
uint32_t serial = fix & 0x0FFFFFFF;
|
||||
|
||||
inst->generic.serial = serial;
|
||||
inst->generic.btn = btn;
|
||||
inst->manufacture_name = "Unknown";
|
||||
|
||||
for
|
||||
M_EACH(mf, *subghz_keystore_get_data(inst->keystore), SubGhzKeyArray_t) {
|
||||
// Normal Learning (Valeo primary)
|
||||
if(mf->type == KEELOQ_LEARNING_NORMAL ||
|
||||
mf->type == KEELOQ_LEARNING_UNKNOWN) {
|
||||
uint64_t man = subghz_protocol_keeloq_common_normal_learning(fix, mf->key);
|
||||
uint32_t decrypt = subghz_protocol_keeloq_common_decrypt(hop, man);
|
||||
if((decrypt >> 28) == btn &&
|
||||
((decrypt >> 16) & 0xFF) == (serial & 0xFF)) {
|
||||
inst->generic.cnt = decrypt & 0xFFFF;
|
||||
inst->manufacture_name = furi_string_get_cstr(mf->name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Simple Learning fallback
|
||||
if(mf->type == KEELOQ_LEARNING_SIMPLE ||
|
||||
mf->type == KEELOQ_LEARNING_UNKNOWN) {
|
||||
uint32_t decrypt = subghz_protocol_keeloq_common_decrypt(hop, mf->key);
|
||||
if((decrypt >> 28) == btn &&
|
||||
((decrypt >> 16) & 0xFF) == (serial & 0xFF)) {
|
||||
inst->generic.cnt = decrypt & 0xFFFF;
|
||||
inst->manufacture_name = furi_string_get_cstr(mf->name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Alloc / Free / Reset ────────────────────────────────────────────────────
|
||||
|
||||
static void* renault_valeo_alloc(SubGhzEnvironment* env) {
|
||||
RenaultValeoDecoder* inst = malloc(sizeof(RenaultValeoDecoder));
|
||||
memset(inst, 0, sizeof(RenaultValeoDecoder));
|
||||
inst->base.protocol = &subghz_protocol_renault_valeo;
|
||||
inst->generic.protocol_name = inst->base.protocol->name;
|
||||
inst->keystore = subghz_environment_get_keystore(env);
|
||||
inst->manufacture_name = "Unknown";
|
||||
return inst;
|
||||
}
|
||||
|
||||
static void renault_valeo_free(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static void renault_valeo_reset(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
RenaultValeoDecoder* inst = ctx;
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = ValeoStepReset;
|
||||
}
|
||||
|
||||
// ─── Feed — OOK PWM ─────────────────────────────────────────────────────────
|
||||
|
||||
static void renault_valeo_try_accept(RenaultValeoDecoder* inst) {
|
||||
if(renault_valeo_frame_is_plausible(inst)) {
|
||||
inst->generic.data = inst->data;
|
||||
inst->generic.data_count_bit = inst->bit_count;
|
||||
renault_valeo_decode_keeloq(inst);
|
||||
if(inst->base.callback) {
|
||||
inst->base.callback(&inst->base, inst->base.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void renault_valeo_feed(void* ctx, bool level, uint32_t duration) {
|
||||
furi_assert(ctx);
|
||||
RenaultValeoDecoder* inst = ctx;
|
||||
|
||||
switch(inst->parser_step) {
|
||||
|
||||
case ValeoStepReset:
|
||||
if(level) {
|
||||
if(renault_valeo_is_short(duration)) {
|
||||
inst->data = (inst->data << 1);
|
||||
inst->bit_count++;
|
||||
inst->parser_step = ValeoStepWaitSpace;
|
||||
} else if(renault_valeo_is_long(duration)) {
|
||||
inst->data = (inst->data << 1) | 1;
|
||||
inst->bit_count++;
|
||||
inst->parser_step = ValeoStepWaitSpace;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ValeoStepWaitSpace:
|
||||
if(!level) {
|
||||
if(renault_valeo_is_short(duration)) {
|
||||
inst->parser_step = ValeoStepWaitMark;
|
||||
} else if(duration >= VALEO_GAP_MIN) {
|
||||
renault_valeo_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = ValeoStepReset;
|
||||
} else {
|
||||
// Allow some tolerance on space — accept wider spaces as inter-bit
|
||||
if(duration < VALEO_GAP_MIN) {
|
||||
inst->parser_step = ValeoStepWaitMark;
|
||||
} else {
|
||||
renault_valeo_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = ValeoStepReset;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ValeoStepWaitMark:
|
||||
if(level) {
|
||||
if(renault_valeo_is_short(duration)) {
|
||||
inst->data = (inst->data << 1);
|
||||
inst->bit_count++;
|
||||
inst->parser_step = ValeoStepWaitSpace;
|
||||
} else if(renault_valeo_is_long(duration)) {
|
||||
inst->data = (inst->data << 1) | 1;
|
||||
inst->bit_count++;
|
||||
inst->parser_step = ValeoStepWaitSpace;
|
||||
} else {
|
||||
renault_valeo_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = ValeoStepReset;
|
||||
}
|
||||
} else {
|
||||
if(duration >= VALEO_GAP_MIN) {
|
||||
renault_valeo_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = ValeoStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
renault_valeo_reset(ctx);
|
||||
break;
|
||||
}
|
||||
|
||||
if(inst->bit_count > VALEO_MAX_BITS) {
|
||||
renault_valeo_try_accept(inst);
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->parser_step = ValeoStepReset;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Hash ────────────────────────────────────────────────────────────────────
|
||||
|
||||
static uint8_t renault_valeo_get_hash(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
RenaultValeoDecoder* inst = ctx;
|
||||
return (uint8_t)(inst->generic.data ^
|
||||
(inst->generic.data >> 8) ^
|
||||
(inst->generic.data >> 16) ^
|
||||
(inst->generic.data >> 24));
|
||||
}
|
||||
|
||||
// ─── Serialize / Deserialize ─────────────────────────────────────────────────
|
||||
|
||||
static SubGhzProtocolStatus renault_valeo_serialize(
|
||||
void* ctx,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(ctx);
|
||||
RenaultValeoDecoder* inst = ctx;
|
||||
SubGhzProtocolStatus res =
|
||||
subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
|
||||
if(res == SubGhzProtocolStatusOk) {
|
||||
if(!flipper_format_write_string_cstr(
|
||||
flipper_format, "Manufacture", inst->manufacture_name)) {
|
||||
res = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static SubGhzProtocolStatus
|
||||
renault_valeo_deserialize(void* ctx, FlipperFormat* flipper_format) {
|
||||
furi_assert(ctx);
|
||||
RenaultValeoDecoder* inst = ctx;
|
||||
SubGhzProtocolStatus res =
|
||||
subghz_block_generic_deserialize_check_count_bit(
|
||||
&inst->generic, flipper_format, VALEO_MIN_BITS);
|
||||
if(res == SubGhzProtocolStatusOk) {
|
||||
inst->data = inst->generic.data;
|
||||
inst->bit_count = inst->generic.data_count_bit;
|
||||
|
||||
// Read manufacture name safely
|
||||
FuriString* mf = furi_string_alloc();
|
||||
if(flipper_format_read_string(flipper_format, "Manufacture", mf)) {
|
||||
// Store a copy since mf will be freed
|
||||
if(furi_string_size(mf) > 0) {
|
||||
inst->manufacture_name = "Loaded";
|
||||
}
|
||||
}
|
||||
furi_string_free(mf);
|
||||
|
||||
// Re-extract fields
|
||||
uint32_t fix = (uint32_t)(inst->generic.data >> 32);
|
||||
inst->generic.serial = fix & 0x0FFFFFFF;
|
||||
inst->generic.btn = (fix >> 28) & 0xF;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// ─── get_string ──────────────────────────────────────────────────────────────
|
||||
|
||||
static void renault_valeo_get_string(void* ctx, FuriString* output) {
|
||||
furi_assert(ctx);
|
||||
RenaultValeoDecoder* inst = ctx;
|
||||
|
||||
uint32_t fix = (uint32_t)(inst->generic.data >> 32);
|
||||
inst->generic.serial = fix & 0x0FFFFFFF;
|
||||
inst->generic.btn = (fix >> 28) & 0xF;
|
||||
|
||||
subghz_block_generic_global.btn_is_available = true;
|
||||
subghz_block_generic_global.current_btn = inst->generic.btn;
|
||||
subghz_block_generic_global.btn_length_bit = 4;
|
||||
subghz_block_generic_global.cnt_is_available = true;
|
||||
subghz_block_generic_global.current_cnt = inst->generic.cnt;
|
||||
subghz_block_generic_global.cnt_length_bit = 16;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Sn:%07lX Btn:%X\r\n"
|
||||
"Cnt:%04lX Mf:%s\r\n",
|
||||
inst->generic.protocol_name,
|
||||
inst->generic.data_count_bit,
|
||||
(unsigned long long)inst->generic.data,
|
||||
(unsigned long)inst->generic.serial,
|
||||
(unsigned int)inst->generic.btn,
|
||||
(unsigned long)inst->generic.cnt,
|
||||
inst->manufacture_name);
|
||||
}
|
||||
|
||||
// ─── Descriptor ──────────────────────────────────────────────────────────────
|
||||
|
||||
static const SubGhzProtocolDecoder renault_valeo_decoder = {
|
||||
.alloc = renault_valeo_alloc,
|
||||
.free = renault_valeo_free,
|
||||
.feed = renault_valeo_feed,
|
||||
.reset = renault_valeo_reset,
|
||||
.get_hash_data = renault_valeo_get_hash,
|
||||
.serialize = renault_valeo_serialize,
|
||||
.deserialize = renault_valeo_deserialize,
|
||||
.get_string = renault_valeo_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_renault_valeo = {
|
||||
.name = SUBGHZ_PROTOCOL_RENAULT_VALEO_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 |
|
||||
SubGhzProtocolFlag_AM |
|
||||
SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save,
|
||||
.decoder = &renault_valeo_decoder,
|
||||
.encoder = NULL,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
|
||||
#define SUBGHZ_PROTOCOL_RENAULT_VALEO_NAME "Renault_Valeo"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_renault_valeo;
|
||||
@@ -1,333 +0,0 @@
|
||||
#include "renault_valeo_fsk.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "keeloq_common.h"
|
||||
#include "../subghz_keystore.h"
|
||||
#include "../subghz_keystore_i.h"
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <m-array.h>
|
||||
|
||||
#define TAG "RenaultValeoFSK"
|
||||
|
||||
// Valeo FSK (Megane III, Scenic III, Ren3) — 2FSKDev476Async
|
||||
// Manchester encoding over FSK
|
||||
// te_short = 500 µs (half-bit cell)
|
||||
// te_long = 1000 µs (full-bit cell)
|
||||
// te_delta = 200 µs
|
||||
// Preamble: alternating half-cells (min 8)
|
||||
|
||||
#define VALEO_FSK_TE_SHORT 500
|
||||
#define VALEO_FSK_TE_LONG 1000
|
||||
#define VALEO_FSK_TE_DELTA 200
|
||||
#define VALEO_FSK_TE_SHORT_ALT 400
|
||||
#define VALEO_FSK_TE_LONG_ALT 800
|
||||
#define VALEO_FSK_TE_DELTA_ALT 180
|
||||
#define VALEO_FSK_MIN_BITS 64
|
||||
#define VALEO_FSK_MAX_BITS 96
|
||||
#define VALEO_FSK_PREAMBLE_MIN 8
|
||||
|
||||
#ifndef DURATION_DIFF
|
||||
#define DURATION_DIFF(x, y) (((x) > (y)) ? ((x) - (y)) : ((y) - (x)))
|
||||
#endif
|
||||
|
||||
typedef enum {
|
||||
ValeoFSKStepReset = 0,
|
||||
ValeoFSKStepPreamble,
|
||||
ValeoFSKStepDecode,
|
||||
} ValeoFSKStep;
|
||||
|
||||
// ─── Struct ──────────────────────────────────────────────────────────────────
|
||||
|
||||
typedef struct {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint64_t data;
|
||||
uint8_t bit_count;
|
||||
uint8_t preamble_count;
|
||||
ManchesterState manchester_state;
|
||||
SubGhzKeystore* keystore;
|
||||
const char* manufacture_name;
|
||||
} RenaultValeoFSKDecoder;
|
||||
|
||||
static bool renault_valeo_fsk_frame_is_plausible(RenaultValeoFSKDecoder* inst) {
|
||||
if(inst->bit_count < VALEO_FSK_MIN_BITS || inst->bit_count > VALEO_FSK_MAX_BITS) {
|
||||
return false;
|
||||
}
|
||||
if((inst->bit_count % 2U) != 0U) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t fix = (uint32_t)(inst->data >> 32U);
|
||||
const uint8_t btn = (uint8_t)((fix >> 28U) & 0x0FU);
|
||||
const uint32_t serial = fix & 0x0FFFFFFFU;
|
||||
return (serial != 0U) && (btn <= 0x0DU);
|
||||
}
|
||||
|
||||
// ─── KeeLoq decode ───────────────────────────────────────────────────────────
|
||||
|
||||
static void renault_valeo_fsk_decode_keeloq(RenaultValeoFSKDecoder* inst) {
|
||||
if(!inst->keystore) return;
|
||||
|
||||
uint32_t fix = (uint32_t)(inst->data >> 32);
|
||||
uint32_t hop = (uint32_t)(inst->data & 0xFFFFFFFF);
|
||||
|
||||
uint8_t btn = (fix >> 28) & 0xF;
|
||||
uint32_t serial = fix & 0x0FFFFFFF;
|
||||
|
||||
inst->generic.serial = serial;
|
||||
inst->generic.btn = btn;
|
||||
inst->manufacture_name = "Unknown";
|
||||
|
||||
for
|
||||
M_EACH(mf, *subghz_keystore_get_data(inst->keystore), SubGhzKeyArray_t) {
|
||||
if(mf->type == KEELOQ_LEARNING_NORMAL ||
|
||||
mf->type == KEELOQ_LEARNING_UNKNOWN) {
|
||||
uint64_t man = subghz_protocol_keeloq_common_normal_learning(fix, mf->key);
|
||||
uint32_t decrypt = subghz_protocol_keeloq_common_decrypt(hop, man);
|
||||
if((decrypt >> 28) == btn &&
|
||||
((decrypt >> 16) & 0xFF) == (serial & 0xFF)) {
|
||||
inst->generic.cnt = decrypt & 0xFFFF;
|
||||
inst->manufacture_name = furi_string_get_cstr(mf->name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if(mf->type == KEELOQ_LEARNING_SIMPLE ||
|
||||
mf->type == KEELOQ_LEARNING_UNKNOWN) {
|
||||
uint32_t decrypt = subghz_protocol_keeloq_common_decrypt(hop, mf->key);
|
||||
if((decrypt >> 28) == btn &&
|
||||
((decrypt >> 16) & 0xFF) == (serial & 0xFF)) {
|
||||
inst->generic.cnt = decrypt & 0xFFFF;
|
||||
inst->manufacture_name = furi_string_get_cstr(mf->name);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Accept helper ───────────────────────────────────────────────────────────
|
||||
|
||||
static void renault_valeo_fsk_try_accept(RenaultValeoFSKDecoder* inst) {
|
||||
if(renault_valeo_fsk_frame_is_plausible(inst)) {
|
||||
inst->generic.data = inst->data;
|
||||
inst->generic.data_count_bit = inst->bit_count;
|
||||
renault_valeo_fsk_decode_keeloq(inst);
|
||||
if(inst->base.callback) {
|
||||
inst->base.callback(&inst->base, inst->base.context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Alloc / Free / Reset ────────────────────────────────────────────────────
|
||||
|
||||
static void* renault_valeo_fsk_alloc(SubGhzEnvironment* env) {
|
||||
RenaultValeoFSKDecoder* inst = malloc(sizeof(RenaultValeoFSKDecoder));
|
||||
memset(inst, 0, sizeof(RenaultValeoFSKDecoder));
|
||||
inst->base.protocol = &subghz_protocol_renault_valeo_fsk;
|
||||
inst->generic.protocol_name = inst->base.protocol->name;
|
||||
inst->keystore = subghz_environment_get_keystore(env);
|
||||
inst->manufacture_name = "Unknown";
|
||||
inst->manchester_state = ManchesterStateMid1;
|
||||
inst->decoder.parser_step = ValeoFSKStepReset;
|
||||
return inst;
|
||||
}
|
||||
|
||||
static void renault_valeo_fsk_free(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
free(ctx);
|
||||
}
|
||||
|
||||
static void renault_valeo_fsk_reset(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
RenaultValeoFSKDecoder* inst = ctx;
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->preamble_count = 0;
|
||||
inst->manchester_state = ManchesterStateMid1;
|
||||
inst->decoder.parser_step = ValeoFSKStepReset;
|
||||
}
|
||||
|
||||
// ─── Feed — Manchester over FSK ──────────────────────────────────────────────
|
||||
|
||||
static void renault_valeo_fsk_feed(void* ctx, bool level, uint32_t duration) {
|
||||
furi_assert(ctx);
|
||||
RenaultValeoFSKDecoder* inst = ctx;
|
||||
|
||||
// Classify duration
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
|
||||
if((DURATION_DIFF(duration, VALEO_FSK_TE_SHORT) < VALEO_FSK_TE_DELTA) ||
|
||||
(DURATION_DIFF(duration, VALEO_FSK_TE_SHORT_ALT) < VALEO_FSK_TE_DELTA_ALT)) {
|
||||
event = level ? ManchesterEventShortHigh : ManchesterEventShortLow;
|
||||
} else if((DURATION_DIFF(duration, VALEO_FSK_TE_LONG) < VALEO_FSK_TE_DELTA) ||
|
||||
(DURATION_DIFF(duration, VALEO_FSK_TE_LONG_ALT) < VALEO_FSK_TE_DELTA_ALT)) {
|
||||
event = level ? ManchesterEventLongHigh : ManchesterEventLongLow;
|
||||
} else {
|
||||
// Out of range — gap or noise
|
||||
renault_valeo_fsk_try_accept(inst);
|
||||
renault_valeo_fsk_reset(ctx);
|
||||
return;
|
||||
}
|
||||
|
||||
switch(inst->decoder.parser_step) {
|
||||
|
||||
case ValeoFSKStepReset:
|
||||
if(event == ManchesterEventShortHigh || event == ManchesterEventShortLow) {
|
||||
inst->preamble_count = 1;
|
||||
inst->decoder.parser_step = ValeoFSKStepPreamble;
|
||||
}
|
||||
break;
|
||||
|
||||
case ValeoFSKStepPreamble:
|
||||
if(event == ManchesterEventShortHigh || event == ManchesterEventShortLow) {
|
||||
inst->preamble_count++;
|
||||
if(inst->preamble_count >= VALEO_FSK_PREAMBLE_MIN) {
|
||||
inst->data = 0;
|
||||
inst->bit_count = 0;
|
||||
inst->manchester_state = ManchesterStateMid1;
|
||||
inst->decoder.parser_step = ValeoFSKStepDecode;
|
||||
}
|
||||
} else {
|
||||
renault_valeo_fsk_reset(ctx);
|
||||
}
|
||||
break;
|
||||
|
||||
case ValeoFSKStepDecode: {
|
||||
bool bit_out = false;
|
||||
ManchesterState next_state;
|
||||
|
||||
if(manchester_advance(
|
||||
inst->manchester_state, event, &next_state, &bit_out)) {
|
||||
inst->data = (inst->data << 1) | (bit_out ? 1 : 0);
|
||||
inst->bit_count++;
|
||||
|
||||
if(inst->bit_count >= VALEO_FSK_MAX_BITS) {
|
||||
renault_valeo_fsk_try_accept(inst);
|
||||
renault_valeo_fsk_reset(ctx);
|
||||
return;
|
||||
}
|
||||
}
|
||||
inst->manchester_state = next_state;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
renault_valeo_fsk_reset(ctx);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Hash ────────────────────────────────────────────────────────────────────
|
||||
|
||||
static uint8_t renault_valeo_fsk_get_hash(void* ctx) {
|
||||
furi_assert(ctx);
|
||||
RenaultValeoFSKDecoder* inst = ctx;
|
||||
return (uint8_t)(inst->generic.data ^
|
||||
(inst->generic.data >> 8) ^
|
||||
(inst->generic.data >> 16) ^
|
||||
(inst->generic.data >> 24));
|
||||
}
|
||||
|
||||
// ─── Serialize / Deserialize ─────────────────────────────────────────────────
|
||||
|
||||
static SubGhzProtocolStatus renault_valeo_fsk_serialize(
|
||||
void* ctx,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(ctx);
|
||||
RenaultValeoFSKDecoder* inst = ctx;
|
||||
SubGhzProtocolStatus res =
|
||||
subghz_block_generic_serialize(&inst->generic, flipper_format, preset);
|
||||
if(res == SubGhzProtocolStatusOk) {
|
||||
if(!flipper_format_write_string_cstr(
|
||||
flipper_format, "Manufacture", inst->manufacture_name)) {
|
||||
res = SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static SubGhzProtocolStatus
|
||||
renault_valeo_fsk_deserialize(void* ctx, FlipperFormat* flipper_format) {
|
||||
furi_assert(ctx);
|
||||
RenaultValeoFSKDecoder* inst = ctx;
|
||||
SubGhzProtocolStatus res =
|
||||
subghz_block_generic_deserialize_check_count_bit(
|
||||
&inst->generic, flipper_format, VALEO_FSK_MIN_BITS);
|
||||
if(res == SubGhzProtocolStatusOk) {
|
||||
inst->data = inst->generic.data;
|
||||
inst->bit_count = inst->generic.data_count_bit;
|
||||
|
||||
FuriString* mf = furi_string_alloc();
|
||||
if(flipper_format_read_string(flipper_format, "Manufacture", mf)) {
|
||||
if(furi_string_size(mf) > 0) {
|
||||
inst->manufacture_name = "Loaded";
|
||||
}
|
||||
}
|
||||
furi_string_free(mf);
|
||||
|
||||
uint32_t fix = (uint32_t)(inst->generic.data >> 32);
|
||||
inst->generic.serial = fix & 0x0FFFFFFF;
|
||||
inst->generic.btn = (fix >> 28) & 0xF;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
// ─── get_string ──────────────────────────────────────────────────────────────
|
||||
|
||||
static void renault_valeo_fsk_get_string(void* ctx, FuriString* output) {
|
||||
furi_assert(ctx);
|
||||
RenaultValeoFSKDecoder* inst = ctx;
|
||||
|
||||
uint32_t fix = (uint32_t)(inst->generic.data >> 32);
|
||||
inst->generic.serial = fix & 0x0FFFFFFF;
|
||||
inst->generic.btn = (fix >> 28) & 0xF;
|
||||
|
||||
subghz_block_generic_global.btn_is_available = true;
|
||||
subghz_block_generic_global.current_btn = inst->generic.btn;
|
||||
subghz_block_generic_global.btn_length_bit = 4;
|
||||
subghz_block_generic_global.cnt_is_available = true;
|
||||
subghz_block_generic_global.current_cnt = inst->generic.cnt;
|
||||
subghz_block_generic_global.cnt_length_bit = 16;
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Sn:%07lX Btn:%X\r\n"
|
||||
"Cnt:%04lX Mf:%s\r\n",
|
||||
inst->generic.protocol_name,
|
||||
inst->generic.data_count_bit,
|
||||
(unsigned long long)inst->generic.data,
|
||||
(unsigned long)inst->generic.serial,
|
||||
(unsigned int)inst->generic.btn,
|
||||
(unsigned long)inst->generic.cnt,
|
||||
inst->manufacture_name);
|
||||
}
|
||||
|
||||
// ─── Descriptor ──────────────────────────────────────────────────────────────
|
||||
|
||||
static const SubGhzProtocolDecoder renault_valeo_fsk_decoder = {
|
||||
.alloc = renault_valeo_fsk_alloc,
|
||||
.free = renault_valeo_fsk_free,
|
||||
.feed = renault_valeo_fsk_feed,
|
||||
.reset = renault_valeo_fsk_reset,
|
||||
.get_hash_data = renault_valeo_fsk_get_hash,
|
||||
.serialize = renault_valeo_fsk_serialize,
|
||||
.deserialize = renault_valeo_fsk_deserialize,
|
||||
.get_string = renault_valeo_fsk_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_renault_valeo_fsk = {
|
||||
.name = SUBGHZ_PROTOCOL_RENAULT_VALEO_FSK_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 |
|
||||
SubGhzProtocolFlag_FM |
|
||||
SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load |
|
||||
SubGhzProtocolFlag_Save,
|
||||
.decoder = &renault_valeo_fsk_decoder,
|
||||
.encoder = NULL,
|
||||
};
|
||||
@@ -1,7 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
|
||||
#define SUBGHZ_PROTOCOL_RENAULT_VALEO_FSK_NAME "Renault_Valeo_FSK"
|
||||
|
||||
extern const SubGhzProtocol subghz_protocol_renault_valeo_fsk;
|
||||
Reference in New Issue
Block a user