From 86584feb163764123ee0c342b9fe14d0e65663bf Mon Sep 17 00:00:00 2001 From: rollorentner Date: Sun, 16 Aug 2026 20:10:04 +0200 Subject: [PATCH] Add Superrollo (GW60/HCS361) TX and RX (#3290) keeloqtx: Superrollo 67-bit transmit mode with rolling counter. subghzd: GW60 receive decoder (reports as KeeLoq; manufacturer key read from KEELOQKEYS/MFCODES). The manufacturer key is not included; add a Superrollo entry to MFCODES to enable TX/RX. --- firmware/application/apps/ui_subghzd.cpp | 6 +- .../external/keeloqtx/ui_keeloqtx.cpp | 40 +++++- .../external/keeloqtx/ui_keeloqtx.hpp | 1 + firmware/baseband/fprotos/s-superrollo.hpp | 124 ++++++++++++++++++ firmware/baseband/fprotos/subghzdprotos.hpp | 2 + firmware/baseband/fprotos/subghztypes.hpp | 1 + sdcard/KEELOQREMOTES/GW60_DOWN.KEELOQ | 1 + sdcard/KEELOQREMOTES/GW60_STOP.KEELOQ | 1 + sdcard/KEELOQREMOTES/GW60_UP.KEELOQ | 1 + 9 files changed, 173 insertions(+), 4 deletions(-) create mode 100644 firmware/baseband/fprotos/s-superrollo.hpp create mode 100644 sdcard/KEELOQREMOTES/GW60_DOWN.KEELOQ create mode 100644 sdcard/KEELOQREMOTES/GW60_STOP.KEELOQ create mode 100644 sdcard/KEELOQREMOTES/GW60_UP.KEELOQ diff --git a/firmware/application/apps/ui_subghzd.cpp b/firmware/application/apps/ui_subghzd.cpp index 027e39124..66951c895 100644 --- a/firmware/application/apps/ui_subghzd.cpp +++ b/firmware/application/apps/ui_subghzd.cpp @@ -74,7 +74,7 @@ void SubGhzDRecentEntryDetailView::update_data() { console.writeln("Action: 0x" + to_string_hex(func_code)); } - if (entry_.sensorType == FPS_KEELOQ) { + if (entry_.sensorType == FPS_KEELOQ || entry_.sensorType == FPS_SUPERROLLO) { console.writeln("Fix: " + to_string_hex(fix)); console.writeln("Encrypted: " + to_string_hex(encrypted)); console.writeln("Manufacturer: " + mf_name); @@ -243,6 +243,8 @@ const char* SubGhzDView::getSensorTypeName(FPROTO_SUBGHZD_SENSOR type) { return "Ido 11x"; case FPS_INTERTECHNOV3: return "InterTehcno v3"; + case FPS_SUPERROLLO: + return "Superrollo"; case FPS_KEELOQ: return "KeeLoq"; case FPS_KINGGATESSTYLO4K: @@ -671,7 +673,7 @@ void SubGhzDRecentEntryDetailView::parseProtocol() { return; } - if (entry_.sensorType == FPS_KEELOQ) { + if (entry_.sensorType == FPS_KEELOQ || entry_.sensorType == FPS_SUPERROLLO) { uint64_t data_rev = FProtoGeneral::subghz_protocol_blocks_reverse_key(entry_.data, 64); btn = data_rev >> 60; diff --git a/firmware/application/external/keeloqtx/ui_keeloqtx.cpp b/firmware/application/external/keeloqtx/ui_keeloqtx.cpp index eb753f15c..aa8ffe99c 100644 --- a/firmware/application/external/keeloqtx/ui_keeloqtx.cpp +++ b/firmware/application/external/keeloqtx/ui_keeloqtx.cpp @@ -34,7 +34,7 @@ void KeeloqTXView::update_hop() { hop = data.btn << 28 | (apri_serial & 0xFFF) << 16 | data.counter; } else if ( - data.mf_name == "DTM_Neo" || data.mf_name == "FAAC_RC,XT" || data.mf_name == "Mutanco_Mutancode" || data.mf_name == "Came_Space" || data.mf_name == "Genius_Bravo" || data.mf_name == "GSN" || data.mf_name == "Rosh" || data.mf_name == "Rossi" || data.mf_name == "Peccinin" || data.mf_name == "Steelmate" || data.mf_name == "Cardin_S449") { + data.mf_name == "DTM_Neo" || data.mf_name == "FAAC_RC,XT" || data.mf_name == "Mutanco_Mutancode" || data.mf_name == "Came_Space" || data.mf_name == "Genius_Bravo" || data.mf_name == "GSN" || data.mf_name == "Rosh" || data.mf_name == "Rossi" || data.mf_name == "Peccinin" || data.mf_name == "Steelmate" || data.mf_name == "Cardin_S449" || data.mf_name == "Superrollo") { hop = data.btn << 28 | (data.serial & 0xFFF) << 16 | data.counter; } else if ( data.mf_name == "NICE_Smilo" || data.mf_name == "NICE_MHOUSE" || data.mf_name == "JCM_Tech") { @@ -75,6 +75,12 @@ void KeeloqTXView::update_payload() { } } + if (data.mf_name == "Superrollo") { + text_payload.set(to_string_hex((uint64_t)(uint32_t)encrypt)); + encode_data_gw60((uint32_t)encrypt); + return; + } + payload = (uint64_t)fix << 32 | encrypt; uint64_t preview_payload = FProtoGeneral::subghz_protocol_blocks_reverse_key(payload, 64); @@ -83,6 +89,34 @@ void KeeloqTXView::update_payload() { encode_data(); } +// Superrollo GW60 (HCS361) 67-bit OOK frame at Te=450us. +void KeeloqTXView::encode_data_gw60(uint32_t hop_enc) { + int bits[67]; + for (uint32_t i = 0; i < 32; i++) bits[i] = (hop_enc >> i) & 1; + for (uint32_t i = 0; i < 28; i++) bits[32 + i] = (data.serial >> i) & 1; + for (uint32_t i = 0; i < 4; i++) bits[60 + i] = (data.btn >> i) & 1; + bits[64] = 1; + + int crc0 = 0, crc1 = 0; + for (uint32_t i = 0; i < 65; i++) { + int new_crc1 = crc0 ^ bits[i]; + int new_crc0 = new_crc1 ^ crc1; + crc0 = new_crc0 & 1; + crc1 = new_crc1 & 1; + } + bits[65] = crc0; + bits[66] = crc1; + + std::string s{}; + for (int i = 0; i < 9; i++) s += "100"; + s += "11111111110000000000"; + for (int i = 0; i < 67; i++) s += keeloq_fragments[bits[i]]; + for (int i = 0; i < 18; i++) s += "0"; + + encoded_data = s; + pause_duration = 0; +} + void KeeloqTXView::encode_data() { std::string fragments{}; @@ -206,9 +240,11 @@ void KeeloqTXView::start_tx() { transmitter_model.enable(); + const double te_us = (data.mf_name == "Superrollo") ? 450.0 : 400.0; + baseband::set_ook_data( bitstream_length, - OOK_SAMPLERATE * (400.0 / 1000000.0), + OOK_SAMPLERATE * (te_us / 1000000.0), repeat, pause_duration); } diff --git a/firmware/application/external/keeloqtx/ui_keeloqtx.hpp b/firmware/application/external/keeloqtx/ui_keeloqtx.hpp index 9c081f6ce..dd2dd9628 100644 --- a/firmware/application/external/keeloqtx/ui_keeloqtx.hpp +++ b/firmware/application/external/keeloqtx/ui_keeloqtx.hpp @@ -106,6 +106,7 @@ class KeeloqTXView : public View { std::string encoded_data{}; void encode_data(); + void encode_data_gw60(uint32_t hop_enc); uint32_t repeat = 4; uint32_t pause_duration = 0; diff --git a/firmware/baseband/fprotos/s-superrollo.hpp b/firmware/baseband/fprotos/s-superrollo.hpp new file mode 100644 index 000000000..9b925f798 --- /dev/null +++ b/firmware/baseband/fprotos/s-superrollo.hpp @@ -0,0 +1,124 @@ + +#ifndef __FPROTO_SUPERROLLO_H__ +#define __FPROTO_SUPERROLLO_H__ + +#include "subghzdbase.hpp" + +// Superrollo GW60 (HCS361) 67-bit OOK decoder. Reports FPS_SUPERROLLO; SubGhzD +// treats FPS_SUPERROLLO like KeeLoq for decrypt/display (see ui_subghzd.cpp), so +// the "Superrollo" keystore key decrypts it while it stays identifiable in logs. + +typedef enum : uint8_t { + SuperrolloStepReset = 0, + SuperrolloStepPreambleLow, + SuperrolloStepSyncLow, + SuperrolloStepSaveDuration, + SuperrolloStepCheckDuration, +} SuperrolloDecoderStep; + +class FProtoSubGhzDSuperrollo : public FProtoSubGhzDBase { + public: + FProtoSubGhzDSuperrollo() { + sensorType = FPS_SUPERROLLO; + te_short = 450; + te_long = 900; + te_delta = 200; + min_count_bit_for_found = 64; + } + + void feed(bool level, uint32_t duration) { + switch (parser_step) { + case SuperrolloStepReset: + if (level) { + if (DURATION_DIFF(duration, te_short) < te_delta) { + header_count++; + parser_step = SuperrolloStepPreambleLow; + } else if ( + (header_count >= 4) && + (DURATION_DIFF(duration, te_short * 10) < te_delta * 10)) { + parser_step = SuperrolloStepSyncLow; + } else { + header_count = 0; + } + } else { + header_count = 0; + } + break; + + case SuperrolloStepPreambleLow: + if ((!level) && (DURATION_DIFF(duration, te_long) < te_delta)) { + parser_step = SuperrolloStepReset; + } else { + parser_step = SuperrolloStepReset; + header_count = 0; + } + break; + + case SuperrolloStepSyncLow: + if ((!level) && (DURATION_DIFF(duration, te_short * 10) < te_delta * 10)) { + parser_step = SuperrolloStepSaveDuration; + decode_data = 0; + decode_count_bit = 0; + } else { + parser_step = SuperrolloStepReset; + header_count = 0; + } + break; + + case SuperrolloStepSaveDuration: + if (level) { + te_last = duration; + parser_step = SuperrolloStepCheckDuration; + } else if (duration >= te_short * 12) { + endFrame(); + } + break; + + case SuperrolloStepCheckDuration: + if (!level) { + if ((DURATION_DIFF(te_last, te_short) < te_delta) && + (DURATION_DIFF(duration, te_long) < te_delta)) { + if (decode_count_bit < min_count_bit_for_found) + subghz_protocol_blocks_add_bit(1); + else + decode_count_bit++; + parser_step = SuperrolloStepSaveDuration; + } else if ( + (DURATION_DIFF(te_last, te_long) < te_delta) && + (DURATION_DIFF(duration, te_short) < te_delta)) { + if (decode_count_bit < min_count_bit_for_found) + subghz_protocol_blocks_add_bit(0); + else + decode_count_bit++; + parser_step = SuperrolloStepSaveDuration; + } else if (duration >= te_short * 12) { + endFrame(); + } else { + parser_step = SuperrolloStepReset; + header_count = 0; + } + } else { + parser_step = SuperrolloStepReset; + header_count = 0; + } + break; + } + } + + uint16_t header_count = 0; + + private: + void endFrame() { + if ((decode_count_bit >= min_count_bit_for_found) && + (decode_count_bit <= min_count_bit_for_found + 3)) { + data_count_bit = min_count_bit_for_found; + if (callback) callback(this); + } + parser_step = SuperrolloStepReset; + decode_data = 0; + decode_count_bit = 0; + header_count = 0; + } +}; + +#endif diff --git a/firmware/baseband/fprotos/subghzdprotos.hpp b/firmware/baseband/fprotos/subghzdprotos.hpp index 6efea5d88..55f4cbbc1 100644 --- a/firmware/baseband/fprotos/subghzdprotos.hpp +++ b/firmware/baseband/fprotos/subghzdprotos.hpp @@ -29,6 +29,7 @@ So include here the .hpp, and add a new element to the protos vector in the cons #include "s-ido.hpp" #include "s-intertechnov3.hpp" #include "s-keeloq.hpp" +#include "s-superrollo.hpp" #include "s-kinggates_stylo_4k.hpp" #include "s-linear.hpp" #include "s-linear_delta3.hpp" @@ -85,6 +86,7 @@ class SubGhzDProtos : public FProtoListGeneral { protos[FPS_IDO] = new FProtoSubGhzDIdo(); protos[FPS_INTERTECHNOV3] = new FProtoSubGhzDIntertechnoV3(); protos[FPS_KEELOQ] = new FProtoSubGhzDKeeLoq(); + protos[FPS_SUPERROLLO] = new FProtoSubGhzDSuperrollo(); protos[FPS_KINGGATESSTYLO4K] = new FProtoSubGhzDKinggatesStylo4K(); protos[FPS_LINEAR] = new FProtoSubGhzDLinear(); protos[FPS_LINEARDELTA3] = new FProtoSubGhzDLinearDelta3(); diff --git a/firmware/baseband/fprotos/subghztypes.hpp b/firmware/baseband/fprotos/subghztypes.hpp index 89cdcb53d..b85a20504 100644 --- a/firmware/baseband/fprotos/subghztypes.hpp +++ b/firmware/baseband/fprotos/subghztypes.hpp @@ -60,6 +60,7 @@ enum FPROTO_SUBGHZD_SENSOR : uint8_t { FPS_MARANTEC24, FPS_HOLTEKHT6P20B, FPS_RESTAURANT_PAGER, + FPS_SUPERROLLO, FPS_COUNT }; diff --git a/sdcard/KEELOQREMOTES/GW60_DOWN.KEELOQ b/sdcard/KEELOQREMOTES/GW60_DOWN.KEELOQ new file mode 100644 index 000000000..d649c1a39 --- /dev/null +++ b/sdcard/KEELOQREMOTES/GW60_DOWN.KEELOQ @@ -0,0 +1 @@ +Superrollo;BADBEEF;1;5 diff --git a/sdcard/KEELOQREMOTES/GW60_STOP.KEELOQ b/sdcard/KEELOQREMOTES/GW60_STOP.KEELOQ new file mode 100644 index 000000000..ccefb6bdb --- /dev/null +++ b/sdcard/KEELOQREMOTES/GW60_STOP.KEELOQ @@ -0,0 +1 @@ +Superrollo;BADBEEF;1;7 diff --git a/sdcard/KEELOQREMOTES/GW60_UP.KEELOQ b/sdcard/KEELOQREMOTES/GW60_UP.KEELOQ new file mode 100644 index 000000000..07c1f7a47 --- /dev/null +++ b/sdcard/KEELOQREMOTES/GW60_UP.KEELOQ @@ -0,0 +1 @@ +Superrollo;BADBEEF;1;3