diff --git a/README.md b/README.md index 30c6ec8..9997af8 100644 --- a/README.md +++ b/README.md @@ -31,22 +31,24 @@ Most of the COTS parts are from [Adafruit](https://www.adafruit.com/) (hence the |:---:|:----:|:----------------------------------------------------------------------------------------------:|:----------:|:-----------:| | 1 | 5477 | Adafruit Esp32-S3 Feather With 4Mb Flash 2Mb Psram - Stemma Qt / Qwiic | 17.5 | 17.5 | | 1 | 4814 | Adafruit 2.13 Hd Tri-Color Eink / Epaper Display Featherwing - 250X122 Rw Panel With Ssd1680 | 24.95 | 24.95 | -| 1 | 5190 | Adafruit SCD-41 - True CO2 Temperature and Humidity Sensor | 59.5 | 59.5 | +| 1 | 5187 | Adafruit SCD-40 - True CO2 Temperature and Humidity Sensor | 49.5 | 49.5 | | 1 | 4494 | Adafruit DPS310 Precision Barometric Pressure/Altitude Sensor - STEMMA QT/Qwiic | 6.95 | 6.95 | | 1 | 2011 | Lithium Ion Battery - 3.7V 2000mAh | 12.5 | 12.5 | | 2 | 4399 | STEMMA QT / Qwiic JST SH 4-Pin Cable - 50mm Long | 0.95 | 1.9 | -| | | | | 123.30 | +| | | | | 113.30 | -In addition, you'll need several fasteners. Most come in the [Adafruit 3299](https://www.adafruit.com/product/3299) kit, but this is overkill (only 20 out of the 380 pieces are used), so you may have better luck at a hardware store. Either way, you'll need: +(Note that the [SCD-41](https://www.adafruit.com/product/5190) can be used as a drop-in replacement.) + +In addition, you'll need several fasteners. Most come in the [Adafruit 3299](https://www.adafruit.com/product/3299) kit, but this is overkill (only 20 out of the 380 pieces are used), so you may have better luck at a hardware store [or](https://smile.amazon.com/gp/product/B018TH1NEM) [on](https://smile.amazon.com/gp/product/B012TAF8AK) [Amazon](https://smile.amazon.com/gp/product/B08F1XFQ7R). Either way, you'll need: | Qty | Part Description | Material | |:---:|:-----------------------------:|----------| | 8 | M2.5 Hex Nut | Nylon | | 8 | M2.5 x 6mm Screw | Nylon | | 4 | M2.5 x 6mm M-F Hex Standoff | Nylon | -| 4 | M2.3 x 5mm Self-Tapping Screw | Steel | +| 4 | M2.6 x 5mm Self-Tapping Screw | Steel | -I also bought the self-tapping screws in [a kit](https://smile.amazon.com/dp/B081DVZMHH), but there are almost certainly cheaper options. +I also bought the self-tapping screws in [a kit](https://smile.amazon.com/dp/B081DVZMHH), but there are [other](https://smile.amazon.com/gp/product/B00GDYDCC0) [options](https://smile.amazon.com/gp/product/B01L7PDGXO) too. Finally, you'll need to 3D print the "top" and "bottom" pieces. If you don't have a 3D printer, either ask someone who does (these are relatively easy parts), visit to a local maker space (maybe even [your library?](https://www.chipublib.org/maker-lab/)), or use a service like [(3D)Hubs](https://www.hubs.com/). (Note: I recommend printing the "top" upside down, both to get a smooth finish on the outside and to avoid unnecessary overhangs.) @@ -68,11 +70,11 @@ First assemble the top and bottom of the case separately: ### Bottom 1. Use 4 M2.5 x 6mm screws to attach the 4 M2.5 x 6mm M-F hex standoffs to the inside of the case (using the holes that border the the sensor slot) -2. Attach the 4-pin STEMMA QT cables to either side of the SCD41 board -3. Press the SCD41 board onto the four exposed standoffs, with the sensor facing "out" of the case, and use 2 M2.5 hex nuts to secure to the bottom two standoffs -4. Connect the DPS310 to the 4-pin STEMMA QT cable on the "right" (when facing the non-sensor side) of the SCD41 board -5. Use 2 M2.5 hex nuts to attach the DPS310 to the top two standoffs, back-to-back with the SCD41 so that the sensor is facing "in" (note: first apply Kapton/electrical tape to the back of the DPS310 to avoid shorting to the SCD41) -6. Carefully insert the battery, which should fit snugly lengthwise, and adjacent to the bottom standoffs holding the SCD41 (note: you can use double-sided tape securely affix the battery to the case) +2. Attach the 4-pin STEMMA QT cables to either side of the SCD4x board +3. Press the SCD4x board onto the four exposed standoffs, with the sensor facing "out" of the case, and use 2 M2.5 hex nuts to secure to the bottom two standoffs +4. Connect the DPS310 to the 4-pin STEMMA QT cable on the "right" (when facing the non-sensor side) of the SCD4x board +5. Use 2 M2.5 hex nuts to attach the DPS310 to the top two standoffs, back-to-back with the SCD4x so that the sensor is facing "in" (note: first apply Kapton/electrical tape to the back of the DPS310 to avoid shorting to the SCD4x) +6. Carefully insert the battery, which should fit snugly lengthwise, and adjacent to the bottom standoffs holding the SCD4x (note: you can use double-sided tape securely affix the battery to the case) ![adanet-assembly-bottom](images/adanet-assembly-bottom.jpg) @@ -93,6 +95,8 @@ The Arduino firmware (adanet-co2-monitor.ino) has been tested using the followin - ESP32 Arduino (v2.0.5) - Sensirion I2C SCD4x (v0.3.1) +### Development environment + To prepare your development environment: 1. Install [Arduino IDE](https://www.arduino.cc/en/software/) (and optionally the [VS Code extension](https://github.com/microsoft/vscode-arduino)) @@ -102,6 +106,8 @@ To prepare your development environment: 5. In the same menu, change "USB Mode" to "Hardware CDC and JTAG" (see [here](https://github.com/espressif/arduino-esp32/issues/6762)) and "Upload Mode" to "UART0/Hardware CDC" 6. Plug in your Feather using the USB-C connector, and set the Port to match the one assigned by your OS (e.g. `COM5` on Windows, `/dev/ttyACM0` on Linux) +### Loading firmware + To load the firmware on the device: 1. Clone this repository somewhere sensible (Arduino projects are typically located in the `$HOME/Documents/Arduino` folder) @@ -110,13 +116,15 @@ To load the firmware on the device: 4. In Arduino (or VS Code using the [Arduino extension](https://github.com/microsoft/vscode-arduino)), open the arduino-co2-monitor.ino file and click Upload (or run the Arduino: Upload task in VS Code) 5. Once finished, you may have to manually press the Reset button again to start the firmware +### Pseudocode + And that's it! The firmware does the following once every 180 seconds (the [fastest update rate](https://learn.adafruit.com/adafruit-2-13-eink-display-breakouts-and-featherwings/usage-expectations) supported by the E Ink screen): -1. Enable the SCD41 and DPS310 sensors, start measurements -2. Light sleep for 5 seconds while the initial CO₂ and pressure measurements are taking place -3. Update SCD41 with ambient pressure (the [nominal reason](http://www.co2meters.com/Documentation/AppNotes/AN149-Senseair-Pressure-Dependence.pdf) for the DPS301 sensor!) -4. Discard initial measurements (as per [SCD41 Low Power Operation](https://sensirion.com/media/documents/077BC86F/62BF01B9/CD_AN_SCD4x_Low_Power_Operation_D1.pdf)) and light sleep for another 5 seconds -5. Read battery charge state from the LC709203F sensor +1. Enable the SCD4x and DPS310 sensors, start measurements +2. Read battery charge state from the LC709203F sensor +3. Light sleep for 5 seconds while the initial CO₂ and pressure measurements are taking place +4. Update SCD4x with ambient pressure (the [nominal reason](http://www.co2meters.com/Documentation/AppNotes/AN149-Senseair-Pressure-Dependence.pdf) for the DPS310 sensor!) +5. Discard initial measurements (as per [SCD4x Low Power Operation](https://sensirion.com/media/documents/077BC86F/62BF01B9/CD_AN_SCD4x_Low_Power_Operation_D1.pdf)) and light sleep for another 5 seconds 6. Save new measurements and disable I2C power (turns off all sensors) 7. Enable the E Ink screen and update the display using the saved measurements 8. Deep sleep for ~170 seconds and repeat diff --git a/adanet-co2-monitor.ino b/adanet-co2-monitor.ino index 1763f9d..0567225 100644 --- a/adanet-co2-monitor.ino +++ b/adanet-co2-monitor.ino @@ -10,14 +10,12 @@ #include #include - static constexpr uint32_t VERSION_MAJOR = 0; -static constexpr uint32_t VERSION_MINOR = 1; +static constexpr uint32_t VERSION_MINOR = 2; static constexpr uint32_t VERSION_PATCH = 0; static constexpr int16_t EPD_DC = 10; // can be any pin, but required! static constexpr int16_t EPD_CS = 9; // can be any pin, but required! -static constexpr int16_t SRAM_CS = 6; // can set to -1 to not use a pin (uses a lot of RAM!) static constexpr int16_t EPD_RESET = -1; // can set to -1 and share with chip Reset (can't deep sleep) static constexpr int16_t EPD_BUSY = -1; // can set to -1 to not use a pin (will wait a fixed delay) @@ -27,11 +25,10 @@ static constexpr int16_t CHAR_HEIGHT = 8; // height of a character in pixels static constexpr int16_t HEADER_SIZE = 3; // text size of header static constexpr int16_t BODY_SIZE = 6; // text size of body static constexpr int16_t FOOTER_SIZE = 3; // text size of footer -static constexpr int16_t ERROR_SIZE = 3; // text size of error +static constexpr int16_t ERROR_SIZE = 2; // text size of error static constexpr int16_t MAXVAL_SIZE = 2; // text size of max value static constexpr int16_t MAXLBL_SIZE = 1; // text size of max label - static constexpr uint32_t DISPLAY_WAIT = 180; // wait between display updates in seconds static constexpr uint32_t NUM_MEASUREMENTS = 2; // number of measurements to take @@ -39,7 +36,8 @@ static constexpr uint32_t MEASUREMENT_WAIT = 5; // wait between checking measure static constexpr uint16_t CO2_LIMIT = 800; // CDC CO2 ppm limit static constexpr int16_t BATT_WIDTH = 3 * FOOTER_SIZE * CHAR_WIDTH; -static constexpr float BATT_LIMIT = 15.0f; +static constexpr float BATT_WARN_LIMIT = 15.0f; +static constexpr float BATT_ERROR_LIMIT = 5.0f; static constexpr size_t MESSAGE_SIZE = 256; @@ -59,8 +57,8 @@ static Adafruit_DPS310 dps; static uint32_t error; static char message[MESSAGE_SIZE]; + static Preferences pref; -static char formattedCo2Str[CO2_VAL_STRING_LEN]; typedef enum { @@ -75,13 +73,14 @@ typedef enum ERROR_CO2_SENSOR, ERROR_PRESSURE_SENSOR, ERROR_BATT_SENSOR, + ERROR_LOW_BATT, } Error; -static int8_t tempUnits; - RTC_DATA_ATTR static uint16_t co2HistoryFifo[UPDATES_PER_WEEK] = {0}; RTC_DATA_ATTR static uint16_t co2HistoryHead = 0; +RTC_DATA_ATTR static uint32_t errorPrev = ERROR_NONE; + void checkSCD4xError(const uint16_t scd4xError) { if (scd4xError) @@ -93,7 +92,7 @@ void checkSCD4xError(const uint16_t scd4xError) void printfAligned(const uint8_t size, const Alignment alignment, const int16_t y, const uint16_t color, const char *fmt, ...) { - char buffer[256]; + char buffer[MESSAGE_SIZE]; va_list args; va_start(args, fmt); vsnprintf(buffer, sizeof(buffer), fmt, args); @@ -126,7 +125,7 @@ void co2HistoryAdd(const uint16_t co2) ++co2HistoryHead %= UPDATES_PER_WEEK; } -uint16_t co2HistoryRead(const uint16_t index) +uint16_t co2HistoryRead(const uint16_t index) { // following a call to co2HistoryAdd, co2HistoryHead points to the next available slot // therefore the latest/most recently updated value is located at (co2HistoryHead - 1) @@ -134,7 +133,7 @@ uint16_t co2HistoryRead(const uint16_t index) int16_t idx = co2HistoryHead - 1 - index; // handle the wraparound of the circular buffer - while (idx < 0) + while (idx < 0) { idx += UPDATES_PER_WEEK; } @@ -142,21 +141,21 @@ uint16_t co2HistoryRead(const uint16_t index) return co2HistoryFifo[idx]; } -void computeCo2Max(uint16_t& dayMax, uint16_t& weekMax) +void computeCo2Max(uint16_t &dayMax, uint16_t &weekMax) { dayMax = 0; weekMax = 0; - for (uint16_t i = 0; i < UPDATES_PER_WEEK; i++) + for (uint16_t i = 0; i < UPDATES_PER_WEEK; i++) { if (i < UPDATES_PER_DAY) { - if (co2HistoryRead(i) > dayMax) + if (co2HistoryRead(i) > dayMax) { dayMax = co2HistoryRead(i); } } - if (co2HistoryRead(i) > weekMax) + if (co2HistoryRead(i) > weekMax) { weekMax = co2HistoryRead(i); } @@ -165,7 +164,7 @@ void computeCo2Max(uint16_t& dayMax, uint16_t& weekMax) void formatCo2(const uint16_t primaryCo2Val, const uint16_t secondaryCo2Val, char *str) { - if ((primaryCo2Val > 9999 && secondaryCo2Val > 999) || (primaryCo2Val > 999 && secondaryCo2Val > 9999)) + if ((primaryCo2Val > 9999 && secondaryCo2Val > 999) || (primaryCo2Val > 999 && secondaryCo2Val > 9999)) { snprintf(str, CO2_VAL_STRING_LEN, "%.0fK", static_cast(secondaryCo2Val) / 1000.f); } @@ -183,8 +182,6 @@ void setup() // turn off neopixel power digitalWrite(NEOPIXEL_POWER, !NEOPIXEL_POWER_ON); - pref.begin("adanet-co2", true); - #ifdef DEBUG Serial.begin(115200); while (!Serial) @@ -200,12 +197,53 @@ void setup() delay(1000); #endif + error = ERROR_NONE; + + pref.begin("adanet-co2", true); // get temperature display units preference from flash - tempUnits = pref.getChar("temp_units", 'C'); + const int8_t tempUnits = pref.getChar("temp_units", 'C'); pref.end(); + // setup lc709203f battery fuel gauge + if (!lc.begin()) + { + error = ERROR_BATT_SENSOR << 16; + snprintf(message, MESSAGE_SIZE, "Couldn't find LC709203F, make sure a battery is plugged in"); + } + + float batt = 0.0f; + if (error == ERROR_NONE) + { +#ifdef DEBUG + Serial.println(F("Found LC709203F")); +#endif + + // TODO(drw): set with DPS310 value? (default temperature in i2c mode is 25C) + lc.setTemperatureMode(LC709203F_TEMPERATURE_I2C); +#ifdef DEBUG + Serial.print("Cell Temperature = "); + Serial.println(lc.getCellTemperature()); +#endif + + lc.setPackSize(LC709203F_APA_2000MAH); + + batt = lc.cellPercent(); + lc.setPowerMode(LC709203F_POWER_SLEEP); + +#ifdef DEBUG + Serial.print("Battery = "); + Serial.println(batt); +#endif + + if (batt < BATT_ERROR_LIMIT) + { + error = ERROR_LOW_BATT << 16; + snprintf(message, MESSAGE_SIZE, "Battery voltage too low, please charge"); + } + } + // setup dps310 pressure sensor - if (!dps.begin_I2C()) + if ((error == ERROR_NONE) && !dps.begin_I2C()) { error = ERROR_PRESSURE_SENSOR << 16; snprintf(message, MESSAGE_SIZE, "Failed to find DPS"); @@ -287,40 +325,6 @@ void setup() snprintf(message, MESSAGE_SIZE, "Invalid sample detected"); } - // setup lc709203f battery fuel gauge - if ((error == ERROR_NONE) && !lc.begin()) - { - error = ERROR_BATT_SENSOR << 16; - snprintf(message, MESSAGE_SIZE, "Couldn't find LC709203F, make sure a battery is plugged in"); - } - - float batt = 0.0f; - if (error == ERROR_NONE) - { -#ifdef DEBUG - Serial.println(F("Found LC709203F")); - Serial.print("Version: 0x"); - Serial.println(lc.getICversion(), HEX); -#endif - - lc.setThermistorB(3950); -#ifdef DEBUG - Serial.print("Thermistor B = "); - Serial.println(lc.getThermistorB()); -#endif - - lc.setPackSize(LC709203F_APA_2000MAH); - lc.setAlarmVoltage(3.8); - - batt = lc.cellPercent(); - lc.setPowerMode(LC709203F_POWER_SLEEP); - -#ifdef DEBUG - Serial.print("Battery = "); - Serial.println(batt); -#endif - } - // turn off i2c power digitalWrite(I2C_POWER, LOW); @@ -334,87 +338,83 @@ void setup() } Serial.println(); #else - // co2 history - uint16_t co2DayMax = 0; - uint16_t co2WeekMax = 0; + // add co2 to history regardless of error in order to properly track time + co2HistoryAdd(co2); - if (error == ERROR_NONE) + // only update display if there is no error, or the error is different from previous + if ((error == ERROR_NONE) || (error != errorPrev)) { - co2HistoryAdd(co2); - computeCo2Max(co2DayMax, co2WeekMax); - } - - // setup display - display.begin(THINKINK_TRICOLOR); - - // enum | orientation | USB - // -----|-------------|------- - // 0 | landscape | right - // 1 | portrait | top - // 2 | landscape | left - // 3 | portrait | bottom - display.setRotation(2); - - display.clearBuffer(); + // setup display + display.begin(THINKINK_TRICOLOR); + + // enum | orientation | USB + // -----|-------------|------- + // 0 | landscape | right + // 1 | portrait | top + // 2 | landscape | left + // 3 | portrait | bottom + display.setRotation(2); + + display.clearBuffer(); + + const int16_t headerY = 0; + const int16_t bodyY = (display.height() - BODY_SIZE * CHAR_HEIGHT) / 2; + const int16_t maxValueY = display.height() / 2 - MAXVAL_SIZE * CHAR_HEIGHT; + const int16_t maxLabelY = display.height() / 2; + const int16_t footerY = display.height() - 1 - FOOTER_SIZE * CHAR_HEIGHT; + if (error == ERROR_NONE) + { + const float dispTemp = (tempUnits == 'C') ? temperature : (temperature / 5.f * 9.f + 32.f); + printfAligned(HEADER_SIZE, ALIGN_LEFT, headerY, EPD_BLACK, "%5.1f%c%c", dispTemp, 0xF7, tempUnits); + printfAligned(HEADER_SIZE, ALIGN_RIGHT, headerY, EPD_BLACK, "%5.1f%%", humidity); + + uint16_t co2Color = co2 >= CO2_LIMIT ? EPD_RED : EPD_BLACK; + printfAligned(BODY_SIZE, ALIGN_CENTER, bodyY, co2Color, "%u", co2); + + // co2 history + uint16_t co2DayMax = 0; + uint16_t co2WeekMax = 0; + computeCo2Max(co2DayMax, co2WeekMax); + + char formattedCo2Str[CO2_VAL_STRING_LEN]; + formatCo2(co2, co2DayMax, formattedCo2Str); + co2Color = co2DayMax >= CO2_LIMIT ? EPD_RED : EPD_BLACK; + printfAligned(MAXVAL_SIZE, ALIGN_LEFT, maxValueY, co2Color, "%s", formattedCo2Str); + + formatCo2(co2, co2WeekMax, formattedCo2Str); + co2Color = co2WeekMax >= CO2_LIMIT ? EPD_RED : EPD_BLACK; + printfAligned(MAXVAL_SIZE, ALIGN_RIGHT, maxValueY, co2Color, "%s", formattedCo2Str); + + printfAligned(MAXLBL_SIZE, ALIGN_LEFT, maxLabelY, EPD_BLACK, "%s", "max/"); + printfAligned(MAXLBL_SIZE, ALIGN_LEFT, maxLabelY + MAXLBL_SIZE * CHAR_HEIGHT, EPD_BLACK, "%s", "day"); + printfAligned(MAXLBL_SIZE, ALIGN_RIGHT, maxLabelY, EPD_BLACK, "%s", "max/"); + printfAligned(MAXLBL_SIZE, ALIGN_RIGHT, maxLabelY + MAXLBL_SIZE * CHAR_HEIGHT, EPD_BLACK, "%s", "week"); + + const uint16_t battColor = batt < BATT_WARN_LIMIT ? EPD_RED : EPD_BLACK; + const int16_t x0 = FOOTER_SIZE * CHAR_WIDTH / 2; + const int16_t y0 = footerY; + const int16_t w = BATT_WIDTH; + const int16_t h = FOOTER_SIZE * CHAR_HEIGHT; + const int16_t x1 = x0 + w; + const int16_t y1 = y0 + h; + display.fillRect(x0, y0, static_cast(batt / 100.0f * static_cast(w)), h, battColor); + display.drawRect(x0, y0, w, h, battColor); + display.fillRect(x1, y0 + h / 4, FOOTER_SIZE * CHAR_WIDTH / 4, h / 2, battColor); + printfAligned(FOOTER_SIZE, ALIGN_RIGHT, footerY, EPD_BLACK, "%u hPa", pressure); + } + else + { + printfAligned(ERROR_SIZE, ALIGN_LEFT, 0, EPD_BLACK, "%012llX", ESP.getEfuseMac()); + printfAligned(ERROR_SIZE, ALIGN_RIGHT, 0, EPD_BLACK, "v%u.%u.%u", VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH); + printfAligned(ERROR_SIZE, ALIGN_LEFT, ERROR_SIZE * CHAR_HEIGHT, EPD_RED, "Error: %08X", error); + printfAligned(ERROR_SIZE, ALIGN_LEFT, ERROR_SIZE * CHAR_HEIGHT * 2, EPD_RED, message); + } - const int16_t headerY = 0; - const int16_t bodyY = (display.height() - BODY_SIZE * CHAR_HEIGHT) / 2; - const int16_t maxValueY = display.height() / 2 - MAXVAL_SIZE * CHAR_HEIGHT; - const int16_t maxLabelY = display.height() / 2; - const int16_t footerY = display.height() - 1 - FOOTER_SIZE * CHAR_HEIGHT; - if (error == ERROR_NONE) - { - const float dispTemp = (tempUnits == 'C') ? temperature : (temperature / 5.f * 9.f + 32.f); - printfAligned(HEADER_SIZE, ALIGN_LEFT, headerY, EPD_BLACK, "%5.1f%c%c", dispTemp, 0xF7, tempUnits); - printfAligned(HEADER_SIZE, ALIGN_RIGHT, headerY, EPD_BLACK, "%5.1f%%", humidity); - - uint16_t co2Color = co2 >= CO2_LIMIT ? EPD_RED : EPD_BLACK; - printfAligned(BODY_SIZE, ALIGN_CENTER, bodyY, co2Color, "%u", co2); - - co2Color = co2DayMax >= CO2_LIMIT ? EPD_RED : EPD_BLACK; - formatCo2(co2, co2DayMax, formattedCo2Str); - printfAligned(MAXVAL_SIZE, ALIGN_LEFT, maxValueY, co2Color, "%s", formattedCo2Str); - - co2Color = co2WeekMax >= CO2_LIMIT ? EPD_RED : EPD_BLACK; - formatCo2(co2, co2WeekMax, formattedCo2Str); - printfAligned(MAXVAL_SIZE, ALIGN_RIGHT, maxValueY, co2Color, "%s", formattedCo2Str); - - printfAligned(MAXLBL_SIZE, ALIGN_LEFT, maxLabelY, EPD_BLACK, "%s", "max/"); - printfAligned(MAXLBL_SIZE, ALIGN_LEFT, maxLabelY + MAXLBL_SIZE * CHAR_HEIGHT, EPD_BLACK, "%s", "day"); - printfAligned(MAXLBL_SIZE, ALIGN_RIGHT, maxLabelY, EPD_BLACK, "%s", "max/"); - printfAligned(MAXLBL_SIZE, ALIGN_RIGHT, maxLabelY + MAXLBL_SIZE * CHAR_HEIGHT, EPD_BLACK, "%s", "week"); - - const uint16_t battColor = batt < BATT_LIMIT ? EPD_RED : EPD_BLACK; - const int16_t x0 = FOOTER_SIZE * CHAR_WIDTH / 2; - const int16_t y0 = footerY; - const int16_t w = BATT_WIDTH; - const int16_t h = FOOTER_SIZE * CHAR_HEIGHT; - const int16_t x1 = x0 + w; - const int16_t y1 = y0 + h; - display.fillRect(x0, y0, static_cast(batt / 100.0f * static_cast(w)), h, battColor); - display.drawRect(x0, y0, w, h, battColor); - display.fillRect(x1, y0 + h / 4, FOOTER_SIZE * CHAR_WIDTH / 4, h / 2, battColor); - printfAligned(FOOTER_SIZE, ALIGN_RIGHT, footerY, EPD_BLACK, "%u hPa", pressure); - } - else - { - display.setTextSize(ERROR_SIZE); - display.setTextColor(EPD_BLACK); - display.setCursor(0, 0); - display.println(ESP.getEfuseMac(), HEX); - display.print("v"); - display.print(VERSION_MAJOR); - display.print("."); - display.print(VERSION_MINOR); - display.print("."); - display.println(VERSION_PATCH); - display.setTextColor(EPD_RED); - display.print(error, HEX); - display.print(": "); - display.println(message); + display.display(true); } - display.display(true); + // store previous error + errorPrev = error; esp_sleep_enable_timer_wakeup((DISPLAY_WAIT - measurements * MEASUREMENT_WAIT) * 1000000ull); esp_deep_sleep_start(); @@ -425,4 +425,4 @@ void loop() { // shouldn't reach here during normal operation delay(10); -} \ No newline at end of file +} diff --git a/adanet-init/adanet-init.ino b/adanet-init/adanet-init.ino index a90913d..d0baa99 100644 --- a/adanet-init/adanet-init.ino +++ b/adanet-init/adanet-init.ino @@ -8,7 +8,6 @@ #include #include - static constexpr int16_t EPD_DC = 10; // can be any pin, but required! static constexpr int16_t EPD_CS = 9; // can be any pin, but required! static constexpr int16_t SRAM_CS = 6; // can set to -1 to not use a pin (uses a lot of RAM!) @@ -62,14 +61,14 @@ void setup() // setup display display.begin(THINKINK_TRICOLOR); - // draw checkerboard pattern to test screen + // draw stripe pattern to test screen display.clearBuffer(); - for (int16_t x = 0; x < display.width(); x += 2) + for (int16_t x = 0; x < display.width() - 1; x += 2) { - for (int16_t y = 0; y < display.height(); y += 2) + for (int16_t y = 0; y < display.height(); y++) { display.drawPixel(x, y, EPD_BLACK); - display.drawPixel(x + 1, y + 1, EPD_RED); + display.drawPixel(x + 1, y, EPD_RED); } } display.display(true); @@ -161,11 +160,10 @@ void setup() // wait a bit to make sure preferences have a chance to get commited to flash before exiting delay(1000); Serial.println("Done."); - + return; } - // get serial input before starting calibration Serial.println("Perform factory reset? [n]"); while (!Serial.available()) @@ -273,12 +271,15 @@ void setup() Serial.print("Forced recalibration correction = "); Serial.println(static_cast(frcCorrection - 0x8000)); } - - // turn off i2c power - digitalWrite(I2C_POWER, LOW); } void loop() { - delay(10); -} \ No newline at end of file + // turn off i2c power + digitalWrite(I2C_POWER, LOW); + + // deep sleep to avoid draining battery after initialization + Serial.println("Entering deep sleep"); + Serial.flush(); + esp_deep_sleep_start(); +} diff --git a/images/adanet-assembled.jpg b/images/adanet-assembled.jpg index be76972..b1260fe 100644 Binary files a/images/adanet-assembled.jpg and b/images/adanet-assembled.jpg differ diff --git a/images/adanet-co2-monitor.jpg b/images/adanet-co2-monitor.jpg index 2982ef7..cb83c3b 100644 Binary files a/images/adanet-co2-monitor.jpg and b/images/adanet-co2-monitor.jpg differ