new_remote 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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