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

Tamashii at RubyKaigi 2017

Liang-Chi Tseng
September 20, 2017

Tamashii at RubyKaigi 2017

Presented by Henry Tseng (@lctseng)

Liang-Chi Tseng

September 20, 2017
Tweet

Other Decks in Programming

Transcript

  1. What is Tamashii Projects built with Tamashii Demo video Technical

    Details Client side Server side Conclusion Outline
  2. 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
  3. High-level API Example Buzzer: play sounds What is Tamashii buzzer

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

    display = Lcd.new display.print("RubyKaigi 2017!")
  5. 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
  6. 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
  7. 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
  8. 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
  9. 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
  10. Rails integration Model-like API What is Tamashii device = Device.find_by(serial:

    "1234") device.beep(3) Query devices Encode Transfer Decode Process command
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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")
  19. 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
  20. 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
  21. Device Wrapper MIFARE: Gem Implement from scratch FeliCa: Gem Developed

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

    Early version is single-threaded Single large loop Technical Details Client Side
  23. 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!
  24. 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
  25. 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
  26. 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
  27. 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
  28. 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
  29. 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 }
  30. 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
  31. 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
  32. 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
  33. WebSocket server with Redis From ActionCable (Rails WebSocket) Why Redis

    Cross process Support multi-process servers Passenger, Puma Technical Details Server Side
  34. 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
  35. 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
  36. 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
  37. 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)
  38. Conclusion Tamashii make it easy to Control devices with Ruby

    Integrate with Rails Future work Refactor & Optimize More different devices Conclusion
  39. 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
  40. 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