Hormann Garage Opener

I managed to trigger the read function when you press the senders button for more than 5sec and put it into transfer mode. unfortunately the flipper crashes when I try to send this signal…

For me a Read Raw and sending back of that signal works for me.

What Kind of Hormann do you have? Is it with bisecure?

Hello I tried with my Hormann door and no luck :frowning:

I have a bi-secure one I don’t get to work on RAW. Its a hse2-868-bs.

Anyone have any tips or tricks?

Hi there @Str0nArm, I too have a Hörmann HSM4 868mhz transmitter (not a bi-secure one, like referenced later in this thread), which appears to replay just fine.

I’ve looked at your HORMANN.sub and I get some results when trying to decode it manually:

$ ./hoermann-hsm4-868mhz-test.py HORMANN.sub 2>/dev/null | sort -ru | head -n5

The results from my files are even prettier:

$ ./hoermann-hsm4-868mhz-test.py 2.sub 2>/dev/null | uniq 

Looks like around 24ms of On signal, then 8x Off-On-On, where every pulse is is 500us (or 1000us in the case of two consequtive On or Offs).

I’ve seen two types of pulse groupings: Off-On-On (LHH) and Off-On-Off (LHL), which I’m guessing mean 0 and 1 respectively.

I’m assuming those first 8 bits are always 0, so they’re probably not part of the payload. That leaves 45 - 8 = 37 bits of data, which is an odd number. I did check whether it was maybe 5x9 bits of which the last bit was (odd) parity, but that did not compute.

45 bits is also what I’ve seen on this page: Garagedoor Remote Duplicator Compatibility List

I’m now looking for more valid samples of this HSM4 to see if I can find some logic (parity or otherwise) in the excess bits, so there are maybe only 32 bits left for the actual secret. Do you have additional codes/sub-files?

Here’s the script I’m using to decode the sub file:

#!/usr/bin/env python3
import sys

HIGH = 'H'
LOW = 'L'

def subghz_file_to_usecs(filename):
    Read Flipper SubGhz file and yield data points

    Filetype: Flipper SubGhz RAW File
    Version: 1
    Frequency: 868350000
    Preset: FuriHalSubGhzPresetOok650Async
    Protocol: RAW
    RAW_Data: 1602 -638120 74971 ...
    RAW_Data: 489 -1042 1005 ...
    with open(filename) as fp:
        for line in fp:
            if line.startswith('RAW_Data: '):
                for usec in line.strip().split()[1:]:
                    yield int(usec)

def hoermann_usec_to_pulses(usecs):
    us_per_bit = 500    # expect a signal change every 500us
    us_safe_diff = 75   # allow signals to be 75us early or late

    seen_preamble = False
    for usec in usecs:
        # If the time is negative, it was LOW for that duration.
        # If it is positive, it was HIGH.
        # Expect a timing of 500us, but accept 80us delta.
        pos = -usec if usec < 0 else usec
        count = pos // us_per_bit
        left = pos % us_per_bit
        if left < us_safe_diff:
            left = 0
        elif left > (us_per_bit - us_safe_diff):
            count += 1
            left = 0

        if count >= 16 and usec > 0:
            # We've been high for over 8ms, assume preamble.
            seen_preamble = True
        elif seen_preamble and left:
            # If there is data that does not fit the timing. Report it.
            # But only if we've found the preamble.
            sys.stderr.write(f'Odd timing of {usec}, back to null\n')
            seen_preamble = False

        if seen_preamble:
            for i in range(0, count):
                yield LOW if usec < 0 else HIGH

def hoermann_find_preamble_and_data(on_off_signals):
    # Start first after the preamble, which is around 12ms of high, so
    # around 24 highs.
        while True:
            signals = []
            count = 0
            for signal in on_off_signals:
                if signal == HIGH:
                    count += 1
                elif count >= 20:
                    count = 0

            while True:
                signal = next(on_off_signals)
                if signal == HIGH and signals[-2:] == [HIGH, HIGH]:
                    if signals[0:3] == [LOW, HIGH, HIGH]:
                        yield ''.join(signals)
    except StopIteration:
        if signals:
            yield ''.join(signals)

