Skip to content
Matteo Dall'Ombra
Menu
  • My Newsletter
  • My ETSY Shop
Menu

Project Tako Update #1 – Room Navigation

April 2, 2025April 8, 2025

It’s only been a few days since the intro to the project, but I’ve already worked on a UI update: room navigation. As I have a decent amount of devices spread around the house, I wanted an easy way to get to all of them without having to deal with one long list.

So, I started tinkering with the idea of room navigation and I managed to get to this simple setup.

Listen how clicky those buttons are!!

For now, I’m focusing purely on UI, navigation and interaction with HA. You can also see it’s all very snappy and the on/off toggle happens as fast as it would if I went to the switch on the wall.

Also, currently everything is hardcoded in the yaml code, but ideally I would want to get to a point where maybe a Blueprint could allow for an easier setup and management of the remote, so it’s easier to pull in new device, or remove old ones.

You may have also noticed the Apple TV control is not there. I completely removed it for now, as in my original POC it wasn’t really working at all. I’m breaking down the project in smaller chunks, making each step more manageable. So for now it’s about home control only.

If you are interested, you can check out the code so far:

spi:
  clk_pin: GPIO18
  mosi_pin: GPIO23

display:
  - platform: st7735
    id: tft_display
    model: "INITR_BLACKTAB"
    device_width: 128
    device_height: 160
    col_start: 0
    row_start: 0
    update_interval: 500ms
    cs_pin: GPIO15
    dc_pin: GPIO22
    reset_pin: GPIO4
    rotation: 0
    lambda: |-
      const int item_height = 36;
      const int left_padding = 4;
      const int top_padding = 6;
      const int content_width = 120;
      const int max_chars_per_line = 18;

      const int total_pages = 3;
      const std::string room_names[total_pages] = {"Kitchen", "Living Room", "Office"};

      struct Device {
        std::string name;
        std::string entity_id;
      };

      std::vector<Device> pages[total_pages] = {
        { {"Couch Light", "light.kitchen_light_switch_switch"}, {"Spotlights", "light.kitchen_light_switch_switch_2"} },
        { {"Ceiling Lights", "switch.living_room_light_switch"} },
        { {"Ceiling Lights", "switch.magic_switch_s1e_52d3_switch_1"} }
      };

      int page = id(current_page) % total_pages;
      const auto& devices = pages[page];
      int index = id(selected_index) % devices.size();

      // Draw header
      it.printf(64, 0, id(font2), TextAlign::TOP_CENTER, "%s", room_names[page].c_str());
      it.line(0, 16, 128, 16, Color::WHITE);

      // Helper: word-wrap
      auto split_name = [](const std::string &text, int max_len) -> std::pair<std::string, std::string> {
        if (text.length() <= max_len) return {text, ""};
        size_t split_pos = text.rfind(' ', max_len);
        if (split_pos == std::string::npos) split_pos = max_len;
        std::string first = text.substr(0, split_pos);
        std::string second = text.substr(split_pos);
        if (second[0] == ' ') second = second.substr(1);
        return {first, second};
      };

      // Render devices
      int start_y = 20;
      for (int i = 0; i < devices.size(); i++) {
        int y = start_y + i * item_height;
        bool selected = (i == index);
        auto [line1, line2] = split_name(devices[i].name, max_chars_per_line);

        if (selected) {
          it.filled_rectangle(0, y, 128, item_height, Color::WHITE);
          it.printf(left_padding, y + top_padding, id(font2), Color::BLACK, TextAlign::TOP_LEFT, "> %s", line1.c_str());
          if (!line2.empty()) {
            it.printf(left_padding + 12, y + top_padding + 14, id(font2), Color::BLACK, TextAlign::TOP_LEFT, "%s", line2.c_str());
          }
        } else {
          it.printf(left_padding, y + top_padding, id(font2), Color::WHITE, TextAlign::TOP_LEFT, "  %s", line1.c_str());
          if (!line2.empty()) {
            it.printf(left_padding + 12, y + top_padding + 14, id(font2), Color::WHITE, TextAlign::TOP_LEFT, "%s", line2.c_str());
          }
        }
      }

      // Page indicator at bottom
      std::string indicator = "";
      for (int i = 0; i < total_pages; i++) {
        indicator += (i == page) ? "* " : "- ";
      }
      it.printf(64, 158, id(font2), TextAlign::BOTTOM_CENTER, "%s", indicator.c_str());

font:
  - file: "OpenSans-Regular.ttf"
    id: font1
    size: 16
  - file: "OpenSans-Regular.ttf"
    id: font2
    size: 12

globals:
  - id: selected_index
    type: int
    initial_value: '0'
  - id: current_page
    type: int
    initial_value: '0'

binary_sensor:
  - platform: gpio
    pin:
      number: GPIO27
      mode: INPUT_PULLUP
      inverted: true
    name: "Up Button"
    on_press:
      then:
        - lambda: |-
            std::vector<int> sizes = {2, 1, 1};
            int page = id(current_page) % 3;
            int max_index = sizes[page] - 1;
            id(selected_index) = (id(selected_index) + max_index) % (max_index + 1);

  - platform: gpio
    pin:
      number: GPIO26
      mode: INPUT_PULLUP
      inverted: true
    name: "Down Button"
    on_press:
      then:
        - lambda: |-
            std::vector<int> sizes = {2, 1, 1};
            int page = id(current_page) % 3;
            int max_index = sizes[page] - 1;
            id(selected_index) = (id(selected_index) + 1) % (max_index + 1);

  - platform: gpio
    pin:
      number: GPIO13
      mode: INPUT_PULLUP
      inverted: true
    name: "Enter Button"
    on_press:
      then:
        - homeassistant.service:
            service: homeassistant.toggle
            data:
              entity_id: !lambda |-
                std::vector<std::vector<std::string>> ids = {
                  {"light.kitchen_light_switch_switch", "light.kitchen_light_switch_switch_2"},
                  {"switch.living_room_light_switch"},
                  {"switch.magic_switch_s1e_52d3_switch_1"}
                };
                int page = id(current_page) % 3;
                return ids[page][id(selected_index) % ids[page].size()];

  - platform: gpio
    pin:
      number: GPIO33
      mode: INPUT_PULLUP
      inverted: true
    name: "Left Button"
    on_press:
      then:
        - lambda: |-
            id(current_page) = (id(current_page) + 2) % 3;
            id(selected_index) = 0;

  - platform: gpio
    pin:
      number: GPIO32
      mode: INPUT_PULLUP
      inverted: true
    name: "Right Button"
    on_press:
      then:
        - lambda: |-
            id(current_page) = (id(current_page) + 1) % 3;
            id(selected_index) = 0;

Post navigation

← Introducing Project Tako – A Fully Custom Remote for Home Assistant
Project Tako Update #2 – Better UI & GitHub Repo →

© 2025 Matteo Dall'Ombra | Powered by Minimalist Blog WordPress Theme
Menu
  • My Newsletter
  • My ETSY Shop