added ota and more led strips
This commit is contained in:
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -37,3 +37,6 @@
|
|||||||
[submodule "components/esp-nimble-cpp"]
|
[submodule "components/esp-nimble-cpp"]
|
||||||
path = components/esp-nimble-cpp
|
path = components/esp-nimble-cpp
|
||||||
url = git@github.com:0xFEEDC0DE64/esp-nimble-cpp.git
|
url = git@github.com:0xFEEDC0DE64/esp-nimble-cpp.git
|
||||||
|
[submodule "components/espasyncota"]
|
||||||
|
path = components/espasyncota
|
||||||
|
url = git@github.com:0xFEEDC0DE64/espasyncota.git
|
||||||
|
1
components/espasyncota
Submodule
1
components/espasyncota
Submodule
Submodule components/espasyncota added at b76c6c7bf2
@ -1,6 +1,7 @@
|
|||||||
set(headers
|
set(headers
|
||||||
ble.h
|
ble.h
|
||||||
led.h
|
led.h
|
||||||
|
ota.h
|
||||||
webserver.h
|
webserver.h
|
||||||
wifi.h
|
wifi.h
|
||||||
)
|
)
|
||||||
@ -9,13 +10,15 @@ set(sources
|
|||||||
ble.cpp
|
ble.cpp
|
||||||
led.cpp
|
led.cpp
|
||||||
main.cpp
|
main.cpp
|
||||||
|
ota.cpp
|
||||||
webserver.cpp
|
webserver.cpp
|
||||||
wifi.cpp
|
wifi.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
set(dependencies
|
set(dependencies
|
||||||
freertos nvs_flash esp_http_server esp_https_ota mdns app_update esp_system esp_websocket_client driver
|
freertos nvs_flash esp_http_server esp_https_ota mdns app_update esp_system esp_websocket_client driver
|
||||||
arduino-esp32 ArduinoJson FastLED-idf cpputils date espchrono espcpputils esp-nimble-cpp esphttpdutils espwifistack expected fmt
|
arduino-esp32 ArduinoJson FastLED-idf cpputils date espchrono espasyncota espcpputils esp-nimble-cpp
|
||||||
|
esphttpdutils espwifistack expected fmt
|
||||||
)
|
)
|
||||||
|
|
||||||
idf_component_register(
|
idf_component_register(
|
||||||
|
165
main/led.cpp
165
main/led.cpp
@ -13,9 +13,8 @@
|
|||||||
#include <FastLED.h>
|
#include <FastLED.h>
|
||||||
#include <espchrono.h>
|
#include <espchrono.h>
|
||||||
|
|
||||||
#define LEDC_CHANNEL_0 0
|
using namespace std::chrono_literals;
|
||||||
#define LEDC_CHANNEL_1 0
|
|
||||||
#define LEDC_CHANNEL_2 0
|
|
||||||
#define LEDC_TIMER_13_BIT 13
|
#define LEDC_TIMER_13_BIT 13
|
||||||
#define LEDC_BASE_FREQ 5000
|
#define LEDC_BASE_FREQ 5000
|
||||||
|
|
||||||
@ -25,10 +24,113 @@ std::array<CRGB, 5> leds;
|
|||||||
CRGBPalette16 currentPalette;
|
CRGBPalette16 currentPalette;
|
||||||
TBlendType currentBlending;
|
TBlendType currentBlending;
|
||||||
|
|
||||||
extern CRGBPalette16 myRedWhiteBluePalette;
|
const TProgmemPalette16 myRedWhiteBluePalette_p =
|
||||||
extern const TProgmemPalette16 myRedWhiteBluePalette_p;
|
{
|
||||||
|
CRGB::Red,
|
||||||
|
CRGB::Gray, // 'white' is too bright compared to red and blue
|
||||||
|
CRGB::Blue,
|
||||||
|
CRGB::Black,
|
||||||
|
|
||||||
|
CRGB::Red,
|
||||||
|
CRGB::Gray,
|
||||||
|
CRGB::Blue,
|
||||||
|
CRGB::Black,
|
||||||
|
|
||||||
|
CRGB::Red,
|
||||||
|
CRGB::Red,
|
||||||
|
CRGB::Gray,
|
||||||
|
CRGB::Gray,
|
||||||
|
CRGB::Blue,
|
||||||
|
CRGB::Blue,
|
||||||
|
CRGB::Black,
|
||||||
|
CRGB::Black
|
||||||
|
};
|
||||||
|
|
||||||
void ledcAnalogWrite(uint8_t channel, uint32_t value, uint32_t valueMax = 255);
|
void ledcAnalogWrite(uint8_t channel, uint32_t value, uint32_t valueMax = 255);
|
||||||
|
void ChangePalettePeriodically();
|
||||||
|
void FillLEDsFromPaletteColors( uint8_t colorIndex);
|
||||||
|
|
||||||
|
espchrono::millis_clock::time_point lastRedraw{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_setup()
|
||||||
|
{
|
||||||
|
currentPalette = RainbowColors_p;
|
||||||
|
currentBlending = LINEARBLEND;
|
||||||
|
|
||||||
|
ledcSetup(0, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
ledcSetup(1, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
ledcSetup(2, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
|
||||||
|
ledcSetup(3, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
ledcSetup(4, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
ledcSetup(5, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
|
||||||
|
ledcSetup(6, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
ledcSetup(7, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
ledcSetup(8, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
|
||||||
|
ledcSetup(9, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
ledcSetup(10, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
ledcSetup(11, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
|
||||||
|
ledcSetup(12, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
ledcSetup(13, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
ledcSetup(14, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
||||||
|
|
||||||
|
ledcAttachPin(27, 0);
|
||||||
|
ledcAttachPin(33, 1);
|
||||||
|
ledcAttachPin(32, 2);
|
||||||
|
|
||||||
|
ledcAttachPin(23, 3);
|
||||||
|
ledcAttachPin(25, 4);
|
||||||
|
ledcAttachPin(26, 5);
|
||||||
|
|
||||||
|
ledcAttachPin(19, 6);
|
||||||
|
ledcAttachPin(21, 7);
|
||||||
|
ledcAttachPin(22, 8);
|
||||||
|
|
||||||
|
ledcAttachPin(18, 9);
|
||||||
|
ledcAttachPin(17, 10);
|
||||||
|
ledcAttachPin(16, 11);
|
||||||
|
|
||||||
|
ledcAttachPin(13, 12);
|
||||||
|
ledcAttachPin(14, 13);
|
||||||
|
ledcAttachPin(15, 14);
|
||||||
|
}
|
||||||
|
|
||||||
|
void led_update()
|
||||||
|
{
|
||||||
|
if (espchrono::ago(lastRedraw) < 20ms)
|
||||||
|
return;
|
||||||
|
lastRedraw = espchrono::millis_clock::now();
|
||||||
|
|
||||||
|
ChangePalettePeriodically();
|
||||||
|
|
||||||
|
static uint8_t startIndex = 0;
|
||||||
|
startIndex = startIndex + 1; /* motion speed */
|
||||||
|
|
||||||
|
FillLEDsFromPaletteColors(startIndex);
|
||||||
|
|
||||||
|
ledcAnalogWrite(0, leds[0].red);
|
||||||
|
ledcAnalogWrite(1, leds[0].green);
|
||||||
|
ledcAnalogWrite(2, leds[0].blue);
|
||||||
|
|
||||||
|
ledcAnalogWrite(3, leds[1].red);
|
||||||
|
ledcAnalogWrite(4, leds[1].green);
|
||||||
|
ledcAnalogWrite(5, leds[1].blue);
|
||||||
|
|
||||||
|
ledcAnalogWrite(6, leds[2].red);
|
||||||
|
ledcAnalogWrite(7, leds[2].green);
|
||||||
|
ledcAnalogWrite(8, leds[2].blue);
|
||||||
|
|
||||||
|
ledcAnalogWrite(9, leds[3].red);
|
||||||
|
ledcAnalogWrite(10, leds[3].green);
|
||||||
|
ledcAnalogWrite(11, leds[3].blue);
|
||||||
|
|
||||||
|
ledcAnalogWrite(12, leds[4].red);
|
||||||
|
ledcAnalogWrite(13, leds[4].green);
|
||||||
|
ledcAnalogWrite(14, leds[4].blue);
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@ -83,29 +185,6 @@ void SetupPurpleAndGreenPalette()
|
|||||||
purple, purple, black, black );
|
purple, purple, black, black );
|
||||||
}
|
}
|
||||||
|
|
||||||
const TProgmemPalette16 myRedWhiteBluePalette_p =
|
|
||||||
{
|
|
||||||
CRGB::Red,
|
|
||||||
CRGB::Gray, // 'white' is too bright compared to red and blue
|
|
||||||
CRGB::Blue,
|
|
||||||
CRGB::Black,
|
|
||||||
|
|
||||||
CRGB::Red,
|
|
||||||
CRGB::Gray,
|
|
||||||
CRGB::Blue,
|
|
||||||
CRGB::Black,
|
|
||||||
|
|
||||||
CRGB::Red,
|
|
||||||
CRGB::Red,
|
|
||||||
CRGB::Gray,
|
|
||||||
CRGB::Gray,
|
|
||||||
CRGB::Blue,
|
|
||||||
CRGB::Blue,
|
|
||||||
CRGB::Black,
|
|
||||||
CRGB::Black
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
void ChangePalettePeriodically()
|
void ChangePalettePeriodically()
|
||||||
{
|
{
|
||||||
constexpr auto millis = []() -> unsigned int { return espchrono::millis_clock::now().time_since_epoch().count(); };
|
constexpr auto millis = []() -> unsigned int { return espchrono::millis_clock::now().time_since_epoch().count(); };
|
||||||
@ -128,32 +207,4 @@ void ChangePalettePeriodically()
|
|||||||
if( secondHand == 55) { currentPalette = myRedWhiteBluePalette_p; currentBlending = LINEARBLEND; }
|
if( secondHand == 55) { currentPalette = myRedWhiteBluePalette_p; currentBlending = LINEARBLEND; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
} // namespace
|
||||||
|
|
||||||
|
|
||||||
void led_setup()
|
|
||||||
{
|
|
||||||
currentPalette = RainbowColors_p;
|
|
||||||
currentBlending = LINEARBLEND;
|
|
||||||
|
|
||||||
ledcSetup(LEDC_CHANNEL_0, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
|
||||||
ledcSetup(LEDC_CHANNEL_1, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
|
||||||
ledcSetup(LEDC_CHANNEL_2, LEDC_BASE_FREQ, LEDC_TIMER_13_BIT);
|
|
||||||
ledcAttachPin(27, LEDC_CHANNEL_0);
|
|
||||||
ledcAttachPin(33, LEDC_CHANNEL_1);
|
|
||||||
ledcAttachPin(32, LEDC_CHANNEL_2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void led_update()
|
|
||||||
{
|
|
||||||
ChangePalettePeriodically();
|
|
||||||
|
|
||||||
static uint8_t startIndex = 0;
|
|
||||||
startIndex = startIndex + 1; /* motion speed */
|
|
||||||
|
|
||||||
FillLEDsFromPaletteColors( startIndex);
|
|
||||||
|
|
||||||
ledcAnalogWrite(LEDC_CHANNEL_0, leds[0].red);
|
|
||||||
ledcAnalogWrite(LEDC_CHANNEL_1, leds[0].green);
|
|
||||||
ledcAnalogWrite(LEDC_CHANNEL_2, leds[0].blue);
|
|
||||||
}
|
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
#include "wifi.h"
|
#include "wifi.h"
|
||||||
#include "ble.h"
|
#include "ble.h"
|
||||||
#include "webserver.h"
|
#include "webserver.h"
|
||||||
|
#include "ota.h"
|
||||||
#include "led.h"
|
#include "led.h"
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
@ -65,6 +66,7 @@ extern "C" void app_main()
|
|||||||
wifi_setup();
|
wifi_setup();
|
||||||
ble_setup();
|
ble_setup();
|
||||||
webserver_setup();
|
webserver_setup();
|
||||||
|
ota_setup();
|
||||||
led_setup();
|
led_setup();
|
||||||
|
|
||||||
while (true)
|
while (true)
|
||||||
@ -77,6 +79,7 @@ extern "C" void app_main()
|
|||||||
wifi_update();
|
wifi_update();
|
||||||
ble_update();
|
ble_update();
|
||||||
webserver_update();
|
webserver_update();
|
||||||
|
ota_update();
|
||||||
led_update();
|
led_update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
50
main/ota.cpp
Normal file
50
main/ota.cpp
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
#include "ota.h"
|
||||||
|
|
||||||
|
// esp-idf includes
|
||||||
|
#include <esp_log.h>
|
||||||
|
|
||||||
|
// 3rdparty lib includes
|
||||||
|
#include <delayedconstruction.h>
|
||||||
|
#include <espasyncota.h>
|
||||||
|
#include <espwifistack.h>
|
||||||
|
|
||||||
|
// local includes
|
||||||
|
#include "wifi.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
constexpr const char * const TAG = "OTA";
|
||||||
|
|
||||||
|
cpputils::DelayedConstruction<EspAsyncOta> _asyncOta;
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
EspAsyncOta &asyncOta{_asyncOta.getUnsafe()};
|
||||||
|
|
||||||
|
void ota_setup()
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "called");
|
||||||
|
|
||||||
|
_asyncOta.construct();
|
||||||
|
|
||||||
|
if (const auto result = _asyncOta->startTask(); !result)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "starting OTA task failed: %.*s", result.error().size(), result.error().data());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ota_update()
|
||||||
|
{
|
||||||
|
_asyncOta->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
tl::expected<void, std::string> triggerOta(std::string_view url)
|
||||||
|
{
|
||||||
|
ESP_LOGI(TAG, "%.*s", url.size(), url.data());
|
||||||
|
|
||||||
|
if (const auto result = _asyncOta->trigger(url, {}, {}, {}); !result)
|
||||||
|
return tl::make_unexpected(std::move(result).error());
|
||||||
|
|
||||||
|
wifi_stack::delete_scan_result();
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
18
main/ota.h
Normal file
18
main/ota.h
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// system includes
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
|
// 3rdparty lib includes
|
||||||
|
#include <tl/expected.hpp>
|
||||||
|
|
||||||
|
// forward declares
|
||||||
|
class EspAsyncOta;
|
||||||
|
|
||||||
|
extern EspAsyncOta &asyncOta;
|
||||||
|
|
||||||
|
void ota_setup();
|
||||||
|
void ota_update();
|
||||||
|
|
||||||
|
tl::expected<void, std::string> triggerOta(std::string_view url);
|
@ -7,8 +7,12 @@
|
|||||||
// 3rdparty lib includes
|
// 3rdparty lib includes
|
||||||
#include <esphttpdutils.h>
|
#include <esphttpdutils.h>
|
||||||
#include <espcppmacros.h>
|
#include <espcppmacros.h>
|
||||||
|
#include <fmt/core.h>
|
||||||
|
#include <espasyncota.h>
|
||||||
|
|
||||||
// local includes
|
// local includes
|
||||||
|
#include "wifi.h"
|
||||||
|
#include "ota.h"
|
||||||
|
|
||||||
using namespace esphttpdutils;
|
using namespace esphttpdutils;
|
||||||
|
|
||||||
@ -18,6 +22,7 @@ constexpr const char * const TAG = "WEBSERVER";
|
|||||||
httpd_handle_t httpdHandle;
|
httpd_handle_t httpdHandle;
|
||||||
|
|
||||||
esp_err_t webserver_root_handler(httpd_req_t *req);
|
esp_err_t webserver_root_handler(httpd_req_t *req);
|
||||||
|
esp_err_t webserver_ota_handler(httpd_req_t *req);
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
void webserver_setup()
|
void webserver_setup()
|
||||||
@ -34,7 +39,8 @@ void webserver_setup()
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (const httpd_uri_t &uri : {
|
for (const httpd_uri_t &uri : {
|
||||||
httpd_uri_t { .uri = "/", .method = HTTP_GET, .handler = webserver_root_handler, .user_ctx = NULL },
|
httpd_uri_t { .uri = "/", .method = HTTP_GET, .handler = webserver_root_handler, .user_ctx = NULL },
|
||||||
|
httpd_uri_t { .uri = "/ota", .method = HTTP_GET, .handler = webserver_ota_handler, .user_ctx = NULL },
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
const auto result = httpd_register_uri_handler(httpdHandle, &uri);
|
const auto result = httpd_register_uri_handler(httpdHandle, &uri);
|
||||||
@ -52,8 +58,78 @@ void webserver_update()
|
|||||||
namespace {
|
namespace {
|
||||||
esp_err_t webserver_root_handler(httpd_req_t *req)
|
esp_err_t webserver_root_handler(httpd_req_t *req)
|
||||||
{
|
{
|
||||||
std::string body = "hello world";
|
std::string body = fmt::format("<h1>{}</h1>", htmlentities(deviceName));
|
||||||
|
|
||||||
|
if (const auto otaStatus = asyncOta.status(); otaStatus == OtaCloudUpdateStatus::Idle)
|
||||||
|
{
|
||||||
|
body +=
|
||||||
|
"<form action=\"/ota\">"
|
||||||
|
"<label>ota url: <input type=\"url\" name=\"url\" required /></label>"
|
||||||
|
"<button type=\"submit\">Start</button>"
|
||||||
|
"</form>";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const auto progress = asyncOta.progress();
|
||||||
|
const char *color;
|
||||||
|
switch (otaStatus)
|
||||||
|
{
|
||||||
|
case OtaCloudUpdateStatus::Updating: color = "#b8b800"; break;
|
||||||
|
case OtaCloudUpdateStatus::Succeeded: color = "#13c200"; break;
|
||||||
|
default: color = "#b80000";
|
||||||
|
}
|
||||||
|
|
||||||
|
body += fmt::format("<p style=\"color: {};\">OTA: status={} progress={}", color, esphttpdutils::htmlentities(toString(otaStatus)), progress);
|
||||||
|
|
||||||
|
if (const auto totalSize = asyncOta.totalSize())
|
||||||
|
{
|
||||||
|
body += fmt::format(" totalSize={}", *totalSize);
|
||||||
|
if (*totalSize)
|
||||||
|
body += fmt::format(" percentage={:.1f}%", 100.f * progress / *totalSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto &message = asyncOta.message(); !message.empty())
|
||||||
|
body += fmt::format(" message={}", esphttpdutils::htmlentities(message));
|
||||||
|
|
||||||
|
body += "</p>\n";
|
||||||
|
}
|
||||||
|
|
||||||
CALL_AND_EXIT(webserver_resp_send_succ, req, "text/html", body)
|
CALL_AND_EXIT(webserver_resp_send_succ, req, "text/html", body)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
esp_err_t webserver_ota_handler(httpd_req_t *req)
|
||||||
|
{
|
||||||
|
std::string query;
|
||||||
|
|
||||||
|
if (const size_t queryLength = httpd_req_get_url_query_len(req))
|
||||||
|
{
|
||||||
|
query.resize(queryLength);
|
||||||
|
CALL_AND_EXIT_ON_ERROR(httpd_req_get_url_query_str, req, query.data(), query.size() + 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
char urlBufEncoded[256];
|
||||||
|
if (const auto result = httpd_query_key_value(query.data(), "url", urlBufEncoded, sizeof(urlBufEncoded)); result == ESP_ERR_NOT_FOUND)
|
||||||
|
{
|
||||||
|
CALL_AND_EXIT(webserver_resp_send_err, req, HTTPD_400_BAD_REQUEST, "text/plain", "url parameter missing")
|
||||||
|
}
|
||||||
|
else if (result != ESP_OK)
|
||||||
|
{
|
||||||
|
const auto msg = fmt::format("httpd_query_key_value() {} failed with {}", "url", esp_err_to_name(result));
|
||||||
|
ESP_LOGE(TAG, "%.*s", msg.size(), msg.data());
|
||||||
|
CALL_AND_EXIT(webserver_resp_send_err, req, HTTPD_400_BAD_REQUEST, "text/plain", msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
char urlBuf[257];
|
||||||
|
esphttpdutils::urldecode(urlBuf, urlBufEncoded);
|
||||||
|
|
||||||
|
std::string_view url{urlBuf};
|
||||||
|
|
||||||
|
if (const auto result = triggerOta(url); !result)
|
||||||
|
{
|
||||||
|
ESP_LOGE(TAG, "%.*s", result.error().size(), result.error().data());
|
||||||
|
CALL_AND_EXIT(webserver_resp_send_err, req, HTTPD_400_BAD_REQUEST, "text/plain", result.error())
|
||||||
|
}
|
||||||
|
|
||||||
|
CALL_AND_EXIT(webserver_resp_send_succ, req, "text/plain", "OTA called...")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
# Name, Type, SubType, Offset, Size, Flags
|
# Name, Type, SubType, Offset, Size, Flags
|
||||||
nvs, data, nvs, 0x10000, 0x5000,
|
nvs, data, nvs, 0x10000, 0x5000,
|
||||||
otadata, data, ota, 0x15000, 0x2000,
|
otadata, data, ota, 0x15000, 0x2000,
|
||||||
app0, app, ota_0, 0x20000, 0x1F0000,
|
app0, app, ota_0, 0x20000, 0x190000,
|
||||||
|
app1, app, ota_1, 0x220000, 0x190000,
|
||||||
|
|
Reference in New Issue
Block a user