Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for KeeLoq HCS362 #3113

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open

Add support for KeeLoq HCS362 #3113

wants to merge 2 commits into from

Conversation

zuckschwerdt
Copy link
Collaborator

Sketch for HCS362, not tested at all. Based on info from #3112

@yashikada
Copy link

Does this PR only support TE 200uS or also 800, 400 and 100?
TABLE 8-4 and 8-5 of datasheet.

@zuckschwerdt
Copy link
Collaborator Author

You'll have to modify the timings (near end of file) for that. When finished with the decoder we might add the other timings also.
A good test would be to check if the buttons show up as expected, the battery low works (use known flat batteries or a variable power supply) and perhaps if the id code is correct (your examples show different id codes?)

@yashikada
Copy link

When finished with the decoder we might add the other timings also.

ok it should be simple.

A good test would be to check if the buttons show up as expected,

my keyfob has the same schema showed in FIGURE 2-1 c.

sw4 -> s2
sw1 -> diode -> s0
sw1 -> diode -> s1
sw2 -> s0
sw3 -> s1

looking at FIGURE 3-5 it is not certain that the status of the buttons is transmitted in the fixed code part, but given that the id is variable XSER should be at 0.

the battery low works (use known flat batteries or a variable power supply)

The battery voltage is 3.09V at sleep state and drops to 3.0V when transmitting.

2.1.3 LOW VOLTAGE DETECTOR

000 - 2.0V     100 - 4.0V
001 - 2.1V     101 - 4.2V
010 - 2.2V     110 - 4.4V
011 - 2.3V     111 - 4.6V

I don't know which is the current configuration, but with 3V (CR2430) battery, it should use a value 2.0 - 2.3V, so I should never see a low battery. However I can do a test using different voltages

and perhaps if the id code is correct (your examples show different id codes?)

I don't know which is the current id, but different id codes are rigth with XSER = 0.
I have a Pickit3, I can try to reprogram the device so I can check if the decoder works correctly. The current unknown configuration is useless anyway.

@yashikada
Copy link

perhaps if the id code is correct (your examples show different id codes?)

I hadn't seen in the code that the last byte of the serial is taken in half with & 0xF0, so the id doesn't have to change depending on the button pressed, instead it happens and the strange thing is that it also changes by pressing the same button, with a sequence incomprehensible.
each button generates id 042704[4-7]

@yashikada
Copy link

I tried to decode without using your code with the follow command rtl_433 -f 868.3M -R 0 -X 'n=HCS362,m=OOK_MC_ZEROBIT,s=214,l=214,g=600,r=900' thanks to your code I was able to better interpret the result and here the result:

SW2 S0
000 13e984c6 c41c8402 22
000 295c7eb8 441c8402 2a
000 0ccecaec 441c8402 26
000 0d50ed82 441c8402 3e

SW3 S1
000 24d848c5 441c8401 12
000 0f35422a 441c8401 32
000 1e93857b 441c8401 1a
000 0fc600c9 c41c8401 36
000 14ffa629 441c8401 2e
000 25fa7439 c41c8401 3e

SW4 S2
000 0100499c 441c8400 b2
000 036b2b63 441c8400 aa
000 3ae4fad3 c41c8400 a6

SW1 S0 + S1
000 0180e091 441c8403 12
000 12a95314 c41c8403 32
000 1e22aa57 c41c8403 2a

it seems that there is an extra bit that moves the fixed code, when the SW4 (S2) button is pressed there should be the last bit at 1 but instead it is 0, the rest of the buttons also seem to ignore S2 in the last position.
Do you confirm this theory?
Even the initial part of the fixed code which is sometimes 1100b and sometimes 0100b is strange, maybe the Manchester code is the problem?
I hope you can understand the issue better than me.

@zuckschwerdt
Copy link
Collaborator Author

Yes, there is a problem with the MC decoding, the initial short pulses are read as 0's but likely should be 1's. That also might shift (or truncate) the code by one bit. I really need to rewrite the MC slicer. I don't think decoding will be reliable this way -- e.g. that's likely why the battery low bit is set and why the id seems variable.

@yashikada
Copy link

MC transmission in HCS362 provides start and stop bits, I tried to understand the pulse_slicer_manchester_zerobit function and it doesn't seem to handle these 2 bits.

@zuckschwerdt
Copy link
Collaborator Author

In MC long pulse/gap provide a clock recovery but short pulse/gap do not initially. We can't know the clock until the first long symbol. The start/stop bits must be hardcoded a s known 1's. Other decoders have the initial bit hardcoded as known 0 (i.e. …_zerobit).

@yashikada
Copy link

The start/stop bits must be hardcoded a s known 1's.

sure in FIGURE 3-4 it is shown as logic 1

@yashikada
Copy link

I used Universal Radio Hacker for record and decode the trasmission, after I used a python script (chatpgt generated) which implement a MC decoder and I got good result:

1

01011100100110010110111111011101
10001000001110010000100000000110
0xbbf6993a
0x109c11
S2 S1 S0 S3
0110

