(Un)Official Prusa Enclosure "Smart Box" - Project
This is still work in progress,
but because there is no news on the "Smart Box" for the enclosure (e.g mentioned here), I started working on my own version and wanted to share my progress.
Basic idea:
Use an ESP32, a Sen54 Sensor to measure PM 1.0 / 2.5, VOC, Temperature, and Humidity, add an LCD and 3 Outputs: Light, AirFilter and Fan.
If the airquality drops , auto start the AirFilter, plus allow control via rotary and switches of On/Off, speeds, brightness, auto airfilter on/off and thresholds.
Firmware is "written" in esphome, Hardware is currently a breadboard prototype, and the case is also just mocked so far.
Here are a view screenshots:
Startup time!
Main, screen - Shows Environment data
Rotary opens the settings screen:
Click and Values can be changes...
And the current state of the Hardware
Next steps:
* Get the correct connectors, Click-Mate something, ...
* Build a better Hardware Version + Design a fitting case for the Enclosure (Bottom left like control board but more like the screen?)
* Test Runs with sensor in Enclosure and PLA, PETG, ABS
Current BOM:
1 * ESP32
1 * Sen54 Sensor
1 * Rotary Encoder
3 * Switches (Retro!)
1 * LCD ST7735
1 * IO Expander (MCP23017, overkill but i had it at home)
3 * Dual MOSFET Switch Module - Control 24v Leds, AirFilter, Fan via esp32
1 * Buck Converter (24v to 5v)
Will post updates here and share my progress, happy to share the current yaml for esp if anyone is interessted. My c coding really sucks so using esphome for the wrong purpose is still better 😉
Best Answer by Tobias Stanzel:
I started to create the github page for this project here:
https://github.com/tuct/smart-control-for-prusa-enclosure
Not perfect but should be better than this post!
I now plan to move the heater to the bottom of the enclosure and replace the flap with this iris:
https://www.printables.com/model/687126-servo-automated-iris-sg90-mod-for-esphome-home-ass
If i manage to do this, I will update the github repo!
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
Short update: Found this awseome mod and started to integrate it already!
So besides auto air filtration, i now can also set a target temp based on the flap and heater from the mod!
I also started to move away from the bread board to a hand made pcb, here are some pics:
New Case, LCD, controlls and ESP32
AC / Traget temp control
Lid / Flap test
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
After seeing those wires connecting to board - that's wayyyyyyy above my pay grade. 😱 😱
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
You can do a lot of this with OctoPrint and the existing plugins for it, which might be a little less daunting than assembling prototype boards and building everything from scratch. I myself only use a plugin to control LEDs in the enclosure and to switch the power for everything off when the printer is idle. And I use the Pi camera with OctoPrint of course. But I know there is an enclosure plugin and you can control stuff via the GPIO pins of the RasPi. Integrating temp sensors and controlling some servos and fans into my setup on the Pi is on my todo list for this winter.
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
This is pretty cool. I had ideas to do much the same, though I am (currently) less interested in VOC and particulates. I have a strip of RGBW LEDs and I was thinking of testing those out this weekend with the intention of integrating them soon. I am getting tired of using a flashlight when I want to inspect a print in-progress.
I have some other unrelated microcontroller projects too that I want to wrap up, and spent today learning how to use git and GitHub for code version control and platform independent coding. So I'm all set for another project now... 🤣
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
Any chance to get the source code?
See my GitHub and printables.com for some 3d stuff that you may like.
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
I will share the source code as soon as I can here, it's just a few hundred lines of yaml for esphome. Only a few a lambdas / c code.
Works without wlan, home assistant but could be enhanced to be monitored and controlled from home assistant as well (or via mqtt).
And yes, I would not recommend this as your first esphome / esp32 project as it requires a lot of soldering 😂
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
Made some progress and the inputs and sensors are now done and not as messy as before!
Need to add the output board with 3 Mosfets (light, fan, air filter), 2 Relais(Heater, Printer Power), and the Servo (Flap) + Buck Converter and AC stuff.
Here is the current esphome source (WIP!)
substitutions: node_name: mista-environment-sensor-test id_prefix: mista_environment_sensor_test name: Environment-Sensor-Test esphome: name: ${node_name} on_boot: priority: 600 then: - script.execute: id: display_off_timer esp32: board: az-delivery-devkit-v4 framework: type: arduino # Enable logging logger: # Enable Home Assistant API api: password: "SetAPassword" ota: password: "SetAPassword" wifi: ssid: "yourssd" password: "yourwifips" # Enable fallback hotspot (captive portal) in case wifi connection fails ap: ssid: "${name}" password: "chooseApassword" #captive_portal: spi: clk_pin: GPIO14 mosi_pin: GPIO26 i2c: sda: GPIO21 scl: GPIO22 frequency: 100kHz mcp23017: - id: 'mcp23017_hub' address: 0x20 globals: - id: auto_filter_pm_min_value type: int restore_value: yes initial_value: '150' - id: auto_filter_voc_min_value type: int restore_value: yes initial_value: '120' - id: light_a_brightness type: int restore_value: yes initial_value: '100' - id: filter_a_speed type: int restore_value: yes initial_value: '100' - id: fan_a_speed type: int restore_value: yes initial_value: '100' - id: filter_a_auto type: int restore_value: yes initial_value: '1' - id: ac_active type: int restore_value: yes initial_value: '0' - id: ac_target_temp_value type: int restore_value: yes initial_value: '25' select: - platform: template id: display_mode optimistic: true options: - environment - settings - single - display_off initial_option: environment set_action: - display.page.show: !lambda |- if (strcmp(x.c_str(), "settings") == 0 || strcmp(x.c_str(), "single") == 0) { return id(page_settings); } else { return id(page_environment); } - platform: template id: settings optimistic: true options: - ac_mode - ac_target_temp - default_light_brightness - default_filter_speed - default_fan_speed - auto_filter - auto_filter_pm_min - auto_filter_voc_min - back initial_option: ac_mode script: - id: display_off_timer mode: restart then: - if: condition: lambda: 'return (strcmp(id(display_mode).state.c_str(), "display_off") == 0 );' then: - light.turn_on: id: lcd_light - select.set: id: display_mode option: "environment" - if: condition: lambda: 'return (strcmp(id(display_mode).state.c_str(), "environment") == 0 );' then: - delay: 1min - select.set: id: display_mode option: "display_off" - light.turn_off: id: lcd_light else: - delay: 5s - select.set: id: display_mode option: "environment" - script.execute: id: display_off_timer - id: update_light # update the light brightness based on setting mode: queued then: - lambda: |- float brightness= ((float)id(light_a_brightness)/100); auto call = id(light_a).turn_on(); call.set_transition_length(0); // in ms call.set_brightness(brightness); call.perform(); - id: update_filter # update the filter speed based on setting mode: queued then: - lambda: |- auto currentState = id(filter_a).current_values.is_on(); auto currentValue = id(filter_a).current_values.get_brightness(); if(id(switch_filter).state){ // filter on float speed= ((float)id(filter_a_speed)/100); if(id(filter_a_auto) == 1){ if(currentState == 1 && id(auto_filter_sensor).state == 0.0 ){ // turn off if auto says no and currently on auto call = id(filter_a).turn_off(); call.set_transition_length(0); // in ms call.set_brightness(0); call.perform(); } if((currentState == 0 || currentValue != speed) && id(auto_filter_sensor).state == 1.0 ){ // turn on if auto says yes and currently off auto call = id(filter_a).turn_on(); call.set_transition_length(0); // in ms call.set_brightness(speed); call.perform(); } }else{ if(currentState == 0 || currentValue != speed ){ auto call = id(filter_a).turn_on(); call.set_transition_length(0); // in ms call.set_brightness(speed); call.perform(); } } }else{ if(currentState == 1){ auto call = id(filter_a).turn_off(); call.set_transition_length(0); // in ms call.set_brightness(0); call.perform(); } } - id: update_fan # update the filter speed based on setting mode: queued then: - lambda: |- float speed= ((float)id(fan_a_speed)/100); auto call = id(fan_a).turn_on(); call.set_transition_length(0); // in ms call.set_brightness(speed); call.perform(); - id: update_ac_target # update the filter speed based on setting mode: queued then: - lambda: |- auto call = id(my_climate).make_call(); call.set_target_temperature_low(id(ac_target_temp_value)); call.set_target_temperature_high(id(ac_target_temp_value)+1); if(id(ac_active)==1){ call.set_mode("HEAT_COOL"); }else{ call.set_mode("OFF"); } call.perform(); - id: open_lid # update the filter speed based on setting mode: single then: - servo.write: id: lid_servo level: -50.0% - id: close_lid # update the filter speed based on setting mode: single then: - servo.write: id: lid_servo level: 0.0% - id: update_ac # update the filter speed based on setting mode: queued then: - lambda: |- bool ac_is_active = false; float servoOpen = 0.5; float servoClosed = 0.0; if(id(ac_active)==1){ ac_is_active = true; } bool fan_switch = id(switch_fan).state; auto ac_mode = id(my_climate).mode; auto ac_action = id(my_climate).action; ESP_LOGI("main", "AC Active: %s",ac_is_active?"true":"false"); ESP_LOGI("main", "Fan switch: %s",fan_switch?"true":"false"); ESP_LOGI("main", "AC Mode: %d",ac_mode); ESP_LOGI("main", "AC Action: %d",ac_action); if(!fan_switch){ //fan/ac is dissabled! ESP_LOGI("main", "AC: OFF, HEATER: OFF, FAN: OFF, LID: closed!"); //lid //id(lid_servo).write(servoClosed); id(close_lid)->execute(); //heater id(heater_relais).turn_off(); //fan - off auto call = id(fan_a).turn_off(); call.set_transition_length(0); // in ms call.set_brightness(1.0); // 1.0 is full brightness call.perform(); }else{ //fan is active, check if we are in AC mode if(ac_is_active){ if(ac_action == CLIMATE_ACTION_HEATING){ ESP_LOGI("main", "AC: ON, HEATER: ON, FAN: ON, LID: closed!"); //fan on full //id(lid_servo).write(servoClosed); id(close_lid)->execute(); id(heater_relais).turn_on(); auto calla = id(fan_a).turn_on(); calla.set_transition_length(0); // in ms calla.set_brightness(1.0); // 1.0 is full brightness calla.perform(); } if(ac_action == CLIMATE_ACTION_COOLING){ ESP_LOGI("main", "AC: ON, HEATER: OFF, FAN: ON, LID: open!"); //id(lid_servo).write(servoOpen); id(open_lid)->execute(); id(heater_relais).turn_off(); //fan on full auto callb = id(fan_a).turn_on(); callb.set_transition_length(0); // in ms callb.set_brightness(1.0); // 1.0 is full brightness callb.perform(); } if(ac_action != CLIMATE_ACTION_HEATING && ac_action != CLIMATE_ACTION_COOLING){ ESP_LOGI("main", "AC: ON, HEATER: OFF, FAN: OFF, LID: closed!"); id(heater_relais).turn_off(); //id(lid_servo).write(servoClosed); id(close_lid)->execute(); auto callc = id(fan_a).turn_off(); callc.set_transition_length(0); // in ms callc.perform(); } }else{ //fan only, open lid ESP_LOGI("main", "AC: OFF, HEATER: OFF, FAN: ON, LID: open!"); //id(lid_servo).write(servoOpen); id(open_lid)->execute(); id(heater_relais).turn_off(); //fan on full auto calld = id(fan_a).turn_on(); calld.set_transition_length(0); // in ms calld.set_brightness(1.0); // 1.0 is full brightness, use configured here!!!! calld.perform(); } } - id: update_setting # used to update the settings mode: queued parameters: dir: int then: - lambda: |- uint stepSize = 10; //for speed/brightness uint minValue = 20; if(dir==0){ // reduce if(strcmp(id(settings).state.c_str(), "ac_mode")==0){ if(id(ac_active)==1){ id(ac_active) = 0; }else{ id(ac_active) = 1; } id(update_ac_target)->execute(); } if(strcmp(id(settings).state.c_str(), "ac_target_temp")==0 && id(ac_target_temp_value)>18){ id(ac_target_temp_value) = id(ac_target_temp_value)-1; id(update_ac_target)->execute(); } if(strcmp(id(settings).state.c_str(), "default_light_brightness")==0 && id(light_a_brightness)>minValue){ id(light_a_brightness) = id(light_a_brightness)-stepSize; //update brightness of light! if(id(switch_light).state){ id(update_light)->execute(); } } if(strcmp(id(settings).state.c_str(), "default_filter_speed")==0 && id(filter_a_speed)>minValue){ id(filter_a_speed) = id(filter_a_speed)-stepSize; //update brightness of light! id(update_filter)->execute(); } if(strcmp(id(settings).state.c_str(), "default_fan_speed")==0 && id(fan_a_speed)>minValue){ id(fan_a_speed) = id(fan_a_speed)-stepSize; //update brightness of light! if(id(switch_fan).state){ id(update_fan)->execute(); } } if(strcmp(id(settings).state.c_str(), "auto_filter")==0){ if(id(filter_a_auto)==1){ id(filter_a_auto) = 0; }else{ id(filter_a_auto) = 1; } } if(strcmp(id(settings).state.c_str(), "auto_filter_pm_min")==0 && id(auto_filter_pm_min_value)>0){ id(auto_filter_pm_min_value) = id(auto_filter_pm_min_value)-stepSize; } if(strcmp(id(settings).state.c_str(), "auto_filter_voc_min")==0 && id(auto_filter_voc_min_value)>0){ id(auto_filter_voc_min_value) = id(auto_filter_voc_min_value)-stepSize; } }else{ // add if(strcmp(id(settings).state.c_str(), "ac_mode")==0){ if(id(ac_active)==1){ id(ac_active) = 0; }else{ id(ac_active) = 1; } id(update_ac_target)->execute(); } if(strcmp(id(settings).state.c_str(), "ac_target_temp")==0 && id(ac_target_temp_value)<50){ id(ac_target_temp_value) = id(ac_target_temp_value)+1; id(update_ac_target)->execute(); } if(strcmp(id(settings).state.c_str(), "default_light_brightness")==0 && id(light_a_brightness)<100){ id(light_a_brightness) = id(light_a_brightness)+stepSize; //update brightness of light! if(id(switch_light).state){ id(update_light)->execute(); } } if(strcmp(id(settings).state.c_str(), "default_filter_speed")==0 && id(filter_a_speed)<100){ id(filter_a_speed) = id(filter_a_speed)+stepSize; //update brightness of light! id(update_filter)->execute(); } if(strcmp(id(settings).state.c_str(), "default_fan_speed")==0 && id(fan_a_speed)<100){ id(fan_a_speed) = id(fan_a_speed)+stepSize; //update brightness of light! if(id(switch_fan).state){ id(update_fan)->execute(); } } if(strcmp(id(settings).state.c_str(), "auto_filter")==0){ if(id(filter_a_auto)==1){ id(filter_a_auto) = 0; }else{ id(filter_a_auto) = 1; } } if(strcmp(id(settings).state.c_str(), "auto_filter_pm_min")==0 && id(auto_filter_pm_min_value)<990){ id(auto_filter_pm_min_value) = id(auto_filter_pm_min_value)+stepSize; } if(strcmp(id(settings).state.c_str(), "auto_filter_voc_min")==0 && id(auto_filter_voc_min_value)<500){ id(auto_filter_voc_min_value) = id(auto_filter_voc_min_value)+stepSize; } } color: - id: my_red red: 100% green: 3% blue: 5% - id: my_green red: 0% green: 100% blue: 0% - id: my_blue red: 0% green: 0% blue: 100% - id: my_yellow red: 50% green: 50% blue: 0% - id: my_gray red: 30% green: 30% blue: 30% font: # gfonts://family[@weight] - file: type: gfonts family: Inconsolata weight: 700 id: roboto glyphs: |- !"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzµ³>/ size: 13 - file: type: gfonts family: Inconsolata weight: 700 id: roboto_small glyphs: |- !"%()+=,-_.:°0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ abcdefghijklmnopqrstuvwxyzµ³>/ size: 11 image: - file: mdi:lightbulb id: icon_light resize: 16x16 - file: mdi:air-filter id: icon_filter resize: 16x16 - file: mdi:air-conditioner id: icon_ac resize: 18x18 - file: mdi:fan id: icon_fan resize: 16x16 - file: "thermometer_FILL0_wght400_GRAD0_opsz24.svg" id: icon_temp resize: 18x18 type: TRANSPARENT_BINARY dither: FLOYDSTEINBERG - file: "humidity_mid_FILL0_wght400_GRAD0_opsz24.svg" id: icon_hum resize: 18x18 type: TRANSPARENT_BINARY dither: FLOYDSTEINBERG graph: # Show bare-minimum auto-ranged graph - id: ${id_prefix}_pm10_graph duration: 5min width: 50 height: 20 y_grid: 10000 border: false traces: - sensor: ${id_prefix}_pm10 line_type: SOLID line_thickness: 2 color: my_blue - id: ${id_prefix}_pm25_graph duration: 5min width: 50 height: 20 y_grid: 10000 border: false traces: - sensor: ${id_prefix}_pm25 line_type: SOLID line_thickness: 2 color: my_blue - id: ${id_prefix}_voc_graph duration: 5min width: 50 height: 20 max_value: 500 min_value: 1 y_grid: 500 border: false traces: - sensor: ${id_prefix}_voc line_type: SOLID line_thickness: 2 color: my_blue - id: ${id_prefix}_temperature_graph duration: 5min width: 50 height: 20 max_value: 70 min_value: 20 y_grid: 200 traces: - sensor: ${id_prefix}_temperature line_type: SOLID line_thickness: 2 color: my_blue - id: ${id_prefix}_humidity_graph duration: 5min width: 50 height: 20 max_value: 90 min_value: 30 y_grid: 200 traces: - sensor: ${id_prefix}_humidity line_type: SOLID line_thickness: 2 color: my_blue display: - platform: st7735 model: "INITR_18BLACKTAB" id: my_display reset_pin: 33 cs_pin: 27 dc_pin: 13 rotation: 180 device_width: 128 device_height: 160 col_start: 0 row_start: 0 eight_bit_color: false update_interval: 300ms pages: - id: page_environment lambda: |- it.print(64, 2, id(roboto),COLOR_ON,TextAlign::TOP_CENTER, "ENVIRONMENT"); if(isnan(id(${id_prefix}_temperature).state)){ it.print(64, 24, id(roboto),COLOR_ON,TextAlign::TOP_CENTER, "Waiting for"); it.print(64, 40, id(roboto),COLOR_ON,TextAlign::TOP_CENTER, "sensor data"); it.print(64, 56, id(roboto),COLOR_ON,TextAlign::TOP_CENTER, "..."); }else{ //it.fill(Color::BLACK); //PM 2.5 - LEFT //it.line(6, 19, 48, 19); uint tempoffsetY = 16; //it.print(3, tempoffsetY+20, id(roboto),COLOR_ON,TextAlign::BOTTOM_LEFT, "TEMP"); it.image(3, tempoffsetY+2, id(icon_temp), ImageAlign::TOP_LEFT); it.graph(42, tempoffsetY+2, id(${id_prefix}_temperature_graph),Color::BLACK); it.printf(112, tempoffsetY+1, id(roboto), TextAlign::TOP_CENTER, "%.1f", id(${id_prefix}_temperature).state); it.print(112, tempoffsetY+11, id(roboto_small), COLOR_ON, TextAlign::TOP_CENTER , "°C"); uint humoffsetY = 40; //it.print(3, humoffsetY+20, id(roboto),COLOR_ON,TextAlign::BOTTOM_LEFT, "HUM"); it.image(3, humoffsetY+2, id(icon_hum), ImageAlign::TOP_LEFT); it.graph(42, humoffsetY+2, id(${id_prefix}_humidity_graph),Color::BLACK); it.printf(112, humoffsetY+1, id(roboto), TextAlign::TOP_CENTER, "%.1f", id(${id_prefix}_humidity).state); it.print(112, humoffsetY+11, id(roboto_small), COLOR_ON, TextAlign::TOP_CENTER , "%"); uint pm10offsetY = 64; it.print(3, pm10offsetY+20, id(roboto),COLOR_ON,TextAlign::BOTTOM_LEFT, "PM1.0"); it.graph(42, pm10offsetY+2, id(${id_prefix}_pm10_graph),Color::BLACK); it.printf(112, pm10offsetY+1, id(roboto), TextAlign::TOP_CENTER, "%.0f", id(${id_prefix}_pm10).state); it.print(112, pm10offsetY+11, id(roboto_small), COLOR_ON, TextAlign::TOP_CENTER , "µg/m³"); uint pm25offsetY = 88; it.print(3, pm25offsetY+20, id(roboto),COLOR_ON,TextAlign::BOTTOM_LEFT, "PM2.5"); it.graph(42, pm25offsetY+2, id(${id_prefix}_pm25_graph),Color::BLACK); it.printf(112, pm25offsetY+1, id(roboto), TextAlign::TOP_CENTER, "%.0f", id(${id_prefix}_pm25).state); it.print(112, pm25offsetY+11, id(roboto_small), COLOR_ON, TextAlign::TOP_CENTER , "µg/m³"); uint vocoffsetY = 112; it.print(3, vocoffsetY+20, id(roboto),COLOR_ON,TextAlign::BOTTOM_LEFT, "VOC"); it.graph(42, vocoffsetY+2, id(${id_prefix}_voc_graph),Color::BLACK); it.printf(112, vocoffsetY+5, id(roboto), TextAlign::TOP_CENTER, "%.0f", id(${id_prefix}_voc).state); //it.print(112, vocoffsetY+10, id(roboto_small), COLOR_ON, TextAlign::TOP_CENTER , "%"); uint iconY = 152; if(id(switch_light).state){ it.image(16, iconY, id(icon_light), ImageAlign::BOTTOM_CENTER,id(my_green)); }else{ it.image(16, iconY, id(icon_light), ImageAlign::BOTTOM_CENTER,id(my_gray)); } if(id(switch_filter).state){ if(id(filter_a_auto) == 1 && id(auto_filter_sensor).state == 0.0){ //auto is on it.image(64, iconY, id(icon_filter), ImageAlign::BOTTOM_CENTER,id(my_yellow)); }else{ it.image(64, iconY, id(icon_filter), ImageAlign::BOTTOM_CENTER,id(my_green)); } }else{ it.image(64, iconY, id(icon_filter), ImageAlign::BOTTOM_CENTER,id(my_gray)); } if(id(switch_fan).state){ if(id(ac_active) ==1){ it.image(112, iconY+2, id(icon_ac), ImageAlign::BOTTOM_CENTER,id(my_green)); //get target from climate?!?! it.printf(99, 150, id(roboto_small),COLOR_ON,TextAlign::BOTTOM_CENTER, "%d",id(ac_target_temp_value)); }else{ it.image(112, iconY, id(icon_fan), ImageAlign::BOTTOM_CENTER,id(my_green)); } }else{ it.image(112, iconY, id(icon_fan), ImageAlign::BOTTOM_CENTER,id(my_gray)); } if(id(filter_a_auto) ==1){ auto msg = "Auto - Run"; if(id(auto_filter_sensor).state == 0.0){ msg = "Auto - Idle"; } it.printf(64, 160, id(roboto_small),COLOR_ON,TextAlign::BOTTOM_CENTER, "%s",msg); } } - id: page_settings lambda: |- uint offsetY = 2; uint offsetX = 10; uint lineheight = 16; uint activeYoffset = offsetY+12; uint activeXoffset = offsetX/2-2; auto displayModeIndex = id(display_mode).active_index(); auto settingsIndex = id(settings).active_index(); if (settingsIndex.has_value()) { activeYoffset = settingsIndex.value(); if(settingsIndex.value()==1 || settingsIndex.value()==6 || settingsIndex.value()==7){ activeXoffset=offsetX/2+3; } } else { ESP_LOGI("main", "No option is active"); } if (displayModeIndex.has_value()) { if(displayModeIndex.value()==2 ){ activeXoffset=98; } } else { ESP_LOGI("main", "No option is active"); } it.print(64, offsetY, id(roboto),COLOR_ON,TextAlign::TOP_CENTER, "SETTINGS"); it.print(offsetX, offsetY+(1*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_LEFT, "A/C ........"); it.print(offsetX, offsetY+(2*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_LEFT, " TARGET C°.."); it.print(offsetX, offsetY+(3*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_LEFT, "LIGHT......."); it.print(offsetX, offsetY+(4*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_LEFT, "FILTER......"); it.print(offsetX, offsetY+(5*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_LEFT, "FAN SPEED..."); it.print(offsetX, offsetY+(6*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_LEFT, "AUTO FILTER."); it.print(offsetX, offsetY+(7*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_LEFT, " PM 2.5 >..."); it.print(offsetX, offsetY+(8*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_LEFT, " VOC >......"); it.print(offsetX, offsetY+(9*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_LEFT, "BACK"); it.circle(offsetX/2-2, offsetY+(1*lineheight)+7, 3); it.circle(offsetX/2+3, offsetY+(2*lineheight)+7, 3); it.circle(offsetX/2-2, offsetY+(3*lineheight)+7, 3); it.circle(offsetX/2-2, offsetY+(4*lineheight)+7, 3); it.circle(offsetX/2-2, offsetY+(5*lineheight)+7, 3); it.circle(offsetX/2-2, offsetY+(6*lineheight)+7, 3); it.circle(offsetX/2+3, offsetY+(7*lineheight)+7, 3); it.circle(offsetX/2+3, offsetY+(8*lineheight)+7, 3); it.circle(offsetX/2-2, offsetY+(9*lineheight)+7, 3); //mark selected setting! it.filled_circle(activeXoffset, offsetY+((activeYoffset+1)*lineheight)+7, 3); //print selected setting! //it.printf(64, offsetY+(8*lineheight), id(roboto), TextAlign::TOP_CENTER, "%s", id(display_mode).state.c_str() ); //values uint lightValue = id(light_a_brightness); uint filterValue = id(filter_a_speed); uint fanValue = id(fan_a_speed); if(id(ac_active) ==1){ it.printf(125, offsetY+(1*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_RIGHT, "%s","ON"); }else{ it.printf(125, offsetY+(1*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_RIGHT, "%s","OFF"); } it.printf(125, offsetY+(2*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_RIGHT, "%d",id(ac_target_temp_value)); it.printf(125, offsetY+(3*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_RIGHT, "%d",lightValue); it.printf(125, offsetY+(4*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_RIGHT, "%d",filterValue); it.printf(125, offsetY+(5*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_RIGHT, "%d",fanValue); if(id(filter_a_auto) ==1){ it.printf(125, offsetY+(6*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_RIGHT, "%s","ON"); }else{ it.printf(125, offsetY+(6*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_RIGHT, "%s","OFF"); } it.printf(125, offsetY+(7*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_RIGHT, "%d",id(auto_filter_pm_min_value)); it.printf(125, offsetY+(8*lineheight), id(roboto),COLOR_ON,TextAlign::TOP_RIGHT, "%d",id(auto_filter_voc_min_value)); ESP_LOGI("main", "auto_filter_pm_max: %d is active", id(auto_filter_pm_min_value)); sensor: - platform: template id: "auto_filter_sensor" lambda: |- id(update_filter)->execute(); id(update_ac)->execute(); if (id(filter_a_auto) == 1) { if(id(${id_prefix}_pm25).state>id(auto_filter_pm_min_value) || id(${id_prefix}_voc).state>id(auto_filter_voc_min_value)){ return 1.0; } return 0.0; } else { //on and no auto, so... return 0.0; } update_interval: 1s - platform: sen5x id: sen54 pm_1_0: name: " PM <1µm Weight concentration" id: ${id_prefix}_pm10 accuracy_decimals: 1 filters: - sliding_window_moving_average: window_size: 9 send_every: 1 pm_2_5: name: " PM <2.5µm Weight concentration" id: ${id_prefix}_pm25 accuracy_decimals: 1 filters: - sliding_window_moving_average: window_size: 9 send_every: 1 pm_4_0: name: " PM <4µm Weight concentration" id: ${id_prefix}_pm40 accuracy_decimals: 1 pm_10_0: name: " PM <10µm Weight concentration" id: ${id_prefix}_pm100 accuracy_decimals: 1 temperature: name: "Temperature" accuracy_decimals: 1 id: ${id_prefix}_temperature humidity: name: "Humidity" accuracy_decimals: 0 id: ${id_prefix}_humidity voc: name: "VOC" id: ${id_prefix}_voc algorithm_tuning: index_offset: 100 learning_time_offset_hours: 12 learning_time_gain_hours: 12 gating_max_duration_minutes: 180 std_initial: 50 gain_factor: 230 filters: - sliding_window_moving_average: window_size: 9 send_every: 1 temperature_compensation: offset: 0 normalized_offset_slope: 0 time_constant: 0 acceleration_mode: low store_baseline: true address: 0x69 update_interval: 10s - platform: rotary_encoder name: "Rotary Encoder" id: rotary pin_a: GPIO05 pin_b: GPIO17 min_value: 1 max_value: 20 on_clockwise: - if: condition: lambda: 'return (strcmp(id(display_mode).state.c_str(), "environment") == 0);' then: - select.set_index: id: display_mode index: 1 - if: condition: lambda: 'return (strcmp(id(display_mode).state.c_str(), "settings") == 0);' then: - select.next: id: settings cycle: true - if: condition: lambda: 'return (strcmp(id(display_mode).state.c_str(), "single") == 0);' then: - lambda: |- ESP_LOGI("main", "UP NOW: %d is active", id(light_a_brightness)); id(update_setting)->execute(1); - lambda: |- id(display_off_timer)->execute(); on_anticlockwise: - if: condition: lambda: 'return (strcmp(id(display_mode).state.c_str(), "environment") == 0);' then: - select.set_index: id: display_mode index: 1 - if: condition: lambda: 'return (strcmp(id(display_mode).state.c_str(), "settings") == 0);' then: - select.previous: id: settings cycle: true - if: condition: lambda: 'return (strcmp(id(display_mode).state.c_str(), "single") == 0 );' then: - lambda: |- id(update_setting)->execute(0); - lambda: |- id(display_off_timer)->execute(); binary_sensor: - platform: gpio pin: mcp23xxx: mcp23017_hub number: 3 mode: input: true inverted: true name: "Rotary" on_multi_click: - timing: - ON for at most 1s - OFF for at least 0.5s then: - lambda: |- //ENVIRONMENT uint clickHandled = 0; if(strcmp(id(display_mode).state.c_str(), "environment") == 0){ auto displayCall = id(display_mode).make_call(); displayCall.set_option("settings"); displayCall.perform(); clickHandled = 1; } //SINGLE if(clickHandled==0 && strcmp(id(display_mode).state.c_str(), "single") ==0){ auto displayCall = id(display_mode).make_call(); displayCall.set_option("settings"); displayCall.perform(); clickHandled = 1; } //SETTINGS // back -> go back to ENVIRONMENT if(clickHandled==0 && strcmp(id(display_mode).state.c_str(), "settings") ==0){ if(strcmp(id(settings).state.c_str(), "back") == 0){ auto displayCall = id(display_mode).make_call(); displayCall.set_option("environment"); displayCall.perform(); }else{ auto displayCall = id(display_mode).make_call(); displayCall.set_option("single"); displayCall.perform(); } clickHandled = 1; } id(display_off_timer)->execute(); - platform: gpio pin: mcp23xxx: mcp23017_hub number: 0 mode: input: true pullup: true inverted: true id: switch_light publish_initial_state: true on_state: - lambda: |- if(id(switch_light).state){ id(update_light)->execute(); }else{ auto call = id(light_a).turn_off(); call.set_transition_length(0); // in ms call.set_brightness(0); call.perform(); } id(display_off_timer)->execute(); filters: - delayed_on_off: time_on: 200ms time_off: 200ms - platform: gpio pin: mcp23xxx: mcp23017_hub number: 2 mode: input: true pullup: true inverted: true id: switch_filter publish_initial_state: true on_state: - lambda: |- id(update_filter)->execute(); id(display_off_timer)->execute(); filters: - delayed_on_off: time_on: 200ms time_off: 200ms - platform: gpio pin: mcp23xxx: mcp23017_hub number: 1 mode: input: true pullup: true inverted: true id: switch_fan publish_initial_state: true on_state: - lambda: |- if(id(switch_fan).state){ id(update_fan)->execute(); }else{ auto call = id(fan_a).turn_off(); call.set_transition_length(0); // in ms call.set_brightness(0); call.perform(); } id(display_off_timer)->execute(); filters: - delayed_on_off: time_on: 200ms time_off: 200ms - platform: gpio pin: mcp23xxx: mcp23017_hub number: 4 mode: input: true pullup: true inverted: true id: switch_printer publish_initial_state: true on_state: - logger.log: PRINTER - lambda: |- if(id(switch_printer).state){ id(printer_relais).turn_on(); }else{ id(printer_relais).turn_off(); } filters: - delayed_on_off: time_on: 200ms time_off: 200ms climate: - platform: thermostat name: "Enclosure Climate Controller" id: my_climate sensor: ${id_prefix}_temperature visual: min_temperature: 18 max_temperature: 55 temperature_step: 1 min_cooling_off_time: 10s min_cooling_run_time: 10s min_heating_off_time: 10s min_heating_run_time: 10s min_idle_time: 10s cool_action: - script.execute: update_ac heat_action: - script.execute: update_ac idle_action: - script.execute: update_ac on_state: - script.execute: update_ac on_control: - script.execute: update_ac default_preset: default preset: - name: default default_target_temperature_low: 25 default_target_temperature_high: 26 mode: HEAT_COOL output: - platform: ledc pin: GPIO16 id: gpio_16 frequency: 25kHz channel: 0 - platform: ledc pin: GPIO23 id: gpio_23 channel: 2 frequency: 25kHz - platform: ledc pin: GPIO19 id: gpio_19 channel: 4 frequency: 25kHz - platform: ledc id: pwm_servo pin: GPIO18 channel: 6 frequency: 50Hz - platform: gpio pin: GPIO04 # mcp23xxx: mcp23017_hub # number: 13 id: lcd_out number: - platform: template id: servo_pos min_value: -100 initial_value: 0 max_value: 100 step: 10 optimistic: true # set_action: # then: # - servo.write: # id: lid_servo # level: !lambda 'return x / 100.0;' servo: - id: lid_servo output: pwm_servo transition_length: 500000s auto_detach_time: 1s switch: - platform: gpio pin: GPIO33 # mcp23xxx: mcp23017_hub # number: 5 id: printer_relais restore_mode: RESTORE_DEFAULT_OFF - platform: gpio pin: GPIO25 # mcp23xxx: mcp23017_hub # number: 5 id: heater_relais restore_mode: ALWAYS_OFF # Example usage in a light light: - platform: monochromatic output: gpio_16 id: light_a name: "Prusa Enclosure - Light" restore_mode: ALWAYS_OFF - platform: monochromatic output: gpio_23 id: filter_a name: "Prusa Enclosure - Air Filter" restore_mode: ALWAYS_OFF - platform: monochromatic output: gpio_19 id: fan_a name: "Prusa Enclosure - Fan" restore_mode: ALWAYS_OFF - platform: binary id: "lcd_light" restore_mode: ALWAYS_ON output: lcd_out
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
Updated POM:
1 * ESP32
1 * Sen54 Sensor
1 * Rotary Encoder
3 * Switches (Retro!)
1 * LCD ST7735
1 * IO Expander (MCP23017, overkill but i had it at home)
3 * Dual MOSFET Switch Module - Control 24v Leds, AirFilter, Fan via esp32
1 * Buck Converter (24v to 5v)
2 * 3.3V Relais (Heater and to control Printer power)
From Automated Heating System
1 * 200W heater+fan combo (i use a MOSFET to control the fan)
1* SG-90 type servo
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
Thanks!
Maybe you should put it all into a git repo?
See my GitHub and printables.com for some 3d stuff that you may like.
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
I will post the code and everything to github / printables when I am done.
Here are some updates:
Added DS18B20 temperature probe for the heater (as in the Automated Heating System)
Created power/breakout board and a case for it + PSU mount with power switch and plug
The case and PSU will be mounted to the back like in the Automated Heating System, case is a bit bigger 😉
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
I just got my enclosure built yesterday and was immediately looking for something like this. Especially since I’m using the HEPA filter and the LED lighting. I absolutely love this! Especially tying it together with the heater automation as I was very interested in that as well. I’m able to get the inside up to 35C but it takes 3 hours of printing. Obviously not ideal for ABS / Nylon. Have you had any more updates to this project? Do you need help with anything? I’d love to help if you need it!
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
Hi all,
It been a while, in the meantime I finished the build and already using it!
I added an additional temp sensor and also a runtime counter for the airfilter + a nevermore v6 to speed up the filtration.
I want to add a timer for the heater or couple it somehow with the printer, so that it stops heating If the print is finished.
Heater and filter is great!
Fun fact, when heating starts, VOC is going up only from the heater! Otherwise I can see that, based on the material there is quite some pm1/2 and or VOC, and my automation autostarts the airfilter and stops when below a treshhold.
I started to work on more documentation (fitzing diagram,...) but I guess I need the Christmas vacation to finish it and upload it to github.
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
Looking forward to it! 👍
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
Here you can see a heating for abs with heater on and heatbed at 100c, took 25min to heat up.
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
First print was a short one, 2nd was longer, so I turned off the heater and went to bed, you can see that the heat from the bed keeps heating up the enclosure 😉
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
I finished the first shot of the fritzing sketch, took longer than building it 😉
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
Great project, Thomas. I'm waiting for my new Mk4 w/Enclosure kit to arrive after Christmas, and I'm excited to see how this progresses -- I'll certainly attempt a make!
RE: (Un)Official Prusa Enclosure "Smart Box" - Project
I started to create the github page for this project here:
https://github.com/tuct/smart-control-for-prusa-enclosure
Not perfect but should be better than this post!
I now plan to move the heater to the bottom of the enclosure and replace the flap with this iris:
https://www.printables.com/model/687126-servo-automated-iris-sg90-mod-for-esphome-home-ass
If i manage to do this, I will update the github repo!