ruby_home 0.1.5 → 0.1.7
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/README.md +64 -5
- data/bin/rubyhome +4 -4
- data/lib/ruby_home/accessory_info.rb +45 -30
- data/lib/ruby_home/factories/characteristic_factory.rb +27 -19
- data/lib/ruby_home/factories/default_values/float_value.rb +1 -1
- data/lib/ruby_home/factories/service_factory.rb +89 -0
- data/lib/ruby_home/factories/templates/characteristic_template.rb +6 -2
- data/lib/ruby_home/hap/accessory.rb +24 -3
- data/lib/ruby_home/hap/accessory_collection.rb +38 -0
- data/lib/ruby_home/hap/characteristic.rb +25 -19
- data/lib/ruby_home/hap/crypto/session_key.rb +31 -0
- data/lib/ruby_home/hap/ev_response.rb +64 -0
- data/lib/ruby_home/{http → hap}/hap_request.rb +12 -5
- data/lib/ruby_home/{http → hap}/hap_response.rb +7 -4
- data/lib/ruby_home/hap/server.rb +81 -0
- data/lib/ruby_home/hap/service.rb +11 -15
- data/lib/ruby_home/http/application.rb +17 -22
- data/lib/ruby_home/http/controllers/application_controller.rb +8 -4
- data/lib/ruby_home/http/controllers/characteristics_controller.rb +19 -4
- data/lib/ruby_home/http/controllers/pair_verifies_controller.rb +3 -5
- data/lib/ruby_home/http/serializers/object_serializer.rb +1 -1
- data/lib/ruby_home/http/services/socket_notifier.rb +40 -0
- data/lib/ruby_home/http/services/start_srp_service.rb +36 -34
- data/lib/ruby_home/http/services/verify_srp_service.rb +55 -53
- data/lib/ruby_home/identifier_cache.rb +22 -49
- data/lib/ruby_home/persistable.rb +36 -0
- data/lib/ruby_home/version.rb +1 -1
- data/lib/ruby_home.rb +14 -5
- data/rubyhome.gemspec +3 -3
- metadata +35 -38
- data/lib/ruby_home/factories/accessory_factory.rb +0 -73
- data/lib/ruby_home/http/hap_server.rb +0 -60
- data/lib/ruby_home/rack/handler/hap_server.rb +0 -21
- data/lib/ruby_home/yaml_record.rb +0 -440
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: b7f5f53b947e2cfbce79204572dd8d29b284c5434304aee7214b6a7a33d3e0a6
         | 
| 4 | 
            +
              data.tar.gz: '086845819574df45f838c8ff67979f55f01763b6fe44114af76fd3479f8f1b83'
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 419df1283d71010c0a4e8e89f60f01fa752ea9366f6fe59a1ca3a2e202a9ab72361ed4dff5389ecdfb8b084feb40cd0d5c62fcbf16b90b65bfcd5096c7a3a4ab
         | 
| 7 | 
            +
              data.tar.gz: 3644d107b711edadd639f881614ebd85cb8e164cbeebf5db30e1748303983b52bf056371e952a6c9c0e39aa2a7744351549ddaf2eef4c655428dabdf0c9fe2eb
         | 
    
        data/README.md
    CHANGED
    
    | @@ -21,18 +21,18 @@ Or install it yourself as: | |
| 21 21 |  | 
| 22 22 | 
             
                $ gem install ruby_home
         | 
| 23 23 |  | 
| 24 | 
            -
            ## Usage
         | 
| 24 | 
            +
            ## Basic Usage
         | 
| 25 25 |  | 
| 26 26 | 
             
            Create a fan with an on/off switch.
         | 
| 27 27 |  | 
| 28 28 | 
             
            ```ruby
         | 
| 29 29 | 
             
            require 'ruby_home'
         | 
| 30 30 |  | 
| 31 | 
            -
            accessory_information = RubyHome:: | 
| 32 | 
            -
            fan = RubyHome:: | 
| 31 | 
            +
            accessory_information = RubyHome::ServiceFactory.create(:accessory_information)
         | 
| 32 | 
            +
            fan = RubyHome::ServiceFactory.create(:fan)
         | 
| 33 33 |  | 
| 34 | 
            -
            fan.characteristic(:on). | 
| 35 | 
            -
              if  | 
| 34 | 
            +
            fan.characteristic(:on).after_update do |characteristic|
         | 
| 35 | 
            +
              if characteristic.value == 1
         | 
| 36 36 | 
             
                puts "Fan switched on"
         | 
| 37 37 | 
             
              else
         | 
| 38 38 | 
             
                puts "Fan switched off"
         | 
| @@ -42,6 +42,65 @@ end | |
| 42 42 | 
             
            RubyHome.run
         | 
| 43 43 | 
             
            ```
         | 
