Portada! Creando un reloj binario (parte final!)

Cómo se le dice?

  • Masking tape

    Votes: 14 70,0%
  • Cinta de papel

    Votes: 0 0,0%
  • Cinta para enmascarar

    Votes: 4 20,0%
  • Permacel

    Votes: 0 0,0%
  • Pegote

    Votes: 1 5,0%
  • Otro...?

    Votes: 1 5,0%

  • Total voters
    20

unreal4u

I solve problems.
Miembro del Equipo
ADMIN
Se incorporó
2 Octubre 2005
Mensajes
13.599
Finalmente, el producto final!


Y finalmente, aquí está! El último artículo de esta serie que mostrará el resultado final y mucho más! Así que si no has leído nada de esta serie de artículos, te sugiero empezar por el principio: aquí establecí cuáles iban a ser los requisitos del reloj. En la segunda parte en cambio le di un buen miro a las partes que había que comprar y por mientras que esperaba escribí una buena parte del código que iba a necesitar. Una vez que ya había llegado todo, en la cuarta parte empecé a hacer pruebas funcionales y en la quinta parte finalmente armé un cajón y me decidí por el diseño final.

Hoy... les voy a mostrar cómo quedó el resultado final así que primero lo primero:

Parte 8: Todo el código​


Hasta el momento he mostrado pedazos aquí y allá del código pero nunca el código completo, así que la voy a hacer corta y aquí está todo el código que ocupé:

YAML:
esphome:
  name: binary-clock
  friendly_name: binary-clock
  comment: Controls a 5x6 matrix of lights and several sensors that interact with each other

esp32:
  board: nodemcu-32s
  framework:
    type: arduino

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: "xxxxx"

ota:
  password: "xxxxx"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Binary-Clock Fallback Hotspot"
    password: "xxxxx"

captive_portal:

mqtt:
  broker: !secret mqtt_host
  username: !secret mqtt_user
  password: !secret mqtt_password
  client_id: binary-clock
  id: mqtt_client

font:
  - file: "gfonts://Roboto"
    id: font_time
    size: 50

  - file:
      type: gfonts
      family: Roboto
      weight: 400
    id: font_date
    size: 15

  - file:
      type: gfonts
      family: Arimo
    id: font_notifications
    size: 13

globals:
  # Clock brightness is a number that goes from 0 (do NOT dim) up to 100 (make the led matrix dimmer)
  # Its value depend on the brightness of the light sensor
  - id: clock_brightness
    type: int
    restore_value: yes
    initial_value: '0'

  # Will keep an internal track of the amount of notifications shown, in order to know if we are in "notification mode" or normal mode
  - id: mqtt_notification_counter
    type: int
    restore_value: no
    initial_value: '0'

  # MQTT subscription to know whether TV is turned or not. Overrides brightness setting and sets it to maximum dim
  - id: tv_downstairs_on
    type: bool
    restore_value: yes
    initial_value: 'false'

  # Timer after which we should cancel the menu action from an inactive rotary encoder operation
  - id: menu_timer
    type: int
    restore_value: no
    initial_value: '0'

  # Holds the selected page in short-term memory, in order to navigate through them
  # I've decided unilaterally that submenu's will inherit their parent's id, so a submenu of option 4 will be 41, 42, 43, etc.
  # This might become an issue if I have more than 10 categories, at which point I should really start asking myself whether I can simplify the UI...
  - id: menu_selected_page
    type: int
    restore_value: no
    initial_value: '0'

  # We have the possibility of disabling the matrix clock entirely, exposed to HA
  - id: clock_enabled_flag
    type: bool
    restore_value: yes
    initial_value: 'true'

  # Optionally, we can set a timer after which the clock will enable itself automatically. Exposed to HA
  - id: disable_clock_timer
    type: int
    restore_value: no
    initial_value: '0'

  # The entity in HA is expressed in hours, while we internally keep track of it in seconds
  - id: disable_clock_multiplier
    type: int
    restore_value: no
    initial_value: '3600'

  # Whether the PIR sensor has detected movement or not
  - id: movement_detected
    type: bool
    restore_value: no
    initial_value: 'true'

# Since we have a menu timer, this interval runs in an eternal loop every second and executes stuff if we are navigating through the menu
interval:
  - interval: 1sec
    then:
      - lambda: |-
          if (id(disable_clock_timer) > 0) {
            if (id(clock_enabled_flag) != false) {
              id(frontend_clock_enabled).turn_off();
            }

            id(disable_clock_timer)--;
            if (id(disable_clock_timer) % id(disable_clock_multiplier) == 0) {
              ESP_LOGD("interval-timer", "Time left: %d seconds, %d hours", id(disable_clock_timer), ceil(id(disable_clock_timer) / id(disable_clock_multiplier)));
              auto call = id(frontend_clock_timer_disable).make_call();
              call.set_value(ceil(id(disable_clock_timer) / id(disable_clock_multiplier)));
              call.perform();

              if (id(disable_clock_timer) == 0) {
                id(frontend_clock_enabled).turn_on();
              }
            }
          }

          if (id(menu_timer) != 0) {
            id(menu_timer)--;
            ESP_LOGD("interval-timer", "Timer menu is running: %d", id(menu_timer));

            if (id(menu_timer) == 0) {
              id(menu_selected_page) = 0;
            }
          }
      # TODO I hate the following piece of code. Find out a way to generically solve this mess
      - display.page.show: !lambda |-
          if (id(menu_selected_page) == 1) {
            return id(menu_selected_1);
          } else if (id(menu_selected_page) == 2 ) {
            return id(menu_selected_2);
          } else if (id(menu_selected_page) == 3) {
            return id(menu_selected_3);
          } else if (id(menu_selected_page) == 4) {
            return id(menu_selected_4);
          } else if (id(menu_selected_page) == 41) {
            return id(menu_selected_41);
          } else if (id(menu_selected_page) == 42) {
            return id(menu_selected_42);
          } else if (id(menu_selected_page) == 43) {
            return id(menu_selected_43);
          }

          return id(time_page);

