M5Stack Core2 / M5Stick Cplus ESPhome support?

I was looking for a self contained ESP32 system with a screen and a few I/O. The Core2 was just perfect until I discovered that being listed as one of the supported devices does NOT mean that you can use all of it features. Based on a github page I found it appears that the power management and screen are not fully supported yet (there are some drivers that can be cobbled together, but it doesn’t work right without some development).

Is there any chance this hardware will be supported relatively soon? Has anyone figured out a reliable way of making it work? I also purchased an M5Stick CPlus as I saw the C version was listed and erroneously thought the Plus would work too. The Plus version has the same issue as the Core2… AXP192 power management and screen support are not there yet.

Is there any other similar hardware that IS fully supported? Criteria is ESP32 in an enclosure ideally with screen but without will do too.

Also, where can I find example code? I’ve searched quite a bit but it appears that I have to piece things together from the pages that talk about the various sub components. This is hardly friendly for a new user. I plan on sharing my completed project once done.

3 Likes

Right here near the top of the documentation page

Also if you keep looking at the docs, there is this near the bottom of the page

Bumping this. I also just got a M5stick C Plus and would like it to work in esphome.
Its a rather perfect room device for home assistant.

1 Like

Feature requests for esphome belong on github. Here is one you may be interested in

PS openhasp works with m5stack core2 https://openhasp.haswitchplate.com/0.6.3/0.6.3/devices/m5stack-core2/

Exelent! Got the M5stick C plus working with ESPhome.

Got the axp192 componemt from:

There is an example.yaml file to paste into your esphome config for the device.
The display works with this code:

#Display size 135*240
display:
  - platform: st7789v
    id: tft
    cs_pin: GPIO5
    dc_pin: GPIO23
    reset_pin: GPIO18
    pages:
      - id: page1
        lambda: |-
          it.printf(67, 20, id(normal), id(blue), TextAlign::CENTER,  "Power");
          it.printf(67, 60, id(normal), id(blue), TextAlign::CENTER, "%.1f", id(batterylevel).state);
3 Likes

I got my M5Stick Cplus working too!

I am sharing my unfinished code as the screen is working and others can use it as an example on top of what you shared. The rest of the code is not fully functional yet (ideally it will control a fan in a network rack based on temperature, and Home Assistant will be able to override things such as disabling the fans so one can watch a movie without the noise - rack is in my media room).

The code below only works after placing the files from Ian Geiser in the components subfolder of the esphome folder.

substitutions:
  devicename: rack-controller
  friendly_devicename: Rack-Controller

esphome:
  name: $devicename
  platform: ESP32
  board: m5stick-c
  platformio_options:
    upload_speed: 115200

external_components:
  - source:
      type: git
      url: https://gitlab.com/geiseri/esphome_extras.git
    refresh: 0s

wifi:
  ssid: !secret iot_wifi_ssid
  password: !secret iot_wifi_pwd
  
  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: $devicename Fallback Hotspot
    password: !secret iot_wifi_pwd

captive_portal:

logger:

api:

ota:

web_server:

globals: ##to set default reboot behavior
## loop on/off    
  - id: disable_network_rack_fans
    type: bool
    restore_value: no
    initial_value: "false"
    
  - id: pwm_var
    type: float
    restore_value: no
    initial_value: "0.0"

i2c:
##  - id: bus_a
##    sda: 0
##    scl: 26
##    scan: true
  - id: bus_b
    sda: 21
    scl: 22
    scan: true
  - id: bus_c
    sda: 32
    scl: 33
    scan: true
    
axp192:
  id: axp
  address: 0x34
  i2c_id: bus_b
  update_interval: 60s

binary_sensor:
  - platform: axp192
    axp192_id: axp
    type: charging
    id: axp_charger
    name: "${friendly_devicename} Charger"
    
  - platform: homeassistant
    id: disable_fans
    internal: true
    entity_id: input_boolean.disable_network_rack_fans
    on_state:
      - globals.set:
          id: disable_network_rack_fans
          value: !lambda 'return id(disable_fans).state;'

