Skip to content
10 changes: 5 additions & 5 deletions boards.txt
Original file line number Diff line number Diff line change
Expand Up @@ -41643,6 +41643,11 @@ sensebox_eye.menu.UploadMode.cdc=USB-OTG CDC (TinyUSB)
sensebox_eye.menu.UploadMode.cdc.upload.use_1200bps_touch=true
sensebox_eye.menu.UploadMode.cdc.upload.wait_for_upload_port=true

sensebox_eye.menu.PartitionScheme.tinyuf2=TinyUF2 Compatibility (2MB APP/12MB FFAT)
sensebox_eye.menu.PartitionScheme.tinyuf2.build.custom_bootloader=bootloader_tinyuf2
sensebox_eye.menu.PartitionScheme.tinyuf2.build.custom_partitions=partitions-16MB-tinyuf2
sensebox_eye.menu.PartitionScheme.tinyuf2.upload.maximum_size=2097152
sensebox_eye.menu.PartitionScheme.tinyuf2.upload.extra_flags=0x410000 "{runtime.platform.path}/variants/{build.variant}/tinyuf2.bin" 0x210000 "{runtime.platform.path}/variants/{build.variant}/APOTA.bin"
sensebox_eye.menu.PartitionScheme.default_16MB=Default (6.25MB APP/3.43MB SPIFFS)
sensebox_eye.menu.PartitionScheme.default_16MB.build.partitions=default_16MB
sensebox_eye.menu.PartitionScheme.default_16MB.upload.maximum_size=6553600
Expand All @@ -41655,11 +41660,6 @@ sensebox_eye.menu.PartitionScheme.app3M_fat9M_16MB.upload.maximum_size=3145728
sensebox_eye.menu.PartitionScheme.fatflash=Large FFAT (2MB APP/12.5MB FATFS)
sensebox_eye.menu.PartitionScheme.fatflash.build.partitions=ffat
sensebox_eye.menu.PartitionScheme.fatflash.upload.maximum_size=2097152
sensebox_eye.menu.PartitionScheme.tinyuf2=TinyUF2 Compatibility (2MB APP/12MB FFAT)
sensebox_eye.menu.PartitionScheme.tinyuf2.build.custom_bootloader=bootloader_tinyuf2
sensebox_eye.menu.PartitionScheme.tinyuf2.build.custom_partitions=partitions_tinyuf2
sensebox_eye.menu.PartitionScheme.tinyuf2.upload.maximum_size=2097152
sensebox_eye.menu.PartitionScheme.tinyuf2.upload.extra_flags=0x410000 "{runtime.platform.path}/variants/{build.variant}/tinyuf2.bin"
sensebox_eye.menu.PartitionScheme.gen4esp32scheme4=Huge App (16MB APP)
sensebox_eye.menu.PartitionScheme.gen4esp32scheme4.build.custom_partitions=gen4esp32_16MBapp
sensebox_eye.menu.PartitionScheme.gen4esp32scheme4.upload.maximum_size=16646144
Expand Down
Binary file added variants/sensebox_eye/APOTA.bin
Binary file not shown.
301 changes: 301 additions & 0 deletions variants/sensebox_eye/APOTA.ino
Original file line number Diff line number Diff line change
@@ -0,0 +1,301 @@
// APOTA is an Arduino fallback sketch that is written to OTA1_Partition.
// APOTA opens an access point which waits to receive a .bin file on /sketch.
// After successful upload, the file is written to OTA0_Partition, and the microcontroller reboots to the newly uploaded sketch.

#define DISPLAY_ENABLED

#include <WiFi.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <WiFiAP.h>
#include <Update.h>
#include <Wire.h>
#ifdef DISPLAY_ENABLED
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel rgb_led = Adafruit_NeoPixel(1, PIN_LED, NEO_GRB + NEO_KHZ800);

#endif
#include "esp_partition.h"
#include "esp_ota_ops.h"
#include "esp_system.h"

String ssid;
uint8_t mac[6];

// Create an instance of the server
WebServer server(80);
bool displayEnabled;

const int BUTTON_PIN = 0; // GPIO for the button
volatile unsigned long lastPressTime = 0; // Time of last button press
volatile bool doublePressDetected = false; // Flag for double press
const unsigned long doublePressInterval = 500; // Max. time (in ms) between two presses for double press
volatile int pressCount = 0; // Counts the button presses

