Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Every purifier covered here at a glance — how it's converted to ESPHome, its s
| [Everest Air](./devices/levoit-everest-air) | Levoit | 🟢 Flash / 🔵 Add ESP | 612 m³/h | 24–56 dB | Easy | [Guide](./devices/levoit-everest-air) | [Amazon](https://amzn.to/3Q1cMB) | ✅ Tested · vent louver, PM1.0/2.5/10, Turbo |
| [Sprout](./devices/levoit-sprout) | Levoit | 🟢 Flash / 🔵 Add ESP | 145 m³/h | 22–47 dB | Easy | [Guide](./devices/levoit-sprout) | [Amazon](https://amzn.to/4oAJs1n) | 🚧 WIP · white-noise audio (I2S MP3) |
| [LV-PUR 131S](./devices/levoit-lv131s/) | Levoit | 🔴 Custom HW | — | — | Medium | [Guide](./devices/levoit-lv131s/) | — | Custom FW + MCU & sensor upgrade |
| [LV-PUR 131](./devices/levoit-lv131/) | Levoit | 🔴 Custom HW | — | — | Medium | [Guide](./devices/levoit-lv131/) | — | Custom FW + MCU upgrade |
| [LV-PUR 131](./devices/levoit-lv131/) | Levoit | 🔴 Custom HW | — | — | Medium | [Guide](./devices/levoit-lv131/) | — | Custom FW + temperature sensor + MCU & sensor upgrade |
| [Levoit Mini](./devices/levoit-mini) | Levoit | 🔴 Custom HW | 78 m³/h | 41.8 – 53.6 dBA | Easy | [Guide](./devices/levoit-mini) | [Amazon](https://amzn.to/4acovEh) | Full custom PCB + 3D parts |
| [Philips AC0650](./devices/philips-600-series) | Philips| 🔵 Add ESP | 170 m³/h | 19–49 dB | Easy | [Guide](./devices/philips-600-series) | [Amazon](https://amzn.to/4vS5Ohs) | ✅ Tested · secure boot → replace module; no AQ sensor |
| [Philips AC0651](./devices/philips-600-series) | Philips| 🔵 Add ESP | 170 m³/h | 19–49 dB | Easy | [Guide](./devices/philips-600-series) | [Amazon](https://amzn.to/4elkSyg) | ✅ Tested · adds PM2.5 (PM1003) + Auto mode |
Expand Down Expand Up @@ -136,7 +136,7 @@ The Levoit Sprout additionally uses the [levoit_audio component](./components/le
### Other Models / Levoit Projects

* [Levoit LV-PUR 131S](./devices/levoit-lv131s/) – Custom Firmware + MCU & sensor upgrade + hardware hack
* [Levoit LV-PUR 131](./devices/levoit-lv131/) – Custom Firmware + MCU upgrade + hardware hack
* [Levoit LV-PUR 131](./devices/levoit-lv131/) – Custom Firmware + temperature sensor + MCU & sensor upgrade + hardware hack
* [Levoit Mini](./devices/levoit-mini) – Custom PCB, 3D parts, hardware hack

### Features
Expand Down
2 changes: 1 addition & 1 deletion devices/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

Special:
* [Levoit LV PUR 131s](./levoit-lv131s/) – Custom Firmware + MCU & sensor upgrade + hardware hack
* [Levoit LV PUR 131](./levoit-lv131/) – Custom Firmware + MCU upgrade + hardware hack
* [Levoit LV PUR 131](./levoit-lv131/) – Custom Firmware + temperature sensor + MCU & sensor upgrade + hardware hack
* [Levoit Mini](./levoit-mini) – Custom PCB, 3D parts, hardware hack

Core 200S/300S/400S and Vital100/200, only require disassembly to flash firmware (no hardware modifications needed).
Expand Down
29 changes: 15 additions & 14 deletions devices/levoit-lv131/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,35 @@

# Levoit LV-PUR131 - Custom Firmware (ESPHome)

The LV-PUR131 model is very similar to the [LV-131S](../levoit-lv131s/README.md) (later called the S board / model) except it doesn't havethe wireless capabilities.
The LV-PUR131 model is very similar to the [LV-PUR131S](../levoit-lv131s/README.md) (later called the S board / model) except it doesn't have the wireless capabilities.
They look identical and have mostly the same components.

**Hardware Upgrades:**
- MCU: Original On-Bright OB39R08A3U20SP -> ESP32-C3 Super Mini
- Sensor upgrade to PM5003 is theoretically possible and should be implemented the same way it is on the S board, but I don't have the money to buy one right now. Please make a PR if you tested whether it works or not.
- Sensor: Original PM1003 -> PM5003

![Whole mod](./images/whole.jpg)

## Features
- WiFi on a machine that never was supposed to have it!!
- A temperature sensor.
- Everything available on the S model.

## Why this works?
Because we replace the old UC with one that can do wireless and levoit reused the same components between this and the wireless model.

## Interesting findings
The main differences between the models are the ones related to the microcontroller and the voltage it uses. The S board uses an ESP32 and runs on 3.3V whereas this model runs on a microcontroller that works fully on 5V - even the display is the same, thanks to which this mod is able to use the wifi icon which normally is not (I think?) used by the machine at all. The routing also changes a bit between the boards but not really by much.
The main differences between the models are the ones related to the microcontroller and the voltage it uses. The S board uses an ESP32 and runs on 3.3V whereas this model runs on a microcontroller that works fully on 5V - even the display is the same, thanks to which this mod is able to use the wifi icon which normally is not (I think?) used by the machine at all. The routing also changes a bit between the boards but not by much.

## Hardware Upgrade Project ("The Hack")
### Required Parts
- Broken or working Levoit LV-PUR131
- Any cheap ESP32 board really. A cheap chinese C3 Super Mini works fine.
- Any cheap ESP32 board really. A chinese C3 Super Mini works fine.
- Wires
- 3x 10k ohm resistor
- 2x 4.7k ohm resistor
- BAT43 diode
- 1x 10k ohm resistor
- 1x 4.7k ohm resistor
- PMS5003
- DHT22 (and the things necessary to make it work if your module doesn't have them on board)

### Required Tools
- Something to cut a trace with
Expand All @@ -36,7 +40,7 @@ The main differences between the models are the ones related to the microcontrol
Same as the S model.

### Hack / Modify PCB
We need to force the board to behave like the S model so the S mod works here as well. To acomplish this we need to add two voltage dividers, a resistor, a diode, cut a trace, and desolder the old UC.
We need to force the board to behave like the S model so the S mod works here as well. To acomplish this we need to add a voltage divider, cut a trace, and desolder the old UC.

This is what the board looks like:
![Board](./images/board.gif)
Expand All @@ -51,12 +55,9 @@ This is what the finished board looks like. Sorry for the flux, I ran out of IPA
![Mod](./images/mod.png)

#### Important?
- I didn't have the schottky diode on hand so I used a 1N4148 diode instead. Because of it's forward voltage I had to add another 10k resistor on the Q101's base. This part is marked in purple and is in the bottom right corner of the solder_points.jpg. The schematic does not include this part, and so shouldn't you.
- The original UC is stuck in place with some kind of glue. You might have to apply a bit of force to get it to move.
- The PMS5003 placement is the same as on the S model, but it does not seem to be the most optimal. I think my module has airflow issues.
- I glued the DHT22 to the outside wall of the space between the air quality sensor and the fan. The temperature readings seem correct, there's only around a .2C difference compared to a sensor I have hanging on a wall which is more than good enough for this kind of sensors. I'll think about getting a more precise one and messing around with the placement as the humidity reading are way off.

### How, why, etc.
The board is designed to work on 5V only. Our ESP32 runs on 3.3V and if we feed it's GPIO 5V it's gonna blow up. Every time there is a risk of 5V going into out new controller we need to somehow convert it to ~3.3V. This is why the voltage dividers are needed, and this is also why we need to cut the trace pulling up the TM1628's lines to 5V and replace it with 3.3V as well.

## Notes

- There still is one GPIO left free on the board. Somebody, please put an LED strip in the vent and share what it looks like.
The board is designed to work on 5V only. Our ESP32 runs on 3.3V and if we feed it's GPIO 5V it's gonna blow up. Every time there is a risk of 5V going into out new controller we need to somehow convert it to ~3.3V. This is why the voltage divider is needed, and this is also why we need to cut the trace pulling up the TM1628's lines to 5V and replace it with a 3.3V pullup as well.
Binary file modified devices/levoit-lv131/images/mod.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified devices/levoit-lv131/images/schematics.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified devices/levoit-lv131/images/solder_points.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added devices/levoit-lv131/images/whole.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
104 changes: 51 additions & 53 deletions devices/levoit-lv131/levoit-lv131-c3.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# Board: ESP32-C3 Super Mini (Generic)
# This is basically the LV-131S definition with just a few changes. The same 500khz notice applies here too.

esphome:
name: lv-pur131
Expand Down Expand Up @@ -33,7 +32,7 @@ wifi:
password: !secret wifi_password
ap:
ssid: LV-PUR131 Fallback Hotspot
password: "Litwo!OjczyznoMoja!TyJestesJakZdrowie."
password: !secret ap_password
passive_scan: true
on_connect:
- globals.set:
Expand All @@ -55,7 +54,7 @@ spi:
spi_device:
id: spidev
cs_pin: GPIO03 #D1
data_rate: 200khz # use 500khz is possible esphome 2026.01+ required!
data_rate: 500khz
spi_mode: 0
bit_order: lsb_first

Expand Down Expand Up @@ -87,6 +86,12 @@ uart:
id(btn_run_check)->stop();
id(btn_run_check)->execute();

# PMS5003
- id: uart_sensor
rx_pin: GPIO08 #D8
tx_pin: GPIO09 #D9
baud_rate: 9600

globals:
- id: display_bits
type: uint8_t[14]
Expand Down Expand Up @@ -135,15 +140,6 @@ switch:
optimistic: true
entity_category: CONFIG
icon: "mdi:volume-high"
# active low heater control switch
- platform: gpio
pin:
number: GPIO08 #D8
mode: OUTPUT
id: sensor_heater
inverted: true
restore_mode: ALWAYS_OFF
internal: true

fan:
- platform: template
Expand All @@ -159,10 +155,8 @@ fan:
- script.execute: update_fan_speed
on_turn_on:
- script.execute: fan_turn_on
- switch.turn_on: sensor_heater # turn on the sensor heater when the fan starts
on_turn_off:
- script.execute: fan_turn_off
- switch.turn_off: sensor_heater # turn off the sensor heater when the fan stops
on_preset_set:
- script.execute: fan_update_preset
on_speed_set:
Expand Down Expand Up @@ -221,40 +215,30 @@ number:
id(timer_text).publish_state(buf);

sensor:
# stock PM1003
# using id sensor_pm2_5 to references
- platform: pulse_width
name: 'PM10'
update_interval: 15s
id: sensor_pm2_5
state_class: measurement
device_class: pm10
icon: mdi:chemical-weapon
unit_of_measurement: 'µg/m³'
accuracy_decimals: 0
pin:
number: GPIO07 #D5
inverted: true
mode:
input: true
on_value:
- script.execute: update_fan_speed
filters:
- round_to_multiple_of: 0.01
- calibrate_linear:
method: exact
datapoints:
- 0.0 -> 0.0
- 0.01 -> 0.0
- 0.1 -> 140
- 0.15 -> 210
- 0.20 -> 285
- 0.25 -> 300
- 1.1 -> 300
- round_to_multiple_of: 1
- sliding_window_moving_average:
window_size: 3
send_every: 1
- platform: pmsx003
type: PMSX003
uart_id: uart_sensor
id: pms5003_hub
pm_1_0:
name: "PM <1.0µm"
pm_2_5:
name: "PM <2.5µm"
id: sensor_pm2_5
on_value:
- script.execute: update_fan_speed
pm_10_0:
name: "PM <10.0µm"
update_interval: 60s

- platform: dht
id: dht_sensor
pin: GPIO07 #D5
temperature:
name: "Purifier Room Temperature"
humidity:
name: "Purifier Room Humidity"
model: DHT22
update_interval: 60s

- platform: template
name: "AQI - PM10"
Expand Down Expand Up @@ -464,6 +448,23 @@ button:
icon: mdi:timer-outline
on_press:
- script.execute: cycle_timer

- platform: template
name: "Refresh All Sensors"
id: refresh_sensors_btn
icon: "mdi:refresh"
on_press:
then:
- logger.log: "Manual sensor refresh triggered"
- component.update: dht_sensor
- script.execute: play_beep
- lambda: |-
// trick the PMS5003 into thinking an update is overdue
id(pms5003_hub)->set_update_interval(30005);
- delay: 35s
- lambda: |-
id(pms5003_hub)->set_update_interval(60000);
- logger.log: "PMS5003 manual refresh completed; returning to sleep."

interval:
- interval: 100ms
Expand Down Expand Up @@ -526,10 +527,8 @@ script:
case 3: speed = 0.8f; break;
default: speed = 0.0f; break;
}
//special speed for sleep
if(ps=="Sleep") speed = 0.33f;

//if auto and clean air, turn off pwm, keep fan on!
if(ps=="Auto" && id(sensor_pm2_5).state<=1.0f) speed = 0.0f;

}
Expand All @@ -551,10 +550,9 @@ script:
then:
- lambda: |-
auto ps = id(fan_lv131).get_preset_mode();
if (ps =="Auto"){
if (ps == "Auto"){
ESP_LOGI("TS","from auto speed");
auto aqi = id(aqi_pm10).state;
//only if different!!!!!
uint8_t new_speed = 1;
if(aqi == 0.0) new_speed = 1;
if(aqi == 1.0) new_speed = 1;
Expand All @@ -575,7 +573,7 @@ script:
then:
- lambda: |-
auto ps = id(fan_lv131).get_preset_mode();
if(id(fan_lv131).state && ps==""){
if(id(fan_lv131).state && ps == ""){
auto call = id(fan_lv131).turn_on();
call.set_speed(2);
call.perform();
Expand Down