mirror of
https://github.com/D4C1-Labs/Flipper-ARF.git
synced 2026-05-13 21:43:06 +00:00
Compare commits
28 Commits
dev-b318b3e
..
1.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
| 5825020a20 | |||
| 91e9a4cbac | |||
| c92b1db8b7 | |||
| 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 |
@@ -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,6 +49,7 @@ 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 | Yes |
|
||||
| Renault (old models) | Marelli | 433 MHz | AM | No | Yes | No|
|
||||
@@ -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,42 @@
|
||||
# to use manual settings and prevent them from being deleted on upgrade, rename *_user.example files to *_user
|
||||
Filetype: Flipper SubGhz Setting File
|
||||
Version: 1
|
||||
# Add Standard frequencies included with firmware and place user frequencies after them
|
||||
#Add_standard_frequencies: true
|
||||
|
||||
# Default Frequency: used as default for "Read" and "Read Raw"
|
||||
#Default_frequency: 433920000
|
||||
|
||||
# Frequencies used for "Read", "Read Raw" and "Frequency Analyzer"
|
||||
#Frequency: 300000000
|
||||
#Frequency: 310000000
|
||||
#Frequency: 320000000
|
||||
|
||||
# Frequencies used for hopping mode (keep this list small or flipper will miss signal)
|
||||
#Hopper_frequency: 300000000
|
||||
#Hopper_frequency: 310000000
|
||||
#Hopper_frequency: 310000000
|
||||
|
||||
# Custom preset
|
||||
# format for CC1101 "Custom_preset_data:" XX YY XX YY .. 00 00 ZZ ZZ ZZ ZZ ZZ ZZ ZZ ZZ, where: XX-register, YY - register data, 00 00 - end load register, ZZ - 8 byte Pa table register
|
||||
|
||||
#Custom_preset_name: FM95
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 0B 06 08 32 07 04 14 00 13 02 12 04 11 83 10 67 15 24 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
|
||||
|
||||
#2-FSK 200khz BW / 135kHz Filter/ 15.86Khz Deviation + Ramping
|
||||
#Custom_preset_name: FM15k
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 03 47 08 32 0B 06 15 32 14 00 13 00 12 00 11 32 10 A7 18 18 19 1D 1D 92 1C 00 1B 04 20 FB 22 17 21 B6 00 00 00 12 0E 34 60 C5 C1 C0
|
||||
|
||||
#Custom_preset_name: Pagers
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 07 04 08 32 0B 06 10 64 11 93 12 0C 13 02 14 00 15 15 18 18 19 16 1B 07 1C 00 1D 91 20 FB 21 56 22 10 00 00 C0 00 00 00 00 00 00 00
|
||||
|
||||
#Custom_preset_name: AM_1
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
|
||||
|
||||
#Custom_preset_name: AM_2
|
||||
#Custom_preset_module: CC1101
|
||||
#Custom_preset_data: 02 0D 03 07 08 32 0B 06 14 00 13 00 12 30 11 32 10 17 18 18 19 18 1D 91 1C 00 1B 07 20 FB 22 11 21 B6 00 00 00 C0 00 00 00 00 00 00
|
||||
@@ -0,0 +1,252 @@
|
||||
#include "aes_common.h"
|
||||
|
||||
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};
|
||||
|
||||
static const uint8_t aes_sbox_inv[256] = {
|
||||
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7,
|
||||
0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde,
|
||||
0xe9, 0xcb, 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42,
|
||||
0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49,
|
||||
0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c,
|
||||
0xcc, 0x5d, 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15,
|
||||
0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7,
|
||||
0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
|
||||
0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc,
|
||||
0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad,
|
||||
0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d,
|
||||
0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, 0xfc, 0x56, 0x3e, 0x4b,
|
||||
0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8,
|
||||
0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, 0x51,
|
||||
0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef, 0xa0,
|
||||
0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
|
||||
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c,
|
||||
0x7d};
|
||||
|
||||
static const uint8_t aes_rcon[10] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36};
|
||||
|
||||
static uint8_t gf_mul2(uint8_t x) {
|
||||
return ((x >> 7) * 0x1b) ^ (x << 1);
|
||||
}
|
||||
|
||||
static void aes_subbytes(uint8_t* state) {
|
||||
for(uint8_t row = 0; row < 4; row++) {
|
||||
for(uint8_t col = 0; col < 4; col++) {
|
||||
state[row + col * 4] = aes_sbox[state[row + col * 4]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void aes_subbytes_inv(uint8_t* state) {
|
||||
for(uint8_t row = 0; row < 4; row++) {
|
||||
for(uint8_t col = 0; col < 4; col++) {
|
||||
state[row + col * 4] = aes_sbox_inv[state[row + col * 4]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void aes_shiftrows(uint8_t* state) {
|
||||
uint8_t temp;
|
||||
|
||||
temp = state[1];
|
||||
state[1] = state[5];
|
||||
state[5] = state[9];
|
||||
state[9] = state[13];
|
||||
state[13] = temp;
|
||||
|
||||
temp = state[2];
|
||||
state[2] = state[10];
|
||||
state[10] = temp;
|
||||
temp = state[6];
|
||||
state[6] = state[14];
|
||||
state[14] = temp;
|
||||
|
||||
temp = state[15];
|
||||
state[15] = state[11];
|
||||
state[11] = state[7];
|
||||
state[7] = state[3];
|
||||
state[3] = temp;
|
||||
}
|
||||
|
||||
static void aes_shiftrows_inv(uint8_t* state) {
|
||||
uint8_t temp;
|
||||
|
||||
temp = state[13];
|
||||
state[13] = state[9];
|
||||
state[9] = state[5];
|
||||
state[5] = state[1];
|
||||
state[1] = temp;
|
||||
|
||||
temp = state[2];
|
||||
state[2] = state[10];
|
||||
state[10] = temp;
|
||||
temp = state[6];
|
||||
state[6] = state[14];
|
||||
state[14] = temp;
|
||||
|
||||
temp = state[3];
|
||||
state[3] = state[7];
|
||||
state[7] = state[11];
|
||||
state[11] = state[15];
|
||||
state[15] = temp;
|
||||
}
|
||||
|
||||
static void aes_mixcolumns(uint8_t* state) {
|
||||
uint8_t a, b, c, d;
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
a = state[i * 4];
|
||||
b = state[i * 4 + 1];
|
||||
c = state[i * 4 + 2];
|
||||
d = state[i * 4 + 3];
|
||||
|
||||
uint8_t a2 = gf_mul2(a);
|
||||
uint8_t b2 = gf_mul2(b);
|
||||
uint8_t c2 = gf_mul2(c);
|
||||
uint8_t d2 = gf_mul2(d);
|
||||
|
||||
state[i * 4] = a2 ^ b2 ^ b ^ c ^ d;
|
||||
state[i * 4 + 1] = a ^ b2 ^ c2 ^ c ^ d;
|
||||
state[i * 4 + 2] = a ^ b ^ c2 ^ d2 ^ d;
|
||||
state[i * 4 + 3] = a2 ^ a ^ b ^ c ^ d2;
|
||||
}
|
||||
}
|
||||
|
||||
static void aes_mixcolumns_inv(uint8_t* state) {
|
||||
uint8_t a, b, c, d;
|
||||
for(uint8_t i = 0; i < 4; i++) {
|
||||
a = state[i * 4];
|
||||
b = state[i * 4 + 1];
|
||||
c = state[i * 4 + 2];
|
||||
d = state[i * 4 + 3];
|
||||
|
||||
uint8_t a2 = gf_mul2(a);
|
||||
uint8_t a4 = gf_mul2(a2);
|
||||
uint8_t a8 = gf_mul2(a4);
|
||||
uint8_t b2 = gf_mul2(b);
|
||||
uint8_t b4 = gf_mul2(b2);
|
||||
uint8_t b8 = gf_mul2(b4);
|
||||
uint8_t c2 = gf_mul2(c);
|
||||
uint8_t c4 = gf_mul2(c2);
|
||||
uint8_t c8 = gf_mul2(c4);
|
||||
uint8_t d2 = gf_mul2(d);
|
||||
uint8_t d4 = gf_mul2(d2);
|
||||
uint8_t d8 = gf_mul2(d4);
|
||||
|
||||
state[i * 4] = (a8 ^ a4 ^ a2) ^ (b8 ^ b2 ^ b) ^ (c8 ^ c4 ^ c) ^ (d8 ^ d);
|
||||
state[i * 4 + 1] = (a8 ^ a) ^ (b8 ^ b4 ^ b2) ^ (c8 ^ c2 ^ c) ^ (d8 ^ d4 ^ d);
|
||||
state[i * 4 + 2] = (a8 ^ a4 ^ a) ^ (b8 ^ b) ^ (c8 ^ c4 ^ c2) ^ (d8 ^ d2 ^ d);
|
||||
state[i * 4 + 3] = (a8 ^ a2 ^ a) ^ (b8 ^ b4 ^ b) ^ (c8 ^ c) ^ (d8 ^ d4 ^ d2);
|
||||
}
|
||||
}
|
||||
|
||||
static void aes_addroundkey(uint8_t* state, const uint8_t* round_key) {
|
||||
for(uint8_t col = 0; col < 4; col++) {
|
||||
state[col * 4] ^= round_key[col * 4];
|
||||
state[col * 4 + 1] ^= round_key[col * 4 + 1];
|
||||
state[col * 4 + 2] ^= round_key[col * 4 + 2];
|
||||
state[col * 4 + 3] ^= round_key[col * 4 + 3];
|
||||
}
|
||||
}
|
||||
|
||||
void aes_key_expansion(const uint8_t* key, uint8_t* round_keys) {
|
||||
for(uint8_t i = 0; i < 16; i++) {
|
||||
round_keys[i] = key[i];
|
||||
}
|
||||
|
||||
for(uint8_t i = 4; i < 44; i++) {
|
||||
uint8_t prev_word_idx = (i - 1) * 4;
|
||||
uint8_t b0 = round_keys[prev_word_idx];
|
||||
uint8_t b1 = round_keys[prev_word_idx + 1];
|
||||
uint8_t b2 = round_keys[prev_word_idx + 2];
|
||||
uint8_t b3 = round_keys[prev_word_idx + 3];
|
||||
|
||||
if((i % 4) == 0) {
|
||||
uint8_t new_b0 = aes_sbox[b1] ^ aes_rcon[(i / 4) - 1];
|
||||
uint8_t new_b1 = aes_sbox[b2];
|
||||
uint8_t new_b2 = aes_sbox[b3];
|
||||
uint8_t new_b3 = aes_sbox[b0];
|
||||
b0 = new_b0;
|
||||
b1 = new_b1;
|
||||
b2 = new_b2;
|
||||
b3 = new_b3;
|
||||
}
|
||||
|
||||
uint8_t back_word_idx = (i - 4) * 4;
|
||||
b0 ^= round_keys[back_word_idx];
|
||||
b1 ^= round_keys[back_word_idx + 1];
|
||||
b2 ^= round_keys[back_word_idx + 2];
|
||||
b3 ^= round_keys[back_word_idx + 3];
|
||||
|
||||
uint8_t curr_word_idx = i * 4;
|
||||
round_keys[curr_word_idx] = b0;
|
||||
round_keys[curr_word_idx + 1] = b1;
|
||||
round_keys[curr_word_idx + 2] = b2;
|
||||
round_keys[curr_word_idx + 3] = b3;
|
||||
}
|
||||
}
|
||||
|
||||
void aes128_encrypt(const uint8_t* expanded_key, uint8_t* data) {
|
||||
uint8_t state[16];
|
||||
memcpy(state, data, 16);
|
||||
|
||||
aes_addroundkey(state, &expanded_key[0]);
|
||||
|
||||
for(uint8_t round = 1; round < 10; round++) {
|
||||
aes_subbytes(state);
|
||||
aes_shiftrows(state);
|
||||
aes_mixcolumns(state);
|
||||
aes_addroundkey(state, &expanded_key[round * 16]);
|
||||
}
|
||||
|
||||
aes_subbytes(state);
|
||||
aes_shiftrows(state);
|
||||
aes_addroundkey(state, &expanded_key[160]);
|
||||
|
||||
memcpy(data, state, 16);
|
||||
}
|
||||
|
||||
void aes128_decrypt(const uint8_t* expanded_key, uint8_t* data) {
|
||||
uint8_t state[16];
|
||||
memcpy(state, data, 16);
|
||||
|
||||
aes_addroundkey(state, &expanded_key[160]);
|
||||
|
||||
for(uint8_t round = 9; round > 0; round--) {
|
||||
aes_shiftrows_inv(state);
|
||||
aes_subbytes_inv(state);
|
||||
aes_addroundkey(state, &expanded_key[round * 16]);
|
||||
aes_mixcolumns_inv(state);
|
||||
}
|
||||
|
||||
aes_shiftrows_inv(state);
|
||||
aes_subbytes_inv(state);
|
||||
aes_addroundkey(state, &expanded_key[0]);
|
||||
|
||||
memcpy(data, state, 16);
|
||||
}
|
||||
|
||||
void reverse_bits_in_bytes(uint8_t* data, uint8_t len) {
|
||||
for(uint8_t i = 0; i < len; i++) {
|
||||
uint8_t byte = data[i];
|
||||
uint8_t step1 = ((byte & 0x55) << 1) | ((byte >> 1) & 0x55);
|
||||
uint8_t step2 = ((step1 & 0x33) << 2) | ((step1 >> 2) & 0x33);
|
||||
data[i] = ((step2 & 0x0F) << 4) | (step2 >> 4);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#include <furi.h>
|
||||
|
||||
void reverse_bits_in_bytes(uint8_t* data, uint8_t len);
|
||||
void aes128_decrypt(const uint8_t* expanded_key, uint8_t* data);
|
||||
void aes128_encrypt(const uint8_t* expanded_key, uint8_t* data);
|
||||
void aes_key_expansion(const uint8_t* key, uint8_t* round_keys);
|
||||
@@ -0,0 +1,376 @@
|
||||
/**
|
||||
* allstar_firefly.c -- Allstar Firefly 318ALD31K native SubGHz protocol
|
||||
*
|
||||
* Implements the SubGhzProtocol vtable for the Supertex ED-9 based gate remote.
|
||||
* Uses subghz_block_generic_serialize / deserialize for standard-format .sub
|
||||
* files, encoding the 9-position trinary DIP code as a uint64 (base-3, MSB
|
||||
* first: '+' = 2, '0' = 3, '-' = 0).
|
||||
*
|
||||
* Saved file format:
|
||||
* Filetype: Flipper SubGhz Key File
|
||||
* Version: 1
|
||||
* Frequency: 318000000
|
||||
* Preset: FuriHalSubGhzPresetOok650Async
|
||||
* Protocol: Allstar Firefly
|
||||
* Key: 00 00 00 00 00 00 34 B9
|
||||
* Bit: 18
|
||||
*
|
||||
* See allstar_firefly.h for full protocol documentation.
|
||||
*/
|
||||
|
||||
#include "allstar_firefly.h"
|
||||
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "AllstarFirefly"
|
||||
|
||||
#define DIP_P 0b11 //(+)
|
||||
#define DIP_O 0b10 //(0)
|
||||
#define DIP_N 0b00 //(-)
|
||||
|
||||
#define DIP_PATTERN "%c%c%c%c%c%c%c%c%c"
|
||||
|
||||
#define SHOW_DIP_P(dip, check_dip) \
|
||||
((((dip >> 0x10) & 0x3) == check_dip) ? '*' : '_'), \
|
||||
((((dip >> 0xE) & 0x3) == check_dip) ? '*' : '_'), \
|
||||
((((dip >> 0xC) & 0x3) == check_dip) ? '*' : '_'), \
|
||||
((((dip >> 0xA) & 0x3) == check_dip) ? '*' : '_'), \
|
||||
((((dip >> 0x8) & 0x3) == check_dip) ? '*' : '_'), \
|
||||
((((dip >> 0x6) & 0x3) == check_dip) ? '*' : '_'), \
|
||||
((((dip >> 0x4) & 0x3) == check_dip) ? '*' : '_'), \
|
||||
((((dip >> 0x2) & 0x3) == check_dip) ? '*' : '_'), \
|
||||
((((dip >> 0x0) & 0x3) == check_dip) ? '*' : '_')
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_allstar_firefly_const = {
|
||||
.te_short = 600,
|
||||
.te_long = 4000,
|
||||
.te_delta = 300,
|
||||
.min_count_bit_for_found = 18,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderAllstarFirefly {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderAllstarFirefly {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
AllstarFireflyDecoderStepReset = 0,
|
||||
AllstarFireflyDecoderStepSaveDuration,
|
||||
AllstarFireflyDecoderStepCheckDuration,
|
||||
} AllstarFireflyDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_allstar_firefly_decoder = {
|
||||
.alloc = subghz_protocol_decoder_allstar_firefly_alloc,
|
||||
.free = subghz_protocol_decoder_allstar_firefly_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_allstar_firefly_feed,
|
||||
.reset = subghz_protocol_decoder_allstar_firefly_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_allstar_firefly_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_allstar_firefly_serialize,
|
||||
.deserialize = subghz_protocol_decoder_allstar_firefly_deserialize,
|
||||
.get_string = subghz_protocol_decoder_allstar_firefly_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_allstar_firefly_encoder = {
|
||||
.alloc = subghz_protocol_encoder_allstar_firefly_alloc,
|
||||
.free = subghz_protocol_encoder_allstar_firefly_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_allstar_firefly_deserialize,
|
||||
.stop = subghz_protocol_encoder_allstar_firefly_stop,
|
||||
.yield = subghz_protocol_encoder_allstar_firefly_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_allstar_firefly = {
|
||||
.name = SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_315 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_allstar_firefly_decoder,
|
||||
.encoder = &subghz_protocol_allstar_firefly_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_allstar_firefly_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderAllstarFirefly* instance =
|
||||
malloc(sizeof(SubGhzProtocolEncoderAllstarFirefly));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_allstar_firefly;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 5;
|
||||
instance->encoder.size_upload = 256;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_allstar_firefly_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderAllstarFirefly* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderAllstarFirefly instance
|
||||
*/
|
||||
static void subghz_protocol_encoder_allstar_firefly_get_upload(
|
||||
SubGhzProtocolEncoderAllstarFirefly* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
|
||||
// Send key and GAP
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_allstar_firefly_const.te_long);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)(subghz_protocol_allstar_firefly_const.te_short * 50) + 400);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_allstar_firefly_const.te_short);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
true, (uint32_t)subghz_protocol_allstar_firefly_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)(subghz_protocol_allstar_firefly_const.te_short * 50) + 400);
|
||||
} else {
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_allstar_firefly_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_allstar_firefly_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderAllstarFirefly* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_allstar_firefly_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
// Optional value
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_encoder_allstar_firefly_get_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_allstar_firefly_stop(void* context) {
|
||||
SubGhzProtocolEncoderAllstarFirefly* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_allstar_firefly_yield(void* context) {
|
||||
SubGhzProtocolEncoderAllstarFirefly* 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;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_allstar_firefly_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderAllstarFirefly* instance =
|
||||
malloc(sizeof(SubGhzProtocolDecoderAllstarFirefly));
|
||||
instance->base.protocol = &subghz_protocol_allstar_firefly;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_allstar_firefly_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAllstarFirefly* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_allstar_firefly_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAllstarFirefly* instance = context;
|
||||
instance->decoder.parser_step = AllstarFireflyDecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_allstar_firefly_feed(
|
||||
void* context,
|
||||
bool level,
|
||||
volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAllstarFirefly* instance = context;
|
||||
|
||||
// Allstar Firefly Decoder
|
||||
// 2026 - by @jlaughter
|
||||
// Remake to match other protocols code base - @xMasterX (MMX)
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case AllstarFireflyDecoderStepReset:
|
||||
if((!level) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_allstar_firefly_const.te_short * 50) <
|
||||
subghz_protocol_allstar_firefly_const.te_delta * 5)) {
|
||||
// 30k us
|
||||
// Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = AllstarFireflyDecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case AllstarFireflyDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = AllstarFireflyDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = AllstarFireflyDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case AllstarFireflyDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 1 is long and short timing = 4000us HIGH (te_last) and 600us LOW
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_allstar_firefly_const.te_long) <
|
||||
subghz_protocol_allstar_firefly_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_allstar_firefly_const.te_short) <
|
||||
subghz_protocol_allstar_firefly_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = AllstarFireflyDecoderStepSaveDuration;
|
||||
// Bit 0 is short and long timing = 600us HIGH (te_last) and 4000us LOW
|
||||
} else if(
|
||||
(DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_allstar_firefly_const.te_short) <
|
||||
subghz_protocol_allstar_firefly_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_allstar_firefly_const.te_long) <
|
||||
subghz_protocol_allstar_firefly_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = AllstarFireflyDecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_allstar_firefly_const.te_short * 50) <
|
||||
subghz_protocol_allstar_firefly_const.te_delta * 5) {
|
||||
// Found next GAP and add bit 0 or 1
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_allstar_firefly_const.te_long) <
|
||||
subghz_protocol_allstar_firefly_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last,
|
||||
subghz_protocol_allstar_firefly_const.te_short) <
|
||||
subghz_protocol_allstar_firefly_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
// If got 18 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_allstar_firefly_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = AllstarFireflyDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = AllstarFireflyDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = AllstarFireflyDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_allstar_firefly_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAllstarFirefly* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAllstarFirefly* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAllstarFirefly* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_allstar_firefly_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_allstar_firefly_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderAllstarFirefly* instance = context;
|
||||
|
||||
uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key(
|
||||
instance->generic.data, instance->generic.data_count_bit);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key:0x%05lX Yek:0x%05lX\r\n"
|
||||
" +: " DIP_PATTERN "\r\n"
|
||||
" o: " DIP_PATTERN "\r\n"
|
||||
" -: " DIP_PATTERN "\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data & 0xFFFFF),
|
||||
(uint32_t)(code_found_reverse & 0xFFFFF),
|
||||
SHOW_DIP_P(instance->generic.data, DIP_P),
|
||||
SHOW_DIP_P(instance->generic.data, DIP_O),
|
||||
SHOW_DIP_P(instance->generic.data, DIP_N));
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* allstar_firefly.h - Allstar Firefly 318ALD31K native SubGHz protocol
|
||||
*
|
||||
* Registers the Allstar Firefly gate remote as a first-class SubGHz protocol,
|
||||
* supporting decode from air, save/load of .sub files, and retransmit.
|
||||
*
|
||||
* Protocol summary (measured from captured remotes):
|
||||
* Carrier : 318 MHz OOK (FuriHalSubGhzPresetOok650Async)
|
||||
* Code type : Static 9-bit trinary (Supertex ED-9 encoder IC)
|
||||
* Frame : 18 symbols (2 per bit), no separate sync pulse
|
||||
* Repeats : 20 frames per keypress
|
||||
* Inter-frame gap: ~30 440 us
|
||||
*
|
||||
* Symbol encoding - each bit = two (pulse, gap) pairs:
|
||||
* '+' ON : H H = [4045us HIGH, 607us LOW] x2
|
||||
* '-' OFF : L L = [530us HIGH, 4139us LOW] x2
|
||||
* '0' FLOAT : H L = [4045us HIGH, 607us LOW, 530us HIGH, 4139us LOW]
|
||||
*
|
||||
* Save file format:
|
||||
* Filetype: Flipper SubGhz Key File
|
||||
* Version: 1
|
||||
* Frequency: 318000000
|
||||
* Preset: FuriHalSubGhzPresetOok650Async
|
||||
* Protocol: Allstar Firefly
|
||||
* Key: +--000++-
|
||||
*
|
||||
* To register with the SubGHz app, in protocol_list.c add:
|
||||
* #include "allstar_firefly.h"
|
||||
* &subghz_protocol_allstar_firefly, (in the protocol array)
|
||||
*/
|
||||
|
||||
#include "base.h"
|
||||
|
||||
/* Protocol name (must match what is written to .sub files) */
|
||||
#define SUBGHZ_PROTOCOL_ALLSTAR_FIREFLY_NAME "Allstar Firefly"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderAllstarFirefly SubGhzProtocolDecoderAllstarFirefly;
|
||||
typedef struct SubGhzProtocolEncoderAllstarFirefly SubGhzProtocolEncoderAllstarFirefly;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_allstar_firefly_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_allstar_firefly_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_allstar_firefly;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderAllstarFirefly.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderAllstarFirefly* pointer to a SubGhzProtocolEncoderAllstarFirefly instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_allstar_firefly_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderAllstarFirefly.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderAllstarFirefly instance
|
||||
*/
|
||||
void subghz_protocol_encoder_allstar_firefly_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderAllstarFirefly instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_encoder_allstar_firefly_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderAllstarFirefly instance
|
||||
*/
|
||||
void subghz_protocol_encoder_allstar_firefly_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderAllstarFirefly instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_allstar_firefly_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderAllstarFirefly.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderAllstarFirefly* pointer to a SubGhzProtocolDecoderAllstarFirefly instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_allstar_firefly_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderAllstarFirefly.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
|
||||
*/
|
||||
void subghz_protocol_decoder_allstar_firefly_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderAllstarFirefly.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
|
||||
*/
|
||||
void subghz_protocol_decoder_allstar_firefly_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_allstar_firefly_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_allstar_firefly_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderAllstarFirefly.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderAllstarFirefly.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_allstar_firefly_deserialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderAllstarFirefly instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_allstar_firefly_get_string(void* context, FuriString* output);
|
||||
@@ -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);
|
||||
@@ -0,0 +1,742 @@
|
||||
#include "ditec_gol4.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 "SubGhzProtocolDitecGOL4"
|
||||
|
||||
#define GOL4_RAW_BYTES 7
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_ditec_gol4_const = {
|
||||
.te_short = 400,
|
||||
.te_long = 1100,
|
||||
.te_delta = 200,
|
||||
.min_count_bit_for_found = 54,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderDitecGOL4 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderDitecGOL4 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
DitecGOL4DecoderStepReset = 0,
|
||||
DitecGOL4DecoderStepStartBit,
|
||||
DitecGOL4DecoderStepSaveDuration,
|
||||
DitecGOL4DecoderStepCheckDuration,
|
||||
} DitecGOL4DecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_ditec_gol4_decoder = {
|
||||
.alloc = subghz_protocol_decoder_ditec_gol4_alloc,
|
||||
.free = subghz_protocol_decoder_ditec_gol4_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_ditec_gol4_feed,
|
||||
.reset = subghz_protocol_decoder_ditec_gol4_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_ditec_gol4_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_ditec_gol4_serialize,
|
||||
.deserialize = subghz_protocol_decoder_ditec_gol4_deserialize,
|
||||
.get_string = subghz_protocol_decoder_ditec_gol4_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_ditec_gol4_encoder = {
|
||||
.alloc = subghz_protocol_encoder_ditec_gol4_alloc,
|
||||
.free = subghz_protocol_encoder_ditec_gol4_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_ditec_gol4_deserialize,
|
||||
.stop = subghz_protocol_encoder_ditec_gol4_stop,
|
||||
.yield = subghz_protocol_encoder_ditec_gol4_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_ditec_gol4 = {
|
||||
.name = SUBGHZ_PROTOCOL_DITEC_GOL4_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_ditec_gol4_decoder,
|
||||
.encoder = &subghz_protocol_ditec_gol4_encoder,
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines the button value for the current btn_id
|
||||
* Basic set | 0x1 | 0x2 | 0x4 | 0x8 | 0x0 PROG
|
||||
* @return Button code
|
||||
*/
|
||||
static uint8_t subghz_protocol_ditec_gol4_get_btn_code(void);
|
||||
|
||||
static uint8_t gol4_bit_reverse(uint8_t b) {
|
||||
b &= 0xFF;
|
||||
uint8_t result = 0;
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
result = (uint8_t)((result << 1) | (b & 1));
|
||||
b >>= 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static uint8_t gol4_bit_parity(uint8_t b) {
|
||||
uint8_t p = 0;
|
||||
for(uint8_t i = 0; i < 8; i++) {
|
||||
if((b >> i) & 1u) p ^= 1u;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
static uint8_t gol4_lcg_step(uint8_t seed, uint8_t steps) {
|
||||
uint8_t x = seed & 0xFF;
|
||||
steps &= 0xFF;
|
||||
for(uint8_t i = 0; i < steps; i++) {
|
||||
x = (uint8_t)((21 * x + 1) & 0xFF);
|
||||
}
|
||||
return x;
|
||||
}
|
||||
|
||||
static uint8_t gol4_lcg_inverse(uint8_t target, uint8_t steps) {
|
||||
steps &= 0xFF;
|
||||
if(steps == 0) return target & 0xFF;
|
||||
return gol4_lcg_step(target, (uint8_t)(256 - steps));
|
||||
}
|
||||
|
||||
static void gol4_decode_rotate_and_bitrev(uint8_t* raw) {
|
||||
uint8_t carry = 0;
|
||||
for(uint8_t r = 0; r < 3; r++) {
|
||||
for(uint8_t i = 2; i < 7; i++) {
|
||||
uint8_t new_carry = raw[i] & 1;
|
||||
raw[i] = (uint8_t)(((raw[i] >> 1) | (carry << 7)) & 0xFF);
|
||||
carry = new_carry;
|
||||
}
|
||||
}
|
||||
|
||||
raw[0] = gol4_bit_reverse(raw[0]);
|
||||
raw[1] = gol4_bit_reverse(raw[1]);
|
||||
raw[3] = gol4_bit_reverse(raw[3]);
|
||||
raw[4] = gol4_bit_reverse(raw[4]);
|
||||
|
||||
uint8_t b2 = raw[2] & 0xDF;
|
||||
b2 = (uint8_t)(((b2 << 4) | (b2 >> 4)) & 0xFF);
|
||||
b2 = (uint8_t)((~b2) & 0xFF);
|
||||
raw[2] = gol4_bit_reverse(b2);
|
||||
|
||||
raw[5] = gol4_bit_reverse(raw[5]);
|
||||
raw[6] = gol4_bit_reverse(raw[6]);
|
||||
}
|
||||
|
||||
static bool gol4_decode_lcg_xor(uint8_t* raw) {
|
||||
if(raw[6] & 0x80) raw[5] ^= 1;
|
||||
|
||||
uint8_t out5 = gol4_lcg_inverse(raw[5], 0xFE);
|
||||
raw[5] = out5;
|
||||
|
||||
uint8_t out6 = gol4_lcg_inverse(raw[6], raw[5]);
|
||||
raw[6] = out6;
|
||||
|
||||
raw[5] ^= 0xA7;
|
||||
raw[6] ^= 0x69;
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool gol4_rolling_decode(uint8_t* raw) {
|
||||
gol4_decode_rotate_and_bitrev(raw);
|
||||
return gol4_decode_lcg_xor(raw);
|
||||
}
|
||||
|
||||
static bool gol4_encode_lcg_xor(uint8_t* raw) {
|
||||
uint8_t dec5 = (uint8_t)(raw[5] ^ 0xA7);
|
||||
uint8_t dec6 = (uint8_t)(raw[6] ^ 0x69);
|
||||
|
||||
uint8_t enc6 = gol4_lcg_step(dec6, dec5);
|
||||
uint8_t enc5 = gol4_lcg_step(dec5, 0xFE);
|
||||
|
||||
if(enc6 & 0x80) enc5 ^= 1;
|
||||
|
||||
raw[5] = enc5;
|
||||
raw[6] = enc6;
|
||||
return true;
|
||||
}
|
||||
|
||||
static void gol4_encode_bitrev_and_rotate(uint8_t* raw) {
|
||||
raw[0] = gol4_bit_reverse(raw[0]);
|
||||
raw[1] = gol4_bit_reverse(raw[1]);
|
||||
raw[3] = gol4_bit_reverse(raw[3]);
|
||||
raw[4] = gol4_bit_reverse(raw[4]);
|
||||
|
||||
if(raw[2] == 0x0) {
|
||||
raw[2] = 0xF0;
|
||||
}
|
||||
uint8_t b2 = gol4_bit_reverse(raw[2]);
|
||||
b2 = (uint8_t)(~b2);
|
||||
b2 = (uint8_t)(((b2 << 4) | (b2 >> 4)) & 0xFF);
|
||||
b2 &= 0xDF;
|
||||
raw[2] = b2;
|
||||
|
||||
raw[5] = gol4_bit_reverse(raw[5]);
|
||||
raw[6] = gol4_bit_reverse(raw[6]);
|
||||
|
||||
uint8_t p5 = gol4_bit_parity(raw[5]);
|
||||
uint8_t p6 = gol4_bit_parity(raw[6]);
|
||||
|
||||
uint8_t carry = 0;
|
||||
for(uint8_t r = 0; r < 3; r++) {
|
||||
for(int8_t i = 6; i >= 2; i--) {
|
||||
uint8_t new_carry = (uint8_t)((raw[i] >> 7) & 1);
|
||||
raw[i] = (uint8_t)(((raw[i] << 1) | carry) & 0xFF);
|
||||
carry = new_carry;
|
||||
}
|
||||
}
|
||||
|
||||
raw[6] = (p5 == p6) ? (uint8_t)(raw[6] & 0xFBu) : (uint8_t)(raw[6] | 0x04u);
|
||||
}
|
||||
|
||||
static bool gol4_rolling_encode(uint8_t* raw) {
|
||||
if(!raw) return false;
|
||||
if(!gol4_encode_lcg_xor(raw)) return false;
|
||||
gol4_encode_bitrev_and_rotate(raw);
|
||||
return true;
|
||||
}
|
||||
|
||||
static void bits_to_raw(const uint8_t* bits, uint8_t* raw) {
|
||||
memset(raw, 0, GOL4_RAW_BYTES);
|
||||
for(uint8_t i = 0; i < subghz_protocol_ditec_gol4_const.min_count_bit_for_found; i++) {
|
||||
uint8_t byte_idx = i / 8;
|
||||
uint8_t bit_idx = 7 - (i % 8);
|
||||
if(bits[i]) raw[byte_idx] |= (1 << bit_idx);
|
||||
}
|
||||
}
|
||||
|
||||
static void raw_to_bits(const uint8_t* raw, uint8_t* bits) {
|
||||
for(uint8_t i = 0; i < subghz_protocol_ditec_gol4_const.min_count_bit_for_found; i++) {
|
||||
uint8_t byte_idx = i / 8;
|
||||
uint8_t bit_idx = 7 - (i % 8);
|
||||
bits[i] = (uint8_t)((raw[byte_idx] >> bit_idx) & 1);
|
||||
}
|
||||
}
|
||||
|
||||
static uint64_t bits_to_data(const uint8_t* bits) {
|
||||
uint64_t data = 0;
|
||||
for(uint8_t i = 0; i < subghz_protocol_ditec_gol4_const.min_count_bit_for_found; i++) {
|
||||
data = (data << 1) | (uint64_t)(bits[i] & 1);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
static uint32_t serial_to_display(const uint8_t* s) {
|
||||
if(!s) return 0;
|
||||
return (uint32_t)((s[0] << 24) | (s[4] << 16) | (s[1] << 8) | s[3]);
|
||||
}
|
||||
|
||||
void* subghz_protocol_encoder_ditec_gol4_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderDitecGOL4* instance = malloc(sizeof(SubGhzProtocolEncoderDitecGOL4));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_ditec_gol4;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 4;
|
||||
instance->encoder.size_upload = 128; // 110 actual
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ditec_gol4_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderDitecGOL4* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderDitecGOL4 instance
|
||||
*/
|
||||
static void
|
||||
subghz_protocol_encoder_ditec_gol4_get_upload(SubGhzProtocolEncoderDitecGOL4* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
|
||||
// Send key and GAP between repeats
|
||||
//Send gap before data
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_ditec_gol4_const.te_long * 22);
|
||||
// Start bit
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_ditec_gol4_const.te_short * 2);
|
||||
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_ditec_gol4_const.te_short);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_ditec_gol4_const.te_long);
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_ditec_gol4_const.te_long);
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_ditec_gol4_const.te_short);
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_ditec_gol4_decode_key(SubGhzBlockGeneric* instance) {
|
||||
// Ditec GOL4 Decoder
|
||||
// 2025 - 2026.02 - @xMasterX (MMX) & @zero-mega
|
||||
//
|
||||
// RAW Samples
|
||||
// 0xCCB2F83208122 - btn 1 = 0011001100101100 101111 100000110010000 01000000100100010
|
||||
//
|
||||
// Programming mode:
|
||||
// 0xCCB1F832103B9 - btn 0 = 0011001100101100 011111 100000110010000 10000001110111001
|
||||
// Regular buttons:
|
||||
// 0xCCB2F8320ED66 - btn 1 = 0011001100101100 101111 100000110010000 01110110101100110
|
||||
// 0xCCB37832104A6 - btn 2 = 0011001100101100 110111 100000110010000 10000010010100110
|
||||
// 0xCCB3B8320DB4E - btn 4 = 0011001100101100 111011 100000110010000 01101101101001110
|
||||
// 0xCCB3D8320E855 - btn 8 = 0011001100101100 111101 100000110010000 01110100001010101
|
||||
//
|
||||
// Regular buttons:
|
||||
// Decoded array: CC 34 71 83 09 F8 C1
|
||||
// Decoded array: CC 34 71 83 09 F9 C1
|
||||
// Decoded array: CC 34 72 83 09 FA C1
|
||||
// Decoded array: CC 34 74 83 09 FB C1
|
||||
// Decoded array: CC 34 78 83 09 FC C1
|
||||
// Programming mode
|
||||
// Decoded array: CC 34 F0 83 09 FD C1
|
||||
// Decoded array: CC 34 F0 83 09 FE C1
|
||||
//
|
||||
uint8_t bits[subghz_protocol_ditec_gol4_const.min_count_bit_for_found];
|
||||
uint64_t data = instance->data;
|
||||
for(int i = subghz_protocol_ditec_gol4_const.min_count_bit_for_found - 1; i >= 0; i--) {
|
||||
bits[i] = (uint8_t)(data & 1);
|
||||
data >>= 1;
|
||||
}
|
||||
uint8_t decrypted[GOL4_RAW_BYTES];
|
||||
bits_to_raw(bits, decrypted);
|
||||
|
||||
if(gol4_rolling_decode(decrypted)) {
|
||||
uint8_t temp_serial[5];
|
||||
memcpy(temp_serial, decrypted, 5);
|
||||
instance->serial = serial_to_display(temp_serial);
|
||||
instance->btn = decrypted[2] & 0x0F;
|
||||
instance->cnt = (uint16_t)((decrypted[5] | (decrypted[6] << 8)) & 0xFFFF);
|
||||
// Save original button for later use
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->btn);
|
||||
}
|
||||
subghz_custom_btn_set_max(4);
|
||||
}
|
||||
}
|
||||
|
||||
static void subghz_protocol_ditec_gol4_encode_key(SubGhzBlockGeneric* instance) {
|
||||
// Encoder crypto part:
|
||||
//
|
||||
// TODO: Current issue - last bit at original remote sometimes 0 but we encode as 1, or vice versa.
|
||||
// This does not affect decoding but may have issue on real receiver
|
||||
//
|
||||
uint8_t decrypted[GOL4_RAW_BYTES];
|
||||
|
||||
// Save original button for later use
|
||||
if(subghz_custom_btn_get_original() == 0) {
|
||||
subghz_custom_btn_set_original(instance->btn);
|
||||
}
|
||||
|
||||
instance->btn = subghz_protocol_ditec_gol4_get_btn_code();
|
||||
|
||||
// override button if we change it with signal settings button editor
|
||||
if(subghz_block_generic_global_button_override_get(&instance->btn))
|
||||
FURI_LOG_D(TAG, "Button sucessfully changed to 0x%X", instance->btn);
|
||||
|
||||
// Check for OFEX (overflow experimental) mode
|
||||
if(furi_hal_subghz_get_rolling_counter_mult() != -0x7FFFFFFF) {
|
||||
// standart counter mode. PULL data from subghz_block_generic_global variables
|
||||
if(!subghz_block_generic_global_counter_override_get(&instance->cnt)) {
|
||||
// if counter_override_get return FALSE then counter was not changed and we increase counter by standart mult value
|
||||
if((instance->cnt + furi_hal_subghz_get_rolling_counter_mult()) > 0xFFFF) {
|
||||
instance->cnt = 0;
|
||||
} else {
|
||||
instance->cnt += furi_hal_subghz_get_rolling_counter_mult();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if((instance->cnt + 0x1) > 0xFFFF) {
|
||||
instance->cnt = 0;
|
||||
} else if(instance->cnt >= 0x1 && instance->cnt != 0xFFFE) {
|
||||
instance->cnt = 0xFFFE;
|
||||
} else {
|
||||
instance->cnt++;
|
||||
}
|
||||
}
|
||||
|
||||
decrypted[0] = (uint8_t)((instance->serial >> 24) & 0xFF);
|
||||
decrypted[4] = (uint8_t)((instance->serial >> 16) & 0xFF);
|
||||
decrypted[1] = (uint8_t)((instance->serial >> 8) & 0xFF);
|
||||
decrypted[3] = (uint8_t)(instance->serial & 0xFF);
|
||||
decrypted[2] = (uint8_t)(instance->btn & 0x0F);
|
||||
|
||||
uint16_t counter = (uint16_t)(instance->cnt & 0xFFFF);
|
||||
decrypted[5] = (uint8_t)(counter & 0xFF);
|
||||
decrypted[6] = (uint8_t)((counter >> 8) & 0xFF);
|
||||
|
||||
gol4_rolling_encode(decrypted);
|
||||
|
||||
uint8_t bits[subghz_protocol_ditec_gol4_const.min_count_bit_for_found];
|
||||
raw_to_bits(decrypted, bits);
|
||||
instance->data = bits_to_data(bits);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_ditec_gol4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderDitecGOL4* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_ditec_gol4_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
// Optional parameter
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_ditec_gol4_decode_key(&instance->generic);
|
||||
subghz_protocol_ditec_gol4_encode_key(&instance->generic);
|
||||
subghz_protocol_encoder_ditec_gol4_get_upload(instance);
|
||||
|
||||
if(!flipper_format_rewind(flipper_format)) {
|
||||
FURI_LOG_E(TAG, "Rewind error");
|
||||
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");
|
||||
break;
|
||||
}
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_ditec_gol4_stop(void* context) {
|
||||
SubGhzProtocolEncoderDitecGOL4* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_ditec_gol4_yield(void* context) {
|
||||
SubGhzProtocolEncoderDitecGOL4* 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;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_ditec_gol4_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderDitecGOL4* instance = malloc(sizeof(SubGhzProtocolDecoderDitecGOL4));
|
||||
instance->base.protocol = &subghz_protocol_ditec_gol4;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ditec_gol4_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderDitecGOL4* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ditec_gol4_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderDitecGOL4* instance = context;
|
||||
instance->decoder.parser_step = DitecGOL4DecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ditec_gol4_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderDitecGOL4* instance = context;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case DitecGOL4DecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_ditec_gol4_const.te_long * 22) <
|
||||
(subghz_protocol_ditec_gol4_const.te_long * 4))) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = DitecGOL4DecoderStepStartBit;
|
||||
}
|
||||
break;
|
||||
|
||||
case DitecGOL4DecoderStepStartBit:
|
||||
if((level) && (DURATION_DIFF(duration, subghz_protocol_ditec_gol4_const.te_short * 2) <
|
||||
subghz_protocol_ditec_gol4_const.te_delta)) {
|
||||
instance->decoder.parser_step = DitecGOL4DecoderStepSaveDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = DitecGOL4DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case DitecGOL4DecoderStepSaveDuration:
|
||||
if(!level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = DitecGOL4DecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = DitecGOL4DecoderStepReset;
|
||||
}
|
||||
break;
|
||||
|
||||
case DitecGOL4DecoderStepCheckDuration:
|
||||
if(level) {
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_ditec_gol4_const.te_short) <
|
||||
subghz_protocol_ditec_gol4_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_ditec_gol4_const.te_long) <
|
||||
subghz_protocol_ditec_gol4_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = DitecGOL4DecoderStepSaveDuration;
|
||||
} else if(
|
||||
(DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_ditec_gol4_const.te_long) <
|
||||
subghz_protocol_ditec_gol4_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_ditec_gol4_const.te_short) <
|
||||
subghz_protocol_ditec_gol4_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = DitecGOL4DecoderStepSaveDuration;
|
||||
}
|
||||
} else {
|
||||
if(DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_ditec_gol4_const.te_long * 20) <
|
||||
(subghz_protocol_ditec_gol4_const.te_long * 3)) {
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_ditec_gol4_const.min_count_bit_for_found) {
|
||||
// 54 bits received, save and continue
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit =
|
||||
subghz_protocol_ditec_gol4_const.min_count_bit_for_found;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = DitecGOL4DecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = DitecGOL4DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_ditec_gol4_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderDitecGOL4* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ditec_gol4_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderDitecGOL4* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_ditec_gol4_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderDitecGOL4* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_ditec_gol4_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
bool subghz_protocol_ditec_gol4_create_data(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
uint32_t serial,
|
||||
uint8_t btn,
|
||||
uint16_t cnt,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderDitecGOL4* instance = context;
|
||||
instance->generic.btn = btn;
|
||||
instance->generic.serial = serial;
|
||||
instance->generic.cnt = cnt;
|
||||
instance->generic.data_count_bit = subghz_protocol_ditec_gol4_const.min_count_bit_for_found;
|
||||
|
||||
subghz_protocol_ditec_gol4_encode_key(&instance->generic);
|
||||
|
||||
return SubGhzProtocolStatusOk ==
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
static uint8_t subghz_protocol_ditec_gol4_get_btn_code(void) {
|
||||
uint8_t custom_btn_id = subghz_custom_btn_get();
|
||||
uint8_t original_btn_code = subghz_custom_btn_get_original();
|
||||
uint8_t btn = original_btn_code;
|
||||
|
||||
// Set custom button
|
||||
if((custom_btn_id == SUBGHZ_CUSTOM_BTN_OK) && (original_btn_code != 0)) {
|
||||
// Restore original button code
|
||||
btn = original_btn_code;
|
||||
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_UP) {
|
||||
switch(original_btn_code) {
|
||||
case 0x1:
|
||||
btn = 0x2;
|
||||
break;
|
||||
case 0x2:
|
||||
btn = 0x1;
|
||||
break;
|
||||
case 0x4:
|
||||
btn = 0x1;
|
||||
break;
|
||||
case 0x8:
|
||||
btn = 0x1;
|
||||
break;
|
||||
case 0x0:
|
||||
btn = 0x1;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_DOWN) {
|
||||
switch(original_btn_code) {
|
||||
case 0x1:
|
||||
btn = 0x4;
|
||||
break;
|
||||
case 0x2:
|
||||
btn = 0x4;
|
||||
break;
|
||||
case 0x4:
|
||||
btn = 0x2;
|
||||
break;
|
||||
case 0x8:
|
||||
btn = 0x4;
|
||||
break;
|
||||
case 0x0:
|
||||
btn = 0x4;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_LEFT) {
|
||||
switch(original_btn_code) {
|
||||
case 0x1:
|
||||
btn = 0x8;
|
||||
break;
|
||||
case 0x2:
|
||||
btn = 0x8;
|
||||
break;
|
||||
case 0x4:
|
||||
btn = 0x8;
|
||||
break;
|
||||
case 0x8:
|
||||
btn = 0x2;
|
||||
break;
|
||||
case 0x0:
|
||||
btn = 0x2;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
} else if(custom_btn_id == SUBGHZ_CUSTOM_BTN_RIGHT) {
|
||||
switch(original_btn_code) {
|
||||
case 0x1:
|
||||
btn = 0x0;
|
||||
break;
|
||||
case 0x2:
|
||||
btn = 0x0;
|
||||
break;
|
||||
case 0x4:
|
||||
btn = 0x0;
|
||||
break;
|
||||
case 0x8:
|
||||
btn = 0x0;
|
||||
break;
|
||||
case 0x0:
|
||||
btn = 0x8;
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return btn;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_ditec_gol4_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderDitecGOL4* instance = context;
|
||||
|
||||
subghz_protocol_ditec_gol4_decode_key(&instance->generic);
|
||||
|
||||
// push protocol data to global variable
|
||||
subghz_block_generic_global.cnt_is_available = true;
|
||||
subghz_block_generic_global.cnt_length_bit = 16;
|
||||
subghz_block_generic_global.current_cnt = instance->generic.cnt;
|
||||
|
||||
subghz_block_generic_global.btn_is_available = true;
|
||||
subghz_block_generic_global.current_btn = instance->generic.btn;
|
||||
subghz_block_generic_global.btn_length_bit = 4;
|
||||
//
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key:0x%0lX%08lX\r\n"
|
||||
"Serial:0x%08lX\r\n"
|
||||
"Btn:%01X %s\r\n"
|
||||
"Cnt:%04lX",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint32_t)(instance->generic.data >> 32),
|
||||
(uint32_t)(instance->generic.data & 0xFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
(instance->generic.btn == 0x0) ? "- Prog" : "",
|
||||
instance->generic.cnt);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_DITEC_GOL4_NAME "Ditec GOL4"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderDitecGOL4 SubGhzProtocolDecoderDitecGOL4;
|
||||
typedef struct SubGhzProtocolEncoderDitecGOL4 SubGhzProtocolEncoderDitecGOL4;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_ditec_gol4_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_ditec_gol4_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_ditec_gol4;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderDitecGOL4.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderDitecGOL4* pointer to a SubGhzProtocolEncoderDitecGOL4 instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_ditec_gol4_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderDitecGOL4.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance
|
||||
*/
|
||||
void subghz_protocol_encoder_ditec_gol4_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_ditec_gol4_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance
|
||||
*/
|
||||
void subghz_protocol_encoder_ditec_gol4_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderDitecGOL4 instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_ditec_gol4_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderDitecGOL4.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderDitecGOL4* pointer to a SubGhzProtocolDecoderDitecGOL4 instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_ditec_gol4_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderDitecGOL4.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_ditec_gol4_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderDitecGOL4.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
|
||||
*/
|
||||
void subghz_protocol_decoder_ditec_gol4_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_ditec_gol4_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_ditec_gol4_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderDitecGOL4.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ditec_gol4_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderDitecGOL4.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_ditec_gol4_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderDitecGOL4 instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_ditec_gol4_get_string(void* context, FuriString* output);
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
#include <furi.h>
|
||||
#include <lib/subghz/protocols/base.h>
|
||||
#include <lib/subghz/types.h>
|
||||
#include <lib/subghz/blocks/const.h>
|
||||
#include <lib/subghz/blocks/decoder.h>
|
||||
#include <lib/subghz/blocks/encoder.h>
|
||||
#include <lib/subghz/blocks/generic.h>
|
||||
#include <lib/subghz/blocks/math.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
#include <flipper_format/flipper_format.h>
|
||||
|
||||
#define FORD_PROTOCOL_V1_NAME "Ford V1"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderFordV1 SubGhzProtocolDecoderFordV1;
|
||||
|
||||
extern const SubGhzProtocol ford_protocol_v1;
|
||||
|
||||
void* subghz_protocol_decoder_ford_v1_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_ford_v1_free(void* context);
|
||||
void subghz_protocol_decoder_ford_v1_reset(void* context);
|
||||
void subghz_protocol_decoder_ford_v1_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_ford_v1_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_ford_v1_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_ford_v1_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_ford_v1_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_ford_v1_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_ford_v1_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_ford_v1_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_ford_v1_yield(void* context);
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_ford_v1_encoder;
|
||||
@@ -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,811 @@
|
||||
#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 \
|
||||
(1U + HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT + (2U * HONDA_STATIC_BIT_COUNT) + 1U)
|
||||
#define HONDA_STATIC_SYMBOL_CAPACITY 512
|
||||
#define HONDA_STATIC_PREAMBLE_ALTERNATING_COUNT 160
|
||||
#define HONDA_STATIC_PREAMBLE_MAX_TRANSITIONS 19
|
||||
#define HONDA_STATIC_SYMBOL_BYTE_COUNT ((HONDA_STATIC_SYMBOL_CAPACITY + 7U) / 8U)
|
||||
|
||||
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;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint8_t symbols[HONDA_STATIC_SYMBOL_BYTE_COUNT];
|
||||
uint16_t symbols_count;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderHondaStatic {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
HondaStaticFields decoded;
|
||||
uint8_t tx_button;
|
||||
uint8_t _reserved_69[3];
|
||||
};
|
||||
|
||||
static void honda_static_decoder_commit(
|
||||
SubGhzProtocolDecoderHondaStatic* instance,
|
||||
const HondaStaticFields* decoded);
|
||||
|
||||
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 void honda_static_symbol_set(uint8_t* buf, uint16_t index, uint8_t v) {
|
||||
const uint8_t byte_index = (uint8_t)(index >> 3U);
|
||||
const uint8_t shift = (uint8_t)(~index) & 0x07U;
|
||||
const uint8_t mask = (uint8_t)(1U << shift);
|
||||
if(v) {
|
||||
buf[byte_index] |= mask;
|
||||
} else {
|
||||
buf[byte_index] &= (uint8_t)~mask;
|
||||
}
|
||||
}
|
||||
|
||||
static uint8_t honda_static_symbol_get(const uint8_t* buf, uint16_t index) {
|
||||
const uint8_t byte_index = (uint8_t)(index >> 3U);
|
||||
const uint8_t shift = (uint8_t)(~index) & 0x07U;
|
||||
return (uint8_t)((buf[byte_index] >> shift) & 1U);
|
||||
}
|
||||
|
||||
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* symbol_bits,
|
||||
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_symbol_get(symbol_bits, pos);
|
||||
const uint8_t b = honda_static_symbol_get(symbol_bits, 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* symbol_bits = instance->symbols;
|
||||
HondaStaticFields decoded;
|
||||
|
||||
uint16_t index = 1U;
|
||||
uint16_t transitions = 0U;
|
||||
|
||||
while(index < count) {
|
||||
if(honda_static_symbol_get(symbol_bits, index) !=
|
||||
honda_static_symbol_get(symbol_bits, 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_symbol_get(symbol_bits, index) ==
|
||||
honda_static_symbol_get(symbol_bits, 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(
|
||||
symbol_bits, count, data_start, inverted, packet, &bit_count)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(honda_static_validate_forward_packet(packet, &decoded)) {
|
||||
honda_static_decoder_commit(instance, &decoded);
|
||||
return true;
|
||||
}
|
||||
|
||||
if(inverted) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(honda_static_validate_reverse_packet(packet, &decoded)) {
|
||||
honda_static_decoder_commit(instance, &decoded);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void honda_static_decoder_commit(
|
||||
SubGhzProtocolDecoderHondaStatic* instance,
|
||||
const HondaStaticFields* decoded) {
|
||||
instance->generic.data_count_bit = HONDA_STATIC_BIT_COUNT;
|
||||
instance->generic.data = honda_static_pack_compact(decoded);
|
||||
instance->generic.serial = decoded->serial;
|
||||
instance->generic.cnt = decoded->counter;
|
||||
instance->generic.btn = 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_FM | 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
|
||||
instance->symbols_count++;
|
||||
}
|
||||
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) {
|
||||
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
|
||||
instance->symbols_count++;
|
||||
honda_static_symbol_set(instance->symbols, instance->symbols_count, sym);
|
||||
instance->symbols_count++;
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
HondaStaticFields decoded;
|
||||
honda_static_unpack_compact(instance->generic.data, &decoded);
|
||||
|
||||
furi_string_printf(
|
||||
output,
|
||||
"%s\r\n"
|
||||
"Key:%016llX\r\n"
|
||||
"Btn:%s\r\n"
|
||||
"Ser:%07lX Cnt:%06lX",
|
||||
instance->generic.protocol_name,
|
||||
(unsigned long long)instance->generic.data,
|
||||
honda_static_button_name(decoded.button),
|
||||
(unsigned long)decoded.serial,
|
||||
(unsigned long)decoded.counter);
|
||||
}
|
||||
|
||||
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;
|
||||
HondaStaticFields decoded;
|
||||
honda_static_unpack_compact(instance->generic.data, &decoded);
|
||||
|
||||
SubGhzProtocolStatus status =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
if(status != SubGhzProtocolStatusOk) {
|
||||
return status;
|
||||
}
|
||||
|
||||
uint32_t temp = decoded.serial;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Serial", &temp, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
temp = decoded.button;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Btn", &temp, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
temp = decoded.counter;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Cnt", &temp, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
temp = decoded.checksum;
|
||||
if(!flipper_format_write_uint32(flipper_format, "Checksum", &temp, 1)) {
|
||||
return SubGhzProtocolStatusErrorParserOthers;
|
||||
}
|
||||
|
||||
return status;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
HondaStaticFields decoded;
|
||||
honda_static_unpack_compact(instance->generic.data, &decoded);
|
||||
uint32_t s = 0;
|
||||
uint32_t b = 0;
|
||||
uint32_t c = 0;
|
||||
if(flipper_format_read_uint32(flipper_format, "Serial", &s, 1)) {
|
||||
decoded.serial = s;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Btn", &b, 1)) {
|
||||
decoded.button = (uint8_t)b;
|
||||
}
|
||||
if(flipper_format_read_uint32(flipper_format, "Cnt", &c, 1)) {
|
||||
decoded.counter = c & 0x00FFFFFFU;
|
||||
}
|
||||
|
||||
instance->generic.data = honda_static_pack_compact(&decoded);
|
||||
instance->generic.serial = decoded.serial;
|
||||
instance->generic.cnt = decoded.counter;
|
||||
instance->generic.btn = decoded.button;
|
||||
|
||||
return status;
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
#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);
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,711 @@
|
||||
#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;
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
#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>
|
||||
#include <lib/toolbox/level_duration.h>
|
||||
#include <lib/toolbox/manchester_decoder.h>
|
||||
|
||||
#define KIA_PROTOCOL_V7_NAME "Kia V7"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderKiaV7 SubGhzProtocolDecoderKiaV7;
|
||||
typedef struct SubGhzProtocolEncoderKiaV7 SubGhzProtocolEncoderKiaV7;
|
||||
|
||||
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);
|
||||
void kia_protocol_decoder_v7_reset(void* context);
|
||||
void kia_protocol_decoder_v7_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t kia_protocol_decoder_v7_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus kia_protocol_decoder_v7_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_decoder_v7_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void kia_protocol_decoder_v7_get_string(void* context, FuriString* output);
|
||||
|
||||
void* kia_protocol_encoder_v7_alloc(SubGhzEnvironment* environment);
|
||||
void kia_protocol_encoder_v7_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
kia_protocol_encoder_v7_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void kia_protocol_encoder_v7_stop(void* context);
|
||||
LevelDuration kia_protocol_encoder_v7_yield(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
|
||||
@@ -0,0 +1,726 @@
|
||||
#include "mazda_v0.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
// =============================================================================
|
||||
// PROTOCOL CONSTANTS
|
||||
// =============================================================================
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_mazda_v0_const = {
|
||||
.te_short = 250,
|
||||
.te_long = 500,
|
||||
.te_delta = 100,
|
||||
.min_count_bit_for_found = 64,
|
||||
};
|
||||
|
||||
#define MAZDA_V0_UPLOAD_CAPACITY 0x184
|
||||
#define MAZDA_V0_GAP_US 0xCB20
|
||||
#define MAZDA_V0_SYNC_BYTE 0xD7
|
||||
#define MAZDA_V0_TAIL_BYTE 0x5A
|
||||
#define MAZDA_V0_PREAMBLE_ONES 16
|
||||
|
||||
// =============================================================================
|
||||
// STRUCT DEFINITIONS
|
||||
// =============================================================================
|
||||
|
||||
typedef struct SubGhzProtocolDecoderMazdaV0 {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
ManchesterState manchester_state;
|
||||
uint16_t preamble_count;
|
||||
uint8_t preamble_pattern;
|
||||
|
||||
uint32_t serial;
|
||||
uint8_t button;
|
||||
uint32_t count;
|
||||
} SubGhzProtocolDecoderMazdaV0;
|
||||
|
||||
//#ifdef ENABLE_EMULATE_FEATURE
|
||||
typedef struct SubGhzProtocolEncoderMazdaV0 {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
|
||||
uint32_t serial;
|
||||
uint8_t button;
|
||||
uint32_t count;
|
||||
} SubGhzProtocolEncoderMazdaV0;
|
||||
//#endif
|
||||
|
||||
typedef enum {
|
||||
MazdaV0DecoderStepReset = 0,
|
||||
MazdaV0DecoderStepPreamble = 5,
|
||||
MazdaV0DecoderStepData = 6,
|
||||
} MazdaV0DecoderStep;
|
||||
|
||||
// =============================================================================
|
||||
// FUNCTION PROTOTYPES
|
||||
// =============================================================================
|
||||
|
||||
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,
|
||||
size_t* index,
|
||||
bool level,
|
||||
uint32_t duration);
|
||||
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,
|
||||
uint8_t button);
|
||||
|
||||
// =============================================================================
|
||||
// PROTOCOL INTERFACE DEFINITIONS
|
||||
// =============================================================================
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_mazda_v0_decoder = {
|
||||
.alloc = subghz_protocol_decoder_mazda_v0_alloc,
|
||||
.free = subghz_protocol_decoder_mazda_v0_free,
|
||||
.feed = subghz_protocol_decoder_mazda_v0_feed,
|
||||
.reset = subghz_protocol_decoder_mazda_v0_reset,
|
||||
.get_hash_data = subghz_protocol_decoder_mazda_v0_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_mazda_v0_serialize,
|
||||
.deserialize = subghz_protocol_decoder_mazda_v0_deserialize,
|
||||
.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,
|
||||
.deserialize = subghz_protocol_encoder_mazda_v0_deserialize,
|
||||
.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 subghz_protocol_mazda_v0 = {
|
||||
.name = MAZDA_PROTOCOL_V0_NAME,
|
||||
.type = SubGhzProtocolTypeDynamic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_FM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
.decoder = &subghz_protocol_mazda_v0_decoder,
|
||||
.encoder = &subghz_protocol_mazda_v0_encoder,
|
||||
};
|
||||
|
||||
// =============================================================================
|
||||
// HELPERS
|
||||
// =============================================================================
|
||||
|
||||
static uint8_t mazda_v0_popcount8(uint8_t x) {
|
||||
uint8_t count = 0;
|
||||
while(x) {
|
||||
count += x & 1;
|
||||
x >>= 1;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
static void mazda_v0_u64_to_bytes_be(uint64_t data, uint8_t bytes[8]) {
|
||||
for(size_t i = 0; i < 8; i++) {
|
||||
bytes[i] = (uint8_t)((data >> ((7 - i) * 8)) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
//#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++) {
|
||||
data = (data << 8) | bytes[i];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
//#endif
|
||||
|
||||
static uint8_t mazda_v0_calculate_checksum(uint32_t serial, uint8_t button, uint32_t counter) {
|
||||
counter &= 0xFFFFFU;
|
||||
return (uint8_t)(((serial >> 24) & 0xFF) + ((serial >> 16) & 0xFF) + ((serial >> 8) & 0xFF) +
|
||||
(serial & 0xFF) + ((counter >> 8) & 0xFF) + (counter & 0xFF) +
|
||||
((((counter >> 16) & 0x0F) | ((button & 0x0F) << 4)) & 0xFF));
|
||||
}
|
||||
|
||||
static const char* mazda_v0_get_button_name(uint8_t button) {
|
||||
switch(button) {
|
||||
case 0x01:
|
||||
return "LOCK";
|
||||
case 0x02:
|
||||
return "UNLOCK";
|
||||
case 0x04:
|
||||
return "BOOT";
|
||||
case 0x08:
|
||||
return "REMOTE";
|
||||
default:
|
||||
return "??";
|
||||
}
|
||||
}
|
||||
|
||||
static bool mazda_v0_get_event(uint32_t duration, bool level, ManchesterEvent* event) {
|
||||
const uint32_t tol = (uint32_t)subghz_protocol_mazda_v0_const.te_delta + 20U;
|
||||
|
||||
if((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_short) < tol) {
|
||||
*event = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
return true;
|
||||
}
|
||||
|
||||
if((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_long) < tol) {
|
||||
*event = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static void mazda_v0_decode_key(SubGhzBlockGeneric* generic) {
|
||||
uint8_t data[8];
|
||||
mazda_v0_u64_to_bytes_be(generic->data, data);
|
||||
|
||||
const bool parity = (mazda_v0_popcount8(data[7]) & 1) != 0;
|
||||
const uint8_t limit = parity ? 6 : 5;
|
||||
const uint8_t mask = data[limit];
|
||||
|
||||
for(uint8_t i = 0; i < limit; i++) {
|
||||
data[i] ^= mask;
|
||||
}
|
||||
|
||||
if(!parity) {
|
||||
data[6] ^= mask;
|
||||
}
|
||||
|
||||
const uint8_t counter_lo = (data[5] & 0x55) | (data[6] & 0xAA);
|
||||
const uint8_t counter_mid = (data[6] & 0x55) | (data[5] & 0xAA);
|
||||
|
||||
generic->serial = ((uint32_t)data[0] << 24) | ((uint32_t)data[1] << 16) |
|
||||
((uint32_t)data[2] << 8) | (uint32_t)data[3];
|
||||
generic->btn = (data[4] >> 4) & 0x0F;
|
||||
generic->cnt = (((uint32_t)data[4] & 0x0F) << 16) | ((uint32_t)counter_mid << 8) |
|
||||
(uint32_t)counter_lo;
|
||||
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];
|
||||
|
||||
counter &= 0xFFFFFU;
|
||||
button &= 0x0F;
|
||||
|
||||
data[0] = (serial >> 24) & 0xFF;
|
||||
data[1] = (serial >> 16) & 0xFF;
|
||||
data[2] = (serial >> 8) & 0xFF;
|
||||
data[3] = serial & 0xFF;
|
||||
data[4] = (button << 4) | ((counter >> 16) & 0x0F);
|
||||
data[5] = (counter >> 8) & 0xFF;
|
||||
data[6] = counter & 0xFF;
|
||||
data[7] = mazda_v0_calculate_checksum(serial, button, counter);
|
||||
|
||||
const uint8_t stored_5 = (data[6] & 0x55) | (data[5] & 0xAA);
|
||||
const uint8_t stored_6 = (data[6] & 0xAA) | (data[5] & 0x55);
|
||||
const uint8_t xor_mask = stored_5 ^ stored_6;
|
||||
const bool replace_second = ((~mazda_v0_popcount8(data[7])) & 1) != 0;
|
||||
const uint8_t forward_mask = replace_second ? stored_5 : stored_6;
|
||||
|
||||
data[5] = replace_second ? stored_5 : xor_mask;
|
||||
data[6] = replace_second ? xor_mask : stored_6;
|
||||
|
||||
for(size_t i = 0; i < 5; i++) {
|
||||
data[i] ^= forward_mask;
|
||||
}
|
||||
|
||||
return mazda_v0_bytes_to_u64_be(data);
|
||||
}
|
||||
|
||||
static bool mazda_v0_encoder_add_level(
|
||||
SubGhzProtocolEncoderMazdaV0* instance,
|
||||
size_t* index,
|
||||
bool level,
|
||||
uint32_t duration) {
|
||||
if(*index >= MAZDA_V0_UPLOAD_CAPACITY) {
|
||||
return false;
|
||||
}
|
||||
instance->encoder.upload[(*index)++] = level_duration_make(level, duration);
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
mazda_v0_append_byte(SubGhzProtocolEncoderMazdaV0* instance, size_t* index, uint8_t value) {
|
||||
if(*index + 16 > MAZDA_V0_UPLOAD_CAPACITY) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t te = subghz_protocol_mazda_v0_const.te_short;
|
||||
|
||||
for(int8_t bit = 7; bit >= 0; bit--) {
|
||||
const bool bit_value = ((value >> bit) & 1) != 0;
|
||||
if(!bit_value) {
|
||||
if(!mazda_v0_encoder_add_level(instance, index, false, te)) {
|
||||
return false;
|
||||
}
|
||||
if(!mazda_v0_encoder_add_level(instance, index, true, te)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if(!mazda_v0_encoder_add_level(instance, index, true, te)) {
|
||||
return false;
|
||||
}
|
||||
if(!mazda_v0_encoder_add_level(instance, index, false, te)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool mazda_v0_build_upload(SubGhzProtocolEncoderMazdaV0* instance) {
|
||||
furi_check(instance);
|
||||
|
||||
size_t index = 0;
|
||||
const uint64_t key64 = instance->generic.data;
|
||||
|
||||
for(size_t r = 0; r < 12; r++) {
|
||||
if(!mazda_v0_append_byte(instance, &index, 0xFF)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mazda_v0_encoder_add_level(instance, &index, false, MAZDA_V0_GAP_US)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!mazda_v0_append_byte(instance, &index, 0xFF) ||
|
||||
!mazda_v0_append_byte(instance, &index, 0xFF) ||
|
||||
!mazda_v0_append_byte(instance, &index, MAZDA_V0_SYNC_BYTE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for(int bi = 0; bi < 8; bi++) {
|
||||
const uint8_t raw = (uint8_t)((key64 >> (56 - bi * 8)) & 0xFF);
|
||||
const uint8_t air = (uint8_t)~raw;
|
||||
if(!mazda_v0_append_byte(instance, &index, air)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if(!mazda_v0_append_byte(instance, &index, MAZDA_V0_TAIL_BYTE)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if(!mazda_v0_encoder_add_level(instance, &index, false, MAZDA_V0_GAP_US)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.size_upload = index;
|
||||
|
||||
return true;
|
||||
}
|
||||
//#endif
|
||||
|
||||
static SubGhzProtocolStatus mazda_v0_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, mazda_v0_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;
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// 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 = &subghz_protocol_mazda_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
instance->encoder.repeat = 10;
|
||||
instance->encoder.size_upload = 0;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.upload = malloc(MAZDA_V0_UPLOAD_CAPACITY * sizeof(LevelDuration));
|
||||
furi_check(instance->encoder.upload);
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_mazda_v0_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderMazdaV0* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderMazdaV0* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
|
||||
instance->encoder.is_running = false;
|
||||
instance->encoder.front = 0;
|
||||
instance->encoder.repeat = 10;
|
||||
|
||||
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,
|
||||
subghz_protocol_mazda_v0_const.min_count_bit_for_found);
|
||||
if(load_st != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
mazda_v0_decode_key(&instance->generic);
|
||||
|
||||
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 = u32;
|
||||
}
|
||||
|
||||
instance->serial = instance->generic.serial;
|
||||
instance->button = instance->generic.btn;
|
||||
instance->count = instance->generic.cnt;
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
if(!flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1)) {
|
||||
instance->encoder.repeat = 10;
|
||||
}
|
||||
instance->generic.btn &= 0x0FU;
|
||||
instance->generic.cnt &= 0xFFFFFU;
|
||||
|
||||
instance->generic.data = mazda_v0_encode_key(
|
||||
instance->generic.serial, instance->generic.btn, instance->generic.cnt);
|
||||
instance->generic.data_count_bit = subghz_protocol_mazda_v0_const.min_count_bit_for_found;
|
||||
|
||||
instance->serial = instance->generic.serial;
|
||||
instance->button = instance->generic.btn;
|
||||
instance->count = instance->generic.cnt;
|
||||
|
||||
if(!mazda_v0_build_upload(instance)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if(instance->encoder.size_upload == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
flipper_format_rewind(flipper_format);
|
||||
uint8_t key_data[sizeof(uint64_t)];
|
||||
mazda_v0_u64_to_bytes_be(instance->generic.data, key_data);
|
||||
if(!flipper_format_update_hex(flipper_format, "Key", key_data, sizeof(key_data))) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint32_t chk =
|
||||
mazda_v0_calculate_checksum(instance->serial, instance->button, instance->count);
|
||||
flipper_format_rewind(flipper_format);
|
||||
flipper_format_insert_or_update_uint32(flipper_format, "Checksum", &chk, 1);
|
||||
|
||||
instance->encoder.is_running = true;
|
||||
|
||||
ret = SubGhzProtocolStatusOk;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_mazda_v0_stop(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderMazdaV0* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_mazda_v0_yield(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolEncoderMazdaV0* instance = context;
|
||||
|
||||
if(!instance->encoder.is_running || instance->encoder.repeat == 0) {
|
||||
instance->encoder.is_running = false;
|
||||
return level_duration_reset();
|
||||
}
|
||||
|
||||
LevelDuration out = instance->encoder.upload[instance->encoder.front];
|
||||
|
||||
if(++instance->encoder.front == instance->encoder.size_upload) {
|
||||
instance->encoder.repeat--;
|
||||
instance->encoder.front = 0;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
//#endif
|
||||
|
||||
// =============================================================================
|
||||
// DECODER
|
||||
// =============================================================================
|
||||
|
||||
void* subghz_protocol_decoder_mazda_v0_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
|
||||
SubGhzProtocolDecoderMazdaV0* instance = calloc(1, sizeof(SubGhzProtocolDecoderMazdaV0));
|
||||
furi_check(instance);
|
||||
|
||||
instance->base.protocol = &subghz_protocol_mazda_v0;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mazda_v0_free(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mazda_v0_reset(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepReset;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->manchester_state = ManchesterStateStart1;
|
||||
instance->preamble_count = 0;
|
||||
instance->preamble_pattern = 0;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mazda_v0_feed(void* context, bool level, uint32_t duration) {
|
||||
furi_check(context);
|
||||
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
ManchesterEvent event = ManchesterEventReset;
|
||||
bool data = false;
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case MazdaV0DecoderStepReset:
|
||||
if(level && ((uint32_t)DURATION_DIFF(duration, subghz_protocol_mazda_v0_const.te_short) <
|
||||
(uint32_t)subghz_protocol_mazda_v0_const.te_delta + 20U)) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepPreamble;
|
||||
instance->manchester_state = ManchesterStateMid1;
|
||||
instance->preamble_count = 0;
|
||||
instance->preamble_pattern = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
case MazdaV0DecoderStepPreamble:
|
||||
if(!mazda_v0_get_event(duration, level, &event)) {
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
if(manchester_advance(
|
||||
instance->manchester_state, event, &instance->manchester_state, &data)) {
|
||||
instance->preamble_pattern = (instance->preamble_pattern << 1) | (data ? 1 : 0);
|
||||
|
||||
if(data) {
|
||||
instance->preamble_count++;
|
||||
} else if(instance->preamble_count <= MAZDA_V0_PREAMBLE_ONES - 1U) {
|
||||
instance->preamble_count = 0;
|
||||
instance->preamble_pattern = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
if((instance->preamble_pattern == MAZDA_V0_SYNC_BYTE) &&
|
||||
(instance->preamble_count > MAZDA_V0_PREAMBLE_ONES - 1U)) {
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepData;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case MazdaV0DecoderStepData:
|
||||
if(!mazda_v0_get_event(duration, level, &event)) {
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepReset;
|
||||
break;
|
||||
}
|
||||
|
||||
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 ==
|
||||
subghz_protocol_mazda_v0_const.min_count_bit_for_found) {
|
||||
instance->generic.data = ~instance->decoder.decode_data;
|
||||
mazda_v0_decode_key(&instance->generic);
|
||||
|
||||
if(mazda_v0_calculate_checksum(
|
||||
instance->generic.serial, instance->generic.btn, instance->generic.cnt) ==
|
||||
(uint8_t)instance->generic.data) {
|
||||
instance->serial = instance->generic.serial;
|
||||
instance->button = instance->generic.btn;
|
||||
instance->count = instance->generic.cnt;
|
||||
|
||||
if(instance->base.callback) {
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
}
|
||||
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->preamble_count = 0;
|
||||
instance->preamble_pattern = 0;
|
||||
instance->manchester_state = ManchesterStateStart1;
|
||||
instance->decoder.te_last = 0;
|
||||
instance->decoder.parser_step = MazdaV0DecoderStepReset;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_mazda_v0_get_hash_data(void* context) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mazda_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
|
||||
mazda_v0_decode_key(&instance->generic);
|
||||
instance->serial = instance->generic.serial;
|
||||
instance->button = instance->generic.btn;
|
||||
instance->count = instance->generic.cnt;
|
||||
|
||||
SubGhzProtocolStatus ret =
|
||||
subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
uint32_t chk =
|
||||
mazda_v0_calculate_checksum(instance->serial, instance->button, instance->count);
|
||||
flipper_format_write_uint32(flipper_format, "Checksum", &chk, 1);
|
||||
|
||||
flipper_format_write_uint32(flipper_format, "Serial", &instance->serial, 1);
|
||||
|
||||
uint32_t temp = instance->button;
|
||||
flipper_format_write_uint32(flipper_format, "Btn", &temp, 1);
|
||||
|
||||
flipper_format_write_uint32(flipper_format, "Cnt", &instance->count, 1);
|
||||
|
||||
ret = mazda_v0_write_display(
|
||||
flipper_format, instance->generic.protocol_name, instance->button);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
|
||||
SubGhzProtocolStatus ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_mazda_v0_const.min_count_bit_for_found);
|
||||
|
||||
if(ret == SubGhzProtocolStatusOk) {
|
||||
flipper_format_rewind(flipper_format);
|
||||
|
||||
flipper_format_read_uint32(flipper_format, "Serial", &instance->serial, 1);
|
||||
instance->generic.serial = instance->serial;
|
||||
|
||||
uint32_t btn_temp = 0;
|
||||
flipper_format_read_uint32(flipper_format, "Btn", &btn_temp, 1);
|
||||
instance->button = (uint8_t)btn_temp;
|
||||
instance->generic.btn = instance->button;
|
||||
|
||||
flipper_format_read_uint32(flipper_format, "Cnt", &instance->count, 1);
|
||||
instance->generic.cnt = instance->count;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_mazda_v0_get_string(void* context, FuriString* output) {
|
||||
furi_check(context);
|
||||
SubGhzProtocolDecoderMazdaV0* instance = context;
|
||||
|
||||
mazda_v0_decode_key(&instance->generic);
|
||||
|
||||
const uint8_t raw_crc = instance->generic.data & 0xFF;
|
||||
const uint8_t calc_crc = mazda_v0_calculate_checksum(
|
||||
instance->generic.serial, instance->generic.btn, instance->generic.cnt);
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %dbit CRC:%s\r\n"
|
||||
"Key: %016llX\r\n"
|
||||
"Sn: %08lX Btn: %02X - %s\r\n"
|
||||
"Cnt: %05lX Chk: %02X\r\n",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(raw_crc == calc_crc) ? "OK" : "BAD",
|
||||
(unsigned long long)instance->generic.data,
|
||||
(unsigned long)instance->generic.serial,
|
||||
instance->generic.btn,
|
||||
mazda_v0_get_button_name(instance->generic.btn),
|
||||
(unsigned long)(instance->generic.cnt & 0xFFFFFU),
|
||||
raw_crc);
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
#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>
|
||||
#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 subghz_protocol_mazda_v0;
|
||||
|
||||
void* subghz_protocol_decoder_mazda_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_decoder_mazda_v0_free(void* context);
|
||||
void subghz_protocol_decoder_mazda_v0_reset(void* context);
|
||||
void subghz_protocol_decoder_mazda_v0_feed(void* context, bool level, uint32_t duration);
|
||||
uint8_t subghz_protocol_decoder_mazda_v0_get_hash_data(void* context);
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_mazda_v0_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_decoder_mazda_v0_get_string(void* context, FuriString* output);
|
||||
|
||||
void* subghz_protocol_encoder_mazda_v0_alloc(SubGhzEnvironment* environment);
|
||||
void subghz_protocol_encoder_mazda_v0_free(void* context);
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_mazda_v0_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
void subghz_protocol_encoder_mazda_v0_stop(void* context);
|
||||
LevelDuration subghz_protocol_encoder_mazda_v0_yield(void* context);
|
||||
@@ -0,0 +1,350 @@
|
||||
#include "nord_ice.h"
|
||||
#include "../blocks/const.h"
|
||||
#include "../blocks/decoder.h"
|
||||
#include "../blocks/encoder.h"
|
||||
#include "../blocks/generic.h"
|
||||
#include "../blocks/math.h"
|
||||
|
||||
#define TAG "SubGhzProtocolNord_Ice"
|
||||
|
||||
static const SubGhzBlockConst subghz_protocol_nord_ice_const = {
|
||||
.te_short = 300,
|
||||
.te_long = 800,
|
||||
.te_delta = 150,
|
||||
.min_count_bit_for_found = 33,
|
||||
};
|
||||
|
||||
struct SubGhzProtocolDecoderNord_Ice {
|
||||
SubGhzProtocolDecoderBase base;
|
||||
|
||||
SubGhzBlockDecoder decoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderNord_Ice {
|
||||
SubGhzProtocolEncoderBase base;
|
||||
|
||||
SubGhzProtocolBlockEncoder encoder;
|
||||
SubGhzBlockGeneric generic;
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
Nord_IceDecoderStepReset = 0,
|
||||
Nord_IceDecoderStepSaveDuration,
|
||||
Nord_IceDecoderStepCheckDuration,
|
||||
} Nord_IceDecoderStep;
|
||||
|
||||
const SubGhzProtocolDecoder subghz_protocol_nord_ice_decoder = {
|
||||
.alloc = subghz_protocol_decoder_nord_ice_alloc,
|
||||
.free = subghz_protocol_decoder_nord_ice_free,
|
||||
|
||||
.feed = subghz_protocol_decoder_nord_ice_feed,
|
||||
.reset = subghz_protocol_decoder_nord_ice_reset,
|
||||
|
||||
.get_hash_data = subghz_protocol_decoder_nord_ice_get_hash_data,
|
||||
.serialize = subghz_protocol_decoder_nord_ice_serialize,
|
||||
.deserialize = subghz_protocol_decoder_nord_ice_deserialize,
|
||||
.get_string = subghz_protocol_decoder_nord_ice_get_string,
|
||||
};
|
||||
|
||||
const SubGhzProtocolEncoder subghz_protocol_nord_ice_encoder = {
|
||||
.alloc = subghz_protocol_encoder_nord_ice_alloc,
|
||||
.free = subghz_protocol_encoder_nord_ice_free,
|
||||
|
||||
.deserialize = subghz_protocol_encoder_nord_ice_deserialize,
|
||||
.stop = subghz_protocol_encoder_nord_ice_stop,
|
||||
.yield = subghz_protocol_encoder_nord_ice_yield,
|
||||
};
|
||||
|
||||
const SubGhzProtocol subghz_protocol_nord_ice = {
|
||||
.name = SUBGHZ_PROTOCOL_NORD_ICE_NAME,
|
||||
.type = SubGhzProtocolTypeStatic,
|
||||
.flag = SubGhzProtocolFlag_433 | SubGhzProtocolFlag_AM | SubGhzProtocolFlag_Decodable |
|
||||
SubGhzProtocolFlag_Load | SubGhzProtocolFlag_Save | SubGhzProtocolFlag_Send,
|
||||
|
||||
.decoder = &subghz_protocol_nord_ice_decoder,
|
||||
.encoder = &subghz_protocol_nord_ice_encoder,
|
||||
};
|
||||
|
||||
void* subghz_protocol_encoder_nord_ice_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolEncoderNord_Ice* instance = malloc(sizeof(SubGhzProtocolEncoderNord_Ice));
|
||||
|
||||
instance->base.protocol = &subghz_protocol_nord_ice;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
|
||||
instance->encoder.repeat = 3;
|
||||
instance->encoder.size_upload = 128;
|
||||
instance->encoder.upload = malloc(instance->encoder.size_upload * sizeof(LevelDuration));
|
||||
instance->encoder.is_running = false;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_nord_ice_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderNord_Ice* instance = context;
|
||||
free(instance->encoder.upload);
|
||||
free(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generating an upload from data.
|
||||
* @param instance Pointer to a SubGhzProtocolEncoderNord_Ice instance
|
||||
*/
|
||||
static void subghz_protocol_encoder_nord_ice_get_upload(SubGhzProtocolEncoderNord_Ice* instance) {
|
||||
furi_assert(instance);
|
||||
size_t index = 0;
|
||||
|
||||
// Send key and GAP
|
||||
for(uint8_t i = instance->generic.data_count_bit; i > 0; i--) {
|
||||
if(bit_read(instance->generic.data, i - 1)) {
|
||||
// Send bit 1
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_nord_ice_const.te_long);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_nord_ice_const.te_short * 25);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_nord_ice_const.te_short);
|
||||
}
|
||||
} else {
|
||||
// Send bit 0
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(true, (uint32_t)subghz_protocol_nord_ice_const.te_short);
|
||||
if(i == 1) {
|
||||
//Send gap if bit was last
|
||||
instance->encoder.upload[index++] = level_duration_make(
|
||||
false, (uint32_t)subghz_protocol_nord_ice_const.te_short * 25);
|
||||
} else {
|
||||
instance->encoder.upload[index++] =
|
||||
level_duration_make(false, (uint32_t)subghz_protocol_nord_ice_const.te_long);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
instance->encoder.size_upload = index;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analysis of received data
|
||||
* @param instance Pointer to a SubGhzBlockGeneric* instance
|
||||
*/
|
||||
static void subghz_protocol_nord_ice_check_remote_controller(SubGhzBlockGeneric* instance) {
|
||||
instance->serial = (instance->data >> 15) << 9 |
|
||||
(instance->data & 0x1FF); // 26 bits for serial
|
||||
instance->btn = (instance->data >> 9) & 0x3F; // 6 bits for button
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_nord_ice_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolEncoderNord_Ice* instance = context;
|
||||
SubGhzProtocolStatus ret = SubGhzProtocolStatusError;
|
||||
do {
|
||||
ret = subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_nord_ice_const.min_count_bit_for_found);
|
||||
if(ret != SubGhzProtocolStatusOk) {
|
||||
break;
|
||||
}
|
||||
// Optional value
|
||||
flipper_format_read_uint32(
|
||||
flipper_format, "Repeat", (uint32_t*)&instance->encoder.repeat, 1);
|
||||
|
||||
subghz_protocol_nord_ice_check_remote_controller(&instance->generic);
|
||||
subghz_protocol_encoder_nord_ice_get_upload(instance);
|
||||
instance->encoder.is_running = true;
|
||||
} while(false);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void subghz_protocol_encoder_nord_ice_stop(void* context) {
|
||||
SubGhzProtocolEncoderNord_Ice* instance = context;
|
||||
instance->encoder.is_running = false;
|
||||
}
|
||||
|
||||
LevelDuration subghz_protocol_encoder_nord_ice_yield(void* context) {
|
||||
SubGhzProtocolEncoderNord_Ice* 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;
|
||||
}
|
||||
|
||||
void* subghz_protocol_decoder_nord_ice_alloc(SubGhzEnvironment* environment) {
|
||||
UNUSED(environment);
|
||||
SubGhzProtocolDecoderNord_Ice* instance = malloc(sizeof(SubGhzProtocolDecoderNord_Ice));
|
||||
instance->base.protocol = &subghz_protocol_nord_ice;
|
||||
instance->generic.protocol_name = instance->base.protocol->name;
|
||||
return instance;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_nord_ice_free(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderNord_Ice* instance = context;
|
||||
free(instance);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_nord_ice_reset(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderNord_Ice* instance = context;
|
||||
instance->decoder.parser_step = Nord_IceDecoderStepReset;
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_nord_ice_feed(void* context, bool level, volatile uint32_t duration) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderNord_Ice* instance = context;
|
||||
|
||||
// Nord ICE Decoder
|
||||
// 2026.03 - @xMasterX (MMX)
|
||||
|
||||
// Key samples
|
||||
//
|
||||
// Serial Btn Serial
|
||||
// 0x9467688A btn 1 = 10010100011001110 110100 010001010
|
||||
// 0x9467308A btn 2 = 10010100011001110 011000 010001010
|
||||
// 0x9467628A btn 3 = 10010100011001110 110001 010001010
|
||||
// 0x9467648A btn 4 = 10010100011001110 110010 010001010
|
||||
|
||||
switch(instance->decoder.parser_step) {
|
||||
case Nord_IceDecoderStepReset:
|
||||
if((!level) && (DURATION_DIFF(duration, subghz_protocol_nord_ice_const.te_short * 25) <
|
||||
subghz_protocol_nord_ice_const.te_delta * 11)) {
|
||||
//Found GAP
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = Nord_IceDecoderStepSaveDuration;
|
||||
}
|
||||
break;
|
||||
case Nord_IceDecoderStepSaveDuration:
|
||||
if(level) {
|
||||
instance->decoder.te_last = duration;
|
||||
instance->decoder.parser_step = Nord_IceDecoderStepCheckDuration;
|
||||
} else {
|
||||
instance->decoder.parser_step = Nord_IceDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
case Nord_IceDecoderStepCheckDuration:
|
||||
if(!level) {
|
||||
// Bit 0 is short and long timing = 300us HIGH (te_last) and 800us LOW
|
||||
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_nord_ice_const.te_short) <
|
||||
subghz_protocol_nord_ice_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_nord_ice_const.te_long) <
|
||||
subghz_protocol_nord_ice_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
instance->decoder.parser_step = Nord_IceDecoderStepSaveDuration;
|
||||
// Bit 1 is long and short timing = 800us HIGH (te_last) and 300us LOW
|
||||
} else if(
|
||||
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_nord_ice_const.te_long) <
|
||||
subghz_protocol_nord_ice_const.te_delta) &&
|
||||
(DURATION_DIFF(duration, subghz_protocol_nord_ice_const.te_short) <
|
||||
subghz_protocol_nord_ice_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
instance->decoder.parser_step = Nord_IceDecoderStepSaveDuration;
|
||||
} else if(
|
||||
// End of the key
|
||||
DURATION_DIFF(duration, subghz_protocol_nord_ice_const.te_short * 25) <
|
||||
subghz_protocol_nord_ice_const.te_delta * 11) {
|
||||
//Found next GAP and add bit 0 or 1 (only bit 0 was found on the remotes)
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_nord_ice_const.te_short) <
|
||||
subghz_protocol_nord_ice_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
|
||||
}
|
||||
if((DURATION_DIFF(
|
||||
instance->decoder.te_last, subghz_protocol_nord_ice_const.te_long) <
|
||||
subghz_protocol_nord_ice_const.te_delta)) {
|
||||
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
|
||||
}
|
||||
// If got 33 bits key reading is finished
|
||||
if(instance->decoder.decode_count_bit ==
|
||||
subghz_protocol_nord_ice_const.min_count_bit_for_found) {
|
||||
instance->generic.data = instance->decoder.decode_data;
|
||||
instance->generic.data_count_bit = instance->decoder.decode_count_bit;
|
||||
if(instance->base.callback)
|
||||
instance->base.callback(&instance->base, instance->base.context);
|
||||
}
|
||||
instance->decoder.decode_data = 0;
|
||||
instance->decoder.decode_count_bit = 0;
|
||||
instance->decoder.parser_step = Nord_IceDecoderStepReset;
|
||||
} else {
|
||||
instance->decoder.parser_step = Nord_IceDecoderStepReset;
|
||||
}
|
||||
} else {
|
||||
instance->decoder.parser_step = Nord_IceDecoderStepReset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t subghz_protocol_decoder_nord_ice_get_hash_data(void* context) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderNord_Ice* instance = context;
|
||||
return subghz_protocol_blocks_get_hash_data(
|
||||
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_nord_ice_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderNord_Ice* instance = context;
|
||||
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
|
||||
}
|
||||
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_nord_ice_deserialize(void* context, FlipperFormat* flipper_format) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderNord_Ice* instance = context;
|
||||
return subghz_block_generic_deserialize_check_count_bit(
|
||||
&instance->generic,
|
||||
flipper_format,
|
||||
subghz_protocol_nord_ice_const.min_count_bit_for_found);
|
||||
}
|
||||
|
||||
void subghz_protocol_decoder_nord_ice_get_string(void* context, FuriString* output) {
|
||||
furi_assert(context);
|
||||
SubGhzProtocolDecoderNord_Ice* instance = context;
|
||||
|
||||
subghz_protocol_nord_ice_check_remote_controller(&instance->generic);
|
||||
|
||||
uint64_t code_found_reverse = subghz_protocol_blocks_reverse_key(
|
||||
instance->generic.data, instance->generic.data_count_bit);
|
||||
|
||||
// for future use
|
||||
// // push protocol data to global variable
|
||||
// subghz_block_generic_global.btn_is_available = false;
|
||||
// subghz_block_generic_global.current_btn = instance->generic.btn;
|
||||
// subghz_block_generic_global.btn_length_bit = 4;
|
||||
// //
|
||||
|
||||
furi_string_cat_printf(
|
||||
output,
|
||||
"%s %db\r\n"
|
||||
"Key: 0x%08llX\r\n"
|
||||
"Yek: 0x%08llX\r\n"
|
||||
"Serial: 0x%07lX\r\n"
|
||||
"Btn: %02X",
|
||||
instance->generic.protocol_name,
|
||||
instance->generic.data_count_bit,
|
||||
(uint64_t)(instance->generic.data & 0xFFFFFFFFF),
|
||||
(code_found_reverse & 0xFFFFFFFFF),
|
||||
instance->generic.serial,
|
||||
instance->generic.btn);
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.h"
|
||||
|
||||
#define SUBGHZ_PROTOCOL_NORD_ICE_NAME "Nord ICE"
|
||||
|
||||
typedef struct SubGhzProtocolDecoderNord_Ice SubGhzProtocolDecoderNord_Ice;
|
||||
typedef struct SubGhzProtocolEncoderNord_Ice SubGhzProtocolEncoderNord_Ice;
|
||||
|
||||
extern const SubGhzProtocolDecoder subghz_protocol_nord_ice_decoder;
|
||||
extern const SubGhzProtocolEncoder subghz_protocol_nord_ice_encoder;
|
||||
extern const SubGhzProtocol subghz_protocol_nord_ice;
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolEncoderNord_Ice.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolEncoderNord_Ice* pointer to a SubGhzProtocolEncoderNord_Ice instance
|
||||
*/
|
||||
void* subghz_protocol_encoder_nord_ice_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolEncoderNord_Ice.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderNord_Ice instance
|
||||
*/
|
||||
void subghz_protocol_encoder_nord_ice_free(void* context);
|
||||
|
||||
/**
|
||||
* Deserialize and generating an upload to send.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderNord_Ice instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_encoder_nord_ice_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Forced transmission stop.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderNord_Ice instance
|
||||
*/
|
||||
void subghz_protocol_encoder_nord_ice_stop(void* context);
|
||||
|
||||
/**
|
||||
* Getting the level and duration of the upload to be loaded into DMA.
|
||||
* @param context Pointer to a SubGhzProtocolEncoderNord_Ice instance
|
||||
* @return LevelDuration
|
||||
*/
|
||||
LevelDuration subghz_protocol_encoder_nord_ice_yield(void* context);
|
||||
|
||||
/**
|
||||
* Allocate SubGhzProtocolDecoderNord_Ice.
|
||||
* @param environment Pointer to a SubGhzEnvironment instance
|
||||
* @return SubGhzProtocolDecoderNord_Ice* pointer to a SubGhzProtocolDecoderNord_Ice instance
|
||||
*/
|
||||
void* subghz_protocol_decoder_nord_ice_alloc(SubGhzEnvironment* environment);
|
||||
|
||||
/**
|
||||
* Free SubGhzProtocolDecoderNord_Ice.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
|
||||
*/
|
||||
void subghz_protocol_decoder_nord_ice_free(void* context);
|
||||
|
||||
/**
|
||||
* Reset decoder SubGhzProtocolDecoderNord_Ice.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
|
||||
*/
|
||||
void subghz_protocol_decoder_nord_ice_reset(void* context);
|
||||
|
||||
/**
|
||||
* Parse a raw sequence of levels and durations received from the air.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
|
||||
* @param level Signal level true-high false-low
|
||||
* @param duration Duration of this level in, us
|
||||
*/
|
||||
void subghz_protocol_decoder_nord_ice_feed(void* context, bool level, uint32_t duration);
|
||||
|
||||
/**
|
||||
* Getting the hash sum of the last randomly received parcel.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
|
||||
* @return hash Hash sum
|
||||
*/
|
||||
uint8_t subghz_protocol_decoder_nord_ice_get_hash_data(void* context);
|
||||
|
||||
/**
|
||||
* Serialize data SubGhzProtocolDecoderNord_Ice.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus subghz_protocol_decoder_nord_ice_serialize(
|
||||
void* context,
|
||||
FlipperFormat* flipper_format,
|
||||
SubGhzRadioPreset* preset);
|
||||
|
||||
/**
|
||||
* Deserialize data SubGhzProtocolDecoderNord_Ice.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
|
||||
* @param flipper_format Pointer to a FlipperFormat instance
|
||||
* @return status
|
||||
*/
|
||||
SubGhzProtocolStatus
|
||||
subghz_protocol_decoder_nord_ice_deserialize(void* context, FlipperFormat* flipper_format);
|
||||
|
||||
/**
|
||||
* Getting a textual representation of the received data.
|
||||
* @param context Pointer to a SubGhzProtocolDecoderNord_Ice instance
|
||||
* @param output Resulting text
|
||||
*/
|
||||
void subghz_protocol_decoder_nord_ice_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,6 +76,15 @@ const SubGhzProtocol* const subghz_protocol_registry_items[] = {
|
||||
&subghz_protocol_star_line,
|
||||
&subghz_protocol_scher_khan,
|
||||
&subghz_protocol_sheriff_cfm,
|
||||
// until fix &subghz_protocol_honda,
|
||||
&subghz_protocol_chrysler,
|
||||
&subghz_protocol_kia_v7,
|
||||
&subghz_protocol_mazda_v0,
|
||||
//&honda_static_protocol,
|
||||
&ford_protocol_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,3 +78,10 @@
|
||||
#include "star_line.h"
|
||||
#include "scher_khan.h"
|
||||
#include "sheriff_cfm.h"
|
||||
#include "chrysler.h"
|
||||
#include "honda_static.h"
|
||||
#include "mazda_v0.h"
|
||||
#include "kia_v7.h"
|
||||
#include "ford_v1.h"
|
||||
#include "ford_v2.h"
|
||||
#include "ford_v3.h"
|
||||
|
||||
+111
-85
@@ -125,6 +125,10 @@ struct SubGhzProtocolDecoderPSA {
|
||||
|
||||
uint32_t last_key1_low;
|
||||
uint32_t last_key1_high;
|
||||
|
||||
uint32_t te_sum;
|
||||
uint16_t te_count;
|
||||
uint32_t te_detected;
|
||||
};
|
||||
|
||||
struct SubGhzProtocolEncoderPSA {
|
||||
@@ -670,6 +674,9 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
instance->pattern_counter = 0;
|
||||
instance->decode_count_bit = 0;
|
||||
instance->mode_serialize = 0;
|
||||
instance->te_sum = duration;
|
||||
instance->te_count = 1;
|
||||
instance->te_detected = 0;
|
||||
instance->prev_duration = duration;
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset,
|
||||
&instance->manchester_state, NULL);
|
||||
@@ -758,6 +765,17 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
instance->mode_serialize = 0x36;
|
||||
}
|
||||
|
||||
// Only fire callback if decrypted or validation nibble matches
|
||||
if(instance->decrypted != 0x50 &&
|
||||
(instance->validation_field & 0xf) != 0xa) {
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
instance->decode_count_bit = 0;
|
||||
new_state = PSADecoderState0;
|
||||
instance->state = new_state;
|
||||
return;
|
||||
}
|
||||
|
||||
instance->generic.data = ((uint64_t)instance->key1_high << 32) | instance->key1_low;
|
||||
instance->generic.data_count_bit = 64;
|
||||
instance->decoder.decode_data = instance->generic.data;
|
||||
@@ -924,39 +942,35 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
return;
|
||||
}
|
||||
|
||||
if(duration < PSA_TE_SHORT_125) {
|
||||
tolerance = PSA_TE_SHORT_125 - duration;
|
||||
if(tolerance < PSA_TOLERANCE_50) {
|
||||
uint32_t prev_diff = psa_abs_diff(prev_dur, PSA_TE_SHORT_125);
|
||||
if(prev_diff <= PSA_TOLERANCE_49) {
|
||||
instance->pattern_counter++;
|
||||
} else {
|
||||
instance->pattern_counter = 0;
|
||||
}
|
||||
instance->prev_duration = duration;
|
||||
return;
|
||||
// Adaptive AM preamble: accept 76-174us, average to detect actual TE
|
||||
if(duration >= 76 && duration <= 174) {
|
||||
if(prev_dur >= 76 && prev_dur <= 174) {
|
||||
instance->pattern_counter++;
|
||||
instance->te_sum += duration;
|
||||
instance->te_count++;
|
||||
} else {
|
||||
instance->pattern_counter = 0;
|
||||
instance->te_sum = duration;
|
||||
instance->te_count = 1;
|
||||
}
|
||||
instance->prev_duration = duration;
|
||||
return;
|
||||
} else {
|
||||
tolerance = duration - PSA_TE_SHORT_125;
|
||||
if(tolerance < PSA_TOLERANCE_50) {
|
||||
uint32_t prev_diff = psa_abs_diff(prev_dur, PSA_TE_SHORT_125);
|
||||
if(prev_diff <= PSA_TOLERANCE_49) {
|
||||
instance->pattern_counter++;
|
||||
} else {
|
||||
instance->pattern_counter = 0;
|
||||
}
|
||||
instance->prev_duration = duration;
|
||||
return;
|
||||
} else if(duration >= PSA_TE_LONG_250 && duration < 0x12c) {
|
||||
if(instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) {
|
||||
new_state = PSADecoderState4;
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
instance->decode_count_bit = 0;
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset,
|
||||
&instance->manchester_state, NULL);
|
||||
instance->state = new_state;
|
||||
}
|
||||
// Check if this is the preamble-to-data transition (2x detected TE)
|
||||
uint32_t te_avg = (instance->te_count > 0) ?
|
||||
(instance->te_sum / instance->te_count) : PSA_TE_SHORT_125;
|
||||
uint32_t te_long_expected = te_avg * 2;
|
||||
uint32_t long_diff = psa_abs_diff(duration, te_long_expected);
|
||||
|
||||
if(long_diff <= te_avg && instance->pattern_counter > PSA_PATTERN_THRESHOLD_2) {
|
||||
instance->te_detected = te_avg;
|
||||
new_state = PSADecoderState4;
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
instance->decode_count_bit = 0;
|
||||
manchester_advance(instance->manchester_state, ManchesterEventReset,
|
||||
&instance->manchester_state, NULL);
|
||||
instance->state = new_state;
|
||||
instance->pattern_counter = 0;
|
||||
instance->prev_duration = duration;
|
||||
return;
|
||||
@@ -964,67 +978,25 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
}
|
||||
|
||||
new_state = PSADecoderState0;
|
||||
instance->pattern_counter = 0;
|
||||
break;
|
||||
|
||||
case PSADecoderState4:
|
||||
case PSADecoderState4: {
|
||||
if(instance->decode_count_bit >= PSA_MAX_BITS) {
|
||||
new_state = PSADecoderState0;
|
||||
break;
|
||||
}
|
||||
|
||||
if(!level) {
|
||||
uint8_t manchester_input;
|
||||
bool decoded_bit = false;
|
||||
|
||||
if(duration < PSA_TE_SHORT_125) {
|
||||
tolerance = PSA_TE_SHORT_125 - duration;
|
||||
if(tolerance > PSA_TOLERANCE_49) {
|
||||
return;
|
||||
}
|
||||
manchester_input = ((level ^ 1) & 0x7f) << 1;
|
||||
} else {
|
||||
tolerance = duration - PSA_TE_SHORT_125;
|
||||
if(tolerance < PSA_TOLERANCE_50) {
|
||||
manchester_input = ((level ^ 1) & 0x7f) << 1;
|
||||
} else if(duration >= PSA_TE_LONG_250 && duration < 0x12c) {
|
||||
if(level == 0) {
|
||||
manchester_input = 6;
|
||||
} else {
|
||||
manchester_input = 4;
|
||||
}
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if(manchester_advance(instance->manchester_state,
|
||||
(ManchesterEvent)manchester_input,
|
||||
&instance->manchester_state,
|
||||
&decoded_bit)) {
|
||||
uint32_t carry = (instance->decode_data_low >> 31) & 1;
|
||||
instance->decode_data_low = (instance->decode_data_low << 1) | (decoded_bit ? 1 : 0);
|
||||
instance->decode_data_high = (instance->decode_data_high << 1) | carry;
|
||||
instance->decode_count_bit++;
|
||||
|
||||
if(instance->decode_count_bit == PSA_KEY1_BITS) {
|
||||
instance->key1_low = instance->decode_data_low;
|
||||
instance->key1_high = instance->decode_data_high;
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
}
|
||||
}
|
||||
} else if(level) {
|
||||
uint32_t end_diff;
|
||||
if(duration < PSA_TE_END_500) {
|
||||
end_diff = PSA_TE_END_500 - duration;
|
||||
} else {
|
||||
end_diff = duration - PSA_TE_END_500;
|
||||
}
|
||||
if(end_diff <= 99) {
|
||||
if(instance->decode_count_bit != PSA_KEY2_BITS) {
|
||||
return;
|
||||
}
|
||||
uint32_t te_s = instance->te_detected ? instance->te_detected : PSA_TE_SHORT_125;
|
||||
uint32_t te_l = te_s * 2;
|
||||
uint32_t te_tol = te_s / 2;
|
||||
uint32_t midpoint = (te_s + te_l) / 2;
|
||||
|
||||
// End marker check: HIGH pulse beyond long range at 80 bits
|
||||
if(level && instance->decode_count_bit == PSA_KEY2_BITS && duration > midpoint) {
|
||||
uint32_t end_expected = te_s * 4;
|
||||
uint32_t end_diff = psa_abs_diff(duration, end_expected);
|
||||
if(end_diff <= te_s * 2) {
|
||||
instance->validation_field = (uint16_t)(instance->decode_data_low & 0xFFFF);
|
||||
instance->key2_low = instance->decode_data_low;
|
||||
instance->key2_high = instance->decode_data_high;
|
||||
@@ -1041,6 +1013,16 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
instance->mode_serialize = 0x36;
|
||||
}
|
||||
|
||||
if(instance->decrypted != 0x50 &&
|
||||
(instance->validation_field & 0xf) != 0xa) {
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
instance->decode_count_bit = 0;
|
||||
new_state = PSADecoderState0;
|
||||
instance->state = new_state;
|
||||
return;
|
||||
}
|
||||
|
||||
instance->generic.data = ((uint64_t)instance->key1_high << 32) | instance->key1_low;
|
||||
instance->generic.data_count_bit = 64;
|
||||
instance->decoder.decode_data = instance->generic.data;
|
||||
@@ -1054,12 +1036,56 @@ void subghz_protocol_decoder_psa_feed(void* context, bool level, uint32_t durati
|
||||
new_state = PSADecoderState0;
|
||||
instance->state = new_state;
|
||||
return;
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
// Manchester decode: process BOTH high and low pulses (unlike original AM path)
|
||||
if(duration > te_l + te_tol) {
|
||||
if(duration > 10000) {
|
||||
new_state = PSADecoderState0;
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
uint8_t manchester_input;
|
||||
bool decoded_bit = false;
|
||||
|
||||
if(duration <= midpoint) {
|
||||
if(psa_abs_diff(duration, te_s) > te_tol) {
|
||||
return;
|
||||
}
|
||||
manchester_input = level ? ManchesterEventShortLow : ManchesterEventShortHigh;
|
||||
} else {
|
||||
if(psa_abs_diff(duration, te_l) > te_tol) {
|
||||
return;
|
||||
}
|
||||
manchester_input = level ? ManchesterEventLongLow : ManchesterEventLongHigh;
|
||||
}
|
||||
|
||||
if(instance->decode_count_bit < PSA_KEY2_BITS) {
|
||||
if(manchester_advance(instance->manchester_state,
|
||||
(ManchesterEvent)manchester_input,
|
||||
&instance->manchester_state,
|
||||
&decoded_bit)) {
|
||||
uint32_t carry = (instance->decode_data_low >> 31) & 1;
|
||||
// PSA AM uses inverted Manchester convention
|
||||
decoded_bit = !decoded_bit;
|
||||
instance->decode_data_low = (instance->decode_data_low << 1) | (decoded_bit ? 1 : 0);
|
||||
instance->decode_data_high = (instance->decode_data_high << 1) | carry;
|
||||
instance->decode_count_bit++;
|
||||
|
||||
if(instance->decode_count_bit == PSA_KEY1_BITS) {
|
||||
instance->key1_low = instance->decode_data_low;
|
||||
instance->key1_high = instance->decode_data_high;
|
||||
instance->decode_data_low = 0;
|
||||
instance->decode_data_high = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
instance->state = new_state;
|
||||
instance->prev_duration = duration;
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user