# Sets the time on the device based on the information coming from HA
time:
  - platform: homeassistant
    id: local_time
    # Denotes Amsterdam: https://github.com/nayarsystems/posix_tz_db/blob/master/zones.csv
    timezone: CET-1CEST,M3.5.0,M10.5.0/3

i2c:
  sda: 21
  scl: 22

text_sensor:
  # Exposes the version of the firmware to HA
  - platform: version
    name: version

  # Subscribes to the notifications/public MQTT topic, not exposed
  - platform: mqtt_subscribe
    name: "Public notifications"
    id: broker_notifications
    internal: True
    topic: notifications/public
    on_raw_value:
      then:
        - lambda: |-
            id(mqtt_notification_counter)++;
        - display.page.show: notification_page
        - component.update: oled_display

  # Subscribes to the sensors/tv-downstairs MQTT topic, not exposed
  - platform: mqtt_subscribe
    name: "TV Downstairs is on"
    id: tv_downstairs_is_on
    internal: True
    topic: sensors/tv-downstairs
    on_value:
      then:
        - lambda: |-
            ESP_LOGD("sensor_value", "%s", x);
            id(tv_downstairs_on) = false;
            return;

number:
  # Exposes a timer (in hours) to HA that will disable clock mode
  platform: template
  id: frontend_clock_timer_disable
  name: "Timer clock disable"
  unit_of_measurement: hours
  optimistic: true
  mode: slider
  max_value: 6
  min_value: 0
  step: 1
  set_action:
    - lambda: |-
        id(disable_clock_timer) = x * id(disable_clock_multiplier);
        if (x == 0 && id(clock_enabled_flag) == false) {
          id(frontend_clock_enabled).turn_on();
        }

sensor:
  # Exposes the uptime in seconds to HA
  - platform: uptime
    name: "uptime"
    update_interval: 60s

  # Exposes wifi signal strength sensor to HA
  - platform: wifi_signal
    name: "WiFi signal"
    update_interval: 10s

  # Brightness sensor, used to calculate max. clock brightness. Exposed to HA
  - platform: adc
    pin: 33
    name: "Brightness"
    update_interval: 16s
    device_class: illuminance
    unit_of_measurement: v
    filters:
      - lambda: |-
          if (id(tv_downstairs_on) == false) {
            if (x >= 1.0) {
              id(clock_brightness) = 90;
            } else if (x <= 0.7) {
              id(clock_brightness) = 0;
            } else {
              id(clock_brightness) = (1 - x) * 300;
            }
            ESP_LOGD("clock_brightness", "Setting clock brightness to %d, multiplier is %.04f", id(clock_brightness), x);
          }
          return x;

  # Temperature and humidity sensor. Exposed to HA
  - platform: dht
    pin: 17
    model: AM2302
    temperature:
      name: "Temperature"
      accuracy_decimals: 1
    humidity:
      name: "Humidity"
      accuracy_decimals: 1
    update_interval: 60s

  # Rotary encoder operations. Exposed to HA
  - platform: rotary_encoder
    name: "Rotary Encoder"
    pin_a: 5
    pin_b: 16
    resolution: 1
    accuracy_decimals: 0
    on_clockwise:
      - lambda: |-
          id(menu_timer) = 10;
          if (id(menu_selected_page) == 4 || id(menu_selected_page) == 43) {
            return;
          }
          id(menu_selected_page)++;
          return;
      - display.page.show: !lambda |-
          if (id(menu_selected_page) == 1) {
            return id(menu_selected_1);
          } else if (id(menu_selected_page) == 2 ) {
            return id(menu_selected_2);
          } else if (id(menu_selected_page) == 3) {
            return id(menu_selected_3);
          } else if (id(menu_selected_page) == 4) {
            return id(menu_selected_4);
          } else if (id(menu_selected_page) == 41) {
            return id(menu_selected_41);
          } else if (id(menu_selected_page) == 42) {
            return id(menu_selected_42);
          }
          return id(menu_selected_43);
      - lambda: |-
          ESP_LOGD("display-page", "menu selected page: %d", id(menu_selected_page));
      - component.update: oled_display
    on_anticlockwise:
      - lambda: |-
          id(menu_timer) = 10;
          if (id(menu_selected_page) == 1 || id(menu_selected_page) == 41) {
            return;
          }
      
          if (id(menu_selected_page) == 0) {
            id(menu_selected_page) = 5;
          }
          id(menu_selected_page)--;
          return;
      - display.page.show: !lambda |-
          if (id(menu_selected_page) == 1) {
            return id(menu_selected_1);
          } else if (id(menu_selected_page) == 2 ) {
            return id(menu_selected_2);
          } else if (id(menu_selected_page) == 3) {
            return id(menu_selected_3);
          } else if (id(menu_selected_page) == 4) {
            return id(menu_selected_4);
          } else if (id(menu_selected_page) == 41) {
            return id(menu_selected_41);
          } else if (id(menu_selected_page) == 42) {
            return id(menu_selected_42);
          }
          return id(menu_selected_43);
      - component.update: oled_display