| 44 44 |  | 
| 45 | 
            +
            Create a garage door opener.
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            ```ruby
         | 
| 48 | 
            +
            require 'ruby_home'
         | 
| 49 | 
            +
             | 
| 50 | 
            +
            accessory_information = RubyHome::ServiceFactory.create(:accessory_information)
         | 
| 51 | 
            +
            door = RubyHome::ServiceFactory.create(:garage_door_opener)
         | 
| 52 | 
            +
             | 
| 53 | 
            +
            door.characteristic(:target_door_state).after_update do |characteristic|
         | 
| 54 | 
            +
              if characteristic.value == 0 # open
         | 
| 55 | 
            +
                sleep 1
         | 
| 56 | 
            +
                door.characteristic(:current_door_state).value = 0
         | 
| 57 | 
            +
              elsif characteristic.value == 1 #closed
         | 
| 58 | 
            +
                sleep 1
         | 
| 59 | 
            +
                door.characteristic(:current_door_state).value = 1
         | 
| 60 | 
            +
              end
         | 
| 61 | 
            +
            end
         | 
| 62 | 
            +
             | 
| 63 | 
            +
            RubyHome.run
         | 
| 64 | 
            +
            ```
         | 
| 65 | 
            +
             | 
| 66 | 
            +
            ## Customization
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            RubyHome ties to provide sane defaults for all services. Customization of any of the options is possible.
         | 
| 69 | 
            +
             | 
| 70 | 
            +
            ```ruby
         | 
| 71 | 
            +
            require 'ruby_home'
         | 
| 72 | 
            +
             | 
| 73 | 
            +
            accessory_information = RubyHome::ServiceFactory.create(:accessory_information,
         | 
| 74 | 
            +
              firmware_revision: '4.3.18421',
         | 
| 75 | 
            +
              manufacturer: 'Fake Company',
         | 
| 76 | 
            +
              model: 'BSB001',
         | 
| 77 | 
            +
              name: 'Kickass fan bridge',
         | 
| 78 | 
            +
              serial_number: 'AB1-UK-A123456'
         | 
| 79 | 
            +
            )
         | 
| 80 | 
            +
             | 
| 81 | 
            +
            fan = RubyHome::ServiceFactory.create(:fan,
         | 
| 82 | 
            +
              on: false,
         | 
| 83 | 
            +
              rotation_speed: 50,
         | 
| 84 | 
            +
              rotation_direction: 1,
         | 
| 85 | 
            +
              firmware_revision: '105.0.21169',
         | 
| 86 | 
            +
              manufacturer: 'Fake Company',
         | 
| 87 | 
            +
              model: 'LWB006',
         | 
| 88 | 
            +
              name: 'Kickass fan',
         | 
| 89 | 
            +
              serial_number: '123-UK-A12345'
         | 
| 90 | 
            +
            )
         | 
| 91 | 
            +
             | 
| 92 | 
            +
            fan.characteristic(:on).after_update do |characteristic|
         | 
| 93 | 
            +
              if characteristic.value == 1
         | 
| 94 | 
            +
                puts "Fan switched on"
         | 
| 95 | 
            +
              else
         | 
| 96 | 
            +
                puts "Fan switched off"
         | 
| 97 | 
            +
              end
         | 
| 98 | 
            +
            end
         | 
| 99 | 
            +
             | 
| 100 | 
            +
            RubyHome.run
         | 
| 101 | 
            +
            ```
         | 
| 102 | 
            +
             | 
| 103 | 
            +
             | 
| 45 104 | 
             
            ## Development
         | 
| 46 105 |  | 
| 47 106 | 
             
            After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
         | 
    
        data/bin/rubyhome
    CHANGED
    
    | @@ -2,11 +2,11 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require_relative '../lib/ruby_home'
         | 
| 4 4 |  | 
| 5 | 
            -
            accessory_information = RubyHome:: | 
| 6 | 
            -
            fan = RubyHome:: | 
| 5 | 
            +
            accessory_information = RubyHome::ServiceFactory.create(:accessory_information)
         | 
