Skip to content

Commit 22265e7

Browse files
committed
feat(board): OTA for senseBox Eye
1 parent 4eff7f9 commit 22265e7

File tree

4 files changed

+340
-8
lines changed

4 files changed

+340
-8
lines changed

boards.txt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41643,6 +41643,11 @@ sensebox_eye.menu.UploadMode.cdc=USB-OTG CDC (TinyUSB)
4164341643
sensebox_eye.menu.UploadMode.cdc.upload.use_1200bps_touch=true
4164441644
sensebox_eye.menu.UploadMode.cdc.upload.wait_for_upload_port=true
4164541645

41646+
sensebox_eye.menu.PartitionScheme.tinyuf2=TinyUF2 Compatibility (2MB APP/12MB FFAT)
41647+
sensebox_eye.menu.PartitionScheme.tinyuf2.build.custom_bootloader=bootloader_tinyuf2
41648+
sensebox_eye.menu.PartitionScheme.tinyuf2.build.custom_partitions=partitions-16MB-tinyuf2
41649+
sensebox_eye.menu.PartitionScheme.tinyuf2.upload.maximum_size=2097152
41650+
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"
4164641651
sensebox_eye.menu.PartitionScheme.default_16MB=Default (6.25MB APP/3.43MB SPIFFS)
4164741652
sensebox_eye.menu.PartitionScheme.default_16MB.build.partitions=default_16MB
4164841653
sensebox_eye.menu.PartitionScheme.default_16MB.upload.maximum_size=6553600
@@ -41655,11 +41660,6 @@ sensebox_eye.menu.PartitionScheme.app3M_fat9M_16MB.upload.maximum_size=3145728
4165541660
sensebox_eye.menu.PartitionScheme.fatflash=Large FFAT (2MB APP/12.5MB FATFS)
4165641661
sensebox_eye.menu.PartitionScheme.fatflash.build.partitions=ffat
4165741662
sensebox_eye.menu.PartitionScheme.fatflash.upload.maximum_size=2097152
41658-
sensebox_eye.menu.PartitionScheme.tinyuf2=TinyUF2 Compatibility (2MB APP/12MB FFAT)
41659-
sensebox_eye.menu.PartitionScheme.tinyuf2.build.custom_bootloader=bootloader_tinyuf2
41660-
sensebox_eye.menu.PartitionScheme.tinyuf2.build.custom_partitions=partitions_tinyuf2
41661-
sensebox_eye.menu.PartitionScheme.tinyuf2.upload.maximum_size=2097152
41662-
sensebox_eye.menu.PartitionScheme.tinyuf2.upload.extra_flags=0x410000 "{runtime.platform.path}/variants/{build.variant}/tinyuf2.bin"
4166341663
sensebox_eye.menu.PartitionScheme.gen4esp32scheme4=Huge App (16MB APP)
4166441664
sensebox_eye.menu.PartitionScheme.gen4esp32scheme4.build.custom_partitions=gen4esp32_16MBapp
4166541665
sensebox_eye.menu.PartitionScheme.gen4esp32scheme4.upload.maximum_size=16646144

variants/sensebox_eye/APOTA.bin

965 KB
Binary file not shown.

