Installing ESPhome on GEEKMAGIC Smart Weather Clock (smalltv/pro)

But doesn’t work on ESP8266…

Tested your lambda on my settings. Drops from 2998ms to 144ms, and does not flicker.

1 Like

After some testing I noticed I was using eightbitcolor: True, which is RGB332. Changing to False, 16-bpp RGB565, gives a better image but also needs more memory… Well, actually more fragments. fragmentation: 6 was unstable, with crashes every few minutes. fragmentation: 8 is stable, has been running for over 8h.

Noticed very slow performance when drawing images, a full-screen draw took ~750ms. Checked the code, figured Image::draw(...) tries to draw the whole image regardless of the display clipping state. This is bad because fragmentation does 8 calls internally, each clipped to only 1/8th of the display. Updated the code to draw only the visible part, and now it draws the whole screen in ~230ms.

Submitted the patches to github, st7789v as PR#8613, and Image as PR#8630. These can be used as external_components while they are not accepted (see config in the spoiler tag).

Click the image below for a video of it running, so you can get an idea of the refresh speed. The striped page serves to emphasize the side-effects of fractional buffering, i.e., lambda is called once per fragment, therefore fill(rand()) uses a different colors each time and we get stripes.

vlcsnap-2025-04-25-21h43m10s645

YAML configuration
esphome:
  name: geekmagic-smalltv-01
  friendly_name: geekmagic-smalltv-01
  platformio_options: 
    board_build.f_cpu: 160000000L
  on_boot:
    - priority: 600
      then:
        - delay: 1s
        - output.turn_on: pwm_output
        - output.set_level:
            id: pwm_output
            level: 50%

esp8266:
  board: esp12e

external_components:
  - source:
      type: git
      url: https://github.com/lhartmann/esphome.git
      ref: dev
    refresh: 0s
    components: [st7789v]
  - source:
      type: git
      url: https://github.com/lhartmann/esphome.git
      ref: image_clipping
    refresh: 0s
    components: [image]

# Enable logging
logger:

# Enable Home Assistant API
api:
  encryption:
    key: !secret api_key

ota:
  - platform: esphome
    password: !secret ota_password

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  # ap:
  #   ssid: "Geekmagic-Smalltv-01"
  #   password: !secret fallback_password

captive_portal:

time:
  - id: ha_time
    platform: homeassistant
    timezone: America/Recife

font:
  - id: roboto
    file: "gfonts://Roboto"
    size: 20
  - id: terminus12n
    file: terminus-font-4.49.1/ter-u12n.bdf
  - id: terminus32b
    file: terminus-font-4.49.1/ter-u32b.bdf
    glyphs: "0123456789:"
  - id: spleen64
    file:
      type: web
      url: https://raw.githubusercontent.com/fcambus/spleen/refs/heads/master/spleen-32x64.bdf
    glyphs: "0123456789:"
    

image:
  - id: bg_image
    file: https://lcsvh.com/ironman.png
    type: rgb565
    resize: 240x240

spi:
  clk_pin: GPIO14
  mosi_pin: GPIO13
  interface: hardware
  id: spihwd

output:
  - id: pwm_output
    platform: esp8266_pwm
    pin: GPIO05
    inverted: true
    frequency: 1000 Hz

light:
  - platform: monochromatic
    output: pwm_output
    name: "Backlight"

color:
  - id: color_green
    red: 0%
    green: 100%
    blue: 0%
  - id: color_ironman_red
    hex: eb1c24
  - id: color_ironman_yellow
    hex: fcb712
  - id: color_ironman_blueglow
    hex: ade0fb
  - id: color_white
    hex: FFFFFF

interval:
  - interval: 5s
    then:
      - display.page.show_next: my_display
      - component.update: my_display

display:
  - id: my_display
    platform: st7789v
    model: custom
    spi_id: spihwd
    height: 240
    width: 240
    offset_height: 0
    offset_width: 0
    fragmentation: 8
    dc_pin: GPIO00
    reset_pin: GPIO02
    eightbitcolor: False
    update_interval: never
    spi_mode: mode3
    data_rate: 40000000
    auto_clear_enabled: False
    pages:
      - id: page_clock
        lambda: |-
          // Calculate the center positions
          int center_x = it.get_width() / 2;
          int center_y = it.get_height() / 2;

          // Background image (also replaces autoclear)
          it.image(0, 0, id(bg_image), ImageAlign::TOP_LEFT);

          it.printf(0, 240, id(terminus12n), id(color_ironman_red), TextAlign::BOTTOM_LEFT, "Hello World");

          // This is a bad-example, to demonstrate side-effect multiplication.
          // With even fragmentation this is cut in half, with k on top and k+1 below.
          static int framecounter = 0;
          it.printf(240, 240, id(terminus12n), id(color_ironman_yellow), TextAlign::BOTTOM_RIGHT, "%d", framecounter++);

          auto now = id(ha_time).now();
          if (true || now.is_valid()) {
            it.printf(12, 12, id(spleen64), id(color_ironman_red), TextAlign::TOP_LEFT, "%02d", now.hour);
            it.printf(240-12, 12, id(spleen64), id(color_ironman_yellow), TextAlign::TOP_RIGHT, "%02d", now.minute);
          }

      - id: page_blankish
        lambda: |-
          it.fill(Color(rand()));
