Those little ESP8266 with 0.96" OLED are sometimes on Asian shops for just a few bucks, so I’ve chosen one of them for my 1st ESPHome project.
- ESP8266 with 0.96" OLED-Display
- BME280 Sensor (Temp/Humidity/Pressure)
- CJMCU-811 Board (CSS811 Sensor with some setup to run the sensor on 3.3V)
- base-board for ESP8266-dev-board
- XH header, removed pins to fit them over the pins from the base-board
- XH plugs and header 4pins/6pins for connecting both sensor boards
I’m using a CJMCU-811 (“purple 3.3V board” with connected WAK to GND and RST to 3V) and a BME280, to get the humidity and temperatur values correctly.
From the 2 6-pin headers on the base-board the pins are pulled out and the headers were positioned onto the pins from the base-board (would also work directly on the ESP8266 board directly).
The BME came with soldered in pins, did there the same trick with the XH header (pulled pins, stuck it over the existing pins).
Rated the TVOC value to the European AQI accordingly… (ok, its’s more a guess).
With the OLED display I was stretching a bit to get it work (address, where is SDA, SCL?) and then, after all, getting dimmed again during dusk/dawn/night time…
Maybe someone has a better idea with the font… I didn’t get it in the 1st attempt to load a ttf font from the config-dir and had issues with the gfont, not displaying unicode characters correctly. (Guess, this is because they are not being taken into the compiled code for the esp8266, have no clue, where to turn which screw, to fix that in general… HA is running on a RasPi with the standard installation)
TBD:
- Index for the CO2-levels…
- Text sensors for the HA frontend
- Verifying the current baseline (Can this be automated? Is there a procedure to set up the baseline by “button press”? )
- adjust the brightness according to +/- after sunrise / sunset instead of time
- override the brightness with room lamp setting after sunset/before sunrise
# Build-in OLED-display 128x64 16 dots yellow (current top position) , 48 dots blue (current bottom position)
globals:
- id: display_page
type: int
initial_value: '0'
- id: ccs811_warmup
type: bool
initial_value: 'true'
- id: oled_contrast
type: float
initial_value: '100'
# I2C Bus A für OLED Display
i2c:
- id: bus_a
scl: GPIO12 # D6
sda: GPIO14 # D5
frequency: 1000kHz # tried to adopt to phone camera with 60Hz
scan: false # deactivate scan on I2C-Bus; set active for unknown device adresses and set logging to verbose
# I2C Bus B for BME280
- id: bus_b
scl: GPIO2 # D4
sda: GPIO0 # D3
scan: false
# I2C Bus C für CJMCU-811
- id: bus_c
scl: GPIO4 # D2
sda: GPIO5 # D1
scan: false
sensor:
- platform: bme280_i2c # this name changes resently from bme280 to bme280_i2c
i2c_id: bus_b
temperature:
name: "BME280 Temperature"
id: temp_sensor
oversampling: 16x
pressure:
name: "BME280 Pressure"
id: pressure_sensor
humidity:
name: "BME280 Humidity"
id: humidity_sensor
address: 0x76
update_interval: 60s
- platform: ccs811
i2c_id: bus_c
eco2:
name: "eCO2 Concentration"
id: eco2_sensor
tvoc:
name: "TVOC Concentration"
id: tvoc_sensor
address: 0x5A
#baseline: 0x1AB7 # 2024-07-01 -> skewed, persons were in the room
baseline: 0x59B5 # 2024-07-03
humidity: humidity_sensor # humidity take from BME280-Sensor
temperature: temp_sensor # temperature taken form BME280-Sensor
update_interval: 60s
interval:
- interval: 3s
then:
- lambda: |-
id(display_page) = (id(display_page) + 1) % 2;
if (id(eco2_sensor).state == 400) {
id(ccs811_warmup) = true;
} else {
id(ccs811_warmup) = false;
}
id(oled_096).update();
font:
- file: "gfonts://Roboto"
id: font_1
size: 15
# dont't know how to circumvent that a ttf-font doesn't load the needed characters "Umlaute" to work properly in German language, so I did this for workaround:
glyphs: [
A,B,C,D,E,F,G,H,I,J,K,L,M,N,O,P,Q,R,S,T,U,V,W,X,Y,Z,Ä,Ö,Ü,
a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,ä,ö,ü,
1,2,3,4,5,6,7,8,9,0,_,°,ß,
"\u0020", #space
"\u0021", #!
"\u0022", #"
"\u0025", #%
"\u0027", #'
"\u003A", #:
"\u002E", #.
"\u003B", #;
"\u002C", #,
"\u002D", #-
"\u002B", #+
"\u0028", #(
"\u0029", #)
"\u003C", #<
"\u003E", #>
]
number: # slider for the frontend to override temporarily brightness
- platform: template
name: "Brightness OLED"
id: brightness_oled
unit_of_measurement: '%'
mode: slider
step: 1
min_value: 0
max_value: 100
optimistic: true
initial_value: 70
on_value:
then: #on an oled display we have only 2 colors (on/off) so contrast works like brightness controll. Method set_brightness() seems to have no effect here.
- lambda: |-
id(oled_contrast) = x;
id(oled_096).set_contrast(id(oled_contrast) / 100.0);
time: # automatic brightness adjustment according to the time
- platform: sntp
id: sntp_time
timezone: Europe/Berlin
on_time:
- seconds: 0
minutes: /1
then: #on an oled display we have only 2 colors (on/off) so contrast works like brightness controll. Method set_brightness() seems to have no effect here.
- lambda: |-
auto time = id(sntp_time).now();
if (time.hour >= 6 && time.hour < 8) {
id(oled_contrast) = 50;
} else if (time.hour >= 8 && time.hour < 16) {
id(oled_contrast) = 100;
} else if (time.hour >= 16 && time.hour < 20) {
id(oled_contrast) = 50;
} else if (time.hour >= 20 && time.hour < 22) {
id(oled_contrast) = 10;
} else {
id(oled_contrast) = 0;
}
id(brightness_oled).publish_state(id(oled_contrast));
id(oled_096).set_contrast(id(oled_contrast) / 100.0);
display:
- platform: ssd1306_i2c
i2c_id: bus_a
model: "SSD1306 128x64"
address: 0x3C
id: oled_096
rotation: 180 # wanted to show label for the sensors on bottom in yellow
update_interval: 3s # Display-Update-Intervall
lambda: |-
float aqi_value = 0;
std::string aqi_text = "Init";
if (id(tvoc_sensor).has_state()) {
float tvoc = id(tvoc_sensor).state;
if (tvoc <= 65) {
aqi_value = tvoc / 65 * 50;
aqi_text = "gut";
} else if (tvoc <= 220) {
aqi_value = (tvoc - 65) / (220 - 65) * 50 + 50;
aqi_text = "mäßig";
} else if (tvoc <= 660) {
aqi_value = (tvoc - 220) / (660 - 220) * 50 + 100;
aqi_text = "eing. unges.";
} else if (tvoc <= 2200) {
aqi_value = (tvoc - 660) / (2200 - 660) * 100 + 150;
aqi_text = "ungesund";
} else if (tvoc <= 3000) {
aqi_value = (tvoc - 2200) / (3000 - 2200) * 100 + 200;
aqi_text = "sehr unges.";
} else {
aqi_value = 300;
aqi_text = "gefährlich";
}
}
it.fill(esphome::display::COLOR_OFF);
if (id(display_page) == 0) {
it.printf(0, 0, id(font_1), "Temp: %4.1f °C", id(temp_sensor).state);
it.printf(0, 15, id(font_1), "Druck: %6.1f hPa", id(pressure_sensor).state);
it.printf(0, 30, id(font_1), "Feuchte: %5.1f%%", id(humidity_sensor).state);
it.printf(0, 45, id(font_1), "BME-280");
} else {
it.printf(0, 0, id(font_1), "eCO2: %4.0f ppm", id(eco2_sensor).state);
it.printf(0, 15, id(font_1), "TVOC: %4.0f ppb", id(tvoc_sensor).state);
it.printf(0, 30, id(font_1), "AQI: %3.0f (%s)", aqi_value, aqi_text.c_str());
if (id(ccs811_warmup)) {
it.printf(0, 45, id(font_1), "CSS-811 WarmUp");
} else {
it.printf(0, 45, id(font_1), "CSS-811");
}
}
