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

Building "learn to touch type" glove with Elixir and Arduino

tetiana12345678
September 04, 2016

Building "learn to touch type" glove with Elixir and Arduino

Knowledge share about building "touch typing glove" with Arduino and Elixir.This talk is about my journey from research, choosing tools, making flexible tiny pressure sensors, plugging them into Arduino, reading information from Arduino to the server running Elixir, rendering result in the browser.

tetiana12345678

September 04, 2016
Tweet

More Decks by tetiana12345678

Other Decks in Programming

Transcript

  1. 1. Display the text to type 2. Record key press

    events from the keyboard 3. Record finger events 4. Analyze events 5. Return match result to UI elixirconf 2016 @tetiana12345678
  2. $ mix phoenix.new typehero_web $ cd typehero_web $ mix deps.get

    $ mix phoenix.server elixirconf 2016 @tetiana12345678
  3. JS JOIN CHANNEL socket.connect() const channel = socket.channel("games:lobby", {}) channel.join()

    .receive("ok", () => { this.state.start("lobby", true, false, channel) }) elixirconf 2016 @tetiana12345678
  4. ADD CHANNEL TO SOCKET defmodule TypeheroWeb.UserSocket do use Phoenix.Socket ...

    channel "games:lobby", TypeheroWeb.LobbyChannel end elixirconf 2016 @tetiana12345678
  5. JOIN CHANNEL CALLBACK defmodule TypeheroWeb.LobbyChannel do use TypeheroWeb.Web, :channel def

    join("games:lobby", _payload, socket) do {:ok, socket} end end elixirconf 2016 @tetiana12345678
  6. PHOENIX CHANNEL defmodule TypeheroWeb.LobbyChannel do ... def handle_in("start_game", _payload, socket)

    do text = Typehero.Core.start_game(socket) push socket, "start_game", %{text: text} {:noreply, socket} end end elixirconf 2016 @tetiana12345678
  7. CORE UMBRELLA APP. INIT defmodule Typehero.Core do ... def init(state)

    do # Get text from Ecto... text_to_type = "in my opinion..." state = %{text: text_to_type, socket: %{}} {:ok, state} end end elixirconf 2016 @tetiana12345678
  8. CORE. START_GAME defmodule Typehero.Core do ... def handle_call({:start_game, socket}, _from,

    state = %{text: text}) do {:reply, text, %{state | socket: socket}} end end elixirconf 2016 @tetiana12345678
  9. CORE AS DEPS OF TYPEHERO_WEB defmodule TypeheroWeb.Mixfile do ... def

    application do [mod: {TypeheroWeb, []}, applications: [..., :typehero]] end end elixirconf 2016 @tetiana12345678
  10. JS SEND KEY EVENT onKeyPress({key}) { const event = this.createEvent(key)

    this.channel.push("key", event) } elixirconf 2016 @tetiana12345678
  11. PHOENIX CHANNEL def handle_in("key", %{"key"=> key, "id"=> id}, socket) do

    Typehero.Core.key_press(key, id) {:noreply, socket} end elixirconf 2016 @tetiana12345678
  12. CORE KEY_PRESS EVENT defmodule Typehero.Core do ... alias Typehero.EventHandler def

    handle_cast({:key, key, id}, state) do EventHandler.key_event(key, id) {:noreply, state} end end elixirconf 2016 @tetiana12345678
  13. C PROGRAM const int analogPin = A0; int finger1 =

    0; void setup() { Serial.begin(9600); } void loop() { finger1 = analogRead(analogPin); Serial.println(finger1); } elixirconf 2016 @tetiana12345678
  14. C PROGRAM. SEND VALUE ONLY ONCE bool finger1_down = false;

    int finger1_sense = 200; ... void loop() { if (finger1 > finger1_sense && !finger1_down) { finger1_down = true; Serial.print("1"); } else if (finger1 <= finger1_sense) { finger1_down = false; } } elixirconf 2016 @tetiana12345678
  15. ADD SERIAL LIBRARY defmodule TypeheroSerial.Mixfile do ... defp deps do

    [ ..., {:serial, "~> 0.1.0"}] end end elixirconf 2016 @tetiana12345678
  16. ADD CORE UMBRELLA APP AS DEPS defmodule TypeheroSerial.Mixfile do ...

    def application do [applications: [..., :typehero ], ] end end elixirconf 2016 @tetiana12345678
  17. SERIAL FORMATTER INIT defmodule TypeheroSerial.Formatter do ... def init(id) do

    id = 0 {:ok, serial} = Serial.start_link Serial.open(serial, "/dev/cu.usbmodem1421") Serial.set_speed(serial, 9600) Serial.connect(serial) {:ok, id} end end elixirconf 2016 @tetiana12345678
  18. FINGER NUMBER SEND FROM ARDUINO defmodule TypeheroSerial.Formatter do ... def

    handle_info({:elixir_serial, serial, data}, id) do cond do data =~ ~r/1/ -> Typehero.Core.finger_press(1, id) data =~ ~r/2/ -> Typehero.Core.finger_press(2, id) ... true -> nil end {:noreply, (id + 1)} end end elixirconf 2016 @tetiana12345678
  19. CORE KEY_PRESS EVENT defmodule Typehero.Core do ... def handle_cast({:key, key,

    id}, state) do EventHandler.key_event(key, id) {:noreply, state} end end elixirconf 2016 @tetiana12345678
  20. CORE FINGER_PRESS EVENT defmodule Typehero.Core do ... def handle_cast({:finger, finger,

    id}, state) do EventHandler.finger_event(finger, id) {:noreply, state} end end elixirconf 2016 @tetiana12345678
  21. EVENTHANDLER INIT defmodule Typehero.EventHandler do ... alias Typehero.Events def init

    do state = %Events{} {:ok, state} end end elixirconf 2016 @tetiana12345678
  22. KEY_EVENT defmodule Typehero.EventHandler do ... def handle_cast({:key_event, key, id}, state)

    do finger = get_event(:finger, state, id) ... end end elixirconf 2016 @tetiana12345678
  23. GET FINGER EVENT defmodule Typehero.EventHandler do ... defp get_event(:finger, %Events{finger_events:

    events}, id) do Map.get(events, id) end end elixirconf 2016 @tetiana12345678
  24. KEY_EVENT defmodule Typehero.EventHandler do ... def handle_cast({:key_event, key, id}, state)

    do finger = get_event(:finger, state, id) # event | nil new_state = process_key(finger, state, id, key) ... end end elixirconf 2016 @tetiana12345678
  25. PROCESS_KEY defmodule Typehero.EventHandler do ... defp process_key(nil, state, id, key)

    do add_event(:key, state, id, key) end end elixirconf 2016 @tetiana12345678
  26. ADD KEY EVENT TO THE EVENTS STRUCT defmodule Typehero.EventHandler do

    ... defp add_event(:key, %Events{key_events: events}, id, key) do events = Map.put(events, id, key) Map.put(%Events{}, :key_events, events) end end elixirconf 2016 @tetiana12345678
  27. PROCESS_KEY defmodule Typehero.EventHandler do ... defp process_key(finger, state, id, key)

    do match_all(key, finger, id, state) end end elixirconf 2016 @tetiana12345678
  28. MATCH_ALL defmodule Typehero.EventHandler do alias Typehero.Matcher ... defp match_all(key, finger,

    id, state) do result = Matcher.match(key, finger, Core.get_current_letter) ... end end elixirconf 2016 @tetiana12345678
  29. CORE.GET_CURRENT_LETTER defmodule Typehero.Core do ... def handle_call(:get_current_letter, _from, state =

    %{text: text}) do {:reply, String.first(text), state} end end elixirconf 2016 @tetiana12345678
  30. PROCESS_KEY defmodule Typehero.EventHandler do alias Typehero.Matcher ... defp match_all(key, finger,

    id, state) do result = Matcher.match(key, finger, Core.get_current_letter) ... end end elixirconf 2016 @tetiana12345678
  31. MATCHER.MATCH defmodule Typehero.Matcher do ... def match(key, finger, letter) do

    key = match_key(key, letter) ... end end elixirconf 2016 @tetiana12345678
  32. MATCHER.MATCH defmodule Typehero.Matcher do ... def match(key, finger, letter) do

    key = match_key(key, letter) finger = match_finger(finger, letter) ... end end elixirconf 2016 @tetiana12345678
  33. MATCHER.MATCH_FINGER defmodule Typehero.Matcher do ... defp match_finger(finger, letter) do finger

    == get_finger_number(letter) end defp get_finger_number(key) do Map.get(@finger_numbers, String.to_atom(key)) end end elixirconf 2016 @tetiana12345678
  34. MATCHER FINGER NUMBERS CONSTS defmodule Typehero.Matcher do # Right hand

    fingers @right_index 1 # index @right_middle 2 # middle @right_fourth 3 # fourth @right_pinkie 4 # pinkie @right_thumb 5 # thumb # Left hand fingers @left_index 6 # index @left_middle 7 # middle @left_fourth 8 # fourth @left_pinkie 9 # pinkie @left_thumb 10 # thumb elixirconf 2016 @tetiana12345678
  35. MATCHER FINGER NUMBERS TO LETTERS MAPPING defmodule Typehero.Matcher do ...

    @finger_numbers %{ q: @left_pinkie, a: @left_pinkie, z: @left_pinkie, w: @left_fourth, s: @left_fourth, x: @left_fourth, e: @left_middle, d: @left_middle, c: @left_middle, r: @left_index, f: @left_index, v: @left_index, t: @left_index, g: @left_index, b: @left_index, y: @right_index, h: @right_index, j: @right_index, n: @right_index, u: @right_index, m: @right_index, i: @right_middle, k: @right_middle, o: @right_fourth, l: @right_fourth, p: @right_pinkie } elixirconf 2016 @tetiana12345678
  36. MATCHER.MATCH defmodule Typehero.Matcher do ... def match(key, finger, letter) do

    key = match_key(key, letter) #true or false finger = match_finger(finger, letter) #true or false total_match(key, finger) end end elixirconf 2016 @tetiana12345678
  37. MATCHER.TOTAL_MATCH defmodule Typehero.Matcher do ... defp total_match(true, true), do: :all_match

    defp total_match(true, false), do: :right_key_wrong_finger defp total_match(false, true), do: :wrong_key_right_finger defp total_match(false, false), do: :no_match end elixirconf 2016 @tetiana12345678
  38. PROCESS_KEY defmodule Typehero.EventHandler do alias Typehero.Matcher ... defp match_all(key, finger,

    id, state) do result = Matcher.match(key, finger, Core.get_current_letter) # result of the match :all_match, :no_match, # :right_key_wrong_finger, :wrong_key_right_finger IO.inspect result ... delete_event(state, id) end end elixirconf 2016 @tetiana12345678
  39. DELETE PROCESSED EVENT FROM THE STATE defmodule Typehero.EventHandler do alias

    Typehero.Matcher ... defp delete_event(events, id) do %{events | key_events: Map.delete(events.key_events, id), finger_events: Map.delete(events.finger_events, id) } end end elixirconf 2016 @tetiana12345678
  40. HANDLE_CAST RESPONSE defmodule Typehero.EventHandler do alias Typehero.Matcher ... def handle_cast({:key_event,

    key, id}, state) do finger = get_event(:finger, state, id) new_state = process_key(finger, state, id, key) {:noreply, new_state} end end elixirconf 2016 @tetiana12345678
  41. EVENTHANDLER FINGER EVENT defmodule Typehero.EventHandler do ... def handle_cast({:finger_event, finger,

    id}, state) do key = get_event(:key, state, id) new_state = process_finger(key, state, id, finger) {:noreply, new_state} end end elixirconf 2016 @tetiana12345678
  42. EVENTHANDLER NOTIFY CORE defmodule Typehero.EventHandler do alias Typehero.Matcher ... defp

    match_all(key, finger, id, state) do result = Matcher.match(key, finger, Core.get_current_letter) # result of the match :all_match, :no_match, # :right_key_wrong_finger, :wrong_key_right_finger Core.event_handler_result(%{result: result, id: id}) delete_event(state, id) end end elixirconf 2016 @tetiana12345678
  43. CORE NOTIFY UI defmodule Typehero.Core do ... def handle_cast({:event_handler_result, payload

    = %{result: :all_match}}, state = %{text: text}) do LobbyChannel.handle_in("result", payload, state.socket) state = %{state | text: remove_first_letter(text)} {:noreply, state} end end elixirconf 2016 @tetiana12345678
  44. CORE UPDATE TEXT IN THE STATE defmodule Typehero.Core do ...

    defp remove_first_letter(text) do case String.myers_difference(text, String.first(text)) do [_, del: updated_text] -> updated_text _ -> "" end end end elixirconf 2016 @tetiana12345678
  45. CORE NOTIFY UI defmodule Typehero.Core do ... def handle_cast({:event_handler_result, payload,

    state = %{text: text}) do LobbyChannel.handle_in("result", payload, state.socket) {:noreply, state} end end elixirconf 2016 @tetiana12345678
  46. PHOENIX CHANNEL NOTIFY UI defmodule TypeheroWeb.LobbyChannel do ... def handle_in("result",

    payload, socket) do broadcast socket, "result", payload {:noreply, socket} end end elixirconf 2016 @tetiana12345678
  47. Elixir is inspiring! Getting hardware involved is fun Think about

    your umbrella apps architechture Data flow diagrams is a huge help