From 65721becff066ae1f6ae0eb9767e7e43b932cb0a Mon Sep 17 00:00:00 2001 From: Derek Jamison Date: Wed, 11 Jan 2023 14:42:06 -0500 Subject: [PATCH] Add initial subghz-protocol-x10-decoder project --- README.md | 6 + subghz/protocols/x10/README.md | 61 ++++++ subghz/protocols/x10/protocol_items.c | 21 ++ subghz/protocols/x10/protocol_items.h | 43 ++++ subghz/protocols/x10/x10.c | 287 ++++++++++++++++++++++++++ subghz/protocols/x10/x10.h | 82 ++++++++ 6 files changed, 500 insertions(+) create mode 100644 subghz/protocols/x10/README.md create mode 100644 subghz/protocols/x10/protocol_items.c create mode 100644 subghz/protocols/x10/protocol_items.h create mode 100644 subghz/protocols/x10/x10.c create mode 100644 subghz/protocols/x10/x10.h diff --git a/README.md b/README.md index 68652ea..597143b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,8 @@ # 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. + +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. diff --git a/subghz/protocols/x10/README.md b/subghz/protocols/x10/README.md new file mode 100644 index 0000000..9b69897 --- /dev/null +++ b/subghz/protocols/x10/README.md @@ -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). \ No newline at end of file diff --git a/subghz/protocols/x10/protocol_items.c b/subghz/protocols/x10/protocol_items.c new file mode 100644 index 0000000..3173019 --- /dev/null +++ b/subghz/protocols/x10/protocol_items.c @@ -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)}; \ No newline at end of file diff --git a/subghz/protocols/x10/protocol_items.h b/subghz/protocols/x10/protocol_items.h new file mode 100644 index 0000000..06b119d --- /dev/null +++ b/subghz/protocols/x10/protocol_items.h @@ -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; diff --git a/subghz/protocols/x10/x10.c b/subghz/protocols/x10/x10.c new file mode 100644 index 0000000..4aff338 --- /dev/null +++ b/subghz/protocols/x10/x10.c @@ -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); +} diff --git a/subghz/protocols/x10/x10.h b/subghz/protocols/x10/x10.h new file mode 100644 index 0000000..042831f --- /dev/null +++ b/subghz/protocols/x10/x10.h @@ -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);