ESPHome  2024.12.2
nexa_protocol.cpp
Go to the documentation of this file.
1 #include "nexa_protocol.h"
2 #include "esphome/core/log.h"
3 
4 namespace esphome {
5 namespace remote_base {
6 
7 static const char *const TAG = "remote.nexa";
8 
9 static const uint8_t NBITS = 32;
10 static const uint32_t HEADER_HIGH_US = 319;
11 static const uint32_t HEADER_LOW_US = 2610;
12 static const uint32_t BIT_HIGH_US = 319;
13 static const uint32_t BIT_ONE_LOW_US = 1000;
14 static const uint32_t BIT_ZERO_LOW_US = 140;
15 
16 static const uint32_t TX_HEADER_HIGH_US = 250;
17 static const uint32_t TX_HEADER_LOW_US = TX_HEADER_HIGH_US * 10;
18 static const uint32_t TX_BIT_HIGH_US = 250;
19 static const uint32_t TX_BIT_ONE_LOW_US = TX_BIT_HIGH_US * 5;
20 static const uint32_t TX_BIT_ZERO_LOW_US = TX_BIT_HIGH_US * 1;
21 
23  // '1' => '10'
24  dst->item(TX_BIT_HIGH_US, TX_BIT_ONE_LOW_US);
25  dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US);
26 }
27 
29  // '0' => '01'
30  dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US);
31  dst->item(TX_BIT_HIGH_US, TX_BIT_ONE_LOW_US);
32 }
33 
34 void NexaProtocol::sync(RemoteTransmitData *dst) const { dst->item(TX_HEADER_HIGH_US, TX_HEADER_LOW_US); }
35 
37  dst->set_carrier_frequency(0);
38 
39  // Send SYNC
40  this->sync(dst);
41 
42  // Device (26 bits)
43  for (int16_t i = 26 - 1; i >= 0; i--) {
44  if (data.device & (1 << i)) {
45  this->one(dst);
46  } else {
47  this->zero(dst);
48  }
49  }
50 
51  // Group (1 bit)
52  if (data.group != 0) {
53  this->one(dst);
54  } else {
55  this->zero(dst);
56  }
57 
58  // State (1 bit)
59  if (data.state == 2) {
60  // Special case for dimmers...send 00 as state
61  dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US);
62  dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US);
63  } else if (data.state == 1) {
64  this->one(dst);
65  } else {
66  this->zero(dst);
67  }
68 
69  // Channel (4 bits)
70  for (int16_t i = 4 - 1; i >= 0; i--) {
71  if (data.channel & (1 << i)) {
72  this->one(dst);
73  } else {
74  this->zero(dst);
75  }
76  }
77 
78  // Level (4 bits)
79  if (data.state == 2) {
80  for (int16_t i = 4 - 1; i >= 0; i--) {
81  if (data.level & (1 << i)) {
82  this->one(dst);
83  } else {
84  this->zero(dst);
85  }
86  }
87  }
88 
89  // Send finishing Zero
90  dst->item(TX_BIT_HIGH_US, TX_BIT_ZERO_LOW_US);
91 }
92 
94  NexaData out{
95  .device = 0,
96  .group = 0,
97  .state = 0,
98  .channel = 0,
99  .level = 0,
100  };
101 
102  // From: http://tech.jolowe.se/home-automation-rf-protocols/
103  // New data: http://tech.jolowe.se/old-home-automation-rf-protocols/
104  /*
105 
106  SHHHH HHHH HHHH HHHH HHHH HHHH HHGO EE BB DDDD 0 P
107 
108  S = Sync bit.
109  H = The first 26 bits are transmitter unique codes, and it is this code that the receiver "learns" to recognize.
110  G = Group code, set to one for the whole group.
111  O = On/Off bit. Set to 1 for on, 0 for off.
112  E = Unit to be turned on or off. The code is inverted, i.e. '11' equals 1, '00' equals 4.
113  B = Button code. The code is inverted, i.e. '11' equals 1, '00' equals 4.
114  D = Dim level bits.
115  0 = packet always ends with a zero.
116  P = Pause, a 10 ms pause in between re-send.
117 
118  Update: First of all the '1' and '0' bit seems to be reversed (and be the same as Jula I protocol below), i.e.
119 
120  */
121 
122  // Require a SYNC pulse + long gap
123  if (!src.expect_pulse_with_gap(HEADER_HIGH_US, HEADER_LOW_US))
124  return {};
125 
126  // Device
127  for (uint8_t i = 0; i < 26; i++) {
128  out.device <<= 1UL;
129  if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) &&
130  (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) {
131  // '1' => '10'
132  out.device |= 0x01;
133  } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) &&
134  (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) {
135  // '0' => '01'
136  out.device |= 0x00;
137  } else {
138  // This should not happen...failed command
139  return {};
140  }
141  }
142 
143  // GROUP
144  for (uint8_t i = 0; i < 1; i++) {
145  out.group <<= 1UL;
146  if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) &&
147  (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) {
148  // '1' => '10'
149  out.group |= 0x01;
150  } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) &&
151  (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) {
152  // '0' => '01'
153  out.group |= 0x00;
154  } else {
155  // This should not happen...failed command
156  return {};
157  }
158  }
159 
160  // STATE
161  for (uint8_t i = 0; i < 1; i++) {
162  out.state <<= 1UL;
163 
164  // Special treatment as we should handle 01, 10 and 00
165  // We need to care for the advance made in the expect functions
166  // hence take them one at a time so that we do not get out of sync
167  // in decoding
168 
169  if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US)) {
170  // Starts with '1'
171  if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
172  // '10' => 1
173  out.state |= 0x01;
174  } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US)) {
175  // '11' => NOT OK
176  // This case is here to make sure we advance through the correct index
177  // This should not happen...failed command
178  return {};
179  }
180  } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
181  // Starts with '0'
182  if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US)) {
183  // '01' => 0
184  out.state |= 0x00;
185  } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US)) {
186  // '00' => Special case for dimmer! => 2
187  out.state |= 0x02;
188  }
189  }
190  }
191 
192  // CHANNEL (EE and BB bits)
193  for (uint8_t i = 0; i < 4; i++) {
194  out.channel <<= 1UL;
195  if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) &&
196  (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) {
197  // '1' => '10'
198  out.channel |= 0x01;
199  } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) &&
200  (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) {
201  // '0' => '01'
202  out.channel |= 0x00;
203  } else {
204  // This should not happen...failed command
205  return {};
206  }
207  }
208 
209  // Optional to transmit LEVEL data (8 bits more)
210  if (int32_t(src.get_index() + 8) >= src.size()) {
211  return out;
212  }
213 
214  // LEVEL
215  for (uint8_t i = 0; i < 4; i++) {
216  out.level <<= 1UL;
217  if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US) &&
218  (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US))) {
219  // '1' => '10'
220  out.level |= 0x01;
221  } else if (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ZERO_LOW_US) &&
222  (src.expect_pulse_with_gap(BIT_HIGH_US, BIT_ONE_LOW_US))) {
223  // '0' => '01'
224  out.level |= 0x00;
225  } else {
226  // This should not happen...failed command
227  break;
228  }
229  }
230 
231  return out;
232 }
233 
234 void NexaProtocol::dump(const NexaData &data) {
235  ESP_LOGI(TAG, "Received NEXA: device=0x%04" PRIX32 " group=%d state=%d channel=%d level=%d", data.device, data.group,
236  data.state, data.channel, data.level);
237 }
238 
239 } // namespace remote_base
240 } // namespace esphome
bool expect_pulse_with_gap(uint32_t mark, uint32_t space)
Definition: remote_base.cpp:81
void set_carrier_frequency(uint32_t carrier_frequency)
Definition: remote_base.h:34
void one(RemoteTransmitData *dst) const
void item(uint32_t mark, uint32_t space)
Definition: remote_base.h:29
optional< NexaData > decode(RemoteReceiveData src) override
void encode(RemoteTransmitData *dst, const NexaData &data) override
void zero(RemoteTransmitData *dst) const
void dump(const NexaData &data) override
void sync(RemoteTransmitData *dst) const
Implementation of SPI Controller mode.
Definition: a01nyub.cpp:7