Skip to content

Commit b48d917

Browse files
committed
407
- Added support for OTA updates using zlib compression. This reduces the size of the update files and consequently halves the update time. Special thanks to [ginkage/CeilingCW-C6](https://github.com/ginkage/CeilingCW-C6) for the inspiration and implementation details.
1 parent ec9719f commit b48d917

File tree

9 files changed

+304
-11
lines changed

9 files changed

+304
-11
lines changed

.vscode/settings.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,5 +63,16 @@
6363
"freertos.h": "c",
6464
"zigbee.h": "c"
6565
},
66-
"editor.formatOnSave": true
66+
"editor.formatOnSave": false,
67+
"idf.projectDirPath": "${workspaceFolder}/firmware",
68+
"idf.port": "/dev/tty.usbmodemflip_Elevlox1",
69+
"idf.espIdfPath": "/Users/lost/esp/v5.3.1/esp-idf",
70+
"idf.pythonBinPath": "/Users/lost/.espressif/python_env/idf5.3_py3.9_env/bin/python",
71+
"idf.toolsPath": "/Users/lost/.espressif",
72+
"idf.customExtraPaths": "/Users/lost/.espressif/tools/xtensa-esp-elf-gdb/14.2_20240403/xtensa-esp-elf-gdb/bin:/Users/lost/.espressif/tools/riscv32-esp-elf-gdb/14.2_20240403/riscv32-esp-elf-gdb/bin:/Users/lost/.espressif/tools/xtensa-esp-elf/esp-13.2.0_20240530/xtensa-esp-elf/bin:/Users/lost/.espressif/tools/riscv32-esp-elf/esp-13.2.0_20240530/riscv32-esp-elf/bin:/Users/lost/.espressif/tools/esp32ulp-elf/2.38_20240113/esp32ulp-elf/bin:/Users/lost/.espressif/tools/cmake/3.24.0/CMake.app/Contents/bin:/Users/lost/.espressif/tools/openocd-esp32/v0.12.0-esp32-20240318/openocd-esp32/bin:/Users/lost/.espressif/tools/ninja/1.11.1:/Users/lost/.espressif/tools/esp-rom-elfs/20240305",
73+
"idf.customExtraVars": {
74+
"OPENOCD_SCRIPTS": "/Users/lost/.espressif/tools/openocd-esp32/v0.12.0-esp32-20240318/openocd-esp32/share/openocd/scripts",
75+
"ESP_ROM_ELF_DIR": "/Users/lost/.espressif/tools/esp-rom-elfs/20240305/"
76+
},
77+
"idf.gitPath": "git"
6778
}

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,10 @@ ota:
117117

118118
### Verified Supported Zigbee Systems
119119