switch:
  # Exposes a switch in HA that enables or disabled clock mode
  - platform: template
    name: "Clock display enabled"
    id: frontend_clock_enabled
    optimistic: true
    lambda: |-
      return id(clock_enabled_flag);
    turn_on_action:
      - lambda: |-
          id(clock_enabled_flag) = true;
          if (id(movement_detected) == true) {
            id(led_matrix_light).turn_on().set_brightness(1.0).set_effect("Easy Binary clock").perform();
          }
      - number.to_min:
          id: frontend_clock_timer_disable
    turn_off_action:
      - lambda: |-
          id(clock_enabled_flag) = false;
      - light.turn_off: led_matrix_light

binary_sensor:
  # PIR sensor, exposed to HA
  - platform: gpio
    pin: 27
    name: "PIR sensor"
    device_class: motion
    id: pir_motion_sensor
    filters:
      - delayed_off: 30s
      - lambda: |-
          if (id(clock_enabled_flag) == true) {
            if (x) {
              id(movement_detected) = true;
              // TODO Resume with the effect we ended the last time
              id(led_matrix_light).turn_on().set_brightness(1.0).set_effect("Easy Binary clock").perform();
            } else {
              id(movement_detected) = false;
              id(led_matrix_light).turn_off().perform();
            }
          }
          return x;

  # The rotary encoder also has a button. Exposed to HA (not really necessary I think?)
  - platform: gpio
    pin: 13
    id: rotary_button_press
    name: "Rotary Button Press"
    filters:
      - invert
    on_press:
      then:
        - lambda: |-
            if (id(menu_selected_page) == 1) {
              id(frontend_clock_enabled).turn_on();
              id(led_matrix_light).
                turn_on().
                set_brightness(1.0).
                set_effect("Easy Binary clock").
                perform();
            } else if (id(menu_selected_page) == 2) {
              id(frontend_clock_enabled).turn_on();
              id(led_matrix_light).
                turn_on().
                set_brightness(1.0).
                set_effect("Difficult Binary clock").
                perform();
            } else if (id(menu_selected_page) == 3) {
              id(frontend_clock_enabled).turn_on();
              id(led_matrix_light).
                turn_on().
                set_brightness(1.0).
                set_effect("Time percentage").
                perform();
            } else if (id(menu_selected_page) == 4) {
              id(menu_selected_page) = 41;
            } else if (id(menu_selected_page) == 41) {
              id(menu_selected_page) = 4;
            } else if (id(menu_selected_page) == 42) {
              id(frontend_clock_timer_disable).
                make_call().
                set_value(1).
                perform();
            } else if (id(menu_selected_page) == 43) {
              id(frontend_clock_timer_disable).
                make_call().
                set_value(3).
                perform();
            }
            return;
        - display.page.show: !lambda |-
            ESP_LOGD("button-press", "Showing page for menu %d", id(menu_selected_page));
            if (id(menu_selected_page) == 4) {
              return id(menu_selected_4);
            } else if (id(menu_selected_page) == 41) {
              return id(menu_selected_41);
            }
            return id(time_page);
        - component.update: oled_display