def hoermann_pulses_to_bits(pulses):
    for triplets in pulses:
        if len(triplets) % 3 != 0:
            sys.stderr.write(f'Bad triplet in {triplets}, short message?\n')
            triplets = triplets[0:(len(triplets) // 3 * 3)]

        for i in range(0, len(triplets), 3):
            triplet = triplets[i:i+3]
            if triplet == 'LHH':
                yield 0
            elif triplet == 'LHL':
                yield 1
                sys.stderr.write(f'Unknown triplet {triplet}, stopping\n')
        yield None

def only_one(it):
    for i in it:
        yield i
        if i is None:

def to_bin(num, length):
    s = bin(num)[2:].zfill(length)
    return '.'.join(s[i:i+4] for i in range(0, len(s), 4))

def hoermann_bits_to_num(bits):
    # Group bits in groups of 9 and assume odd parity.
    num = 0
    parity = 1
    for n, bit in enumerate(bits):
        if bit is None:
            yield bit
        elif n % 9 == 8:
            assert bit == parity, (bin(num), bit, 'expected', parity)
            yield (num, bin(num), bit, 'expected', parity)
            num = 0
            parity = 1
            num <<= 1
            num |= bit
            parity ^= bit
    if n % 9 != 0:
        sys.stderr.write(f'got leftover {num} at {(n//9*9)}+{(n%9)}\n')

def play_func(bits):
    num = length = 0
    for bit in bits:
        if bit is None:
            yield to_bin(num, length)
            num = length = 0
            num <<= 1
            num |= bit
            length += 1

    if length:
        yield to_bin(num)

it = subghz_file_to_usecs(sys.argv[1])
it = hoermann_usec_to_pulses(it)
it = hoermann_find_preamble_and_data(it)
if 1:  # toggle to 0 to see pulses
    it = hoermann_pulses_to_bits(it)
    #it = only_one(it)
    #it = hoermann_bits_to_num(it)
    it = play_func(it)

for n, signal in enumerate(it):
    if signal in (0, 1, LOW, HIGH):
        if (n % 48) == 47:
        elif (n % 8) == 7:
            sys.stdout.write(' ')
    elif signal is None:
        sys.stdout.write(str(signal) + '\n')


At the bottom, you can toggle the 1 to 0 to view raw(er) pulses.

I’d love to hear other thoughts what the excess bits might mean. Or if others can chime in with more sub file examples.


By the way, if you trim down the HORMANN.sub to HORMANN_single.sub (by looking for 12ms (12xxx) and keeping only one set of values between those), you get this:

HORMANN_single.sub (581 Bytes)

One can upload this to the plotter ( My Flipper ), which is potent enough to decode it automatically:

@Str0nArm: maybe rename the topic to include HSM4/868MHz ?

Oh yeah, wow. Is that enough info for the firmware to start decoding these then now without a manual record and re-transmit?

Is that enough info for the firmware to start decoding these then now without a manual record and re-transmit?


If you can follow the steps described here, that would make it complete.

(I don’t think step 6 does something differently. I think it just keeps repeating the same code over and over again, but I haven’t rigorously tested.)

Alternately, someone could look into copying the existing hormann.c to a new file, and altering things as appropriate:


Where https://github.com/flipperdevices/flipperzero-firmware/blob/9b138424671e0e1bdea7e93ca152703427af6bee/lib/subghz/protocols/hormann.c#L264-L265 is where I think it is saved if the decoding is deemed succesful.

So, instead of subghz_protocol_hormann_const.te_short * 64 we’d want 24*short_high and then the 45 bits. But maybe only if the first bits are 8*0 or we might wrongly match other protocols. Therefore having additional sub files from more people would be beneficial.

Did someone have any luck with a HSE4-868-BS? Captures in RAW, no luck with the replay.

I can provide raw files from HackRF, for Hormann HSP 4 BiSecur if it helps

1 Like

is there any news to this topic?

I lost my remote for a Hörmann A 460 garage door (HET-E2 24 BiSecur)
Can I learn a new remote with my flipper without a existing remote?


I am a bit late to the party here but having a look at the raw data provided in this thread it seems like you can interpret this using the following:

The sample HORMANN.sub posted here therefore equates to 00 67 27 45 0B C0. As you can see there are a few errors in this one but having tried a raw recording i took myself it appears to be bang on.

I have checked this against a few raw sample files and the common pattern appears to be

\x00 \xXX \xXX \xXX \xXX \xC0

Still to test this but there is my 10p.


1 Like

my subs upload here and photo of hw

1 Like

easiest way to check, record raw and replay, works or not?

Record raw and replay works for me. I guess it works for the rest too. (For the “old school” HSM4 868mhz, that is.)

in fact, there is support for this protocol in the flipper, it may need to be tweaked a little. if it doesn’t bother you, could you collect all the RAW records for this protocol in a heap and post it again. so I can download and see everything at once.

i also got an Hormann bi-secure.
What can i do go get the FlipperZero work with it?

Could you help me out with this bro i cant seem to get mine right