120-
- [zigbee2mqtt](https://www.zigbee2mqtt.io/) - Full support, still requires [external converter](https://github.com/xyzroe/ZigUSB_C6/tree/main/external_converter/ZigUSB_C6.js) ⭐⭐⭐⭐⭐
120+
- [zigbee2mqtt](https://www.zigbee2mqtt.io/) - Full support, no longer requires an [external converter](https://github.com/xyzroe/ZigUSB_C6/tree/main/external_converter/ZigUSB_C6.js) ⭐⭐⭐⭐⭐
121121
- [HOMEd](https://wiki.homed.dev/page/HOMEd) - Partial support ⭐⭐⭐⭐
122122
- [ZHA](https://www.home-assistant.io/integrations/zha/) - Partial support ⭐⭐⭐⭐
123+
- Other systems must be tested. The device uses standard clusters and attributes, so most coordinators can support it out of the box.
123124

124125
### Where to buy?
125126

main/const.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
#define EXT_LED_ENDPOINT 3 /* the endpoint number for the external LED */
2828
#define INV_USB_ENDPOINT 4 /* the endpoint number for the USB switch (inverted logic) */
2929

30-
#define OTA_FW_VERSION 0x00000143 /* The attribute indicates the version of the firmware */
31-
#define FW_BUILD_DATE "20241024" /* The parameter indicates the build date of the firmware */
30+
#define OTA_FW_VERSION 0x00000133 /* The attribute indicates the version of the firmware */
31+
#define FW_BUILD_DATE "20241108" /* The parameter indicates the build date of the firmware */
3232

3333
/* GPIO configuration */
3434
#define BTN_GPIO_1 5 /* Button from v0.3 */

main/idf_component.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ dependencies:
33
espressif/button: "^3.2.0"
44
espressif/esp-zboss-lib: "1.5.0"
55
espressif/esp-zigbee-lib: "1.5.0"
6-
## espressif/zlib: "1.3.0"
6+
espressif/zlib: "1.3.0"
77
## Required IDF version
88
idf:
99
version: "5.3.1"

main/ota.c

Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
#include "ota.h"
2+
3+
#include <esp_err.h>
4+
#include <esp_log.h>
5+
#include <esp_ota_ops.h>
6+
#include <esp_timer.h>
7+
#include <string.h>
8+
#include <zlib.h>
9+
10+
bool zlib_init = false;
11+
static const esp_partition_t *s_ota_partition = NULL;
12+
static esp_ota_handle_t s_ota_handle = 0;
13+
z_stream zlib_stream;
14+
15+
uint8_t ota_header[6];
16+
size_t ota_header_len = 0;
17+
bool ota_upgrade_subelement = false;
18+
size_t ota_data_len = 0;
19+
uint64_t ota_last_receive_us = 0;
20+
size_t ota_receive_not_logged = 0;
21+
22+
void ota_reset()
23+
{
24+
if (s_ota_partition) {
25+
esp_ota_abort(s_ota_handle);
26+
s_ota_partition = NULL;
27+
}
28+
29+
if (zlib_init) {
30+
inflateEnd(&zlib_stream);
31+
zlib_init = false;
32+
}
33+
}
34+
35+
bool ota_start()
36+
{
37+
zlib_init = false;
38+
s_ota_partition = NULL;
39+
s_ota_handle = 0;
40+
41+
memset(&zlib_stream, 0, sizeof(zlib_stream));
42+
int ret = inflateInit(&zlib_stream);
43+
if (ret == Z_OK) {
44+
zlib_init = true;
45+
} else {
46+
ESP_LOGE(__func__, "zlib init failed: %d", ret);
47+
return false;
48+
}
49+
50+
s_ota_partition = esp_ota_get_next_update_partition(NULL);
51+
if (!s_ota_partition) {
52+
ESP_LOGE(__func__, "No next OTA partition");
53+
return false;
54+
}
55+
56+
esp_err_t err = esp_ota_begin(s_ota_partition, OTA_WITH_SEQUENTIAL_WRITES, &s_ota_handle);
57+
if (err != ESP_OK) {
58+
ESP_LOGE(__func__, "Error starting OTA: %d", err);
59+
s_ota_partition = NULL;
60+
return false;
61+
}
62+
63+
return true;
64+
}
65+
66+
bool ota_write(const uint8_t *data, size_t size, bool flush)
67+
{
68+
uint8_t buf[256];
69+
70+
if (!s_ota_partition) {
71+
return false;
72+
}
73+
74+
zlib_stream.avail_in = size;
75+
zlib_stream.next_in = data;
76+
77+
do {
78+
zlib_stream.avail_out = sizeof(buf);
79+
zlib_stream.next_out = buf;
80+
81+
int ret = inflate(&zlib_stream, flush ? Z_FINISH : Z_NO_FLUSH);
82+
if (ret == Z_STREAM_ERROR || ret == Z_NEED_DICT || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) {
83+
ESP_LOGE(__func__, "zlib error: %d", ret);
84+
esp_ota_abort(s_ota_handle);
85+
s_ota_partition = NULL;
86+
return false;
87+
}
88+
89+
size_t available = sizeof(buf) - zlib_stream.avail_out;
90+
if (available > 0) {
91+
esp_err_t err = esp_ota_write(s_ota_handle, buf, available);
92+
if (err != ESP_OK) {
93+
ESP_LOGE(__func__, "Error writing OTA: %d", err);
94+
esp_ota_abort(s_ota_handle);
95+
s_ota_partition = NULL;
96+
return false;
97+
}
98+
}
99+
} while (zlib_stream.avail_in > 0 || zlib_stream.avail_out == 0);
100+
101+
return true;
102+
}
103+
104+
bool ota_finish()
105+
{
106+
if (!s_ota_partition) {
107+
ESP_LOGE(__func__, "OTA not running");
108+
return false;
109+
}
110+
111+
if (!ota_write(NULL, 0, true)) {
112+
return false;
113+
}
114+
115+
esp_err_t err = esp_ota_end(s_ota_handle);
116+
if (err != ESP_OK) {
117+
ESP_LOGE(__func__, "Error ending OTA: %d", err);
118+
s_ota_partition = NULL;
119+
return false;
120+
}
121+
122+
inflateEnd(&zlib_stream);
123+
zlib_init = false;
124+
125+
err = esp_ota_set_boot_partition(s_ota_partition);
126+
if (err != ESP_OK) {
127+
ESP_LOGE(__func__, "Error setting boot partition: %d", err);
128+
s_ota_partition = NULL;
129+
return false;
130+
}
131+
132+
s_ota_partition = NULL;
133+
return true;
134+
}
135+
136+
esp_err_t zb_ota_upgrade_status_handler(esp_zb_zcl_ota_upgrade_value_message_t message)
137+
{
138+
esp_err_t ret = ESP_OK;
139+
140+
if (message.info.status == ESP_ZB_ZCL_STATUS_SUCCESS) {
141+
if (message.upgrade_status != ESP_ZB_ZCL_OTA_UPGRADE_STATUS_RECEIVE) {
142+
if (ota_receive_not_logged) {
143+
ESP_LOGD(__func__, "OTA (%zu receive data messages suppressed)",
144+
ota_receive_not_logged);
145+
ota_receive_not_logged = 0;
146+
}
147+
ota_last_receive_us = 0;
148+
}
149+
150+
switch (message.upgrade_status) {
151+
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_START:
152+
ESP_LOGI(__func__, "OTA start");
153+
ota_reset();
154+
ota_header_len = 0;
155+
ota_upgrade_subelement = false;
156+
ota_data_len = 0;
157+
if (!ota_start()) {
158+
ota_reset();
159+
ret = ESP_FAIL;
160+
}
161+
break;
162+
163+
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_RECEIVE:
164+
const uint8_t *payload = message.payload;
165+
size_t payload_size = message.payload_size;
166+
167+
// Read and process the first sub-element, ignoring everything else
168+
while (ota_header_len < 6 && payload_size > 0) {
169+
ota_header[ota_header_len++] = payload[0];
170+
payload++;
171+
payload_size--;
172+
}
173+
174+
if (!ota_upgrade_subelement && ota_header_len == 6) {
175+
if (ota_header[0] == 0 && ota_header[1] == 0) {
176+
ota_upgrade_subelement = true;
177+
ota_data_len =
178+
(((int)ota_header[5] & 0xFF) << 24)
179+
| (((int)ota_header[4] & 0xFF) << 16)
180+
| (((int)ota_header[3] & 0xFF) << 8 )
181+
| ((int)ota_header[2] & 0xFF);
182+
ESP_LOGD(__func__, "OTA sub-element size %zu", ota_data_len);
183+
} else {
184+
ESP_LOGE(__func__, "OTA sub-element type %02x%02x not supported", ota_header[0], ota_header[1]);
185+
ota_reset();
186+
ret = ESP_FAIL;
187+
}
188+
}
189+
190+
if (ota_data_len) {
191+
if (payload_size > ota_data_len)
192+
payload_size = ota_data_len;
193+
ota_data_len -= payload_size;
194+
195+
if (ota_write(payload, payload_size, false)) {
196+
uint64_t now_us = esp_timer_get_time();
197+
if (!ota_last_receive_us
198+
|| now_us - ota_last_receive_us >= 30 * 1000 * 1000) {
199+
ESP_LOGD(__func__, "OTA receive data (%zu messages suppressed)",
200+
ota_receive_not_logged);
201+
ota_last_receive_us = now_us;
202+
ota_receive_not_logged = 0;
203+
} else {
204+
ota_receive_not_logged++;
205+
}
206+
} else {
207+
ota_reset();
208+
ret = ESP_FAIL;
209+
}
210+
}
211+
break;
212+
213+
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_APPLY:
214+
ESP_LOGI(__func__, "OTA apply");
215+
break;
216+
217+
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_CHECK:
218+
ESP_LOGI(__func__, "OTA data complete");
219+
break;
220+
221+
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_FINISH:
222+
ESP_LOGI(__func__, "OTA finished");
223+
bool ok = ota_finish();
224+
ota_reset();
225+
if (ok)
226+
esp_restart();
227+
break;
228+
229+
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_ABORT:
230+
ESP_LOGI(__func__, "OTA aborted");
231+
ota_reset();
232+
break;
233+
234+
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_OK:
235+
ESP_LOGI(__func__, "OTA data ok");
236+
break;
237+
238+
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_ERROR:
239+
ESP_LOGI(__func__, "OTA data error");
240+
ota_reset();
241+
break;
242+
243+
case ESP_ZB_ZCL_OTA_UPGRADE_IMAGE_STATUS_NORMAL:
244+
ESP_LOGI(__func__, "OTA image accepted");
245+
break;
246+
247+
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_BUSY:
248+
ESP_LOGI(__func__, "OTA busy");
249+
ota_reset();
250+
break;
251+
252+
case ESP_ZB_ZCL_OTA_UPGRADE_STATUS_SERVER_NOT_FOUND:
253+
ESP_LOGI(__func__, "OTA server not found");
254+
ota_reset();
255+
break;
256+
257+
default:
258+
ESP_LOGI(__func__, "OTA status: %d", message.upgrade_status);
259+
break;
260+
}
261+
}
262+
263+
return ret;
264+
}

main/ota.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
3+
#include <ha/esp_zigbee_ha_standard.h>
4+
5+
#ifdef __cplusplus
6+
extern "C" {
7+
#endif
8+
9+
esp_err_t zb_ota_upgrade_status_handler(esp_zb_zcl_ota_upgrade_value_message_t message);
10+
11+
#ifdef __cplusplus
12+
} // extern "C"
13+
#endif

main/zigbee.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include "perf.h"
2525
#include "tools.h"
2626
#include "zigbee.h"
27+
#include "ota.h"
2728

2829
/*------ Global definitions -----------*/
2930

@@ -310,6 +311,7 @@ size_t ota_header_len_;
310311
bool ota_upgrade_subelement_;
311312
uint8_t ota_header_[6];
312313

314+
/*
313315
static esp_err_t zb_ota_upgrade_status_handler(esp_zb_zcl_ota_upgrade_value_message_t message)
314316
{
315317
static uint32_t total_size = 0;
@@ -338,7 +340,7 @@ static esp_err_t zb_ota_upgrade_status_handler(esp_zb_zcl_ota_upgrade_value_mess
338340
339341
ESP_LOGI(__func__, "-- OTA Client receives data: progress [%ld/%ld]", offset, total_size);
340342
341-
/* Read and process the first sub-element, ignoring everything else */
343+
// Read and process the first sub-element, ignoring everything else
342344
while (ota_header_len_ < 6 && payload_size > 0)
343345
{
344346
ota_header_[ota_header_len_] = payload[0];
@@ -401,6 +403,7 @@ static esp_err_t zb_ota_upgrade_status_handler(esp_zb_zcl_ota_upgrade_value_mess
401403
}
402404
return ret;
403405
}
406+
*/
404407

405408
static esp_err_t zb_ota_upgrade_query_image_resp_handler(esp_zb_zcl_ota_upgrade_query_image_resp_message_t message)
406409
{
@@ -422,6 +425,7 @@ static esp_err_t zb_ota_upgrade_query_image_resp_handler(esp_zb_zcl_ota_upgrade_
422425
return ret;
423426
}
424427

428+
425429
static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message)
426430
{
427431
esp_err_t ret = ESP_OK;

main/zigbee.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ extern "C"
5757

5858
static esp_err_t zb_attribute_handler(const esp_zb_zcl_set_attr_value_message_t *message);
5959
static esp_err_t zb_read_attr_resp_handler(const esp_zb_zcl_cmd_read_attr_resp_message_t *message);
60-
static esp_err_t zb_ota_upgrade_status_handler(esp_zb_zcl_ota_upgrade_value_message_t message);
60+
//static esp_err_t zb_ota_upgrade_status_handler(esp_zb_zcl_ota_upgrade_value_message_t message);
6161
static esp_err_t zb_ota_upgrade_query_image_resp_handler(esp_zb_zcl_ota_upgrade_query_image_resp_message_t message);
6262
static esp_err_t zb_action_handler(esp_zb_core_action_callback_id_t callback_id, const void *message);
6363

tools/create-ota.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ def create(filename, manufacturer_id, image_type, file_version, header_string):
2626
with open(filename, "rb") as f:
2727
data = f.read()
2828

29-
#zobj = zlib.compressobj(level=zlib.Z_BEST_COMPRESSION)
30-
#zdata = zobj.compress(data)
31-
#zdata += zobj.flush()
32-
zdata = data
29+
zobj = zlib.compressobj(level=zlib.Z_BEST_COMPRESSION)
30+
zdata = zobj.compress(data)
31+
zdata += zobj.flush()
32+
#zdata = data
3333

3434
image = zigpy.ota.image.OTAImage(
3535
header=zigpy.ota.image.OTAImageHeader(

0 commit comments

Comments
 (0)