light:
  # The actual LED strip as an entity
  - platform: fastled_clockless
    chipset: WS2812B
    pin: 18
    num_leds: 30
    rgb_order: GRB
    name: "led_matrix"
    id: led_matrix_light
    internal: False
    restore_mode: RESTORE_AND_ON
    effects:
      #  We have several "effects" which are actually modes of operation
      - addressable_lambda:
          name: "Easy Binary clock"
          update_interval: 100ms
          lambda: |-
            auto time = id(local_time).now();
            if (!time.is_valid()) {
              return;
            }

            // Always clear screen before a new run
            it.all() = Color(0, 0, 0);

            Color purple = Color(0xFF00FF);
            //Color warm_white = Color(0xD5CA97); // Looks great with -100 color
            Color warm_white = Color(0xD7974F); // Try this one out with the smoke grey screen

            int column = 6;
            const int TIME_PARTS [column] = {
              floor(time.hour / 10),   // first_hour_digit
              (int) time.hour % 10,    // last_hour_digit
              floor(time.minute / 10), // first_minute_digit
              (int) time.minute % 10,  // last_minute_digit
              floor(time.second / 10), // first_second_digit
              (int) time.second % 10,  // last_second_digit
            };

            int row = 4;
            const int POSSIBLE_NUMBERS [row] = { 1, 2, 4, 8 };

            for (column = 5; column >= 0; column--) {
              // The base color that will illuminate hours and minutes
              Color chosen_color = warm_white;

              // Seconds have a distinct color (visual aid)
              if (column >= 4) {
                // Seconds are always a bit less bright than minutes and hours
                chosen_color = purple - 25;
              }

              // Include the measurements of the light sensor to determine LED brightness
              chosen_color -= id(clock_brightness);

              int tmp = TIME_PARTS[column];
              for (row = 3; row >= 0; row--) {
                int current = POSSIBLE_NUMBERS[row];
                if (tmp >= current && (tmp & current) != 0) {
                  // Calculates row offset using "row" and adds up the chosen column using "column"
                  it[(6 * (3 - row)) + column] = chosen_color;
                  tmp -= current;
                }
              }
            }

      - addressable_lambda:
          # TODO Dirty code, needs a cleanup
          name: "Difficult Binary clock"
          update_interval: 1s
          lambda: |-
            auto time = id(local_time).now();
            if (!time.is_valid()) {
                return;
            }

            it.all() = Color(0, 0, 0);

            // Initialize colors always with full brightness
            Color purple = Color(0xFF00FF);
            Color white  = Color(0xFFFFFF);
            //Color warm_white = Color(0xFDF4DC);
            Color warm_white = Color(0xD5CA97); // Looks great with -100 color
            Color experiment = Color(0xD7974F);

            int time_parts[3] = {
                time.hour,
                time.minute,
                time.second,
            };

            for (int i = 2; i >= 0; i--) {
                Color chosen_color = experiment;
                int tmp = 0;

                if (i == 2) {
                    // Seconds are always a bit less bright than minutes and hours
                    chosen_color = purple - 25;
                    // Somehow if I use i to retrieve the seconds, it goes totally haywire. If I use a numeric constant, it accepts it?
                    tmp = time_parts[2];
                    //ESP_LOGD("difficult", "Tmp: %d ; Index: %d ; Calculated: %d ; i minus 1: %d", tmp, i, time_parts[i], time_parts[i - 1]);
                } else {
                    tmp = time_parts[i];
                }

                chosen_color -= id(clock_brightness);

                //ESP_LOGD("difficult", "Tmp: %d ; Index: %d ; Calculated: %d ; i minus 1: %d", tmp, i, TIME_PARTS[i], TIME_PARTS[i - 1]);
                if (tmp >= 32 && tmp % 32 < 32) {
                    it[(i * 12) + 5] = chosen_color;
                    tmp += 0 - 32;
                }
                if (tmp >= 16 && tmp % 16 < 16) {
                    it[(i * 12) + 4] = chosen_color;
                    tmp += 0 - 16;
                }
                if (tmp >= 8 && tmp % 8 < 8) {
                    it[(i * 12) + 3] = chosen_color;
                    tmp += 0 - 8;
                }
                if (tmp >= 4 && tmp % 4 < 4) {
                    it[(i * 12) + 2] = chosen_color;
                    tmp += 0 - 4;
                }
                if (tmp >= 2 && tmp % 2 < 2) {
                    it[(i * 12) + 1] = chosen_color;
                    tmp += 0 - 2;
                }
                if (tmp >= 1 && tmp % 1 < 1) {
                    it[(i * 12) + 0] = chosen_color;
                }
            }

      - addressable_lambda:
          # TODO Dirty code, needs a cleanup
          name: "Time percentage"
          update_interval: 1s
          lambda: |-
            int night_compensation = 50;

            auto time = id(local_time).now();
            if (!time.is_valid()) {
                return;
            }

            it.all() = Color(0, 0, 0);

            // Initialize colors always with full brightness
            Color purple = Color(0xFF00FF);
            Color white  = Color(0xFFFFFF);
            //Color warm_white = Color(0xFDF4DC);
            Color warm_white = Color(0xD5CA97); // Looks great with -100 color
            Color experiment = Color(0xD7974F);

            int i = 0;

            int hour_leds = (int) floor(time.hour / 4);
            int minute_leds = (int) floor(time.minute / 5);
            int second_leds = (int) floor(time.second / 5);

            int max_brightness = 150;

            //ESP_LOGD("percentage", "Plotting hour %d, minute %d, second %d as %d, %d and %d", time.hour, time.minute, time.second, hour_leds, minute_leds, second_leds);

            for (i = 0; i < hour_leds; i++) {
                it[i] = experiment - night_compensation;
            }
            int rest_hours = time.hour % 4;
            if (rest_hours > 0) {
                int percentage = rest_hours * 25;
                int absolute_brightness = (int) floor(150 - (150 * percentage / 100));
                it[i] = experiment - night_compensation - absolute_brightness;
            }

            for (i = 0; i < minute_leds; i++) {
                it[6 + i] = experiment - night_compensation;
            }
            int rest_minutes = time.minute % 5;
            if (rest_minutes > 0) {
                int percentage = rest_minutes * 20;
                int absolute_brightness = (int) floor(150 - (150 * percentage / 100));
                it[6 + i] = experiment - night_compensation - absolute_brightness;
            }

            for (i = 0; i < second_leds; i++) {
                it[18 + i] = purple - night_compensation;
            }
            int rest_seconds = time.second % 5;
            if (rest_seconds > 0) {
                int percentage = rest_seconds * 20;
                int absolute_brightness = (int) floor(150 - (150 * percentage / 100));
                it[18 + i] = purple - night_compensation - absolute_brightness;
            }