| 6 | 
            +
            fan = RubyHome::ServiceFactory.create(:fan)
         | 
| 7 7 |  | 
| 8 | 
            -
            fan.characteristic(:on). | 
| 9 | 
            -
              if  | 
| 8 | 
            +
            fan.characteristic(:on).after_update do |characteristic|
         | 
| 9 | 
            +
              if characteristic.value == 1
         | 
| 10 10 | 
             
                puts "Fan switched on"
         | 
| 11 11 | 
             
              else
         | 
| 12 12 | 
             
                puts "Fan switched off"
         | 
| @@ -1,24 +1,46 @@ | |
| 1 | 
            -
            require_relative ' | 
| 2 | 
            -
            require_relative 'yaml_record'
         | 
| 1 | 
            +
            require_relative 'persistable'
         | 
| 3 2 |  | 
| 4 3 | 
             
            module RubyHome
         | 
| 5 | 
            -
              class AccessoryInfo | 
| 4 | 
            +
              class AccessoryInfo
         | 
| 5 | 
            +
                include Persistable
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                self.source = 'accessory_info.yml'
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def self.instance
         | 
| 10 | 
            +
                  @@_instance ||= persisted || create
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def self.reload
         | 
| 14 | 
            +
                  @@_instance = nil
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 6 17 | 
             
                USERNAME = -'Pair-Setup'
         | 
| 7 18 |  | 
| 8 | 
            -
                 | 
| 9 | 
            -
             | 
| 19 | 
            +
                def initialize(device_id: nil, paired_clients: [], password: nil, signature_key: nil)
         | 
| 20 | 
            +
                  @device_id = device_id
         | 
| 21 | 
            +
                  @paired_clients = paired_clients
         | 
| 22 | 
            +
                  @password = password
         | 
| 23 | 
            +
                  @signature_key = signature_key
         | 
| 24 | 
            +
                end
         | 
| 10 25 |  | 
| 11 | 
            -
                 | 
| 12 | 
            -
             | 
| 13 | 
            -
                 | 
| 14 | 
            -
                set_callback :before_create, :set_signature_key
         | 
| 26 | 
            +
                def username
         | 
| 27 | 
            +
                  USERNAME
         | 
| 28 | 
            +
                end
         | 
| 15 29 |  | 
| 16 | 
            -
                def  | 
| 17 | 
            -
                   | 
| 30 | 
            +
                def password
         | 
| 31 | 
            +
                  @password ||= Password.generate
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def device_id
         | 
| 35 | 
            +
                  @device_id ||= DeviceID.generate
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                def paired_clients
         | 
| 39 | 
            +
                  @paired_clients ||= []
         | 
| 18 40 | 
             
                end
         | 
| 19 41 |  | 
| 20 42 | 
             
                def add_paired_client(admin: false, identifier: , public_key: )
         | 
| 21 | 
            -
                  paired_clients << { admin: admin, identifier: identifier, public_key: public_key }
         | 
| 43 | 
            +
                  @paired_clients << { admin: admin, identifier: identifier, public_key: public_key }
         | 
| 22 44 | 
             
                  save
         | 
| 23 45 | 
             
                end
         | 
| 24 46 |  | 
| @@ -35,26 +57,19 @@ module RubyHome | |
| 35 57 | 
             
                  @signing_key ||= RbNaCl::Signatures::Ed25519::SigningKey.new([signature_key].pack('H*'))
         | 
| 36 58 | 
             
                end
         | 
| 37 59 |  | 
| 38 | 
            -
                def username
         | 
| 39 | 
            -
                  USERNAME
         | 
| 40 | 
            -
                end
         | 
| 41 | 
            -
             | 
| 42 60 | 
             
                private
         | 
| 43 61 |  | 
| 44 | 
            -
             | 
| 45 | 
            -
             | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
                def set_paired_clients
         | 
| 49 | 
            -
                  self.paired_clients = []
         | 
| 50 | 
            -
                end
         | 
| 62 | 
            +
                  def signature_key
         | 
| 63 | 
            +
                    @signature_key ||= RbNaCl::Signatures::Ed25519::SigningKey.generate.to_bytes.unpack1('H*')
         | 
| 64 | 
            +
                  end
         | 
| 51 65 |  | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
             | 
| 56 | 
            -
             | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 66 | 
            +
                  def persisted_attributes
         | 