variants/sensebox_eye/APOTA.ino

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,305 @@
1+
// APOTA is an Arduino fallback sketch that is written to OTA1_Partition.
2+
// APOTA opens an access point which waits to receive a .bin file on /sketch.
3+
// After successful upload, the file is written to OTA0_Partition, and the microcontroller reboots to the newly uploaded sketch.
4+
5+
#define DISPLAY_ENABLED
6+
7+
#include <WiFi.h>
8+
#include <WebServer.h>
9+
#include <ESPmDNS.h>
10+
#include <WiFiAP.h>
11+
#include <Update.h>
12+
#include <Wire.h>
13+
#ifdef DISPLAY_ENABLED
14+
#define SCREEN_WIDTH 128
15+
#define SCREEN_HEIGHT 64
16+
#define OLED_RESET -1
17+
#include <Adafruit_GFX.h>
18+
#include <Adafruit_SSD1306.h>
19+
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
20+
#include <Adafruit_NeoPixel.h>
21+
Adafruit_NeoPixel rgb_led = Adafruit_NeoPixel(1, PIN_LED, NEO_GRB + NEO_KHZ800);
22+
23+
#endif
24+
#include "esp_partition.h"
25+
#include "esp_ota_ops.h"
26+
#include "esp_system.h"
27+
28+
String ssid;
29+
uint8_t mac[6];
30+
31+
// Create an instance of the server
32+
WebServer server(80);
33+
bool displayEnabled;
34+
35+
const int BUTTON_PIN = 0; // GPIO for the button
36+
volatile unsigned long lastPressTime = 0; // Time of last button press
37+
volatile bool doublePressDetected = false; // Flag for double press
38+
const unsigned long doublePressInterval = 500; // Max. time (in ms) between two presses for double press
39+
volatile int pressCount = 0; // Counts the button presses
40+
41+
const unsigned char epd_bitmap_wifi[] PROGMEM = {
42+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
43+
0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x01, 0xff, 0xf0, 0x00, 0x00, 0x00, 0x07, 0xff, 0xfc, 0x00, 0x00, 0x00, 0x1f, 0xe0, 0xff, 0x00, 0x00,
44+
0x00, 0x3f, 0x00, 0x0f, 0x80, 0x00, 0x00, 0x7c, 0x00, 0x03, 0xe0, 0x00, 0x00, 0xf0, 0x00, 0x01, 0xf0, 0x00, 0x01, 0xe0, 0x00, 0x00, 0x78, 0x00,
45+
0x03, 0xc0, 0x00, 0x00, 0x38, 0x00, 0x07, 0x80, 0x00, 0x00, 0x1c, 0x00, 0x0f, 0x00, 0x06, 0x00, 0x0e, 0x00, 0x0e, 0x00, 0x7f, 0xe0, 0x0e, 0x00,
46+
0x0c, 0x01, 0xff, 0xf0, 0x06, 0x00, 0x00, 0x07, 0xff, 0xfc, 0x02, 0x00, 0x00, 0x0f, 0x80, 0x3e, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x0f, 0x00, 0x00,
47+
0x00, 0x1c, 0x00, 0x07, 0x80, 0x00, 0x00, 0x38, 0x00, 0x03, 0xc0, 0x00, 0x00, 0x70, 0x00, 0x01, 0xc0, 0x00, 0x00, 0x70, 0x00, 0x00, 0xc0, 0x00,
48+
0x00, 0x20, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x1f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7f, 0xc0, 0x00, 0x00, 0x00, 0x00, 0xff, 0xe0, 0x00, 0x00,
49+
0x00, 0x01, 0xe0, 0xf0, 0x00, 0x00, 0x00, 0x01, 0xc0, 0x78, 0x00, 0x00, 0x00, 0x03, 0x80, 0x38, 0x00, 0x00, 0x00, 0x01, 0x00, 0x10, 0x00, 0x00,
50+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
51+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
52+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
53+
};
54+
55+
// 'checkmark', 44x44px
56+
const unsigned char epd_bitmap_checkmark[] PROGMEM = {
57+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
58+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
59+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
60+
0x00, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x00, 0x00, 0x03, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00,
61+
0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x0e, 0x00, 0xf0, 0x00, 0x00,
62+
0x00, 0x0f, 0x01, 0xe0, 0x00, 0x00, 0x00, 0x0f, 0x83, 0xc0, 0x00, 0x00, 0x00, 0x07, 0xc7, 0x80, 0x00, 0x00, 0x00, 0x03, 0xef, 0x00, 0x00, 0x00,
63+
0x00, 0x01, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00,
64+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
65+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
66+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
67+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
68+
};
69+
70+
void IRAM_ATTR handleButtonPress() {
71+
unsigned long currentTime = millis(); // Get current time
72+
73+
// Debounce: If the current press is too close to the last one, ignore it
74+
if (currentTime - lastPressTime > 50) {
75+
pressCount++; // Count the button press
76+
77+
// Check if this is the second press within the double-press interval
78+
if (pressCount == 2 && (currentTime - lastPressTime <= doublePressInterval)) {
79+
doublePressDetected = true; // Double press detected
80+
pressCount = 0; // Reset counter
81+
}
82+
83+
lastPressTime = currentTime; // Update the time of the last press
84+
}
85+
}
86+
87+
// Function to switch the boot partition to OTA1
88+
void setBootPartitionToOTA0() {
89+
const esp_partition_t *ota0_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_0, NULL);
90+
91+
if (ota0_partition) {
92+
// Set OTA1 as new boot partition
93+
esp_ota_set_boot_partition(ota0_partition);
94+
Serial.println("Boot partition changed to OTA0. Restarting...");
95+
96+
// Restart to boot from the new partition
97+
esp_restart();
98+
} else {
99+
Serial.println("OTA1 partition not found!");
100+
}
101+
}
102+
103+
void setupDisplay() {
104+
Serial.println("checking display connection...");
105+
Wire.begin(PIN_QWIIC_SDA,PIN_QWIIC_SCL);
106+
displayEnabled = Wire.requestFrom(0x3D, 1); // Check if the display is connected
107+
if (displayEnabled) {
108+
delay(1000);
109+
Serial.println("Display found!");
110+
display.begin(SSD1306_SWITCHCAPVCC, 0x3D);
111+
display.display();
112+
delay(100);
113+
display.clearDisplay();
114+
} else {
115+
while(true) {
116+
Serial.println("Display not found!");
117+
delay(1000);
118+
}
119+
}
120+
}
121+
122+
void displayStatusBar(int progress) {
123+
display.clearDisplay();
124+
display.setCursor(24, 8);
125+
display.println("Sketch wird");
126+
display.setCursor(22, 22);
127+
display.println("hochgeladen!");
128+
129+
display.fillRect(0, SCREEN_HEIGHT - 24, SCREEN_WIDTH - 4, 8, BLACK); // Clear status bar area
130+
display.drawRect(0, SCREEN_HEIGHT - 24, SCREEN_WIDTH - 4, 8, WHITE); // Draw border
131+
int filledWidth = (progress * SCREEN_WIDTH - 4) / 100; // Calculate progress width
132+
display.fillRect(1, SCREEN_HEIGHT - 23, filledWidth - 4, 6, WHITE); // Fill progress bar
133+
134+
display.setCursor((SCREEN_WIDTH / 2) - 12, SCREEN_HEIGHT - 10);
135+
display.setTextSize(1);
136+
display.setTextColor(WHITE, BLACK);
137+
display.print(progress);
138+
display.println(" %");
139+
display.display();
140+
}
141+
142+
void displayWelcomeScreen() {
143+
display.clearDisplay();
144+
145+
// Draw WiFi symbol
146+
display.drawBitmap(0, 12, epd_bitmap_wifi, 44, 44, WHITE);
147+
148+
// Display SSID text
149+
display.setCursor(40, 13);
150+
display.setTextSize(1);
151+
display.setTextColor(WHITE, BLACK);
152+
display.println("Verbinde dich"); // "Connect"
153+
display.setCursor(60, 27);
154+
display.println("mit:"); // "with"
155+
156+
// Display SSID
157+
display.setCursor(40, 43);
158+
display.setTextSize(1); // Larger text for SSID
159+
display.print(ssid);
160+
161+
display.display();
162+
}
163+
164+
void displaySuccessScreen() {
165+
display.clearDisplay();
166+
167+
// Draw WiFi symbol
168+
display.drawBitmap(0, 12, epd_bitmap_checkmark, 44, 44, WHITE);
169+
170+
// Display SSID text
171+
display.setCursor(48, 22);
172+
display.setTextSize(1);
173+
display.setTextColor(WHITE, BLACK);
174+
display.println("Erfolgreich"); // "Successfully"
175+
display.setCursor(48, 36);
176+
display.println("hochgeladen!"); // "uploaded!"
177+
178+
display.display();
179+
}
180+
181+
void wipeDisplay() {
182+
display.clearDisplay();
183+
display.println("");
184+
display.display();
185+
}
186+
187+
void setupWiFi() {
188+
WiFi.macAddress(mac);
189+
char macLastFour[5];
190+
snprintf(macLastFour, sizeof(macLastFour), "%02X%02X", mac[4], mac[5]);
191+
ssid = "senseBox:" + String(macLastFour);
192+
193+
// Define the IP address, gateway, and subnet mask
194+
IPAddress local_IP(192, 168, 1, 1); // The new IP address
195+
IPAddress gateway(192, 168, 1, 1); // Gateway address (can be the same as the AP's IP)
196+
IPAddress subnet(255, 255, 255, 0); // Subnet mask
197+
198+
// Set the IP address, gateway, and subnet mask of the access point
199+
WiFi.softAPConfig(local_IP, gateway, subnet);
200+
201+
// Start the access point
202+
WiFi.softAP(ssid.c_str());
203+
}
204+
205+
void setupOTA() {
206+
// Handle updating process
207+
server.on(
208+
"/sketch", HTTP_POST,
209+
[]() {
210+
server.sendHeader("Connection", "close");
211+
server.send(200, "text/plain", (Update.hasError()) ? "FAIL" : "OK");
212+
ESP.restart();
213+
},
214+
[]() {
215+
HTTPUpload &upload = server.upload();
216+
217+
if (upload.status == UPLOAD_FILE_START) {
218+
Serial.setDebugOutput(true);
219+
size_t fsize = UPDATE_SIZE_UNKNOWN;
220+
if (server.clientContentLength() > 0) {
221+
fsize = server.clientContentLength();
222+
}
223+
Serial.printf("Receiving Update: %s, Size: %d\n", upload.filename.c_str(), fsize);
224+
225+
Serial.printf("Update: %s\n", upload.filename.c_str());
226+
if (!Update.begin(fsize)) { //start with max available size
227+
Update.printError(Serial);
228+
}
229+
} else if (upload.status == UPLOAD_FILE_WRITE) {
230+
/* flashing firmware to ESP*/
231+
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) {
232+
Update.printError(Serial);
233+
} else {
234+
int progress = (Update.progress() * 100) / Update.size();
235+
if (displayEnabled) {
236+
displayStatusBar(progress); // Update progress on display
237+
}
238+
}
239+
} else if (upload.status == UPLOAD_FILE_END) {
240+
if (Update.end(true)) { //true to set the size to the current progress
241+
if (displayEnabled) {
242+
displaySuccessScreen();
243+
}
244+
delay(3000);
245+
wipeDisplay();
246+
} else {
247+
Update.printError(Serial);
248+
}
249+
Serial.setDebugOutput(false);
250+
}
251+
yield();
252+
}
253+
);
254+
}
255+
256+
void setup() {
257+
// Start Serial communication
258+
Serial.begin(115200);
259+
rgb_led.begin();
260+
rgb_led.setBrightness(15);
261+
rgb_led.setPixelColor(0, rgb_led.Color(51, 51, 255));
262+
rgb_led.show();
263+
264+
// Configure button pin as input
265+
pinMode(BUTTON_PIN, INPUT_PULLUP);
266+
267+
// Interrupt for the button
268+
attachInterrupt(digitalPinToInterrupt(BUTTON_PIN), handleButtonPress, FALLING);
269+
270+
#ifdef DISPLAY_ENABLED
271+
setupDisplay();
272+
#endif
273+
setupWiFi();
274+
// Set the ESP32 as an access point
275+
setupOTA();
276+
server.begin();
277+
}
278+
279+
void loop() {
280+
// Handle client requests
281+
server.handleClient();
282+
283+
#ifdef DISPLAY_ENABLED
284+
if (displayEnabled) {
285+
displayWelcomeScreen();
286+
}
287+
#endif
288+
289+
if (doublePressDetected) {
290+
Serial.println("Doppeldruck erkannt!"); // "Double press detected!"
291+
setBootPartitionToOTA0();
292+
#ifdef DISPLAY_ENABLED
293+
if (displayEnabled) {
294+
display.setCursor(0, 0);
295+
display.setTextSize(1);
296+
display.setTextColor(WHITE, BLACK);
297+
display.println("");
298+
display.display();
299+
delay(50);
300+
}
301+
#endif
302+
// Restart to boot from the new partition
303+
esp_restart();
304+
}
305+
}

