Add initial subghz-protocol-x10-decoder project

This commit is contained in:
Derek Jamison 2023-01-11 14:42:06 -05:00
parent b8972e54fd
commit 65721becff
6 changed files with 500 additions and 0 deletions

View File

@ -1,2 +1,8 @@
# flipper-zero-tutorials # flipper-zero-tutorials
I will use this repository for my Flipper Zero projects. The various README.md files should describe how to use the files on your Flipper Zero. I will use this repository for my Flipper Zero projects. The various README.md files should describe how to use the files on your Flipper Zero.
Feel free to reach out to me at CodeAllNight@outlook.com with any questions or leave them in the [issues section](https://github.com/jamisonderek/flipper-zero-tutorials/issues) for this project.
## subghz-protocol-x10-decoder
[project](./subghz/protocols/x10/README.md) - This is a protocol decoder for the Flipper Zero to decode the x10 series of devices when doing a read from the Sub-GHz menu.

View File

@ -0,0 +1,61 @@
# X10 Decoder
## Introduction
This is a protocol decoder for the Flipper Zero to decode the X10 series of devices when doing a read from the Sub-GHz menu.
## Validation
NOTE: As an Amazon Associate I (CodeAllNight) earn from qualifying purchases from the amzn.to links below.
The code was tested with the "X10 Camera Control System Remote Control Model CR12A" which you can purchase from
https://amzn.to/3issZPs and should also be compatible with signals from https://amzn.to/3QEF7cM
The receiver I used is no longer for sale, but a similar module can be purchased from https://amzn.to/3CFWy6M
The House (Channel) is A-P setting.
The Unit (Button) is 1-16 setting (slider switches between 1-8 and 9-16).
The signal is "On" or "Off". For each channel there is also a "Dim" or "Bright".
The signal is broadcast at 310MHz with a modulation of AM650.
## Installation Directions
This project is intended to be overlayed on top of an existing firmware repo.
- Clone an existing flipper zero firmware repo
- Build the firmware
- Deploy the firmware
- Confirm the firmware is working properly
- Add the x10.c file to the \lib\subghz\protocols folder
- Add the x10.h file to the \lib\subghz\protocols folder
- Update the \lib\subghz\protocols\protocol_items.h file
- After all of the other #include statements add the following line:
```
#include "x10.h"
```
- Update the \lib\subghz\protocols\protocol_items.c file
- After all of the other &subghz_protocol_{name}, entries add the following:
```
&subghz_protocol_x10,
```
- NOTE: Your updated files should look similar to the protocol_items.c and protocol_items.h in this project.
- Build the firmware
- Deploy the firmware
## Running the updated firmware
These directions assume you are starting at the flipper desktop. If not, please press the back button until you are at the desktop.
- Press the OK button on the flipper to pull up the main menu.
- Choose "Sub-GHz" from the menu.
- Choose "Read" from the sub-menu.
- Choose the "Config" option (Click the left arrow)
- Change the frequency to 310.00 (Click the left arrow multiple times)
- Confirm that Hopping is OFF.
- Confirm the Modulation is AM650.
- Press the BACK button.
- The flipper should say "Scanning... and show 310.00 AM 00/50"
Click a button on your X10 remote.
The flipper should show "X10 <some code>"
- Press the OK button on the flipper
It should show the Channel, Button number and the kind of button (ON/OFF/DIM/BRIGHT).

View File

@ -0,0 +1,21 @@
#include "protocol_items.h"
const SubGhzProtocol* subghz_protocol_registry_items[] = {
&subghz_protocol_gate_tx, &subghz_protocol_keeloq, &subghz_protocol_star_line,
&subghz_protocol_nice_flo, &subghz_protocol_came, &subghz_protocol_faac_slh,
&subghz_protocol_nice_flor_s, &subghz_protocol_came_twee, &subghz_protocol_came_atomo,
&subghz_protocol_nero_sketch, &subghz_protocol_ido, &subghz_protocol_kia,
&subghz_protocol_hormann, &subghz_protocol_nero_radio, &subghz_protocol_somfy_telis,
&subghz_protocol_somfy_keytis, &subghz_protocol_scher_khan, &subghz_protocol_princeton,
&subghz_protocol_raw, &subghz_protocol_linear, &subghz_protocol_secplus_v2,
&subghz_protocol_secplus_v1, &subghz_protocol_megacode, &subghz_protocol_holtek,
&subghz_protocol_chamb_code, &subghz_protocol_power_smart, &subghz_protocol_marantec,
&subghz_protocol_bett, &subghz_protocol_doitrand, &subghz_protocol_phoenix_v2,
&subghz_protocol_honeywell_wdb, &subghz_protocol_magellan, &subghz_protocol_intertechno_v3,
&subghz_protocol_clemsa, &subghz_protocol_ansonic, &subghz_protocol_smc5326,
&subghz_protocol_holtek_th12x, &subghz_protocol_x10,
};
const SubGhzProtocolRegistry subghz_protocol_registry = {
.items = subghz_protocol_registry_items,
.size = COUNT_OF(subghz_protocol_registry_items)};

View File

@ -0,0 +1,43 @@
#pragma once
#include "../registry.h"
#include "princeton.h"
#include "keeloq.h"
#include "star_line.h"
#include "nice_flo.h"
#include "came.h"
#include "faac_slh.h"
#include "nice_flor_s.h"
#include "came_twee.h"
#include "came_atomo.h"
#include "nero_sketch.h"
#include "ido.h"
#include "kia.h"
#include "hormann.h"
#include "nero_radio.h"
#include "somfy_telis.h"
#include "somfy_keytis.h"
#include "scher_khan.h"
#include "gate_tx.h"
#include "raw.h"
#include "linear.h"
#include "secplus_v2.h"
#include "secplus_v1.h"
#include "megacode.h"
#include "holtek.h"
#include "chamberlain_code.h"
#include "power_smart.h"
#include "marantec.h"
#include "bett.h"
#include "doitrand.h"
#include "phoenix_v2.h"
#include "honeywell_wdb.h"
#include "magellan.h"
#include "intertechno_v3.h"
#include "clemsa.h"
#include "ansonic.h"
#include "smc5326.h"
#include "holtek_ht12x.h"
#include "x10.h"
extern const SubGhzProtocolRegistry subghz_protocol_registry;

287
subghz/protocols/x10/x10.c Normal file
View File

@ -0,0 +1,287 @@
#include "x10.h"
#include "../blocks/const.h"
#include "../blocks/decoder.h"
#include "../blocks/encoder.h"
#include "../blocks/generic.h"
#include "../blocks/math.h"
#define TAG "SubGhzProtocolX10"
// @CodeAllNight - X10 Packet decoder...
//
// Do a Sub-GHz read at 310MHz, with 650KHz AM modulation.
//
// Pulses are as follows...
// + 9600 [16*te_short] ~ [te_delta*3] | 9025 [te_delta*7]
// - 4875 [8*te_short] ~ [te_delta*3] | 4488 [te_delta*5]
//
// 32 bits of data (see below)...
// + 600 [te_short] | 550
// - 600 [te_short] (for 0) or 1800 [te_long] (for 1) | 550 (for 0) or 1700 (for 1) [te_delta*2]
//
// + 600 [te_short]
// -43200 [72*te_short] ~ [te_delta*2]
//
// Data simplification of 32 bits can the thought of as:
// first 8 bits are device id (technically first 4 bits sent are channel #).
// second 8 bits are inverted from previous 8 bits.
// next 8 bits are command.
// last 8 bits are inverted from previous 8 bits.
//
// Format: SSSSXBXX ssssxbxx DBOQBXXX dboqbxxx
// S - The serial number (Channel) is encoded in the first four bits that were sent.
// x - Unused bits
// B - Bit 6 is set if the button should be button 9-16, instead of buttons 1-8.
// DQ - The 1st bit of byte 3 is 1 if DIMMER. (bit 4=0 for BRIGHT, bit 4=1 for DIM)
// B - The 2nd bit of byte 3 is the button number.
// Q - 3rd bit of byte 3 are 1 for OFF and 0 for ON (unless DIMMER).
// B - 4th and 5th bit of byte 3 is the rest of the button number.
//
// Actual protocol can be found at http://kbase.x10.com/wiki/CM17A_Protocol
static const SubGhzBlockConst subghz_protocol_x10_const = {
.te_short = 600,
.te_long = 1800,
.te_delta = 100,
.min_count_bit_for_found = 32,
};
struct SubGhzProtocolDecoderX10 {
SubGhzProtocolDecoderBase base;
SubGhzBlockDecoder decoder;
SubGhzBlockGeneric generic;
};
struct SubGhzProtocolEncoderX10 {
SubGhzProtocolEncoderBase base;
SubGhzProtocolBlockEncoder encoder;
SubGhzBlockGeneric generic;
};
typedef enum {
X10DecoderStepReset = 0,
X10DecoderStepFoundPreambula,
X10DecoderStepSaveDuration,
X10DecoderStepCheckDuration,
} X10DecoderStep;
const SubGhzProtocolDecoder subghz_protocol_x10_decoder = {
.alloc = subghz_protocol_decoder_x10_alloc,
.free = subghz_protocol_decoder_x10_free,
.feed = subghz_protocol_decoder_x10_feed,
.reset = subghz_protocol_decoder_x10_reset,
.get_hash_data = subghz_protocol_decoder_x10_get_hash_data,
.serialize = subghz_protocol_decoder_x10_serialize,
.deserialize = subghz_protocol_decoder_x10_deserialize,
.get_string = subghz_protocol_decoder_x10_get_string,
};
const SubGhzProtocolEncoder subghz_protocol_x10_encoder = {
.alloc = NULL,
.free = NULL,
.deserialize = NULL,
.stop = NULL,
.yield = NULL,
};
const SubGhzProtocol subghz_protocol_x10 = {
.name = SUBGHZ_PROTOCOL_X10_NAME,
.type = SubGhzProtocolTypeDynamic,
.flag = SubGhzProtocolFlag_315 /* Technically it is 310MHz only */ | SubGhzProtocolFlag_AM |
SubGhzProtocolFlag_Decodable,
.decoder = &subghz_protocol_x10_decoder,
.encoder = &subghz_protocol_x10_encoder,
};
void* subghz_protocol_decoder_x10_alloc(SubGhzEnvironment* environment) {
UNUSED(environment);
SubGhzProtocolDecoderX10* instance = malloc(sizeof(SubGhzProtocolDecoderX10));
instance->base.protocol = &subghz_protocol_x10;
instance->generic.protocol_name = instance->base.protocol->name;
return instance;
}
void subghz_protocol_decoder_x10_free(void* context) {
furi_assert(context);
SubGhzProtocolDecoderX10* instance = context;
free(instance);
}
void subghz_protocol_decoder_x10_reset(void* context) {
furi_assert(context);
SubGhzProtocolDecoderX10* instance = context;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
instance->decoder.parser_step = X10DecoderStepReset;
}
bool subghz_protocol_x10_validate(void* context) {
furi_assert(context);
SubGhzProtocolDecoderX10* instance = context;
SubGhzBlockDecoder* decoder = &instance->decoder;
uint64_t data = decoder->decode_data;
return decoder->decode_count_bit >= subghz_protocol_x10_const.min_count_bit_for_found &&
((((data >> 24) ^ (data >> 16)) & 0xFF) == 0xFF) &&
((((data >> 8) ^ (data )) & 0xFF) == 0xFF);
}
void subghz_protocol_decoder_x10_feed(void* context, bool level, uint32_t duration) {
furi_assert(context);
SubGhzProtocolDecoderX10* instance = context;
switch(instance->decoder.parser_step) {
case X10DecoderStepReset:
if((level) && (DURATION_DIFF(duration, subghz_protocol_x10_const.te_short * 16) <
subghz_protocol_x10_const.te_delta * 7)) {
instance->decoder.parser_step = X10DecoderStepFoundPreambula;
}
break;
case X10DecoderStepFoundPreambula:
if((!level) && (DURATION_DIFF(duration, subghz_protocol_x10_const.te_short * 8) <
subghz_protocol_x10_const.te_delta * 5)) {
instance->decoder.parser_step = X10DecoderStepSaveDuration;
instance->decoder.decode_data = 0;
instance->decoder.decode_count_bit = 0;
} else {
subghz_protocol_decoder_x10_reset(context);
}
break;
case X10DecoderStepSaveDuration:
if(level) {
if(DURATION_DIFF(duration, subghz_protocol_x10_const.te_short) <
subghz_protocol_x10_const.te_delta) {
if(instance->decoder.decode_count_bit ==
subghz_protocol_x10_const.min_count_bit_for_found) {
instance->decoder.parser_step = X10DecoderStepReset;
if (subghz_protocol_x10_validate(context)) {
FURI_LOG_E(TAG, "Decoded a signal!");
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);
}
subghz_protocol_decoder_x10_reset(context);
} else {
instance->decoder.te_last = duration;
instance->decoder.parser_step = X10DecoderStepCheckDuration;
}
} else {
subghz_protocol_decoder_x10_reset(context);
}
} else {
subghz_protocol_decoder_x10_reset(context);
}
break;
case X10DecoderStepCheckDuration:
if(!level) {
if((DURATION_DIFF(instance->decoder.te_last, subghz_protocol_x10_const.te_short) <
subghz_protocol_x10_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_x10_const.te_short) <
subghz_protocol_x10_const.te_delta)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 0);
instance->decoder.parser_step = X10DecoderStepSaveDuration;
} else if(
(DURATION_DIFF(instance->decoder.te_last, subghz_protocol_x10_const.te_short) <
subghz_protocol_x10_const.te_delta) &&
(DURATION_DIFF(duration, subghz_protocol_x10_const.te_long) <
subghz_protocol_x10_const.te_delta * 2)) {
subghz_protocol_blocks_add_bit(&instance->decoder, 1);
instance->decoder.parser_step = X10DecoderStepSaveDuration;
} else {
subghz_protocol_decoder_x10_reset(context);
}
} else {
subghz_protocol_decoder_x10_reset(context);
}
break;
}
}
/**
* Set the serial and btn values based on the data and data_count_bit.
* @param instance Pointer to a SubGhzBlockGeneric* instance
*/
static void subghz_protocol_x10_check_remote_controller(SubGhzBlockGeneric* instance) {
instance->serial = (instance->data & 0xF0000000) >> (24+4);
instance->btn = (((instance->data & 0x07000000) >> 24) | ((instance->data & 0xF800) >> 8));
}
uint8_t subghz_protocol_decoder_x10_get_hash_data(void* context) {
furi_assert(context);
SubGhzProtocolDecoderX10* instance = context;
return subghz_protocol_blocks_get_hash_data(
&instance->decoder, (instance->decoder.decode_count_bit / 8) + 1);
}
bool subghz_protocol_decoder_x10_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset) {
furi_assert(context);
SubGhzProtocolDecoderX10* instance = context;
return subghz_block_generic_serialize(&instance->generic, flipper_format, preset);
}
bool subghz_protocol_decoder_x10_deserialize(void* context, FlipperFormat* flipper_format) {
furi_assert(context);
SubGhzProtocolDecoderX10* instance = context;
bool ret = false;
do {
if(!subghz_block_generic_deserialize(&instance->generic, flipper_format)) {
break;
}
if(instance->generic.data_count_bit !=
subghz_protocol_x10_const.min_count_bit_for_found) {
FURI_LOG_E(TAG, "Wrong number of bits in key");
break;
}
ret = true;
} while(false);
return ret;
}
const char* CHANNEL_LETTERS = "MNOPCDABEFGHKLIJ";
void subghz_protocol_decoder_x10_get_string(void* context, FuriString* output) {
furi_assert(context);
SubGhzProtocolDecoderX10* instance = context;
subghz_protocol_x10_check_remote_controller(&instance->generic);
char code_channel = CHANNEL_LETTERS[(instance->generic.serial & 0x0F)];
uint32_t code_button = 1 + (
((instance->generic.btn&0x10) >> 4) |
((instance->generic.btn&0x8) >> 2) |
((instance->generic.btn&0x40)>>4) |
((instance->generic.btn&4)<<1));
char* code_action = (instance->generic.btn & 0x20) == 0x20 ? "Off" : "On";
if (instance->generic.btn == 0x98) {
code_button = 0;
code_action = "Dim";
} else if (instance->generic.btn == 0x88) {
code_button = 0;
code_action = "Bright";
}
furi_string_cat_printf(
output,
"%s %dbit\r\n"
"Channel:%c \r\n"
"Button:%ld %s\r\n\r\n"
"Key:%lX%08lX\r\n"
"Sn:%07lX Btn:%X\r\n",
instance->generic.protocol_name,
instance->generic.data_count_bit,
code_channel,
code_button,
code_action,
(uint32_t)(instance->generic.data >> 32),
(uint32_t)instance->generic.data,
instance->generic.serial,
instance->generic.btn);
}