display:
  # The small OLED screen operation
  - platform: ssd1306_i2c
    model: "SSD1306 128x64"
    reset_pin: 0
    address: 0x3C
    update_interval: 1s
    id: oled_display
    pages:
      # I do use pages for this, since it allows me to keep code simpler. Bit repetitive for menus, but easier to read
      - id: time_page
        lambda: |-
          it.fill(COLOR_OFF);
          auto time = id(local_time).now();

          it.strftime(0, 0, id(font_time), "%H", time);
          it.strftime(65, 0, id(font_time), "%M", time);
          if ((int) time.second % 2 == 1) {
            it.printf(55, 0, id(font_time), ".");
          } else {
            it.printf(55, 0, id(font_time), " ");
          }
          it.strftime(32, 50, id(font_date), "%d-%m-%Y", time);
  
      - id: notification_page
        lambda: |-
          int NOTIFICATION_TIMER = 8;

          static int notification_seconds_counter = NOTIFICATION_TIMER;
          static struct {
            int notification_counter = 0;
            const char* message = "";
          } notification;

          if (notification.notification_counter != id(mqtt_notification_counter)) {
            notification.message = id(broker_notifications).state.c_str();
            notification.notification_counter = id(mqtt_notification_counter);
            // We might have gotten a new message while an old one was still displaying, reset counter
            notification_seconds_counter = NOTIFICATION_TIMER;
          }

          it.fill(COLOR_OFF);
          it.rectangle(0, 0, 128, 64);

          it.printf(2, 2, id(font_notifications), "%s", notification.message);
          it.printf(2, 47, id(font_date), "Hides in %d", notification_seconds_counter);
      
          notification_seconds_counter -= 1;
          if (notification_seconds_counter == 0) {
            notification_seconds_counter = NOTIFICATION_TIMER;
            it.show_page(id(time_page));
          }

      - id: menu_selected_1
        lambda: |-
          it.fill(COLOR_OFF);
          it.rectangle(0, 0, 128, 21);
          it.printf(2, 2, id(font_date), "Easy binary");
          it.printf(2, 22, id(font_date), "Complex binary");
          it.printf(2, 44, id(font_date), "Percentage");
  
      - id: menu_selected_2
        lambda: |-
          it.fill(COLOR_OFF);
          it.rectangle(0, 21, 128, 21);
          it.printf(2, 2, id(font_date), "Easy binary");
          it.printf(2, 22, id(font_date), "Complex binary");
          it.printf(2, 44, id(font_date), "Percentage");

      - id: menu_selected_3
        lambda: |-
          it.fill(COLOR_OFF);
          it.rectangle(0, 21, 128, 21);
          it.printf(2, 2, id(font_date), "Complex binary");
          it.printf(2, 22, id(font_date), "Percentage");
          it.printf(2, 44, id(font_date), "Off");

      - id: menu_selected_4
        lambda: |-
          it.fill(COLOR_OFF);
          it.rectangle(0, 43, 128, 21);
          it.printf(2, 2, id(font_date), "Complex binary");
          it.printf(2, 22, id(font_date), "Percentage");
          it.printf(2, 44, id(font_date), "Off");

      - id: menu_selected_41
        lambda: |-
          it.fill(COLOR_OFF);
          it.rectangle(0, 0, 128, 21);
          it.printf(2, 2, id(font_date), "Previous");
          it.printf(2, 22, id(font_date), "Hour");
          it.printf(2, 44, id(font_date), "3 hours");
      
      - id: menu_selected_42
        lambda: |-
          it.fill(COLOR_OFF);
          it.rectangle(0, 21, 128, 21);
          it.printf(2, 2, id(font_date), "Previous");
          it.printf(2, 22, id(font_date), "Hour");
          it.printf(2, 44, id(font_date), "3 hours");

      - id: menu_selected_43
        lambda: |-
          it.fill(COLOR_OFF);
          it.rectangle(0, 43, 128, 21);
          it.printf(2, 2, id(font_date), "Previous");
          it.printf(2, 22, id(font_date), "Hour");
          it.printf(2, 44, id(font_date), "3 hours");

Todavía tiene algunos bugs y me gustaría mejorar algunas cosas, pero el grueso de la obra ya está listo y puedo ir iterando de a poco en las mejoras.

Parte 9: El producto final​


Una vez que le aplicamos las 3 capas de barniz (dos que me oscurecieron la madera y otra transparente de protección) llegó finalmente el momento de montarlo todo y sacarle las protecciones al plexiglas, lo cual fue una satisfacción tremenda después de tanto tiempo aguantándome:

El reloj todavía con la lámina de protección del plexiglas


Ahora lo único que faltaban eran los toques finales: monté el sensor de temperatura y humedad, creé la conexión eléctrica y monté también el sensor de iluminación:

El interior de la bestia: cables y circuito


Montando el sensor de temperatura y humedad me mandé un cagazo, pero afortunadamente está por la parte de atrás que espero nadie en la vida se fije:

Me equivoqué de hacer el hoyo por un par de milímetros


Ya sé que me he alargado demasiado con el tema, así que no los dejaré más en suspenso y postearé fotos del producto final!

Close-up de los controles y pantalla frontal


Todo el reloj todavía en mi pequeño lab


El reloj ya de noche en su ubicación final


El reloj en su ubicación final (de día)


Y eso concluye esta pequeña guía de idea a ejecución final! Espero que hayan disfrutado de esta serie de artículos tanto como yo disfruté haciendo todo: aunque si bien es cierto los artículos que publiqué sólo son una pequeña parte del trabajo total quedé más que conforme con el resultado final. Hasta mi querida esposa me dijo que aunque al principio dudaba del proyecto, también le gustó cómo quedó.

Y esas son las mejores noticias que uno puede recibir :)

Parte 10: Cosas a mejorar?​


