ruby_home 0.2.1 → 0.2.6
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.
- 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 || {}
|