From 0553b9907f912e56a2cd2a08b03a83ce6f2a75c6 Mon Sep 17 00:00:00 2001 From: manuel Date: Tue, 17 Mar 2020 12:18:56 +0100 Subject: Add martin/door --- martin/door/src/hcs200.cpp | 242 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 martin/door/src/hcs200.cpp (limited to 'martin/door/src/hcs200.cpp') diff --git a/martin/door/src/hcs200.cpp b/martin/door/src/hcs200.cpp new file mode 100644 index 0000000..401e670 --- /dev/null +++ b/martin/door/src/hcs200.cpp @@ -0,0 +1,242 @@ +#include + +#include "hcs200.h" + +/** Pulse width lengths used by the transmitter. A pulse width is the time + * between a transition from 0 to 1 or from 1 to 0 of the input pin. + */ +enum RbType +{ + RB_SHORT = 0, // pulse_width + RB_LONG, // 2 * pulse_width + RB_ERROR +}; + +/** Determine if 'pulse' is a short pulse, RB_SHORT or a long one, RB_LONG. + * Normally, a short pulse is the same as the 'tx_clock' and a long pulse is + * twice as long. However, timing problems with servicing interrupts means + * that we need to add some fudge factors. + */ +int HCS200::classify(unsigned pulse) +{ + int d = pulse - tx_clock; + if (d < -100) + return RB_ERROR; + else if (d < 100) + return RB_SHORT; + else + { + d -= tx_clock; + if (d < -100) + return RB_ERROR; + else if (d < 100) + return RB_LONG; + else + return RB_ERROR; + } +} + +#define IN_RANGE(x, min, max) (x >= min && x <= max) +void HCS200::on_isr(uint8_t value) +{ + unsigned long timestamp = micros(); + unsigned long pulse_width = timestamp - last_timestamp; + int d; + + switch (rx_state) { + case RS_PREAMBLE_START: + // value == 0 + preamble_count = 1; + rx_state = RS_PREAMBLE_LOW; + tx_clock = pulse_width; + break; + case RS_PREAMBLE_LOW: + // value == 1 + preamble_count++; + d = pulse_width - tx_clock; + rx_state = (IN_RANGE(d, -100, 100)) ? RS_PREAMBLE_HIGH : RS_NOSYNC; + break; + case RS_PREAMBLE_HIGH: + // value == 0 + preamble_count++; + d = pulse_width - tx_clock; + if (IN_RANGE(d, -100, 100)) + rx_state = (preamble_count == 23) ? RS_PREAMBLE_HEADER : RS_PREAMBLE_LOW; + else + rx_state = RS_NOSYNC; + break; + case RS_PREAMBLE_HEADER: + // header should be low for 10*tx_clock + d = pulse_width - 10 * tx_clock; + if (value == 1 && IN_RANGE(d, -100, 100)) + { + rx_state = RS_DATA; + rx_bit_count = 0; + memset(rx_buf, 0, sizeof(rx_buf)); + } + else + rx_state = RS_NOSYNC; + break; + case RS_DATA: + if (value == 1) + { + int first = classify(last_pulse_width); + int second = classify(pulse_width); + // received a 1 bit + if (first == RB_SHORT && second == RB_LONG) + { + int idx = rx_bit_count / 32; + rx_buf[idx] >>= 1; + rx_buf[idx] |= 0x80000000; + rx_bit_count++; + } + // received a 0 bit + else if (first == RB_LONG && second == RB_SHORT) + { + int idx = rx_bit_count / 32; + rx_buf[idx] >>= 1; + rx_bit_count++; + } + else + rx_state = RS_NOSYNC; + + // we ignore the last bit as it's always "1" + // instead we use the raising edge as trigger to stop + if (rx_bit_count == MAX_BITS - 1) + rx_state = RS_COMPLETED; + } + break; + } + + // check outside of the state machine + // this is important as otherwise otherwise we always miss the start + if (rx_state == RS_NOSYNC && value == 1) + rx_state = RS_PREAMBLE_START; + + last_timestamp = timestamp; + last_pulse_width = pulse_width; +} + +void HCS200::reset() +{ + rx_state = RS_NOSYNC; +} + +bool HCS200::decode(HCS200_Keycode &out) +{ + if (rx_state != RS_COMPLETED) + return false; + out.encrypted = rx_buf[0]; + out.serial = rx_buf[1] & 0x0FFFFFFF; + out.buttons = (rx_buf[1] >> 28) & 0xF; + out.lowbat = rx_buf[2] & 0x80000000; + return true; +} + +void HCS200::print_state(Print &stream) +{ + stream.print("rx_state="); + stream.print(rx_state, DEC); + stream.print(", rx_bit_count="); + Serial.print(rx_bit_count, DEC); + stream.print(", tx_clock="); + stream.print(tx_clock, DEC); + stream.print(", preamble_count="); + stream.println(preamble_count, DEC); +} + +void HCS200_Keycode::print(Print &stream) +{ + stream.print("Keyfob# "); + stream.print(serial, HEX); + stream.print(", buttons:"); + if (buttons & BM_S0) + stream.print(" 1"); + if (buttons & BM_S1) + stream.print(" 2"); + if (buttons & BM_S2) + stream.print(" 3"); + if (buttons & BM_S3) + stream.print(" 4"); + stream.print(", lowbat="); + stream.print(lowbat, DEC); + stream.print(", code="); + stream.print(encrypted, HEX); + stream.print("\n"); +} + +#define PULSE_WIDTH 400 + +inline void HCS200_Keycode::send(bool value, std::function setOutput) +{ + if (!value) + { + setOutput(HIGH); + delayMicroseconds(PULSE_WIDTH); + delayMicroseconds(PULSE_WIDTH); + setOutput(LOW); + delayMicroseconds(PULSE_WIDTH); + } + else + { + setOutput(HIGH); + delayMicroseconds(PULSE_WIDTH); + setOutput(LOW); + delayMicroseconds(PULSE_WIDTH); + delayMicroseconds(PULSE_WIDTH); + } +} + +void HCS200_Keycode::send(std::function setOutput) +{ + uint32_t val; + + // preamble + for(unsigned short i = 0; i < 11; i++) + { + setOutput(HIGH); + delayMicroseconds(PULSE_WIDTH); + setOutput(LOW); + delayMicroseconds(PULSE_WIDTH); + } + setOutput(HIGH); + delayMicroseconds(PULSE_WIDTH); + + // header + setOutput(LOW); + delayMicroseconds(PULSE_WIDTH * 10); + + // encrypted + val = this->encrypted; + for(unsigned short i = 0; i < 32; i++) + { + send(val & 0x01, setOutput); + val >>= 1; + } + + // serial + val = this->serial; + for(unsigned short i = 0; i < 28; i++) + { + send(val & 0x01, setOutput); + val >>= 1; + } + + // buttons + val = this->buttons; + for(unsigned short i = 0; i < 4; i++) + { + send(val & 0x01, setOutput); + val >>= 1; + } + + // lowbat + send(this->lowbat, setOutput); + + // RPT + send(1, setOutput); + + // guard time + setOutput(LOW); + delayMicroseconds(PULSE_WIDTH * 39); +} -- cgit v1.2.3