#include "nexa_protocol.h" #include "esphome/core/log.h" namespace esphome { namespace remote_base { static const char *const TAG = "remote.nexa"; static const uint8_t NBITS = 32; static const uint32_t HEADER_HIGH_US = 319; static const uint32_t HEADER_LOW_US = 2610; static const uint32_t BIT_HIGH_US = 319; static const uint32_t BIT_ONE_LOW_US = 1000; static const uint32_t BIT_ZERO_LOW_US = 140; static const uint32_t TX_HEADER_HIGH_US = 250; static const uint32_t TX_HEADER_LOW_US = TX_HEADER_HIGH_US * 10; static const uint32_t TX_BIT_HIGH_US = 250; static const uint32_t TX_BIT_ONE_LOW_US = TX_BIT_HIGH_US * 5; static const uint32_t TX_BIT_ZERO_LOW_US = TX_BIT_HIGH_US * 1; void NexaProtocol::one(RemoteTransmitData *dst) const { // '1' => '10' dst->item(TX_BIT_HIGH_US, TX_BIT_ONE_LOW_US); dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US); } void NexaProtocol::zero(RemoteTransmitData *dst) const { // '0' => '01' dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US); dst->item(TX_BIT_HIGH_US, TX_BIT_ONE_LOW_US); } void NexaProtocol::sync(RemoteTransmitData *dst) const { dst->item(TX_HEADER_HIGH_US, TX_HEADER_LOW_US); } void NexaProtocol::encode(RemoteTransmitData *dst, const NexaData &data) { dst->set_carrier_frequency(0); // Send SYNC this->sync(dst); // Device (26 bits) for (int16_t i = 26 - 1; i >= 0; i--) { if (data.device & (1 << i)) { this->one(dst); } else { this->zero(dst); } } // Group (1 bit) if (data.group != 0) { this->one(dst); } else { this->zero(dst); } // State (1 bit) if (data.state == 2) { // Special case for dimmers...send 00 as state dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US); dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US); } else if (data.state == 1) { this->one(dst); } else { this->zero(dst); } // Channel (4 bits) for (int16_t i = 4 - 1; i >= 0; i--) { if (data.channel & (1 << i)) { this->one(dst); } else { this->zero(dst); } } // Level (4 bits) if (data.state == 2) { for (int16_t i = 4 - 1; i >= 0; i--) { if (data.level & (1 << i)) { this->one(dst); } else { this->zero(dst); } } } // Send finishing Zero dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US); } optional NexaProtocol::decode(RemoteReceiveData src) { NexaData out{ .device = 0, .group = 0, .state = 0, .channel = 0, .level = 0, }; // From: http://tech.jolowe.se/home-automation-rf-protocols/ // New data: http://tech.jolowe.se/old-home-automation-rf-protocols/ /* SHHHH HHHH HHHH HHHH HHHH HHHH HHGO EE BB DDDD 0 P S = Sync bit. H = The first 26 bits are transmitter unique codes, and it is this code that the receiver "learns" to recognize. G = Group code, set to one for the whole group. O = On/Off bit. Set to 1 for on, 0 for off. E = Unit to be turned on or off. The code is inverted, i.e. '11' equals 1, '00' equals 4. B = Button code. The code is inverted, i.e. '11' equals 1, '00' equals 4. D = Dim level bits. 0 = packet always ends with a zero. P = Pause, a 10 ms pause in between re-send. Update: First of all the '1' and '0' bit seems to be reversed (and be the same as Jula I protocol below), i.e. */ // Require a SYNC pulse + long gap if (!src.expect_pulse_with_gap(HEADER_HIGH_US, HEADER_LOW_US)) return {}; // Device for (uint8_t i = 0; i < 26; i++) { out.device <<= 1UL; if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) && (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) { // '1' => '10' out.device |= 0x01; } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) && (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) { // '0' => '01' out.device |= 0x00; } else { // This should not happen...failed command return {}; } } // GROUP for (uint8_t i = 0; i < 1; i++) { out.group <<= 1UL; if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) && (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) { // '1' => '10' out.group |= 0x01; } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) && (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) { // '0' => '01' out.group |= 0x00; } else { // This should not happen...failed command return {}; } } // STATE for (uint8_t i = 0; i < 1; i++) { out.state <<= 1UL; // Special treatment as we should handle 01, 10 and 00 // We need to care for the advance made in the expect functions // hence take them one at a time so that we do not get out of sync // in decoding if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US)) { // Starts with '1' if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US)) { // '10' => 1 out.state |= 0x01; } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US)) { // '11' => NOT OK // This case is here to make sure we advance through the correct index // This should not happen...failed command return {}; } } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US)) { // Starts with '0' if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US)) { // '01' => 0 out.state |= 0x00; } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US)) { // '00' => Special case for dimmer! => 2 out.state |= 0x02; } } } // CHANNEL (EE and BB bits) for (uint8_t i = 0; i < 4; i++) { out.channel <<= 1UL; if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) && (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) { // '1' => '10' out.channel |= 0x01; } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) && (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) { // '0' => '01' out.channel |= 0x00; } else { // This should not happen...failed command return {}; } } // Optional to transmit LEVEL data (8 bits more) if (int32_t(src.get_index() + 8) >= src.size()) { return out; } // LEVEL for (uint8_t i = 0; i < 4; i++) { out.level <<= 1UL; if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) && (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) { // '1' => '10' out.level |= 0x01; } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) && (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) { // '0' => '01' out.level |= 0x00; } else { // This should not happen...failed command break; } } return out; } void NexaProtocol::dump(const NexaData &data) { ESP_LOGI(TAG, "Received NEXA: device=0x%04" PRIX32 " group=%d state=%d channel=%d level=%d", data.device, data.group, data.state, data.channel, data.level); } } // namespace remote_base } // namespace esphome