const unsigned char epd_bitmap_wifi[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0xff, 0x00, 0x00,
0x00, 0x3f, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x7c, 0x00, 0x03, 0xe0, 0x00, 0x00, 0xf0, 0x00, 0x01, 0xf0, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x78, 0x00,
0x03, 0xc0, 0x00, 0x00, 0x38, 0x00, 0x07, 0x80, 0x00, 0x00, 0x1c, 0x00, 0x0f, 0x00, 0x06, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x7f, 0xe0, 0x0e, 0x00,
0x0c, 0x01, 0xff, 0xf0, 0x06, 0x00, 0x00, 0x07, 0xff, 0xfc, 0x02, 0x00, 0x00, 0x0f, 0x80, 0x3e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x0f, 0x00, 0x00,
0x00, 0x1c, 0x00, 0x07, 0x80, 0x00, 0x00, 0x38, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x70, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x70, 0x00, 0x00, 0xc0, 0x00,
0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x00,
0x00, 0x01, 0xe0, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x78, 0x00, 0x00, 0x00, 0x03, 0x80, 0x38, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

// 'checkmark', 44x44px
const unsigned char epd_bitmap_checkmark[] PROGMEM = {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00,
0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x0e, 0x00, 0xf0, 0x00, 0x00,
0x00, 0x0f, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x0f, 0x83, 0xc0, 0x00, 0x00, 0x00, 0x07, 0xc7, 0x80, 0x00, 0x00, 0x00, 0x03, 0xef, 0x00, 0x00, 0x00,
0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

void IRAM_ATTR handleButtonPress() {
unsigned long currentTime = millis(); // Get current time

// Debounce: If the current press is too close to the last one, ignore it
if (currentTime - lastPressTime > 50) {
pressCount++; // Count the button press

// Check if this is the second press within the double-press interval
if (pressCount == 2 && (currentTime - lastPressTime <= doublePressInterval)) {
doublePressDetected = true; // Double press detected
pressCount = 0; // Reset counter
}

lastPressTime = currentTime; // Update the time of the last press
}
}

// Function to switch the boot partition to OTA1
void setBootPartitionToOTA0() {
const esp_partition_t *ota0_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL);

if (ota0_partition) {
// Set OTA1 as new boot partition
esp_ota_set_boot_partition(ota0_partition);
Serial.println("Boot partition changed to OTA0. Restarting...");

// Restart to boot from the new partition
esp_restart();
} else {
Serial.println("OTA1 partition not found!");
}
}

void setupDisplay() {
Wire.begin(PIN_QWIIC_SDA,PIN_QWIIC_SCL);
displayEnabled = Wire.requestFrom(0x3D, 1); // Check if the display is connected
if (displayEnabled) {
display.begin(SSD1306_SWITCHCAPVCC, 0x3D);
display.display();
delay(100);
display.clearDisplay();
}
}

void displayStatusBar(int progress) {
display.clearDisplay();
display.setCursor(24, 8);
display.println("Sketch wird");
display.setCursor(22, 22);
display.println("hochgeladen!");

display.fillRect(0, SCREEN_HEIGHT - 24, SCREEN_WIDTH - 4, 8, BLACK); // Clear status bar area
display.drawRect(0, SCREEN_HEIGHT - 24, SCREEN_WIDTH - 4, 8, WHITE); // Draw border
int filledWidth = (progress * SCREEN_WIDTH - 4) / 100; // Calculate progress width
display.fillRect(1, SCREEN_HEIGHT - 23, filledWidth - 4, 6, WHITE); // Fill progress bar

display.setCursor((SCREEN_WIDTH / 2) - 12, SCREEN_HEIGHT - 10);
display.setTextSize(1);
display.setTextColor(WHITE, BLACK);
display.print(progress);
display.println(" %");
display.display();
}

void displayWelcomeScreen() {
display.clearDisplay();

// Draw WiFi symbol
display.drawBitmap(0, 12, epd_bitmap_wifi, 44, 44, WHITE);

// Display SSID text
display.setCursor(40, 13);
display.setTextSize(1);
display.setTextColor(WHITE, BLACK);
display.println("Verbinde dich"); // "Connect"
display.setCursor(60, 27);
display.println("mit:"); // "with"

// Display SSID
display.setCursor(40, 43);
display.setTextSize(1); // Larger text for SSID
display.print(ssid);

display.display();
}

void displaySuccessScreen() {
display.clearDisplay();

// Draw WiFi symbol
display.drawBitmap(0, 12, epd_bitmap_checkmark, 44, 44, WHITE);

// Display SSID text
display.setCursor(48, 22);
display.setTextSize(1);
display.setTextColor(WHITE, BLACK);
display.println("Erfolgreich"); // "Successfully"
display.setCursor(48, 36);
display.println("hochgeladen!"); // "uploaded!"

display.display();
}

void wipeDisplay() {
display.clearDisplay();
display.println("");
display.display();
}

void setupWiFi() {
WiFi.macAddress(mac);
char macLastFour[5];
snprintf(macLastFour, sizeof(macLastFour), "%02X%02X", mac[4], mac[5]);
ssid = "senseBox:" + String(macLastFour);

// Define the IP address, gateway, and subnet mask
IPAddress local_IP(192, 168, 1, 1); // The new IP address
IPAddress gateway(192, 168, 1, 1); // Gateway address (can be the same as the AP's IP)
IPAddress subnet(255, 255, 255, 0); // Subnet mask

// Set the IP address, gateway, and subnet mask of the access point
WiFi.softAPConfig(local_IP, gateway, subnet);

// Start the access point
WiFi.softAP(ssid.c_str());
}

void setupOTA() {
// Handle updating process
server.on(
"/sketch", HTTP_POST,
[]() {
server.sendHeader("Connection", "close");
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
ESP.restart();
},
[]() {
HTTPUpload &upload = server.upload();

if (upload.status == UPLOAD_FILE_START) {
Serial.setDebugOutput(true);
size_t fsize = UPDATE_SIZE_UNKNOWN;
if (server.clientContentLength() > 0) {
fsize = server.clientContentLength();
}
Serial.printf("Receiving Update: %s, Size: %d\n", upload.filename.c_str(), fsize);

Serial.printf("Update: %s\n", upload.filename.c_str());
if (!Update.begin(fsize)) { //start with max available size
Update.printError(Serial);
}
} else if (upload.status == UPLOAD_FILE_WRITE) {
/* flashing firmware to ESP*/
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
Update.printError(Serial);
} else {
int progress = (Update.progress() * 100) / Update.size();
if (displayEnabled) {
displayStatusBar(progress); // Update progress on display
}
rgb_led.setPixelColor(0, rgb_led.Color(255, 255, 51));
rgb_led.show();
}
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) { //true to set the size to the current progress
if (displayEnabled) {
displaySuccessScreen();
delay(3000);
wipeDisplay();
}
rgb_led.setPixelColor(0, rgb_led.Color(51, 51, 255));
rgb_led.show();
} else {
Update.printError(Serial);
}
Serial.setDebugOutput(false);
}
yield();
}
);
}

