Skip to content

Add OTA to senseBox Eye board #11684

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
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
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 OTA0
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 OTA0 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("OTA0 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"
Comment on lines +144 to +146

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All log messages are English, why not also use English on the display?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The board is mainly sold in germany and OTA is mainly used in workshops in schools also in germany

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hope these students also learn to use English, as the software world is very much English oriented 😆

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At least it is not consistent. It is a mix of English and German.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the concern. We want to stick with German for the display tho, if possible. I dont think its mixed right now, because the students will only see the display output, so to them its consistently German (they use a graphical programming interface in German too).
I hope this is ok?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't they also look at the serial log output of the ESP?


// 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
}
}
}