##binary sensor for condition - invert as the toggle is to DISABLE the fans thus loop
  - platform: template
    name: "Loop template"
    id: loop_template
    lambda: |-
      if (id(disable_network_rack_fans)) {
        return false;
      } else {
        return true;
      }

  - platform: status
    name: "Node Status"
    id: system_status

  - platform: gpio
    pin:
      number: GPIO37
      inverted: true
    name: ${friendly_devicename} M5 Button
    on_press:
      then:
        - light.turn_on: led1
        - display.page.show_next: tft
        - output.turn_on: fan_pwm
        - output.set_level:
            id: fan_pwm
            level: "50%"
    on_release:
      then:
        - light.turn_off: led1
        - output.turn_on: fan_pwm
        - output.set_level:
            id: fan_pwm
            level: "10%"
        
  - platform: gpio
    pin:
      number: GPIO39
      inverted: true
    name: ${friendly_devicename} Button B
    on_press:
      then:
        - light.toggle: led1
        - light.toggle: backlight


# internal LED
light:
  - platform: monochromatic
    output:  builtin_led
    name: ${friendly_devicename} Led
    id: led1

# internal LED
  - platform: axp192
    id: backlight
    axp192_id: axp
    restore_mode: ALWAYS_ON
    name: "${friendly_devicename} Backlight"
    
output:
  - platform: ledc
    pin: GPIO26
    #frequency: "19531Hz" #This breaks the PWM output
    inverted: false
    id: fan_pwm
    
  - platform: ledc
    pin: GPIO10
    inverted: false
    id: builtin_led    

fan:
  - platform: speed
    output: fan_pwm
    name: "Network Rack Exhaust Fan Speed"

sensor:
  - platform: axp192
    axp192_id: axp
    id: batterylevel
    name: "${friendly_devicename} Battery Level"

  - platform: wifi_signal
    name: ${friendly_devicename} WiFi Signal
    id: wifi_dbm
    
  - platform: uptime
    name: ${friendly_devicename} Uptime

  - platform: sht3xd
    i2c_id: bus_c
    address: 0x44
    humidity:
      name: "Network Rack Humidity"
    temperature:
      name: "Network Rack Temperature"
      id: rack_temp
      on_value:
        then:
          lambda: |-
            
            if (id(disable_network_rack_fans) == false) {      
                  
                  if (id(rack_temp).state < 23.0) {
                      id(pwm_var) = 0.0;
                  }
                  /*Adding 0.5C to prevent constant on/off when temp is right at 23*/
                  else if ((id(rack_temp).state >= 23.0) and (id(rack_temp).state <= 23.5)) {
                      ESP_LOGD("PWM", "Leave PWM as is. Dead band.");
                  }
                  else if ((id(rack_temp).state >= 23.5) and (id(rack_temp).state <= 24.0)) {
                      id(pwm_var) = 0.05;
                  }
                  else if ((id(rack_temp).state > 24.0) and (id(rack_temp).state <= 25.0)) {
                      id(pwm_var) = 0.1;
                  }
                  else if ((id(rack_temp).state > 25.0) and (id(rack_temp).state <= 26.0)) {
                      id(pwm_var) = 0.2;
                  }
                  else if ((id(rack_temp).state > 26.0) and (id(rack_temp).state <= 27.0)) {
                      id(pwm_var) = 0.3;
                  }
                  else if ((id(rack_temp).state > 27.0) and (id(rack_temp).state <= 28.0)) {
                      id(pwm_var) = 0.4;
                  }
                  else if ((id(rack_temp).state > 28.0) and (id(rack_temp).state <= 29.0)) {
                      id(pwm_var) = 0.5;
                  }
                  else if ((id(rack_temp).state > 29.0) and (id(rack_temp).state <= 30.0)) {
                      id(pwm_var) = 0.6;
                  }
                  else if ((id(rack_temp).state > 30.0) and (id(rack_temp).state <= 32.0)) {
                      id(pwm_var) = 0.7;
                  }
                  else if ((id(rack_temp).state > 32.0) and (id(rack_temp).state <= 35.0)) {
                      id(pwm_var) = 0.8;
                  }
                  else {
                      id(pwm_var) = 1.0;
                      ESP_LOGD("ALERT", "OVER 35C! Setting fan to 100");
                  }
            }
            else {
                  id(pwm_var) = 0.0;
                  ESP_LOGD("ALERT", "FAN CONTROL DISABLED IN HA");
            }
            id(fan_pwm).set_level(id(pwm_var));
            ESP_LOGD("PWM", "PWM: %.0f%%", id(pwm_var)*100);
    update_interval: 2s




