ruby_home 0.2.1 → 0.2.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/tests.yml +41 -0
- data/.gitignore +2 -2
- data/.rubocop.yml +2 -9
- data/.standard.yml +3 -0
- data/CHANGELOG.md +18 -0
- data/Gemfile +2 -2
- data/README.md +40 -77
- data/Rakefile +4 -4
- data/examples/air_purifier.rb +66 -0
- data/examples/air_quality_sensor.rb +119 -0
- data/examples/battery_service.rb +58 -0
- data/examples/carbon_dioxide_sensor.rb +78 -0
- data/examples/carbon_monoxide_sensor.rb +78 -0
- data/examples/contact_sensor.rb +66 -0
- data/examples/door.rb +48 -0
- data/examples/fan.rb +30 -0
- data/examples/fan_v2.rb +68 -0
- data/examples/garage_door_opener.rb +71 -0
- data/examples/heater_cooler.rb +92 -0
- data/examples/humidifier_dehumidifier.rb +88 -0
- data/examples/humidity_sensor.rb +62 -0
- data/examples/leak_sensor.rb +66 -0
- data/examples/light_sensor.rb +64 -0
- data/examples/lightbulb.rb +31 -0
- data/examples/lock_mechanism.rb +34 -0
- data/examples/motion_sensor.rb +66 -0
- data/examples/occupancy_sensor.rb +66 -0
- data/examples/outlet.rb +29 -0
- data/examples/security_system.rb +53 -0
- data/examples/smoke_sensor.rb +66 -0
- data/examples/switch.rb +16 -0
- data/examples/television.rb +73 -0
- data/examples/temperature_sensor.rb +62 -0
- data/examples/thermostat.rb +113 -0
- data/examples/window.rb +48 -0
- data/examples/window_covering.rb +72 -0
- data/lib/ruby_home.rb +21 -22
- data/lib/ruby_home/accessory.rb +1 -1
- data/lib/ruby_home/accessory_collection.rb +6 -6
- data/lib/ruby_home/accessory_info.rb +18 -18
- data/lib/ruby_home/characteristic.rb +13 -7
- data/lib/ruby_home/characteristic_collection.rb +13 -13
- data/lib/ruby_home/config/categories.yml +39 -0
- data/lib/ruby_home/config/characteristics.yml +190 -319
- data/lib/ruby_home/config/manual_characteristics.yml +278 -0
- data/lib/ruby_home/config/manual_services.yml +45 -0
- data/lib/ruby_home/config/services.yml +338 -304
- data/lib/ruby_home/configuration.rb +21 -2
- data/lib/ruby_home/device_id.rb +3 -3
- data/lib/ruby_home/dns/service.rb +4 -6
- data/lib/ruby_home/dns/text_record.rb +10 -10
- data/lib/ruby_home/errors.rb +1 -0
- data/lib/ruby_home/factories/characteristic_factory.rb +38 -37
- data/lib/ruby_home/factories/service_factory.rb +72 -72
- data/lib/ruby_home/factories/templates/characteristic_template.rb +6 -6
- data/lib/ruby_home/factories/templates/service_template.rb +12 -11
- data/lib/ruby_home/hap/crypto/chacha20poly1305.rb +2 -2
- data/lib/ruby_home/hap/crypto/hkdf.rb +4 -4
- data/lib/ruby_home/hap/crypto/session_key.rb +7 -7
- data/lib/ruby_home/hap/decrypter.rb +8 -8
- data/lib/ruby_home/hap/encrypter.rb +5 -6
- data/lib/ruby_home/hap/ev_response.rb +17 -17
- data/lib/ruby_home/hap/hap_request.rb +1 -1
- data/lib/ruby_home/hap/server.rb +4 -4
- data/lib/ruby_home/hap/server_handler.rb +8 -10
- data/lib/ruby_home/hap/session.rb +22 -22
- data/lib/ruby_home/hap/values/base_value.rb +3 -3
- data/lib/ruby_home/hap/values/bool_value.rb +4 -4
- data/lib/ruby_home/hap/values/float_value.rb +4 -4
- data/lib/ruby_home/hap/values/identify_value.rb +1 -1
- data/lib/ruby_home/hap/values/int32_value.rb +4 -4
- data/lib/ruby_home/hap/values/null_value.rb +1 -1
- data/lib/ruby_home/hap/values/string_value.rb +11 -11
- data/lib/ruby_home/hap/values/uint32_value.rb +5 -5
- data/lib/ruby_home/hap/values/uint8_value.rb +14 -14
- data/lib/ruby_home/hex_helper.rb +3 -3
- data/lib/ruby_home/http/application.rb +7 -7
- data/lib/ruby_home/http/controllers/accessories_controller.rb +3 -3
- data/lib/ruby_home/http/controllers/application_controller.rb +6 -6
- data/lib/ruby_home/http/controllers/characteristics_controller.rb +29 -29
- data/lib/ruby_home/http/controllers/identify_controller.rb +10 -10
- data/lib/ruby_home/http/controllers/pair_setups_controller.rb +19 -19
- data/lib/ruby_home/http/controllers/pair_verifies_controller.rb +9 -9
- data/lib/ruby_home/http/controllers/pairings_controller.rb +6 -6
- data/lib/ruby_home/http/serializers/accessory_serializer.rb +5 -5
- data/lib/ruby_home/http/serializers/characteristic_serializer.rb +27 -19
- data/lib/ruby_home/http/serializers/characteristic_value_serializer.rb +5 -5
- data/lib/ruby_home/http/serializers/object_serializer.rb +1 -1
- data/lib/ruby_home/http/serializers/service_serializer.rb +19 -9
- data/lib/ruby_home/http/serializers/uuid_helper.rb +2 -2
- data/lib/ruby_home/http/services/session_notifier.rb +8 -8
- data/lib/ruby_home/http/services/start_srp_service.rb +3 -3
- data/lib/ruby_home/http/services/verify_finish_service.rb +22 -22
- data/lib/ruby_home/http/services/verify_srp_service.rb +22 -22
- data/lib/ruby_home/identifier_cache.rb +4 -5
- data/lib/ruby_home/password.rb +14 -14
- data/lib/ruby_home/persistable.rb +19 -12
- data/lib/ruby_home/service.rb +5 -2
- data/lib/ruby_home/service_collection.rb +1 -1
- data/lib/ruby_home/version.rb +1 -1
- data/rubyhome.gemspec +31 -31
- data/sbin/characteristic_generator.rb +22 -24
- data/sbin/service_generator.rb +38 -19
- metadata +85 -44
- data/.travis.yml +0 -31
@@ -1,16 +1,22 @@
|
|
1
1
|
module RubyHome
|
2
|
+
class UnknownCategoriyIdentifierError < StandardError; end
|
3
|
+
|
2
4
|
class Configuration
|
3
5
|
extend Forwardable
|
4
6
|
|
7
|
+
CATEGORIES_FILEPATH = (File.dirname(__FILE__) + "/config/categories.yml").freeze
|
8
|
+
CATEGORIES = YAML.load_file(CATEGORIES_FILEPATH).freeze
|
9
|
+
|
5
10
|
def initialize(accessory_info = AccessoryInfo.instance)
|
6
11
|
@accessory_info = accessory_info
|
7
12
|
end
|
8
13
|
|
9
|
-
DEFAULT_NAME = -
|
10
|
-
DEFAULT_HOST = -
|
14
|
+
DEFAULT_NAME = -"RubyHome"
|
15
|
+
DEFAULT_HOST = -"0.0.0.0"
|
11
16
|
DEFAULT_PORT = 4567
|
12
17
|
DEFAULT_MODEL_NAME = DEFAULT_NAME
|
13
18
|
DEFAULT_DISCOVERY_NAME = DEFAULT_NAME
|
19
|
+
DEFAULT_CATEGORY_IDENTIFIER = 2
|
14
20
|
|
15
21
|
def discovery_name
|
16
22
|
@discovery_name || DEFAULT_DISCOVERY_NAME
|
@@ -28,6 +34,19 @@ module RubyHome
|
|
28
34
|
@host || DEFAULT_HOST
|
29
35
|
end
|
30
36
|
|
37
|
+
def category_identifier
|
38
|
+
@category_identifier || DEFAULT_CATEGORY_IDENTIFIER
|
39
|
+
end
|
40
|
+
|
41
|
+
def category_identifier=(value)
|
42
|
+
if value.is_a?(Symbol)
|
43
|
+
raise UnknownCategoriyIdentifierError unless CATEGORIES.include?(value)
|
44
|
+
@category_identifier = CATEGORIES[value]
|
45
|
+
else
|
46
|
+
@category_identifier = value.to_i
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
31
50
|
attr_writer :discovery_name, :model_name, :host, :port
|
32
51
|
|
33
52
|
def_delegators :@accessory_info, :password, :password=
|
data/lib/ruby_home/device_id.rb
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
module RubyHome
|
2
2
|
module DeviceID
|
3
3
|
class << self
|
4
|
-
DELIMITER =
|
4
|
+
DELIMITER = ":"
|
5
5
|
|
6
6
|
# Device ID of the accessory must be a unique random number generated at every
|
7
7
|
# factory reset and must persist across reboots
|
8
8
|
|
9
|
-
def generate(digits=12)
|
9
|
+
def generate(digits = 12)
|
10
10
|
random_hexadecimals(digits)
|
11
11
|
.map(&:upcase)
|
12
12
|
.each_slice(2)
|
@@ -18,7 +18,7 @@ module RubyHome
|
|
18
18
|
rand(15).to_s(16)
|
19
19
|
end
|
20
20
|
|
21
|
-
def random_hexadecimals(digits=12)
|
21
|
+
def random_hexadecimals(digits = 12)
|
22
22
|
digits
|
23
23
|
.times
|
24
24
|
.map { random_hexadecimal }
|
@@ -1,12 +1,12 @@
|
|
1
1
|
module RubyHome
|
2
2
|
module DNS
|
3
3
|
class Service
|
4
|
-
def initialize(configuration:
|
4
|
+
def initialize(configuration:)
|
5
5
|
@configuration = configuration
|
6
6
|
end
|
7
7
|
|
8
8
|
def update
|
9
|
-
return if RbConfig::CONFIG[
|
9
|
+
return if /linux/.match?(RbConfig::CONFIG["target_os"])
|
10
10
|
|
11
11
|
dnssd_service.add_record(DNSSD::Record::TXT, text_record.encode)
|
12
12
|
end
|
@@ -20,9 +20,7 @@ module RubyHome
|
|
20
20
|
attr_reader :configuration
|
21
21
|
|
22
22
|
def dnssd_service
|
23
|
-
@_dnssd_service ||=
|
24
|
-
DNSSD::Service.register(name, type, nil, port, host, text_record)
|
25
|
-
end
|
23
|
+
@_dnssd_service ||= DNSSD::Service.register(name, type, nil, port, host, text_record)
|
26
24
|
end
|
27
25
|
|
28
26
|
def name
|
@@ -30,7 +28,7 @@ module RubyHome
|
|
30
28
|
end
|
31
29
|
|
32
30
|
def type
|
33
|
-
-
|
31
|
+
-"_hap._tcp"
|
34
32
|
end
|
35
33
|
|
36
34
|
def port
|
@@ -12,14 +12,14 @@ module RubyHome
|
|
12
12
|
|
13
13
|
def to_hash
|
14
14
|
{
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
15
|
+
"c#" => current_configuration_number,
|
16
|
+
"ci" => accessory_category_identifier,
|
17
|
+
"ff" => feature_flags,
|
18
|
+
"id" => device_id,
|
19
|
+
"md" => model_name,
|
20
|
+
"pv" => protocol_version,
|
21
|
+
"s#" => current_state_number,
|
22
|
+
"sf" => status_flags
|
23
23
|
}
|
24
24
|
end
|
25
25
|
|
@@ -38,7 +38,7 @@ module RubyHome
|
|
38
38
|
# 1-65535.
|
39
39
|
|
40
40
|
def accessory_category_identifier
|
41
|
-
|
41
|
+
@configuration.category_identifier
|
42
42
|
end
|
43
43
|
|
44
44
|
# Feature flags (e.g. "0x3" for bits 0 and 1). Required if non-zero.
|
@@ -52,7 +52,7 @@ module RubyHome
|
|
52
52
|
|
53
53
|
STATUS_FLAGS = {
|
54
54
|
PAIRED: 0,
|
55
|
-
NOT_PAIRED: 1
|
55
|
+
NOT_PAIRED: 1
|
56
56
|
}.freeze
|
57
57
|
|
58
58
|
def status_flags
|
data/lib/ruby_home/errors.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
module RubyHome
|
2
2
|
class CharacteristicFactory
|
3
|
-
def self.create(characteristic_name, service
|
3
|
+
def self.create(characteristic_name, service:, subtype: "default", value: nil)
|
4
4
|
new(
|
5
5
|
characteristic_name: characteristic_name,
|
6
6
|
service: service,
|
@@ -15,6 +15,7 @@ module RubyHome
|
|
15
15
|
format: template.format,
|
16
16
|
name: characteristic_name,
|
17
17
|
properties: template.properties,
|
18
|
+
constraints: template.constraints,
|
18
19
|
service: service,
|
19
20
|
unit: template.unit,
|
20
21
|
uuid: template.uuid,
|
@@ -35,48 +36,48 @@ module RubyHome
|
|
35
36
|
|
36
37
|
private
|
37
38
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
39
|
+
def initialize(characteristic_name:, service:, subtype:, value:)
|
40
|
+
@characteristic_name = characteristic_name.to_sym
|
41
|
+
@service = service
|
42
|
+
@subtype = subtype
|
43
|
+
@requested_value = value
|
44
|
+
end
|
44
45
|
|
45
|
-
|
46
|
+
attr_reader :service, :characteristic_name, :subtype
|
46
47
|
|
47
|
-
|
48
|
-
|
49
|
-
|
48
|
+
def accessory
|
49
|
+
service.accessory
|
50
|
+
end
|
50
51
|
|
51
|
-
|
52
|
-
|
53
|
-
|
52
|
+
def template
|
53
|
+
@template ||= CharacteristicTemplate.find_by(name: characteristic_name)
|
54
|
+
end
|
54
55
|
|
55
|
-
|
56
|
-
|
57
|
-
|
56
|
+
def value_object
|
57
|
+
value_object_class.new(template, @requested_value)
|
58
|
+
end
|
58
59
|
|
59
|
-
|
60
|
-
|
61
|
-
|
60
|
+
def value_object_class
|
61
|
+
BaseValue.value_for_template(template)
|
62
|
+
end
|
62
63
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
64
|
+
def persisted_characteristic
|
65
|
+
IdentifierCache.find_by(
|
66
|
+
accessory_id: service.accessory_id,
|
67
|
+
service_uuid: service.uuid,
|
68
|
+
uuid: template.uuid,
|
69
|
+
subtype: subtype
|
70
|
+
)
|
71
|
+
end
|
71
72
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
73
|
+
def persist_characteristic(characteristic)
|
74
|
+
IdentifierCache.create(
|
75
|
+
accessory_id: characteristic.accessory_id,
|
76
|
+
instance_id: characteristic.instance_id,
|
77
|
+
service_uuid: service.uuid,
|
78
|
+
uuid: characteristic.uuid,
|
79
|
+
subtype: subtype
|
80
|
+
)
|
81
|
+
end
|
81
82
|
end
|
82
83
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module RubyHome
|
2
2
|
class ServiceFactory
|
3
|
-
def self.create(service_name, accessory: Accessory.new, subtype:
|
3
|
+
def self.create(service_name, accessory: Accessory.new, subtype: "default", **options)
|
4
4
|
new(
|
5
5
|
service_name: service_name,
|
6
6
|
accessory: accessory,
|
@@ -14,7 +14,7 @@ module RubyHome
|
|
14
14
|
accessory: accessory,
|
15
15
|
description: template.description,
|
16
16
|
name: service_name,
|
17
|
-
uuid: template.uuid
|
17
|
+
uuid: template.uuid
|
18
18
|
)
|
19
19
|
|
20
20
|
if persisted_service
|
@@ -38,87 +38,87 @@ module RubyHome
|
|
38
38
|
|
39
39
|
private
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
41
|
+
ACCESSORY_INFORMATION_OPTIONS = [
|
42
|
+
:firmware_revision,
|
43
|
+
:manufacturer,
|
44
|
+
:model,
|
45
|
+
:name,
|
46
|
+
:serial_number
|
47
|
+
].freeze
|
48
|
+
|
49
|
+
def initialize(service_name:, accessory:, subtype:, **options)
|
50
|
+
@service_name = service_name.to_sym
|
51
|
+
@accessory = accessory
|
52
|
+
@subtype = subtype
|
53
|
+
@options = options
|
54
|
+
end
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
def create_required_characteristics(service)
|
59
|
-
template.required_characteristics.each do |characteristic_template|
|
60
|
-
characteristic_name = characteristic_template.name
|
61
|
-
CharacteristicFactory.create(
|
62
|
-
characteristic_template.name,
|
63
|
-
service: service,
|
64
|
-
subtype: subtype,
|
65
|
-
value: options[characteristic_name]
|
66
|
-
)
|
67
|
-
end
|
68
|
-
end
|
56
|
+
attr_reader :service_name, :accessory, :subtype, :options
|
69
57
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
end
|
58
|
+
def create_required_characteristics(service)
|
59
|
+
template.required_characteristics.each do |characteristic_template|
|
60
|
+
characteristic_name = characteristic_template.name
|
61
|
+
CharacteristicFactory.create(
|
62
|
+
characteristic_template.name,
|
63
|
+
service: service,
|
64
|
+
subtype: subtype,
|
65
|
+
value: options[characteristic_name]
|
66
|
+
)
|
80
67
|
end
|
68
|
+
end
|
81
69
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
70
|
+
def create_optional_characteristics(service)
|
71
|
+
optional_characteristic_templates.each do |characteristic_template|
|
72
|
+
characteristic_name = characteristic_template.name
|
73
|
+
CharacteristicFactory.create(
|
74
|
+
characteristic_name,
|
75
|
+
service: service,
|
76
|
+
subtype: subtype,
|
77
|
+
value: options[characteristic_name]
|
78
|
+
)
|
86
79
|
end
|
80
|
+
end
|
87
81
|
|
88
|
-
|
89
|
-
|
82
|
+
def optional_characteristic_templates
|
83
|
+
template.optional_characteristics.select do |characteristic_template|
|
84
|
+
options.key?(characteristic_template.name)
|
90
85
|
end
|
86
|
+
end
|
91
87
|
|
92
|
-
|
93
|
-
|
88
|
+
def accessory_information_factory?
|
89
|
+
service_name == :accessory_information
|
90
|
+
end
|
94
91
|
|
95
|
-
|
96
|
-
|
97
|
-
accessory: accessory,
|
98
|
-
subtype: 'accessory_information',
|
99
|
-
**options.slice(*ACCESSORY_INFORMATION_OPTIONS)
|
100
|
-
)
|
101
|
-
end
|
92
|
+
def create_accessory_information
|
93
|
+
return if accessory.has_accessory_information?
|
102
94
|
|
103
|
-
|
104
|
-
|
105
|
-
|
95
|
+
ServiceFactory.create(
|
96
|
+
:accessory_information,
|
97
|
+
accessory: accessory,
|
98
|
+
subtype: "accessory_information",
|
99
|
+
**options.slice(*ACCESSORY_INFORMATION_OPTIONS)
|
100
|
+
)
|
101
|
+
end
|
106
102
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
uuid: template.uuid,
|
111
|
-
subtype: subtype
|
112
|
-
)
|
113
|
-
end
|
103
|
+
def template
|
104
|
+
@template ||= ServiceTemplate.find_by(name: service_name)
|
105
|
+
end
|
114
106
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
107
|
+
def persisted_service
|
108
|
+
IdentifierCache.find_by(
|
109
|
+
accessory_id: accessory.id,
|
110
|
+
uuid: template.uuid,
|
111
|
+
subtype: subtype
|
112
|
+
)
|
113
|
+
end
|
114
|
+
|
115
|
+
def persist_service(service)
|
116
|
+
IdentifierCache.create(
|
117
|
+
accessory_id: accessory.id,
|
118
|
+
instance_id: service.instance_id,
|
119
|
+
uuid: template.uuid,
|
120
|
+
subtype: subtype
|
121
|
+
)
|
122
|
+
end
|
123
123
|
end
|
124
124
|
end
|
@@ -1,10 +1,11 @@
|
|
1
1
|
module RubyHome
|
2
2
|
class CharacteristicTemplate
|
3
|
-
|
4
|
-
|
3
|
+
FILENAMES = %w[characteristics.yml manual_characteristics.yml].freeze
|
4
|
+
FILEPATHS = FILENAMES.map { |filename| File.join(__dir__, "..", "..", "config", filename) }.freeze
|
5
|
+
DATA = FILEPATHS.flat_map { |filepath| YAML.load_file(filepath) }.freeze
|
5
6
|
|
6
7
|
def self.all
|
7
|
-
@@all ||= DATA.map { |data| new(data) }
|
8
|
+
@@all ||= DATA.map { |data| new(**data) }
|
8
9
|
end
|
9
10
|
|
10
11
|
def self.find_by(options)
|
@@ -15,18 +16,17 @@ module RubyHome
|
|
15
16
|
end
|
16
17
|
end
|
17
18
|
|
18
|
-
def initialize(name:, description:, uuid:, format:, unit:,
|
19
|
+
def initialize(name:, description:, uuid:, format:, unit:, properties:, constraints:)
|
19
20
|
@name = name
|
20
21
|
@description = description
|
21
22
|
@uuid = uuid
|
22
23
|
@format = format
|
23
24
|
@unit = unit
|
24
|
-
@permissions = permissions
|
25
25
|
@properties = properties
|
26
26
|
@constraints = constraints
|
27
27
|
end
|
28
28
|
|
29
|
-
attr_reader :name, :description, :uuid, :format, :unit, :
|
29
|
+
attr_reader :name, :description, :uuid, :format, :unit, :properties
|
30
30
|
|
31
31
|
def constraints
|
32
32
|
@constraints || {}
|