Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Tamashii at RubyKaigi 2017

0177cb9d87a24b0f12dca159d4d779dc?s=47 Liang-Chi Tseng
September 20, 2017

Tamashii at RubyKaigi 2017

Presented by Henry Tseng (@lctseng)

0177cb9d87a24b0f12dca159d4d779dc?s=128

Liang-Chi Tseng

September 20, 2017
Tweet

Other Decks in Programming

Transcript

  1. Create Rails IoT applications more easily @lctseng (Henry Tseng) RubyKaigi

    2017 https://tamashii.io
  2. What is Tamashii Projects built with Tamashii Demo video Technical

    Details Client side Server side Conclusion Outline
  3. Henry Tseng Ruby/Rails Developer GitHub/Twitter: lctseng 5xRuby.tw Ruby community in

    Taiwan Who am I
  4. None
  5. Why “Tamashii” 魂(たましい) The “Soul” of devices Ruby-based framework for

    easy integration between IoT devices and Rails Client: Raspberry PI Server: Rails Control devices with elegant Ruby code Rack-based Easy integration with Rails What is Tamashii
  6. Features Hardware component management Event-based data exchange WebSocket networking Rails

    Integration What is Tamashii
  7. Hardware component management RFID reader, motor, LCD display, buzzer High-level

    API Hide tedious device protocol What is Tamashii
  8. High-level API Example Buzzer: play sounds What is Tamashii buzzer

    = Buzzer.new buzzer.beep(3) #=> Beep, Beep, Beep!
  9. High-level API Example LCD Display: print text What is Tamashii

    display = Lcd.new display.print("RubyKaigi 2017!")
  10. High-level API Example RFID reader: read card data SUICA, ICOCA

    What is Tamashii reader = RfidReader.new if data = reader.read puts "Card data: #{data}" end
  11. Event-based data exchange Combine components instead of using individually Example:

    play a sound and show messages after reading the card What is Tamashii Event Beep! Event Event
  12. Networking Client à Server: sensor data Server à Client: updates,

    commands What is Tamashii Sensor response LCD message Server Client Sensor data RFID card number Device commands Reboot, Updates
  13. Networking with WebSocket Integration with web application High performance WebSocket

    (HTML5) Two-way communication (server push) Use the same port as HTTP Support TLS (just like HTTPS) Supported by modern web servers and frameworks (Rails) What is Tamashii
  14. Rails integration Rack-based server WebSocket for connection Redis for storage

    Device management protocol Shared by client and server Data events: RFID card data Command events: reboot, play sound What is Tamashii
  15. Rails integration Model-like API What is Tamashii device = Device.find_by(serial:

    "1234") device.beep(3) Query devices Encode Transfer Decode Process command
  16. Device Management https://youtu.be/hH6u4sJx_L4?t=26s Demo Video

  17. Check-in System for Conference Track the flow of attendees Deployed

    on RubyConf.tw 2016 17 devices 270 attendees Access Control System Remotely control the gate for entrance Not deployed yet Projects Built with Tamashii
  18. Track the flow of attendees Which sessions they attended Which

    sponsors they visited Workflow Registration stage Check-in stage Projects Built with Tamashii Check-in System
  19. Registration stage Give every attendee a RFID sticker (attach to

    their badge) Bind the attendee with their RFID sticker Projects Built with Tamashii Check-in System Register Attendee Card Number Count User A 11-22-33-44 0 User B User B 55-66-77-88 0 Registrar Device
  20. Check-in stage Devices @ entrance and sponsor booths Check-in with

    their RFID identifier Server find attendees & record to database Projects Built with Tamashii Check-in System Attendee Card Number Count User A 11-22-33-44 0 User B 55-66-77-88 0 User B 55-66-77-88 1 Check-in User B Check-in Device
  21. Check-in System https://youtu.be/hH6u4sJx_L4?t=54s Demo Video

  22. Remotely control the gate for entrance Similar to Check-in System

    Registration stage (same) Check-in stage (different) Control the behavior on Check-in devices Instead of simply recording the data Allow or deny users Projects Built with Tamashii Access Control
  23. Admin Workflow when check-in User request access Admin decide to

    allow or deny Projects Built with Tamashii Access Control Request User Gate Device Response
  24. Access Control System https://youtu.be/hH6u4sJx_L4?t=1m36s Demo Video

  25. Client side Device wrapper Concurrent model (event-based data exchange) Future-based

    asynchronized networking model Technical Details
  26. Device Wrapper Hide all low-level hardware operations Example: LCD display

    (I2C interface) Control using gem 6 bytes of binary command for “A” Technical Details Client Side [0x49, 0x4d, 0x49, 0x19, 0x1d, 0x19].each do |byte| i2c_write_byte(byte) end # => "A" Model: LCM 1602 I2C i2c
  27. Device Wrapper Focus on behavior Do not need to rewrite

    for different hardware model Technical Details Client Side module Lcd class Lcm1602I2C < Base def print(message) # Lots of tedious stuff end end end Lcd::Lcm1602I2C.new.print("A")
  28. Device Wrapper Example: RFID card reader (for metro/subway) Taiwan: MIFARE

    Easy Card, iPass Japan: FeliCa SUICA, ICOCA Technical Details Client Side Model: MFRC522 Model: PN532
  29. Device Wrapper Common operation: read the UID Technical Details Client

    Side module RfidReader class Base def read_uid raise NotImplementedError end end end module RfidReader class Mfrc522 < Base def read_uid # code for MIFARE end end end module RfidReader class Pn532 < Base def read_uid # code for FeliCa end end end
  30. Device Wrapper MIFARE: Gem Implement from scratch FeliCa: Gem Developed

    by @tenderlove https://github.com/tenderlove/nfc Technical Details Client Side mfrc522 nfc
  31. Concurrent Model Multi-thread Prevent program hangs from one component failure

    Early version is single-threaded Single large loop Technical Details Client Side
  32. Concurrent Model Example: beep after read card Single thread approach

    Technical Details Client Side reader = RfidReader.new buzzer = Buzzer.new loop do if data = reader.read # non-blocking read buzzer.beep(3) # blocking operation else sleep 0.1 # save CPU end end Thread #1 New Card Beep!
  33. What is the problem? One component stops working è whole

    program crashes! Technical Details Client Side reader = RfidReader.new buzzer = Buzzer.new # stops working loop do if data = reader.read buzzer.beep(3) # crash! else sleep 0.1 end end Thread #1 Wait for response… ? New Card
  34. Concurrent Model Multi-thread Components have worker threads Event-based Communicate by

    events Technical Details Client Side
  35. Concurrent Model Technical Details Client Side RFID Thread Event queue

    Master Thread Buzzer Thread Event queue Event Emit event Dispatch event Event Beep! New Card Emit event rfid_data Check registered components Register Register
  36. Technical Details Client Side master = Master.new reader = RfidReader.new

    buzzer = Buzzer.new master.register(buzzer) master.register(reader) buzzer.on :rfid_data do |payload| self.beep(3) # run on another thread end loop do if data = reader.read reader.emit(Event.new(:rfid_data, data)) else sleep 0.1 end end RFID Thread Master Thread Buzzer Thread
  37. Concurrent Model Good extensibility Only need to register more handler

    Example: add LCD display Technical Details Client Side display = Lcd.new master.register(display) display.on :rfid_data do |payload| self.print(payload) end
  38. Concurrent Model Restart individual component Call clean-up method Restart worker

    thread Technical Details Client Side
  39. Future-based asynchronized networking Cooperate with event-based model Request & response

    are no longer paired Server can discard the client request (no response) Server can push data to clients (no request) Technical Details Client Side
  40. Future-based asynchronized networking Do not know when can receive response

    Future-based request: request with callback Technical Details Client Side Request Pool Request Type: RFID_DATA Payload: 11-22-33-44 Callback: beep_count = response[:auth] ? 1 : 3 emit(Event.new(:beep, beep_count)) Send request Recv response Response Type: RFID_DATA_RESPONSE Payload: { :auth => true }
  41. Future-based asynchronized networking Future-based request Objects (gem ) Every request

    is a With callbacks and parameters Called when fulfilled Fulfilled è Received response Technical Details Client Side Future concurrent-ruby Future
  42. Future-based asynchronized networking Request Pool Stores future-based requests Timeout: invalidate

    and call callback Technical Details Client Side Request Pool Pending Pending Pending Send Recv Response Send Expired Request Timeout Fulfilled
  43. Future-based asynchronized networking Network worker thread Sending the request Dispatch

    response to request Emit events (from server command) WebSocket and IO handling gem gem Technical Details Client Side websocket-driver nio4r
  44. nfc Client Architecture Overview Technical Details Client Side concurrent-ruby i2c

    nio4r mfrc522 websocket-driver pi_piper pi_piper
  45. Server side WebSocket server with Redis Rails Hook Model Wrapping

    Technical Details
  46. WebSocket server with Redis From ActionCable (Rails WebSocket) Why Redis

    Cross process Support multi-process servers Passenger, Puma Technical Details Server Side
  47. WebSocket server with Redis Exchange messages between clients Redis publish/subscription

    Callback when receive messages Technical Details Server Side Redis Client #1 Client #3 Client #2 Subscribe Publish message: “Hello” subscribe('_tamashii_internal') do |on| on.message do |_, message| process_message(message) end end
  48. Rails Hook Mount Tamashii in Initialize in Receive data from

    client devices Params: the device data User defined Tamashii controller In Do everything that is available in Rails environment Technical Details Server Side config/routes.rb config/initializers app/tamashii/*.rb
  49. Technical Details Server Side class TamashiiRailsHook < Tamashii::Hook def call(request_packet)

    client = @env[:client] json = JSON.parse(request_packet.body) packet_id = json['id'] card_id = json['ev_body'] type = Tamashii::Type::RFID_RESPONSE_JSON body = { id: packet_id, ev_body: { auth: card_id == "1234" }.to_json }.to_json response_packet = Tamashii::Packet.new( type, client.tag, body ) client.send(response_packet.dump) true end end Extract data Create response Write to client
  50. Model Wrapping Wrap whole device into model-like object Control devices

    with high-level API Send commands to devices Technical Details Server Side device = Device.find_by(serial: "1234") device.beep(3)
  51. Special callbacks Like in ActiveRecord Technical Details Server Side on_connect

    on_auth_success on_closed before_save
  52. Server Architecture Overview Technical Details Server Side concurrent-ruby rack websocket-driver

    nio4r redis Connection Storage Rails Integration
  53. Conclusion Tamashii make it easy to Control devices with Ruby

    Integrate with Rails Future work Refactor & Optimize More different devices Conclusion
  54. Buzzer SFM-27-W via PWM RFID Reader MFRC522 via SPI PN532

    via UART LCD LCM 1602 via I2C Keyboard TTP229 via GPIO 4x4 Button Matrix via GPIO Supported Devices On Raspberry PI 3
  55. Websocket server Websocket client Client module on Raspberry PI Check-in

    system based on Tamashii (Rails app) Repositories tamashii tamashii-client tamashii-agent tamashii-manager tamashii-common tamashii-checkin Server module based on Rack Shared library More information at https://tamashii.io
  56. Thank You! https://tamashii.io