Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add QR code output to the greeter #99

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
MIT License

Copyright (c) 2018 Karl Entwistle
Copyright (c) 2017 Maxim Kulkin
Copyright (c) 2020 Philipp Erbelding

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
10 changes: 8 additions & 2 deletions lib/ruby_home/accessory_info.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ def self.reload

USERNAME = -'Pair-Setup'

def initialize(device_id: nil, paired_clients: [], password: nil, signature_key: nil)
def initialize(device_id: nil, paired_clients: [], password: nil, signature_key: nil, setup_id: nil)
@device_id = device_id
@paired_clients = paired_clients
@password = password
@signature_key = signature_key
@setup_id = setup_id
end

def username
Expand Down Expand Up @@ -62,6 +63,10 @@ def signing_key
@signing_key ||= RbNaCl::Signatures::Ed25519::SigningKey.new([signature_key].pack('H*'))
end

def setup_id
@setup_id ||= SetupID.generate
end

private

def signature_key
Expand All @@ -73,7 +78,8 @@ def persisted_attributes
device_id: device_id,
paired_clients: paired_clients,
password: password,
signature_key: signature_key
signature_key: signature_key,
setup_id: setup_id
}
end
end
Expand Down
8 changes: 4 additions & 4 deletions lib/ruby_home/dns/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ def register
dnssd_service
end

def text_record
TextRecord.new(accessory_info: accessory_info, configuration: configuration)
end

private

attr_reader :configuration
Expand Down Expand Up @@ -43,10 +47,6 @@ def host
configuration.host
end

def text_record
TextRecord.new(accessory_info: accessory_info, configuration: configuration)
end

def accessory_info
AccessoryInfo.instance
end
Expand Down
74 changes: 47 additions & 27 deletions lib/ruby_home/dns/text_record.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'digest'
require 'base64'
module RubyHome
class TextRecord < DNSSD::TextRecord
def initialize(accessory_info:, configuration:)
Expand All @@ -6,6 +8,32 @@ def initialize(accessory_info:, configuration:)
super(to_hash)
end

# Accessory Category Identifier. Required. Indicates the category that best
# describes the primary function of the accessory. This must have a range of
# 1-65535.

def accessory_category_identifier
2
end

# Protocol version string <major>.<minor> (e.g. "1.0"). Required if value is
# not "1.0". The client should check this before displaying an accessory to
# the user. If the major version is greater than the major version the client
# software was built to support, it should hide the accessory from the user. A
# change in the minor version indicates the protocol is still compatible. This
# mechanism allows future versions of the protocol to hide itself from older
# clients that may not know how to handle it.

def protocol_version
'1.1'
end

# Feature flags (e.g. "0x3" for bits 0 and 1). Required if non-zero.

def feature_flags
0
end

private

attr_reader :accessory_info, :configuration
Expand All @@ -19,7 +47,8 @@ def to_hash
'md' => model_name,
'pv' => protocol_version,
's#' => current_state_number,
'sf' => status_flags
'sf' => status_flags,
'sh' => setup_hash
}
end

Expand All @@ -33,20 +62,6 @@ def current_configuration_number
1
end

# Accessory Category Identifier. Required. Indicates the category that best
# describes the primary function of the accessory. This must have a range of
# 1-65535.

def accessory_category_identifier
2
end

# Feature flags (e.g. "0x3" for bits 0 and 1). Required if non-zero.

def feature_flags
0
end

# Status flags (e.g. "0x04" for bit 3). Value should be an unsigned integer.
# Required if non-zero.

Expand Down Expand Up @@ -78,22 +93,27 @@ def model_name
configuration.model_name
end

# Protocol version string <major>.<minor> (e.g. "1.0"). Required if value is
# not "1.0". The client should check this before displaying an accessory to
# the user. If the major version is greater than the major version the client
# software was built to support, it should hide the accessory from the user. A
# change in the minor version indicates the protocol is still compatible. This
# mechanism allows future versions of the protocol to hide itself from older
# clients that may not know how to handle it.

def protocol_version
1.0
end

# Current state number. Required. This must have a value of "1".

def current_state_number
1
end

# Setup id

def setup_id
accessory_info.setup_id
end

def setup_hash
# concat setup_id and whatever accessory_info.username is (id field in the record)
input = setup_id + device_id
# sha512 digest that
hashvalue = Digest::SHA2.new(512).digest(input)
# first 4 bytes of that
payload = hashvalue.slice(0,4)
# bas64 to string that
Base64.strict_encode64(payload)
end
end
end
37 changes: 37 additions & 0 deletions lib/ruby_home/greeter.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require 'rqrcode'
module RubyHome
module Greeter
class << self
Expand All @@ -9,6 +10,7 @@ def run
puts " │ " + pin + " │ "
puts " └────────────┘ "
puts " "
puts ascii_qrcode
end
end

Expand All @@ -23,6 +25,41 @@ def paired?
def accessory_info
AccessoryInfo.instance
end

def url
record = RubyHome.dns_service.text_record
category = record.accessory_category_identifier
password = accessory_info.password
setup_id = accessory_info.setup_id

version = 0
reserved = 0
flags = record.feature_flags

payload = 0
payload |= (version & 0x7)

payload <<= 4
payload |= (reserved & 0xf)

payload <<= 8
payload |= (category & 0xff)

payload <<= 4
payload |= (flags & 0xf)

payload <<= 27
payload |= password.gsub('-', '').to_i(10) & 0x7fffffff

"X-HM://#{payload.to_s(36).rjust(9, '0').upcase}#{setup_id.upcase}"
end

def ascii_qrcode
qrcode = RQRCode::QRCode.new(url)
terminal_qr = qrcode.to_s
terminal_qr.gsub!(' ', '⬜')
terminal_qr.gsub!('x', '⬛')
end
end
end
end
4 changes: 4 additions & 0 deletions lib/ruby_home/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,9 @@ def characteristic(characteristic_name)
characteristic.name == characteristic_name
end
end

def to_s
"#{@name} - #{@description}"
end
end
end
21 changes: 21 additions & 0 deletions lib/ruby_home/setup_id.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
module RubyHome
module SetupID
class << self
# Requirements for Setup ID of the accessory are not completely clear.
# A unique random string of letters, generated at every
# factory reset and persisted across reboots seems fine.

def generate(count=4)
id = ''
count.times {
id << random_letter
}
id
end

def random_letter
('A'..'Z').to_a[rand(26)]
end
end
end
end
2 changes: 1 addition & 1 deletion lib/ruby_home/version.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module RubyHome
VERSION = '0.2.4'
VERSION = '0.3.0'
end
2 changes: 2 additions & 0 deletions rubyhome.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,11 @@ Gem::Specification.new do |spec|
spec.add_dependency 'hkdf', '~> 0.3.0'
spec.add_dependency 'oj', '~> 3.10'
spec.add_dependency 'rbnacl', '~> 7.0'
spec.add_dependency 'rqrcode', '~> 1.2.0'
spec.add_dependency 'ruby_home-srp', '~> 1.3'
spec.add_dependency 'ruby_home-tlv', '~> 0.1'
spec.add_dependency 'sinatra', '~> 2.0'
spec.add_dependency 'webrick', '~> 1.7.0'
spec.add_dependency 'wisper', '~> 2.0'

spec.add_development_dependency 'byebug', '~> 11.0'
Expand Down
9 changes: 9 additions & 0 deletions spec/lib/setup_id_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
RSpec.describe RubyHome::SetupID do

describe 'Setup ID generator' do
it 'outputs lenght of 4' do
expect(RubyHome::SetupID.generate.length).to eql(4)
end
end

end