spi:
  clk_pin: GPIO13
  mosi_pin: GPIO15

font:
  - file: "arial.ttf"
    id: font1
    size: 14

  - file: "arial.ttf"
    id: font2
    size: 32

  - file: "arial.ttf"
    id: font3
    size: 14
    
color:
  - id: my_red
    red: 100%
    green: 0%
    blue: 0%
  - id: my_yellow
    red: 100%
    green: 100%
    blue: 0%
  - id: my_green
    red: 0%
    green: 100%
    blue: 0%
  - id: my_blue
    red: 0%
    green: 0%
    blue: 100%
  - id: my_gray
    red: 50%
    green: 50%
    blue: 50%

#Display size 135*240
display:
  - platform: st7789v
    id: tft
    cs_pin: GPIO5
    dc_pin: GPIO23
    reset_pin: GPIO18
    rotation: 270
    pages:
      - id: page1
        lambda: |-

          // Draw a line from [0,22] to [240,22]
          it.line(0, 22, it.get_width(), 22);
          //it.rectangle(0,  0, it.get_width(), it.get_height(), id(my_blue));
          //it.rectangle(0, 20, it.get_width(), it.get_height(), id(my_blue));   // header bar

          //it.strftime((240 / 2), (140 / 3) * 1 + 5, id(font1), id(my_gray), TextAlign::CENTER, "%Y-%m-%d", id(homeassistant_time).now());
          //it.strftime((240 / 2), (140 / 3) * 2 + 5, id(font1), id(my_gray), TextAlign::CENTER, "%H:%M:%S", id(homeassistant_time).now());
          it.print(5, 5, id(font1), id(my_yellow), TextAlign::TOP_LEFT, "FAN CONTROLLER");

          // Comment out the above lines to see the image without text overlaid
          // it.image(0, 0, id(my_image));

          if (id(system_status).state) {
            it.print(235, 5, id(font1), id(my_green), TextAlign::TOP_RIGHT, "Online");
          }
          else {
            it.print(235, 5, id(font1), id(my_red), TextAlign::TOP_RIGHT, "Offline");
          }

          it.printf( 5, 25, id(font1), id(my_blue), TextAlign::LEFT, "Battery: %.1f%%", id(batterylevel).state);
          it.printf( 5, 45, id(font1), id(my_blue), TextAlign::LEFT, "Temp: %.1f°C", id(rack_temp).state);
          it.printf( 5, 65, id(font1), id(my_blue), TextAlign::LEFT, "PWM: %.0f%%", id(pwm_var)*100);
          it.printf( 5, 85, id(font1), id(my_blue), TextAlign::LEFT, "FANS: %s", id(disable_fans).state ? "ON" : "OFF");

      - id: page2
        lambda: |-

          // Draw a line from [0,22] to [240,22]
          it.line(0, 22, it.get_width(), 22);
          //it.rectangle(0,  0, it.get_width(), it.get_height(), id(my_blue));
          //it.rectangle(0, 20, it.get_width(), it.get_height(), id(my_blue));   // header bar

          it.strftime((240 / 2), (140 / 3) * 1 + 5, id(font1), id(my_gray), TextAlign::CENTER, "%Y-%m-%d", id(homeassistant_time).now());
          it.strftime((240 / 2), (140 / 3) * 2 + 5, id(font1), id(my_gray), TextAlign::CENTER, "%H:%M:%S", id(homeassistant_time).now());
          it.print(5, 5, id(font1), id(my_yellow), TextAlign::TOP_LEFT, "FAN CONTROLLER");

          // Comment out the above lines to see the image without text overlaid
          // it.image(0, 0, id(my_image));

          if (id(system_status).state) {
            it.print(235, 5, id(font1), id(my_green), TextAlign::TOP_RIGHT, "Online");
          }
          else {
            it.print(235, 5, id(font1), id(my_red), TextAlign::TOP_RIGHT, "Offline");
          }

          //it.printf( 5, 25, id(font1), id(my_blue), TextAlign::LEFT, "Battery: %.1f%%", id(batterylevel).state);
          //it.printf( 5, 45, id(font1), id(my_blue), TextAlign::LEFT, "Temp: %.1f°C", id(rack_temp).state);
          //it.printf( 5, 65, id(font1), id(my_blue), TextAlign::LEFT, "PWM: %.0f%%", id(pwm_var)*100);
          //it.printf( 5, 85, id(font1), id(my_blue), TextAlign::LEFT, "FANS: %s", id(disable_fans).state ? "ON" : "OFF");