void setup() {
// Start Serial communication
Serial.begin(115200);
rgb_led.begin();
rgb_led.setBrightness(15);
rgb_led.setPixelColor(0, rgb_led.Color(51, 51, 255));
rgb_led.show();

// Configure button pin as input
pinMode(BUTTON_PIN, INPUT_PULLUP);

// Interrupt for the button
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), handleButtonPress, FALLING);

#ifdef DISPLAY_ENABLED
setupDisplay();
#endif
setupWiFi();
// Set the ESP32 as an access point
setupOTA();
server.begin();
}

void loop() {
// Handle client requests
server.handleClient();

#ifdef DISPLAY_ENABLED
if (displayEnabled) {
displayWelcomeScreen();
}
#endif

if (doublePressDetected) {
Serial.println("Doppeldruck erkannt!"); // "Double press detected!"
setBootPartitionToOTA0();
#ifdef DISPLAY_ENABLED
if (displayEnabled) {
display.setCursor(0, 0);
display.setTextSize(1);
display.setTextColor(WHITE, BLACK);
display.println("");
display.display();
delay(50);
}
#endif
// Restart to boot from the new partition
esp_restart();
}
}
33 changes: 30 additions & 3 deletions variants/sensebox_eye/variant.cpp
Original file line number Diff line number Diff line change
@@ -1,12 +1,39 @@
#include "esp32-hal-gpio.h"
#include "pins_arduino.h"
#include "esp_log.h"
#include "esp_partition.h"
#include "esp_system.h"
#include "esp_ota_ops.h"

extern "C" {

void initVariant(void) {
// blink the RGB LED
rgbLedWrite(PIN_LED, 0x00, 0x10, 0x00); // green
void blinkLED(uint8_t r, uint8_t g, uint8_t b) {
rgbLedWrite(PIN_LED, r, g, b);
delay(20);
rgbLedWrite(PIN_LED, 0x00, 0x00, 0x00); // off
}

void initVariant(void) {
// define button pin
pinMode(47, INPUT_PULLUP);

// Check if button is pressed
if (digitalRead(47) == LOW) {
// When the button is pressed and then released, boot into the OTA1 partition
const esp_partition_t *ota1_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL);

if (ota1_partition) {
esp_err_t err = esp_ota_set_boot_partition(ota1_partition);
if (err == ESP_OK) {
blinkLED(0x00, 0x00, 0x10); // blue
esp_restart(); // restart, to boot OTA1 partition
} else {
blinkLED(0x10, 0x00, 0x00); // red
ESP_LOGE("OTA", "Error setting OTA1 partition: %s", esp_err_to_name(err));
}
}
} else {
blinkLED(0x00, 0x10, 0x00); // green
}
}
}