3 Likes

This is really cool @lhartmann , thanks for sharing!

Previously, updating every 60s:

With your component (fragmentation: 8, haven’t played around with this setting yet), updating every 5s:

And more importantly, it doesn’t do a massive flicker on every update.

Bought myself one of those monitors Smart Weather Clock ESP8266 I’ve seen in commercials, people set up currency exchange rates for themselves there. But in the settings there are no settings for exchange rates, and I can not find a module or firmware for it with exchange rates! Maybe someone saw something about exchange rates for this monitor?

The ESP8266 based models (SmallTV and SmallTV Ultra) only include the clock/weather screen. Only the ESP32 version (a.k.a. SmallTV Pro) has the additional screens.

If, however, you can get exchange rates via home assistant, then you can create a custom screen starting from the settings I posted above.

1 Like

Updates on the pull-requests:

PR#8630 for clipping-aware image redraw was merged. It is now accessible by pointing external_components directly from esphome’s dev branch. In a future update this will also be unnecessary.

external_components:
  - source:
      type: git
      url: https://github.com/esphome/esphome.git
      ref: dev
    refresh: 0s
    components: [image]

PR#8613 for the fractional framebuffer was rejected, mostly because the st7789v driver is deprecated in favor of ili9xxx, which is also about to be deprecated in favor of a newer driver. In response to this, I moved my modified version to a separate repository, where it will stay archived.

external_components:
  - source:
      type: git
      url: https://github.com/lhartmann/esphome-st7789v-fractional-framebuffer
      ref: main
    refresh: 0s
    components: [st7789v]

There are plans to bring fraction framebuffer to ALL buffered displays but, while they don’t materialize, the configuration above is expected to remain valid.

And, just for fun, one last picture of it working (draws in 170ms).

7 Likes

Thanks for the updates on your work. I was just playing with your code and it stopped compiling. Luckly I saw the update just as you posted it. Your example is a starting point for me but it’s a great one. Thanks again.

I had a cool idea that I thought I’d share, a decora light switch face plate with this small tv integrated would be a really inexpensive way to get a display in each room, and if you had a pir sensor, it could turn on when it sees motion, so it’s not wasting power. Could be cool, and a LOT cheaper than other options. Also, it appears like when you take it apart, you can make it really really thin.

3 Likes

This is my configuration, maybe someone will find it useful.

esphome:
  name: smalltv
  friendly_name: SmallTV
esp8266:
  board: esp12e
  framework:
    version: recommended

external_components:
  - source:
      type: git
      url: https://github.com/rletendu/esphome.git
      ref: st7789_nobuffer_202312
    # refresh: 0s
    components: [st7789v]

# Enable logging
logger:

ota:
  platform: esphome

# Enable Home Assistant API
api:
  encryption:
    key: "pihTkDrpuE3BSqFHagILJHX+1ZBQxh97jMuZq3JO6gw="

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "Cash From Cahos"
    password: "CFC_Rules_Since_69"

# Ici, on active le serveur WEB du ESP8266, en allant sur l'IP du ESP8266, on tombe sur une interface pour piloter les relais

captive_portal:

spi:
  clk_pin: GPIO14
  mosi_pin: GPIO13
  interface: hardware
  id: spihwd

# status_led:
#   pin: GPIO2


output:
  - platform: esp8266_pwm
    pin: GPIO05
    frequency: 10 Hz
    id: pwm_output

light:
  - platform: monochromatic
    output: pwm_output
    name: "Backlight"

font:
  - file: 'Roboto-Italic.ttf'
    id: font1
    size: 38
  - file: 'Ubuntu-Regular.ttf'
    id: font2
    size: 30
  - file: 'Ubuntu-Regular.ttf'
    id: font3
    size: 20
  - file: 'led.ttf'
    id: font4
    size: 85    

time:
  - platform: homeassistant
    id: esptime


sensor:
  - platform: homeassistant
    entity_id: sensor.moc_l1_l2_l3
    id: Mocl13
    internal: true
    accuracy_decimals: 1
    unit_of_measurement: "W"
    device_class: "power"
    state_class: "measurement"
 
  - platform: homeassistant
    entity_id: sensor.sonoffbasic_ds_multi_out
    id: tempzew
    internal: true
    accuracy_decimals: 2
    unit_of_measurement: "°C"
    device_class: "temperature"
    state_class: "measurement"

  - platform: homeassistant
    entity_id: sensor.temp_dom
    id: poktv
    internal: true
    accuracy_decimals: 2
    unit_of_measurement: "°C"
    device_class: "temperature"
    state_class: "measurement"

  - platform: homeassistant
    entity_id: sensor.temperature_max
    id: maxout
    internal: true
    accuracy_decimals: 2
    unit_of_measurement: "°C"
    device_class: "temperature"
    state_class: "measurement"

