Automate your Home with Ruby

Automate your Home with Ruby

With the increasing number of home automation devices, our homes are getting smarter and smarter. How can Ruby help?

Instead of installing separate apps to control my many devices, I wanted to use HomeKit via Siri and the pre-installed Home app on any iOS device, for one unified experience.

This is a talk about how I created the first ever Ruby library for the HomeKit accessory protocol to bridge the gap between different platforms and just some of the exciting possibilities this could unleash.

067bd84ab041f6ebef468eb982a01fc2?s=128

Karl Entwistle

May 01, 2019
Tweet

Transcript

  1. 3.

    !

  2. 8.
  3. 9.
  4. 10.

    HomeKit HomeKit is a software framework for making smart home

    devices work seamlessly with iOS devices.
  5. 12.
  6. 13.

    Hey Siri • Turn on the living room lights •

    Dim the living room lights • Turn off the lights • Set the temperature to 20 degrees • Set the dinner scene • Set the living room lights to red • What temperature is the shed
  7. 14.
  8. 20.
  9. 23.
  10. 25.

    HAP Specification RFC 5054 RFC 3927 RFC 4862 RFC 7230

    RFC 7539 RFC 4122 RFC 3986 RFC 4566 RFC 3551 RFC 4585 RFC 3550 RFC 5124 256 page document directly references 12 RFCs
  11. 27.
  12. 28.
  13. 29.
  14. 30.
  15. 31.

    ~ 5. HomeKit Accessory Protocol for IP Accessories “HAP accessory

    servers must support Multicast DNS host names.”
  16. 32.
  17. 34.
  18. 35.
  19. 40.
  20. 41.
  21. 42.
  22. 43.
  23. 47.
  24. 49.

    ~ 4.15. Pairing over IP “Pairing responses are delivered as

    TLV data in the body of the HTTP response.”
  25. 51.

    require 'bindata' class TLV < BinData::Record uint8 :type_id uint8 :len

    string :val, read_length: :len end 00 01 00 06 01 01 Type Type
  26. 52.

    require 'bindata' class TLV < BinData::Record uint8 :type_id uint8 :len

    string :val, read_length: :len end 00 01 00 06 01 01 Length Length
  27. 53.

    require 'bindata' class TLV < BinData::Record uint8 :type_id uint8 :len

    string :val, read_length: :len end 00 01 00 06 01 01 Value Value
  28. 56.

    HTTP/1.1 Response HTTP/1.1 200 OK X-Content-Type-Options: nosniff Content-Type: text/html;charset=utf-8 Server:

    thin X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block Content-Length: 2641 Connection: close Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus nec purus ac nulla vulputate faucibus. Mauris sit amet ante sed massa sodales suscipit. Donec iaculis at neque eu ullamcorper. Vivamus nec aliquet sem. Nulla ornare velit neque, non pulvinar elit iaculis a. Suspendisse nec vulputate enim. Nunc sed tincidunt massa. Aliquam vitae condimentum purus. Curabitur vulputate nibh urna, ut lobortis ligula commodo nec. Fusce facilisis dui orci, ac hendrerit velit varius imperdiet. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vestibulum sed dolor eget metus rhoncus scelerisque. Praesent lorem massa, tristique ut tellus elementum, condimentum faucibus augue. Praesent eleifend eleifend vehicula. Curabitur vehicula, erat id finibus hendrerit, elit nisl iaculis nunc, et auctor mauris lorem ut odio. Integer accumsan consectetur est ut pellentesque. Donec varius odio a ultricies maximus. Pellentesque dictum, risus in elementum suscipit, magna mi eleifend risus, eu viverra libero nulla sit amet ex. Nam et odio non ex cursus accumsan. Mauris cursus sodales rhoncus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mollis, arcu quis blandit ullamcorper, ipsum nisl molestie urna, in mattis orci est id sem. Nullam placerat elementum dignissim. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam a interdum magna. Proin vel ipsum nec est egestas placerat in a leo. Duis nunc eros, varius non dolor id, consequat iaculis lorem. Curabitur non erat ipsum. Aliquam pharetra ultricies tortor, eget malesuada urna convallis vitae. Vestibulum porttitor odio dolor, posuere malesuada ante fringilla quis. Nunc sodales, est at pretium dignissim, sapien risus faucibus turpis, ut ultricies sem magna in arcu. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis dictum erat lorem, non congue nulla tincidunt eu. Ut hendrerit diam sed fermentum commodo. Aenean sagittis quis leo eget aliquet. In hac habitasse platea dictumst. Donec volutpat sollicitudin eros sed laoreet. Vivamus gravida tincidunt purus. Praesent pulvinar auctor tellus, et gravida nibh. Aliquam suscipit vulputate tellus, quis euismod massa laoreet sit amet. Etiam tortor lorem, elementum eget tellus sit amet, ultricies consectetur felis. Proin sodales sed odio sit amet accumsan. Curabitur magna nisl, mollis quis ipsum ut, mattis auctor risus. Sed lacinia mauris ac massa pellentesque, eu egestas purus convallis. Mauris id lacus ut turpis convallis condimentum. Nam hendrerit nibh non turpis tempus, et auctor felis fringilla. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
  29. 57.

    HTTP/1.1 200 OK X-Content-Type-Options: nosniff Content-Type: text/html;charset=utf-8 Server: thin X-Frame-Options:

    SAMEORIGIN X-XSS-Protection: 1; mode=block Content-Length: 2641 Connection: close Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus nec purus ac nulla vulputate faucibus. Mauris sit amet ante sed massa sodales suscipit. Donec iaculis at neque eu ullamcorper. Vivamus nec aliquet sem. Nulla ornare velit neque, non pulvinar elit iaculis a. Suspendisse nec vulputate enim. Nunc sed tincidunt massa. Aliquam vitae condimentum purus. Curabitur vulputate nibh urna, ut lobortis ligula commodo nec. Fusce facilisis dui orci, ac hendrerit velit varius imperdiet. Interdum et malesuada fames ac ante ipsum primis in faucibus. Vestibulum sed dolor eget metus rhoncus scelerisque. Praesent lorem massa, tristique ut tellus elementum, condimentum faucibus augue. Praesent eleifend eleifend vehicula. Curabitur vehicula, erat id finibus hendrerit, elit nisl iaculis nunc, et auctor mauris lorem ut odio . Integer accumsan consectetur est ut pellentesque. Donec varius odio a ultricies maximus. Pellentesque dictum, risus in elementum suscipit, magna mi eleifend risus, eu viverra libero nulla sit amet ex. Nam et odio non ex cursus accumsan. Mauris cursus sodales rhoncus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum mollis, arcu quis blandit ullamcorper, ipsum nisl molestie urna, in mattis orci est id sem. Nullam placerat elementum dignissim. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam a interdum magna. Proin vel ipsum nec est egestas placerat in a leo. Duis nunc eros, varius non dolor id, consequat iaculis lorem. Curabitur non erat ipsum. Aliquam pharetra ultricies tortor, eget malesuada urna convallis vitae. Vestibulum porttitor odio dolor, posuere malesuada ante fringilla quis. Nunc sodales, est at pretium dignissim, sapien risus faucibus turpis, ut ultricies sem magna in arcu. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis d ictum erat lorem, non congue nulla tincidunt eu. Ut hendrerit diam sed fermentum commodo. Aenean sagittis quis leo eget aliquet. In hac habitasse platea dictumst. Donec volutpat sollicitudin eros sed laoreet. Vivamus gravida tincidunt purus. Praesent pulvinar auctor tellus, et gravida nibh. Aliquam suscipit vulputate tellus, quis euismod massa laoreet sit amet. Etiam tortor lorem, elementum eget tellus sit amet, ultricies consectetur felis. Proin sodales sed odio sit amet accumsan. Curabitur magna nisl, mollis quis ipsum ut, mattis auctor risus. Sed lacinia mauris ac massa pellentesque, eu egestas purus convallis. Mauris id lacus ut turpis convallis condimentum. Nam hendrerit nibh non turpis tempus, et auctor felis fringilla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. }1024 bytes } } 792 bytes 1024 bytes
  30. 58.
  31. 59.
  32. 60.

    + +

  33. 61.

    HAP Response 0004737519cd8bf33e5793198c354ecebc7a88c529b12dcbe0daa730e9cedae6c8f873a2b8a5cc3ed004bf1c866c01584c136b6b6ac51bf 411fedb70db72949c5c879fea195ea2c30110d64cee61e6bd72aa7d86d887482d6e4fb0e38b48118892377b38f0d9ece8f55354a87354649 8da1f2d6f274a50abfd5b06e101ebf7400ffcb5b2cea8a7d80d9b3468587f3460e2e4760c7f8f2a2abd038d9b5a57666f1dc6fd5044db3c357 98b844d06964ded15fd87ac10eaeaaa5aa83f68f33147f4a272807433309ec32c937239b910f35fad4930feed6917bbf4541b38f18bf371a805 6f3ec320bcce54636a4d77d85c36b3ea53d481badb1317461a8cb8f1fe77ea82b851a6dbf0410ca4416e932897db332fdae3f996038a3d15 0aad42f163e917a64429a2e20c0873bdab424c08c4084dcb170c802b44748d4abac03a2f90943cb2dc34a12ce4817d1e5ed05fccc433828 3eb23775f6f047781db9536d7bcc890c0016a1ed7a4384cdeeee428a7147c15e401682b52f209ee6506e83d2752aed2983f40d3546a0a73c 1b37ae5d9da908e868d779f56a256e03253e63c91aeed2a122ff1f32708bea16308de8e33fa99186fa56bb2da7d892e44a4918659b2ed0ff22

    f83d64dea4d3b4b7ffa39e5bd9a691fd07e343327a7a1caaa210fbe1f339ab3b47d5881f2a6880bf7c0d0b50065caba3f153ea225a2d5f40bc ccda398356741312910ec1dc5ac73e47b6a4d1d27e26fe961200820a1dcc9506fe690bb7fc150ab6d7b9170eb6426dce09b87c1866b7c31 b38f82c394f704b4e1294327c9b70b0a1276e5ec36cda4c12375e20e257b63806245fb7adb21e6c9e0845333595bd9c3ae1bd5cb7f901ff3 98c7c27c172214ac9efcf960e1a74c4e508012c05153dea784da710b264d7c648112ccd901ce5fc810776fcaa2d199848b439909801196f6f d0b06d08c046d42830bef28818b0697fb7d5baf91d5aa4d6037048f3823b887f90763450b3dbb94092044a6114e53ee2f21786e6e795f00b8 a1f0ac9d06081797ba7c42aa0517af355e5d41d7aecc4cbd5a89f0586d256806951161ff5c3448b792f555f76826012be03814376c9cd58a8 6fe27a8597c87219371f378a12955d8ce8e415b19fccd83987d814ff2d8304e599dc48263298df61f294cdad932f30fbc6e9aa0504bc84f7c99 0894f786399d664c7dade2438b5582a769e2e54caf1dfe81858ef0e03f375706eaa86ab26121769f59ca17205f59a97a1d0c1ac9f0687608e5 34eff025a56fae679db98a01608c8d8208edc02f4070814cf17957bb28b05091f38653ae39b6a0b0be8b301dded9758dd69d203a6f0c80615 3d195a9e8791a2bdc1ca3299dccd28ba1ccc70434eeadfce406d256002b835efcc6b72d13698b070301469746b35fe5f1292dbbe2f99e82e 2fafd3374f58f20fd356fb58cb95cbfbd053e63c0af724a30c4d8000486b9402b78743570349b99d2c92377b964145d050e3ab54aecf870d63 dc5f65385ca94b8c84fa73964b6eebd1632fa927ca37f6a71cc82c652f6920e956a029e74d7d309c67af7f2a1bdf0427188dfe6eaec1918574f 00c0b996cdfeeb55c4916ef2d16e9676315d47c4ab740843e455a17cd2a319c67fabfaef71fd21e3ef980c4e6a98f2e922b7be058f73e399c6 5dd27c2bc4f23d7b4bf33cf6d3eb97127929279b0cf64ae9b7bf1f2e6d6d18fbee5a78f9a170793b75790eb342ccb3e0c5b9106a0c0c0a671 cd8bfb858f5bbd6e9bb82c4eff67703f7bbdbac50b66e3db28e83f8aeb7cec5a2ab0f7bdf95d58ef22b208dba8125b9e22256e193874aebce 2b0774ec0d1af3f3d034441c2537979841b800886d09277b7ccc3f3188deaa24bf00c397923c2d2df33d03995506c92889c943caecf151583 59a330aa0a6d26866ca4f757ffee5b29e44ea107d61b2d51b16593e1ca20b6e3deb35005dd960bf7648d8639b51dfd04575426561e359eeb 44de889d8def5181f24b39040decbb2a02522954b696afe03d5331fc6dc63397a7ec5f0cdc70808b0df65ee9c7515a8844de72c50c4d811b a2ae358e1ed8c97a8238a7b296ffc90c35ed8b0cc2b1bddc352c87730759cf3eb53e0ff6a77f4e697e611fd2bac51d12fd66f51cbf17fd2d834 3c842578a36a4e702bd54597c050dcad20d07a6dcf000daa296d8feb4d75306624b873211ed267ac1070c587a81d0977ca04f9dcf131256 76dd3bdd0e855f654957dffe8169e9c6443f97c9c253bc6b5b9ea41023355d935f0c68bdc2d01987a17cd3560a20bf13f0ca0ec01644bc1b9
  34. 64.

    Revisit Rack To use Rack, provide an "app": an object

    that responds to the call method, taking the environment hash as a parameter, and returning an Array with three elements: • HTTP response code • A hash of headers • The response body
  35. 66.

    HTTP/1.1 200 OK Content-Type: text/html Transfer-Encoding: chunked A barebones rack

    app 0004737519cd8bf33e5793198c354ecebc7a88c529b 12dcbe0daa730e9cedae6c8f873a2b8a5cc3ed004bf 1c866c01584c136b6b6ac51bf411fedb70db72
  36. 69.

    module WEBrick class HTTPServer < ::WEBrick::GenericServer def run(sock) while true

    req = create_request(@config) res = create_response(@config) server = self begin timeout = @config[:RequestTimeout] while timeout > 0 break if sock.to_io.wait_readable(0.5) break if @status != :Running timeout -= 0.5 end raise HTTPStatus::EOFError if timeout <= 0 || @status != :Running raise HTTPStatus::EOFError if sock.eof? req.parse(sock) res.request_method = req.request_method res.request_uri = req.request_uri res.request_http_version = req.http_version res.keep_alive = req.keep_alive? server = lookup_server(req) || self if callback = server[:RequestCallback] callback.call(req, res) elsif callback = server[:RequestHandler] msg = ":RequestHandler is deprecated, please use :RequestCallback" @logger.warn(msg) callback.call(req, res) end server.service(req, res) rescue HTTPStatus::EOFError, HTTPStatus::RequestTimeout => ex res.set_error(ex) rescue HTTPStatus::Error => ex @logger.error(ex.message) res.set_error(ex) rescue HTTPStatus::Status => ex res.status = ex.code rescue StandardError => ex @logger.error(ex) res.set_error(ex, true) ensure if req.request_line if req.keep_alive? && res.keep_alive? req.fixup() end res.send_response(sock) server.access_log(@config, req, res) end end break if @http_version < "1.1" break unless req.keep_alive? break unless res.keep_alive? end end end end
  37. 70.

    module WEBric class HTTPServer < ::WEBrick::GenericServer def run(sock) while true

    req = HTTPRequest.new(@config) res = HTTPResponse.new(@config) req.parse(sock) res.request_method = req.request_method res.request_uri = req.request_uri res.request_http_version = req.http_version res.keep_alive = req.keep_alive? service(req, res) res.send_response(sock) end end end end
  38. 71.

    module WEBric class HTTPServer < ::WEBrick::GenericServer def run(sock) while true

    req = HTTPRequest.new(@config) res = HTTPResponse.new(@config) req.parse(sock) res.request_method = req.request_method res.request_uri = req.request_uri res.request_http_version = req.http_version res.keep_alive = req.keep_alive? service(req, res) res.send_response(sock) end end end end
  39. 72.

    module WEBric class HTTPServer < ::WEBrick::GenericServer def run(sock) while true

    req = HTTPRequest.new(@config) res = HTTPResponse.new(@config) req.parse(sock) res.request_method = req.request_method res.request_uri = req.request_uri res.request_http_version = req.http_version res.keep_alive = req.keep_alive? service(req, res) res.send_response(sock) end end end end
  40. 73.

    module WEBric class HTTPServer < ::WEBrick::GenericServer def run(sock) while true

    req = HTTPRequest.new(@config) res = HTTPResponse.new(@config) req.parse(sock) res.request_method = req.request_method res.request_uri = req.request_uri res.request_http_version = req.http_version res.keep_alive = req.keep_alive? service(req, res) res.send_response(sock) end end end end
  41. 74.

    module WEBric class HTTPServer < ::WEBrick::GenericServer def run(sock) while true

    req = HAPRequest.new(@config) res = HAPResponse.new(@config) req.parse(sock) res.request_method = req.request_method res.request_uri = req.request_uri res.request_http_version = req.http_version res.keep_alive = req.keep_alive? service(req, res) res.send_response(sock) end end end end
  42. 75.

    module WEBric class HTTPServer < ::WEBrick::GenericServer def run(sock) while true

    req = HAPRequest.new(@config) res = HAPResponse.new(@config) req.parse(sock) res.request_method = req.request_method res.request_uri = req.request_uri res.request_http_version = req.http_version res.keep_alive = req.keep_alive? service(req, res) res.send_response(sock) end end end end
  43. 76.

    module WEBric class HTTPServer < ::WEBrick::GenericServer def run(sock) while true

    req = HAPRequest.new(@config) res = HAPResponse.new(@config) req.parse(sock) res.request_method = req.request_method res.request_uri = req.request_uri res.request_http_version = req.http_version res.keep_alive = req.keep_alive? service(req, res) res.send_response(sock) end end end end
  44. 77.

    class HAPRequest < WEBrick::HTTPRequest def parse(socket) if unencrypted? super(socket) else

    super(decrypt(socket)) end end end module WEBric class HTTPServer < ::WEBrick::GenericServer def run(sock) while true req = HAPRequest.new(@config) res = HAPResponse.new(@config) req.parse(sock) res.request_method = req.request_method res.request_uri = req.request_uri res.request_http_version = req.http_version res.keep_alive = req.keep_alive? service(req, res) res.send_response(sock) end end end end
  45. 78.

    class HAPResponse < WEBrick::HTTPResponse def send_response(socket) if unencrypted? super(socket) else

    super(encrypt(socket)) end end end module WEBric class HTTPServer < ::WEBrick::GenericServer def run(sock) while true req = HAPRequest.new(@config) res = HAPResponse.new(@config) req.parse(sock) res.request_method = req.request_method res.request_uri = req.request_uri res.request_http_version = req.http_version res.keep_alive = req.keep_alive? service(req, res) res.send_response(sock) end end end end
  46. 80.

    ~ 5.8.2 Accessory Sends Events to Controller. HomeKit Accessory Protocol

    for IP Accessories “The accessory delivers notifications by sending an event message, which is an unsolicited HTTP response, over the TCP connection established by the controller.”
  47. 83.
  48. 85.
  49. 89.

    require 'ruby_home' fan = RubyHome::ServiceFactory.create(:fan) on_characteristic = fan.characteristic(:on) on_characteristic.after_update do

    |updated_value| if updated_value == true puts "The fan is on!" else puts "The fan is off" end end RubyHome.run
  50. 90.
  51. 91.

    require 'ruby_home' fan = RubyHome::ServiceFactory.create(:fan) on_characteristic = fan.characteristic(:on) on_characteristic.after_update do

    |updated_value| if updated_value == true puts "The fan is on!" else puts "The fan is off" end end RubyHome.run
  52. 94.
  53. 98.

    require 'ruby_home' RubyHome::ServiceFactory.create(:accessory_information) air_purifier = RubyHome::ServiceFactory.create(:air_purifier, name: "Air purifier" )

    air_quality_sensor = RubyHome::ServiceFactory.create(:air_quality_sensor, name: "Air quality sensor" ) fan = RubyHome::ServiceFactory.create(:fan, name: "Fan" ) garage_door_opener = RubyHome::ServiceFactory.create(:garage_door_opener, name: "Garage door opener" ) lightbulb = RubyHome::ServiceFactory.create(:lightbulb, name: "Lightbulb" ) outlet = RubyHome::ServiceFactory.create(:outlet, name: "Outlet" ) RubyHome.run
  54. 99.
  55. 101.
  56. 102.
  57. 103.

    :accessory_information :air_purifier :air_quality_sensor :battery_service :camera_rtp_stream_management :carbon_dioxide_sensor :carbon_monoxide_sensor :contact_sensor :door :doorbell

    :fan :fan_v2 :filter_maintenance :garage_door_opener :heater_cooler :humidifier_dehumidifier :humidity_sensor :leak_sensor :light_sensor :lightbulb :lock_management :lock_mechanism :microphone :motion_sensor :occupancy_sensor :outlet :security_system :service_label :slat :smoke_sensor :speaker :stateless_programmable_switch :switch :temperature_sensor :thermostat :window :window_covering Services