Mucho antes de terminar el proyecto ya me encontré con bastantes pifias y que me gustaría arreglar si alguna vez lo hiciera de nuevo. Algunas de ellas son:

  • Ocupar madera menos gruesa como base: encuentro que ocupé madera demasiado gruesa para el proyecto, esto fue más que nada porque la madera que encontré estaba con descuento y me salía más barato comprar un palo de esos que madera más delgada.
  • Ponerle shielding al cable de datos de la matriz LED: Tuve hartos problemas de interferencia que pude solucionar cambiando los cables de lugar, me gustaría una mejor solución si que en parte se soluciona por:
  • Hacer un circuito más profesional: El estado actual de mi board es más un prototipo antes que algo finalizado. Me gustaría hacer para una segunda versión algo un poco más pro que considere también la posible interferencia entre los distintos sensores. Con un board hecho a medida podría aislar mucho mejor esto y dejar separaciones entre los cables para reducir interferencia.
  • Poner algunos LEDs de estado: Pensaba ocupar la matriz para notificaciones, pero esto resultó ser bastante más complejo que lo que pensé en primer lugar, más que nada porque los efectos sobre-escriben cualquier intento de poder manejarlos y no he podido encontrar una manera en que podría re-usar algunos LEDs en uso.
  • Cambiar la posición del sensor de iluminación: Actualmente, debido a la posición del sensor me dice que oscurece mucho más temprano que lo que realmente es. Idealmente, pondría el sensor quizás por el lado que es donde le llega más luz.
  • Crear el circuito del sensor de iluminación: Además del punto anterior, no ocuparía el módulo que ocupé, sino que compraría y configuraría el sensor yo mismo (incorporando el circuito de éste en mi circuito).
  • Usar USB-C en vez de barrel connector: Si voy a hacer un circuito con una placa, ocuparía un conector USB-C. Esto me permitiría además poder conectar el ESP directamente al puerto de datos (aunque no sé si sea buena idea, este tema es bastante más complejo).
  • Crear una base distinta para la matriz: No quedé 100% conforme con la base para la matriz la verdad. Por el momento no tengo idea cómo lo haría mejor, pero sí me gustaría probar algunos diseños distintos. Si es que algún día hago una v2 de este reloj, lo diseñaría en SketchUp primero para ir probando distintas alternativas virtualmente antes de implementarlo.
  • Cambiar la distribución de peso: En este momento hay mucho peso todavía en la matriz y poco abajo, a pesar de haber ocupado madera gruesa abajo que pesa harto. No quedó inestable, pero a mi gusto quedó con mucho peso arriba.
  • Cambiar el modo de manejar las pantallas, es un culo: Una de las limitantes que he tenido es el sistema de las notificaciones: no hay forma de poder saber si mi fuente se sale de la pantalla o no y aunque hay librerías de manejo de pantalla que hacen esto, no son compatibles con ESPHome.
  • Pantalla menos transparente (para la matriz, no para abajo): Me fui por un plexiglas que deja pasar un 70% de luz, pero quiero probar con un acabado más difuminado. Sin embargo, no tengo las herramientas en casa como para dejar una plancha de ese tamaño pulido de forma tan pareja (o me falta la experiencia para hacerlo!).
  • Una pantalla OLED que refresque más rápido: Una cosa que no me gustó de la OLED que compré es que es increíblemente lenta para refrescar la pantalla completa. He visto otras pantallas que refrescan mucho más rápido que lo que tengo, aunque esto sea probablemente debido a que ocupé I²C para comunicar y no SPI.
  • Creo que me gustaría probar también con un panel matriz de fábrica, sólo para ver cuáles son las posibilidades. Podría tener muchos más LEDs a mi disposición en un espacio más reducido, sin tener que soldar tanto.

Parte 11: Video de todas las opciones​


Hice también un pequeño video demostrativo de la operación del reloj. Suscríbanse, denle like, comenten, etc. Aquí está:



Parte 12: Herramientas y otras cosas ocupadas​


Naturalmente, para poder hacer todo el proyecto, ocupé una variedad de herramientas:

  • Sierra de mesa: fue la que más ocupé para hacer todos los cortes, además de cortes más complicados como los slots donde se encaja el plexiglas + la matriz LED
  • Fresadora: con un bit de 12mm y uno redondo para... si, redondear!
  • Taladro: la más útil fue la de mesa ya que me permite hacer los hoyos con gran precisión y correcta velocidad (sobretodo para el plexiglas!) pero un taladro de mano también funciona
  • Herramientas menores como martillo, cincel de madera varios, lija (80 - 120 - 240), atornillador, etc.
  • Soldador para soldar toda la parte eléctrica

Mucho más que eso en herramientas no ocupé la verdad. El listado de materiales sí es bastante más grande. Está la gran mayoría listado en la segunda entrega de esta serie pero me faltaron algunos detalles para finalizar, principalmente la fuente de poder 5v 3A que terminé ocupando (la idea original era poner un step-down converter de 12v a 5v pero luego me di cuenta que eso era una tontera y que podría alimentar todo directamente con 5v) y el correspondiente conector. Lo otro que también terminé comprando fueron unos botones más decentes para el rotary encoder.

Por supuesto que eso no lo es todo. Software variado también ocupé:
  • EasyEDA y Fritzing: para comprobar sanidad de mi circuito y además probarlo todo via software
  • ESPHome: para programar el reloj y todas sus funcionalidades
  • Sketchup: había empezado a hacer un modelo 3D en Sketchup (una versión revieja de antes que se lanzaran al modelo pagado), pero al final terminé haciendo sólo la base en este software. Lo demás fue más bien una cosa del momento

Lo bueno del diseño de este reloj es que tengo el control total sobre lo que puedo mostrar. Tal como dije en el video, así se ve ahora una sesión de F1 en mi living:

Sesión de F1 en mi casa


(Nota: dejé la pantalla mucha más blanca que lo que se ve normalmente para no tener problemas con F1TV).