View File

@ -0,0 +1,82 @@
#pragma once
#include "base.h"
#define SUBGHZ_PROTOCOL_X10_NAME "X10"
typedef struct SubGhzProtocolDecoderX10 SubGhzProtocolDecoderX10;
typedef struct SubGhzProtocolEncoderX10 SubGhzProtocolEncoderX10;
extern const SubGhzProtocolDecoder subghz_protocol_x10_decoder;
extern const SubGhzProtocolEncoder subghz_protocol_x10_encoder;
extern const SubGhzProtocol subghz_protocol_x10;
/**
* Allocate SubGhzProtocolDecoderX10.
* @param environment Pointer to a SubGhzEnvironment instance
* @return SubGhzProtocolDecoderFaacSLH* pointer to a SubGhzProtocolDecoderX10 instance
*/
void* subghz_protocol_decoder_x10_alloc(SubGhzEnvironment* environment);
/**
* Free SubGhzProtocolDecoderX10.
* @param context Pointer to a SubGhzProtocolDecoderX10 instance
*/
void subghz_protocol_decoder_x10_free(void* context);
/**
* Reset decoder SubGhzProtocolDecoderX10.
* @param context Pointer to a SubGhzProtocolDecoderX10 instance
*/
void subghz_protocol_decoder_x10_reset(void* context);
/**
* Parse a raw sequence of levels and durations received from the air.
* @param context Pointer to a SubGhzProtocolDecoderX10 instance
* @param level Signal level true-high false-low
* @param duration Duration of this level in, us
*/
void subghz_protocol_decoder_x10_feed(void* context, bool level, uint32_t duration);
/**
* Validates if the current data is valid.
*
* @param decoder Pointer to a SubGhzProtocolDecoderX10 instance.
* @return true if packet data is valid
* @return false if packet data in not valid
*/
bool subghz_protocol_x10_validate(void* context);
/**
* Getting the hash sum of the last randomly received parcel.
* @param context Pointer to a SubGhzProtocolDecoderFaacSLH instance
* @return hash Hash sum
*/
uint8_t subghz_protocol_decoder_x10_get_hash_data(void* context);
/**
* Serialize data SubGhzProtocolDecoderX10.
* @param context Pointer to a SubGhzProtocolDecoderX10 instance
* @param flipper_format Pointer to a FlipperFormat instance
* @param preset The modulation on which the signal was received, SubGhzRadioPreset
* @return true On success
*/
bool subghz_protocol_decoder_x10_serialize(
void* context,
FlipperFormat* flipper_format,
SubGhzRadioPreset* preset);
/**
* Deserialize data SubGhzProtocolDecoderX10.
* @param context Pointer to a SubGhzProtocolDecoderX10 instance
* @param flipper_format Pointer to a FlipperFormat instance
* @return true On success
*/
bool subghz_protocol_decoder_x10_deserialize(void* context, FlipperFormat* flipper_format);
/**
* Getting a textual representation of the received data.
* @param context Pointer to a SubGhzProtocolDecoderX10 instance
* @param output Resulting text
*/
void subghz_protocol_decoder_x10_get_string(void* context, FuriString* output);