hacklet 0.5.0 → 0.5.1
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 +40 -19
- data/hacklet.gemspec +1 -0
- data/lib/hacklet/dongle.rb +21 -1
- data/lib/hacklet/messages.rb +59 -21
- data/lib/hacklet/version.rb +1 -1
- data/spec/hacklet/dongle_spec.rb +8 -2
- data/spec/spec_helper.rb +1 -0
- metadata +18 -2
data/README.md
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
# Hacklet [](https://travis-ci.org/mcolyer/hacklet)
|
2
2
|
|
3
|
-
A library, written in Ruby, for controlling the [Modlet] (smart) outlet.
|
3
|
+
A library, written in Ruby, for controlling the [Modlet][modlet] (smart) outlet.
|
4
4
|
|
5
5
|
If you haven't heard of the Modlet before, it's a smart outlet cover
|
6
|
-
which allows you to convert any outlet
|
6
|
+
which allows you to convert any outlet in your house into a smart
|
7
7
|
outlet. This means that you can control whether a plug is on or off and
|
8
8
|
you can also determine how much energy it's using with a sampling
|
9
9
|
frequency of 10 seconds.
|
10
10
|
|
11
11
|
There are alot of other similar products but this is the first one that
|
12
|
-
I've
|
13
|
-
the
|
12
|
+
I've seen that [costs $50][amazon] and includes control as well as
|
13
|
+
monitoring of the both sockets independently.
|
14
14
|
|
15
15
|
## Why
|
16
16
|
|
@@ -26,25 +26,46 @@ control and availability of the data.
|
|
26
26
|
|
27
27
|
## Getting Started
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
which Modlet and socket you'd like to read or control.
|
29
|
+
```shell
|
30
|
+
gem install hacklet
|
32
31
|
|
33
|
-
|
34
|
-
bundle install
|
32
|
+
# Load the USB-to-serial kernel driver, this only works with Linux currently
|
35
33
|
sudo modprobe ftdi_sio vendor=0x0403 product=0x8c81
|
36
|
-
|
34
|
+
|
35
|
+
# Add the device to the network, keep a copy of the network ids (ie 0xDEED)
|
36
|
+
hacklet commission
|
37
|
+
|
38
|
+
# Get the samples from the top socket on the device registered as 0xDEED
|
39
|
+
hacklet read -n 0xDEED -s 0
|
40
|
+
|
41
|
+
# Turn the top socket off
|
42
|
+
hacklet off -n 0xDEED -s 0
|
43
|
+
|
44
|
+
# And back on again
|
45
|
+
hacklet on -n 0xDEED -s 0
|
37
46
|
```
|
38
47
|
|
39
|
-
##
|
48
|
+
## Contributing
|
49
|
+
|
50
|
+
All contributions are welcome (bug reports, bug fixes, documentation or
|
51
|
+
new features)! If you're looking for something to do check the [issue]
|
52
|
+
list and see if there's something already there. If you've got a new
|
53
|
+
idea, feel free to create an issue for discussion.
|
54
|
+
|
55
|
+
To get a general understanding of how things work, checkout the
|
56
|
+
[developer wiki]
|
57
|
+
|
58
|
+
### Getting Started
|
40
59
|
|
41
|
-
*
|
42
|
-
*
|
43
|
-
*
|
44
|
-
*
|
45
|
-
*
|
46
|
-
*
|
47
|
-
*
|
60
|
+
* Checkout the repository
|
61
|
+
* Install dependencies `bundle install`
|
62
|
+
* Create a feature branch `git checkout -b short-name`
|
63
|
+
* Run tests `bundle exec rake`
|
64
|
+
* Write your feature (and tests)
|
65
|
+
* Run tests `bundle exec rake`
|
66
|
+
* Create a pull request
|
48
67
|
|
49
|
-
[
|
68
|
+
[modlet]: http://themodlet.com
|
50
69
|
[amazon]: http://www.amazon.com/ThinkEco-TE1010-Modlet-Starter-White/dp/B00AAT43OA/
|
70
|
+
[issue]: https://github.com/mcolyer/hacklet/issues
|
71
|
+
[developer wiki]: https://github.com/mcolyer/hacklet/wiki
|
data/hacklet.gemspec
CHANGED
data/lib/hacklet/dongle.rb
CHANGED
@@ -38,16 +38,18 @@ module Hacklet
|
|
38
38
|
def commission
|
39
39
|
require_session
|
40
40
|
|
41
|
+
response = nil
|
41
42
|
begin
|
42
43
|
unlock_network
|
43
44
|
Timeout.timeout(30) do
|
45
|
+
@logger.info("Listening for devices ...")
|
44
46
|
loop do
|
45
|
-
@logger.info("Listening for devices ...")
|
46
47
|
buffer = receive(4)
|
47
48
|
buffer += receive(buffer.bytes.to_a[3]+1)
|
48
49
|
if buffer.bytes.to_a[1] == 0xa0
|
49
50
|
response = BroadcastResponse.read(buffer)
|
50
51
|
@logger.info("Found device 0x%x on network 0x%x" % [response.device_id, response.network_id])
|
52
|
+
break
|
51
53
|
end
|
52
54
|
end
|
53
55
|
end
|
@@ -55,6 +57,8 @@ module Hacklet
|
|
55
57
|
ensure
|
56
58
|
lock_network
|
57
59
|
end
|
60
|
+
|
61
|
+
update_time(response.network_id) if response
|
58
62
|
end
|
59
63
|
|
60
64
|
# Public: Selects the network.
|
@@ -159,6 +163,22 @@ module Hacklet
|
|
159
163
|
BootConfirmResponse.read(receive(6))
|
160
164
|
end
|
161
165
|
|
166
|
+
# Private: Updates the time of a device.
|
167
|
+
#
|
168
|
+
# This must be executed within an open session. I'm guessing it selects the
|
169
|
+
# network.
|
170
|
+
#
|
171
|
+
# network_id - 2 byte identified for the network.
|
172
|
+
#
|
173
|
+
# Returns nothing.
|
174
|
+
def update_time(network_id)
|
175
|
+
require_session
|
176
|
+
|
177
|
+
transmit(UpdateTimeRequest.new(:network_id => network_id))
|
178
|
+
UpdateTimeAckResponse.read(receive(6))
|
179
|
+
UpdateTimeResponse.read(receive(8))
|
180
|
+
end
|
181
|
+
|
162
182
|
# Private: Initializes the serial port
|
163
183
|
#
|
164
184
|
# port - the String to the device to open as a serial port.
|
data/lib/hacklet/messages.rb
CHANGED
@@ -49,16 +49,16 @@ module Hacklet
|
|
49
49
|
endian :big
|
50
50
|
|
51
51
|
uint8 :header, :check_value => lambda { value == 0x02 }
|
52
|
-
uint16
|
52
|
+
uint16 :command, :check_value => lambda { value == 0xA013 }
|
53
53
|
uint8 :payload_length, :check_value => lambda { value == 11 }
|
54
54
|
|
55
55
|
uint16 :network_id
|
56
56
|
uint64 :device_id
|
57
57
|
|
58
58
|
# TODO: Not sure why this is here.
|
59
|
-
uint8
|
59
|
+
uint8 :data
|
60
60
|
|
61
|
-
uint8
|
61
|
+
uint8 :checksum, :check_value => lambda { calculate_checksum == checksum }
|
62
62
|
end
|
63
63
|
|
64
64
|
class LockResponse < Message
|
@@ -68,9 +68,34 @@ module Hacklet
|
|
68
68
|
uint16 :command, :check_value => lambda { value == 0xA0F9 }
|
69
69
|
uint8 :payload_length, :check_value => lambda { value == 1 }
|
70
70
|
|
71
|
-
|
71
|
+
uint8 :data, :check_value => lambda { value == 0x00 }
|
72
72
|
|
73
|
-
uint8
|
73
|
+
uint8 :checksum, :check_value => lambda { calculate_checksum == checksum }
|
74
|
+
end
|
75
|
+
|
76
|
+
class UpdateTimeAckResponse < Message
|
77
|
+
endian :big
|
78
|
+
|
79
|
+
uint8 :header, :check_value => lambda { value == 0x02 }
|
80
|
+
uint16 :command, :check_value => lambda { value == 0x4022 }
|
81
|
+
uint8 :payload_length, :check_value => lambda { value == 1 }
|
82
|
+
|
83
|
+
uint8 :data, :check_value => lambda { value == 0x00 }
|
84
|
+
|
85
|
+
uint8 :checksum, :check_value => lambda { calculate_checksum == checksum }
|
86
|
+
end
|
87
|
+
|
88
|
+
class UpdateTimeResponse < Message
|
89
|
+
endian :big
|
90
|
+
|
91
|
+
uint8 :header, :check_value => lambda { value == 0x02 }
|
92
|
+
uint16 :command, :check_value => lambda { value == 0x40a2 }
|
93
|
+
uint8 :payload_length, :check_value => lambda { value == 3 }
|
94
|
+
|
95
|
+
uint16 :network_id
|
96
|
+
uint8 :data, :check_value => lambda { value == 0x00 }
|
97
|
+
|
98
|
+
uint8 :checksum, :check_value => lambda { calculate_checksum == checksum }
|
74
99
|
end
|
75
100
|
|
76
101
|
class HandshakeResponse < Message
|
@@ -80,9 +105,9 @@ module Hacklet
|
|
80
105
|
uint16 :command, :check_value => lambda { value == 0x4003 }
|
81
106
|
uint8 :payload_length, :check_value => lambda { value == 1 }
|
82
107
|
|
83
|
-
|
108
|
+
uint8 :data, :check_value => lambda { value == 0x00 }
|
84
109
|
|
85
|
-
uint8
|
110
|
+
uint8 :checksum, :check_value => lambda { calculate_checksum == checksum }
|
86
111
|
end
|
87
112
|
|
88
113
|
class AckResponse < Message
|
@@ -92,17 +117,17 @@ module Hacklet
|
|
92
117
|
uint16 :command, :check_value => lambda { value == 0x4024 }
|
93
118
|
uint8 :payload_length, :check_value => lambda { value == 1 }
|
94
119
|
|
95
|
-
|
120
|
+
uint8 :data, :check_value => lambda { value == 0x00 }
|
96
121
|
|
97
|
-
uint8
|
122
|
+
uint8 :checksum, :check_value => lambda { calculate_checksum == checksum }
|
98
123
|
end
|
99
124
|
|
100
125
|
class SamplesResponse < Message
|
101
126
|
endian :big
|
102
127
|
|
103
|
-
uint8
|
104
|
-
uint16
|
105
|
-
uint8
|
128
|
+
uint8 :header, :check_value => lambda { value == 0x02 }
|
129
|
+
uint16 :command, :check_value => lambda { value == 0x40A4 }
|
130
|
+
uint8 :payload_length
|
106
131
|
|
107
132
|
uint16 :network_id
|
108
133
|
uint16 :channel_id
|
@@ -130,9 +155,9 @@ module Hacklet
|
|
130
155
|
uint16 :command, :check_value => lambda { value == 0x4023 }
|
131
156
|
uint8 :payload_length, :check_value => lambda { value == 1 }
|
132
157
|
|
133
|
-
|
158
|
+
uint8 :data, :check_value => lambda { value == 0x00 }
|
134
159
|
|
135
|
-
uint8
|
160
|
+
uint8 :checksum, :check_value => lambda { calculate_checksum == checksum }
|
136
161
|
end
|
137
162
|
|
138
163
|
class BootRequest < Message
|
@@ -142,7 +167,7 @@ module Hacklet
|
|
142
167
|
uint16 :command, :initial_value => 0x4004
|
143
168
|
uint8 :payload_length
|
144
169
|
|
145
|
-
uint8
|
170
|
+
uint8 :checksum, :value => :calculate_checksum
|
146
171
|
end
|
147
172
|
|
148
173
|
class BootConfirmRequest < Message
|
@@ -152,7 +177,7 @@ module Hacklet
|
|
152
177
|
uint16 :command, :initial_value => 0x4000
|
153
178
|
uint8 :payload_length
|
154
179
|
|
155
|
-
uint8
|
180
|
+
uint8 :checksum, :value => :calculate_checksum
|
156
181
|
end
|
157
182
|
|
158
183
|
class UnlockRequest < Message
|
@@ -165,7 +190,7 @@ module Hacklet
|
|
165
190
|
# TODO: What is this?
|
166
191
|
uint32 :data, :initial_value => 0xFCFF9001
|
167
192
|
|
168
|
-
uint8
|
193
|
+
uint8 :checksum, :value => :calculate_checksum
|
169
194
|
end
|
170
195
|
|
171
196
|
class LockRequest < Message
|
@@ -178,7 +203,20 @@ module Hacklet
|
|
178
203
|
# TODO: What is this?
|
179
204
|
uint32 :data, :initial_value => 0xFCFF0001
|
180
205
|
|
181
|
-
uint8
|
206
|
+
uint8 :checksum, :value => :calculate_checksum
|
207
|
+
end
|
208
|
+
|
209
|
+
class UpdateTimeRequest < Message
|
210
|
+
endian :big
|
211
|
+
|
212
|
+
uint8 :header, :initial_value => 0x02
|
213
|
+
uint16 :command, :initial_value => 0x4022
|
214
|
+
uint8 :payload_length, :initial_value => 6
|
215
|
+
|
216
|
+
uint16 :network_id
|
217
|
+
uint32le :time, :initial_value => lambda { Time.now.to_i }
|
218
|
+
|
219
|
+
uint8 :checksum, :value => :calculate_checksum
|
182
220
|
end
|
183
221
|
|
184
222
|
class HandshakeRequest < Message
|
@@ -192,7 +230,7 @@ module Hacklet
|
|
192
230
|
# TODO: What is this?
|
193
231
|
uint16 :data, :initial_value => 0x0500
|
194
232
|
|
195
|
-
uint8
|
233
|
+
uint8 :checksum, :value => :calculate_checksum
|
196
234
|
end
|
197
235
|
|
198
236
|
class SamplesRequest < Message
|
@@ -207,7 +245,7 @@ module Hacklet
|
|
207
245
|
# TODO: What is this?
|
208
246
|
uint16 :data, :initial_value => 0x0A00
|
209
247
|
|
210
|
-
uint8
|
248
|
+
uint8 :checksum, :value => :calculate_checksum
|
211
249
|
end
|
212
250
|
|
213
251
|
class ScheduleRequest < Message
|
@@ -221,7 +259,7 @@ module Hacklet
|
|
221
259
|
uint8 :channel_id
|
222
260
|
array :schedule, :type => [:uint8], :initial_length => 56
|
223
261
|
|
224
|
-
uint8
|
262
|
+
uint8 :checksum, :value => :calculate_checksum
|
225
263
|
|
226
264
|
def always_off!
|
227
265
|
bitmap = [0x7f]*56
|
data/lib/hacklet/version.rb
CHANGED
data/spec/hacklet/dongle_spec.rb
CHANGED
@@ -48,12 +48,16 @@ describe Hacklet::Dongle do
|
|
48
48
|
serial_port.should_receive(:read).and_return([0x02, 0xa0, 0x13, 0x0b].pack('c'*4))
|
49
49
|
serial_port.should_receive(:read).and_return([0x66, 0xad, 0xcc, 0x1f, 0x10, 0x00,
|
50
50
|
0x00, 0x58, 0x4f, 0x80, 0x8e, 0xa9].pack('c'*12))
|
51
|
-
serial_port.should_receive(:read).and_raise(Timeout::Error)
|
52
51
|
|
53
52
|
# Lock Network
|
54
53
|
serial_port.should_receive(:write).with([0x02, 0xA2, 0x36, 0x04, 0xFC, 0xFF, 0x00, 0x01, 0x92].pack('c'*9))
|
55
54
|
serial_port.should_receive(:read).and_return([0x02, 0xA0, 0xF9, 0x01, 0x00, 0x58].pack('c'*6))
|
56
55
|
|
56
|
+
# Update Time
|
57
|
+
serial_port.should_receive(:write).and_return([0x02, 0x40, 0x22, 0x06, 0x66, 0xAD, 0xdb, 0xb4, 0xa8, 0x51, 0x0b].pack('c'*11))
|
58
|
+
serial_port.should_receive(:read).and_return([0x02, 0x40, 0x22, 0x01, 0x00, 0x63].pack('c'*6))
|
59
|
+
serial_port.should_receive(:read).and_return([0x02, 0x40, 0xA2, 0x03, 0x66, 0xAD, 0x00, 0x2a].pack('c'*8))
|
60
|
+
|
57
61
|
serial_port.should_receive(:close)
|
58
62
|
serial_port.stub!(:closed?).and_return(false)
|
59
63
|
subject.should_receive(:open_serial_port).and_return(serial_port)
|
@@ -61,7 +65,9 @@ describe Hacklet::Dongle do
|
|
61
65
|
subject.should_receive(:boot_confirm)
|
62
66
|
|
63
67
|
subject.open_session do |session|
|
64
|
-
|
68
|
+
Timecop.freeze(Time.at(0x51a8b4db)) do
|
69
|
+
session.commission
|
70
|
+
end
|
65
71
|
end
|
66
72
|
end
|
67
73
|
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hacklet
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.5.
|
4
|
+
version: 0.5.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-
|
12
|
+
date: 2013-06-02 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: serialport
|
@@ -91,6 +91,22 @@ dependencies:
|
|
91
91
|
- - ! '>='
|
92
92
|
- !ruby/object:Gem::Version
|
93
93
|
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: timecop
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
94
110
|
description: An Open Source client for the Modlet (smart) outlet
|
95
111
|
email:
|
96
112
|
- matt@colyer.name
|