00000
1
-----------------------------
1

11010001101011000000111011000010
10001000001110010000100000000010
0x4370358b
0x109c11
S2 S1 S0 S3
0100

00000
1
-----------------------------
1

10011101110000000101000110110001
10001000001110010000100000000100
0x8d8a03b9
0x109c11
S2 S1 S0 S3
0010

01000
1
-----------------------------
1

00000111011001100110100001000111
10001000001110010000100000000001
0xe21666e0
0x109c11
S2 S1 S0 S3
1000

01000
1

serial is always the same and pressed button match.
once the start and stop bits have been managed there should be no errors.

@zuckschwerdt
Copy link
Collaborator Author

Yes, I used a manual MC decode now and strip the start bit. Output should match now.

@yashikada
Copy link

Yes, works now. Battery is 1, I don't know if is correct.

@yashikada
Copy link

I think battery_low should be shown without inversion, Learn mode doesn't exist as bit and repeat should show queue bits which are 2.

@yashikada
Copy link

I tested repeat with this code:

int repeat         = (reverse8(b[8]) & 0x18) >> 3;

and works.
Regarding Learn mode I looked at your code better and not a bit is used but it verifies that all the left pins are at 1, in my case it can't happen because S3 is used as RFEN and not as a button.

In the next few days I will try to reprogram HCS362 with other configurations so I can verify that it works in all ways.

@zuckschwerdt
Copy link
Collaborator Author

Battery is actually battery_ok, you can see that with the -F json output. We have that key in nearly every decoder.
I copied the Learn mode logic from the HCS200/300 decoder -- not sure if it is applicable.

@yashikada
Copy link

yashikada commented Dec 21, 2024

Sorry for the delay. Now I can program the EEPROM of HCS362 and change every parameter.
I replicated the previous configuration and I changed the modulation from Manchester to PWM, so I can test also the decoder 271.
After the last commit the decoder 271 is not compatible with mode PWM.
I tried the first commit but still doesn't work.
Could you improve it?
I don't know if in the real list there is a device which use HCS362 and PWM mode for trasmission, but if you want I attached 2 capture here

@yashikada
Copy link

I changed gap_limit to 550 and not 600 and used this function from your first commit and some changes

static int hcs362_decode_pwm(r_device *decoder, bitbuffer_t *bitbuffer)
{
    // Reject codes of wrong length
    if (bitbuffer->bits_per_row[0] != 12 || bitbuffer->bits_per_row[1] != 69)
        return DECODE_ABORT_LENGTH;

    uint8_t *b = bitbuffer->bb[0];
    // Reject codes with an incorrect preamble (expected 0xfff)
    if (b[0] != 0xff || (b[1] & 0xf0) != 0xf0) {
        decoder_log(decoder, 2, __func__, "Preamble not found");
        return DECODE_ABORT_EARLY;
    }

    // Second row is data
    b = bitbuffer->bb[1];

    // No need to decode/extract values for simple test
    if (b[1] == 0xff && b[2] == 0xff && b[3] == 0xff && b[4] == 0xff
            && b[5] == 0xff && b[6] == 0xff && b[7] == 0xff) {
        decoder_log(decoder, 2, __func__, "DECODE_FAIL_SANITY data all 0xff");
        return DECODE_FAIL_SANITY;
    }

    // The transmission is LSB first, big endian.
    uint32_t encrypted = ((unsigned)reverse8(b[3]) << 24) | (reverse8(b[2]) << 16) | (reverse8(b[1]) << 8) | (reverse8(b[0]));
    int serial         = (reverse8(b[7] & 0xf0) << 24) | (reverse8(b[6]) << 16) | (reverse8(b[5]) << 8) | (reverse8(b[4]));
    int btn            = (b[7] & 0x0f);
    int btn_num        = (btn & 0x08) | ((btn & 0x01) << 2) | (btn & 0x02) | ((btn & 0x04) >> 2); // S3, S0, S1, S2
    //int learn          = (b[7] & 0x0f) == 0x0f;
    int battery_low    = (b[8] & 0x80) == 0x80;
    int repeat         = (reverse8(b[8]) & 0x18) >> 3;

    char encrypted_str[9];
    snprintf(encrypted_str, sizeof(encrypted_str), "%08X", encrypted);
    char serial_str[9];
    snprintf(serial_str, sizeof(serial_str), "%07X", serial);

    /* clang-format off */
    data_t *data = data_make(
            "model",            "",             DATA_STRING,    "Microchip-HCS362",
            "id",               "",             DATA_STRING,    serial_str,
            "battery_ok",       "Battery",      DATA_INT,       !battery_low,
            "button",           "Button",       DATA_INT,       btn_num,
            //"learn",            "Learn mode",   DATA_INT,       learn,
            "repeat",           "Repeat",       DATA_INT,       repeat,
            "encrypted",        "",             DATA_STRING,    encrypted_str,
            NULL);
    /* clang-format on */

    decoder_output_data(decoder, data);
    return 1;
}

and works.

@yashikada
Copy link

