[{"data":1,"prerenderedAt":614},["ShallowReactive",2],{"doc-\u002Fhardware\u002Fdiy-esp32":3},{"id":4,"title":5,"body":6,"description":604,"edit":605,"extension":606,"meta":607,"navigation":608,"path":609,"seo":610,"stem":611,"vertical":605,"weight":612,"__hash__":613},"content\u002Fhardware\u002Fdiy-esp32.md","DIY ESP32 + ESPHome",{"type":7,"value":8,"toc":596},"minimark",[9,13,18,34,42,46,97,107,111,122,198,205,227,230,236,243,247,254,284,287,291,310,427,434,531,535,538,589,592],[10,11,12],"p",{},"A bring-your-own-hardware path for tinkerers and anyone whose sensor mix\nis not covered by a commercial unit. Total bill of materials: ≈ €8.",[14,15,17],"h2",{"id":16},"bom","BOM",[19,20,21,25,28,31],"ul",{},[22,23,24],"li",{},"ESP32 dev board: WROOM-32 or ESP32-S3-DevKit, ~€4.",[22,26,27],{},"BME280 breakout (temperature + humidity + pressure), ~€2.",[22,29,30],{},"18650 holder + cell, optional, ~€2.",[22,32,33],{},"Four jumper wires.",[10,35,36,37,41],{},"The BME280 talks I²C. Default address on most breakouts is ",[38,39,40],"code",{},"0x76",".",[14,43,45],{"id":44},"wire","Wire",[47,48,49,62],"table",{},[50,51,52],"thead",{},[53,54,55,59],"tr",{},[56,57,58],"th",{},"BME280",[56,60,61],{},"ESP32 (WROOM)",[63,64,65,74,81,89],"tbody",{},[53,66,67,71],{},[68,69,70],"td",{},"VCC",[68,72,73],{},"3V3",[53,75,76,79],{},[68,77,78],{},"GND",[68,80,78],{},[53,82,83,86],{},[68,84,85],{},"SDA",[68,87,88],{},"GPIO 21",[53,90,91,94],{},[68,92,93],{},"SCL",[68,95,96],{},"GPIO 22",[10,98,99,100,103,104,41],{},"If you bought the BMP280 by mistake (no humidity, only T + P), the wiring\nis the same and ESPHome's ",[38,101,102],{},"bmp280"," platform works in place of ",[38,105,106],{},"bme280",[14,108,110],{"id":109},"flash-esphome","Flash ESPHome",[10,112,113,114,117,118,121],{},"Install the ESPHome CLI (",[38,115,116],{},"pip install esphome"," or use the Docker image).\nSave the following to ",[38,119,120],{},"opensense-fridge.yaml",":",[123,124,126,129,132,135,138,141,144,149,152,155,160,163,166],"cmd",{"label":125},"$ opensense-fridge.yaml",[10,127,128],{},"esphome:\nname: opensense-fridge\nfriendly_name: Walk-in fridge",[10,130,131],{},"esp32:\nboard: esp32dev\nframework:\ntype: arduino",[10,133,134],{},"logger:\nlevel: INFO",[10,136,137],{},"wifi:\nssid: !secret wifi_ssid\npassword: !secret wifi_pwd\nmanual_ip:\nstatic_ip: 192.168.1.42\ngateway: 192.168.1.1\nsubnet: 255.255.255.0",[10,139,140],{},"api:\nencryption:\nkey: !secret api_key",[10,142,143],{},"ota:",[19,145,146],{},[22,147,148],{},"platform: esphome",[10,150,151],{},"i2c:\nsda: 21\nscl: 22\nscan: true",[10,153,154],{},"sensor:",[19,156,157],{},[22,158,159],{},"platform: bme280_i2c\naddress: 0x76\nupdate_interval: 60s\ntemperature:\nname: \"Temperature\"\nid: temp\nhumidity:\nname: \"Humidity\"\nid: hum\npressure:\nname: \"Pressure\"\nid: pres",[10,161,162],{},"http_request:\nuseragent: opensense-esp32\ntimeout: 10s",[10,164,165],{},"interval:",[19,167,168],{},[22,169,170,171],{},"interval: 60s\nthen:\n",[19,172,173],{},[22,174,175,176,180,181,197],{},"http_request.post:\nurl: !secret opensense_ingest_url\nheaders:\nContent-Type: application\u002Fjson\nbody: |-\n{\n\"ts\": \"",[177,178],"binding",{"value":179},"now().to_iso()","\",\n\"measurements\": ",[182,183,184,185,188,189,192,193,196],"span",{},"\n{ \"type\": \"temperature\", \"value\": ",[177,186],{"value":187},"id(temp).state"," },\n{ \"type\": \"humidity\",    \"value\": ",[177,190],{"value":191},"id(hum).state"," },\n{ \"type\": \"pressure\",    \"value\": ",[177,194],{"value":195},"id(pres).state"," }\n","\n}",[10,199,200,201,204],{},"Create a ",[38,202,203],{},"secrets.yaml"," next to it:",[123,206,208],{"label":207},"$ secrets.yaml",[10,209,210,211,214,215,218,219,226],{},"wifi_ssid: 'CafeBratislava'\nwifi_pwd:  'pourover4eva'\napi_key:   '\u003C32-byte base64 from ",[38,212,213],{},"esphome wizard"," or ",[38,216,217],{},"openssl rand -base64 32",">'\nopensense_ingest_url: '",[220,221,225],"a",{"href":222,"rel":223},"https:\u002F\u002Fapi.opensense.murzin.digital\u002Fv1\u002Fingest?token=ds_live_%E2%80%A6&device=fridge01",[224],"nofollow","https:\u002F\u002Fapi.opensense.murzin.digital\u002Fv1\u002Fingest?token=ds_live_…&device=fridge01","'",[10,228,229],{},"Compile and flash:",[123,231,233],{"label":232},"$ flash",[10,234,235],{},"esphome run opensense-fridge.yaml",[10,237,238,239,242],{},"After the first boot the ESP32 starts POSTing to OpenSense every 60 s. Add\nthe device via ",[38,240,241],{},"+ ADD DEVICE → DIY → ESP32"," to receive the ingest URL.",[14,244,246],{"id":245},"deep-sleep-battery-operation","Deep sleep (battery operation)",[10,248,249,250,253],{},"For battery operation, replace the ",[38,251,252],{},"interval"," block with a deep-sleep\nstrategy:",[123,255,257,260,265,268],{"label":256},"$ esphome deep-sleep",[10,258,259],{},"deep_sleep:\nrun_duration: 8s\nsleep_duration: 600s   # 10 minutes\nid: ds",[261,262,264],"h1",{"id":263},"trigger-one-readpost-on-boot-then-sleep","Trigger one read+post on boot, then sleep",[10,266,267],{},"on_boot:",[19,269,270],{},[22,271,272,273],{},"priority: -100.0\nthen:\n",[19,274,275,278,281],{},[22,276,277],{},"sensor.template.publish:\nid: temp\nstate: !lambda 'return id(bme280).temperature->state;'",[22,279,280],{},"http_request.post: …",[22,282,283],{},"deep_sleep.enter: ds",[10,285,286],{},"A WROOM-32 in this regime draws \u003C 12 µA during sleep; a 3000 mAh 18650\nruns for about 6 months at 10-minute cadence. The ESP32-S3 is worse;\nprefer WROOM-32 or ESP32-C3 for battery sensors.",[14,288,290],{"id":289},"multiple-probes","Multiple probes",[10,292,293,294,298,299,302,303,305,306,309],{},"The BME280 is one I²C address. To run ",[295,296,297],"strong",{},"two"," BME280s on the same ESP32,\nsolder the ",[38,300,301],{},"SDO"," line of the second one to ",[38,304,73],{}," to switch its I²C address\nto ",[38,307,308],{},"0x77",". In the YAML:",[311,312,317],"pre",{"className":313,"code":314,"language":315,"meta":316,"style":316},"language-yaml shiki shiki-themes github-dark github-dark","sensor:\n  - platform: bme280_i2c\n    address: 0x76\n    temperature: { name: \"Fridge front\" }\n    …\n  - platform: bme280_i2c\n    address: 0x77\n    temperature: { name: \"Fridge back\"  }\n    …\n","yaml","",[38,318,319,331,347,359,378,384,395,405,422],{"__ignoreMap":316},[182,320,323,327],{"class":321,"line":322},"line",1,[182,324,326],{"class":325},"sxg3X","sensor",[182,328,330],{"class":329},"suv1-",":\n",[182,332,334,337,340,343],{"class":321,"line":333},2,[182,335,336],{"class":329},"  - ",[182,338,339],{"class":325},"platform",[182,341,342],{"class":329},": ",[182,344,346],{"class":345},"s4wv1","bme280_i2c\n",[182,348,350,353,355],{"class":321,"line":349},3,[182,351,352],{"class":325},"    address",[182,354,342],{"class":329},[182,356,358],{"class":357},"s8ozJ","0x76\n",[182,360,362,365,368,371,373,376],{"class":321,"line":361},4,[182,363,364],{"class":325},"    temperature",[182,366,367],{"class":329},": { ",[182,369,370],{"class":325},"name",[182,372,342],{"class":329},[182,374,375],{"class":345},"\"Fridge front\"",[182,377,196],{"class":329},[182,379,381],{"class":321,"line":380},5,[182,382,383],{"class":345},"    …\n",[182,385,387,389,391,393],{"class":321,"line":386},6,[182,388,336],{"class":329},[182,390,339],{"class":325},[182,392,342],{"class":329},[182,394,346],{"class":345},[182,396,398,400,402],{"class":321,"line":397},7,[182,399,352],{"class":325},[182,401,342],{"class":329},[182,403,404],{"class":357},"0x77\n",[182,406,408,410,412,414,416,419],{"class":321,"line":407},8,[182,409,364],{"class":325},[182,411,367],{"class":329},[182,413,370],{"class":325},[182,415,342],{"class":329},[182,417,418],{"class":345},"\"Fridge back\"",[182,420,421],{"class":329},"  }\n",[182,423,425],{"class":321,"line":424},9,[182,426,383],{"class":345},[10,428,429,430,433],{},"In the JSON POST, include a ",[38,431,432],{},"label"," per measurement so OpenSense can map\nto two channels:",[311,435,439],{"className":436,"code":437,"language":438,"meta":316,"style":316},"language-json shiki shiki-themes github-dark github-dark","{\n  \"measurements\": [\n    { \"type\": \"temperature\", \"value\": 4.1, \"label\": \"front\" },\n    { \"type\": \"temperature\", \"value\": 4.3, \"label\": \"back\"  }\n  ]\n}\n","json",[38,440,441,446,454,491,521,526],{"__ignoreMap":316},[182,442,443],{"class":321,"line":322},[182,444,445],{"class":329},"{\n",[182,447,448,451],{"class":321,"line":333},[182,449,450],{"class":357},"  \"measurements\"",[182,452,453],{"class":329},": [\n",[182,455,456,459,462,464,467,470,473,475,478,480,483,485,488],{"class":321,"line":349},[182,457,458],{"class":329},"    { ",[182,460,461],{"class":357},"\"type\"",[182,463,342],{"class":329},[182,465,466],{"class":345},"\"temperature\"",[182,468,469],{"class":329},", ",[182,471,472],{"class":357},"\"value\"",[182,474,342],{"class":329},[182,476,477],{"class":357},"4.1",[182,479,469],{"class":329},[182,481,482],{"class":357},"\"label\"",[182,484,342],{"class":329},[182,486,487],{"class":345},"\"front\"",[182,489,490],{"class":329}," },\n",[182,492,493,495,497,499,501,503,505,507,510,512,514,516,519],{"class":321,"line":361},[182,494,458],{"class":329},[182,496,461],{"class":357},[182,498,342],{"class":329},[182,500,466],{"class":345},[182,502,469],{"class":329},[182,504,472],{"class":357},[182,506,342],{"class":329},[182,508,509],{"class":357},"4.3",[182,511,469],{"class":329},[182,513,482],{"class":357},[182,515,342],{"class":329},[182,517,518],{"class":345},"\"back\"",[182,520,421],{"class":329},[182,522,523],{"class":321,"line":380},[182,524,525],{"class":329},"  ]\n",[182,527,528],{"class":321,"line":386},[182,529,530],{"class":329},"}\n",[14,532,534],{"id":533},"calibration","Calibration",[10,536,537],{},"If you need calibrated readings (lab \u002F pharmacy), pair this ESP32 with a\ncalibrated thermistor (PT100 + MAX31865, or a Sensirion SHT45 instead of\nthe BME280). Run a single-point calibration in ice water at 0 °C and a\ntwo-point against a calibrated reference. Apply the offset in ESPHome:",[311,539,541],{"className":313,"code":540,"language":315,"meta":316,"style":316},"sensor:\n  - platform: bme280_i2c\n    temperature:\n      filters:\n        - offset: -0.3   # measured at 0 °C against a Fluke 1551A\n",[38,542,543,549,559,565,572],{"__ignoreMap":316},[182,544,545,547],{"class":321,"line":322},[182,546,326],{"class":325},[182,548,330],{"class":329},[182,550,551,553,555,557],{"class":321,"line":333},[182,552,336],{"class":329},[182,554,339],{"class":325},[182,556,342],{"class":329},[182,558,346],{"class":345},[182,560,561,563],{"class":321,"line":349},[182,562,364],{"class":325},[182,564,330],{"class":329},[182,566,567,570],{"class":321,"line":361},[182,568,569],{"class":325},"      filters",[182,571,330],{"class":329},[182,573,574,577,580,582,585],{"class":321,"line":380},[182,575,576],{"class":329},"        - ",[182,578,579],{"class":325},"offset",[182,581,342],{"class":329},[182,583,584],{"class":357},"-0.3",[182,586,588],{"class":587},"sJ8bj","   # measured at 0 °C against a Fluke 1551A\n",[10,590,591],{},"The OpenSense data model has no concept of \"raw vs calibrated\" — whatever\nyour device sends is what is stored. If you need to record the calibration\nevent, attach it to the device page as a free-text note. We may formalise\ncalibration as a first-class object later.",[593,594,595],"style",{},"html pre.shiki code .sxg3X, html code.shiki .sxg3X{--shiki-default:#85E89D;--shiki-dark:#85E89D}html pre.shiki code .suv1-, html code.shiki .suv1-{--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .s4wv1, html code.shiki .s4wv1{--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .s8ozJ, html code.shiki .s8ozJ{--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}",{"title":316,"searchDepth":349,"depth":349,"links":597},[598,599,600,601,602,603],{"id":16,"depth":333,"text":17},{"id":44,"depth":333,"text":45},{"id":109,"depth":333,"text":110},{"id":245,"depth":333,"text":246},{"id":289,"depth":333,"text":290},{"id":533,"depth":333,"text":534},"BME280 over WiFi, ESPHome YAML",null,"md",{},true,"\u002Fhardware\u002Fdiy-esp32",{"title":5,"description":604},"hardware\u002Fdiy-esp32",150,"8wSunEx7diwEvecBhkVzuvfYiP8USKf5oOTkzJ4tPVc",1779022953849]