Insumos me hicieron harta falta, aunque lo principal fue:

  • Barniz: dos capas de barniz que iban pintando la madera y uno en spray transparente de protección y resaltado. No me quedaba en la casa así que eso lo tuve que comprar, y es caro.
  • Pintura: Pintura ocupé spray en negro no más del más barato que encontré
  • Lija: Ocupé bastante madera vieja y me mandé unos cagazos también con el barniz, la cosa es que ocupé mucha más lija del que me hubiera gustado. Sin embargo, venía todo de lo que tenía guardado.
  • Madera: Como hice buena planificación, la cantidad de madera que ocupé fue muy poco: un solo palo de 2m fue suficiente para hacer todo el case (y me sobró) y ocupé varios trozos chicos de madera viejos que tenía dando vuelta.
  • Plexiglas: Lo más caro dentro de todos los insumos fue sin duda alguna el plexiglas. Casi el 60% del valor del proyecto se fue aquí.

Sin embargo, lo que más destaco y lo que más ocupé fue... tiempo! Si bien es cierto ha sido de los proyectos más profesionales que he hecho, invertí una cantidad brutal de tiempo en este proyecto, incluyendo el crear la serie de artículos. Sin embargo, fue tiempo bien invertido.

Parte 13: Palabras al cierre​


Lo bueno del proyecto es que aprendí un montón: nunca había ocupado plexiglas en alguno de mis proyectos y era la primera vez que ocupaba pintura en spray. Nunca había ocupado tampoco la fresadora así que fue una buena excusa para aprender a manejar esa herramienta tan versátil.

El total de plata que me gasté en insumos fue €77.84 o CLP 68.000 aprox.

Cuando ya me había gastado casi toda la plata, salió un video que muestra una opción que me habría salido más barato: https://github.com/Blueforcer/awtrix-light. Sin embargo, este también tiene distintas funcionalidades (algunas más, otras menos) que mi versión. Lo bueno es que puedo decir que mi reloj es único en el mundo!

Saludos.
 

unreal4u

I solve problems.
Miembro del Equipo
ADMIN
Se incorporó
2 Octubre 2005
Mensajes
13.599
Acabo de darme cuenta que no mencioné en ninguna parte el tiempo que gasté en este proyecto: la idea empezó a principios de abril, mientras que el fin del proyecto fue a finales de mayo. Me demoré como 2 meses aproximadamente, aunque gran parte de eso fue esperar que llegaran las piezas.

En cuanto a trabajo puro y duro, me habré demorado unos 2 fines de semana, aunque en la programación me demoré como una semana. En preparación (y también escribir para capa9!) me habré demorado unas 3 semanas.

Lo bueno es que ahora por fin podré cerrar la pestaña del borrador de este artículo! jajajajaj

Saludos.
 

el_dva

Capo
Se incorporó
23 Noviembre 2009
Mensajes
203
La Raja, excelente trabajo, te felicito por compartir tu experiencia y se agradece que compartas tu trabajo.
 

Bigotiao

...
Se incorporó
24 Noviembre 2006
Mensajes
492
Te pasaste, muchas gracias por compartir. Estoy lejos de tener tus conocimientos como para hacer un proyecto así, pero me siento contento de verlo terminado al fin.
 

tglaria

InExperto
Se incorporó
10 Febrero 2005
Mensajes
9.753
Sobre la velocidad de la pantalla, veo que alguien logró sacarle más de 150FPS con la interfaz i2c.
Si molesta la pantalla y quieres meterte a programar, puedes jugar con ella
 

unreal4u

I solve problems.
Miembro del Equipo
ADMIN
Se incorporó
2 Octubre 2005
Mensajes
13.599
Te pasaste, muchas gracias por compartir. Estoy lejos de tener tus conocimientos como para hacer un proyecto así, pero me siento contento de verlo terminado al fin.

Cuando empecé con este proyecto habían muchas cosas de las que no tenía idea todavía jajajaja Si quieres hacer un proyecto similar, diría dale no más!

Sobre la velocidad de la pantalla, veo que alguien logró sacarle más de 150FPS con la interfaz i2c.
Si molesta la pantalla y quieres meterte a programar, puedes jugar con ella

Wena, tb le había echado el ojo a DMA, pero voy a ver si puedo mejorar esto. Creo que el problema simplemente está en la librería que ocupo. Podría hacer otras cosas pero tendré que meterme mucho más a fondo. Gracias de todas formas por la idea!

¿Y puedes leer la hora en binario?

por alǵun motivo siempre me mareo entre 5 y 6 jajajajaj pero aparte de eso no tengo problemas para leer la hora :) Los segundos eso si van demasiado rápidos todavía para mi xD

Mi esposa tb los lee pero le cuesta un poco más: lógico tb pq no pasó ni la décima parte de lo que yo estuve tratando de implementar el código xD Pero es cosa de práctica no más!

Saludos.
 

spam_loc

. : § p A M _ L 0 © : .
Se incorporó
21 Noviembre 2007
Mensajes
834
GUAUUUUU ... que gran proyecto .... felicitaciones y que ganas de copiarlo aun que no cacho nada de un proyecto así por lo menos la hora la pude leer creo que bien jajajaja

te deje mi sub y like en yutu ....

saludos
 

blah

Miembro Activo
Se incorporó
9 Junio 2023
Mensajes
4
por alǵun motivo siempre me mareo entre 5 y 6 jajajajaj pero aparte de eso no tengo problemas para leer la hora :) Los segundos eso si van demasiado rápidos todavía para mi xD

Mi esposa tb los lee pero le cuesta un poco más: lógico tb pq no pasó ni la décima parte de lo que yo estuve tratando de implementar el código xD Pero es cosa de práctica no más!

Saludos.

Un dato curioso:

En su momento durante mi juventud, estuve aprendiendo Japonés. Pensé, nunca voy a entender estos garabatos, pero conforme pasaban los días repitiendo como un loro los alfabetos y mirando los gráficos por cada caracter, me di cuenta que los empezaba a reconocer.

Buscando en internet, leí que no era un tema de práctica, para las personas con alfabetización, era un tema de tiempo, exposición, asociación y repetición, para que el área del cerebro especializada hiciera su trabajo.

De todas formas me parece bien interesante el concepto que lograste en el reloj, porque con la exposición suficiente, leer la hora pudiera reducirse a una línea del tipo morse y el reloj pudiera ser claro, pero muy minimalista.

El concepto, lo probé hace algunos años en un sistema de escritura pictográfico que desarrollé. Es súper interesante, el como la mente está encajonada en el abstracto de la cultura, lenguaje y escritura locales.

Pd: Me gustó el acabado que le diste a la madera, me recuerda a los muebles que tenían mis familiares de época.
 

unreal4u

I solve problems.
Miembro del Equipo
ADMIN
Se incorporó
2 Octubre 2005
Mensajes
13.599
Un dato curioso:

En su momento durante mi juventud, estuve aprendiendo Japonés. Pensé, nunca voy a entender estos garabatos, pero conforme pasaban los días repitiendo como un loro los alfabetos y mirando los gráficos por cada caracter, me di cuenta que los empezaba a reconocer.

Buscando en internet, leí que no era un tema de práctica, para las personas con alfabetización, era un tema de tiempo, exposición, asociación y repetición, para que el área del cerebro especializada hiciera su trabajo.

De todas formas me parece bien interesante el concepto que lograste en el reloj, porque con la exposición suficiente, leer la hora pudiera reducirse a una línea del tipo morse y el reloj pudiera ser claro, pero muy minimalista.

El concepto, lo probé hace algunos años en un sistema de escritura pictográfico que desarrollé. Es súper interesante, el como la mente está encajonada en el abstracto de la cultura, lenguaje y escritura locales.

Si, es cierto: sobretodo al principio me tomó un par de días acostumbrarme, pero en algún momento mi cerebro hizo el click y de ese momento en adelante el "esfuerzo mental" que tengo que hacer se redujo en un 90%. Eso sí, sigo encontrando nuevas formas de leer el reloj: me he encontrado muchas veces preguntándome si por ejemplo son las 18 o ya son las 19hrs: simplemente miro el primer dígito de la segunda columna para ver si es un número par o impar y con eso ya tengo mi respuesta jajajaj

Pd: Me gustó el acabado que le diste a la madera, me recuerda a los muebles que tenían mis familiares de época.

En cuanto al acabado, me quedó lijeramente más oscuro de lo que hubiese querido, pero una capa de barniz quedaba de un tono más lijero de lo que me hubiese gustado jajajja

Al final, da lo mismo porque el efecto que quería lograr se cumplió: algo moderno envuelto en una chaqueta clásica :)

Saludos.
 

unreal4u

I solve problems.
Miembro del Equipo
ADMIN
Se incorporó
2 Octubre 2005
Mensajes
13.599
Revivo este thread para entregarles noticias al respecto jaja

Sucede que cuando armé todo una de las cosas ocn las que no quedé conforme fue la placa. Muy a veces se notaba que había una pequeña interferencia en las luces y a partir de noviembre del año pasado la verdad es que el reloj andaba super inestable y se reiniciaba a cada rato.

Lamentablemente, cuando armé el reloj la primera vez nunca pensé en exponer los puertos de debugging, así que nunca pude verificar muy bien cuál era el problema tampoco, ya que tb podría haber sido uno de programación.

Pero bueno, para descartar de plano que fuese un error de programación, me dispuse a cambiar la placa original:

Ver adjunto 30980

Creo que nunca mostré mi 'excelente' trabajo de soldadura:

P_20240625_000532.jpg


Pero bueno, cambié ese monstruo de malas soldaduras por este circuito diseñado por mi y lo mandé a hacer a JLCPCB:

P_20240624_210844.jpg


Creo que en un año he mejorado bastante la mano para soldar, aunque todavía no me queda perfecto hay bastante mejoría:

P_20240624_210648.jpg


La placa final:

P_20240624_210730.jpg


Le agregué algunas cosas si: más adelante me gustaría cambiar el sensor PIR por uno de presencia (que se puede esconder y no queda la protuberancia gigante que queda con el PIR) así que lo agregué en hardware por mientras que veo si encargo un nuevo panel acrílico.
Además de eso le puse un sensor de temperatura justo debajo del ESP, más que nada a modo de control. Como no estaba seguro de cuál había sido la pifia del reloj anterior mejor irse por la segura.

La otra novedad es que dejé el puerto de debugging que como dije ahora viene incluído en todos mis diseños de forma predeterminada.

Se me olvidó sacarle una foto una vez que estaba montado, pero lo hice en el mismo formato que la placa anterior con los hoyos en exactamente la misma posición así que fue cortar los cables del anterior, atornillar y controlar. Cuando corté el cable de energía me di cuenta que el cable del GND estaba suelto así que eso declara de por sí las caídas pffff

Pero bueno, lo importante es que ahora dormiré más tranquilo ya que tengo una placa como la gente ahora. Dejé la conexión de las luces lo más alejado posible de toda la demás circuitería para tener la menor interferencia posible.

El tiempo dirá si tuvo algún efecto, pero le tengo fe!

Saludos.
 

Archivo adjunto

  • P_20240625_000400.jpg
    P_20240625_000400.jpg
    298,3 KB · Visitas: 53
Subir