Skip to content

Commit 06dbbed

Browse files
committed
WIP: Trying to decode Nexa RF signals
1 parent fea6a7d commit 06dbbed

File tree

2 files changed

+226
-0
lines changed

2 files changed

+226
-0
lines changed

nexa_decoder/nexa_decoder.ino

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
/*
2+
* Decoder sketch for 433 MHz receiver module receiving Nexa commands.
3+
*
4+
* Objective:
5+
*
6+
* Read the digital output from a RWS-371 or similar 433 MHz receiver, and
7+
* output the Nexa-style commands to the serial port.
8+
*
9+
* Connections:
10+
*
11+
* -----------------------
12+
* | 433 MHz RF Receiver |
13+
* -----------------------
14+
* | | | | | | | |
15+
* 1 2 3 4 5 6 7 8
16+
*
17+
* 1: GND
18+
* 2: Digital output (connect to PB4 (Arduino pin #12))
19+
* 3. Linear output (maybe: pull-down resistor to remove noise from pin 2)
20+
* 4: VCC (5V)
21+
* 5: VCC
22+
* 6: GND
23+
* 7: GND
24+
* 8: Optional Antenna (10-15 cm wire, or 35 cm wire)
25+
*
26+
* Author: Johan Herland <johan@herland.net>
27+
* License: GNU GPL v2 or later
28+
*/
29+
30+
#include <limits.h>
31+
32+
// Adjust the following to match where the RF receiver is connected.
33+
#define RF_SETUP() bitClear(DDRB, 4)
34+
#define RF_READ() bitRead(PINB, 4)
35+
36+
#define ARRAY_LENGTH(a) ((sizeof (a)) / (sizeof (a)[0]))
37+
38+
enum PulseType {
39+
PULSE_A, // ~10 150µs LOW pulse that starts the sync
40+
PULSE_B, // ~2 643µs LOW pulse that continues the sync
41+
PULSE_X, // ~1 236µs LOW pulse that is a half of a bit
42+
PULSE_Y, // ~215µs LOW pulse that is the other half of a bit
43+
PULSE_H, // ~310µs HIGH pulse
44+
PULSE_NO // Not one of the others. An invalid pulse.
45+
};
46+
47+
const unsigned int TIMINGS_LEN = 512;
48+
unsigned int timings[TIMINGS_LEN];
49+
50+
void setup()
51+
{
52+
RF_SETUP();
53+
Serial.begin(115200);
54+
Serial.println(F("rf_decoder ready:"));
55+
}
56+
57+
/*
58+
* Read the next pulse from the RF receiver and return it.
59+
*
60+
* This will block until RF_READ() changes. At that point it will return
61+
* an int whose absolute value is the pulse length in µs, and the sign
62+
* is positive for a HIGH pulse and negative for a LOW pulse.
63+
*
64+
* This function must be called more often than the shortest pulse to be
65+
* detected.
66+
*
67+
* This function assumes that the longest pulse of interest is shorter
68+
* than INT_MAX µs. If the measured pulse length is longer, the returned
69+
* value will be pinned at INT_MAX or INT_MIN (for a HIGH and LOW pulse,
70+
* respectively).
71+
*/
72+
int next_pulse()
73+
{
74+
static unsigned long start = 0;
75+
static int state = false;
76+
77+
while (state == RF_READ())
78+
; // spin until state changes
79+
unsigned long now = micros();
80+
bool ret_state = state;
81+
state = RF_READ();
82+
83+
int ret;
84+
if (ret_state)
85+
ret = (now - start > INT_MAX) ? INT_MAX : now - start;
86+
else
87+
ret = (start - now < INT_MIN) ? INT_MIN : start - now;
88+
start = now;
89+
return ret;
90+
}
91+
92+
/*
93+
* Convert value from next_pulse() to one of the expected pulse types.
94+
*/
95+
/* enum PulseType */ int quantize_pulse(int p)
96+
{
97+
// comments: min/max from observations
98+
if (p > 0 && p <= 1000) // 248 / 432
99+
return PULSE_H;
100+
else if (p < 0 && p >= -500) // -44 / -284
101+
return PULSE_Y;
102+
else if (p <= -500 && p >= -1500) // -1132 / -1300
103+
return PULSE_X;
104+
else if (p <= -2000 && p >= -3000) // -2592 / -2692
105+
return PULSE_B;
106+
else if (p <= -9000 && p >= -11000) // -10080 / -10208
107+
return PULSE_A;
108+
else
109+
return PULSE_NO;
110+
}
111+
112+
/*
113+
* Look for the expected RF sync pattern, and return when detected.
114+
*/
115+
void detect_sync()
116+
{
117+
// Sync pattern: A-H-B-H
118+
static const int sync[] = { PULSE_A, PULSE_H, PULSE_B, PULSE_H };
119+
unsigned int i = 0;
120+
while (i < ARRAY_LENGTH(sync))
121+
i = quantize_pulse(next_pulse()) == sync[i] ? i + 1 : 0;
122+
}
123+
124+
void loop()
125+
{
126+
const unsigned int BUF_SIZE = 1024;
127+
char buf[BUF_SIZE];
128+
unsigned int i = 0;
129+
130+
detect_sync();
131+
buf[i++] = '>';
132+
while (i < BUF_SIZE - 1) {
133+
int p = next_pulse();
134+
switch (quantize_pulse(p)) {
135+
case PULSE_A:
136+
buf[i++] = 'A';
137+
break;
138+
case PULSE_B:
139+
buf[i++] = 'B';
140+
break;
141+
case PULSE_X:
142+
buf[i++] = 'X';
143+
break;
144+
case PULSE_Y:
145+
buf[i++] = 'Y';
146+
break;
147+
case PULSE_H:
148+
buf[i++] = 'H';
149+
break;
150+
default:
151+
buf[i++] = '\0';
152+
Serial.println(buf);
153+
Serial.print(F("and then "));
154+
Serial.println(p);
155+
return;
156+
}
157+
}
158+
buf[i++] = '\0';
159+
Serial.println(buf);
160+
Serial.flush();
161+
}