variants/sensebox_eye/variant.cpp

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,39 @@
11
#include "esp32-hal-gpio.h"
22
#include "pins_arduino.h"
3+
#include "esp_log.h"
4+
#include "esp_partition.h"
5+
#include "esp_system.h"
6+
#include "esp_ota_ops.h"
37

48
extern "C" {
59

6-
void initVariant(void) {
7-
// blink the RGB LED
8-
rgbLedWrite(PIN_LED, 0x00, 0x10, 0x00); // green
10+
void blinkLED(uint8_t r, uint8_t g, uint8_t b) {
11+
rgbLedWrite(PIN_LED, r, g, b);
912
delay(20);
1013
rgbLedWrite(PIN_LED, 0x00, 0x00, 0x00); // off
1114
}
15+
16+
void initVariant(void) {
17+
// define button pin
18+
pinMode(47, INPUT_PULLUP);
19+
20+
// Check if button is pressed
21+
if (digitalRead(47) == LOW) {
22+
// When the button is pressed and then released, boot into the OTA1 partition
23+
const esp_partition_t *ota1_partition = esp_partition_find_first(ESP_PARTITION_TYPE_APP, ESP_PARTITION_SUBTYPE_APP_OTA_1, NULL);
24+
25+
if (ota1_partition) {
26+
esp_err_t err = esp_ota_set_boot_partition(ota1_partition);
27+
if (err == ESP_OK) {
28+
blinkLED(0x00, 0x00, 0x10); // blue
29+
esp_restart(); // restart, to boot OTA1 partition
30+
} else {
31+
blinkLED(0x10, 0x00, 0x00); // red
32+
ESP_LOGE("OTA", "Error setting OTA1 partition: %s", esp_err_to_name(err));
33+
}
34+
}
35+
} else {
36+
blinkLED(0x00, 0x10, 0x00); // green
37+
}
38+
}
1239
}

0 commit comments

Comments
 (0)