Tangarine: a button-based faceplate for Tangara
#37
I've updated to the latest commit (08a6aa2b33) from the PR and I'm happy to report that everything still works, even the bottom-right button.  This is my definition for that button:

Code:
[2] = {
    click = function()
      -- Go to now-playing screen
      require("playing"):push_if_not_shown()
    end,
  },

I'm happy to no longer need the eval_on_ui_thread call!

I have no idea why it wouldn't work for you, though.  If you're using similar code, and you've already replaced the faceplate, and Emily has the same issue... it seems that either I'm doing something "wrong" (that in fact makes it work) or you're both very unlucky.  I'm probably doing something different, given those odds.  All I'm doing is a build and flash based on that latest commit, though.  I'll put my full input.lua here in case there's some other difference:

Code:
local i2c = require('i2c')
local gpio = require('gpio')
local input_device = require('input_device')
local queue = require('queue')
local playback = require('playback')
local time = require('time')
local backstack = require("backstack")

--[[
Up to 16 buttons are supported with this driver.
If you assign actions to buttons you don't have, they will appear to always be pressed, so don't do that.

Tangarine button layout:
  6  7  0

  5  8  1

  4  3  2

Actions that can be assigned per button:
[0] = {
  click = function()
    -- button was pressed and released once
  end,
  doubleclick = function()
    -- button was pressed and released twice
  end
  press = function()
    -- button was just pressed, not released yet
  end,
  longpress = function()
    -- button was held
  end,
  repeatpress = function()
    -- button is still being held after longpress
  end,
},

You can perform arbitrary actions in these functions, or return data to be wrapped into an lvgl input event.
If returning data, it should be a table containing zero or more of the keys:
{
  encoder_diff = (integer),
  encoder_button_pressed = (boolean),
}

]]

local button_map = {
  -- Top-right button
  [0] = {
    press = function()
      if (not playback.track:get()) then
        -- Restart the last played track
        queue.position:set(queue.position:get())
      end
      playback.playing:set(not playback.playing:get())
    end,
  },

  -- Right button
  [1] = {
    click = function()
      queue:next()
    end,
  },

  -- Bottom-right button
  [2] = {
    click = function()
      -- Go to now-playing screen
      require("playing"):push_if_not_shown()
    end,
  },

  -- Bottom button
  [3] = {
    -- "doing literally anything to these buttons results in movement" feels more responsive to me
    press = function()
      return { encoder_diff = 1 }
    end,
    -- Hold down for fast scroll
    repeatpress = function()
      return { encoder_diff = 10 }
    end,
    doubleclick = function()
      return { encoder_diff = 1 }
    end,
  },

  -- Bottom-left button
  [4] = {
    longpress = function()
      queue.clear()
    end,
  },

  -- Left button
  [5] = {
    click = function()
      if playback.position:get() > 3 then
        playback.position:set(0)
      else
        queue.previous()
      end
    end,
  },

  -- Top-left button
  [6] = {
    longpress = function()
      backstack.reset(require("main_menu"):new())
    end,
    click = function()
      backstack.pop()
    end
  },

  -- Top button
  [7] = {
    press = function()
      return { encoder_diff = -1 }
    end,
    repeatpress = function()
      return { encoder_diff = -10 }
    end,
    doubleclick = function()
      return { encoder_diff = -1 }
    end,
  },

  -- Center button
  [8] = {
    click = function()
      return { encoder_button_pressed = true }
    end,
  },
}

local triggers = {}

local function buttons_setup()
  for reg, value in pairs({
    [6] = 0xFF, -- direction low: all pins inputs
    [7] = 0xFF, -- direction high
    [4] = 0xFF, -- invert low: all pins active low
    [5] = 0xFF, -- invert high
  }) do
    i2c.execute(
      i2c.start(),
      i2c.write_addr(0x27, 'write'),
      i2c.write_ack(reg),
      i2c.write_ack(value),
      i2c.stop()
    )
  end

  for i = 0, 15 do
    triggers[i] = input_device.make_trigger()
  end
end

local button_states = {}
local locked = false

local function buttons_read()
  if gpio.get_faceplate_interrupt_level() == 0 then
    local input_low, input_high = i2c.execute(
      i2c.start(),
      i2c.write_addr(0x27, 'write'),
      i2c.write_ack(0), -- input low
      i2c.start(),
      i2c.write_addr(0x27, 'read'),
      i2c.read(),
      i2c.read('nack'),
      i2c.stop()
    )
    for i = 0, 7 do
      button_states[i] = (input_low & (1 << i)) ~= 0
      button_states[i + 8] = (input_high & (1 << i)) ~= 0
    end
  end

  local merged_event = {
    encoder_diff = 0,
    encoder_button_pressed = false,
  }

  for i = 0, 15 do
    local state = triggers[i]:update(button_states[i])
    if not locked and button_map[i] and button_map[i][state] then
      local ev = button_map[i][state]()
      if type(ev) == 'table' then
        if type(ev.encoder_diff) == 'number' then
          merged_event.encoder_diff = merged_event.encoder_diff + ev.encoder_diff
        end
        if type(ev.encoder_button_pressed) == 'boolean' then
          merged_event.encoder_button_pressed = (merged_event.encoder_button_pressed or ev.encoder_button_pressed)
        end
      end
    end
  end

  return merged_event
end

buttons_setup()
input_device.register_read_func(buttons_read)
input_device.register_lock_func(function()
  locked = true
end)
input_device.register_unlock_func(function()
  locked = false
end)
  Reply


Messages In This Thread
RE: Tangarine: a button-based faceplate for Tangara - by redshift - 2025-05-29, 09:30 PM

Forum Jump: