new_remote 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # NewRemoteSwitch for Raspberry Pi
2
+
3
+ This is a port of the [NewRemoteSwitch for Arduino](https://bitbucket.org/fuzzillogic/433mhzforarduino/wiki/Home) (by Randy Simons) to the Raspberry Pi as a Ruby gem.
4
+
5
+ It can be used to send and receive signals from and to the new A-series [KlikAanKlikUit](https://www.klikaanklikuit.nl/) remote switches. Using a 433MHz transmitter and/or receiver connected to the GPIO ports on a Raspberry Pi. And from Ruby. Isn't that awesome?
6
+
7
+ ## Installation
8
+
9
+ If you haven't installed Ruby yet, then do that first:
10
+
11
+ $ sudo apt-get install ruby ruby-dev
12
+
13
+ This gem uses the [wiringPi](http://wiringpi.com/) library, which should be installed as well:
14
+
15
+ $ sudo apt-get install wiringpi
16
+
17
+ Install the gem:
18
+
19
+ $ sudo gem install new_remote
20
+
21
+ ## Usage
22
+
23
+ If you want to use this library as a non-root user, then you need to add that user to the `gpio` group:
24
+
25
+ $ sudo usermod -a -G gpio username
26
+
27
+ ### Transmitter
28
+
29
+ ```ruby
30
+ require 'new_remote'
31
+
32
+ transmitter = NewRemote::Transmitter.new(address, pin, periodusec = 260, repeats = 4)
33
+ ```
34
+
35
+ The `address` parameter can be either a random number, or copied from your existing remote. You can use the `Receiver` to determine your remotes address.
36
+
37
+ The `pin` parameter is the GPIO pin where the transmitter is connected to. Please note that it uses the [wiringPi pin numbering scheme](http://wiringpi.com/pins/).
38
+
39
+ For more details about the parameters, please read the comments in `NewRemoteTransmitter.h`.
40
+
41
+ The usage of the transmitter is pretty simple and almost needs no further explanation:
42
+
43
+ ```ruby
44
+ switch = transmitter.unit(0) # select first unit (range 0-15)
45
+ switch = transmitter.group # or select group
46
+
47
+ switch.on # switch on devices(s)
48
+ switch.off # switch off device(s)
49
+ switch.dim(8) # set dim level (range 0-15); also switches on device(s)
50
+ ```
51
+
52
+ ### Receiver
53
+
54
+ The `receive` method loops forever and shows the received codes on the standard output.
55
+
56
+ ```ruby
57
+ require 'new_remote'
58
+
59
+ NewRemote::Receiver.receive(pin, repeats)
60
+ ```
61
+
62
+ The `pin` parameter is the GPIO pin where the receiver is connected to. Please note that it uses the [wiringPi pin numbering scheme](http://wiringpi.com/pins/).
63
+
64
+ The `repeats` parameter indicates the number of times the same message must be received to be valid.
65
+
66
+ For more information about the inner working, please read the comments in `NewRemoteReceiver.h`.
67
+
68
+ ## Development
69
+
70
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
71
+
72
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
73
+
74
+ ## Contributing
75
+
76
+ Bug reports and pull requests are welcome on GitHub at https://github.com/ppostma/NewRemoteSwitch.
77
+
78
+ ## License
79
+
80
+ The gem is available as open source under the terms of the [GNU General Public License version 3](https://opensource.org/licenses/GPL-3.0).
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/extensiontask"
5
+
6
+ Rake::ExtensionTask.new("new_remote") do |ext|
7
+ ext.ext_dir = "ext/new_remote"
8
+ ext.lib_dir = "lib/new_remote"
9
+ end
10
+
11
+ task default: [:clean, :compile]
data/bin/console ADDED
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "new_remote"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ # (If you use this, don't forget to add pry to your Gemfile!)
11
+ # require "pry"
12
+ # Pry.start
13
+
14
+ require "irb"
15
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,311 @@
1
+ /*
2
+ * NewRemoteSwitch library v1.2.0 (20140128) made by Randy Simons http://randysimons.nl/
3
+ * See NewRemoteReceiver.h for details.
4
+ *
5
+ * License: GPLv3. See license.txt
6
+ */
7
+
8
+ #include "NewRemoteReceiver.h"
9
+
10
+ #define RESET_STATE _state = -1 // Resets state to initial position.
11
+
12
+ /************
13
+ * NewRemoteReceiver
14
+
15
+ Protocol. (Copied from Wieltje, http://www.circuitsonline.net/forum/view/message/1181410#1181410,
16
+ but with slightly different timings, as measured on my device.)
17
+ _ _
18
+ '0': | |_| |_____ (T,T,T,5T)
19
+ _ _
20
+ '1': | |_____| |_ (T,5T,T,T)
21
+ _ _
22
+ dim: | |_| |_ (T,T,T,T)
23
+
24
+ T = short period of ~260µs. However, this code tries
25
+ to figure out the correct period
26
+
27
+ A full frame looks like this:
28
+
29
+ - start pulse: 1T high, 10.44T low
30
+ - 26 bit: Address
31
+ - 1 bit: group bit
32
+ - 1 bit: on/off/[dim]
33
+ - 4 bit: unit
34
+ - [4 bit: dim level. Present of [dim] is chosen, but might be present anyway...]
35
+ - stop pulse: 1T high, 40T low
36
+
37
+ ************/
38
+
39
+ int8_t NewRemoteReceiver::_interrupt;
40
+ volatile short NewRemoteReceiver::_state;
41
+ uint8_t NewRemoteReceiver::_minRepeats;
42
+ NewRemoteReceiverCallBack NewRemoteReceiver::_callback;
43
+ bool NewRemoteReceiver::_inCallback = false;
44
+ bool NewRemoteReceiver::_enabled = false;
45
+
46
+ void NewRemoteReceiver::init(int8_t interrupt, uint8_t minRepeats, NewRemoteReceiverCallBack callback) {
47
+ _interrupt = interrupt;
48
+ _minRepeats = minRepeats;
49
+ _callback = callback;
50
+
51
+ enable();
52
+ if (_interrupt >= 0) {
53
+ wiringPiISR(_interrupt, INT_EDGE_BOTH, &interruptHandler);
54
+ }
55
+ }
56
+
57
+ void NewRemoteReceiver::enable() {
58
+ RESET_STATE;
59
+ _enabled = true;
60
+ }
61
+
62
+ void NewRemoteReceiver::disable() {
63
+ _enabled = false;
64
+ }
65
+
66
+ void NewRemoteReceiver::deinit() {
67
+ _enabled = false;
68
+ if (_interrupt >= 0) {
69
+ //detachInterrupt(_interrupt);
70
+ }
71
+ }
72
+
73
+ void NewRemoteReceiver::interruptHandler() {
74
+ // This method is written as compact code to keep it fast. While breaking up this method into more
75
+ // methods would certainly increase the readability, it would also be much slower to execute.
76
+ // Making calls to other methods is quite expensive on AVR. As These interrupt handlers are called
77
+ // many times a second, calling other methods should be kept to a minimum.
78
+
79
+ if (!_enabled) {
80
+ return;
81
+ }
82
+
83
+ static uint8_t receivedBit; // Contains "bit" currently receiving
84
+ static NewRemoteCode receivedCode; // Contains received code
85
+ static NewRemoteCode previousCode; // Contains previous received code
86
+ static uint8_t repeats = 0; // The number of times the an identical code is received in a row.
87
+ static unsigned long edgeTimeStamp[3] = {0, }; // Timestamp of edges
88
+ static unsigned int min1Period, max1Period, min5Period, max5Period;
89
+ static bool skip;
90
+
91
+ // Filter out too short pulses. This method works as a low pass filter.
92
+ edgeTimeStamp[1] = edgeTimeStamp[2];
93
+ edgeTimeStamp[2] = micros();
94
+
95
+ if (skip) {
96
+ skip = false;
97
+ return;
98
+ }
99
+
100
+ if (_state >= 0 && edgeTimeStamp[2]-edgeTimeStamp[1] < min1Period) {
101
+ // Last edge was too short.
102
+ // Skip this edge, and the next too.
103
+ skip = true;
104
+ return;
105
+ }
106
+
107
+ unsigned int duration = edgeTimeStamp[1] - edgeTimeStamp[0];
108
+ edgeTimeStamp[0] = edgeTimeStamp[1];
109
+
110
+ // Note that if state>=0, duration is always >= 1 period.
111
+
112
+ if (_state == -1) {
113
+ // wait for the long low part of a stop bit.
114
+ // Stopbit: 1T high, 40T low
115
+ // By default 1T is 260µs, but for maximum compatibility go as low as 120µs
116
+ if (duration > 4800) { // =40*120µs, minimal time between two edges before decoding starts.
117
+ // Sync signal received.. Preparing for decoding
118
+ repeats = 0;
119
+
120
+ receivedCode.period = duration / 40; // Measured signal is 40T, so 1T (period) is measured signal / 40.
121
+
122
+ // Allow for large error-margin. ElCheapo-hardware :(
123
+ min1Period = receivedCode.period * 3 / 10; // Lower limit for 1 period is 0.3 times measured period; high signals can "linger" a bit sometimes, making low signals quite short.
124
+ max1Period = receivedCode.period * 3; // Upper limit for 1 period is 3 times measured period
125
+ min5Period = receivedCode.period * 3; // Lower limit for 5 periods is 3 times measured period
126
+ max5Period = receivedCode.period * 8; // Upper limit for 5 periods is 8 times measured period
127
+ }
128
+ else {
129
+ return;
130
+ }
131
+ } else if (_state == 0) { // Verify start bit part 1 of 2
132
+ // Duration must be ~1T
133
+ if (duration > max1Period) {
134
+ RESET_STATE;
135
+ return;
136
+ }
137
+ // Start-bit passed. Do some clean-up.
138
+ receivedCode.address = receivedCode.unit = receivedCode.dimLevel = 0;
139
+ } else if (_state == 1) { // Verify start bit part 2 of 2
140
+ // Duration must be ~10.44T
141
+ if (duration < 7 * receivedCode.period || duration > 15 * receivedCode.period) {
142
+ RESET_STATE;
143
+ return;
144
+ }
145
+ } else if (_state < 148) { // state 146 is first edge of stop-sequence. All bits before that adhere to default protocol, with exception of dim-bit
146
+ receivedBit <<= 1;
147
+
148
+ // One bit consists out of 4 bit parts.
149
+ // bit part durations can ONLY be 1 or 5 periods.
150
+ if (duration <= max1Period) {
151
+ receivedBit &= 0b1110; // Clear LSB of receivedBit
152
+ } else if (duration >= min5Period && duration <= max5Period) {
153
+ receivedBit |= 0b1; // Set LSB of receivedBit
154
+ } else if (
155
+ // Check if duration matches the second part of stopbit (duration must be ~40T), and ...
156
+ (duration >= 20 * receivedCode.period && duration <= 80 * receivedCode.period) &&
157
+ // if first part op stopbit was a short signal (short signal yielded a 0 as second bit in receivedBit), and ...
158
+ ((receivedBit & 0b10) == 0b00) &&
159
+ // we are in a state in which a stopbit is actually valid, only then ...
160
+ (_state == 147 || _state == 131) ) {
161
+ // If a dim-level was present...
162
+ if (_state == 147) {
163
+ // mark received switch signal as signal-with-dim
164
+ receivedCode.dimLevelPresent = true;
165
+ }
166
+
167
+ // a valid signal was found!
168
+ if (
169
+ receivedCode.address != previousCode.address ||
170
+ receivedCode.unit != previousCode.unit ||
171
+ receivedCode.dimLevelPresent != previousCode.dimLevelPresent ||
172
+ receivedCode.dimLevel != previousCode.dimLevel ||
173
+ receivedCode.groupBit != previousCode.groupBit ||
174
+ receivedCode.switchType != previousCode.switchType
175
+ ) { // memcmp isn't deemed safe
176
+ repeats=0;
177
+ previousCode = receivedCode;
178
+ }
179
+
180
+ repeats++;
181
+
182
+ if (repeats>=_minRepeats) {
183
+ if (!_inCallback) {
184
+ _inCallback = true;
185
+ (_callback)(receivedCode);
186
+ _inCallback = false;
187
+ }
188
+ // Reset after callback.
189
+ RESET_STATE;
190
+ return;
191
+ }
192
+
193
+ // Reset for next round
194
+ _state=0; // no need to wait for another sync-bit!
195
+ return;
196
+ }
197
+ else { // Otherwise the entire sequence is invalid
198
+ RESET_STATE;
199
+ return;
200
+ }
201
+
202
+ if (_state % 4 == 1) { // Last bit part? Note: this is the short version of "if ( (_state-2) % 4 == 3 )"
203
+ // There are 3 valid options for receivedBit:
204
+ // 0, indicated by short short short long == B0001.
205
+ // 1, short long shot short == B0100.
206
+ // dim, short shot short shot == B0000.
207
+ // Everything else: inconsistent data, trash the whole sequence.
208
+
209
+
210
+ if (_state < 106) {
211
+ // States 2 - 105 are address bit states
212
+
213
+ receivedCode.address <<= 1;
214
+
215
+ // Decode bit. Only 4 LSB's of receivedBit are used; trim the rest.
216
+ switch (receivedBit & 0b1111) {
217
+ case 0b0001: // Bit "0" received.
218
+ // receivedCode.address |= 0; But let's not do that, as it is wasteful.
219
+ break;
220
+ case 0b0100: // Bit "1" received.
221
+ receivedCode.address |= 1;
222
+ break;
223
+ default: // Bit was invalid. Abort.
224
+ RESET_STATE;
225
+ return;
226
+ }
227
+ } else if (_state < 110) {
228
+ // States 106 - 109 are group bit states.
229
+ switch (receivedBit & 0b1111) {
230
+ case 0b0001: // Bit "0" received.
231
+ receivedCode.groupBit = false;
232
+ break;
233
+ case 0b0100: // Bit "1" received.
234
+ receivedCode.groupBit = true;
235
+ break;
236
+ default: // Bit was invalid. Abort.
237
+ RESET_STATE;
238
+ return;
239
+ }
240
+ } else if (_state < 114) {
241
+ // States 110 - 113 are switch bit states.
242
+ switch (receivedBit & 0b1111) {
243
+ case 0b0001: // Bit "0" received.
244
+ receivedCode.switchType = NewRemoteCode::off;
245
+ break;
246
+ case 0b0100: // Bit "1" received. Note: this might turn out to be a on_with_dim signal.
247
+ receivedCode.switchType = NewRemoteCode::on;
248
+ break;
249
+ case 0b0000: // Bit "dim" received.
250
+ receivedCode.switchType = NewRemoteCode::dim;
251
+ break;
252
+ default: // Bit was invalid. Abort.
253
+ RESET_STATE;
254
+ return;
255
+ }
256
+ } else if (_state < 130){
257
+ // States 114 - 129 are unit bit states.
258
+ receivedCode.unit <<= 1;
259
+
260
+ // Decode bit.
261
+ switch (receivedBit & 0b1111) {
262
+ case 0b0001: // Bit "0" received.
263
+ // receivedCode.unit |= 0; But let's not do that, as it is wasteful.
264
+ break;
265
+ case 0b0100: // Bit "1" received.
266
+ receivedCode.unit |= 1;
267
+ break;
268
+ default: // Bit was invalid. Abort.
269
+ RESET_STATE;
270
+ return;
271
+ }
272
+
273
+ } else if (_state < 146) {
274
+ // States 130 - 145 are dim bit states.
275
+ // Depending on hardware, these bits can be present, even if switchType is NewRemoteCode::on or NewRemoteCode::off
276
+
277
+ receivedCode.dimLevel <<= 1;
278
+
279
+ // Decode bit.
280
+ switch (receivedBit & 0b1111) {
281
+ case 0b0001: // Bit "0" received.
282
+ // receivedCode.dimLevel |= 0; But let's not do that, as it is wasteful.
283
+ break;
284
+ case 0b0100: // Bit "1" received.
285
+ receivedCode.dimLevel |= 1;
286
+ break;
287
+ default: // Bit was invalid. Abort.
288
+ RESET_STATE;
289
+ return;
290
+ }
291
+ }
292
+ }
293
+ }
294
+
295
+ _state++;
296
+ return;
297
+ }
298
+
299
+ bool NewRemoteReceiver::isReceiving(int waitMillis) {
300
+ unsigned long startTime=millis();
301
+
302
+ int waited; // Signed int!
303
+ do {
304
+ if (_state >= 34) { // Abort if a significant part of a code (start pulse + 8 bits) has been received
305
+ return true;
306
+ }
307
+ waited = (millis()-startTime);
308
+ } while(waited>=0 && waited <= waitMillis); // Yes, clock wraps every 50 days. And then you'd have to wait for a looooong time.
309
+
310
+ return false;
311
+ }
@@ -0,0 +1,108 @@
1
+ /*
2
+ * NewRemoteSwitch library v1.2.0 (20140128) made by Randy Simons http://randysimons.nl/
3
+ *
4
+ * License: GPLv3. See license.txt
5
+ */
6
+
7
+ #ifndef NewRemoteReceiver_h
8
+ #define NewRemoteReceiver_h
9
+
10
+ #include <cstdint>
11
+ #include <wiringPi.h>
12
+
13
+ struct NewRemoteCode {
14
+ enum SwitchType {
15
+ off = 0,
16
+ on = 1,
17
+ dim = 2
18
+ };
19
+
20
+ unsigned int period; // Detected duration in microseconds of 1T in the received signal
21
+ unsigned long address; // Address of received code. [0..2^26-1]
22
+ bool groupBit; // Group bit set or not
23
+ SwitchType switchType; // off, on, dim, on_with_dim.
24
+ uint8_t unit; // Unit code of received code [0..15]
25
+ bool dimLevelPresent; // Dim level present or not. Will be available for switchType dim, but might be available for on or off too, depending on remote.
26
+ uint8_t dimLevel; // Dim level [0..15]. Will be available if switchType is dim, on_with_dim or off_with_dim.
27
+ };
28
+
29
+ typedef void (*NewRemoteReceiverCallBack)(NewRemoteCode);
30
+
31
+ /**
32
+ * See RemoteSwitch for introduction.
33
+ *
34
+ * NewRemoteReceiver decodes the signal received from a 433MHz-receiver, like the "KlikAanKlikUit"-system
35
+ * as well as the signal sent by the RemoteSwtich class. When a correct signal is received,
36
+ * a user-defined callback function is called.
37
+ *
38
+ * Note that in the callback function, the interrupts are still disabled. You can enabled them, if needed.
39
+ * A call to the callback must be finished before NewRemoteReceiver will call the callback function again, thus
40
+ * there is no re-entrant problem.
41
+ *
42
+ * When sending your own code using NewRemoteSwich, disable() the receiver first.
43
+ *
44
+ * This is a pure static class, for simplicity and to limit memory-use.
45
+ */
46
+
47
+ class NewRemoteReceiver {
48
+ public:
49
+ /**
50
+ * Initializes the decoder.
51
+ *
52
+ * If interrupt >= 0, init will register pin <interrupt> to this library.
53
+ * If interrupt < 0, no interrupt is registered. In that case, you have to call interruptHandler()
54
+ * yourself whenever the output of the receiver changes, or you can use InterruptChain.
55
+ *
56
+ * @param interrupt The interrupt as is used by Arduino's attachInterrupt function. See attachInterrupt for details.
57
+ If < 0, you must call interruptHandler() yourself.
58
+ * @param minRepeats The number of times the same code must be received in a row before the callback is calles
59
+ * @param callback Pointer to a callback function, with signature void (*func)(NewRemoteCode)
60
+ */
61
+ static void init(int8_t interrupt, uint8_t minRepeats, NewRemoteReceiverCallBack callback);
62
+
63
+ /**
64
+ * Enable decoding. No need to call enable() after init().
65
+ */
66
+ static void enable();
67
+
68
+ /**
69
+ * Disable decoding. You can re-enable decoding by calling enable();
70
+ */
71
+ static void disable();
72
+
73
+ /**
74
+ * Deinitializes the decoder. Disables decoding and detaches the interrupt handler. If you want to
75
+ * re-enable decoding, call init() again.
76
+ */
77
+ static void deinit();
78
+
79
+ /**
80
+ * Tells wether a signal is being received. If a compatible signal is detected within the time out, isReceiving returns true.
81
+ * Since it makes no sense to transmit while another transmitter is active, it's best to wait for isReceiving() to false.
82
+ * By default it waits for 150ms, in which a (relative slow) KaKu signal can be broadcasted three times.
83
+ *
84
+ * Note: isReceiving() depends on interrupts enabled. Thus, when disabled()'ed, or when interrupts are disabled (as is
85
+ * the case in the callback), isReceiving() will not work properly.
86
+ *
87
+ * @param waitMillis number of milliseconds to monitor for signal.
88
+ * @return bool If after waitMillis no signal was being processed, returns false. If before expiration a signal was being processed, returns true.
89
+ */
90
+ static bool isReceiving(int waitMillis = 150);
91
+
92
+ /**
93
+ * Called every time the signal level changes (high to low or vice versa). Usually called by interrupt.
94
+ */
95
+ static void interruptHandler();
96
+
97
+ private:
98
+
99
+ static int8_t _interrupt; // Radio input interrupt
100
+ volatile static short _state; // State of decoding process.
101
+ static uint8_t _minRepeats;
102
+ static NewRemoteReceiverCallBack _callback;
103
+ static bool _inCallback; // When true, the callback function is being executed; prevents re-entrance.
104
+ static bool _enabled; // If true, monitoring and decoding is enabled. If false, interruptHandler will return immediately.
105
+
106
+ };
107
+
108
+ #endif