nexa_decoder/nexa_info.txt

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
Observations of the 433MHz Nexa communication
2+
=============================================
3+
4+
The receiver is noisy. When there is no signal, the output from the
5+
receiver seems to be random noise. However, when a Nexa command is being
6+
transmitted, the signal seems "somewhat less noisy". There seems to be a
7+
fairly regular pulse train, often ending for a LOW state for up to ~100ms,
8+
before the signal reverts to noise.
9+
10+
During a command sequence, the HIGH states are usually 300-400µs long,
11+
while the LOW states are either ~200µs, ~1200µs, ~2600µs, or 10000µs long.
12+
13+
- A command sequence has at least 3 repetitions of the same command.
14+
- Each command starts with ~10.2ms LOW pulse
15+
- Each command is ~77-81ms long and consists of exactly 132 L/H pulses
16+
- Between repeated commands, corresponding pulses deviates less than 100µs
17+
from their average.
18+
- Between repeated commands, the total command length deviates less than
19+
60µs from the average.
20+
21+
- All HIGH pulses are on average ~310µs long, within ~248µs to ~432µs.
22+
- All LOW pulses fall into one of the following categories (pulse lengths
23+
are given as min / avg / max in µs):
24+
A. 10080 / 10150 / 10208 (One per command, This is the first LOW
25+
pulse that introduces the command)
26+
B. 2592 / 2643 / 2692 (One per command, This is the second LOW
27+
pulse in every command)
28+
X. 1132 / 1236 / 1300 (There are 32 of these in every command)
29+
Y. 44 / 215 / 284 (There are 32 of these in every command)
30+
31+
The shorter LOW-pulses (X and Y) always appear in pairs (obviously with
32+
HIGH pulses in between), either X followed by Y, or Y followed by X.
33+
34+
So we seem to have the following sync pattern introducing each command:
35+
____ ____
36+
________________________________________| |________________| |
37+
~10 150µs ~310µs ~2 643µs ~310µs
38+
39+
Or abbreviated to: A-H-B-H
40+
41+
Furthermore we have two more waveform, combinations of which make up the
42+
rest of the command:
43+
____ ____
44+
___| |________________| |
45+
~215µs ~310µs ~1 236µs ~310µs
46+
47+
Abbreviated to: Y-H-X-H
48+
____ ____
49+
________________| |___| |
50+
~1 236µs ~310µs ~215µs ~310µs
51+
52+
Abbreviated to: X-H-Y-H
53+
54+
Let's assume these waveforms represents one data bit each, and let's
55+
arbitrarily call them the 0-bit and 1-bit, respectively.
56+
57+
We can then summarize our findings as follows:
58+
- Each command starts with the following sync pattern: A-H-B-H
59+
- Then follows 32 data bits, each bit formatted as:
60+
0: Y-H-X-H
61+
1: X-H-Y-H
62+
- Each command is repeated at least thrice.
63+
64+
From this, we can start constructing an algorithm for decoding the
65+
incoming signal into 32-bit command packets.

0 commit comments

Comments
 (0)