| 67 | 
            +
                    {
         | 
| 68 | 
            +
                      device_id: device_id,
         | 
| 69 | 
            +
                      paired_clients: paired_clients,
         | 
| 70 | 
            +
                      password: password,
         | 
| 71 | 
            +
                      signature_key: signature_key
         | 
| 72 | 
            +
                    }
         | 
| 73 | 
            +
                  end
         | 
| 59 74 | 
             
              end
         | 
| 60 75 | 
             
            end
         | 
| @@ -4,37 +4,45 @@ module RubyHome | |
| 4 4 | 
             
                  identify: nil,
         | 
| 5 5 | 
             
                }.freeze
         | 
| 6 6 |  | 
| 7 | 
            -
                def self.create(characteristic_name,  | 
| 8 | 
            -
                  new( | 
| 9 | 
            -
             | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
                   | 
| 13 | 
            -
                  @options = options
         | 
| 7 | 
            +
                def self.create(characteristic_name, service: , value: nil)
         | 
| 8 | 
            +
                  new(
         | 
| 9 | 
            +
                    characteristic_name: characteristic_name,
         | 
| 10 | 
            +
                    service: service,
         | 
| 11 | 
            +
                    value: value
         | 
| 12 | 
            +
                  ).create
         | 
| 14 13 | 
             
                end
         | 
| 15 14 |  | 
| 16 15 | 
             
                def create
         | 
| 17 | 
            -
                   | 
| 16 | 
            +
                  service.characteristics << new_characteristic
         | 
| 18 17 |  | 
| 19 | 
            -
                   | 
| 20 | 
            -
                  characteristic
         | 
| 18 | 
            +
                  new_characteristic
         | 
| 21 19 | 
             
                end
         | 
| 22 20 |  | 
| 23 21 | 
             
                private
         | 
| 24 22 |  | 
| 25 | 
            -
                   | 
| 26 | 
            -
             | 
| 27 | 
            -
             | 
| 28 | 
            -
                    @ | 
| 23 | 
            +
                  def initialize(characteristic_name:, service:, value:)
         | 
| 24 | 
            +
                    @characteristic_name = characteristic_name.to_sym
         | 
| 25 | 
            +
                    @service = service
         | 
| 26 | 
            +
                    @value = value || default_value
         | 
| 29 27 | 
             
                  end
         | 
| 30 28 |  | 
| 31 | 
            -
                   | 
| 32 | 
            -
             | 
| 29 | 
            +
                  attr_reader :service, :characteristic_name, :value
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def new_characteristic
         | 
| 32 | 
            +
                    @new_characteristic ||= Characteristic.new(
         | 
| 33 | 
            +
                      description: template.description,
         | 
| 34 | 
            +
                      format: template.format,
         | 
| 35 | 
            +
                      name: characteristic_name,
         | 
| 36 | 
            +
                      properties: template.properties,
         | 
| 37 | 
            +
                      service: service,
         | 
| 38 | 
            +
                      unit: template.unit,
         | 
| 39 | 
            +
                      uuid: template.uuid,
         | 
| 40 | 
            +
                      value: value
         | 
| 41 | 
            +
                    )
         | 
| 33 42 | 
             
                  end
         | 
| 34 43 |  | 
| 35 | 
            -
                  def  | 
| 36 | 
            -
                     | 
| 37 | 
            -
                    options.merge(template.to_hash)
         | 
| 44 | 
            +
                  def template
         | 
| 45 | 
            +
                    @template ||= CharacteristicTemplate.find_by(name: characteristic_name)
         | 
| 38 46 | 
             
                  end
         | 
| 39 47 |  | 
| 40 48 | 
             
                  def default_value
         | 
| @@ -0,0 +1,89 @@ | |
| 1 | 
            +
            module RubyHome
         | 
| 2 | 
            +
              class ServiceFactory
         | 
| 3 | 
            +
                def self.create(service_name, accessory: Accessory.new, **options)
         | 
| 4 | 
            +
                  new(
         | 
| 5 | 
            +
                    service_name: service_name,
         | 
| 6 | 
            +
                    accessory: accessory,
         | 
| 7 | 
            +
                    **options
         | 
| 8 | 
            +
                  ).create
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                def create
         | 
| 12 | 
            +
                  accessory.services << new_service
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                  create_accessory_information
         | 
| 15 | 
            +
                  create_required_characteristics
         | 
| 16 | 
            +
                  create_optional_characteristics
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                  new_service
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                private
         | 
| 22 | 
            +
                  ACCESSORY_INFORMATION_OPTIONS = [
         | 
| 23 | 
            +
                    :firmware_revision,
         | 
| 24 | 
            +
                    :manufacturer,
         | 
| 25 | 
            +
                    :model,
         | 
| 26 | 
            +
                    :name,
         | 
| 27 | 
            +
                    :serial_number
         | 
| 28 | 
            +
                  ].freeze
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def initialize(service_name:, accessory:, **options)
         | 
| 31 | 
            +
                    @service_name = service_name.to_sym
         | 
| 32 | 
            +
                    @accessory = accessory
         | 
| 33 | 
            +
                    @options = options
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                  attr_reader :service_name, :accessory, :options
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def new_service
         | 
| 39 | 
            +
                    @new_service ||= Service.new(
         | 
| 40 | 
            +
                      accessory: accessory,
         | 
| 41 | 
            +
                      description: template.description,
         | 
| 42 | 
            +
                      name: service_name,
         | 
| 43 | 
            +
                      uuid: template.uuid
         | 
| 44 | 
            +
                    )
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  def create_required_characteristics
         | 
| 48 | 
            +
                    template.required_characteristics.each do |characteristic_template|
         | 
| 49 | 
            +
                      characteristic_name = characteristic_template.name
         | 
| 50 | 
            +
                      CharacteristicFactory.create(
         | 
| 51 | 
            +
                        characteristic_template.name,
         | 
| 52 | 
            +
                        service: new_service,
         | 
| 53 | 
            +
                        value: options[characteristic_name]
         | 
| 54 | 
            +
                      )
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
                  end
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                  def create_optional_characteristics
         | 
| 59 | 
            +
                    optional_characteristic_templates.each do |characteristic_template|
         | 
| 60 | 
            +
                      characteristic_name = characteristic_template.name
         | 
| 61 | 
            +
                      CharacteristicFactory.create(
         | 
| 62 | 
            +
                        characteristic_name,
         | 
| 63 | 
            +
                        service: new_service,
         | 
| 64 | 
            +
                        value: options[characteristic_name]
         | 
| 65 | 
            +
                      )
         | 
| 66 | 
            +
                    end
         | 
| 67 | 
            +
                  end
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  def optional_characteristic_templates
         | 
| 70 | 
            +
                    template.optional_characteristics.select do |characteristic_template|
         | 
| 71 | 
            +
                      options.keys.include?(characteristic_template.name)
         | 
| 72 | 
            +
                    end
         | 
| 73 | 
            +
                  end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                  def create_accessory_information
         | 
| 76 | 
            +
                    unless service_name == :accessory_information
         | 
| 77 | 
            +
                      ServiceFactory.create(
         | 
| 78 | 
            +
                        :accessory_information,
         | 
| 79 | 
            +
                        accessory: accessory,
         | 
| 80 | 
            +
                        **options.slice(*ACCESSORY_INFORMATION_OPTIONS)
         | 
| 81 | 
            +
                      )
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
                  end
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  def template
         | 
| 86 | 
            +
                    @template ||= ServiceTemplate.find_by(name: service_name)
         | 
| 87 | 
            +
                  end
         | 
| 88 | 
            +
              end
         | 
| 89 | 
            +
            end
         | 
| @@ -15,7 +15,7 @@ module RubyHome | |
| 15 15 | 
             
                  end
         | 
| 16 16 | 
             
                end
         | 
| 17 17 |  | 
| 18 | 
            -
                def initialize(name:, description:, uuid:, format:, unit:, permissions:, properties:, constraints:)
         | 
| 18 | 
            +
                def initialize(name:, description:, uuid:, format:, unit:, permissions:, properties:, constraints: )
         | 
| 19 19 | 
             
                  @name = name
         | 
| 20 20 | 
             
                  @description = description
         | 
| 21 21 | 
             
                  @uuid = uuid
         | 
| @@ -26,7 +26,11 @@ module RubyHome | |
| 26 26 | 
             
                  @constraints = constraints
         | 
| 27 27 | 
             
                end
         | 
| 28 28 |  | 
| 29 | 
            -
                attr_reader :name, :description, :uuid, :format, :unit, :permissions, :properties | 
| 29 | 
            +
                attr_reader :name, :description, :uuid, :format, :unit, :permissions, :properties
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                def constraints
         | 
| 32 | 
            +
                  @constraints || {}
         | 
| 33 | 
            +
                end
         | 
| 30 34 |  | 
| 31 35 | 
             
                def to_hash
         | 
| 32 36 | 
             
                  {
         | 
| @@ -1,11 +1,22 @@ | |
| 1 1 | 
             
            module RubyHome
         | 
| 2 2 | 
             
              class Accessory
         | 
| 3 | 
            +
                @@all = AccessoryCollection.new
         | 
| 4 | 
            +
             | 
| 5 | 
            +
                def self.all
         | 
| 6 | 
            +
                  @@all
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def self.reset
         | 
| 10 | 
            +
                  @@all = AccessoryCollection.new
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 3 13 | 
             
                def initialize
         | 
| 4 14 | 
             
                  @services = []
         | 
| 15 | 
            +
                  @id = next_available_accessory_id
         | 
| 16 | 
            +
                  @@all << self
         | 
| 5 17 | 
             
                end
         | 
| 6 18 |  | 
| 7 | 
            -
                 | 
| 8 | 
            -
                attr_reader :services
         | 
| 19 | 
            +
                attr_reader :services, :id
         | 
| 9 20 |  | 
| 10 21 | 
             
                def characteristics
         | 
| 11 22 | 
             
                  services.flat_map(&:characteristics)
         | 
| @@ -15,12 +26,22 @@ module RubyHome | |
| 15 26 | 
             
                  (largest_instance_id || 0) + 1
         | 
| 16 27 | 
             
                end
         | 
| 17 28 |  | 
| 29 | 
            +
                private
         | 
| 30 | 
            +
             | 
| 18 31 | 
             
                def instance_ids
         | 
| 19 | 
            -
                   | 
| 32 | 
            +
                  instances.map(&:instance_id)
         | 
| 33 | 
            +
                end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                def instances
         | 
| 36 | 
            +
                  services + characteristics
         | 
| 20 37 | 
             
                end
         | 
| 21 38 |  | 
| 22 39 | 
             
                def largest_instance_id
         | 
| 23 40 | 
             
                  instance_ids.max
         | 
| 24 41 | 
             
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                def next_available_accessory_id
         | 
| 44 | 
            +
                  self.class.all.count + 1
         | 
| 45 | 
            +
                end
         | 
| 25 46 | 
             
              end
         | 
| 26 47 | 
             
            end
         | 
| @@ -0,0 +1,38 @@ | |
| 1 | 
            +
            module RubyHome
         | 
| 2 | 
            +
              class AccessoryCollection
         | 
| 3 | 
            +
                include Enumerable
         | 
| 4 | 
            +
                attr_accessor :accessories
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                def initialize(*accessories)
         | 
| 7 | 
            +
                  @accessories = accessories
         | 
| 8 | 
            +
                end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def each
         | 
| 11 | 
            +
                  @accessories.map do |accessory|
         | 
| 12 | 
            +
                    yield accessory
         | 
| 13 | 
            +
                  end
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                def <<(accessory)
         | 
| 17 | 
            +
                  @accessories << accessory
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                def find_characteristic(attributes)
         | 
| 21 | 
            +
                  characteristics.find do |characteristic|
         | 
| 22 | 
            +
                    attributes.all? do |key, value|
         | 
| 23 | 
            +
                      characteristic.send(key) == value
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                private
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def services
         | 
| 31 | 
            +
                  accessories.flat_map(&:services)
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def characteristics
         | 
| 35 | 
            +
                  services.flat_map(&:characteristics)
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
              end
         | 
| 38 | 
            +
            end
         | 
| @@ -2,6 +2,18 @@ module RubyHome | |
| 2 2 | 
             
              class Characteristic
         | 
| 3 3 | 
             
                include Wisper::Publisher
         | 
| 4 4 |  | 
| 5 | 
            +
                public :local_registrations
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                def unsubscribe(*listeners)
         | 
| 8 | 
            +
                  local_registrations.delete_if do |registration|
         | 
| 9 | 
            +
                    listeners.include?(registration.listener)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                def after_update(&block)
         | 
| 14 | 
            +
                  on(:after_update, &block)
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 5 17 | 
             
                PROPERTIES = {
         | 
| 6 18 | 
             
                  'cnotify' => 'ev',
         | 
| 7 19 | 
             
                  'read' => 'pr',
         | 
| @@ -18,10 +30,20 @@ module RubyHome | |
| 18 30 | 
             
                  @properties = properties
         | 
| 19 31 | 
             
                  @service = service
         | 
| 20 32 | 
             
                  @value = value
         | 
| 33 | 
            +
                  @instance_id = accessory.next_available_instance_id
         | 
| 21 34 | 
             
                end
         | 
| 22 35 |  | 
| 23 | 
            -
                attr_reader | 
| 24 | 
            -
             | 
| 36 | 
            +
                attr_reader(
         | 
| 37 | 
            +
                  :service,
         | 
| 38 | 
            +
                  :value,
         | 
| 39 | 
            +
                  :uuid,
         | 
| 40 | 
            +
                  :name,
         | 
| 41 | 
            +
                  :description,
         | 
| 42 | 
            +
                  :format,
         | 
| 43 | 
            +
                  :unit,
         | 
| 44 | 
            +
                  :properties,
         | 
| 45 | 
            +
                  :instance_id
         | 
| 46 | 
            +
                )
         | 
| 25 47 |  | 
| 26 48 | 
             
                def accessory
         | 
| 27 49 | 
             
                  service.accessory
         | 
| @@ -37,24 +59,8 @@ module RubyHome | |
| 37 59 |  | 
| 38 60 | 
             
                def value=(new_value)
         | 
| 39 61 | 
             
                  return if name == :identify
         | 
| 40 | 
            -
             | 
| 41 62 | 
             
                  @value = new_value
         | 
| 42 | 
            -
                  broadcast(: | 
| 43 | 
            -
                end
         | 
| 44 | 
            -
             | 
| 45 | 
            -
                def inspect
         | 
| 46 | 
            -
                  {
         | 
| 47 | 
            -
                    name: name,
         | 
| 48 | 
            -
                    value: value,
         | 
| 49 | 
            -
                    accessory_id: accessory_id,
         | 
| 50 | 
            -
                    service_iid: service_iid,
         | 
| 51 | 
            -
                    instance_id: instance_id
         | 
| 52 | 
            -
                  }
         | 
| 53 | 
            -
                end
         | 
| 54 | 
            -
             | 
| 55 | 
            -
                def save
         | 
| 56 | 
            -
                  IdentifierCache.add_accessory(accessory)
         | 
| 57 | 
            -
                  IdentifierCache.add_characteristic(self)
         | 
| 63 | 
            +
                  broadcast(:after_update, self)
         | 
| 58 64 | 
             
                end
         | 
| 59 65 | 
             
              end
         | 
| 60 66 | 
             
            end
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            module RubyHome
         | 
| 2 | 
            +
              module HAP
         | 
| 3 | 
            +
                module Crypto
         | 
| 4 | 
            +
                  class SessionKey
         | 
| 5 | 
            +
                    def initialize(shared_secret)
         | 
| 6 | 
            +
                      @shared_secret = shared_secret
         | 
| 7 | 
            +
                    end
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                    def controller_to_accessory_key
         | 
| 10 | 
            +
                      @controller_to_accessory_key ||= generate_shared_secret_key(WRITE)
         | 
| 11 | 
            +
                    end
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def accessory_to_controller_key
         | 
| 14 | 
            +
                      @accessory_to_controller_key ||= generate_shared_secret_key(READ)
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    private
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                      SALT = -'Control-Salt'
         | 
| 20 | 
            +
                      READ = -'Control-Read-Encryption-Key'
         | 
| 21 | 
            +
                      WRITE = -'Control-Write-Encryption-Key'
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                      attr_reader :shared_secret
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                      def generate_shared_secret_key(info)
         | 
| 26 | 
            +
                        Crypto::HKDF.new(info: info, salt: SALT).encrypt(shared_secret)
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
              end
         | 
| 31 | 
            +
            end
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            module RubyHome
         | 
| 2 | 
            +
              module HAP
         | 
| 3 | 
            +
                class EVResponse
         | 
| 4 | 
            +
                  def initialize(socket, body)
         | 
| 5 | 
            +
                    @socket = socket
         | 
| 6 | 
            +
                    @body = body
         | 
| 7 | 
            +
                    cache[:accessory_to_controller_count] ||= 0
         | 
| 8 | 
            +
                  end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def send_response
         | 
| 11 | 
            +
                    response = ''
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    send_header(response)
         | 
| 14 | 
            +
                    send_body(response)
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    encrypted_response = encrypter.encrypt(response).join
         | 
| 17 | 
            +
                    cache[:accessory_to_controller_count] = encrypter.count
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    socket << encrypted_response
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  private
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    CRLF = -"\x0d\x0a"
         | 
| 25 | 
            +
                    STATUS_LINE = -'EVENT/1.0 200 OK'
         | 
| 26 | 
            +
                    CONTENT_TYPE_LINE = -'Content-Type: application/hap+json'
         | 
| 27 | 
            +
             | 
| 28 | 
            +
                    def send_header(socket)
         | 
| 29 | 
            +
                      socket << STATUS_LINE + CRLF
         | 
| 30 | 
            +
                      socket << CONTENT_TYPE_LINE + CRLF
         | 
| 31 | 
            +
                      socket << content_length_line + CRLF
         | 
| 32 | 
            +
                      socket << CRLF
         | 
| 33 | 
            +
                    end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                    def send_body(socket)
         | 
| 36 | 
            +
                      socket << body
         | 
| 37 | 
            +
                    end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                    def content_length_line
         | 
| 40 | 
            +
                      "Content-Length: #{body.length}"
         | 
| 41 | 
            +
                    end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                    attr_reader :body, :socket
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                    def encrypter
         | 
| 46 | 
            +
                      @_encrypter ||= RubyHome::HAP::HTTPEncryption.new(encryption_key, encrypter_params)
         | 
| 47 | 
            +
                    end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    def encrypter_params
         | 
| 50 | 
            +
                      {
         | 
| 51 | 
            +
                        count: cache[:accessory_to_controller_count]
         | 
| 52 | 
            +
                      }
         | 
| 53 | 
            +
                    end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    def encryption_key
         | 
| 56 | 
            +
                      cache[:accessory_to_controller_key]
         | 
| 57 | 
            +
                    end
         | 
| 58 | 
            +
             | 
| 59 | 
            +
                    def cache
         | 
| 60 | 
            +
                      RubyHome.socket_store[socket]
         | 
| 61 | 
            +
                    end
         | 
| 62 | 
            +
                end
         | 
| 63 | 
            +
              end
         | 
| 64 | 
            +
            end
         | 
| @@ -1,13 +1,14 @@ | |
| 1 1 | 
             
            module RubyHome
         | 
| 2 | 
            -
              module  | 
| 2 | 
            +
              module HAP
         | 
| 3 3 | 
             
                class HAPRequest < WEBrick::HTTPRequest
         | 
| 4 | 
            -
                  def initialize( | 
| 4 | 
            +
                  def initialize(config, sock)
         | 
| 5 | 
            +
                    @sock = sock
         | 
| 5 6 | 
             
                    cache[:controller_to_accessory_count] ||= 0
         | 
| 6 7 |  | 
| 7 | 
            -
                    super( | 
| 8 | 
            +
                    super(config)
         | 
| 8 9 | 
             
                  end
         | 
| 9 10 |  | 
| 10 | 
            -
                  def parse(socket | 
| 11 | 
            +
                  def parse(socket)
         | 
| 11 12 | 
             
                    if decryption_time?
         | 
| 12 13 | 
             
                      request_line = socket.read_nonblock(@buffer_size)
         | 
| 13 14 |  | 
| @@ -24,8 +25,14 @@ module RubyHome | |
| 24 25 | 
             
                    cache[:controller_to_accessory_count] >= 1
         | 
| 25 26 | 
             
                  end
         | 
| 26 27 |  | 
| 28 | 
            +
                  def meta_vars
         | 
| 29 | 
            +
                    super.merge({"REQUEST_SOCKET" => sock})
         | 
| 30 | 
            +
                  end
         | 
| 31 | 
            +
             | 
| 27 32 | 
             
                  private
         | 
| 28 33 |  | 
| 34 | 
            +
                  attr_reader :sock
         | 
| 35 | 
            +
             | 
| 29 36 | 
             
                  def decrypter
         | 
| 30 37 | 
             
                    @_decrypter ||= RubyHome::HAP::HTTPDecryption.new(decryption_key, decrypter_params)
         | 
| 31 38 | 
             
                  end
         | 
| @@ -45,7 +52,7 @@ module RubyHome | |
| 45 52 | 
             
                  end
         | 
| 46 53 |  | 
| 47 54 | 
             
                  def cache
         | 
| 48 | 
            -
                     | 
| 55 | 
            +
                    RubyHome.socket_store[sock]
         | 
| 49 56 | 
             
                  end
         | 
| 50 57 | 
             
                end
         | 
| 51 58 | 
             
              end
         |