time:
  - platform: homeassistant
    id: homeassistant_time
  - platform: sntp
    id: sntp_time


Disclaimer: please consider this code hacked together… I am just starting!

1 Like

Can you link to files please?

Thanks!

Its in the thread.

Does anyone know whether the M5Stick Cplus can output a PWM at 25kHz? My code stops working if I use the frequency configuration to set it to 19kHz and my scope says the output stays at about 6%. If I comment out the line, the code works but there is an insane whine from the fans at about 1kHz. I can’t find anything that confirms whether the hardware, and specifically the GPIO26 output I used, is compatible with outputting a 19kHz PWM signal.

output:
  - platform: ledc
    pin: GPIO26
    #frequency: "19531Hz" #This breaks the PWM output
    inverted: false
    id: fan_pwm

Getting the following error…

I have to custom components loaded and also am referencing the git directly… thoughts?

ERROR Unable to load component axp192.sensor:
Traceback (most recent call last):
  File "/esphome/esphome/loader.py", line 162, in _lookup_module
    module = importlib.import_module(f"esphome.components.{domain}")
  File "/usr/lib/python3.9/importlib/__init__.py", line 127, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
  File "<frozen importlib._bootstrap>", line 1030, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1007, in _find_and_load
  File "<frozen importlib._bootstrap>", line 986, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 680, in _load_unlocked
  File "<frozen importlib._bootstrap_external>", line 790, in exec_module
  File "<frozen importlib._bootstrap>", line 228, in _call_with_frames_removed
  File "/config/esphome/.esphome/external_components/19e2b645/components/axp192/sensor.py", line 11, in <module>
    CONFIG_SCHEMA = sensor.sensor_schema(UNIT_PERCENT, ICON_BATTERY, 1).extend({
TypeError: sensor_schema() takes from 0 to 1 positional arguments but 3 were given
esphome:
  name: m5stickcplus-test

esp32:
  board: m5stick-c
  framework:
    type: arduino

external_components:
  - source:
      type: git
      url: https://gitlab.com/geiseri/esphome_extras.git
    refresh: 0s

# Enable logging
logger:

# Enable Home Assistant API
api:

ota:
  password: ""

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

  # Enable fallback hotspot (captive portal) in case wifi connection fails
  ap:
    ssid: "-Test"
    password: ""

captive_portal:
    

web_server:

i2c:
##  - id: bus_a
##    sda: 0
##    scl: 26
##    scan: true
  - id: bus_b
    sda: 21
    scl: 22
    scan: true
  - id: bus_c
    sda: 32
    scl: 33
    scan: true
    
axp192:
  id: axp
  address: 0x34
  i2c_id: bus_b
  update_interval: 60s

binary_sensor:
  - platform: axp192
    axp192_id: axp
    type: charging
    id: axp_charger
    name: "${friendly_devicename} Charger"
    
  - platform: status
    name: "Node Status"
    id: system_status

  - platform: gpio
    pin:
      number: GPIO37
      inverted: true
    name: ${friendly_devicename} M5 Button
    on_press:
      then:
        - light.turn_on: led1
        - display.page.show_next: tft
    on_release:
      then:
        - light.turn_off: led1
        
  - platform: gpio
    pin:
      number: GPIO39
      inverted: true
    name: ${friendly_devicename} Button B
    on_press:
      then:
        - light.toggle: led1
        - light.toggle: backlight


# internal LED
light:
  - platform: monochromatic
    output:  builtin_led
    name: ${friendly_devicename} Led
    id: led1

# internal LED
  - platform: axp192
    id: backlight
    axp192_id: axp
    restore_mode: ALWAYS_ON
    name: "${friendly_devicename} Backlight"
    
output:   
  - platform: ledc
    pin: GPIO10
    inverted: false
    id: builtin_led    

sensor:
  - platform: axp192
    axp192_id: axp
    id: batterylevel
    name: "${friendly_devicename} Battery Level"

  - platform: wifi_signal
    name: ${friendly_devicename} WiFi Signal
    id: wifi_dbm
    
  - platform: uptime
    name: ${friendly_devicename} Uptime

spi:
  clk_pin: GPIO13
  mosi_pin: GPIO15

font:
  - file: "arial.ttf"
    id: font1
    size: 14

  - file: "arial.ttf"
    id: font2
    size: 32

  - file: "arial.ttf"
    id: font3
    size: 14
    
color:
  - id: my_red
    red: 100%
    green: 0%
    blue: 0%
  - id: my_yellow
    red: 100%
    green: 100%
    blue: 0%
  - id: my_green
    red: 0%
    green: 100%
    blue: 0%
  - id: my_blue
    red: 0%
    green: 0%
    blue: 100%
  - id: my_gray
    red: 50%
    green: 50%
    blue: 50%

#Display size 135*240
display:
  - platform: st7789v
    id: tft
    cs_pin: GPIO5
    dc_pin: GPIO23
    reset_pin: GPIO18
    rotation: 270
    pages:
      - id: page1
        lambda: |-

          // Draw a line from [0,22] to [240,22]
          it.line(0, 22, it.get_width(), 22);
          //it.rectangle(0,  0, it.get_width(), it.get_height(), id(my_blue));
          //it.rectangle(0, 20, it.get_width(), it.get_height(), id(my_blue));   // header bar

          //it.strftime((240 / 2), (140 / 3) * 1 + 5, id(font1), id(my_gray), TextAlign::CENTER, "%Y-%m-%d", id(homeassistant_time).now());
          //it.strftime((240 / 2), (140 / 3) * 2 + 5, id(font1), id(my_gray), TextAlign::CENTER, "%H:%M:%S", id(homeassistant_time).now());
          it.print(5, 5, id(font1), id(my_yellow), TextAlign::TOP_LEFT, "FAN CONTROLLER");

          // Comment out the above lines to see the image without text overlaid
          // it.image(0, 0, id(my_image));

          if (id(system_status).state) {
            it.print(235, 5, id(font1), id(my_green), TextAlign::TOP_RIGHT, "Online");
          }
          else {
            it.print(235, 5, id(font1), id(my_red), TextAlign::TOP_RIGHT, "Offline");
          }

          it.printf( 5, 25, id(font1), id(my_blue), TextAlign::LEFT, "Battery: %.1f%%", id(batterylevel).state);
          it.printf( 5, 45, id(font1), id(my_blue), TextAlign::LEFT, "Temp: %.1f°C", id(rack_temp).state);

      - id: page2
        lambda: |-

          // Draw a line from [0,22] to [240,22]
          it.line(0, 22, it.get_width(), 22);
          //it.rectangle(0,  0, it.get_width(), it.get_height(), id(my_blue));
          //it.rectangle(0, 20, it.get_width(), it.get_height(), id(my_blue));   // header bar

          it.strftime((240 / 2), (140 / 3) * 1 + 5, id(font1), id(my_gray), TextAlign::CENTER, "%Y-%m-%d", id(homeassistant_time).now());
          it.strftime((240 / 2), (140 / 3) * 2 + 5, id(font1), id(my_gray), TextAlign::CENTER, "%H:%M:%S", id(homeassistant_time).now());
          it.print(5, 5, id(font1), id(my_yellow), TextAlign::TOP_LEFT, "FAN CONTROLLER");

          // Comment out the above lines to see the image without text overlaid
          // it.image(0, 0, id(my_image));

          if (id(system_status).state) {
            it.print(235, 5, id(font1), id(my_green), TextAlign::TOP_RIGHT, "Online");
          }
          else {
            it.print(235, 5, id(font1), id(my_red), TextAlign::TOP_RIGHT, "Offline");
          }

          //it.printf( 5, 25, id(font1), id(my_blue), TextAlign::LEFT, "Battery: %.1f%%", id(batterylevel).state);
          //it.printf( 5, 45, id(font1), id(my_blue), TextAlign::LEFT, "Temp: %.1f°C", id(rack_temp).state);
          //it.printf( 5, 65, id(font1), id(my_blue), TextAlign::LEFT, "PWM: %.0f%%", id(pwm_var)*100);
          //it.printf( 5, 85, id(font1), id(my_blue), TextAlign::LEFT, "FANS: %s", id(disable_fans).state ? "ON" : "OFF");
time:
  - platform: homeassistant
    id: homeassistant_time
  - platform: sntp
    id: sntp_time

Nevermind… its this … M5Stick - axp192 no longer working since upgrading to ESPHome 2022.3.0 - 16th March 2022 - #9 by nickrout