text_sensor:    
  - platform: homeassistant
    name: "Energy 0/1"
    entity_id: input_select.energy_0_1
    id: energy

color:
  - id: color_w
    red: 90%
    green: 90%
    blue: 90%
  - id: color_green
    red: 10%
    green: 60%
    blue: 40%
  - id: color_red
    red: 100%
    green: 0%
    blue: 0%
  - id: color_blue
    red: 10%
    green: 40%
    blue: 60%
  - id: color_blue2
    red: 70%
    green: 70%
    blue: 100%
display:
  - platform: st7789v
    model: "Custom"
    spi_id: spihwd
    height: 240
    width: 240
    offset_height: 0
    offset_width: 0
    # dc_pin: GPIO02
    # reset_pin: GPIO04
    dc_pin: GPIO00
    reset_pin: GPIO02
    #backlight_pin: GPIO25
    eightbitcolor: True
    #update_interval: never
    update_interval: 30s
    id: disp
    spi_mode: mode3
    lambda: |-
      const auto RED     = Color(255, 0,   0,   0);
      // Calculate the center positions
      int center_x = it.get_width() / 2;
      int center_y = it.get_height() / 2;

      // Print "Hello World" centered
      // it.printf(center_x, center_y - 20, id(font2), TextAlign::CENTER, "Hello World");

      
      // Display the date in abbreviated weekday, MM/DD/YY format
      it.strftime(center_x, 130, id(font1), id(color_blue), TextAlign::CENTER, "%d:%b:%Y", id(esptime).now());
      it.strftime(center_x, 190, id(font4), id(color_blue2), TextAlign::CENTER, "%H:%M", id(esptime).now());
      
      // it.print(0, 0, id(font1), RED, "Hello World in RED!");

      // it.printf(0, 200, id(font1),"Test1");
      // it.print(130, 5, id(font3), id(color_w), "Grid Power:");
      it.printf(240,40, id(font2), id(color_w), TextAlign::RIGHT, "%.1fW",id(Mocl13).state);
      it.printf(center_x, 90, id(font3), id(color_green), TextAlign::CENTER, "%s", to_string(id(energy).state).c_str());
      it.printf(4, 4, id(font1),TextAlign::LEFT, "%.1f°C", id(tempzew).state);
4 Likes

Or any esphome+screen combination.

Just some info, i ordered from Temu(this one) and this is what it looks like on the inside.


So i wasn’t able to flash this with ESPhome(i’m a beginner at this) so i just came up with a different solution. I changed the settings to show the image gallery. Deleted all images. And in Home Assistant, i generate an image on sensor change(to generate an image you can do lots of different solutions, i did it with PHP) and just simply upload the file to the device with this api call:

curl --request POST \
  --url 'http://192.168.0.36/doUpload?dir=%2Fimage%2F' \
  --header 'Content-Type: multipart/form-data' \
  --form [email protected]

I setup a shell script to handle the image generation and uploading, triggered by an automation on sensor status change.

And thats it, if you upload a file with the same name, it will just replace it automatically, works almost instantly. This is my setup, just to have a quick look at my door statuses:

Possibilities are endless, because you can upload multiple images and if you setup a slideshow, it will switch between each screen automatically(or generate a GIF that switches between screens).

6 Likes

kmplngj’s Git from here:

It’s a great code for the ultra version. I modified it with monospace font and removed formatting, columns, etc… so there is only small and medium font rendering with colors, and alignment to the left only. I also disabled webserver, so there is muuch more ram for fonts to use and the thing works stable.

2 Likes

does anyone have any advice on how to recover these devices once esphome is installed? mine is now bootlooping, wont connect to wifi, wont setup an ap, and cannot be see by any of my computers! =(


just as a reminder and for new users searching for the pinout

3 Likes

Anyone knows which ESP this cute version has?
https://de.aliexpress.com/item/1005008045089831.html

I have a smalltv-Ultra search for a working yaml config.

Hello! ESP12f or ESP32-wroom-32 version is easier to install ESP-HOME?

There is absolutely no difference in ‘ease of installing’ between esp8266 or esp32, but the later is more modern, faster, usually comes with more flash and can do WPA3. So if you get to choose, go with the esp32 (or even esp32-s3 by now). Yes, all of these need their own device specific YAML files, but at 10’000 above sea level the differences are minor and ‘cosmetic’ only.

1 Like