I optimized the code in a single function, tested both protocol num and works.

static int hcs362_decode(r_device *decoder, bitbuffer_t *bitbuffer)
{
    uint8_t *b;

    if (decoder->modulation == OOK_PULSE_PCM) {
        // Check preamble
        if (bitbuffer->bits_per_row[0] < 12 * 2 - 8 || bitbuffer->bits_per_row[0] > 12 * 2 + 8) {
            decoder_log(decoder, 2, __func__, "Preamble not found");
            return DECODE_ABORT_LENGTH;
        }
        // Reject codes with an incorrect preamble (expected 0xaaaaaa)
        b = bitbuffer->bb[0];
        if (b[0] != 0xaa || b[1] != 0xaa || b[2] != 0xaa) {
            decoder_log(decoder, 2, __func__, "Preamble invalid");
            return DECODE_ABORT_EARLY;
        }
        // Reject codes of wrong length
        if (bitbuffer->bits_per_row[1] < 72 * 2 || bitbuffer->bits_per_row[1] > 72 * 2 + 4) {
            return DECODE_ABORT_LENGTH;
        }

        // Second row is data
        b = bitbuffer->bb[1];
        // Check for the start bit
        if ((b[0] & 0xc0) != 0x80) {
            decoder_log(decoder, 2, __func__, "Startbit not found");
            return DECODE_ABORT_EARLY;
        }
        // Manchester decode, excluding startbit
        bitbuffer_t msg = {0};
        unsigned len = bitbuffer_manchester_decode(bitbuffer, 1, 2, &msg, 72);
        decoder_log_bitbuffer(decoder, 1, __func__, &msg, "Decoded");

        // Reject codes of wrong length
        if (len < 69 + 1) {
            return DECODE_ABORT_LENGTH;
        }

        bitbuffer_invert(&msg); // want G.E.Thomas, not IEEE 802.3
        b = msg.bb[0];
        // No need to decode/extract values for simple test
        if (b[1] == 0xff && b[2] == 0xff && b[3] == 0xff && b[4] == 0xff
                && b[5] == 0xff && b[6] == 0xff && b[7] == 0xff) {
            decoder_log(decoder, 2, __func__, "DECODE_FAIL_SANITY data all 0xff");
            return DECODE_FAIL_SANITY;
        }
    } else {
        // Reject codes of wrong length
        if (bitbuffer->bits_per_row[0] != 12 || bitbuffer->bits_per_row[1] != 69)
            return DECODE_ABORT_LENGTH;

        b = bitbuffer->bb[0];
        // Reject codes with an incorrect preamble (expected 0xfff)
        // Manchester decoding might read this as 0x000
        if (b[0] != 0xff || (b[1] & 0xf0) != 0xf0) {
            decoder_log(decoder, 2, __func__, "Preamble not found");
            return DECODE_ABORT_EARLY;
        }

        // Second row is data
        b = bitbuffer->bb[1];

        // No need to decode/extract values for simple test
        if (b[1] == 0xff && b[2] == 0xff && b[3] == 0xff && b[4] == 0xff
                && b[5] == 0xff && b[6] == 0xff && b[7] == 0xff) {
            decoder_log(decoder, 2, __func__, "DECODE_FAIL_SANITY data all 0xff");
            return DECODE_FAIL_SANITY;
        }
    }

    // The transmission is LSB first, big endian.
    uint32_t encrypted = ((unsigned)reverse8(b[3]) << 24) | (reverse8(b[2]) << 16) | (reverse8(b[1]) << 8) | (reverse8(b[0]));
    int serial         = (reverse8(b[7] & 0xf0) << 24) | (reverse8(b[6]) << 16) | (reverse8(b[5]) << 8) | (reverse8(b[4]));
    int btn            = (b[7] & 0x0f);
    int btn_num        = (btn & 0x08) | ((btn & 0x01) << 2) | (btn & 0x02) | ((btn & 0x04) >> 2); // S3, S0, S1, S2
    int battery_low    = (b[8] & 0x80) == 0x80;
    int repeat         = (reverse8(b[8]) & 0x18) >> 3;

    char encrypted_str[9];
    snprintf(encrypted_str, sizeof(encrypted_str), "%08X", encrypted);
    char serial_str[9];
    snprintf(serial_str, sizeof(serial_str), "%07X", serial);

    /* clang-format off */
    data_t *data = data_make(
            "model",            "",             DATA_STRING,    "Microchip-HCS362",
            "id",               "",             DATA_STRING,    serial_str,
            "battery_ok",       "Battery",      DATA_INT,       !battery_low,
            "button",           "Button",       DATA_INT,       btn_num,
            //"learn",            "Learn mode",   DATA_INT,       learn,
            "repeat",           "Repeat",       DATA_INT,       repeat,
            "encrypted",        "",             DATA_STRING,    encrypted_str,
            NULL);
    /* clang-format on */

    decoder_output_data(decoder, data);
    return 1;
}

@zuckschwerdt zuckschwerdt self-assigned this Dec 21, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants