mirror of
https://github.com/esphome/esphome.git
synced 2026-10-26 16:02:13 +00:00
Compare commits
19
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2df80fbf71 | ||
|
|
d2f6b389d4 | ||
|
|
470f7eb51d | ||
|
|
e7b2dfed57 | ||
|
|
875b2076c0 | ||
|
|
83080c4560 | ||
|
|
104cb09475 | ||
|
|
a7824ed4e7 | ||
|
|
6420db90b1 | ||
|
|
d3c2a7c734 | ||
|
|
c21660251c | ||
|
|
995acb94b4 | ||
|
|
24b7eefaad | ||
|
|
2349e0be00 | ||
|
|
63349801b7 | ||
|
|
afdfa7b298 | ||
|
|
400407b135 | ||
|
|
22b32b194a | ||
|
|
6dcd989a7f |
No files matched your search
@@ -31,11 +31,25 @@ static inline bool IRAM_ATTR is_code_addr(uint32_t addr) {
|
||||
}
|
||||
|
||||
#if CONFIG_IDF_TARGET_ARCH_RISCV
|
||||
// Bounds of this image's flash-mapped code, from the ESP-IDF linker script.
|
||||
extern "C" char _instruction_reserved_start;
|
||||
extern "C" char _instruction_reserved_end;
|
||||
|
||||
// Check that [start, end) lies in this image's flash-mapped code or in IRAM.
|
||||
// The IROM range is larger than what the MMU maps, and reading an unmapped
|
||||
// address faults.
|
||||
static inline bool is_readable_code(uint32_t start, uint32_t end) {
|
||||
auto irom_start = reinterpret_cast<uint32_t>(&_instruction_reserved_start);
|
||||
auto irom_end = reinterpret_cast<uint32_t>(&_instruction_reserved_end);
|
||||
return (start >= irom_start && end <= irom_end) || (start >= SOC_IRAM_LOW && end <= SOC_IRAM_HIGH);
|
||||
}
|
||||
|
||||
// Check if a code address is a real return address by verifying the preceding
|
||||
// instruction is a JAL or JALR with rd=ra (x1). Called at log time (not during
|
||||
// panic) so flash cache is available and both IRAM and IROM are safely readable.
|
||||
// panic) so flash cache is available. The address may come from an earlier
|
||||
// build, so check that the bytes before it are mapped before reading them.
|
||||
static inline bool is_return_addr(uint32_t addr) {
|
||||
if (!is_code_addr(addr) || addr < 4)
|
||||
if (!is_code_addr(addr) || !is_readable_code(addr - 4, addr))
|
||||
return false;
|
||||
// A return address on the stack points to the instruction after a call.
|
||||
// Check for 4-byte JAL/JALR call instruction before this address.
|
||||
|
||||
@@ -271,7 +271,9 @@ void ESP32Camera::loop() {
|
||||
fb->timestamp = timestamp;
|
||||
}
|
||||
#endif
|
||||
this->current_image_ = std::make_shared<ESP32CameraImage>(fb, this->single_requesters_ | this->stream_requesters_);
|
||||
// A request that arrives while this image is handed out is kept for the next frame
|
||||
const uint8_t single_requesters = this->single_requesters_.exchange(0);
|
||||
this->current_image_ = std::make_shared<ESP32CameraImage>(fb, single_requesters | this->stream_requesters_);
|
||||
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
ESP_LOGV(TAG, "Got Image: len=%u", fb->len);
|
||||
@@ -291,7 +293,6 @@ void ESP32Camera::loop() {
|
||||
listener->on_camera_image(this->current_image_);
|
||||
}
|
||||
this->last_update_ = now;
|
||||
this->single_requesters_ = 0;
|
||||
}
|
||||
|
||||
/* ---------------- constructors ---------------- */
|
||||
|
||||
@@ -373,7 +373,7 @@ void ESPHomeOTAComponent::handle_handshake_() {
|
||||
// Value initialized: a corrupt stream that back references the
|
||||
// window before it is filled then copies zeros, never stale memory.
|
||||
// Default placement, PSRAM first where present: the session lives for one
|
||||
// upload and keeps 4.9 KB of internal heap free while it runs
|
||||
// upload and keeps 6.0 KB of internal heap free while it runs
|
||||
this->inflate_ = RAMAllocator<InflateSession>().make_unique();
|
||||
if (this->inflate_ != nullptr) {
|
||||
this->handshake_buf_[1] |= SERVER_FEATURE_SUPPORTS_DEFLATE;
|
||||
|
||||
@@ -36,7 +36,10 @@
|
||||
* Altered for ESPHome: this is the raw deflate decoder from uzlib's
|
||||
* tinflate.c (v2.9.5) with the gzip/zlib header parsers, checksums,
|
||||
* runtime table builder and in-memory (non ring window) output path
|
||||
* removed, and the public names prefixed with ota_inflate.
|
||||
* removed, and the public names prefixed with ota_inflate. Symbols are
|
||||
* decoded from a 32 bit bit buffer through 9 bit lookup tables, with a
|
||||
* fast loop for when a whole symbol fits in both the input and the output,
|
||||
* and the output is written straight into the ring window.
|
||||
*/
|
||||
|
||||
#include "ota_esphome_inflate.h"
|
||||
@@ -49,16 +52,6 @@
|
||||
#define TINF_DICT_ERROR OTA_INFLATE_DICT_ERROR
|
||||
#define TINF_DATA struct OtaInflateState
|
||||
#define TINF_TREE struct OtaInflateTree
|
||||
#define TINF_ARRAY_SIZE(arr) (sizeof(arr) / sizeof(*(arr)))
|
||||
|
||||
/* every output byte also goes into the ring window */
|
||||
#define TINF_PUT(d, c) \
|
||||
{ \
|
||||
*d->dest++ = c; \
|
||||
d->dict_ring[d->dict_idx++] = c; \
|
||||
if (d->dict_idx == d->dict_size) \
|
||||
d->dict_idx = 0; \
|
||||
}
|
||||
|
||||
/* --------------------------------------------------- *
|
||||
* -- constant tables (upstream builds them at runtime) -- *
|
||||
@@ -82,34 +75,52 @@ static const unsigned char CLCIDX[] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4,
|
||||
* -- utility functions -- *
|
||||
* ----------------------- */
|
||||
|
||||
/* given an array of code lengths, build a tree */
|
||||
static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned int num) {
|
||||
unsigned short offs[16];
|
||||
unsigned int i, sum;
|
||||
enum {
|
||||
LIT_FAST_BITS = OTA_INFLATE_LIT_FAST_BITS,
|
||||
DIST_FAST_BITS = OTA_INFLATE_DIST_FAST_BITS,
|
||||
/* a lookup table entry is (code length << SYM_BITS) | symbol; 0 marks a longer code */
|
||||
SYM_BITS = 9,
|
||||
SYM_MASK = (1 << SYM_BITS) - 1,
|
||||
/* input bytes one pass of the fast loop may take: three refills of up to 4 bytes */
|
||||
FAST_IN_MIN = 12,
|
||||
};
|
||||
|
||||
/* given an array of code lengths, build a tree and its lookup table */
|
||||
static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned int num, unsigned int fast_bits) {
|
||||
const unsigned int fast_size = 1u << fast_bits;
|
||||
unsigned short offs[16], next[16];
|
||||
unsigned int i, sum, code;
|
||||
|
||||
/* clear code length count table */
|
||||
for (i = 0; i < 16; ++i)
|
||||
t->table[i] = 0;
|
||||
|
||||
/* scan symbol lengths, and sum code length counts */
|
||||
for (i = 0; i < num; ++i)
|
||||
t->table[lengths[i]]++;
|
||||
|
||||
/* In the lengths array, 0 means unused code. So, t->table[0] now contains
|
||||
number of unused codes. But table's purpose is to contain # of codes of
|
||||
particular length, and there're 0 codes of length 0. */
|
||||
t->table[0] = 0;
|
||||
|
||||
/* compute offset table for distribution sort */
|
||||
for (sum = 0, i = 0; i < 16; ++i) {
|
||||
for (sum = 0, code = 0, i = 0; i < 16; ++i) {
|
||||
offs[i] = sum;
|
||||
sum += t->table[i];
|
||||
if (i > 0)
|
||||
code = (code + t->table[i - 1]) << 1;
|
||||
next[i] = code;
|
||||
}
|
||||
|
||||
/* create code->symbol translation table (symbols sorted by code) */
|
||||
for (i = 0; i < fast_size; ++i)
|
||||
t->fast[i] = 0;
|
||||
|
||||
for (i = 0; i < num; ++i) {
|
||||
if (lengths[i])
|
||||
t->trans[offs[lengths[i]]++] = i;
|
||||
unsigned int len = lengths[i];
|
||||
if (!len)
|
||||
continue;
|
||||
t->trans[offs[len]++] = i;
|
||||
if (len <= fast_bits) {
|
||||
/* codes arrive MSB first, the bit buffer is LSB first */
|
||||
unsigned int c = next[len]++, rev = 0, j;
|
||||
for (j = 0; j < len; ++j, c >>= 1)
|
||||
rev = (rev << 1) | (c & 1);
|
||||
for (j = rev; j < fast_size; j += 1u << len)
|
||||
t->fast[j] = (unsigned short) ((len << SYM_BITS) | i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,89 +128,78 @@ static void tinf_build_tree(TINF_TREE *t, const unsigned char *lengths, unsigned
|
||||
* -- decode functions -- *
|
||||
* ---------------------- */
|
||||
|
||||
static unsigned char uzlib_get_byte(TINF_DATA *d) {
|
||||
/* If end of source buffer is not reached, return next byte from source
|
||||
buffer. */
|
||||
if (d->source < d->source_limit) {
|
||||
return *d->source++;
|
||||
/* top the bit buffer up from the input buffer, without blocking for more */
|
||||
static inline void tinf_refill(TINF_DATA *d) {
|
||||
while (d->bitcount <= 24 && d->source < d->source_limit) {
|
||||
d->tag |= (unsigned int) *d->source++ << d->bitcount;
|
||||
d->bitcount += 8;
|
||||
}
|
||||
|
||||
/* Otherwise if there's callback and we haven't seen EOF yet, try to
|
||||
read next byte using it. (Note: the callback can also update ->source
|
||||
and ->source_limit). */
|
||||
if (!d->eof) {
|
||||
int val = d->source_read_cb(d);
|
||||
if (val >= 0) {
|
||||
return (unsigned char) val;
|
||||
}
|
||||
}
|
||||
|
||||
/* Otherwise, we hit EOF (either from ->source_read_cb() or from exhaustion
|
||||
of the buffer), and it will be "sticky", i.e. further calls to this
|
||||
function will end up here too. */
|
||||
d->eof = true;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* get one bit from source stream */
|
||||
static int tinf_getbit(TINF_DATA *d) {
|
||||
unsigned int bit;
|
||||
|
||||
/* check if tag is empty */
|
||||
if (!d->bitcount--) {
|
||||
/* load next tag */
|
||||
d->tag = uzlib_get_byte(d);
|
||||
d->bitcount = 7;
|
||||
/* make at least num (<= 16) bits available; past the end of input reads zeros and sets eof */
|
||||
static inline void tinf_need(TINF_DATA *d, unsigned int num) {
|
||||
while (d->bitcount < num) {
|
||||
int val = -1;
|
||||
if (d->source < d->source_limit) {
|
||||
val = *d->source++;
|
||||
} else if (!d->eof) {
|
||||
val = d->source_read_cb(d);
|
||||
}
|
||||
if (val < 0) {
|
||||
d->eof = true;
|
||||
val = 0;
|
||||
}
|
||||
d->tag |= (unsigned int) val << d->bitcount;
|
||||
d->bitcount += 8;
|
||||
}
|
||||
}
|
||||
|
||||
/* shift bit out of tag */
|
||||
bit = d->tag & 0x01;
|
||||
d->tag >>= 1;
|
||||
|
||||
return bit;
|
||||
static inline unsigned int tinf_take(TINF_DATA *d, unsigned int num) {
|
||||
unsigned int val = d->tag & ((1u << num) - 1);
|
||||
d->tag >>= num;
|
||||
d->bitcount -= num;
|
||||
return val;
|
||||
}
|
||||
|
||||
/* read a num bit value from a stream and add base */
|
||||
static unsigned int tinf_read_bits(TINF_DATA *d, int num, int base) {
|
||||
unsigned int val = 0;
|
||||
|
||||
/* read num bits */
|
||||
if (num) {
|
||||
unsigned int limit = 1 << (num);
|
||||
unsigned int mask;
|
||||
|
||||
for (mask = 1; mask < limit; mask *= 2)
|
||||
if (tinf_getbit(d))
|
||||
val += mask;
|
||||
}
|
||||
|
||||
return val + base;
|
||||
static inline unsigned int tinf_read_bits(TINF_DATA *d, unsigned int num, unsigned int base) {
|
||||
tinf_need(d, num);
|
||||
return tinf_take(d, num) + base;
|
||||
}
|
||||
|
||||
/* given a data stream and a tree, decode a symbol */
|
||||
static int tinf_decode_symbol(TINF_DATA *d, TINF_TREE *t) {
|
||||
int sum = 0, cur = 0, len = 0;
|
||||
|
||||
/* get more bits while code value is above sum */
|
||||
do {
|
||||
cur = 2 * cur + tinf_getbit(d);
|
||||
|
||||
if (++len == TINF_ARRAY_SIZE(t->table)) {
|
||||
return TINF_DATA_ERROR;
|
||||
/* canonical decode of the code at the bottom of tag; len gets the code length.
|
||||
The trans index stays below the tree's symbol count, which trans always holds */
|
||||
static int tinf_decode_long(const TINF_TREE *t, unsigned int tag, unsigned int *len) {
|
||||
unsigned int code = 0, first = 0, index = 0, l;
|
||||
for (l = 1; l < 16; ++l, tag >>= 1) {
|
||||
unsigned int count = t->table[l];
|
||||
code |= tag & 1;
|
||||
if (code - first < count) {
|
||||
*len = l;
|
||||
return t->trans[index + code - first];
|
||||
}
|
||||
|
||||
sum += t->table[len];
|
||||
cur -= t->table[len];
|
||||
|
||||
} while (cur >= 0);
|
||||
|
||||
sum += cur;
|
||||
if (sum < 0 || sum >= t->size) {
|
||||
return TINF_DATA_ERROR;
|
||||
index += count;
|
||||
first = (first + count) << 1;
|
||||
code <<= 1;
|
||||
}
|
||||
return TINF_DATA_ERROR;
|
||||
}
|
||||
|
||||
return t->trans[sum];
|
||||
/* decode a symbol, reading more input only while the code is not all in the bit
|
||||
buffer; bits above bitcount are zero, so a code no longer than bitcount is real */
|
||||
static int tinf_decode_symbol(TINF_DATA *d, const TINF_TREE *t) {
|
||||
tinf_refill(d);
|
||||
for (;;) {
|
||||
unsigned int len;
|
||||
int sym = tinf_decode_long(t, d->tag, &len);
|
||||
if (sym >= 0 && len <= d->bitcount) {
|
||||
tinf_take(d, len);
|
||||
return sym;
|
||||
}
|
||||
if (d->bitcount >= 15)
|
||||
return TINF_DATA_ERROR;
|
||||
tinf_need(d, d->bitcount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
/* given a data stream, decode dynamic trees from it */
|
||||
@@ -230,7 +230,7 @@ static int tinf_decode_trees(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) {
|
||||
}
|
||||
|
||||
/* build code length tree, temporarily use length tree */
|
||||
tinf_build_tree(lt, lengths, 19);
|
||||
tinf_build_tree(lt, lengths, 19, LIT_FAST_BITS);
|
||||
|
||||
/* decode code lengths for the dynamic trees */
|
||||
hlimit = hlit + hdist;
|
||||
@@ -282,8 +282,8 @@ static int tinf_decode_trees(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) {
|
||||
}
|
||||
|
||||
/* build dynamic trees */
|
||||
tinf_build_tree(lt, lengths, hlit);
|
||||
tinf_build_tree(dt, lengths + hlit, hdist);
|
||||
tinf_build_tree(lt, lengths, hlit, LIT_FAST_BITS);
|
||||
tinf_build_tree(dt, lengths + hlit, hdist, DIST_FAST_BITS);
|
||||
|
||||
return TINF_OK;
|
||||
}
|
||||
@@ -302,84 +302,189 @@ static void tinf_build_fixed_trees(TINF_TREE *lt, TINF_TREE *dt) {
|
||||
lengths[i] = 7;
|
||||
for (; i < 288; ++i)
|
||||
lengths[i] = 8;
|
||||
tinf_build_tree(lt, lengths, 288);
|
||||
tinf_build_tree(lt, lengths, 288, LIT_FAST_BITS);
|
||||
|
||||
for (i = 0; i < 32; ++i)
|
||||
lengths[i] = 5;
|
||||
tinf_build_tree(dt, lengths, 32);
|
||||
tinf_build_tree(dt, lengths, 32, DIST_FAST_BITS);
|
||||
}
|
||||
|
||||
/* inflate with the bit buffer and both cursors in locals while the input holds a
|
||||
whole symbol; a match that runs past the end of the ring is left in curlen */
|
||||
static int tinf_inflate_fast(TINF_DATA *d, const TINF_TREE *lt, const TINF_TREE *dt) {
|
||||
const unsigned char *src = d->source;
|
||||
const unsigned char *const src_end = d->source_limit;
|
||||
unsigned char *const ring = d->dict_ring;
|
||||
unsigned char *const ring_end = ring + d->dict_size;
|
||||
unsigned char *out = d->dest;
|
||||
const uint16_t *const lfast = lt->fast;
|
||||
const uint16_t *const dfast = dt->fast;
|
||||
unsigned int tag = d->tag, bits = d->bitcount;
|
||||
int res = TINF_OK;
|
||||
|
||||
#define REFILL() \
|
||||
while (bits <= 24) { \
|
||||
tag |= (unsigned int) *src++ << bits; \
|
||||
bits += 8; \
|
||||
}
|
||||
#define DROP(n) (tag >>= (n), bits -= (n))
|
||||
|
||||
while (src_end - src >= FAST_IN_MIN && out < ring_end) {
|
||||
unsigned int e, len;
|
||||
int sym;
|
||||
|
||||
REFILL();
|
||||
e = lfast[tag & ((1u << LIT_FAST_BITS) - 1)];
|
||||
if (e) {
|
||||
len = e >> SYM_BITS;
|
||||
sym = e & SYM_MASK;
|
||||
} else {
|
||||
sym = tinf_decode_long(lt, tag, &len);
|
||||
if (sym < 0) {
|
||||
res = sym;
|
||||
break;
|
||||
}
|
||||
}
|
||||
DROP(len);
|
||||
|
||||
if (sym < 256) {
|
||||
*out++ = sym;
|
||||
continue;
|
||||
}
|
||||
if (sym == 256) {
|
||||
res = TINF_DONE;
|
||||
break;
|
||||
}
|
||||
sym -= 257;
|
||||
if (sym >= 29) {
|
||||
res = TINF_DATA_ERROR;
|
||||
break;
|
||||
}
|
||||
/* the lit/len code and its extra bits fit the refilled buffer together */
|
||||
unsigned int length = LENGTH_BASE[sym] + (tag & ((1u << LENGTH_BITS[sym]) - 1));
|
||||
DROP(LENGTH_BITS[sym]);
|
||||
|
||||
REFILL();
|
||||
e = dfast[tag & ((1u << DIST_FAST_BITS) - 1)];
|
||||
if (e) {
|
||||
len = e >> SYM_BITS;
|
||||
sym = e & SYM_MASK;
|
||||
} else {
|
||||
sym = tinf_decode_long(dt, tag, &len);
|
||||
}
|
||||
if (sym < 0 || sym >= 30) {
|
||||
res = TINF_DATA_ERROR;
|
||||
break;
|
||||
}
|
||||
DROP(len);
|
||||
REFILL();
|
||||
unsigned int offs = DIST_BASE[sym] + (tag & ((1u << DIST_BITS[sym]) - 1));
|
||||
DROP(DIST_BITS[sym]);
|
||||
if (offs > d->dict_size) {
|
||||
res = TINF_DICT_ERROR;
|
||||
break;
|
||||
}
|
||||
|
||||
/* wrap as an index: a pointer before the ring would be undefined */
|
||||
unsigned int at = out - ring;
|
||||
const unsigned char *from = ring + (at >= offs ? at - offs : at + d->dict_size - offs);
|
||||
unsigned int n = ring_end - out;
|
||||
if (n > length)
|
||||
n = length;
|
||||
length -= n;
|
||||
/* at most two runs: up to the end of the ring, then on from its start */
|
||||
while (n) {
|
||||
unsigned int run = ring_end - from;
|
||||
if (run > n)
|
||||
run = n;
|
||||
n -= run;
|
||||
do {
|
||||
*out++ = *from++;
|
||||
} while (--run);
|
||||
if (from == ring_end)
|
||||
from = ring;
|
||||
}
|
||||
if (length) {
|
||||
d->curlen = length;
|
||||
d->lz_off = from - ring;
|
||||
}
|
||||
}
|
||||
#undef REFILL
|
||||
#undef DROP
|
||||
|
||||
d->source = src;
|
||||
d->dest = out;
|
||||
d->tag = tag;
|
||||
d->bitcount = bits;
|
||||
return res;
|
||||
}
|
||||
|
||||
/* ----------------------------- *
|
||||
* -- block inflate functions -- *
|
||||
* ----------------------------- */
|
||||
|
||||
/* given a stream and two trees, inflate next chunk of output (a byte or more) */
|
||||
/* given a stream and two trees, inflate until the ring is full or the block ends */
|
||||
static int tinf_inflate_block_data(TINF_DATA *d, TINF_TREE *lt, TINF_TREE *dt) {
|
||||
if (d->curlen == 0) {
|
||||
unsigned char *const ring_end = d->dict_ring + d->dict_size;
|
||||
while (d->dest < ring_end) {
|
||||
if (d->curlen != 0) {
|
||||
unsigned int n = ring_end - d->dest;
|
||||
if (n > d->curlen)
|
||||
n = d->curlen;
|
||||
d->curlen -= n;
|
||||
do {
|
||||
*d->dest++ = d->dict_ring[d->lz_off];
|
||||
if ((unsigned) ++d->lz_off == d->dict_size)
|
||||
d->lz_off = 0;
|
||||
} while (--n);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (d->source_limit - d->source >= FAST_IN_MIN) {
|
||||
int res = tinf_inflate_fast(d, lt, dt);
|
||||
if (res != TINF_OK)
|
||||
return res;
|
||||
continue;
|
||||
}
|
||||
|
||||
unsigned int offs;
|
||||
int dist;
|
||||
int sym = tinf_decode_symbol(d, lt);
|
||||
|
||||
if (d->eof) {
|
||||
if (d->eof)
|
||||
return TINF_DATA_ERROR;
|
||||
}
|
||||
|
||||
if (sym < 0) {
|
||||
if (sym < 0)
|
||||
return sym;
|
||||
}
|
||||
|
||||
/* literal byte */
|
||||
if (sym < 256) {
|
||||
TINF_PUT(d, sym);
|
||||
return TINF_OK;
|
||||
*d->dest++ = sym;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* end of block */
|
||||
if (sym == 256) {
|
||||
if (sym == 256)
|
||||
return TINF_DONE;
|
||||
}
|
||||
|
||||
/* substring from sliding dictionary */
|
||||
sym -= 257;
|
||||
if (sym >= 29) {
|
||||
if (sym >= 29)
|
||||
return TINF_DATA_ERROR;
|
||||
}
|
||||
|
||||
/* possibly get more bits from length code */
|
||||
d->curlen = tinf_read_bits(d, LENGTH_BITS[sym], LENGTH_BASE[sym]);
|
||||
|
||||
dist = tinf_decode_symbol(d, dt);
|
||||
if (dist < 0 || dist >= 30) {
|
||||
if (dist < 0 || dist >= 30)
|
||||
return TINF_DATA_ERROR;
|
||||
}
|
||||
|
||||
/* possibly get more bits from distance code */
|
||||
offs = tinf_read_bits(d, DIST_BITS[dist], DIST_BASE[dist]);
|
||||
|
||||
/* calculate and validate actual LZ offset to use */
|
||||
if (offs > d->dict_size) {
|
||||
if (offs > d->dict_size)
|
||||
return TINF_DICT_ERROR;
|
||||
}
|
||||
/* Note: we don't try to catch offset which points to not yet filled
|
||||
part of the dictionary here. Doing so would require keeping another
|
||||
variable to track "filled in" size of the dictionary. Appearance of
|
||||
such an offset cannot lead to accessing memory outside of the
|
||||
dictionary buffer, and clients which don't want to leak unrelated
|
||||
information, should explicitly initialize dictionary buffer passed
|
||||
to uzlib. */
|
||||
|
||||
d->lz_off = d->dict_idx - offs;
|
||||
if (d->lz_off < 0) {
|
||||
d->lz_off = (int) (d->dest - d->dict_ring) - (int) offs;
|
||||
if (d->lz_off < 0)
|
||||
d->lz_off += d->dict_size;
|
||||
}
|
||||
}
|
||||
|
||||
/* copy next byte from dict substring */
|
||||
TINF_PUT(d, d->dict_ring[d->lz_off]);
|
||||
if ((unsigned) ++d->lz_off == d->dict_size) {
|
||||
d->lz_off = 0;
|
||||
}
|
||||
d->curlen--;
|
||||
return TINF_OK;
|
||||
}
|
||||
|
||||
@@ -388,30 +493,22 @@ static int tinf_inflate_uncompressed_block(TINF_DATA *d) {
|
||||
if (d->curlen == 0) {
|
||||
unsigned int length, invlength;
|
||||
|
||||
/* get length */
|
||||
length = uzlib_get_byte(d);
|
||||
length += 256 * uzlib_get_byte(d);
|
||||
/* get one's complement of length */
|
||||
invlength = uzlib_get_byte(d);
|
||||
invlength += 256 * uzlib_get_byte(d);
|
||||
/* check length */
|
||||
/* the block starts on a byte boundary */
|
||||
tinf_take(d, d->bitcount & 7);
|
||||
length = tinf_read_bits(d, 16, 0);
|
||||
invlength = tinf_read_bits(d, 16, 0);
|
||||
if (length != (~invlength & 0x0000ffff))
|
||||
return TINF_DATA_ERROR;
|
||||
|
||||
/* increment length to properly return TINF_DONE below, without
|
||||
producing data at the same time */
|
||||
d->curlen = length + 1;
|
||||
|
||||
/* make sure we start next block on a byte boundary */
|
||||
d->bitcount = 0;
|
||||
}
|
||||
|
||||
if (--d->curlen == 0) {
|
||||
if (--d->curlen == 0)
|
||||
return TINF_DONE;
|
||||
}
|
||||
|
||||
unsigned char c = uzlib_get_byte(d);
|
||||
TINF_PUT(d, c);
|
||||
/* read first: a read that needs input may flush the output up to dest */
|
||||
unsigned char c = tinf_read_bits(d, 8, 0);
|
||||
*d->dest++ = c;
|
||||
return TINF_OK;
|
||||
}
|
||||
|
||||
@@ -419,10 +516,10 @@ static int tinf_inflate_uncompressed_block(TINF_DATA *d) {
|
||||
* -- public functions -- *
|
||||
* ---------------------- */
|
||||
|
||||
/* initialize decompression structure */
|
||||
void ota_inflate_init(TINF_DATA *d, unsigned char *dict, unsigned int dict_len) {
|
||||
d->source = NULL;
|
||||
d->source_limit = NULL;
|
||||
d->dest = dict;
|
||||
d->tag = 0;
|
||||
d->eof = 0;
|
||||
d->bitcount = 0;
|
||||
@@ -431,68 +528,56 @@ void ota_inflate_init(TINF_DATA *d, unsigned char *dict, unsigned int dict_len)
|
||||
d->btype = -1;
|
||||
d->dict_size = dict_len;
|
||||
d->dict_ring = dict;
|
||||
d->dict_idx = 0;
|
||||
d->curlen = 0;
|
||||
d->ltree.trans = d->ltrans;
|
||||
d->ltree.size = TINF_ARRAY_SIZE(d->ltrans);
|
||||
d->ltree.fast = d->lfast;
|
||||
d->dtree.trans = d->dtrans;
|
||||
d->dtree.size = TINF_ARRAY_SIZE(d->dtrans);
|
||||
d->dtree.fast = d->dfast;
|
||||
}
|
||||
|
||||
/* inflate next output bytes from compressed stream */
|
||||
int ota_inflate(TINF_DATA *d) {
|
||||
/* the caller has taken the previous ringful */
|
||||
if (d->dest == d->dict_ring + d->dict_size)
|
||||
d->dest = d->dict_ring;
|
||||
|
||||
do {
|
||||
int res;
|
||||
|
||||
/* start a new block */
|
||||
if (d->btype == -1) {
|
||||
int old_btype;
|
||||
next_blk:
|
||||
old_btype = d->btype;
|
||||
/* read final block flag */
|
||||
d->bfinal = tinf_getbit(d);
|
||||
/* read block type (2 bits) */
|
||||
d->bfinal = tinf_read_bits(d, 1, 0);
|
||||
d->btype = tinf_read_bits(d, 2, 0);
|
||||
|
||||
if (d->btype == 1 && old_btype != 1) {
|
||||
/* build fixed huffman trees */
|
||||
tinf_build_fixed_trees(&d->ltree, &d->dtree);
|
||||
} else if (d->btype == 2) {
|
||||
/* decode trees from stream */
|
||||
res = tinf_decode_trees(d, &d->ltree, &d->dtree);
|
||||
if (res != TINF_OK) {
|
||||
if (res != TINF_OK)
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* process current block */
|
||||
switch (d->btype) {
|
||||
case 0:
|
||||
/* decompress uncompressed block */
|
||||
res = tinf_inflate_uncompressed_block(d);
|
||||
break;
|
||||
case 1:
|
||||
case 2:
|
||||
/* decompress block with fixed/dynamic huffman trees */
|
||||
/* trees were decoded previously, so it's the same routine for both */
|
||||
res = tinf_inflate_block_data(d, &d->ltree, &d->dtree);
|
||||
break;
|
||||
default:
|
||||
return TINF_DATA_ERROR;
|
||||
}
|
||||
|
||||
if (res == TINF_DONE && !d->bfinal) {
|
||||
/* the block has ended (without producing more data), but we
|
||||
can't return without data, so start procesing next block */
|
||||
if (res == TINF_DONE && !d->bfinal)
|
||||
goto next_blk;
|
||||
}
|
||||
|
||||
if (res != TINF_OK) {
|
||||
if (res != TINF_OK)
|
||||
return res;
|
||||
}
|
||||
|
||||
} while (d->dest < d->dest_limit);
|
||||
} while (d->dest < d->dict_ring + d->dict_size);
|
||||
|
||||
return TINF_OK;
|
||||
}
|
||||
@@ -16,10 +16,14 @@ enum OtaInflateResult {
|
||||
OTA_INFLATE_DICT_ERROR = -5,
|
||||
};
|
||||
|
||||
/* Code bits each lookup table resolves; longer codes take the canonical decode.
|
||||
The distance alphabet has 30 symbols, so its table is kept small */
|
||||
enum { OTA_INFLATE_LIT_FAST_BITS = 9, OTA_INFLATE_DIST_FAST_BITS = 6 };
|
||||
|
||||
struct OtaInflateTree {
|
||||
uint16_t table[16]; /* table of code length counts */
|
||||
uint16_t *trans; /* code -> symbol translation table, size entries */
|
||||
uint16_t size;
|
||||
uint16_t *fast; /* (len << 9) | symbol by the next input bits, 0 = longer code */
|
||||
uint16_t *trans; /* code -> symbol translation table */
|
||||
};
|
||||
|
||||
struct OtaInflateState {
|
||||
@@ -29,9 +33,8 @@ struct OtaInflateState {
|
||||
/* Called when source is exhausted; returns the next byte or -1 at EOF.
|
||||
It may refill source/source_limit for buffered operation. */
|
||||
int (*source_read_cb)(struct OtaInflateState *d);
|
||||
/* Output cursor and one past the end of the output buffer */
|
||||
/* Output cursor in dict_ring: each call fills the ring from here */
|
||||
unsigned char *dest;
|
||||
unsigned char *dest_limit;
|
||||
/* Ring window holding the last dict_size output bytes for back references */
|
||||
unsigned char *dict_ring;
|
||||
|
||||
@@ -39,9 +42,8 @@ struct OtaInflateState {
|
||||
unsigned int curlen;
|
||||
int lz_off;
|
||||
unsigned int dict_size;
|
||||
unsigned int dict_idx;
|
||||
|
||||
/* One word: btype is -1 between blocks, bitcount never exceeds 7 */
|
||||
/* btype is -1 between blocks; bitcount is the number of valid bits in tag (<= 32) */
|
||||
int8_t btype;
|
||||
uint8_t bfinal;
|
||||
uint8_t bitcount;
|
||||
@@ -51,13 +53,15 @@ struct OtaInflateState {
|
||||
struct OtaInflateTree dtree; /* dynamic distance tree */
|
||||
uint16_t ltrans[288];
|
||||
uint16_t dtrans[32]; /* the distance alphabet has 30 symbols, so the tree is kept small */
|
||||
uint16_t lfast[1 << OTA_INFLATE_LIT_FAST_BITS];
|
||||
uint16_t dfast[1 << OTA_INFLATE_DIST_FAST_BITS];
|
||||
};
|
||||
|
||||
/* dict must cover the encoder's window (its largest back reference) */
|
||||
void ota_inflate_init(struct OtaInflateState *d, unsigned char *dict, unsigned int dict_len);
|
||||
/* Fills dest up to dest_limit (OK) or to the end of the stream (DONE). dest may
|
||||
alias dict only if dest_limit - dest == dict_len and dest is reset to dict
|
||||
exactly when a call returns OK, so the ring index and dest stay in lockstep */
|
||||
/* Inflates into dict until it is full (OK) or the stream ends (DONE); the output
|
||||
is dict up to dest. The next call after OK starts dict over, so take its
|
||||
contents first */
|
||||
int ota_inflate(struct OtaInflateState *d);
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
@@ -65,16 +65,15 @@ ota::OTAResponseTypes ESPHomeOTAComponent::inflate_data_(uint8_t *in, size_t ima
|
||||
};
|
||||
|
||||
int res;
|
||||
session.flushed = 0;
|
||||
do {
|
||||
// The ring index wrapped to 0 exactly when the window filled
|
||||
session.dest = session.window;
|
||||
session.dest_limit = session.window + OTA_INFLATE_WINDOW_SIZE;
|
||||
session.flushed = 0;
|
||||
res = ota_inflate(&session);
|
||||
// A stored block keeps emitting zeros after a failed read, hence eof
|
||||
if (res < 0 || session.eof)
|
||||
break;
|
||||
session.error = this->inflate_flush_(session);
|
||||
// A full window is all in flash; the next call starts it over
|
||||
session.flushed = 0;
|
||||
} while (res != OTA_INFLATE_DONE && session.error == ota::OTA_RESPONSE_OK);
|
||||
|
||||
// Transport and flash failures are logged where they happen
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import climate_ir
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_MODEL
|
||||
from esphome.const import CONF_MODEL, CONF_USE_FAHRENHEIT
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from . import gree_ns
|
||||
@@ -19,6 +19,7 @@ MODELS = Model.options(
|
||||
CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(GreeClimate).extend(
|
||||
{
|
||||
cv.Required(CONF_MODEL): cv.enum(MODELS, lower=True),
|
||||
cv.Optional(CONF_USE_FAHRENHEIT, default=False): cv.boolean,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -26,5 +27,8 @@ CONFIG_SCHEMA = climate_ir.climate_ir_with_receiver_schema(GreeClimate).extend(
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = await climate_ir.new_climate_ir(config)
|
||||
cg.add(var.set_model(config[CONF_MODEL]))
|
||||
if config[CONF_USE_FAHRENHEIT]:
|
||||
cg.add_define("USE_GREE_FAHRENHEIT")
|
||||
cg.add(var.set_use_fahrenheit(True))
|
||||
if config[CONF_MODEL] == "yaw1f":
|
||||
cg.add_define("USE_GREE_YAW1F")
|
||||
@@ -1,4 +1,6 @@
|
||||
#include "gree.h"
|
||||
|
||||
#include <cmath>
|
||||
#include "esphome/components/remote_base/remote_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
@@ -34,6 +36,21 @@ void GreeClimate::set_mode_bit(uint8_t bit_mask, bool enabled) {
|
||||
this->transmit_state();
|
||||
}
|
||||
|
||||
#ifdef USE_GREE_FAHRENHEIT
|
||||
uint8_t fahrenheit_half_steps(float target_celsius) {
|
||||
if (!std::isfinite(target_celsius)) {
|
||||
return 0;
|
||||
}
|
||||
// Whole degrees shown at k = 0 and at the top of the range
|
||||
static constexpr int MIN_F = static_cast<int>(celsius_to_fahrenheit(GREE_TEMP_MIN));
|
||||
static constexpr int MAX_F = static_cast<int>(celsius_to_fahrenheit(GREE_TEMP_MAX));
|
||||
const int desired_f = static_cast<int>(roundf(clamp<float>(celsius_to_fahrenheit(target_celsius), MIN_F, MAX_F)));
|
||||
// The display at k is (10 * 32 + 18 * GREE_TEMP_MIN + 9 * k) / 10; pick the smallest k that reaches desired_f
|
||||
const int tenths_above_min = 10 * desired_f - (10 * 32 + 18 * GREE_TEMP_MIN);
|
||||
return static_cast<uint8_t>((tenths_above_min + 8) / 9);
|
||||
}
|
||||
#endif
|
||||
|
||||
void GreeClimate::transmit_state() {
|
||||
uint8_t remote_state[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00};
|
||||
const Model model = this->model_;
|
||||
@@ -41,7 +58,12 @@ void GreeClimate::transmit_state() {
|
||||
const bool yag_layout = model == GREE_YAG || model == GREE_YAW1F;
|
||||
|
||||
remote_state[0] = this->fan_speed_() | this->operation_mode_();
|
||||
#ifdef USE_GREE_FAHRENHEIT
|
||||
const uint8_t half_steps = this->use_fahrenheit_ ? fahrenheit_half_steps(this->target_temperature) : 0;
|
||||
remote_state[1] = this->use_fahrenheit_ ? half_steps >> 1 : this->temperature_();
|
||||
#else
|
||||
remote_state[1] = this->temperature_();
|
||||
#endif
|
||||
|
||||
if (model == GREE_YAN) {
|
||||
remote_state[3] = 0x50; // bits 4..7 always 0101
|
||||
@@ -107,16 +129,15 @@ void GreeClimate::transmit_state() {
|
||||
remote_state[0] |= GREE_PRESET_SLEEP_BIT;
|
||||
}
|
||||
|
||||
// Calculate the checksum
|
||||
if (model == GREE_YAN) {
|
||||
remote_state[7] = ((remote_state[0] << 4) + (remote_state[1] << 4) + 0xC0);
|
||||
} else {
|
||||
remote_state[7] =
|
||||
((((remote_state[0] & 0x0F) + (remote_state[1] & 0x0F) + (remote_state[2] & 0x0F) + (remote_state[3] & 0x0F) +
|
||||
((remote_state[4] & 0xF0) >> 4) + ((remote_state[5] & 0xF0) >> 4) + ((remote_state[6] & 0xF0) >> 4) + 0x0A) &
|
||||
0x0F)
|
||||
<< 4);
|
||||
#ifdef USE_GREE_FAHRENHEIT
|
||||
if (this->use_fahrenheit_) {
|
||||
remote_state[3] |= GREE_FAHRENHEIT_BIT;
|
||||
if (half_steps & 1)
|
||||
remote_state[3] |= GREE_TEMP_HALF_DEGREE_BIT;
|
||||
}
|
||||
#endif
|
||||
|
||||
remote_state[7] = frame_checksum(remote_state);
|
||||
|
||||
auto transmit = this->transmitter_->transmit();
|
||||
auto *data = transmit.get_data();
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/climate_ir/climate_ir.h"
|
||||
#include "esphome/core/defines.h"
|
||||
|
||||
namespace esphome::gree {
|
||||
|
||||
@@ -90,6 +91,26 @@ static constexpr uint8_t GREE_MODE_BIT_LIGHT = 0x20;
|
||||
static constexpr uint8_t GREE_MODE_BIT_HEALTH = 0x40;
|
||||
static constexpr uint8_t GREE_MODE_BIT_XFAN = 0x80;
|
||||
|
||||
// Byte 3 Fahrenheit bits
|
||||
static constexpr uint8_t GREE_FAHRENHEIT_BIT = 0x08;
|
||||
static constexpr uint8_t GREE_TEMP_HALF_DEGREE_BIT = 0x04; // Half a degree Celsius added to the temperature
|
||||
|
||||
/// Checksum of a state frame: the low nibbles of bytes 0-3 plus the high nibbles of bytes 4-6 plus 0x0A, in the high
|
||||
/// nibble.
|
||||
inline uint8_t frame_checksum(const uint8_t *remote_state) {
|
||||
return (
|
||||
(((remote_state[0] & 0x0F) + (remote_state[1] & 0x0F) + (remote_state[2] & 0x0F) + (remote_state[3] & 0x0F) +
|
||||
((remote_state[4] & 0xF0) >> 4) + ((remote_state[5] & 0xF0) >> 4) + ((remote_state[6] & 0xF0) >> 4) + 0x0A) &
|
||||
0x0F)
|
||||
<< 4);
|
||||
}
|
||||
|
||||
#ifdef USE_GREE_FAHRENHEIT
|
||||
/// Return k = 2 * temp_field + half_degree_bit (0..28) for a Celsius target. The unit displays
|
||||
/// trunc(celsius_to_fahrenheit(GREE_TEMP_MIN + k / 2)), so k is the smallest value that shows the target in F.
|
||||
uint8_t fahrenheit_half_steps(float target_celsius);
|
||||
#endif
|
||||
|
||||
// Model codes
|
||||
enum Model : uint8_t { GREE_GENERIC, GREE_YAN, GREE_YAA, GREE_YAC, GREE_YAC1FB9, GREE_YX1FF, GREE_YAG, GREE_YAW1F };
|
||||
|
||||
@@ -103,6 +124,9 @@ class GreeClimate final : public climate_ir::ClimateIR {
|
||||
climate::CLIMATE_SWING_HORIZONTAL, climate::CLIMATE_SWING_BOTH}) {}
|
||||
|
||||
void set_model(Model model);
|
||||
#ifdef USE_GREE_FAHRENHEIT
|
||||
void set_use_fahrenheit(bool use_fahrenheit) { this->use_fahrenheit_ = use_fahrenheit; }
|
||||
#endif
|
||||
void set_mode_bit(uint8_t bit_mask, bool enabled);
|
||||
|
||||
protected:
|
||||
@@ -118,6 +142,9 @@ class GreeClimate final : public climate_ir::ClimateIR {
|
||||
|
||||
Model model_{};
|
||||
uint8_t mode_bits_{GREE_MODE_BIT_LIGHT}; // The display light is on until a switch says otherwise
|
||||
#ifdef USE_GREE_FAHRENHEIT
|
||||
bool use_fahrenheit_{false};
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace esphome::gree
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "image.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
@@ -165,6 +166,21 @@ Color Image::get_pixel(int x, int y, const Color color_on, const Color color_off
|
||||
}
|
||||
#ifdef USE_LVGL
|
||||
lv_image_dsc_t *Image::get_lv_image_dsc() {
|
||||
// No pixels yet. A widget keeps this descriptor and reads it on every draw, and LVGL warns on every draw of an image
|
||||
// without data, so give it a transparent pixel instead
|
||||
static constexpr uint8_t TRANSPARENT_PIXEL = 0;
|
||||
if (this->data_start_ == nullptr) {
|
||||
if (this->dsc_.data != &TRANSPARENT_PIXEL) {
|
||||
memset(&this->dsc_, 0, sizeof(this->dsc_));
|
||||
this->dsc_.header.cf = LV_COLOR_FORMAT_A8;
|
||||
this->dsc_.header.w = 1;
|
||||
this->dsc_.header.h = 1;
|
||||
this->dsc_.header.stride = 1;
|
||||
this->dsc_.data_size = 1;
|
||||
this->dsc_.data = &TRANSPARENT_PIXEL;
|
||||
}
|
||||
return &this->dsc_;
|
||||
}
|
||||
// lazily construct lvgl image_dsc.
|
||||
if (this->dsc_.data != this->data_start_) {
|
||||
this->dsc_.data = this->data_start_;
|
||||
|
||||
@@ -43,7 +43,12 @@ class Image : public display::BaseImage {
|
||||
bool has_transparency() const { return this->transparency_ != TRANSPARENCY_OPAQUE; }
|
||||
|
||||
#ifdef USE_LVGL
|
||||
lv_image_dsc_t *get_lv_image_dsc();
|
||||
virtual lv_image_dsc_t *get_lv_image_dsc();
|
||||
#endif
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
/// Called when an LVGL image widget is given this image. An image whose size can change keeps the widget, so it can
|
||||
/// give the widget its source again when the size changes: the widget keeps the size it read from the descriptor.
|
||||
virtual void add_lvgl_widget(lv_obj_t *obj) {}
|
||||
#endif
|
||||
protected:
|
||||
/// Per-pixel draw of the clipped range, rows (true) or columns (false) outer.
|
||||
|
||||
@@ -82,7 +82,10 @@ inline void lv_style_set_text_font(lv_style_t *style, const font::Font *font) {
|
||||
#ifdef USE_IMAGE
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
// Shortcut / overload, so that the source of an image widget can easily be updated from within a lambda.
|
||||
inline void lv_image_set_src(lv_obj_t *obj, image::Image *image) { ::lv_image_set_src(obj, image->get_lv_image_dsc()); }
|
||||
inline void lv_image_set_src(lv_obj_t *obj, image::Image *image) {
|
||||
::lv_image_set_src(obj, image->get_lv_image_dsc());
|
||||
image->add_lvgl_widget(obj);
|
||||
}
|
||||
|
||||
inline void lv_obj_set_style_bitmap_mask_src(lv_obj_t *obj, image::Image *image, lv_style_selector_t selector) {
|
||||
::lv_obj_set_style_bitmap_mask_src(obj, image->get_lv_image_dsc(), selector);
|
||||
|
||||
@@ -69,8 +69,9 @@ PackedBits = modbus.modbus_ns.class_("PackedBits")
|
||||
# The exception code passed to on_error handlers.
|
||||
ExceptionCode = modbus.modbus_ns.enum("ExceptionCode")
|
||||
|
||||
# Lambda argument types for the reply handlers: the device address the send targeted, and the
|
||||
# request/response PDUs (function code + data). The spans are only valid for the duration of the handler.
|
||||
# Lambda arguments: the shared handlers and send's on_response get the address of their own request first,
|
||||
# then the request/response PDUs (function code + data). The spans are only valid for the duration of the handler.
|
||||
_ADDRESS_ARG = (cg.uint8, "address")
|
||||
_PDU_SPAN = cg.std_span.template(cg.uint8.operator("const"))
|
||||
|
||||
# The pdu lambda's return type: a stack-allocated StaticVector capped at the Modbus PDU limit
|
||||
@@ -135,11 +136,11 @@ _ACTION_BASE_SCHEMA = cv.Schema(
|
||||
cv.GenerateID(modbus.CONF_MODBUS_ID): cv.use_id(modbus.ModbusClient),
|
||||
cv.Required(CONF_ADDRESS): cv.templatable(cv.hex_uint8_t),
|
||||
# Optional handlers. on_sent fires when the frame reaches the wire; the reply handlers arrive
|
||||
# later (fire-and-continue), so all run with the request/reply available - not the outer
|
||||
# automation's variables.
|
||||
# later (fire-and-continue), so all run with the address and request/reply available - not the
|
||||
# outer automation's variables.
|
||||
cv.Optional(CONF_ON_SENT): _handler_schema(),
|
||||
cv.Optional(CONF_ON_ERROR): _handler_schema(),
|
||||
# on_no_response takes either a returning lambda (`!lambda "return <bool>;"`, gets `request`,
|
||||
# on_no_response takes either a returning lambda (`!lambda "return <bool>;"`, gets `address`/`request`,
|
||||
# returns true to have the hub retry the frame) OR a `then:` automation of actions; the automation
|
||||
# form may also carry an optional `retry:` returning lambda to run actions AND decide the retry.
|
||||
cv.Optional(CONF_ON_NO_RESPONSE): cv.All(
|
||||
@@ -199,7 +200,7 @@ async def register_client_action(
|
||||
)
|
||||
if sent_conf := config.get(CONF_ON_SENT):
|
||||
await automation.build_automation(
|
||||
var.get_sent_trigger(), [(_PDU_SPAN, "request")], sent_conf
|
||||
var.get_sent_trigger(), [_ADDRESS_ARG, (_PDU_SPAN, "request")], sent_conf
|
||||
)
|
||||
if response_conf := config.get(CONF_ON_RESPONSE):
|
||||
await automation.build_automation(
|
||||
@@ -217,7 +218,7 @@ async def register_client_action(
|
||||
if error_conf := config.get(CONF_ON_ERROR):
|
||||
await automation.build_automation(
|
||||
var.get_error_trigger(),
|
||||
[(_PDU_SPAN, "request"), (ExceptionCode, "exception_code")],
|
||||
[_ADDRESS_ARG, (_PDU_SPAN, "request"), (ExceptionCode, "exception_code")],
|
||||
error_conf,
|
||||
)
|
||||
if (no_response_conf := config.get(CONF_ON_NO_RESPONSE)) is not None:
|
||||
@@ -228,18 +229,20 @@ async def register_client_action(
|
||||
else:
|
||||
await automation.build_automation(
|
||||
var.get_no_response_trigger(),
|
||||
[(_PDU_SPAN, "request")],
|
||||
[_ADDRESS_ARG, (_PDU_SPAN, "request")],
|
||||
no_response_conf,
|
||||
)
|
||||
retry_conf = no_response_conf.get(CONF_RETRY)
|
||||
if retry_conf is not None:
|
||||
retry_lambda = await cg.process_lambda(
|
||||
retry_conf, [(_PDU_SPAN, "request")], return_type=cg.bool_
|
||||
retry_conf, [_ADDRESS_ARG, (_PDU_SPAN, "request")], return_type=cg.bool_
|
||||
)
|
||||
cg.add(var.set_retry(retry_lambda))
|
||||
if not_sent_conf := config.get(CONF_ON_NOT_SENT):
|
||||
await automation.build_automation(
|
||||
var.get_not_sent_trigger(), [(_PDU_SPAN, "request")], not_sent_conf
|
||||
var.get_not_sent_trigger(),
|
||||
[_ADDRESS_ARG, (_PDU_SPAN, "request")],
|
||||
not_sent_conf,
|
||||
)
|
||||
# Wire any command options the action's schema opted into (e.g. continuous on reads). Pass the
|
||||
# matching direction so a write action never generates a read option's setter.
|
||||
@@ -265,7 +268,7 @@ async def modbus_client_send_to_code(config, action_id, template_arg, args):
|
||||
var,
|
||||
config,
|
||||
args,
|
||||
[(_PDU_SPAN, "request"), (_PDU_SPAN, "response")],
|
||||
[_ADDRESS_ARG, (_PDU_SPAN, "request"), (_PDU_SPAN, "response")],
|
||||
)
|
||||
|
||||
|
||||
|
||||
@@ -13,45 +13,51 @@ namespace esphome::modbus_client {
|
||||
/// the hub routes every reply (or its lack) straight back to the action that sent it, so there is no
|
||||
/// central client object and no request matching. The device address is templatable; it is stamped on the
|
||||
/// device at play() time; the hub routes each reply by device pointer, so a changed address never
|
||||
/// mis-routes an earlier reply. (The address is not passed to the reply triggers - under overlapping
|
||||
/// sends it could misreport, and the handler can recompute the expression it configured.)
|
||||
/// mis-routes an earlier reply. The handlers wired here and send's on_response get `address` first: the
|
||||
/// address of their own request, which the hub takes from its frame, so overlapping sends each report their own.
|
||||
template<typename... Ts> class ClientActionBase : public Action<Ts...>, public modbus::ModbusClientDevice {
|
||||
public:
|
||||
TEMPLATABLE_VALUE(uint8_t, target_address) // the modbus device address
|
||||
|
||||
Trigger<std::span<const uint8_t>> *get_sent_trigger() { return &this->sent_trigger_; }
|
||||
Trigger<std::span<const uint8_t>, modbus::ExceptionCode> *get_error_trigger() { return &this->error_trigger_; }
|
||||
Trigger<std::span<const uint8_t>> *get_no_response_trigger() { return &this->no_response_trigger_; }
|
||||
Trigger<std::span<const uint8_t>> *get_not_sent_trigger() { return &this->not_sent_trigger_; }
|
||||
Trigger<uint8_t, std::span<const uint8_t>> *get_sent_trigger() { return &this->sent_trigger_; }
|
||||
Trigger<uint8_t, std::span<const uint8_t>, modbus::ExceptionCode> *get_error_trigger() {
|
||||
return &this->error_trigger_;
|
||||
}
|
||||
Trigger<uint8_t, std::span<const uint8_t>> *get_no_response_trigger() { return &this->no_response_trigger_; }
|
||||
Trigger<uint8_t, std::span<const uint8_t>> *get_not_sent_trigger() { return &this->not_sent_trigger_; }
|
||||
|
||||
/// The retry decision for on_no_response: given the request PDU, return true to have the hub re-queue
|
||||
/// The retry decision for on_no_response: given the request (address, PDU), return true to have the hub re-queue
|
||||
/// the frame. Set from the lambda form or a then: automation's nested retry lambda; may coexist with
|
||||
/// the no_response trigger (actions run, then this decides the retry).
|
||||
using retry_func_t = bool (*)(std::span<const uint8_t>);
|
||||
using retry_func_t = bool (*)(uint8_t, std::span<const uint8_t>);
|
||||
void set_retry(retry_func_t f) { this->retry_func_ = f; }
|
||||
|
||||
/// The frame was written to the wire: fires once per transmission, before any reply, and never for a
|
||||
/// send that ended in on_not_sent. request_pdu is the PDU sent (function code + data).
|
||||
void on_sent(std::span<const uint8_t> request_pdu) override { this->sent_trigger_.trigger(request_pdu); }
|
||||
void on_sent(uint8_t address, std::span<const uint8_t> request_pdu) override {
|
||||
this->sent_trigger_.trigger(address, request_pdu);
|
||||
}
|
||||
/// Never reached the wire, from either of two sources. The hub calls this for a request it accepted
|
||||
/// and then dropped, which happens only when clear_tx_queue_for_address() retires it - a modbus
|
||||
/// device going offline, say. Everything the hub refuses at the door instead returns false from
|
||||
/// queue_pdu() with no callback at all, so send_or_resolve_() below turns those into this same
|
||||
/// callback: a full queue, a duplicate write, or an empty PDU from a rejecting builder.
|
||||
void on_not_sent(std::span<const uint8_t> request_pdu) override { this->not_sent_trigger_.trigger(request_pdu); }
|
||||
void on_not_sent(uint8_t address, std::span<const uint8_t> request_pdu) override {
|
||||
this->not_sent_trigger_.trigger(address, request_pdu);
|
||||
}
|
||||
/// A Modbus exception reply. Lives here beside its trigger so every action subclass gets the pairing:
|
||||
/// register_client_action() wires on_error for all of them, so a derived class must not have to
|
||||
/// remember the override.
|
||||
void on_error(std::span<const uint8_t> request_pdu, modbus::ExceptionCode exception_code) override {
|
||||
this->error_trigger_.trigger(request_pdu, exception_code);
|
||||
void on_error(uint8_t address, std::span<const uint8_t> request_pdu, modbus::ExceptionCode exception_code) override {
|
||||
this->error_trigger_.trigger(address, request_pdu, exception_code);
|
||||
}
|
||||
/// No reply within send_wait_time. Run the on_no_response actions (empty in the pure-lambda form),
|
||||
/// then let the retry lambda, if set, decide whether the hub re-queues the frame (true = retry). The
|
||||
/// two coexist: a then: automation can also carry a retry lambda. No lambda = no retry.
|
||||
bool on_no_response(std::span<const uint8_t> request_pdu) override {
|
||||
this->no_response_trigger_.trigger(request_pdu);
|
||||
bool on_no_response(uint8_t address, std::span<const uint8_t> request_pdu) override {
|
||||
this->no_response_trigger_.trigger(address, request_pdu);
|
||||
if (this->retry_func_ != nullptr)
|
||||
return this->retry_func_(request_pdu);
|
||||
return this->retry_func_(address, request_pdu);
|
||||
return false;
|
||||
}
|
||||
/// Stamp the templated device address before every play(): subclasses cannot forget it, and the hub
|
||||
@@ -70,13 +76,13 @@ template<typename... Ts> class ClientActionBase : public Action<Ts...>, public m
|
||||
/// a PduBuffer parameter would widen each one to the 253-byte maximum just to cross the call.
|
||||
void send_or_resolve_(std::span<const uint8_t> pdu, modbus::CommandOptions options = {}) {
|
||||
if (!this->queue_pdu(pdu, options))
|
||||
this->on_not_sent(pdu);
|
||||
this->on_not_sent(this->address_, pdu);
|
||||
}
|
||||
|
||||
Trigger<std::span<const uint8_t>> sent_trigger_;
|
||||
Trigger<std::span<const uint8_t>, modbus::ExceptionCode> error_trigger_;
|
||||
Trigger<std::span<const uint8_t>> no_response_trigger_;
|
||||
Trigger<std::span<const uint8_t>> not_sent_trigger_;
|
||||
Trigger<uint8_t, std::span<const uint8_t>> sent_trigger_;
|
||||
Trigger<uint8_t, std::span<const uint8_t>, modbus::ExceptionCode> error_trigger_;
|
||||
Trigger<uint8_t, std::span<const uint8_t>> no_response_trigger_;
|
||||
Trigger<uint8_t, std::span<const uint8_t>> not_sent_trigger_;
|
||||
retry_func_t retry_func_{nullptr};
|
||||
};
|
||||
|
||||
@@ -119,7 +125,7 @@ template<typename... Ts> class WriteCommandOptions {
|
||||
};
|
||||
|
||||
/// modbus_client.send: fire a raw PDU (function code + data; the hub adds address and CRC). The reply is
|
||||
/// delivered raw - on_response(request, response) - deliberately bypassing the typed dispatch, so
|
||||
/// delivered raw - on_response(address, request, response) - deliberately bypassing the typed dispatch, so
|
||||
/// non-standard/custom transactions pass through untouched.
|
||||
/// The PDU is a stack-allocated modbus::helpers::PduBuffer, so a pdu lambda can build one with the
|
||||
/// modbus::helpers::create_*_pdu() builders and return it directly (smaller builder results convert).
|
||||
@@ -133,7 +139,7 @@ class ModbusClientSendAction : public ClientActionBase<Ts...>,
|
||||
public:
|
||||
TEMPLATABLE_VALUE(modbus::helpers::PduBuffer, pdu)
|
||||
|
||||
Trigger<std::span<const uint8_t>, std::span<const uint8_t>> *get_response_trigger() {
|
||||
Trigger<uint8_t, std::span<const uint8_t>, std::span<const uint8_t>> *get_response_trigger() {
|
||||
return &this->response_trigger_;
|
||||
}
|
||||
|
||||
@@ -143,12 +149,13 @@ class ModbusClientSendAction : public ClientActionBase<Ts...>,
|
||||
this->send_or_resolve_(this->pdu_.value(x...), options);
|
||||
}
|
||||
|
||||
void on_response(std::span<const uint8_t> request_pdu, std::span<const uint8_t> response_pdu) override {
|
||||
this->response_trigger_.trigger(request_pdu, response_pdu);
|
||||
void on_response(uint8_t address, std::span<const uint8_t> request_pdu,
|
||||
std::span<const uint8_t> response_pdu) override {
|
||||
this->response_trigger_.trigger(address, request_pdu, response_pdu);
|
||||
}
|
||||
|
||||
protected:
|
||||
Trigger<std::span<const uint8_t>, std::span<const uint8_t>> response_trigger_;
|
||||
Trigger<uint8_t, std::span<const uint8_t>, std::span<const uint8_t>> response_trigger_;
|
||||
};
|
||||
|
||||
/// Typed actions: these do NOT override the raw on_response, so the base ModbusClientDevice default runs
|
||||
|
||||
@@ -2,24 +2,18 @@ import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_SOURCE_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import Number, number_ns
|
||||
from .. import Number
|
||||
|
||||
NumberSensor = number_ns.class_("NumberSensor", sensor.Sensor, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
sensor.sensor_schema(NumberSensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(Number),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = sensor.sensor_schema(sensor.Sensor).extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(Number),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
source = await cg.get_variable(config[CONF_SOURCE_ID])
|
||||
var = await sensor.new_sensor(config, source)
|
||||
await cg.register_component(var, config)
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = await sensor.new_sensor(config)
|
||||
# Number has the same float state callback as Sensor
|
||||
sensor.follow_sensor(var, await cg.get_variable(config[CONF_SOURCE_ID]))
|
||||
@@ -1,16 +0,0 @@
|
||||
#include "number_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::number {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "number.sensor");
|
||||
|
||||
void NumberSensor::setup() {
|
||||
this->source_->add_on_state_callback([this](float value) { this->publish_state(value); });
|
||||
if (this->source_->has_state())
|
||||
this->publish_state(this->source_->state);
|
||||
}
|
||||
|
||||
void NumberSensor::dump_config() { LOG_SENSOR("", "Number Sensor", this); }
|
||||
|
||||
} // namespace esphome::number
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../number.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
|
||||
namespace esphome::number {
|
||||
|
||||
class NumberSensor final : public sensor::Sensor, public Component {
|
||||
public:
|
||||
explicit NumberSensor(Number *source) : source_(source) {}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
Number *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::number
|
||||
@@ -42,6 +42,7 @@ ONLINE_IMAGE_SCHEMA = (
|
||||
# AUTO (Content-Type detection) is online_image specific; not in the shared registry
|
||||
cv.Required(CONF_FORMAT): cv.one_of(*IMAGE_FORMATS, "AUTO", upper=True),
|
||||
cv.Optional(CONF_BUFFER_SIZE, default=65536): cv.int_range(256, 65536),
|
||||
cv.Optional(runtime_image.CONF_DOUBLE_BUFFER, default=False): cv.boolean,
|
||||
cv.Optional(CONF_REQUEST_HEADERS): cv.All(
|
||||
cv.Schema({cv.string: cv.templatable(cv.string)})
|
||||
),
|
||||
@@ -141,6 +142,8 @@ async def setup_online_image(config: ConfigType) -> None:
|
||||
)
|
||||
await cg.register_component(var, config)
|
||||
await cg.register_parented(var, config[CONF_HTTP_REQUEST_ID])
|
||||
if settings.double_buffer:
|
||||
cg.add(var.set_double_buffer(True))
|
||||
|
||||
for key, value in config.get(CONF_REQUEST_HEADERS, {}).items():
|
||||
if isinstance(value, Lambda):
|
||||
|
||||
@@ -428,9 +428,8 @@ void OnlineImage::finish_download_() {
|
||||
|
||||
void OnlineImage::end_connection_() {
|
||||
// Abort any in-progress decode; the decoder object is kept warm for the next decode.
|
||||
// Use RuntimeImage::release() directly to avoid recursion with OnlineImage::release().
|
||||
if (this->is_decoding()) {
|
||||
RuntimeImage::release();
|
||||
this->abort_decode();
|
||||
}
|
||||
if (this->downloader_) {
|
||||
this->downloader_->end();
|
||||
|
||||
@@ -3004,3 +3004,73 @@ register_protocol_action(
|
||||
(CONF_CHANNELS, "channels", cg.uint16),
|
||||
(CONF_COMMAND, "command", cg.uint8),
|
||||
)
|
||||
|
||||
|
||||
# Cardin S438
|
||||
(
|
||||
CardinS438Data,
|
||||
CardinS438BinarySensor,
|
||||
CardinS438Trigger,
|
||||
_,
|
||||
CardinS438Dumper,
|
||||
) = declare_protocol("CardinS438")
|
||||
|
||||
|
||||
CARDIN_S438_CHANNELS = {
|
||||
"A": 0x02,
|
||||
"B": 0x00,
|
||||
"C": 0x03,
|
||||
"D": 0x01,
|
||||
}
|
||||
|
||||
|
||||
def _validate_cardin_s438_code(code):
|
||||
"""Accept the DIP switches as a 14-character binary string."""
|
||||
code = cv.string_strict(code)
|
||||
if len(code) != 14 or any(bit not in "01" for bit in code):
|
||||
raise cv.Invalid("code must be exactly 14 binary digits, e.g. '10100110101010'")
|
||||
return int(code, 2)
|
||||
|
||||
|
||||
CARDIN_S438_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Required(CONF_CODE): _validate_cardin_s438_code,
|
||||
cv.Optional(CONF_CHANNEL, default="A"): cv.enum(
|
||||
CARDIN_S438_CHANNELS, upper=True
|
||||
),
|
||||
cv.Optional(CONF_COMMAND_REPEATS, default=3): cv.int_range(min=1, max=255),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@register_binary_sensor("cardin_s438", CardinS438BinarySensor, CARDIN_S438_SCHEMA)
|
||||
def cardin_s438_binary_sensor(var: MockObj, config: ConfigType) -> None:
|
||||
cg.add(
|
||||
var.set_data(
|
||||
cg.StructInitializer(
|
||||
CardinS438Data,
|
||||
("code", config[CONF_CODE]),
|
||||
("channel", config[CONF_CHANNEL]),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@register_trigger("cardin_s438", CardinS438Trigger, CardinS438Data)
|
||||
def cardin_s438_trigger(var: MockObj, config: ConfigType) -> None:
|
||||
"""The trigger takes no options beyond the automation."""
|
||||
|
||||
|
||||
@register_dumper("cardin_s438", CardinS438Dumper)
|
||||
def cardin_s438_dumper(var: MockObj, config: ConfigType) -> None:
|
||||
"""The dumper takes no options."""
|
||||
|
||||
|
||||
register_protocol_action(
|
||||
"cardin_s438",
|
||||
"CardinS438",
|
||||
CARDIN_S438_SCHEMA,
|
||||
(CONF_CODE, "code", cg.uint16),
|
||||
(CONF_CHANNEL, "channel", cg.uint8),
|
||||
(CONF_COMMAND_REPEATS, "repeats", cg.uint8),
|
||||
)
|
||||
@@ -0,0 +1,159 @@
|
||||
#include "cardin_s438_protocol.h"
|
||||
#include "esphome/core/hal.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::remote_base {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "remote.cardin_s438");
|
||||
|
||||
namespace {
|
||||
|
||||
/*
|
||||
* Cardin S438 wire format
|
||||
*
|
||||
* Frame: 6400 us sync mark + 40 data bits (5 bytes, LSB-first).
|
||||
* 0 = 1460 us space, 750 us mark
|
||||
* 1 = 750 us space, 1460 us mark
|
||||
*
|
||||
* Bytes:
|
||||
* [0..2] Encoded pin fields and seed
|
||||
* [2] (seed + marker 0x87), modulo 256
|
||||
* Carries propagate into bytes 1 and 0.
|
||||
* [3..4] Sum of bytes [0..2], little-endian
|
||||
*
|
||||
* Pin code: 14 bits, MSB-first, mapped to X/Y fields:
|
||||
* Y7,Y6,X7,X6,Y5,X5,Y4,X4,Y3,X3,Y2,Y1,Y0,X2
|
||||
* Channels in X bits 1:0: A=10, B=00, C=11, D=01.
|
||||
*
|
||||
* Three frames per transmission, with 63650 us LOW gaps.
|
||||
* Seed chosen randomly; no pattern found in captures.
|
||||
*/
|
||||
|
||||
// Frame format and timing, in microseconds.
|
||||
constexpr uint8_t MARKER = 0x87;
|
||||
constexpr uint16_t SYNC_MARK_US = 6400;
|
||||
constexpr uint32_t FRAME_GAP_US = 63650;
|
||||
constexpr uint8_t WIRE_SIZE = 5;
|
||||
// Each bit is a space then a mark, and both lengths carry it
|
||||
constexpr BitTimings CARDIN_TIMINGS{
|
||||
.one_space = 750, .zero_space = 1460, .one_mark = 1460, .zero_mark = 750, .space_first = true};
|
||||
|
||||
// Maps pincode bits, most-significant first, to a bit of the X or Y field: PIN_Y marks Y, the low bits are the bit
|
||||
// number. In flash, so it stays out of RAM on ESP8266.
|
||||
constexpr uint8_t PIN_Y = 0x08;
|
||||
constexpr uint8_t PINCODE_BITS = 14;
|
||||
constexpr uint8_t PINCODE_MAP[PINCODE_BITS] PROGMEM = {
|
||||
PIN_Y | 7, PIN_Y | 6, 7, 6, PIN_Y | 5, 5, PIN_Y | 4, 4, PIN_Y | 3, 3, PIN_Y | 2, PIN_Y | 1, PIN_Y | 0, 2,
|
||||
};
|
||||
|
||||
// Convert the pincode and channel to the two decoded pin fields.
|
||||
void encode_pin_fields(uint16_t pincode, uint8_t channel, uint8_t &plain0, uint8_t &plain1) {
|
||||
uint8_t x = channel;
|
||||
uint8_t y = 0;
|
||||
|
||||
for (uint8_t i = 0; i < PINCODE_BITS; ++i) {
|
||||
if (((pincode >> (PINCODE_BITS - 1 - i)) & 1U) == 0)
|
||||
continue;
|
||||
const uint8_t entry = progmem_read_byte(&PINCODE_MAP[i]);
|
||||
(entry & PIN_Y ? y : x) |= 1U << (entry & 0x07);
|
||||
}
|
||||
|
||||
plain1 = ~y;
|
||||
plain0 = plain1 - x + 1;
|
||||
}
|
||||
|
||||
// Construct the wire bytes for one seed.
|
||||
void build_wire(uint8_t wire[WIRE_SIZE], uint8_t seed, uint8_t plain0, uint8_t plain1) {
|
||||
uint16_t sum = MARKER + seed;
|
||||
wire[2] = sum;
|
||||
|
||||
uint8_t key = seed + (sum >> 8);
|
||||
|
||||
sum = plain1 + key;
|
||||
wire[1] = sum;
|
||||
key += sum >> 8;
|
||||
|
||||
wire[0] = plain0 + key;
|
||||
|
||||
// checksum over the first three bytes, little-endian.
|
||||
const uint16_t checksum = sum16_bytes(wire, 3);
|
||||
wire[3] = checksum;
|
||||
wire[4] = checksum >> 8;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void CardinS438Protocol::encode(RemoteTransmitData *dst, const CardinS438Data &data) {
|
||||
uint8_t plain0;
|
||||
uint8_t plain1;
|
||||
|
||||
encode_pin_fields(data.code & 0x3FFF, data.channel & 0x03, plain0, plain1);
|
||||
|
||||
for (uint8_t frame = 0; frame < data.repeats; ++frame) {
|
||||
uint8_t wire[WIRE_SIZE];
|
||||
uint8_t seed = static_cast<uint8_t>(random_uint32() & 0xFF);
|
||||
|
||||
build_wire(wire, seed, plain0, plain1);
|
||||
dst->mark(SYNC_MARK_US);
|
||||
dst->item_bytes<CARDIN_TIMINGS>(wire, WIRE_SIZE);
|
||||
dst->space(FRAME_GAP_US);
|
||||
}
|
||||
}
|
||||
|
||||
optional<CardinS438Data> CardinS438Protocol::decode(RemoteReceiveData src) {
|
||||
while (src.is_valid()) {
|
||||
// Find the frame's long sync mark.
|
||||
if (!src.expect_mark(SYNC_MARK_US)) {
|
||||
src.advance();
|
||||
continue;
|
||||
}
|
||||
|
||||
uint8_t wire[WIRE_SIZE];
|
||||
if (!src.expect_bytes<CARDIN_TIMINGS>(wire, WIRE_SIZE) ||
|
||||
encode_value_le<uint16_t>(wire + 3) != sum16_bytes(wire, 3)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Recover the seed and undo the encoder's carry transform.
|
||||
uint8_t seed = wire[2] - MARKER;
|
||||
|
||||
uint8_t key = seed;
|
||||
key += (seed + MARKER) >> 8;
|
||||
|
||||
uint8_t plain1 = wire[1] - key;
|
||||
key += (plain1 + key) >> 8;
|
||||
|
||||
uint8_t plain0 = wire[0] - key;
|
||||
|
||||
// Recover the original X and Y fields.
|
||||
uint8_t x = plain1 - plain0 + 1;
|
||||
uint8_t y = ~plain1;
|
||||
|
||||
// Reconstruct the 14-bit code in transmission order.
|
||||
uint16_t code = 0;
|
||||
for (const uint8_t &slot : PINCODE_MAP) {
|
||||
const uint8_t entry = progmem_read_byte(&slot);
|
||||
code = (code << 1) | (((entry & PIN_Y ? y : x) >> (entry & 0x07)) & 1U);
|
||||
}
|
||||
|
||||
return CardinS438Data{
|
||||
.code = code,
|
||||
.channel = static_cast<uint8_t>(x & 0x03),
|
||||
.seed = seed,
|
||||
};
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
void CardinS438Protocol::dump(const CardinS438Data &data) {
|
||||
// The DIP switches as the config takes them; channels as CARDIN_S438_CHANNELS in __init__.py maps them
|
||||
char code[PINCODE_BITS + 1];
|
||||
for (uint8_t i = 0; i < PINCODE_BITS; ++i)
|
||||
code[i] = (data.code >> (PINCODE_BITS - 1 - i)) & 1U ? '1' : '0';
|
||||
code[PINCODE_BITS] = '\0';
|
||||
ESP_LOGI(TAG, "Received Cardin S438: code=%s channel=%c seed=0x%02X", code, "BDAC"[data.channel & 0x03], data.seed);
|
||||
}
|
||||
|
||||
} // namespace esphome::remote_base
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/components/remote_base/remote_base.h"
|
||||
|
||||
namespace esphome::remote_base {
|
||||
|
||||
struct CardinS438Data {
|
||||
uint16_t code{0};
|
||||
uint8_t channel{0};
|
||||
uint8_t seed{0};
|
||||
uint8_t repeats{3};
|
||||
|
||||
bool operator==(const CardinS438Data &rhs) const { return code == rhs.code && channel == rhs.channel; }
|
||||
};
|
||||
|
||||
class CardinS438Protocol : public RemoteProtocol<CardinS438Data> {
|
||||
public:
|
||||
void encode(RemoteTransmitData *dst, const CardinS438Data &data);
|
||||
optional<CardinS438Data> decode(RemoteReceiveData src);
|
||||
void dump(const CardinS438Data &data);
|
||||
};
|
||||
|
||||
DECLARE_REMOTE_PROTOCOL(CardinS438)
|
||||
|
||||
} // namespace esphome::remote_base
|
||||
@@ -19,6 +19,7 @@ AUTO_LOAD = ["image"]
|
||||
CODEOWNERS = ["@guillempages", "@clydebarrow", "@kahrendt"]
|
||||
DOMAIN = "runtime_image"
|
||||
|
||||
CONF_DOUBLE_BUFFER = "double_buffer"
|
||||
CONF_PLACEHOLDER = "placeholder"
|
||||
CONF_TRANSPARENCY = "transparency"
|
||||
|
||||
@@ -212,6 +213,7 @@ class RuntimeImageSettings:
|
||||
transparent: cg.MockObj
|
||||
byte_order_big_endian: bool
|
||||
placeholder: cg.MockObj | None
|
||||
double_buffer: bool
|
||||
|
||||
|
||||
async def process_runtime_image_config(config: dict) -> RuntimeImageSettings:
|
||||
@@ -256,4 +258,5 @@ async def process_runtime_image_config(config: dict) -> RuntimeImageSettings:
|
||||
transparent=transparent,
|
||||
byte_order_big_endian=byte_order_big_endian,
|
||||
placeholder=placeholder,
|
||||
double_buffer=config.get(CONF_DOUBLE_BUFFER, False),
|
||||
)
|
||||
@@ -8,6 +8,8 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
#include <new>
|
||||
|
||||
#ifdef USE_ESP_IDF
|
||||
#include "esp_task_wdt.h"
|
||||
#endif
|
||||
@@ -94,7 +96,38 @@ static int draw_callback(JPEGDRAW *jpeg) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
JpegDecoder::JpegDecoder(RuntimeImage *image) : ImageDecoder(image, JPEG) {
|
||||
RAMAllocator<JPEGDEC> allocator;
|
||||
JPEGDEC *jpeg = allocator.allocate(1);
|
||||
if (jpeg == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate memory for the JPEG decoder");
|
||||
return;
|
||||
}
|
||||
this->jpeg_ = new (jpeg) JPEGDEC();
|
||||
}
|
||||
|
||||
JpegDecoder::~JpegDecoder() {
|
||||
if (this->jpeg_ != nullptr) {
|
||||
this->jpeg_->~JPEGDEC();
|
||||
RAMAllocator<JPEGDEC> allocator;
|
||||
allocator.deallocate(this->jpeg_, 1);
|
||||
}
|
||||
}
|
||||
|
||||
int JpegDecoder::prepare(size_t expected_size) {
|
||||
// Check before the base prepare() so a failure never leaves an active session
|
||||
if (this->jpeg_ == nullptr) {
|
||||
ESP_LOGE(TAG, "JPEG decoder engine not initialized!");
|
||||
return DECODE_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
return ImageDecoder::prepare(expected_size);
|
||||
}
|
||||
|
||||
int HOT JpegDecoder::decode(uint8_t *buffer, size_t size) {
|
||||
if (this->jpeg_ == nullptr) {
|
||||
ESP_LOGE(TAG, "JPEG decoder engine not initialized!");
|
||||
return DECODE_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
// JPEG decoder requires complete data
|
||||
// If we know the expected size, wait for it
|
||||
if (this->expected_size_ > 0 && size < this->expected_size_) {
|
||||
@@ -105,22 +138,22 @@ int HOT JpegDecoder::decode(uint8_t *buffer, size_t size) {
|
||||
// If size unknown, try to decode and see if it's valid
|
||||
// The JPEGDEC library will fail gracefully if data is incomplete
|
||||
|
||||
if (!this->jpeg_.openRAM(buffer, size, draw_callback)) {
|
||||
ESP_LOGE(TAG, "Could not open image for decoding: %d", this->jpeg_.getLastError());
|
||||
if (!this->jpeg_->openRAM(buffer, size, draw_callback)) {
|
||||
ESP_LOGE(TAG, "Could not open image for decoding: %d", this->jpeg_->getLastError());
|
||||
return DECODE_ERROR_INVALID_TYPE;
|
||||
}
|
||||
const int err = this->start_();
|
||||
if (err != 0)
|
||||
return err;
|
||||
if (!this->jpeg_.decode(0, 0, this->scale_option_()))
|
||||
if (!this->jpeg_->decode(0, 0, this->scale_option_()))
|
||||
return this->fail_();
|
||||
this->decoded_bytes_ = size;
|
||||
this->jpeg_.close();
|
||||
this->jpeg_->close();
|
||||
return size;
|
||||
}
|
||||
|
||||
int JpegDecoder::start_() {
|
||||
auto jpeg_type = this->jpeg_.getJPEGType();
|
||||
auto jpeg_type = this->jpeg_->getJPEGType();
|
||||
if (jpeg_type == JPEG_MODE_INVALID) {
|
||||
ESP_LOGE(TAG, "Unsupported JPEG image");
|
||||
return DECODE_ERROR_INVALID_TYPE;
|
||||
@@ -128,11 +161,12 @@ int JpegDecoder::start_() {
|
||||
ESP_LOGE(TAG, "Progressive JPEG images not supported");
|
||||
return DECODE_ERROR_UNSUPPORTED_FORMAT;
|
||||
}
|
||||
ESP_LOGD(TAG, "Image size: %d x %d, bpp: %d", this->jpeg_.getWidth(), this->jpeg_.getHeight(), this->jpeg_.getBpp());
|
||||
ESP_LOGD(TAG, "Image size: %d x %d, bpp: %d", this->jpeg_->getWidth(), this->jpeg_->getHeight(),
|
||||
this->jpeg_->getBpp());
|
||||
|
||||
this->jpeg_.setUserPointer(this);
|
||||
const int width = this->jpeg_.getWidth();
|
||||
const int height = this->jpeg_.getHeight();
|
||||
this->jpeg_->setUserPointer(this);
|
||||
const int width = this->jpeg_->getWidth();
|
||||
const int height = this->jpeg_->getHeight();
|
||||
// The tables for drawing are only built once the decoded size is known, below
|
||||
if (!this->resize_image_(width, height)) {
|
||||
return DECODE_ERROR_OUT_OF_MEMORY;
|
||||
@@ -155,17 +189,17 @@ int JpegDecoder::start_() {
|
||||
if (this->direct_rgb565_) {
|
||||
// JPEGDEC's RGB565_BIG_ENDIAN output is not byte swapped on every build (the x86 host build
|
||||
// returns little-endian words for it), so the byte order is applied in draw_rgb565 instead
|
||||
this->jpeg_.setPixelType(RGB565_LITTLE_ENDIAN);
|
||||
this->jpeg_->setPixelType(RGB565_LITTLE_ENDIAN);
|
||||
} else {
|
||||
this->jpeg_.setPixelType(RGB8888);
|
||||
this->jpeg_->setPixelType(RGB8888);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int JpegDecoder::fail_() {
|
||||
const bool read_failed = this->source_ != nullptr && this->source_failed_;
|
||||
const int error = this->jpeg_.getLastError();
|
||||
this->jpeg_.close();
|
||||
const int error = this->jpeg_->getLastError();
|
||||
this->jpeg_->close();
|
||||
this->source_ = nullptr;
|
||||
if (read_failed) {
|
||||
ESP_LOGE(TAG, "Reading the image failed");
|
||||
@@ -189,7 +223,7 @@ int JpegDecoder::fail_() {
|
||||
void JpegDecoder::reset() {
|
||||
// A decode from a source that was given up part way still has the image open
|
||||
if (this->source_ != nullptr) {
|
||||
this->jpeg_.close();
|
||||
this->jpeg_->close();
|
||||
this->source_ = nullptr;
|
||||
}
|
||||
ImageDecoder::reset();
|
||||
@@ -218,6 +252,10 @@ int32_t JpegDecoder::source_seek(JPEGFILE *file, int32_t position) {
|
||||
}
|
||||
|
||||
int JpegDecoder::begin_source(DecodeSource *source) {
|
||||
if (this->jpeg_ == nullptr) {
|
||||
ESP_LOGE(TAG, "JPEG decoder engine not initialized!");
|
||||
return DECODE_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
if (this->expected_size_ == 0 || this->expected_size_ > INT32_MAX) {
|
||||
ESP_LOGE(TAG, "Image size %zu cannot be read from a source", this->expected_size_);
|
||||
return DECODE_ERROR_INVALID_TYPE;
|
||||
@@ -228,19 +266,19 @@ int JpegDecoder::begin_source(DecodeSource *source) {
|
||||
}
|
||||
this->source_ = source;
|
||||
this->source_failed_ = false;
|
||||
if (!this->jpeg_.open(this, this->expected_size_, source_close, source_read, source_seek, draw_callback)) {
|
||||
if (!this->jpeg_->open(this, this->expected_size_, source_close, source_read, source_seek, draw_callback)) {
|
||||
// The caller may recover, for example by reading the whole image first, so it reports the failure
|
||||
ESP_LOGD(TAG, "Could not open image for decoding: %d", this->jpeg_.getLastError());
|
||||
ESP_LOGD(TAG, "Could not open image for decoding: %d", this->jpeg_->getLastError());
|
||||
this->source_ = nullptr;
|
||||
return this->source_failed_ ? DECODE_ERROR_READ : DECODE_ERROR_INVALID_TYPE;
|
||||
}
|
||||
const int err = this->start_();
|
||||
if (err != 0) {
|
||||
this->jpeg_.close();
|
||||
this->jpeg_->close();
|
||||
this->source_ = nullptr;
|
||||
return err;
|
||||
}
|
||||
if (!this->jpeg_.decodeBegin(0, 0, this->scale_option_()))
|
||||
if (!this->jpeg_->decodeBegin(0, 0, this->scale_option_()))
|
||||
return this->fail_();
|
||||
return 0;
|
||||
}
|
||||
@@ -248,7 +286,7 @@ int JpegDecoder::begin_source(DecodeSource *source) {
|
||||
int HOT JpegDecoder::decode_step() {
|
||||
if (this->source_ == nullptr)
|
||||
return DECODE_ERROR_INVALID_TYPE;
|
||||
const int result = this->jpeg_.decodeRows(1);
|
||||
const int result = this->jpeg_->decodeRows(1);
|
||||
// JPEGDEC may decode on with the missing data, so check the read before its result
|
||||
if (this->source_failed_)
|
||||
return this->fail_();
|
||||
@@ -259,7 +297,7 @@ int HOT JpegDecoder::decode_step() {
|
||||
if (result == 0)
|
||||
return this->fail_();
|
||||
this->decoded_bytes_ = this->expected_size_;
|
||||
this->jpeg_.close();
|
||||
this->jpeg_->close();
|
||||
this->source_ = nullptr;
|
||||
return DECODE_STEP_DONE;
|
||||
}
|
||||
|
||||
@@ -18,9 +18,10 @@ class JpegDecoder : public ImageDecoder {
|
||||
*
|
||||
* @param image The RuntimeImage to decode the stream into.
|
||||
*/
|
||||
JpegDecoder(RuntimeImage *image) : ImageDecoder(image, JPEG) {}
|
||||
~JpegDecoder() override {}
|
||||
JpegDecoder(RuntimeImage *image);
|
||||
~JpegDecoder() override;
|
||||
|
||||
int prepare(size_t expected_size) override;
|
||||
int HOT decode(uint8_t *buffer, size_t size) override;
|
||||
void reset() override;
|
||||
|
||||
@@ -46,7 +47,8 @@ class JpegDecoder : public ImageDecoder {
|
||||
static int32_t source_seek(JPEGFILE *file, int32_t position);
|
||||
static void source_close(void *handle) {}
|
||||
|
||||
JPEGDEC jpeg_{};
|
||||
/// About 18 KB of decoder state, allocated like pngle in PngDecoder: PSRAM first, nullptr if out of memory
|
||||
JPEGDEC *jpeg_{nullptr};
|
||||
int scale_{1}; // JPEGDEC decodes at 1/scale_ of the image size
|
||||
DecodeSource *source_{nullptr}; // Set while a begin_source() decode is in progress
|
||||
bool source_failed_{false};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <limits>
|
||||
@@ -48,7 +49,14 @@ RuntimeImage::RuntimeImage(ImageFormat format, image::ImageType type, image::Tra
|
||||
fixed_height_(fixed_height),
|
||||
placeholder_(placeholder) {}
|
||||
|
||||
RuntimeImage::~RuntimeImage() { this->release(); }
|
||||
RuntimeImage::~RuntimeImage() {
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
for (lv_obj_t *obj : this->lvgl_widgets_)
|
||||
lv_obj_remove_event_cb_with_user_data(obj, lvgl_widget_deleted, this);
|
||||
this->lvgl_widgets_.clear();
|
||||
#endif
|
||||
this->release();
|
||||
}
|
||||
|
||||
int RuntimeImage::resize(int width, int height) {
|
||||
// Use fixed dimensions if specified (0 means auto-resize)
|
||||
@@ -69,6 +77,7 @@ int RuntimeImage::resize(int width, int height) {
|
||||
this->width_ = this->buffer_width_;
|
||||
this->height_ = this->buffer_height_;
|
||||
this->data_start_ = this->buffer_;
|
||||
this->refresh_lvgl_();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -176,6 +185,52 @@ void RuntimeImage::map_chroma_key(Color &color) {
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_LVGL
|
||||
lv_image_dsc_t *RuntimeImage::get_lv_image_dsc() {
|
||||
if (this->data_start_ == nullptr && this->placeholder_ != nullptr) {
|
||||
this->dsc_ = *this->placeholder_->get_lv_image_dsc();
|
||||
return &this->dsc_;
|
||||
}
|
||||
return Image::get_lv_image_dsc();
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
void RuntimeImage::add_lvgl_widget(lv_obj_t *obj) {
|
||||
if (std::find(this->lvgl_widgets_.begin(), this->lvgl_widgets_.end(), obj) != this->lvgl_widgets_.end())
|
||||
return;
|
||||
this->lvgl_widgets_.push_back(obj);
|
||||
lv_obj_add_event_cb(obj, lvgl_widget_deleted, LV_EVENT_DELETE, this);
|
||||
}
|
||||
|
||||
void RuntimeImage::lvgl_widget_deleted(lv_event_t *event) {
|
||||
auto *image = static_cast<RuntimeImage *>(lv_event_get_user_data(event));
|
||||
auto &widgets = image->lvgl_widgets_;
|
||||
widgets.erase(std::remove(widgets.begin(), widgets.end(), lv_event_get_target_obj(event)), widgets.end());
|
||||
}
|
||||
#endif
|
||||
|
||||
void RuntimeImage::refresh_lvgl_() {
|
||||
#ifdef USE_LVGL
|
||||
// Not a virtual call: this also runs from the destructor
|
||||
lv_image_dsc_t *dsc = this->RuntimeImage::get_lv_image_dsc();
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
auto &widgets = this->lvgl_widgets_;
|
||||
for (size_t i = 0; i < widgets.size();) {
|
||||
lv_obj_t *obj = widgets[i];
|
||||
if (lv_image_get_src(obj) == dsc) {
|
||||
// Setting the same source makes the widget read the size again, and redraws it
|
||||
::lv_image_set_src(obj, dsc);
|
||||
i++;
|
||||
} else {
|
||||
lv_obj_remove_event_cb_with_user_data(obj, lvgl_widget_deleted, this);
|
||||
widgets.erase(widgets.begin() + i);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
}
|
||||
|
||||
void RuntimeImage::draw(int x, int y, display::Display *display, Color color_on, Color color_off) {
|
||||
if (this->data_start_) {
|
||||
// If we have a complete image, use the base class draw method
|
||||
@@ -263,11 +318,21 @@ bool RuntimeImage::end_decode() {
|
||||
}
|
||||
|
||||
// Finalize the image for display
|
||||
if (!this->progressive_display_) {
|
||||
if (this->is_double_buffered_()) {
|
||||
if (this->buffer_ != nullptr) {
|
||||
// Show the new image, and decode into the old one's buffer next time
|
||||
std::swap(this->front_buffer_, this->buffer_);
|
||||
std::swap(this->width_, this->buffer_width_);
|
||||
std::swap(this->height_, this->buffer_height_);
|
||||
this->data_start_ = this->front_buffer_;
|
||||
this->refresh_lvgl_();
|
||||
}
|
||||
} else if (!this->progressive_display_) {
|
||||
// Only now make the image visible
|
||||
this->width_ = this->buffer_width_;
|
||||
this->height_ = this->buffer_height_;
|
||||
this->data_start_ = this->buffer_;
|
||||
this->refresh_lvgl_();
|
||||
}
|
||||
|
||||
// End the session; the decoder object stays warm so the next decode can
|
||||
@@ -284,6 +349,15 @@ bool RuntimeImage::decoder_knows_end() const { return this->is_decoding() && thi
|
||||
|
||||
void RuntimeImage::release() {
|
||||
this->release_buffer_();
|
||||
if (this->front_buffer_) {
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
allocator.deallocate(this->front_buffer_, this->get_buffer_size(this->width_, this->height_));
|
||||
this->front_buffer_ = nullptr;
|
||||
this->data_start_ = nullptr;
|
||||
this->width_ = 0;
|
||||
this->height_ = 0;
|
||||
this->refresh_lvgl_();
|
||||
}
|
||||
// End any active decode session; decoders free the format-specific working buffers
|
||||
// they can (PNG), while the decoder object itself is kept warm for the next decode.
|
||||
if (this->decoder_) {
|
||||
@@ -291,6 +365,16 @@ void RuntimeImage::release() {
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeImage::abort_decode() {
|
||||
if (!this->is_double_buffered_()) {
|
||||
this->release();
|
||||
return;
|
||||
}
|
||||
if (this->decoder_) {
|
||||
this->decoder_->reset();
|
||||
}
|
||||
}
|
||||
|
||||
void RuntimeImage::release_buffer_() {
|
||||
if (this->buffer_) {
|
||||
if (this->external_buffer_) {
|
||||
@@ -302,19 +386,25 @@ void RuntimeImage::release_buffer_() {
|
||||
RAMAllocator<uint8_t> allocator;
|
||||
allocator.deallocate(this->buffer_, this->get_buffer_size(this->buffer_width_, this->buffer_height_));
|
||||
}
|
||||
const bool shown = this->data_start_ == this->buffer_;
|
||||
this->buffer_ = nullptr;
|
||||
this->data_start_ = nullptr;
|
||||
this->width_ = 0;
|
||||
this->height_ = 0;
|
||||
this->buffer_width_ = 0;
|
||||
this->buffer_height_ = 0;
|
||||
#ifdef USE_LVGL
|
||||
memset(&this->dsc_, 0, sizeof(this->dsc_));
|
||||
#endif
|
||||
// With double buffering the buffer decoded into is not the one on show
|
||||
if (shown) {
|
||||
this->data_start_ = nullptr;
|
||||
this->width_ = 0;
|
||||
this->height_ = 0;
|
||||
this->refresh_lvgl_();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool RuntimeImage::set_external_buffer(uint8_t *buffer, int width, int height) {
|
||||
if (this->is_double_buffered_()) {
|
||||
ESP_LOGE(TAG, "An external buffer cannot be double buffered");
|
||||
return false;
|
||||
}
|
||||
this->release_buffer_();
|
||||
if (buffer == nullptr || this->get_buffer_size(width, height) == 0) {
|
||||
// Keep the released state rather than remembering a buffer that cannot be decoded into: an
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
#include "esphome/components/image/image.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "image_decoder.h"
|
||||
#include "image_format.h"
|
||||
|
||||
@@ -54,6 +56,13 @@ class RuntimeImage : public image::Image {
|
||||
void map_chroma_key(Color &color);
|
||||
int get_buffer_width() const { return this->buffer_width_; }
|
||||
int get_buffer_height() const { return this->buffer_height_; }
|
||||
#ifdef USE_LVGL
|
||||
/// Until an image is loaded, the placeholder's descriptor if there is one
|
||||
lv_image_dsc_t *get_lv_image_dsc() override;
|
||||
#endif
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
void add_lvgl_widget(lv_obj_t *obj) override;
|
||||
#endif
|
||||
|
||||
// Image drawing interface
|
||||
void draw(int x, int y, display::Display *display, Color color_on, Color color_off) override;
|
||||
@@ -118,7 +127,9 @@ class RuntimeImage : public image::Image {
|
||||
/**
|
||||
* @brief Check if an image is currently loaded.
|
||||
*/
|
||||
bool is_loaded() const { return this->buffer_ != nullptr; }
|
||||
bool is_loaded() const {
|
||||
return this->is_double_buffered_() ? this->front_buffer_ != nullptr : this->buffer_ != nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Get the image format.
|
||||
@@ -134,6 +145,14 @@ class RuntimeImage : public image::Image {
|
||||
*/
|
||||
void release();
|
||||
|
||||
/**
|
||||
* @brief End a decode that will not be finished.
|
||||
*
|
||||
* With double buffering the image on show stays and the buffer decoded into is kept for the next
|
||||
* decode. Otherwise the same as release(), since the image on show was being decoded over.
|
||||
*/
|
||||
void abort_decode();
|
||||
|
||||
/**
|
||||
* @brief Decode into a buffer the caller owns, instead of one allocated here.
|
||||
*
|
||||
@@ -157,6 +176,8 @@ class RuntimeImage : public image::Image {
|
||||
* A null buffer or dimensions the image cannot decode at are refused, leaving the image with
|
||||
* no buffer at all.
|
||||
*
|
||||
* Refused when double buffering is on.
|
||||
*
|
||||
* @param buffer Memory for a picture of the given size, at least get_buffer_size() bytes.
|
||||
* @param width Width of the buffer in pixels.
|
||||
* @param height Height of the buffer in pixels.
|
||||
@@ -179,6 +200,13 @@ class RuntimeImage : public image::Image {
|
||||
*/
|
||||
void set_progressive_display(bool progressive) { this->progressive_display_ = progressive; }
|
||||
|
||||
/**
|
||||
* @brief Set whether to decode into a second buffer, keeping the image on show until the new one is complete.
|
||||
*
|
||||
* Both buffers are kept, so a new image of the same size allocates nothing. Ignored with progressive display.
|
||||
*/
|
||||
void set_double_buffer(bool double_buffer) { this->double_buffer_ = double_buffer; }
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Resize the image buffer to the requested dimensions.
|
||||
@@ -196,6 +224,11 @@ class RuntimeImage : public image::Image {
|
||||
*/
|
||||
void release_buffer_();
|
||||
|
||||
bool is_double_buffered_() const { return this->double_buffer_ && !this->progressive_display_; }
|
||||
|
||||
/// Bring the LVGL descriptor up to date after the image shown changed, and give it again to the widgets showing it
|
||||
void refresh_lvgl_();
|
||||
|
||||
/**
|
||||
* @brief Get the position in the buffer for a pixel.
|
||||
*/
|
||||
@@ -210,6 +243,8 @@ class RuntimeImage : public image::Image {
|
||||
|
||||
// Memory management
|
||||
uint8_t *buffer_{nullptr};
|
||||
/// With double buffering, the buffer on show, width_ by height_. buffer_ is then the one decoded into
|
||||
uint8_t *front_buffer_{nullptr};
|
||||
|
||||
// Decoder management
|
||||
std::unique_ptr<ImageDecoder> decoder_{nullptr};
|
||||
@@ -246,8 +281,14 @@ class RuntimeImage : public image::Image {
|
||||
|
||||
// Configuration
|
||||
bool progressive_display_{false};
|
||||
bool double_buffer_{false};
|
||||
/** Whether buffer_ belongs to the caller, so it must not be freed or resized here. */
|
||||
bool external_buffer_{false};
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
static void lvgl_widget_deleted(lv_event_t *event);
|
||||
/// Widgets given this image. One moved to another image is dropped at the next refresh
|
||||
std::vector<lv_obj_t *> lvgl_widgets_;
|
||||
#endif
|
||||
|
||||
friend class ImageDecoder;
|
||||
};
|
||||
|
||||
@@ -2,29 +2,22 @@ import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_SOURCE_ID
|
||||
from esphome.core.entity_helpers import FollowEntity
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import Switch, switch_ns
|
||||
from .. import Switch
|
||||
|
||||
CODEOWNERS = ["@ssieb"]
|
||||
|
||||
SwitchBinarySensor = switch_ns.class_(
|
||||
"SwitchBinarySensor", binary_sensor.BinarySensor, cg.Component
|
||||
_FOLLOW_SWITCH = FollowEntity("add_on_state_callback", ((cg.bool_, "x"),), "x")
|
||||
|
||||
CONFIG_SCHEMA = binary_sensor.binary_sensor_schema(binary_sensor.BinarySensor).extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(Switch),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
binary_sensor.binary_sensor_schema(SwitchBinarySensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(Switch),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
var = await binary_sensor.new_binary_sensor(config)
|
||||
await cg.register_component(var, config)
|
||||
|
||||
await cg.add_variable(var.set_source, config[CONF_SOURCE_ID])
|
||||
_FOLLOW_SWITCH(var, await cg.get_variable(config[CONF_SOURCE_ID]))
|
||||
@@ -1,15 +0,0 @@
|
||||
#include "switch_binary_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::switch_ {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "switch.binary_sensor");
|
||||
|
||||
void SwitchBinarySensor::setup() {
|
||||
source_->add_on_state_callback([this](bool value) { this->publish_state(value); });
|
||||
this->publish_state(source_->state);
|
||||
}
|
||||
|
||||
void SwitchBinarySensor::dump_config() { LOG_BINARY_SENSOR("", "Switch Binary Sensor", this); }
|
||||
|
||||
} // namespace esphome::switch_
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../switch.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
|
||||
namespace esphome::switch_ {
|
||||
|
||||
class SwitchBinarySensor final : public binary_sensor::BinarySensor, public Component {
|
||||
public:
|
||||
void set_source(Switch *source) { source_ = source; }
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
Switch *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::switch_
|
||||
@@ -4,23 +4,16 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_SOURCE_ID
|
||||
from esphome.types import ConfigType
|
||||
|
||||
from .. import Text, text_ns
|
||||
from .. import Text
|
||||
|
||||
TextTextSensor = text_ns.class_("TextTextSensor", text_sensor.TextSensor, cg.Component)
|
||||
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
text_sensor.text_sensor_schema(TextTextSensor)
|
||||
.extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(Text),
|
||||
}
|
||||
)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
CONFIG_SCHEMA = text_sensor.text_sensor_schema(text_sensor.TextSensor).extend(
|
||||
{
|
||||
cv.Required(CONF_SOURCE_ID): cv.use_id(Text),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config: ConfigType) -> None:
|
||||
source = await cg.get_variable(config[CONF_SOURCE_ID])
|
||||
var = await text_sensor.new_text_sensor(config, source)
|
||||
await cg.register_component(var, config)
|
||||
var = await text_sensor.new_text_sensor(config)
|
||||
# Text has the same string state callback as TextSensor
|
||||
text_sensor.follow_text_sensor(var, await cg.get_variable(config[CONF_SOURCE_ID]))
|
||||
@@ -1,16 +0,0 @@
|
||||
#include "text_text_sensor.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome::text {
|
||||
|
||||
ESPHOME_LOG_TAG(TAG, "text.text_sensor");
|
||||
|
||||
void TextTextSensor::setup() {
|
||||
this->source_->add_on_state_callback([this](const std::string &value) { this->publish_state(value); });
|
||||
if (this->source_->has_state())
|
||||
this->publish_state(this->source_->state);
|
||||
}
|
||||
|
||||
void TextTextSensor::dump_config() { LOG_TEXT_SENSOR("", "Text Text Sensor", this); }
|
||||
|
||||
} // namespace esphome::text
|
||||
@@ -1,19 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../text.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/text_sensor/text_sensor.h"
|
||||
|
||||
namespace esphome::text {
|
||||
|
||||
class TextTextSensor final : public text_sensor::TextSensor, public Component {
|
||||
public:
|
||||
explicit TextTextSensor(Text *source) : source_(source) {}
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
|
||||
protected:
|
||||
Text *source_;
|
||||
};
|
||||
|
||||
} // namespace esphome::text
|
||||
@@ -96,6 +96,7 @@
|
||||
#define USE_GPIO_SWITCH_INTERLOCK
|
||||
#define USE_GRAPH
|
||||
#define USE_GRAPHICAL_DISPLAY_MENU
|
||||
#define USE_GREE_FAHRENHEIT
|
||||
#define USE_GREE_YAW1F
|
||||
#define USE_HOERMANN_HCP_IDENTITY
|
||||
#define USE_HOMEASSISTANT_TIME
|
||||
@@ -201,6 +202,7 @@
|
||||
#define USE_REMOTE_PROTOCOL_BRENNENSTUHL
|
||||
#define USE_REMOTE_PROTOCOL_BYRONSX
|
||||
#define USE_REMOTE_PROTOCOL_CANALSAT
|
||||
#define USE_REMOTE_PROTOCOL_CARDIN_S438
|
||||
#define USE_REMOTE_PROTOCOL_COOLIX
|
||||
#define USE_REMOTE_PROTOCOL_DISH
|
||||
#define USE_REMOTE_PROTOCOL_DOOYA
|
||||
|
||||
@@ -483,13 +483,6 @@ uint32_t HOT Scheduler::call(uint32_t now) {
|
||||
// Not reached timeout yet, done for this call
|
||||
break;
|
||||
}
|
||||
// Don't run on failed components (is_item_failed_ exempts SELF_POINTER delays).
|
||||
if (this->is_item_failed_(item)) {
|
||||
LockGuard guard{this->lock_};
|
||||
this->recycle_item_main_loop_(this->pop_raw_locked_());
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if item is marked for removal
|
||||
// This handles two cases:
|
||||
// 1. Item was marked for removal after cleanup_() but before we got here
|
||||
@@ -514,6 +507,13 @@ uint32_t HOT Scheduler::call(uint32_t now) {
|
||||
}
|
||||
#endif
|
||||
|
||||
// Don't run on failed components (is_item_failed_ exempts SELF_POINTER delays).
|
||||
if (this->is_item_failed_(item)) {
|
||||
LockGuard guard{this->lock_};
|
||||
this->recycle_item_main_loop_(this->pop_raw_locked_());
|
||||
continue;
|
||||
}
|
||||
|
||||
#ifdef ESPHOME_DEBUG_SCHEDULER
|
||||
{
|
||||
SchedulerNameLog name_log;
|
||||
|
||||
+21
-9
@@ -326,6 +326,25 @@ def load_test_manifest_overrides(
|
||||
ManifestOverrideLoader = Callable[[list[str]], None]
|
||||
|
||||
|
||||
def resolve_dependencies_with_overrides(
|
||||
components: list[str], manifest_override_loader: ManifestOverrideLoader
|
||||
) -> list[str]:
|
||||
"""Return the components and all their dependencies, with overrides applied.
|
||||
|
||||
Overrides are applied before each dependency search, so dependencies added by
|
||||
any override_manifest(), including one of a component found only as a
|
||||
dependency, are included. The loader is called again with components it has
|
||||
already seen, so it must apply each override only once.
|
||||
"""
|
||||
resolved = sorted(components)
|
||||
while True:
|
||||
manifest_override_loader(resolved)
|
||||
expanded = sorted(get_all_dependencies(set(resolved)))
|
||||
if expanded == resolved:
|
||||
return resolved
|
||||
resolved = expanded
|
||||
|
||||
|
||||
def compile_and_get_binary(
|
||||
config: dict,
|
||||
components: list[str],
|
||||
@@ -366,19 +385,12 @@ def compile_and_get_binary(
|
||||
else:
|
||||
config.setdefault(key, value)
|
||||
|
||||
# Apply manifest overrides before dependency resolution so that any
|
||||
# dependency additions made by override_manifest() are picked up.
|
||||
manifest_override_loader(components)
|
||||
|
||||
# Obtain possible dependencies BEFORE validate_config, because
|
||||
# get_all_dependencies calls CORE.reset() which clears build_path.
|
||||
components_with_dependencies: list[str] = sorted(
|
||||
get_all_dependencies(set(components))
|
||||
components_with_dependencies = resolve_dependencies_with_overrides(
|
||||
components, manifest_override_loader
|
||||
)
|
||||
|
||||
# Apply overrides for any transitively discovered dependencies.
|
||||
manifest_override_loader(components_with_dependencies)
|
||||
|
||||
CORE.config_path = tests_dir / "dummy.yaml"
|
||||
CORE.dashboard = None
|
||||
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
namespace esphome::core::testing {
|
||||
@@ -41,4 +43,40 @@ TEST(Scheduler, TimeoutAddedByACancelledCallbackIsVisibleAfterCall) {
|
||||
scheduler.trim_freelist();
|
||||
}
|
||||
|
||||
// A cancelled timeout of a component that has since failed has to be counted as removed when call()
|
||||
// reaches it, or the leftover count makes the next few cancellations trigger a full cleanup early.
|
||||
TEST(Scheduler, CancelledItemOfFailedComponentIsCountedAsRemoved) {
|
||||
Scheduler scheduler;
|
||||
SchedulerTestComponent healthy;
|
||||
SchedulerTestComponent failing;
|
||||
|
||||
// The healthy item is due first, so cleanup_() stops at it and the cancelled item is only reached in the run loop
|
||||
scheduler.set_timeout(&healthy, "first", 1, []() {});
|
||||
scheduler.set_timeout(&failing, "cancelled", 2, []() {});
|
||||
scheduler.process_to_add();
|
||||
scheduler.cancel_timeout(&failing, "cancelled");
|
||||
failing.mark_failed();
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5));
|
||||
scheduler.call(millis());
|
||||
|
||||
// Four cancelled items behind a live one stay below the full cleanup threshold of five, so their callbacks are
|
||||
// released only once they reach the top of the heap
|
||||
auto token = std::make_shared<int>(0);
|
||||
scheduler.set_timeout(&healthy, "keep", 10000, []() {});
|
||||
static constexpr const char *NAMES[] = {"c0", "c1", "c2", "c3"};
|
||||
for (const char *name : NAMES)
|
||||
scheduler.set_timeout(&healthy, name, 20000, [token]() {});
|
||||
scheduler.process_to_add();
|
||||
for (const char *name : NAMES)
|
||||
scheduler.cancel_timeout(&healthy, name);
|
||||
ASSERT_EQ(token.use_count(), 5);
|
||||
scheduler.call(millis());
|
||||
EXPECT_EQ(token.use_count(), 5);
|
||||
|
||||
scheduler.cancel_timeout(&healthy, "keep");
|
||||
scheduler.call(millis());
|
||||
EXPECT_EQ(token.use_count(), 1);
|
||||
scheduler.trim_freelist();
|
||||
}
|
||||
|
||||
} // namespace esphome::core::testing
|
||||
@@ -11,6 +11,14 @@ namespace esphome::testing {
|
||||
// build_plain() compressed with the CLI's window (espota2.DEFLATE_WINDOW_BITS):
|
||||
// DEFLATED = zlib.compress(plain, 9, wbits=-12)
|
||||
// STORED = zlib.compress(plain[:300], 0, wbits=-12)
|
||||
// c = zlib.compressobj(9, zlib.DEFLATED, -12, 9, zlib.Z_FIXED)
|
||||
// FIXED = c.compress(plain) + c.flush()
|
||||
// and build_skewed(), whose rare bytes get codes longer than the lookup tables:
|
||||
// SKEWED = zlib.compress(skewed, 9, wbits=-12)
|
||||
// and build_mixed() in three parts, so stored blocks follow Huffman blocks:
|
||||
// c = zlib.compressobj(9, zlib.DEFLATED, -12)
|
||||
// MIXED = (c.compress(text) + c.flush(zlib.Z_SYNC_FLUSH) + c.compress(noise)
|
||||
// + c.flush(zlib.Z_SYNC_FLUSH) + c.compress(tail) + c.flush())
|
||||
static const uint8_t DEFLATED[] = {
|
||||
0xed, 0xc8, 0xf7, 0x3f, 0xd4, 0x0f, 0x03, 0x00, 0x70, 0x67, 0xaf, 0x4b, 0x67, 0x66, 0x9f, 0x90, 0x91, 0x11, 0xc2,
|
||||
0x11, 0x91, 0xb8, 0xb3, 0xf7, 0x3a, 0xd9, 0x5f, 0x4e, 0x99, 0x67, 0x1e, 0xce, 0x8a, 0xac, 0xec, 0x59, 0xb8, 0xc2,
|
||||
@@ -201,6 +209,532 @@ static const uint8_t STORED[] = {
|
||||
0x68, 0x6f, 0x6d, 0x65, 0x20, 0x6f, 0x74, 0x61, 0x20, 0x64, 0x65, 0x66, 0x6c, 0x61, 0x74, 0x65, 0x20,
|
||||
};
|
||||
|
||||
static const uint8_t FIXED[] = {
|
||||
0x4b, 0x2d, 0x2e, 0xc8, 0xc8, 0xcf, 0x4d, 0x55, 0xc8, 0x2f, 0x49, 0x54, 0x48, 0x49, 0x4d, 0xcb, 0x49, 0x2c, 0x49,
|
||||
0x55, 0x48, 0x1d, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b,
|
||||
0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15,
|
||||
0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x23, 0x49, 0xec, 0x58, 0x5d, 0x63, 0xb6, 0xf7, 0xef,
|
||||
0x47, 0xbf, 0x43, 0xbe, 0xed, 0xbd, 0x5f, 0x23, 0xf3, 0xb0, 0x9d, 0x71, 0xbf, 0xe1, 0xbd, 0xb0, 0x22, 0x7e, 0xf7,
|
||||
0xf4, 0xb4, 0xf6, 0xc8, 0x55, 0x1d, 0x36, 0x91, 0xaf, 0xc2, 0x84, 0xab, 0x2f, 0xb5, 0x2e, 0xbc, 0x61, 0x13, 0x12,
|
||||
0xaa, 0x6f, 0xbe, 0x2e, 0x35, 0xfa, 0x16, 0x53, 0xe5, 0x8c, 0x33, 0x8f, 0xa5, 0xca, 0xfa, 0xe2, 0x6f, 0xce, 0xec,
|
||||
0x97, 0xb7, 0x37, 0x7b, 0xe7, 0x5c, 0xe1, 0xcb, 0xfb, 0x6b, 0xdf, 0xb2, 0x5b, 0x4f, 0xda, 0xfa, 0xee, 0x68, 0xe6,
|
||||
0xfa, 0xfd, 0x0f, 0x7b, 0x58, 0xa0, 0xf0, 0xbb, 0x7f, 0x63, 0x04, 0xeb, 0x84, 0xa3, 0x9c, 0x77, 0x82, 0xcf, 0xae,
|
||||
0xb2, 0xf6, 0x98, 0x19, 0x74, 0x39, 0x68, 0x2e, 0xdb, 0xfc, 0x57, 0x5b, 0x0f, 0xb1, 0x09, 0xcf, 0xf0, 0xdc, 0xc4,
|
||||
0x28, 0xb7, 0xc6, 0xa8, 0xc3, 0x70, 0x4e, 0x90, 0xdb, 0xd4, 0x42, 0xb3, 0xfe, 0xf0, 0x6f, 0x96, 0xb2, 0x62, 0xbf,
|
||||
0x3a, 0x4a, 0xbe, 0xce, 0xa8, 0x11, 0x8f, 0x71, 0xdc, 0x9d, 0x5b, 0xd8, 0xc7, 0x5f, 0x10, 0x79, 0x9c, 0x51, 0x5a,
|
||||
0xdf, 0xd8, 0x76, 0xe2, 0x01, 0xd9, 0xa5, 0xbc, 0xbc, 0xab, 0x8d, 0x7b, 0xeb, 0xe2, 0xfa, 0xed, 0x9e, 0x65, 0x94,
|
||||
0x2c, 0xb3, 0xda, 0x78, 0x78, 0xb2, 0xe0, 0x8a, 0x94, 0xe3, 0xb7, 0x4f, 0x3d, 0x48, 0x78, 0xf8, 0x79, 0x3f, 0x27,
|
||||
0x43, 0xfa, 0xa2, 0xc7, 0xaa, 0x0b, 0x14, 0x0d, 0xdb, 0xaf, 0x26, 0x1d, 0x5d, 0xe1, 0x5f, 0xa7, 0xc7, 0x99, 0x3d,
|
||||
0x65, 0xfe, 0x86, 0xdc, 0x95, 0xf3, 0xa2, 0xb8, 0xdd, 0x0a, 0x1a, 0xb6, 0x9d, 0x77, 0xe7, 0x59, 0xb6, 0x54, 0xeb,
|
||||
0xc6, 0x9a, 0xdf, 0x0b, 0x5e, 0x6f, 0xaf, 0x54, 0x29, 0x52, 0x9e, 0xe4, 0xd1, 0x70, 0x74, 0xd9, 0xf2, 0xd6, 0xed,
|
||||
0xd7, 0x7b, 0x26, 0x3c, 0x59, 0x9d, 0xec, 0x12, 0x94, 0xf6, 0x78, 0x8e, 0xb1, 0xea, 0xcf, 0xb8, 0x55, 0xbb, 0x8a,
|
||||
0x13, 0x62, 0xbd, 0x0b, 0xeb, 0xf6, 0xad, 0xec, 0x09, 0x97, 0x2c, 0x3c, 0x7c, 0x2a, 0xee, 0xa9, 0x96, 0xf1, 0x9a,
|
||||
0x8e, 0xc0, 0xb4, 0x85, 0xd5, 0xa5, 0xe9, 0x29, 0xb3, 0x32, 0xdf, 0xe7, 0x87, 0x39, 0x2d, 0x90, 0x0d, 0x3c, 0xca,
|
||||
0xf4, 0x7d, 0xf7, 0x24, 0xd7, 0x7d, 0xf9, 0xbc, 0xdb, 0x2c, 0xce, 0x08, 0xfc, 0xdd, 0x1d, 0x12, 0x28, 0x53, 0xcd,
|
||||
0x3e, 0x45, 0x7d, 0x72, 0xed, 0xa4, 0xc3, 0x57, 0x8e, 0x2d, 0x4d, 0x0c, 0x64, 0xb4, 0xb0, 0x58, 0xbe, 0xff, 0x23,
|
||||
0x0b, 0xaf, 0xe8, 0xec, 0x06, 0xf9, 0xe6, 0xab, 0x4b, 0x32, 0x3b, 0x6a, 0xe6, 0x6f, 0x63, 0xbc, 0x35, 0x59, 0xdc,
|
||||
0xb5, 0x5b, 0x68, 0x13, 0x93, 0x71, 0x4c, 0xc0, 0xb5, 0x87, 0x61, 0x4b, 0xd6, 0x3a, 0x79, 0xc5, 0xdc, 0x6d, 0x4b,
|
||||
0x7c, 0xc9, 0x2c, 0xf4, 0x90, 0x7f, 0xf6, 0x2b, 0x35, 0x9d, 0xc4, 0x3b, 0x49, 0x1e, 0xd9, 0xb9, 0x22, 0x0f, 0x98,
|
||||
0x5b, 0xbd, 0x8a, 0xdc, 0x6e, 0x4d, 0x3b, 0x51, 0x2b, 0x73, 0x91, 0xd5, 0xee, 0xe9, 0xa4, 0x02, 0xe7, 0xf8, 0x1c,
|
||||
0x66, 0xd6, 0xcd, 0xaf, 0x37, 0x2b, 0x98, 0xfa, 0xd6, 0xa5, 0x05, 0x30, 0x9a, 0x1d, 0x30, 0x7e, 0xc8, 0x7f, 0xd2,
|
||||
0x42, 0xef, 0xa5, 0xa6, 0xa4, 0x7f, 0xdc, 0xc6, 0x8b, 0x9e, 0xdd, 0xd6, 0xc1, 0x7f, 0xe7, 0xdb, 0xbf, 0x53, 0x55,
|
||||
0x35, 0xad, 0xe6, 0x15, 0x5c, 0xef, 0x23, 0xd8, 0x63, 0x74, 0xe5, 0x56, 0xfd, 0x0d, 0xb1, 0xf0, 0x87, 0xcb, 0xce,
|
||||
0xd5, 0x1e, 0x5c, 0x97, 0xb4, 0x5f, 0xf8, 0x49, 0xbb, 0x8f, 0xd5, 0xc1, 0xcd, 0x3c, 0x91, 0x33, 0xdd, 0x23, 0xa2,
|
||||
0xf6, 0x56, 0xd4, 0xec, 0x0a, 0x60, 0x7c, 0x2b, 0xfd, 0xaa, 0xcb, 0xb3, 0xe3, 0xdd, 0x35, 0x91, 0xd6, 0xd5, 0x1b,
|
||||
0x74, 0xee, 0x99, 0x4e, 0x16, 0xd4, 0x65, 0x94, 0xb9, 0xae, 0xe1, 0x6c, 0xf0, 0x7c, 0x03, 0xc7, 0xdb, 0xca, 0x99,
|
||||
0xc2, 0x81, 0x97, 0xac, 0xca, 0xd7, 0xda, 0x6e, 0xf9, 0x71, 0xfc, 0x14, 0xb3, 0xd2, 0xa5, 0x93, 0xc7, 0xd4, 0xf9,
|
||||
0x59, 0xce, 0x55, 0xd9, 0x1f, 0xc8, 0xd0, 0x39, 0x5f, 0x94, 0xc5, 0x79, 0xc8, 0x89, 0xa1, 0x28, 0xce, 0xd1, 0xe4,
|
||||
0xc7, 0xb4, 0x4c, 0xfb, 0xbd, 0x56, 0x11, 0x13, 0xbb, 0x1f, 0x9e, 0x59, 0xb4, 0x71, 0xd2, 0xdd, 0xf2, 0x85, 0xa6,
|
||||
0xff, 0x3e, 0x7b, 0xef, 0xd9, 0xf8, 0xd8, 0x5c, 0x90, 0xf7, 0x78, 0xea, 0xbe, 0x8f, 0x89, 0x4f, 0xe3, 0xd8, 0xfe,
|
||||
0x9b, 0x1e, 0x2f, 0xeb, 0x8c, 0xfd, 0x92, 0xe7, 0x75, 0x66, 0x6b, 0x48, 0xdd, 0x47, 0xd1, 0x13, 0x0b, 0x66, 0xf6,
|
||||
0xc7, 0x14, 0x70, 0xbf, 0x17, 0x39, 0xf6, 0x94, 0x6b, 0x8e, 0xe4, 0x16, 0x59, 0x9f, 0x73, 0x61, 0x6c, 0x77, 0x9c,
|
||||
0x04, 0x55, 0x9f, 0x4f, 0xcb, 0xe7, 0x57, 0xb4, 0xbd, 0xff, 0x33, 0xdc, 0x9d, 0xf7, 0xbe, 0x76, 0xd6, 0x9f, 0xf2,
|
||||
0xde, 0xab, 0x2f, 0x6f, 0xfe, 0xdc, 0xfa, 0xe0, 0x75, 0x51, 0x8b, 0x54, 0x9f, 0x93, 0x88, 0x6c, 0x57, 0x5e, 0xfc,
|
||||
0x24, 0xab, 0xdf, 0xdc, 0x4f, 0xbf, 0x3d, 0x39, 0x30, 0xdf, 0xf5, 0x9a, 0x56, 0xf3, 0xfe, 0x8d, 0x67, 0xb3, 0x8e,
|
||||
0xec, 0xef, 0xb9, 0x77, 0x7f, 0xd3, 0xf7, 0xca, 0xef, 0x09, 0xe1, 0x7f, 0xac, 0x6d, 0xab, 0xf5, 0x4e, 0xcf, 0x71,
|
||||
0xac, 0x56, 0x5f, 0xfa, 0xd8, 0x23, 0x42, 0x94, 0x5d, 0xfc, 0xc1, 0xce, 0xd6, 0xf8, 0xe4, 0x15, 0xdf, 0x34, 0x85,
|
||||
0x9c, 0x19, 0xb2, 0x6e, 0xbf, 0x4b, 0x51, 0x09, 0xea, 0x3e, 0x62, 0x1d, 0xbb, 0xdb, 0x54, 0x62, 0xd1, 0xe5, 0xce,
|
||||
0xff, 0x9b, 0x16, 0x44, 0x1a, 0x7c, 0xba, 0x7d, 0xf5, 0xa0, 0x6f, 0x96, 0xb7, 0xd9, 0x9c, 0xd8, 0x8a, 0x67, 0x17,
|
||||
0x16, 0x4f, 0xe2, 0x7d, 0x3a, 0x41, 0x70, 0x43, 0x1b, 0xbf, 0xa3, 0x49, 0xc3, 0xb2, 0xce, 0xbd, 0x2f, 0xf5, 0x2b,
|
||||
0xdc, 0x79, 0x03, 0xa6, 0xb6, 0x4b, 0xef, 0x7f, 0x5c, 0x3f, 0xc5, 0xdc, 0xec, 0x49, 0xbe, 0xa5, 0x85, 0x3e, 0x4f,
|
||||
0xb3, 0x55, 0xeb, 0xfd, 0xc0, 0x3d, 0x1e, 0x37, 0xc3, 0x76, 0x57, 0x4e, 0xad, 0xdc, 0x7b, 0xc5, 0x23, 0x60, 0xee,
|
||||
0xca, 0xd4, 0x58, 0xf1, 0x1a, 0x61, 0x6e, 0xa1, 0x18, 0xff, 0xf4, 0x0d, 0x2c, 0x0f, 0xe7, 0x49, 0x6c, 0x66, 0xb0,
|
||||
0xfa, 0x77, 0xfa, 0x88, 0xcc, 0x77, 0xed, 0x00, 0x8b, 0x3a, 0xbf, 0xdd, 0xc2, 0x47, 0x15, 0x0e, 0xff, 0xb3, 0x5d,
|
||||
0x62, 0xc0, 0xff, 0xc4, 0x9d, 0xeb, 0x49, 0x10, 0x63, 0x95, 0x78, 0xa3, 0x61, 0x43, 0x43, 0xbc, 0x69, 0x94, 0xae,
|
||||
0xe8, 0x99, 0x0d, 0x4a, 0xa2, 0xba, 0x0d, 0x17, 0x9f, 0x3d, 0x39, 0x13, 0xb1, 0x3e, 0x9f, 0xb5, 0xb6, 0x75, 0x8e,
|
||||
0x69, 0x56, 0xc9, 0x82, 0x0f, 0x1a, 0xfe, 0xdf, 0x7f, 0xde, 0xb1, 0x60, 0xd8, 0x7c, 0xe4, 0x5d, 0x88, 0xdf, 0xc7,
|
||||
0x9b, 0xaf, 0xd6, 0x1e, 0xba, 0xfe, 0x5a, 0x52, 0xe5, 0x48, 0xd8, 0x8a, 0xee, 0xd3, 0x21, 0xd9, 0xeb, 0x0b, 0x22,
|
||||
0xa2, 0xd8, 0x23, 0xff, 0x31, 0xb0, 0xdd, 0x5f, 0xf8, 0x4c, 0x22, 0x72, 0xd7, 0xc1, 0x68, 0xe5, 0x3f, 0xd1, 0x72,
|
||||
0x05, 0x06, 0x4e, 0x52, 0x57, 0x2e, 0x18, 0x15, 0x4d, 0x48, 0x73, 0xca, 0x99, 0xbb, 0xe8, 0xe2, 0xdb, 0x72, 0x3b,
|
||||
0x83, 0x6d, 0xeb, 0x26, 0xf1, 0x26, 0xea, 0x7d, 0x5b, 0x24, 0xe5, 0x79, 0x7b, 0xa1, 0x6c, 0xe7, 0x8a, 0x7b, 0x9f,
|
||||
0x2c, 0xc2, 0x76, 0x65, 0xaf, 0x3e, 0x15, 0x1c, 0x15, 0xfc, 0x2d, 0x57, 0xb8, 0x71, 0x9d, 0xfc, 0xd2, 0x3f, 0x5e,
|
||||
0xb6, 0xd7, 0x5d, 0x19, 0x3b, 0x9f, 0x2c, 0x61, 0x98, 0xf1, 0xed, 0xb7, 0x6f, 0x5b, 0x8a, 0x5b, 0x7c, 0xe4, 0x9a,
|
||||
0xaf, 0x95, 0x66, 0xfa, 0xaf, 0x4e, 0xb9, 0xad, 0x0f, 0x70, 0x4b, 0xeb, 0x54, 0x74, 0x9a, 0xb8, 0xb1, 0xec, 0x12,
|
||||
0x6f, 0x51, 0xef, 0xe3, 0x88, 0xc7, 0x73, 0xc4, 0x2f, 0x6a, 0x44, 0x24, 0xab, 0xe7, 0xb9, 0x64, 0x37, 0x2d, 0xd9,
|
||||
0x35, 0xa3, 0xf8, 0xd7, 0xee, 0xff, 0x73, 0xa4, 0xca, 0x3e, 0xc9, 0x6b, 0xce, 0x4c, 0x3a, 0x51, 0x13, 0xfd, 0xfb,
|
||||
0xa7, 0x94, 0xdb, 0xdf, 0xc8, 0x6f, 0x47, 0x6f, 0xdb, 0xbc, 0x2c, 0x9c, 0x76, 0xa1, 0x50, 0xe6, 0x06, 0xaf, 0xce,
|
||||
0xcc, 0x0b, 0x51, 0x42, 0x81, 0x17, 0x18, 0x4a, 0xdb, 0x57, 0xf8, 0xef, 0x4a, 0x3b, 0x30, 0xe9, 0xea, 0x85, 0xef,
|
||||
0x5b, 0xda, 0x9e, 0xda, 0xaf, 0x0f, 0x0d, 0xfd, 0xba, 0xc3, 0x2f, 0x8d, 0x51, 0xa7, 0xf6, 0xc8, 0x26, 0x0b, 0x0d,
|
||||
0x9e, 0x30, 0xef, 0xf3, 0xe2, 0x73, 0x6c, 0x9f, 0xb0, 0xaf, 0xb6, 0xf1, 0x12, 0xfa, 0x57, 0x3d, 0x41, 0x90, 0x6d,
|
||||
0xe6, 0xab, 0xe3, 0xb5, 0x17, 0x3f, 0x7f, 0xea, 0x79, 0xae, 0x2a, 0x32, 0xe7, 0x9c, 0xc8, 0xbf, 0x3f, 0x92, 0xb9,
|
||||
0x8a, 0xe6, 0x1a, 0x9b, 0xa6, 0x18, 0xf3, 0x6f, 0x7e, 0xc2, 0xe5, 0x7a, 0x7a, 0xfe, 0x0a, 0xc1, 0x07, 0xf3, 0x35,
|
||||
0xb7, 0x48, 0x88, 0xbf, 0x0f, 0x8f, 0x89, 0x6f, 0xdb, 0xdc, 0x5b, 0x6f, 0xd9, 0xd4, 0x59, 0x5b, 0xb8, 0xf2, 0x4e,
|
||||
0xfa, 0x05, 0x25, 0x37, 0x79, 0xc1, 0xd5, 0x1f, 0x5f, 0xce, 0x33, 0xc8, 0xdf, 0xf6, 0xee, 0x67, 0xa9, 0xde, 0xd2,
|
||||
0x29, 0x91, 0xf5, 0x99, 0x0d, 0xbe, 0x2f, 0x5a, 0xe7, 0x45, 0xb2, 0x38, 0x44, 0x48, 0x5d, 0xff, 0xdd, 0x67, 0x33,
|
||||
0x8b, 0xd7, 0x75, 0xa7, 0x5b, 0x3c, 0xdf, 0xb9, 0x47, 0xc7, 0x2c, 0x0e, 0xf5, 0xaa, 0x6c, 0x0d, 0xf3, 0xb6, 0x3d,
|
||||
0xcb, 0xdd, 0x1f, 0xd9, 0x22, 0xd6, 0x33, 0xff, 0x8c, 0x8a, 0x8d, 0x4e, 0xf6, 0x39, 0xdd, 0x6f, 0xab, 0x6e, 0xf1,
|
||||
0xa5, 0x1c, 0x36, 0xff, 0xbb, 0x92, 0x63, 0x7b, 0xdf, 0x93, 0xcb, 0x5d, 0xb3, 0x7f, 0x1a, 0xd6, 0x9d, 0xd3, 0xf5,
|
||||
0xfd, 0xf1, 0xbe, 0x79, 0xde, 0xc6, 0x77, 0xb7, 0x2e, 0x18, 0x6d, 0x38, 0x5c, 0xcc, 0x3b, 0x4b, 0x25, 0xed, 0xe1,
|
||||
0xbd, 0x3e, 0x26, 0xee, 0x8e, 0x58, 0x36, 0x1d, 0xf7, 0xa9, 0xae, 0xf1, 0x7f, 0xca, 0x05, 0xcd, 0x59, 0x9e, 0xa5,
|
||||
0x55, 0xbb, 0xd5, 0x5e, 0x5b, 0xf8, 0x3b, 0xd7, 0x82, 0xdb, 0x41, 0x5c, 0x80, 0x39, 0x36, 0x77, 0x6f, 0xc5, 0x65,
|
||||
0xce, 0xd4, 0x32, 0x75, 0xae, 0x85, 0xe9, 0x85, 0x9b, 0x9e, 0x73, 0x2f, 0x3e, 0xb0, 0xdb, 0x72, 0x56, 0xdf, 0xd4,
|
||||
0xe0, 0x67, 0xaf, 0x27, 0x76, 0x45, 0x6d, 0xbb, 0x79, 0x3d, 0xc8, 0xfe, 0xd2, 0x96, 0xe3, 0xb1, 0x9c, 0xf3, 0x44,
|
||||
0xfc, 0xef, 0xf8, 0xb4, 0x06, 0xbf, 0x58, 0xb3, 0x94, 0xc3, 0x6c, 0x91, 0x4b, 0x8b, 0x4a, 0x83, 0x97, 0xa9, 0xa8,
|
||||
0xb3, 0x7d, 0xc5, 0x8d, 0xc9, 0xd3, 0x7e, 0xdf, 0xac, 0xdc, 0x73, 0x99, 0xeb, 0xde, 0xd3, 0x98, 0xfe, 0xe3, 0x13,
|
||||
0xaf, 0xe8, 0x04, 0x3d, 0xd8, 0x9e, 0x5f, 0x30, 0xfb, 0xc6, 0xdc, 0x84, 0x7f, 0x2e, 0xb1, 0xef, 0xdd, 0xaf, 0xa9,
|
||||
0x15, 0xfe, 0x9f, 0x95, 0x55, 0xcb, 0xfd, 0xa8, 0x3e, 0xa7, 0x50, 0x2b, 0x68, 0xc2, 0xeb, 0xb5, 0xa7, 0x4c, 0xf5,
|
||||
0x0e, 0xff, 0x8d, 0xfc, 0xce, 0x28, 0xaa, 0x75, 0x8b, 0x9f, 0xd1, 0xe5, 0x94, 0xfb, 0xed, 0xe5, 0xe9, 0xc2, 0x32,
|
||||
0x55, 0xdc, 0xcc, 0x4d, 0x8d, 0x93, 0x37, 0xee, 0x49, 0x78, 0x1b, 0x7a, 0xbb, 0x37, 0x4d, 0xbd, 0x52, 0x6c, 0x63,
|
||||
0xc5, 0x72, 0x89, 0x6d, 0xfd, 0x33, 0x7e, 0x2b, 0xb8, 0xf0, 0xe5, 0x2d, 0x8d, 0xeb, 0x50, 0x13, 0x59, 0xa7, 0x11,
|
||||
0xa6, 0xf0, 0x22, 0xed, 0xed, 0x3b, 0x97, 0xf2, 0x49, 0x09, 0x37, 0xaa, 0x13, 0xe4, 0xb7, 0x64, 0x26, 0x66, 0xef,
|
||||
0xde, 0x7d, 0x66, 0x91, 0xcb, 0xcd, 0x7f, 0x13, 0x4b, 0xdc, 0xac, 0xea, 0x22, 0x7b, 0x14, 0x3f, 0x1e, 0x7f, 0xf1,
|
||||
0xc1, 0xed, 0xf3, 0xb6, 0xea, 0x2f, 0x17, 0x67, 0xbf, 0x9d, 0xad, 0x5b, 0x62, 0x7b, 0xbe, 0x9b, 0x31, 0x7f, 0xf9,
|
||||
0x81, 0xc0, 0x7e, 0x16, 0xdf, 0xb7, 0x79, 0x0b, 0xeb, 0x8e, 0x48, 0xdf, 0x77, 0xbf, 0xa5, 0xe0, 0x77, 0x73, 0x7d,
|
||||
0xd3, 0x6f, 0xf6, 0x82, 0xb2, 0x9a, 0x98, 0x07, 0x8f, 0xf7, 0xfc, 0x60, 0x99, 0xd3, 0xae, 0x3f, 0x31, 0xea, 0xca,
|
||||
0x03, 0xb1, 0xaa, 0x6b, 0x53, 0x5f, 0xd6, 0x1c, 0x8c, 0xbf, 0x6c, 0x1e, 0x93, 0x5f, 0x7d, 0xd7, 0xbb, 0x64, 0xf2,
|
||||
0x66, 0xa9, 0x1d, 0x47, 0x7a, 0x39, 0x7f, 0x45, 0x71, 0xcd, 0xe2, 0x5c, 0x3f, 0xf5, 0xb6, 0x6a, 0xa4, 0x78, 0x89,
|
||||
0xa8, 0x70, 0x4d, 0x3e, 0x47, 0xce, 0x9b, 0x53, 0x3a, 0x86, 0xc7, 0xf6, 0x09, 0xcc, 0x8e, 0x39, 0xbb, 0xcb, 0xb2,
|
||||
0xb0, 0xaf, 0x63, 0x4e, 0xb1, 0xc5, 0xf1, 0x23, 0x15, 0x1f, 0x44, 0x7d, 0x7f, 0x5f, 0x2a, 0x8f, 0x0c, 0x3e, 0x68,
|
||||
0x69, 0xf3, 0xfd, 0x7d, 0xe7, 0xab, 0x62, 0xed, 0x4b, 0x8a, 0x9a, 0xef, 0x6e, 0x86, 0x9d, 0x50, 0x99, 0x95, 0xd8,
|
||||
0xb7, 0xeb, 0xc1, 0x0b, 0xdb, 0xd7, 0xcb, 0xbb, 0xef, 0x79, 0x1b, 0x5e, 0xb1, 0x9c, 0xf0, 0xea, 0x2e, 0xbf, 0xf2,
|
||||
0xdf, 0x7d, 0x72, 0xd9, 0x9b, 0xf6, 0x5e, 0xba, 0xd2, 0x67, 0x7a, 0x7a, 0xa1, 0xa6, 0x93, 0x70, 0xf9, 0x59, 0x23,
|
||||
0xe9, 0xa5, 0x97, 0x57, 0x57, 0x99, 0xec, 0x9b, 0x93, 0xb6, 0x49, 0xe4, 0xc9, 0xbb, 0xfd, 0x0c, 0x47, 0xff, 0x86,
|
||||
0xac, 0x64, 0xe7, 0xbf, 0x16, 0xf0, 0x66, 0xc3, 0x7d, 0x9d, 0xeb, 0x3b, 0x8f, 0xb3, 0xee, 0xf6, 0xfa, 0x32, 0xc9,
|
||||
0xa5, 0xed, 0xd9, 0x94, 0x13, 0x82, 0x8c, 0xeb, 0xdf, 0x78, 0x4b, 0x72, 0x8b, 0x79, 0x1e, 0x58, 0x37, 0x6d, 0xba,
|
||||
0xff, 0xf4, 0xc9, 0x1b, 0xb6, 0xce, 0xde, 0x6e, 0xc5, 0xc4, 0x38, 0x8b, 0x69, 0xd3, 0x9d, 0x0f, 0xbb, 0x76, 0x69,
|
||||
0x17, 0x96, 0x84, 0x6c, 0xec, 0xbe, 0xdb, 0x3d, 0xf5, 0x94, 0x55, 0xeb, 0x2e, 0x66, 0x95, 0x29, 0x77, 0x5c, 0x98,
|
||||
0x7f, 0xe7, 0x57, 0xfb, 0x34, 0x58, 0xbc, 0xac, 0xda, 0xb6, 0xc2, 0xe5, 0x1c, 0xfb, 0xef, 0xf5, 0xc7, 0x9a, 0x45,
|
||||
0xa3, 0x62, 0x73, 0xc4, 0x7f, 0x72, 0xd4, 0x1e, 0x79, 0x96, 0xfb, 0x6f, 0xba, 0xe8, 0xc3, 0xce, 0x05, 0xbb, 0x57,
|
||||
0xcc, 0x54, 0xda, 0xf7, 0xe6, 0xc6, 0xbb, 0xdb, 0x95, 0xaa, 0x75, 0x33, 0xed, 0xd2, 0x2f, 0x7c, 0x6c, 0x11, 0xe1,
|
||||
0xd8, 0xf5, 0xba, 0xe1, 0xe8, 0x35, 0xcd, 0x67, 0xf6, 0xf2, 0x4d, 0x16, 0xaa, 0xfc, 0xec, 0xcb, 0x2c, 0x0c, 0xfb,
|
||||
0x3e, 0x2c, 0xf7, 0x2e, 0xcd, 0xd1, 0xf4, 0x10, 0xbb, 0x76, 0xf7, 0x05, 0xc7, 0xed, 0x87, 0x6a, 0xd2, 0x29, 0x5b,
|
||||
0x72, 0x84, 0x16, 0xfb, 0x54, 0xca, 0xdd, 0xfb, 0xc6, 0x27, 0xff, 0xef, 0x53, 0xcc, 0xac, 0x6b, 0xa7, 0x74, 0x2b,
|
||||
0x4c, 0xcb, 0xae, 0xb4, 0xac, 0x32, 0xbc, 0xb6, 0x48, 0x4a, 0x32, 0xf4, 0x02, 0x73, 0xa7, 0xc3, 0xbd, 0x5e, 0x73,
|
||||
0x9b, 0xb7, 0xa1, 0x3c, 0x81, 0x2b, 0x7f, 0x1e, 0x0b, 0x75, 0x4b, 0x0e, 0x90, 0xb4, 0xb9, 0x76, 0x27, 0xf4, 0xe0,
|
||||
0xae, 0x63, 0xc1, 0xdc, 0xea, 0x9a, 0x71, 0x4e, 0xc6, 0xb6, 0xaf, 0xdc, 0xff, 0x94, 0x37, 0xa8, 0x97, 0xc4, 0x15,
|
||||
0xc4, 0xbe, 0xd7, 0x37, 0x39, 0x9d, 0x67, 0xb0, 0xac, 0x7c, 0xe5, 0x95, 0x47, 0x6c, 0xf7, 0xa6, 0xfc, 0xfc, 0x19,
|
||||
0xd3, 0x11, 0xb5, 0xf2, 0xdc, 0x71, 0x46, 0x66, 0x8f, 0x96, 0x58, 0x16, 0xe1, 0xa7, 0x4c, 0x9f, 0x2d, 0x17, 0x09,
|
||||
0x27, 0x9d, 0x4f, 0xca, 0x7d, 0xc4, 0x7a, 0x6d, 0x4a, 0xe7, 0xfb, 0x89, 0x71, 0xaa, 0x02, 0xeb, 0x12, 0x6d, 0x57,
|
||||
0x2b, 0xc8, 0x9e, 0x3e, 0x75, 0xfd, 0xd5, 0x1e, 0xee, 0x19, 0x0b, 0x94, 0x75, 0x67, 0x72, 0x38, 0xc6, 0xdd, 0x67,
|
||||
0x35, 0x73, 0x8a, 0x68, 0x6a, 0x3e, 0xbc, 0x43, 0xda, 0x7d, 0xb6, 0x48, 0xb7, 0xe9, 0x62, 0xfb, 0x1b, 0x11, 0x37,
|
||||
0x5f, 0x38, 0xb4, 0x19, 0x15, 0x3c, 0x5c, 0xaa, 0xda, 0xa3, 0xb3, 0xf0, 0xcb, 0x7c, 0x0e, 0xcd, 0x1d, 0x57, 0x8e,
|
||||
0xea, 0x98, 0xfc, 0x3f, 0x2e, 0x1e, 0x66, 0xf8, 0xfe, 0x74, 0x8f, 0xec, 0x89, 0x84, 0xb3, 0x7a, 0x65, 0x73, 0x92,
|
||||
0x52, 0xe2, 0x0d, 0x2b, 0x3e, 0x4d, 0xdb, 0x95, 0x3e, 0x93, 0xa7, 0xe4, 0xc0, 0xa1, 0xd2, 0x3d, 0x92, 0x71, 0xbf,
|
||||
0x7d, 0xa6, 0xd7, 0x2d, 0x33, 0x75, 0xd8, 0xd8, 0x36, 0xe7, 0x9f, 0xe2, 0x52, 0x93, 0xa2, 0x9c, 0x0d, 0xf5, 0x75,
|
||||
0xce, 0xf1, 0x47, 0x26, 0x1e, 0x5d, 0x75, 0x7e, 0xe3, 0xcc, 0x55, 0xd9, 0x5e, 0x67, 0xfd, 0x17, 0xec, 0x28, 0x3a,
|
||||
0xbe, 0x76, 0xda, 0x97, 0x95, 0x47, 0x8e, 0x58, 0x3d, 0xed, 0xb0, 0x6a, 0xcc, 0x75, 0x9f, 0xf0, 0x63, 0xfe, 0x77,
|
||||
0x4f, 0x99, 0xca, 0x4f, 0x8f, 0x2f, 0x55, 0x17, 0xce, 0x77, 0x8b, 0x3e, 0x25, 0xd0, 0x9a, 0x9d, 0x99, 0x76, 0xe7,
|
||||
0xf4, 0x84, 0x8a, 0x0f, 0x7e, 0xe7, 0x26, 0xcf, 0xd2, 0x75, 0x60, 0xe9, 0xc8, 0xeb, 0x4e, 0x9f, 0x2a, 0x34, 0x75,
|
||||
0xdd, 0x63, 0x46, 0xb6, 0x0f, 0xfb, 0xb6, 0x35, 0x7d, 0x37, 0x58, 0xb5, 0xb8, 0x23, 0xa5, 0x69, 0x47, 0xc1, 0xee,
|
||||
0xef, 0xf6, 0xc1, 0x1b, 0x3a, 0x55, 0x4c, 0x72, 0x1e, 0xef, 0x38, 0xac, 0xc1, 0x5b, 0x90, 0xe5, 0x1e, 0x92, 0x24,
|
||||
0xa6, 0xff, 0xb3, 0xfe, 0xd8, 0xeb, 0x39, 0x13, 0xaf, 0x34, 0xa5, 0x7d, 0x61, 0x13, 0xf9, 0x29, 0x12, 0xb2, 0x4b,
|
||||
0x72, 0x55, 0xb9, 0xb4, 0xf8, 0x56, 0xb3, 0x73, 0x8c, 0x56, 0x05, 0x25, 0xdd, 0x7b, 0x5e, 0x4c, 0xd8, 0x53, 0x7d,
|
||||
0x59, 0x37, 0x22, 0x47, 0x59, 0x4f, 0xf0, 0xf7, 0xc4, 0xe2, 0x9c, 0xe6, 0xdc, 0x8d, 0x25, 0x9d, 0xaa, 0x7c, 0x4a,
|
||||
0x7b, 0xa6, 0xd7, 0xb7, 0xaf, 0x15, 0x7b, 0xb4, 0xdf, 0xcf, 0xee, 0xd6, 0x34, 0x9d, 0x8a, 0xbc, 0xef, 0x39, 0x3e,
|
||||
0x89, 0x92, 0xed, 0x59, 0xfe, 0xe9, 0x47, 0xcb, 0x7a, 0x8c, 0xab, 0xa7, 0xed, 0x91, 0x66, 0xd9, 0x6b, 0x64, 0xde,
|
||||
0xb0, 0x8d, 0x93, 0xc3, 0xff, 0x03, 0xab, 0x82, 0x0f, 0x8f, 0xfa, 0xc4, 0x93, 0xea, 0x42, 0x62, 0xfe, 0xcf, 0x15,
|
||||
0x58, 0x84, 0xdc, 0x9d, 0xdf, 0x99, 0x29, 0xcf, 0x93, 0x3e, 0xd2, 0x7c, 0x7f, 0x65, 0xd6, 0xf2, 0xa4, 0xda, 0x6f,
|
||||
0xd7, 0xd8, 0x3f, 0x4d, 0x7c, 0x7e, 0xde, 0x76, 0x77, 0x8f, 0xe5, 0x22, 0xae, 0xa4, 0xf8, 0x88, 0x45, 0x3b, 0xcf,
|
||||
0xdb, 0x16, 0x2b, 0x6b, 0x59, 0x99, 0xed, 0xab, 0x2f, 0x51, 0x75, 0x4c, 0x35, 0xaf, 0x3d, 0x79, 0x6b, 0xf1, 0x96,
|
||||
0xc4, 0xfb, 0x22, 0x55, 0xc1, 0xe7, 0x83, 0x8b, 0x0c, 0x9b, 0xeb, 0xd6, 0x2e, 0xd2, 0xd8, 0xfe, 0xfa, 0xdd, 0xed,
|
||||
0xfe, 0x09, 0xee, 0x9c, 0x86, 0xe9, 0x77, 0x3b, 0xfd, 0xa4, 0x16, 0x94, 0x9d, 0xc9, 0xbd, 0x7c, 0x4e, 0xcf, 0x2e,
|
||||
0xf4, 0xcc, 0x41, 0xdb, 0x75, 0xac, 0x3d, 0xd9, 0x77, 0x5c, 0xbe, 0x86, 0xdc, 0x51, 0x37, 0xe6, 0xfe, 0x5e, 0x56,
|
||||
0xfe, 0xea, 0xa2, 0xdb, 0x2d, 0x76, 0xe3, 0x4b, 0xac, 0xa5, 0x93, 0x45, 0x16, 0xd9, 0x67, 0xaa, 0xb7, 0xa9, 0x1a,
|
||||
0xc6, 0xcc, 0x16, 0xb1, 0x93, 0xeb, 0x4f, 0x77, 0x6d, 0xb3, 0x7b, 0xb0, 0x74, 0xef, 0x83, 0x94, 0x72, 0xd1, 0x4f,
|
||||
0x55, 0x51, 0xcf, 0x9e, 0x36, 0xfd, 0xd6, 0xea, 0xdc, 0x9a, 0x7e, 0xc3, 0xe2, 0x81, 0x00, 0xe3, 0xa1, 0x07, 0x0b,
|
||||
0x56, 0xcb, 0xdc, 0x69, 0x2c, 0x3c, 0xee, 0xaa, 0x79, 0x9d, 0xa9, 0x5f, 0x2d, 0x6f, 0xfd, 0x9a, 0xb7, 0xab, 0x59,
|
||||
0x7d, 0x93, 0x3a, 0x7e, 0x5f, 0x57, 0xbf, 0x51, 0xa0, 0xbe, 0x34, 0x5b, 0x8a, 0xed, 0xf6, 0x53, 0xd3, 0xec, 0x97,
|
||||
0xf1, 0xcf, 0x8b, 0xa3, 0x77, 0x97, 0x2a, 0x9a, 0x1d, 0xd7, 0xb8, 0x7a, 0x54, 0xf2, 0x6f, 0x9f, 0xc2, 0x12, 0xfe,
|
||||
0xfb, 0x8d, 0x72, 0x47, 0x37, 0xb9, 0x09, 0xed, 0x3e, 0x70, 0xf4, 0x48, 0xd1, 0x99, 0x2b, 0x27, 0xb6, 0x2d, 0x12,
|
||||
0x4e, 0xbc, 0x7c, 0x99, 0x61, 0x56, 0xb9, 0xee, 0x0e, 0xb5, 0xd4, 0xbf, 0x7d, 0x3f, 0xef, 0xb3, 0xf8, 0x2e, 0xbe,
|
||||
0x60, 0xe5, 0x9b, 0xbf, 0xd7, 0xc5, 0xc2, 0x3e, 0x6b, 0xfb, 0x2c, 0x55, 0x1f, 0x2e, 0xad, 0x43, 0x9b, 0x62, 0x6f,
|
||||
0xc5, 0x6f, 0x7c, 0xb3, 0xe0, 0xec, 0x95, 0x40, 0xa6, 0x3f, 0x0d, 0xb5, 0x29, 0x71, 0x9a, 0xa5, 0x82, 0x3f, 0xcd,
|
||||
0xcf, 0x70, 0xfb, 0x6d, 0x7b, 0x32, 0xf9, 0xdd, 0x81, 0xc7, 0xe5, 0x57, 0x57, 0x95, 0xcb, 0xf5, 0x7e, 0xf4, 0x0a,
|
||||
0x61, 0x7e, 0xec, 0x72, 0x4d, 0x83, 0xb7, 0x65, 0xae, 0x7b, 0x86, 0xcc, 0x6b, 0x4d, 0xa7, 0x3e, 0xb1, 0x57, 0xef,
|
||||
0xb8, 0xc4, 0x67, 0x6a, 0xa7, 0x5e, 0x9a, 0x1c, 0x7a, 0x53, 0x79, 0x8e, 0x6f, 0xfd, 0x9a, 0x43, 0x9a, 0xbd, 0x89,
|
||||
0x9f, 0xe6, 0xf8, 0x5f, 0x08, 0x57, 0x79, 0x6e, 0x58, 0xf6, 0xed, 0x88, 0x11, 0xf7, 0xca, 0xc7, 0xf5, 0x0b, 0x97,
|
||||
0x9c, 0x10, 0xfe, 0xec, 0x69, 0xed, 0xc8, 0x7a, 0xef, 0x70, 0x23, 0x93, 0xc3, 0x93, 0xdf, 0x57, 0xdc, 0xc4, 0xaf,
|
||||
0xb6, 0x7e, 0xeb, 0xb5, 0x8d, 0xcc, 0x5a, 0x62, 0xba, 0xbc, 0x41, 0x4d, 0xa9, 0xda, 0xd5, 0x45, 0xe8, 0x9d, 0x7c,
|
||||
0xe8, 0x05, 0x87, 0x3d, 0x7e, 0xab, 0x76, 0xed, 0x63, 0xf8, 0xeb, 0xa6, 0x2c, 0xda, 0x7a, 0xaa, 0xec, 0x44, 0x9a,
|
||||
0xe7, 0xf6, 0xf4, 0x9f, 0x3b, 0xb9, 0xbb, 0xc2, 0x17, 0x29, 0x0b, 0xae, 0xa8, 0xce, 0xbb, 0xaf, 0x79, 0xe4, 0x5f,
|
||||
0xc4, 0xbf, 0x47, 0xd6, 0xb3, 0xf7, 0xee, 0xf7, 0xaa, 0xdf, 0x26, 0x67, 0xed, 0xcf, 0xb2, 0xef, 0xab, 0x5b, 0xa8,
|
||||
0xd9, 0xc3, 0x93, 0x95, 0xdd, 0x91, 0x69, 0xab, 0x27, 0x9d, 0x9f, 0xb0, 0x88, 0x73, 0xaf, 0x44, 0x78, 0x71, 0x40,
|
||||
0xf6, 0xd4, 0xed, 0x57, 0xfa, 0x9c, 0xca, 0xef, 0xdd, 0xca, 0x37, 0xb4, 0x2e, 0x57, 0x94, 0x98, 0x33, 0xf3, 0xee,
|
||||
0x86, 0xbf, 0xb5, 0x5f, 0x75, 0x6e, 0x29, 0x4d, 0x6d, 0x17, 0x3c, 0xd1, 0x9b, 0x32, 0x55, 0xeb, 0xce, 0xf2, 0x9b,
|
||||
0x55, 0x0c, 0xe7, 0xbf, 0x29, 0x30, 0x48, 0x2d, 0x32, 0x36, 0x0d, 0xbf, 0xb8, 0xe5, 0x4e, 0xa7, 0x46, 0x6f, 0xac,
|
||||
0x83, 0x9a, 0x4f, 0xe4, 0x76, 0xa1, 0x79, 0x65, 0xeb, 0x16, 0xf5, 0xbd, 0x62, 0x2b, 0xa8, 0x3e, 0x21, 0x27, 0x7b,
|
||||
0x9b, 0xf7, 0x83, 0xd3, 0xa6, 0xac, 0x57, 0xd2, 0x17, 0xce, 0xf7, 0x7e, 0x3a, 0xbb, 0xe7, 0xb8, 0xef, 0x51, 0x77,
|
||||
0xc1, 0xeb, 0x57, 0xde, 0x78, 0xf0, 0x07, 0x88, 0xfb, 0x6c, 0x3a, 0x59, 0x1c, 0xfd, 0xf2, 0xd0, 0x07, 0x6d, 0x23,
|
||||
0x9d, 0x69, 0x27, 0x05, 0x96, 0xf1, 0x9a, 0x14, 0x95, 0xbf, 0xd8, 0xf1, 0xd4, 0x7d, 0x2b, 0x4f, 0xc5, 0x82, 0xa3,
|
||||
0xda, 0xc5, 0x1a, 0x7b, 0x3e, 0x6f, 0x9f, 0x73, 0xbd, 0xee, 0x73, 0x0f, 0x83, 0x4b, 0xcf, 0xcd, 0xc9, 0x6f, 0x3d,
|
||||
0xdf, 0xbd, 0xfb, 0x7e, 0xe8, 0xe0, 0x6e, 0x9e, 0xb6, 0x8e, 0xbf, 0x0a, 0xfc, 0x36, 0xb7, 0xfd, 0xd2, 0xce, 0xeb,
|
||||
0x17, 0x4c, 0xd3, 0x39, 0xd7, 0x70, 0xe9, 0xd3, 0xd4, 0x4d, 0xa9, 0x1f, 0x2c, 0x7c, 0xe3, 0x12, 0x4e, 0xf9, 0x7e,
|
||||
0x7f, 0xc2, 0xb3, 0xeb, 0xa4, 0x53, 0xe9, 0xb1, 0x4d, 0xf7, 0x58, 0x57, 0x36, 0xfd, 0x3e, 0x65, 0x9b, 0xb1, 0x91,
|
||||
0x61, 0x14, 0x8c, 0x82, 0x51, 0x30, 0x0a, 0x46, 0xc1, 0x28, 0x18, 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x30,
|
||||
0x0a, 0x46, 0xc1, 0x28, 0x18, 0x05, 0xa3, 0x60, 0x14, 0x8c, 0x82, 0x51, 0x40, 0x73, 0x90, 0x5a, 0x5c, 0x90, 0x91,
|
||||
0x9f, 0x9b, 0xaa, 0x90, 0x5f, 0x92, 0xa8, 0x90, 0x92, 0x9a, 0x96, 0x93, 0x58, 0x92, 0xaa, 0x30, 0x2a, 0x36, 0x2a,
|
||||
0x36, 0x2a, 0x36, 0x2a, 0x36, 0x2a, 0x36, 0x2a, 0x36, 0x2a, 0x36, 0xb4, 0xc4, 0x00,
|
||||
};
|
||||
|
||||
static const uint8_t SKEWED[] = {
|
||||
0x2d, 0x97, 0x79, 0xf0, 0x97, 0x55, 0x15, 0xc6, 0x2d, 0x0a, 0xc5, 0x25, 0x25, 0x1b, 0x9d, 0x6c, 0xcc, 0xe3, 0xd8,
|
||||
0xf0, 0x07, 0x4c, 0x8e, 0x96, 0x63, 0xe2, 0x16, 0x90, 0x26, 0x6a, 0x35, 0x14, 0x2a, 0xc5, 0xe8, 0xc8, 0x35, 0xaf,
|
||||
0x7a, 0x5d, 0xce, 0x51, 0xba, 0x1a, 0x82, 0x4c, 0x63, 0x2a, 0x69, 0xa6, 0xe0, 0xa8, 0x8c, 0xb8, 0x11, 0x1a, 0x03,
|
||||
0x29, 0x2e, 0x29, 0xa2, 0x69, 0x2e, 0x8c, 0x1a, 0x05, 0x3f, 0xad, 0x46, 0x74, 0xc4, 0x22, 0xb7, 0x18, 0xd3, 0x16,
|
||||
0x74, 0x10, 0xd1, 0x98, 0xac, 0xe7, 0x73, 0xbe, 0xf2, 0x63, 0xbe, 0xdf, 0xef, 0xfb, 0xbe, 0xf7, 0xbd, 0xf7, 0x2c,
|
||||
0xcf, 0x79, 0xce, 0x73, 0xa2, 0x37, 0xf3, 0x62, 0xad, 0xd6, 0xe2, 0xbd, 0x4d, 0xa8, 0x2d, 0xfa, 0x3e, 0xde, 0x7b,
|
||||
0x6b, 0xd6, 0xad, 0xb8, 0x7b, 0x09, 0xd7, 0xf3, 0x28, 0xad, 0x78, 0x44, 0xf3, 0x68, 0x25, 0xf4, 0x82, 0x45, 0xd7,
|
||||
0xb3, 0xda, 0x43, 0x1f, 0xc5, 0x9e, 0xd0, 0x97, 0x5d, 0x5a, 0xb4, 0x4b, 0x58, 0x78, 0xdb, 0xb7, 0x9b, 0xf5, 0x28,
|
||||
0xd6, 0x3b, 0xaf, 0x15, 0xf3, 0x1e, 0xb5, 0xd9, 0x5c, 0x8f, 0xd7, 0xa2, 0x0f, 0xd5, 0xf2, 0x78, 0xf7, 0x56, 0xce,
|
||||
0x2a, 0x5e, 0x6a, 0xad, 0xad, 0x1e, 0xe3, 0x75, 0xad, 0x47, 0xd1, 0xda, 0xd6, 0xf4, 0x15, 0xd3, 0x2d, 0x22, 0x7a,
|
||||
0x75, 0x5d, 0x55, 0x1d, 0x12, 0xd5, 0x8e, 0x73, 0x0f, 0xed, 0x50, 0xb4, 0x58, 0xdb, 0x37, 0x9d, 0x58, 0xe3, 0x56,
|
||||
0xad, 0xd7, 0x06, 0xa1, 0x45, 0xd3, 0x4b, 0x2d, 0xda, 0x5f, 0x26, 0xb4, 0xae, 0x9d, 0xab, 0x75, 0xad, 0xd3, 0x6b,
|
||||
0xf2, 0xa6, 0xd7, 0xa8, 0xdd, 0xc7, 0x58, 0x95, 0xd9, 0xba, 0x3b, 0x24, 0xdf, 0x46, 0xba, 0x95, 0x16, 0xb2, 0x4b,
|
||||
0xe7, 0x45, 0x9b, 0x6e, 0xde, 0xfc, 0xf6, 0x43, 0xd7, 0x85, 0xc9, 0xab, 0xf6, 0x57, 0x59, 0xed, 0xc3, 0x65, 0x83,
|
||||
0xce, 0x97, 0x2d, 0xda, 0x42, 0xeb, 0x88, 0x40, 0x8f, 0xc7, 0xe4, 0xbb, 0x36, 0x6d, 0xae, 0xa8, 0x5c, 0x25, 0xc3,
|
||||
0xaa, 0x36, 0xd0, 0x6f, 0xfd, 0x23, 0x78, 0x66, 0x55, 0x6e, 0x1e, 0xd1, 0x8b, 0x2c, 0xb2, 0x1f, 0xd6, 0xbd, 0xcc,
|
||||
0x1e, 0x96, 0xe9, 0x17, 0xd6, 0x3e, 0x59, 0x2b, 0x4d, 0xd1, 0xd2, 0x4a, 0xc5, 0xad, 0x17, 0x1f, 0x5a, 0xad, 0x6d,
|
||||
0x0c, 0x7f, 0x74, 0xb7, 0xee, 0xf8, 0xf6, 0x07, 0xeb, 0xe3, 0x06, 0xed, 0x2b, 0x03, 0xf1, 0xae, 0x6a, 0xaf, 0xbf,
|
||||
0xeb, 0xd0, 0x62, 0xda, 0x68, 0x92, 0x5e, 0xf3, 0xd2, 0x74, 0x4f, 0x6e, 0x34, 0x57, 0x30, 0xc2, 0x8f, 0xd4, 0x16,
|
||||
0x79, 0x75, 0x4f, 0x9d, 0x17, 0x25, 0xec, 0xe4, 0x6a, 0x6b, 0x6b, 0xd7, 0xa2, 0x8a, 0xf7, 0x85, 0xe8, 0xcb, 0x74,
|
||||
0x9d, 0x75, 0x80, 0x0e, 0xab, 0x3b, 0xe8, 0x51, 0x89, 0x47, 0x9b, 0x55, 0xe5, 0x58, 0x36, 0x12, 0x54, 0x65, 0xf0,
|
||||
0x4c, 0x25, 0xd7, 0xdb, 0x24, 0x8f, 0xcc, 0xb0, 0x16, 0x7b, 0xb5, 0x6a, 0xed, 0x70, 0xc5, 0xa5, 0x96, 0x6e, 0x3a,
|
||||
0x82, 0x5c, 0x2b, 0xca, 0xfd, 0xc5, 0xee, 0x33, 0x5d, 0x89, 0x5e, 0x14, 0x32, 0x6e, 0x8a, 0xde, 0x7f, 0x49, 0x56,
|
||||
0x0a, 0x14, 0xe5, 0x90, 0x57, 0x3d, 0xd6, 0x44, 0xf8, 0x6d, 0x5d, 0x89, 0x7b, 0x5b, 0x10, 0x50, 0x34, 0x2f, 0x2d,
|
||||
0x44, 0x5e, 0xdb, 0x3d, 0x7b, 0x7f, 0x08, 0x4e, 0x0a, 0x6c, 0x74, 0x23, 0x64, 0xe5, 0x12, 0xf9, 0x6f, 0x02, 0xcb,
|
||||
0x2b, 0x3e, 0x96, 0xa0, 0xd6, 0xae, 0x84, 0x68, 0x1d, 0x59, 0xd1, 0x8b, 0x24, 0x64, 0xa1, 0x1c, 0x2f, 0x56, 0xff,
|
||||
0xe6, 0xb1, 0x8d, 0x70, 0xb1, 0x61, 0x85, 0x96, 0x09, 0x72, 0x7e, 0x94, 0x37, 0x6b, 0x06, 0x0e, 0x14, 0x7d, 0xfd,
|
||||
0x92, 0x8f, 0x02, 0x80, 0x11, 0x97, 0x32, 0xbb, 0x5d, 0xa8, 0x3c, 0x29, 0x09, 0x72, 0xf2, 0xa1, 0xe2, 0xb7, 0x2a,
|
||||
0xd0, 0x4a, 0xaf, 0x7d, 0x17, 0x58, 0xcc, 0x27, 0x9d, 0x2c, 0x55, 0x4c, 0x46, 0xc9, 0x26, 0x85, 0x4c, 0xc0, 0xe8,
|
||||
0x3b, 0xd7, 0x2a, 0x70, 0xe9, 0x52, 0x5b, 0x96, 0x8f, 0x57, 0x21, 0xb4, 0xbc, 0x25, 0x2c, 0xb5, 0xf2, 0x9c, 0xff,
|
||||
0x51, 0xe9, 0x39, 0x5e, 0x89, 0x57, 0xfe, 0xea, 0xeb, 0x42, 0xb6, 0x82, 0x25, 0xe0, 0xcb, 0x4a, 0x61, 0x8f, 0x57,
|
||||
0x0a, 0xa0, 0x53, 0xd2, 0xff, 0x22, 0x8b, 0xfa, 0xdd, 0x72, 0x40, 0xc7, 0x78, 0x4c, 0xee, 0xd6, 0xfa, 0xaf, 0x65,
|
||||
0x41, 0xd4, 0xe3, 0x48, 0x30, 0xc1, 0xaf, 0x8d, 0x78, 0xbb, 0x71, 0xbe, 0x16, 0x0d, 0x70, 0x54, 0xfa, 0x52, 0x59,
|
||||
0x17, 0x53, 0x74, 0x5f, 0xb1, 0x68, 0xf2, 0xb5, 0x39, 0x15, 0xa2, 0xed, 0xbb, 0xa0, 0xa3, 0x20, 0xe6, 0xb7, 0x1e,
|
||||
0x29, 0xdc, 0x05, 0xa4, 0x7b, 0xfb, 0x9f, 0xf2, 0xd8, 0x95, 0x5b, 0x25, 0x24, 0xfa, 0xfe, 0xbe, 0x1f, 0x96, 0xd4,
|
||||
0xb4, 0xa4, 0xb7, 0x45, 0xed, 0x04, 0x53, 0xaa, 0x04, 0x13, 0xcb, 0xc8, 0x28, 0x91, 0x9d, 0xca, 0xd8, 0x55, 0x38,
|
||||
0x71, 0x30, 0xe7, 0x27, 0x5a, 0x98, 0x91, 0xda, 0xe7, 0x14, 0x34, 0x6a, 0xa9, 0xe0, 0x80, 0x5d, 0x5f, 0x41, 0xae,
|
||||
0xd0, 0xe8, 0x32, 0xec, 0x88, 0xba, 0x84, 0x0a, 0x97, 0xa7, 0xbc, 0x4b, 0xa2, 0x7d, 0x27, 0x2a, 0xa6, 0x6f, 0x92,
|
||||
0x05, 0x06, 0x20, 0x95, 0x6a, 0xb9, 0xda, 0xff, 0xa5, 0x7a, 0xac, 0xfd, 0x61, 0x2d, 0x15, 0xc2, 0xde, 0x95, 0xad,
|
||||
0xde, 0xee, 0x28, 0xb6, 0xc8, 0x6f, 0x96, 0xcd, 0xb7, 0x41, 0x0b, 0x20, 0xba, 0x53, 0x57, 0x0d, 0xcb, 0xbb, 0xbd,
|
||||
0xaf, 0x13, 0x40, 0x78, 0x91, 0xed, 0x72, 0x6f, 0x34, 0x29, 0x93, 0x8f, 0xa5, 0x43, 0x33, 0x8b, 0x65, 0xd9, 0x5a,
|
||||
0xd5, 0xbe, 0x3f, 0x2f, 0x83, 0x74, 0x2b, 0xca, 0x51, 0x14, 0x97, 0x78, 0x42, 0x68, 0xa1, 0x98, 0x14, 0x7e, 0xaf,
|
||||
0xfb, 0xc3, 0x49, 0xf2, 0x5e, 0x99, 0x14, 0x88, 0x47, 0x0a, 0x26, 0x7d, 0xc2, 0x09, 0x8a, 0x4d, 0x5c, 0x11, 0xa7,
|
||||
0xc8, 0xb1, 0x7b, 0xea, 0x5b, 0x02, 0x77, 0x07, 0xc4, 0x03, 0x94, 0x41, 0x20, 0xfe, 0x61, 0xc7, 0x9a, 0x58, 0x63,
|
||||
0x13, 0xb4, 0x9e, 0xa4, 0xab, 0x30, 0x15, 0xa2, 0x42, 0xc9, 0xad, 0x57, 0x88, 0x27, 0xea, 0x96, 0x58, 0xc1, 0x6d,
|
||||
0x7b, 0x45, 0xc4, 0xa1, 0x88, 0x0a, 0xe0, 0x15, 0xe0, 0xe3, 0xbb, 0x3c, 0x6d, 0xf6, 0x16, 0x27, 0x1c, 0xff, 0x4d,
|
||||
0xed, 0xa5, 0xd7, 0xbe, 0xe6, 0xd4, 0xa4, 0x00, 0xac, 0x10, 0x29, 0x2a, 0xba, 0xb5, 0x97, 0x98, 0x61, 0x8d, 0xa2,
|
||||
0x2e, 0x5c, 0x89, 0x01, 0xcb, 0x37, 0x14, 0x59, 0x5d, 0xc8, 0x42, 0x31, 0x4e, 0xf2, 0x64, 0x83, 0x7b, 0xa6, 0xba,
|
||||
0x70, 0x11, 0x5b, 0x29, 0x58, 0x41, 0x41, 0xa1, 0xd3, 0xe9, 0x04, 0xe6, 0xd9, 0xcf, 0x6a, 0xb9, 0x4a, 0x4d, 0x0f,
|
||||
0xeb, 0x07, 0x3a, 0xff, 0x0c, 0x3f, 0x6c, 0xc4, 0x91, 0xef, 0x4d, 0x2d, 0x49, 0x06, 0xd0, 0xa4, 0x7c, 0x50, 0x14,
|
||||
0xb5, 0xb9, 0xf2, 0x13, 0x00, 0xfb, 0x5e, 0x08, 0xb0, 0x2b, 0xec, 0xbe, 0xa0, 0x1c, 0xf4, 0x7b, 0xef, 0x38, 0x13,
|
||||
0x31, 0x8a, 0x78, 0x2b, 0x7e, 0x49, 0x54, 0x54, 0xc7, 0x54, 0x59, 0xd7, 0x37, 0x34, 0x10, 0x58, 0x4f, 0x25, 0xce,
|
||||
0x61, 0x73, 0xec, 0x41, 0x85, 0x06, 0x8e, 0xcd, 0x5c, 0xaa, 0xd0, 0x55, 0x6e, 0xc2, 0xa8, 0x8a, 0x4d, 0x41, 0x16,
|
||||
0x5b, 0xe8, 0xae, 0x51, 0x84, 0x14, 0xc7, 0x17, 0xa1, 0x0e, 0x51, 0x9d, 0x5c, 0x15, 0xaa, 0xb4, 0xae, 0xe8, 0x42,
|
||||
0x68, 0xb6, 0xef, 0x28, 0x09, 0xa7, 0xcd, 0x50, 0x00, 0x94, 0x93, 0x00, 0xd9, 0x09, 0x4f, 0x19, 0xa8, 0xe0, 0x89,
|
||||
0x08, 0xc0, 0xa7, 0x56, 0xee, 0xec, 0x6d, 0x27, 0xfb, 0x4d, 0x87, 0x44, 0x28, 0x93, 0x78, 0x5a, 0x34, 0x54, 0x49,
|
||||
0x8d, 0x90, 0x29, 0xb7, 0x2d, 0x46, 0x97, 0xb2, 0x8e, 0x3e, 0x21, 0x9f, 0xaf, 0x55, 0xc8, 0xa8, 0x0c, 0x19, 0x14,
|
||||
0xb7, 0xc3, 0x9b, 0xca, 0xc0, 0x79, 0x25, 0x39, 0x8c, 0x7e, 0x22, 0x20, 0x2b, 0x5e, 0x43, 0x24, 0x75, 0x48, 0xc7,
|
||||
0xb6, 0x8b, 0x2b, 0x99, 0x80, 0xe1, 0xe5, 0x6c, 0xeb, 0xe7, 0x89, 0x69, 0x74, 0xa8, 0xf2, 0x6f, 0x97, 0x90, 0x59,
|
||||
0x48, 0xb8, 0x7d, 0x0b, 0xc2, 0x83, 0xc6, 0x8c, 0x8e, 0x62, 0x47, 0x44, 0x8c, 0x59, 0xdb, 0xab, 0x7d, 0x99, 0x1e,
|
||||
0x46, 0xfe, 0xa8, 0x61, 0x71, 0xa8, 0x01, 0x08, 0xd5, 0x5f, 0xc1, 0x85, 0x68, 0xf7, 0x0a, 0x59, 0x22, 0xe9, 0x42,
|
||||
0xd4, 0x5a, 0x9b, 0x53, 0xff, 0xdb, 0x62, 0x83, 0x2e, 0x5a, 0x6c, 0xce, 0x9a, 0xac, 0x90, 0x81, 0x12, 0xd3, 0x6f,
|
||||
0x86, 0x94, 0xa9, 0x90, 0xce, 0x16, 0x01, 0xc3, 0x41, 0x66, 0xad, 0xff, 0xc9, 0xe1, 0x69, 0xba, 0x0c, 0xdc, 0x65,
|
||||
0x49, 0x39, 0xb0, 0xa3, 0x9c, 0x9f, 0x50, 0x20, 0xf9, 0x3d, 0x45, 0x73, 0x76, 0x1f, 0xcc, 0x01, 0x78, 0x94, 0xaa,
|
||||
0x37, 0xf4, 0x65, 0xdb, 0x38, 0xf1, 0x83, 0x26, 0xba, 0xbd, 0x53, 0xa8, 0xaf, 0xfa, 0x3b, 0xac, 0x52, 0x68, 0x2b,
|
||||
0x9b, 0xe0, 0x1b, 0xb4, 0x12, 0x2d, 0xe9, 0x60, 0xb9, 0xa2, 0xaa, 0x2a, 0x73, 0x18, 0xbe, 0x72, 0x50, 0xc0, 0x87,
|
||||
0xb2, 0x99, 0x66, 0x4b, 0x16, 0x1d, 0x24, 0xf6, 0x3a, 0xc2, 0xc1, 0xd6, 0xe7, 0x68, 0xd5, 0x72, 0x79, 0x17, 0xed,
|
||||
0x73, 0xb7, 0xd8, 0x87, 0x64, 0x8a, 0x80, 0xee, 0xa1, 0xcd, 0x71, 0x42, 0x9b, 0x5a, 0xe9, 0x2a, 0x7a, 0x00, 0x0b,
|
||||
0x0b, 0x4a, 0x9f, 0xc2, 0xba, 0xb0, 0x9b, 0x08, 0x30, 0x68, 0x10, 0x54, 0xa7, 0x7d, 0x66, 0xdd, 0x83, 0x59, 0x39,
|
||||
0x49, 0xb7, 0xf2, 0x50, 0x49, 0x2c, 0xf8, 0x79, 0x83, 0xac, 0xd7, 0x2a, 0x6d, 0x9f, 0x0c, 0x40, 0x3f, 0xa9, 0xfe,
|
||||
0x3a, 0x8c, 0x5f, 0x29, 0x9d, 0xa3, 0xe9, 0x1c, 0xda, 0x53, 0x15, 0xb0, 0x27, 0x85, 0x29, 0x9b, 0x15, 0x7a, 0xca,
|
||||
0x83, 0xcc, 0x68, 0x47, 0xc5, 0xe4, 0x2c, 0xa1, 0x8a, 0xfa, 0x1b, 0x05, 0x97, 0xc9, 0x21, 0x03, 0x4c, 0x0e, 0x9a,
|
||||
0x74, 0xdb, 0xe3, 0x05, 0xc2, 0x57, 0xa7, 0x72, 0x9e, 0xf6, 0x39, 0x5c, 0xd4, 0x01, 0x3e, 0xb2, 0xa9, 0x26, 0x11,
|
||||
0x5b, 0xbf, 0xd1, 0x07, 0xbc, 0x1a, 0x20, 0x85, 0xf8, 0x08, 0xa7, 0xe9, 0x18, 0x0d, 0x43, 0x49, 0xd0, 0xa3, 0x8d,
|
||||
0x49, 0xe9, 0xf4, 0xe6, 0xcc, 0x8a, 0xbe, 0xe9, 0x6a, 0x38, 0x22, 0x82, 0xb0, 0x91, 0x9d, 0xf2, 0x4b, 0x97, 0x94,
|
||||
0x7d, 0x2a, 0x4f, 0xb8, 0x85, 0x04, 0x10, 0x11, 0x75, 0x58, 0xb4, 0x5b, 0xe0, 0x28, 0x80, 0xd2, 0xe9, 0xd2, 0x73,
|
||||
0x4b, 0x79, 0xb9, 0xb3, 0x99, 0x32, 0x55, 0x00, 0xfc, 0x53, 0xc9, 0x71, 0x85, 0x64, 0xf4, 0x8d, 0x2a, 0xc8, 0x96,
|
||||
0x04, 0xa6, 0xe3, 0x75, 0xe4, 0xae, 0x75, 0x97, 0x93, 0x14, 0xb6, 0x5c, 0x2b, 0x32, 0x85, 0xf1, 0xaf, 0x6d, 0x74,
|
||||
0x25, 0x79, 0x48, 0xcf, 0x53, 0x52, 0xc7, 0xc9, 0x7a, 0x10, 0x93, 0x92, 0x45, 0x31, 0xfc, 0x27, 0x0c, 0xa4, 0xe0,
|
||||
0x42, 0x42, 0x60, 0x44, 0xb1, 0x2a, 0xd9, 0xec, 0x00, 0x66, 0x87, 0xcd, 0xb2, 0x22, 0x9d, 0x3d, 0x69, 0xe9, 0xa5,
|
||||
0x3e, 0x04, 0xa9, 0xb6, 0x74, 0xce, 0x11, 0x1c, 0x5b, 0xb5, 0xab, 0x4a, 0x18, 0xaa, 0x69, 0x3b, 0x8b, 0x82, 0x5f,
|
||||
0xe6, 0x80, 0x0a, 0x92, 0x9d, 0xaa, 0x26, 0x4d, 0x22, 0xd2, 0xb9, 0x7a, 0x33, 0x61, 0xcc, 0x8b, 0x9e, 0xdf, 0xfd,
|
||||
0xa3, 0xb6, 0x28, 0x03, 0xf6, 0x03, 0x85, 0x08, 0xb2, 0xd9, 0xaa, 0xf2, 0x02, 0xa3, 0xd8, 0x35, 0x4e, 0x14, 0x3b,
|
||||
0xf2, 0xad, 0x95, 0xbd, 0x91, 0x2e, 0x78, 0x0f, 0x83, 0xaf, 0x90, 0x3c, 0xd0, 0x0f, 0x27, 0x6d, 0x47, 0x09, 0x9a,
|
||||
0x72, 0x58, 0x2d, 0x93, 0x76, 0xad, 0xc3, 0xfe, 0x1c, 0x33, 0x54, 0xf9, 0x2d, 0x93, 0x83, 0xa4, 0xa9, 0x10, 0x38,
|
||||
0x50, 0xa5, 0x70, 0x2f, 0x30, 0xff, 0xe4, 0x1d, 0xc5, 0x97, 0x2d, 0x2e, 0xbf, 0x2c, 0x33, 0xe5, 0xce, 0x23, 0xdd,
|
||||
0x16, 0x54, 0xb4, 0x03, 0x10, 0x8a, 0x64, 0x2a, 0x42, 0x1f, 0x64, 0x9f, 0x28, 0x25, 0x21, 0xb5, 0xd8, 0xcd, 0x7c,
|
||||
0x42, 0xd0, 0x34, 0x91, 0x83, 0x3f, 0xc2, 0x1e, 0x05, 0x53, 0xb6, 0xa8, 0x5c, 0xfe, 0xd1, 0x32, 0xc1, 0x14, 0x21,
|
||||
0x25, 0xa1, 0x07, 0xb3, 0xa1, 0xd7, 0x8c, 0x61, 0xf3, 0xff, 0xe8, 0x65, 0x98, 0xab, 0xc5, 0x39, 0xbe, 0x55, 0x61,
|
||||
0xae, 0x3d, 0x63, 0x5a, 0xb6, 0xa8, 0x21, 0x5c, 0xde, 0x92, 0xc1, 0x2b, 0x42, 0x47, 0xd8, 0x38, 0x87, 0x2e, 0x58,
|
||||
0x3f, 0x86, 0x0e, 0xdb, 0x6c, 0x75, 0x5e, 0x6d, 0xe7, 0x88, 0x5e, 0xeb, 0x89, 0x9c, 0x22, 0xc3, 0x6f, 0x27, 0xe5,
|
||||
0x38, 0x08, 0xe3, 0x02, 0x47, 0xfe, 0xb7, 0xc5, 0x6d, 0x2c, 0x85, 0xb4, 0xad, 0x21, 0x95, 0xfd, 0x5c, 0x7b, 0xc5,
|
||||
0x4f, 0x87, 0xf6, 0xba, 0xbf, 0xc1, 0x2a, 0xa4, 0x09, 0x1d, 0x11, 0x2a, 0x4f, 0xc7, 0x94, 0x8f, 0xd1, 0x54, 0xaf,
|
||||
0x12, 0x80, 0xfe, 0x2a, 0x94, 0x90, 0xdb, 0x95, 0x15, 0x7a, 0x48, 0x23, 0x56, 0xf9, 0xd5, 0x0d, 0xa8, 0xa2, 0xc6,
|
||||
0x9e, 0x04, 0x7f, 0x14, 0x3e, 0x02, 0xa8, 0x53, 0xe0, 0x70, 0x06, 0x0d, 0xa5, 0xd7, 0xe5, 0xb6, 0xfc, 0x8a, 0xf2,
|
||||
0x25, 0x2e, 0xe1, 0xcf, 0x98, 0x1c, 0x7d, 0x8b, 0xb6, 0x98, 0xee, 0x71, 0x12, 0xe6, 0x86, 0x1d, 0x27, 0x98, 0xdf,
|
||||
0x58, 0x10, 0x43, 0x1d, 0x1e, 0x1d, 0x26, 0x42, 0xda, 0xaf, 0x67, 0xd3, 0x97, 0x04, 0x5e, 0x2d, 0x8c, 0x4d, 0x84,
|
||||
0x7f, 0x55, 0x7a, 0x00, 0x8f, 0x0c, 0x93, 0x38, 0xa9, 0x21, 0x52, 0x10, 0x68, 0x36, 0xbf, 0x18, 0xe3, 0x75, 0xe0,
|
||||
0x2a, 0x3e, 0x07, 0x55, 0xe5, 0xd9, 0x58, 0x69, 0x4c, 0x28, 0x51, 0x79, 0x0f, 0x05, 0x06, 0x10, 0xf3, 0x3e, 0x1f,
|
||||
0xf5, 0x90, 0x35, 0x78, 0x97, 0x70, 0xb4, 0x5e, 0x71, 0xfe, 0x05, 0xd5, 0xa8, 0x3b, 0x47, 0xd5, 0x14, 0x1e, 0x34,
|
||||
0x13, 0x8a, 0x96, 0xb0, 0xf8, 0x19, 0xb1, 0x14, 0xe9, 0x95, 0x22, 0x5a, 0x31, 0xa5, 0x71, 0xd3, 0xcd, 0xe4, 0xa1,
|
||||
0xb5, 0x6b, 0x8c, 0x93, 0xea, 0x99, 0xd1, 0x95, 0x9f, 0x71, 0x14, 0x8e, 0x63, 0x8f, 0xc2, 0x5a, 0x76, 0x03, 0xcb,
|
||||
0xf4, 0xf3, 0x2b, 0xe9, 0x8f, 0x11, 0x29, 0x0c, 0x5b, 0x1b, 0x66, 0x30, 0xb2, 0x4f, 0xcf, 0xf6, 0xc1, 0x5f, 0xc9,
|
||||
0x41, 0xc6, 0x53, 0x49, 0x44, 0x96, 0xb5, 0xce, 0x38, 0x15, 0x6e, 0xe8, 0xe3, 0x22, 0x99, 0x38, 0x45, 0xaa, 0x0a,
|
||||
0x17, 0x25, 0x4a, 0x57, 0xf9, 0x1e, 0x42, 0x0f, 0x78, 0x7b, 0x5d, 0xc2, 0xe9, 0xc8, 0x64, 0x18, 0x09, 0x25, 0x84,
|
||||
0x16, 0x8e, 0x24, 0xf6, 0x65, 0x17, 0x55, 0x94, 0x37, 0x08, 0xc2, 0xf9, 0x82, 0x2a, 0xef, 0x67, 0xab, 0xb5, 0x55,
|
||||
0x14, 0x7a, 0x63, 0x9a, 0x50, 0xf2, 0x52, 0x71, 0x8b, 0x55, 0xd0, 0x99, 0x46, 0xa7, 0x3d, 0x2b, 0x05, 0x59, 0x50,
|
||||
0xb3, 0xe0, 0x77, 0xa3, 0x4c, 0x2f, 0x91, 0x93, 0x05, 0x3c, 0x4a, 0x8f, 0x41, 0x6d, 0xc6, 0xd2, 0x24, 0xa8, 0x8e,
|
||||
0x92, 0x54, 0x43, 0x15, 0xd8, 0x53, 0x79, 0x56, 0x4b, 0x06, 0xa8, 0x36, 0x5b, 0x76, 0xb4, 0xd7, 0x10, 0x82, 0x8c,
|
||||
0x63, 0x5f, 0xad, 0xcf, 0x2b, 0x67, 0x4b, 0xe4, 0xff, 0xb7, 0xa3, 0xbc, 0xaf, 0x9b, 0x2a, 0xaf, 0x1c, 0x80, 0xbe,
|
||||
0x1f, 0x7e, 0x05, 0xf4, 0x8a, 0xb8, 0x13, 0x10, 0xa8, 0x02, 0x05, 0xf7, 0xee, 0xc1, 0x89, 0x54, 0x7d, 0xc1, 0x4e,
|
||||
0x5a, 0x80, 0x38, 0x80, 0x09, 0x49, 0x86, 0xd6, 0xe1, 0x0b, 0x2b, 0x73, 0x13, 0x9a, 0xb0, 0xd0, 0xe6, 0x15, 0xa8,
|
||||
0xb9, 0x6e, 0x89, 0xbc, 0x8e, 0x2c, 0x73, 0x95, 0x9c, 0xe4, 0x91, 0x3c, 0xde, 0x74, 0x26, 0x49, 0xac, 0x7b, 0x24,
|
||||
0xa3, 0x50, 0x61, 0xa7, 0x88, 0xb6, 0xb5, 0xe5, 0x3c, 0xcb, 0xde, 0x94, 0x7d, 0x0a, 0x91, 0x91, 0x50, 0x3a, 0xd8,
|
||||
0x50, 0xed, 0x2b, 0xc8, 0x84, 0xcc, 0x11, 0x3d, 0xaf, 0x67, 0xd0, 0x84, 0x9a, 0x0a, 0x95, 0xe1, 0xc8, 0x87, 0xed,
|
||||
0xe5, 0xff, 0xaf, 0x12, 0xc6, 0x57, 0x5d, 0x27, 0x2a, 0x2a, 0x28, 0x93, 0xb6, 0x03, 0x1c, 0xb0, 0x97, 0x36, 0x3b,
|
||||
0xa6, 0xd3, 0x57, 0x82, 0x49, 0x42, 0x5f, 0x9b, 0xd4, 0x7b, 0x37, 0xdb, 0x16, 0x71, 0x1d, 0xec, 0xd7, 0xe2, 0x52,
|
||||
0x20, 0x93, 0xfa, 0x27, 0xd1, 0xd4, 0xfd, 0x19, 0x21, 0x46, 0xc7, 0x5c, 0x8c, 0x1a, 0x56, 0x3e, 0x0f, 0xf4, 0x36,
|
||||
0xda, 0x99, 0x55, 0xc1, 0x74, 0xaa, 0xf5, 0xdd, 0x0d, 0x4e, 0xfb, 0x50, 0x3e, 0xa5, 0x16, 0x52, 0xe4, 0x67, 0x20,
|
||||
0x38, 0xd4, 0x0a, 0x0e, 0xa9, 0x7e, 0xf6, 0xf9, 0xe8, 0x1f, 0xa8, 0xf7, 0xeb, 0xaa, 0xfc, 0x41, 0x93, 0xe8, 0xe5,
|
||||
0xae, 0xda, 0x6f, 0x53, 0x90, 0xb6, 0x3c, 0xb5, 0x0c, 0x6f, 0x7e, 0x2c, 0xbf, 0x17, 0x40, 0xd4, 0x3d, 0xfb, 0x85,
|
||||
0xaa, 0x78, 0xb8, 0xf8, 0x6d, 0x5f, 0xc6, 0x4c, 0x79, 0x5e, 0x9e, 0xce, 0x96, 0xc7, 0xe8, 0x0a, 0x39, 0xfa, 0x81,
|
||||
0x25, 0xcf, 0x6b, 0xe8, 0xd8, 0x3d, 0x60, 0x64, 0xc4, 0x73, 0x6a, 0x94, 0x96, 0x1d, 0x0c, 0xfe, 0xfc, 0x4a, 0x49,
|
||||
0xdd, 0xf3, 0x80, 0x32, 0x4f, 0x47, 0xb4, 0xf1, 0x72, 0x59, 0x79, 0xfc, 0x81, 0xcc, 0xe8, 0x39, 0xbe, 0x4a, 0xc2,
|
||||
0x3c, 0x3e, 0x4d, 0xe9, 0xbb, 0x2c, 0x36, 0x32, 0xed, 0xc2, 0xc2, 0x29, 0x4d, 0xfa, 0xad, 0x88, 0xb2, 0x1c, 0x25,
|
||||
0x9c, 0x91, 0x64, 0xd0, 0x61, 0x69, 0x97, 0x66, 0x57, 0xfb, 0xb3, 0x43, 0xfa, 0x75, 0x2c, 0x22, 0x06, 0x0c, 0xd2,
|
||||
0xac, 0xcb, 0x41, 0x07, 0x0e, 0x14, 0x45, 0x1a, 0x81, 0xf6, 0x43, 0x19, 0xe5, 0xdf, 0x24, 0x00, 0xac, 0x20, 0x5e,
|
||||
0x17, 0xc9, 0x01, 0x28, 0xe6, 0x9c, 0x9e, 0x88, 0x29, 0x9d, 0xc7, 0x52, 0xda, 0x72, 0xa5, 0x7d, 0x90, 0x31, 0x22,
|
||||
0xe0, 0x29, 0xc2, 0xb3, 0x50, 0x40, 0xff, 0x70, 0xde, 0xb1, 0xc8, 0xe1, 0x65, 0xd2, 0x95, 0x07, 0x97, 0x0d, 0x6d,
|
||||
0x26, 0xda, 0x1e, 0x61, 0xf2, 0x9a, 0x80, 0x07, 0x71, 0x32, 0xee, 0xd5, 0xd8, 0xda, 0x08, 0x35, 0xda, 0x8a, 0x72,
|
||||
0x40, 0x91, 0xff, 0x5c, 0x8e, 0x31, 0xce, 0x40, 0x6d, 0x32, 0xff, 0x32, 0xec, 0xaf, 0x4c, 0x79, 0x42, 0xd0, 0x6f,
|
||||
0xb3, 0xcd, 0x8b, 0xca, 0x73, 0x42, 0xc0, 0x03, 0x85, 0x78, 0xf5, 0x89, 0x93, 0x99, 0x19, 0x7d, 0x39, 0x13, 0xe1,
|
||||
0x4b, 0x63, 0xc0, 0x90, 0x10, 0xb5, 0x8f, 0xd3, 0xf2, 0x8e, 0x1d, 0x05, 0x55, 0xb5, 0xfa, 0xcc, 0x9c, 0xec, 0xee,
|
||||
0x6a, 0x6c, 0xe6, 0x39, 0x61, 0xda, 0x9d, 0x6d, 0xd9, 0x4d, 0x0c, 0x48, 0xd3, 0x30, 0xbe, 0xc2, 0x61, 0xac, 0x97,
|
||||
0x07, 0xa4, 0x12, 0x7f, 0xca, 0x23, 0xc0, 0xb7, 0x33, 0xe8, 0x65, 0xf7, 0x98, 0xd8, 0x16, 0x67, 0x8a, 0x21, 0x5b,
|
||||
0xea, 0x96, 0x1e, 0xa7, 0x64, 0xa2, 0xdc, 0xcb, 0x4a, 0xfa, 0xb0, 0xd8, 0xbc, 0xa6, 0x6c, 0x44, 0x77, 0x0f, 0xc4,
|
||||
0x98, 0xbc, 0x9f, 0xf5, 0x62, 0x20, 0x81, 0xa9, 0xab, 0xeb, 0xeb, 0xcf, 0x50, 0x67, 0x8c, 0x35, 0x3d, 0x27, 0x2a,
|
||||
0x79, 0xda, 0x11, 0xbb, 0x6d, 0xf4, 0x2a, 0x55, 0xcc, 0xd0, 0x11, 0x8b, 0xf5, 0x73, 0x72, 0xf8, 0x58, 0x26, 0x9e,
|
||||
0x32, 0xa4, 0x8a, 0x38, 0x0d, 0x14, 0x6b, 0xc3, 0x4a, 0x67, 0xf5, 0x72, 0x7f, 0xed, 0x2b, 0x4b, 0x8a, 0x26, 0x54,
|
||||
0xcb, 0x44, 0xb2, 0x45, 0x50, 0xc9, 0xb2, 0x22, 0x76, 0xaa, 0x82, 0xae, 0xa7, 0x57, 0x09, 0x11, 0x36, 0x8b, 0x99,
|
||||
0x32, 0x49, 0xa5, 0x51, 0xe6, 0x9c, 0x14, 0xc9, 0x4e, 0x9e, 0x33, 0x5e, 0x30, 0xdb, 0xc9, 0xa4, 0x43, 0x54, 0xdc,
|
||||
0x0f, 0x28, 0xed, 0xa7, 0xe9, 0x41, 0x4f, 0xd0, 0xe4, 0x54, 0x58, 0xdf, 0xe5, 0x95, 0x31, 0x35, 0x91, 0x10, 0x82,
|
||||
0xf0, 0xb0, 0xd8, 0x1b, 0x19, 0xdf, 0xb7, 0xb6, 0xb5, 0xad, 0xaf, 0x93, 0xff, 0x4c, 0xb3, 0xf2, 0x7e, 0x3b, 0xd1,
|
||||
0xea, 0x13, 0x36, 0x07, 0x09, 0x5c, 0x6d, 0xdb, 0x6c, 0xef, 0xb2, 0x05, 0x31, 0x4a, 0xff, 0x32, 0x3e, 0x2e, 0x8a,
|
||||
0x96, 0xbd, 0x1b, 0x01, 0x24, 0xac, 0x9c, 0x2b, 0x60, 0x8c, 0x64, 0x36, 0x64, 0x0a, 0x55, 0xa0, 0xb7, 0x11, 0x64,
|
||||
0xda, 0x82, 0x5a, 0x2e, 0xa8, 0x34, 0x13, 0x5f, 0xfd, 0x66, 0x1c, 0x8d, 0x2c, 0xe0, 0x55, 0xc5, 0xf6, 0x0f, 0x1d,
|
||||
0x9d, 0x8d, 0xed, 0x71, 0xfa, 0x8e, 0x1e, 0x2f, 0x7b, 0x4d, 0x72, 0xbe, 0xb9, 0x41, 0xc1, 0x0a, 0xde, 0x7c, 0x5d,
|
||||
0x31, 0x5d, 0xd1, 0x47, 0x4a, 0xb6, 0x6e, 0x06, 0x83, 0xb5, 0xde, 0xde, 0x24, 0x65, 0xed, 0x30, 0x79, 0xfb, 0xf8,
|
||||
0xa0, 0x23, 0xd4, 0x72, 0x2f, 0x9b, 0x36, 0xba, 0x05, 0x50, 0x0b, 0xbf, 0x3f, 0x72, 0xb0, 0xd1, 0x3b, 0x8f, 0xf9,
|
||||
0x1a, 0xf8, 0x38, 0x67, 0xb2, 0x32, 0xe0, 0xc1, 0x27, 0x43, 0x42, 0x1d, 0x0d, 0x29, 0x79, 0x54, 0x88, 0xbe, 0x98,
|
||||
0x02, 0xc5, 0xe5, 0xf1, 0x53, 0x44, 0x0d, 0xcc, 0xad, 0x3b, 0x10, 0x86, 0xbd, 0xdf, 0x6c, 0xdb, 0x7e, 0x17, 0x4d,
|
||||
0xec, 0x1a, 0x2d, 0xa5, 0x09, 0xa8, 0x60, 0xc6, 0x78, 0x2a, 0x0f, 0xed, 0xc9, 0xa5, 0x12, 0x0e, 0xfc, 0x8e, 0xd5,
|
||||
0x79, 0x75, 0xba, 0xb8, 0x2f, 0xa9, 0x5c, 0x0b, 0x27, 0x19, 0x5c, 0x2b, 0x07, 0x56, 0x45, 0x49, 0x31, 0x55, 0xb9,
|
||||
0x8f, 0x0a, 0xa5, 0x22, 0x05, 0xb4, 0x4f, 0x35, 0x5f, 0xa7, 0x84, 0x30, 0xc7, 0x6a, 0xab, 0xf1, 0x34, 0x9a, 0x32,
|
||||
0x3e, 0xc6, 0x56, 0xdb, 0xb1, 0x93, 0xaf, 0x93, 0x99, 0x35, 0x10, 0x85, 0xf7, 0x31, 0xee, 0x22, 0xd4, 0xf5, 0x81,
|
||||
0xe8, 0xf1, 0x3d, 0x39, 0xcf, 0x62, 0x8a, 0x00, 0xaf, 0xb4, 0x97, 0x32, 0x2f, 0xd5, 0x44, 0x5d, 0x9d, 0xa5, 0x64,
|
||||
0xa9, 0x6d, 0x65, 0x52, 0x6a, 0x70, 0x79, 0x6b, 0x7e, 0x19, 0x40, 0xb1, 0x9d, 0x98, 0x6a, 0x8b, 0x2d, 0x41, 0x82,
|
||||
0xd0, 0x97, 0x67, 0x31, 0x09, 0xf9, 0x41, 0xb1, 0xc9, 0x63, 0x4b, 0xd4, 0x5d, 0x27, 0xaa, 0x27, 0xd5, 0xcf, 0x3f,
|
||||
0x11, 0xf3, 0x0d, 0xc9, 0x81, 0x9b, 0xb2, 0xe7, 0x55, 0x27, 0xbe, 0x72, 0xe3, 0xd3, 0xd9, 0x9e, 0x64, 0x06, 0xcf,
|
||||
0x56, 0xde, 0xd2, 0x61, 0x3f, 0xa4, 0x68, 0x4b, 0xda, 0x22, 0x43, 0x8c, 0xb9, 0xba, 0xfb, 0x62, 0x4f, 0x7e, 0x13,
|
||||
0x0c, 0xed, 0x27, 0x08, 0x88, 0x54, 0x11, 0x71, 0x3e, 0x30, 0xd5, 0xff, 0x19, 0x49, 0x20, 0xb3, 0x0c, 0x09, 0xdc,
|
||||
0x46, 0xc4, 0x10, 0xb2, 0xd6, 0x60, 0xb7, 0xde, 0xdf, 0x73, 0x92, 0xc3, 0x4f, 0x6d, 0xe4, 0xe3, 0x69, 0x56, 0xda,
|
||||
0xa8, 0x6e, 0x96, 0x1b, 0xb4, 0x35, 0x32, 0xa8, 0xfb, 0x1b, 0x6a, 0x56, 0x7e, 0x21, 0x6e, 0xdc, 0xeb, 0xb0, 0x51,
|
||||
0x36, 0x49, 0x4d, 0x99, 0x35, 0x72, 0xe6, 0xea, 0x54, 0xf8, 0x64, 0x56, 0x33, 0x73, 0x22, 0x4b, 0x20, 0x33, 0x01,
|
||||
0x56, 0xb6, 0xee, 0xa3, 0xc8, 0x13, 0x16, 0xf9, 0xa2, 0x58, 0x28, 0x2b, 0x1f, 0xfa, 0x17, 0x7c, 0x4b, 0x1f, 0xa7,
|
||||
0xe2, 0xf0, 0xfb, 0xe4, 0x21, 0xbd, 0x59, 0x5e, 0x26, 0x6d, 0x54, 0x3a, 0x97, 0xdd, 0x04, 0x86, 0x6b, 0x8c, 0x49,
|
||||
0x9a, 0x1c, 0xa8, 0x68, 0xac, 0xa9, 0x31, 0x98, 0x52, 0xe5, 0xea, 0x47, 0x03, 0xaa, 0x68, 0x1c, 0x4a, 0x69, 0x14,
|
||||
0x8a, 0xd3, 0x84, 0x5a, 0xbb, 0x9c, 0x79, 0x92, 0x81, 0xcc, 0x92, 0x31, 0x73, 0xb0, 0x75, 0x5a, 0xb7, 0xaf, 0x04,
|
||||
0x26, 0x00, 0x16, 0x92, 0x69, 0x03, 0x7a, 0x61, 0x5a, 0x41, 0xd5, 0x97, 0x63, 0x00, 0x5f, 0x63, 0x30, 0x85, 0xcc,
|
||||
0x6b, 0x8e, 0x8a, 0xbb, 0xa3, 0xff, 0x25, 0x15, 0x54, 0x49, 0x76, 0x0d, 0x9a, 0xb2, 0xd9, 0x70, 0x04, 0x9f, 0xbe,
|
||||
0xfd, 0x25, 0x81, 0x50, 0x87, 0xcc, 0x54, 0x8d, 0x34, 0x32, 0x2b, 0x10, 0xc8, 0x82, 0x43, 0x11, 0x3a, 0xe0, 0x91,
|
||||
0xb4, 0xc7, 0xa3, 0x8c, 0x98, 0xfa, 0x59, 0x72, 0x9a, 0x92, 0x15, 0x0b, 0x03, 0x93, 0x0c, 0xee, 0xd0, 0x2e, 0x15,
|
||||
0xa7, 0x19, 0xcc, 0x62, 0x84, 0xce, 0xab, 0x19, 0x62, 0xa5, 0xf3, 0x01, 0x6d, 0xd4, 0x21, 0x6e, 0xe5, 0x9d, 0xe6,
|
||||
0x87, 0x56, 0xa5, 0x43, 0x40, 0x93, 0xa8, 0x09, 0xbd, 0x95, 0xfa, 0x87, 0xd6, 0x30, 0xb9, 0xf4, 0x65, 0x35, 0x07,
|
||||
0x93, 0xd4, 0x85, 0x9e, 0xa3, 0x98, 0x62, 0xb7, 0x0d, 0x81, 0xc4, 0xf2, 0xca, 0x1c, 0x02, 0x70, 0x73, 0x5c, 0x11,
|
||||
0xae, 0xac, 0x1e, 0x20, 0x4c, 0xac, 0x18, 0xe8, 0xa4, 0x14, 0xbf, 0x0a, 0x09, 0x3e, 0xf7, 0xd8, 0x2e, 0x07, 0xc1,
|
||||
0x9e, 0x13, 0x90, 0xce, 0xa6, 0x69, 0xdb, 0x5a, 0xf8, 0x47, 0x60, 0x1c, 0x4c, 0x71, 0xef, 0xd8, 0x8d, 0x7d, 0x11,
|
||||
0xbd, 0xce, 0x10, 0x1f, 0x8e, 0xf8, 0xa4, 0x58, 0x30, 0xad, 0xa1, 0xe3, 0xca, 0xb3, 0x2d, 0xc7, 0xd4, 0x52, 0xfe,
|
||||
0x8d, 0x18, 0xab, 0xd3, 0x22, 0x49, 0x6b, 0xa5, 0x8a, 0xcd, 0xec, 0x5d, 0x66, 0x2e, 0x1d, 0x88, 0xa6, 0xef, 0x03,
|
||||
0x15, 0x4c, 0x47, 0x47, 0x27, 0xd1, 0x9c, 0x91, 0x27, 0x8d, 0x2f, 0x14, 0x15, 0x0a, 0x8f, 0x03, 0x69, 0xba, 0xed,
|
||||
0x4e, 0x6b, 0x4b, 0x0b, 0x83, 0xec, 0xf9, 0x54, 0x08, 0x9a, 0xaa, 0xbc, 0x50, 0x18, 0x65, 0xd1, 0x37, 0x23, 0xda,
|
||||
0x28, 0x44, 0x7e, 0xb7, 0x4f, 0xe4, 0x80, 0xa8, 0xf8, 0x2f, 0xdc, 0xad, 0xaf, 0xb4, 0xfa, 0x7f,
|
||||
};
|
||||
|
||||
static const uint8_t MIXED[] = {
|
||||
0x4a, 0x2d, 0x2e, 0xc8, 0xc8, 0xcf, 0x4d, 0x55, 0xc8, 0x2f, 0x49, 0x54, 0x48, 0x49, 0x4d, 0xcb, 0x49, 0x2c, 0x49,
|
||||
0x55, 0x48, 0x1d, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x15, 0x1b, 0x84, 0x62, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff,
|
||||
0x00, 0xb8, 0x0b, 0x47, 0xf4, 0x53, 0xc3, 0x7d, 0x78, 0x8e, 0xb4, 0x4d, 0xb7, 0x48, 0x2f, 0x6d, 0x46, 0x3d, 0x19,
|
||||
0xe5, 0x70, 0x24, 0x4c, 0xbb, 0xa0, 0xe3, 0x58, 0xfc, 0x78, 0x74, 0xfa, 0x8c, 0xb1, 0x95, 0x5c, 0xaf, 0xb5, 0x32,
|
||||
0x12, 0x53, 0xfe, 0x93, 0xd1, 0x23, 0x2c, 0x45, 0xed, 0x4c, 0xe9, 0xc9, 0x99, 0x0d, 0x7d, 0xff, 0xdc, 0x01, 0x30,
|
||||
0x51, 0x55, 0x2c, 0x63, 0xa0, 0xb0, 0xc7, 0x6d, 0xee, 0xe4, 0xcc, 0x36, 0xd0, 0x32, 0x40, 0x96, 0x91, 0xdd, 0x43,
|
||||
0x6b, 0x26, 0xaa, 0xd8, 0x7c, 0xd6, 0x16, 0x75, 0x11, 0xa6, 0x5a, 0x4a, 0x4e, 0x86, 0x1f, 0x51, 0x53, 0x3c, 0x01,
|
||||
0x1a, 0x16, 0x14, 0xc6, 0x54, 0xfb, 0x44, 0x5b, 0x1a, 0x38, 0x21, 0x92, 0x03, 0xeb, 0x04, 0x9d, 0xe8, 0xf8, 0xfa,
|
||||
0x4a, 0x73, 0xa4, 0x2f, 0xfc, 0x6d, 0xf3, 0x18, 0x6d, 0xc4, 0xc1, 0x62, 0x25, 0x5d, 0xa3, 0x9d, 0xb9, 0x9f, 0x7b,
|
||||
0xa8, 0xc4, 0xbb, 0xdd, 0xdb, 0xa7, 0xbd, 0x25, 0xf7, 0x00, 0x54, 0x54, 0xce, 0xeb, 0x61, 0xaf, 0xb2, 0xfb, 0x42,
|
||||
0x16, 0x9f, 0xf7, 0xdb, 0x25, 0x28, 0x54, 0x68, 0x0c, 0x22, 0x76, 0x06, 0x2f, 0x12, 0xa7, 0xfa, 0x7c, 0x57, 0xd3,
|
||||
0xc8, 0x90, 0x17, 0x09, 0xf5, 0x89, 0xea, 0xb2, 0x96, 0xa9, 0x49, 0x8f, 0xa1, 0xb0, 0xb5, 0x74, 0xf0, 0xf6, 0xa7,
|
||||
0xc6, 0x14, 0x4a, 0x3a, 0xb6, 0xdf, 0x8d, 0x9a, 0x3a, 0xb0, 0x0e, 0x2c, 0xd0, 0x7c, 0xa5, 0x7b, 0xf2, 0xa1, 0x8e,
|
||||
0xe5, 0x58, 0x6b, 0x0b, 0x0a, 0xf0, 0x62, 0xb8, 0xf0, 0x9e, 0x59, 0xac, 0xf6, 0xb3, 0x38, 0x54, 0x7f, 0x2f, 0x84,
|
||||
0x10, 0x5a, 0xb7, 0xb3, 0x8a, 0xf3, 0x54, 0x32, 0xdb, 0x3b, 0xf1, 0x32, 0x5c, 0x59, 0x93, 0x36, 0x4c, 0x0d, 0x56,
|
||||
0x5e, 0x26, 0xe8, 0x2b, 0x71, 0xc0, 0x2e, 0x53, 0xab, 0x23, 0x86, 0x9a, 0x4c, 0x2e, 0x68, 0x54, 0xdd, 0xe9, 0x43,
|
||||
0x19, 0x40, 0xaa, 0x71, 0x40, 0x7f, 0xea, 0xdb, 0x1c, 0x51, 0xe4, 0x6c, 0xf9, 0x6b, 0xf3, 0x37, 0xd4, 0x8d, 0xa9,
|
||||
0x67, 0xde, 0x48, 0xae, 0xea, 0xaf, 0x8f, 0x5f, 0xdd, 0x4a, 0x05, 0x22, 0xb6, 0xd5, 0x00, 0x8b, 0x33, 0x15, 0x60,
|
||||
0x30, 0x06, 0xab, 0x13, 0x4c, 0x3d, 0x10, 0x63, 0x16, 0x73, 0x52, 0x06, 0xdf, 0xb8, 0x8c, 0xe4, 0xee, 0x41, 0x26,
|
||||
0xa6, 0x1c, 0xd0, 0xd2, 0x03, 0x2e, 0xe2, 0x41, 0xc9, 0xf5, 0x9a, 0xa9, 0xad, 0x27, 0x70, 0xf8, 0xd4, 0xfc, 0x99,
|
||||
0xd5, 0x1d, 0x88, 0x35, 0x5b, 0x35, 0xdb, 0x3b, 0x94, 0x58, 0xda, 0x24, 0xb7, 0xe7, 0xdc, 0x5a, 0x22, 0xaf, 0x04,
|
||||
0x5f, 0x16, 0xf3, 0x61, 0xbd, 0x34, 0x3f, 0xc6, 0xb2, 0xbb, 0xec, 0x6a, 0x97, 0x7f, 0x72, 0x40, 0x14, 0xa8, 0x70,
|
||||
0x15, 0x06, 0x30, 0x7d, 0x6b, 0x2b, 0x1a, 0xac, 0x17, 0x93, 0x73, 0xe1, 0x9c, 0xc7, 0x0a, 0xd7, 0x46, 0xb6, 0xee,
|
||||
0xed, 0x06, 0x63, 0xac, 0xb3, 0x11, 0x50, 0x45, 0x38, 0x37, 0xa9, 0xf1, 0x11, 0x04, 0x31, 0x97, 0x2a, 0x98, 0xed,
|
||||
0x07, 0x4c, 0x46, 0x92, 0xfd, 0x72, 0xef, 0xb0, 0xd6, 0xcb, 0x1d, 0x9b, 0x10, 0x8f, 0xde, 0x1c, 0x83, 0x82, 0x66,
|
||||
0xdc, 0x61, 0x49, 0x64, 0x8c, 0xef, 0x70, 0x20, 0xd5, 0x00, 0x37, 0x59, 0x03, 0x36, 0x1f, 0xe1, 0x71, 0xf8, 0x39,
|
||||
0xf4, 0xb2, 0x32, 0x29, 0x5d, 0x84, 0xcf, 0xfd, 0x47, 0x75, 0xf8, 0xb0, 0xe4, 0x53, 0x09, 0x7c, 0xbf, 0x55, 0x58,
|
||||
0xe3, 0xe2, 0x0a, 0xa5, 0x7c, 0xa7, 0x05, 0x5f, 0x7e, 0x5e, 0x45, 0x9c, 0x0e, 0xa4, 0x67, 0xd6, 0x48, 0x7e, 0x8b,
|
||||
0x66, 0x0f, 0x38, 0x06, 0xc2, 0x95, 0x00, 0xcc, 0x77, 0xa9, 0x41, 0x9b, 0xe4, 0xc3, 0xc1, 0xe8, 0xbc, 0xd1, 0x79,
|
||||
0x8a, 0x39, 0xbf, 0x38, 0x29, 0x21, 0xc9, 0xf6, 0x65, 0x7a, 0x80, 0xf9, 0xc6, 0x0c, 0x9d, 0xaa, 0x66, 0x9b, 0x89,
|
||||
0x34, 0x60, 0xe1, 0xa8, 0xe2, 0xf7, 0x4d, 0x69, 0x35, 0x86, 0x7e, 0x0f, 0xc8, 0x2c, 0x7e, 0x3c, 0xe4, 0x33, 0xbf,
|
||||
0x42, 0xe3, 0x44, 0xd6, 0x29, 0x45, 0x4d, 0xf9, 0x80, 0x94, 0x2e, 0x3a, 0xe4, 0xf8, 0x26, 0x80, 0x52, 0x99, 0x00,
|
||||
0x4a, 0x2c, 0xb7, 0xfd, 0x24, 0x0c, 0x8b, 0x80, 0xe5, 0x4c, 0xdd, 0x7c, 0xad, 0x52, 0x62, 0x9e, 0xa2, 0x9c, 0xdb,
|
||||
0x3a, 0x65, 0x8f, 0xee, 0xf6, 0x56, 0xff, 0xc3, 0x38, 0x8d, 0x7d, 0x5e, 0x50, 0x93, 0x64, 0xbf, 0x65, 0xe4, 0xa2,
|
||||
0xbb, 0x21, 0x25, 0x47, 0x98, 0x1c, 0x25, 0xcf, 0x6b, 0x09, 0x95, 0x30, 0x32, 0xa3, 0x87, 0xa2, 0xb1, 0x55, 0xa9,
|
||||
0x30, 0x0d, 0xac, 0x3e, 0x03, 0x29, 0x7d, 0xe0, 0x69, 0xc4, 0xd5, 0xf7, 0xa8, 0x4f, 0xa5, 0xf8, 0x85, 0x8f, 0x2a,
|
||||
0x5e, 0x93, 0xf7, 0x1e, 0x69, 0x3c, 0xc3, 0xa0, 0x99, 0x91, 0xd4, 0xe4, 0xe9, 0xd1, 0x4f, 0x9a, 0xcc, 0xbb, 0xf4,
|
||||
0x1f, 0xe8, 0x94, 0x3e, 0x66, 0x95, 0xf7, 0x3f, 0xa5, 0x14, 0x5f, 0x3a, 0xbf, 0x8f, 0x75, 0xfe, 0x74, 0xd5, 0x1a,
|
||||
0x05, 0x4b, 0xd2, 0x34, 0x50, 0x3b, 0xcf, 0x39, 0x00, 0x1c, 0x6f, 0x7d, 0xb4, 0xd0, 0x65, 0x3b, 0xa6, 0x30, 0xf6,
|
||||
0x65, 0x84, 0xb8, 0x32, 0x2c, 0x10, 0xf3, 0xf2, 0x4e, 0x76, 0xa5, 0x90, 0x22, 0x71, 0xba, 0x69, 0x64, 0x1c, 0xf1,
|
||||
0x14, 0xc1, 0x98, 0x48, 0x5f, 0x21, 0x63, 0x26, 0x0d, 0xb8, 0x73, 0x4b, 0x52, 0xcc, 0x16, 0x78, 0x0a, 0x44, 0x89,
|
||||
0xdd, 0xbd, 0xf4, 0x5a, 0x47, 0x54, 0xaa, 0x7e, 0x06, 0x97, 0xf8, 0x31, 0x9f, 0x6e, 0xbf, 0x91, 0x36, 0xd4, 0x80,
|
||||
0xf9, 0xba, 0x9a, 0x62, 0x1e, 0xce, 0xe1, 0x03, 0xec, 0x7a, 0x55, 0xff, 0x1e, 0x96, 0x2a, 0x41, 0x9f, 0xf2, 0xd7,
|
||||
0x0c, 0xa4, 0x46, 0x95, 0xc8, 0x86, 0xdd, 0x96, 0x8d, 0x60, 0x00, 0x06, 0x71, 0x6d, 0x23, 0xc2, 0x91, 0x1e, 0xd2,
|
||||
0xdb, 0xe2, 0xfe, 0x59, 0xc9, 0xb5, 0x46, 0x36, 0x6f, 0x0b, 0x3f, 0x3f, 0xd2, 0x9e, 0x5c, 0x93, 0x99, 0xac, 0x13,
|
||||
0x42, 0x44, 0x82, 0x7f, 0xbc, 0x2c, 0xcc, 0xb7, 0xfa, 0x3f, 0x9e, 0xec, 0x6e, 0x77, 0x41, 0x44, 0xab, 0x1f, 0xbe,
|
||||
0x7a, 0xd4, 0xc5, 0x2c, 0x32, 0xc6, 0xfe, 0xba, 0x1d, 0x05, 0xdc, 0x7c, 0xd1, 0x67, 0x30, 0xf6, 0x65, 0x83, 0x80,
|
||||
0x69, 0xd0, 0xd7, 0xc7, 0xdf, 0xff, 0xbb, 0xef, 0xf8, 0xb8, 0x09, 0x0f, 0x73, 0x31, 0x89, 0x64, 0x7c, 0x6f, 0x1f,
|
||||
0x11, 0xb8, 0x60, 0xb4, 0x97, 0x11, 0xb7, 0xe4, 0x12, 0x41, 0x8b, 0x6c, 0x39, 0x20, 0xaf, 0x51, 0x13, 0xae, 0xcf,
|
||||
0xd1, 0x7a, 0xe0, 0x4f, 0x0b, 0x51, 0x2b, 0xe3, 0x6d, 0x84, 0xd1, 0xee, 0xe1, 0xc4, 0xee, 0x9c, 0xb7, 0x00, 0x41,
|
||||
0xbe, 0x51, 0xa2, 0xbb, 0x68, 0x90, 0x8e, 0xca, 0x4c, 0x02, 0xdb, 0x62, 0xd5, 0xc8, 0x4f, 0xd0, 0x03, 0x48, 0x9d,
|
||||
0x3e, 0x09, 0x98, 0x5d, 0x05, 0xa8, 0xa5, 0xcf, 0xb6, 0x4a, 0x27, 0x50, 0xe6, 0xdd, 0x45, 0x98, 0xbd, 0x75, 0x07,
|
||||
0xb9, 0x3d, 0xa0, 0x7f, 0xd7, 0x53, 0x88, 0xb7, 0xa8, 0xa0, 0xcc, 0x59, 0xab, 0x04, 0x1b, 0x21, 0x27, 0xee, 0x94,
|
||||
0x03, 0xea, 0x66, 0xe1, 0x1b, 0xbf, 0xd7, 0x0d, 0x59, 0xac, 0x9e, 0x29, 0xe9, 0xf0, 0x53, 0xa3, 0x63, 0xbf, 0xda,
|
||||
0x61, 0xb7, 0xbb, 0x28, 0xa0, 0xd6, 0x32, 0xd5, 0x90, 0x21, 0x1a, 0x66, 0xab, 0x92, 0xcd, 0x58, 0xd9, 0xad, 0x85,
|
||||
0xec, 0x45, 0x24, 0x95, 0xb6, 0xfd, 0x4d, 0x6d, 0xe3, 0x4c, 0x44, 0x4b, 0x4d, 0xd7, 0xe0, 0xc2, 0x40, 0x7a, 0x56,
|
||||
0xec, 0x0a, 0xe0, 0xaf, 0x6e, 0x45, 0xe4, 0xeb, 0x30, 0xe3, 0xab, 0xf3, 0xd9, 0xfe, 0x7e, 0x40, 0x0d, 0x5d, 0x68,
|
||||
0x4c, 0x66, 0xc5, 0x93, 0xbb, 0x33, 0x06, 0x63, 0x49, 0xf3, 0xc1, 0x4f, 0x73, 0x90, 0xfa, 0x84, 0xe5, 0x5b, 0x61,
|
||||
0x38, 0xa6, 0xcb, 0x60, 0xce, 0x05, 0xf5, 0xe3, 0xaf, 0x43, 0xcb, 0xeb, 0xe2, 0xfd, 0x14, 0xd2, 0x71, 0x60, 0x2f,
|
||||
0x5c, 0x7c, 0x0b, 0x87, 0x81, 0x1a, 0xc2, 0xd5, 0xfd, 0xf3, 0xd9, 0x19, 0x93, 0x9e, 0x5b, 0x56, 0x25, 0xbb, 0xfd,
|
||||
0x10, 0x75, 0xd0, 0xc5, 0x86, 0xb9, 0xe4, 0x7a, 0xb0, 0xe0, 0xdd, 0xca, 0xf7, 0xa7, 0x9a, 0x3e, 0xb6, 0x5a, 0xd0,
|
||||
0xde, 0x75, 0x6b, 0xa5, 0xa3, 0xdf, 0xb4, 0x0e, 0xa1, 0x89, 0x8c, 0xe9, 0xf1, 0x62, 0x8b, 0xda, 0x5f, 0xf8, 0x1d,
|
||||
0xe6, 0xd9, 0x70, 0xc9, 0xd0, 0x92, 0x42, 0x3f, 0x36, 0xf9, 0xb9, 0x23, 0x6b, 0x5d, 0x26, 0x9c, 0x14, 0x5d, 0xe7,
|
||||
0x9b, 0x81, 0x11, 0x1b, 0xec, 0xd1, 0xf9, 0x22, 0xfe, 0xc4, 0xa9, 0xd7, 0x71, 0x5e, 0x32, 0x8d, 0x64, 0x41, 0x50,
|
||||
0xca, 0x7c, 0xc8, 0x55, 0xc6, 0xb5, 0xe3, 0xd8, 0xa1, 0xe8, 0xb5, 0x1c, 0x68, 0x20, 0xef, 0x42, 0xc6, 0x9d, 0xea,
|
||||
0x2f, 0x8a, 0xa4, 0x87, 0x3b, 0xdc, 0x0c, 0xc7, 0x9f, 0x3f, 0x89, 0x29, 0x9b, 0x46, 0xb7, 0xc7, 0x6a, 0x16, 0xe6,
|
||||
0x2f, 0xe6, 0x9f, 0xa8, 0x02, 0xfb, 0x99, 0x1b, 0x4f, 0xcd, 0x40, 0xf7, 0xad, 0xa9, 0xd0, 0x56, 0x1a, 0xad, 0xc0,
|
||||
0x47, 0x96, 0x36, 0xbe, 0x11, 0x7f, 0x0c, 0x6d, 0x47, 0xab, 0x4f, 0xe1, 0x90, 0x47, 0x21, 0xd6, 0x33, 0x72, 0x11,
|
||||
0xb4, 0x65, 0x99, 0x4a, 0x43, 0x52, 0x90, 0x80, 0x2e, 0xed, 0x75, 0x96, 0x39, 0x78, 0x45, 0x0f, 0x42, 0xd2, 0x3b,
|
||||
0x3b, 0xf9, 0x84, 0xed, 0x1d, 0x5e, 0x8a, 0x24, 0x1f, 0xff, 0xe1, 0x7e, 0x75, 0xef, 0xd4, 0xc5, 0x51, 0x85, 0x07,
|
||||
0x0e, 0xcb, 0xda, 0x40, 0x92, 0x8e, 0x00, 0xf9, 0x4c, 0x44, 0x03, 0xa6, 0x56, 0xbf, 0xa0, 0xc6, 0x04, 0xec, 0xc7,
|
||||
0xac, 0xbd, 0x78, 0xd2, 0x0a, 0x9d, 0x3f, 0x83, 0x42, 0xcc, 0x79, 0xc0, 0x6d, 0x9d, 0xa3, 0x0d, 0x27, 0x66, 0x2f,
|
||||
0xcd, 0x21, 0x22, 0xeb, 0x38, 0x64, 0xc7, 0x31, 0x1b, 0x66, 0x68, 0xd4, 0x53, 0xce, 0x0b, 0xc5, 0x07, 0x07, 0x48,
|
||||
0x89, 0xa9, 0x86, 0xa6, 0x5b, 0xaa, 0xdc, 0xb5, 0x20, 0x00, 0x7b, 0xeb, 0x0f, 0x58, 0x48, 0x3f, 0x1d, 0x1b, 0xe4,
|
||||
0x87, 0x29, 0x23, 0xba, 0x92, 0xec, 0x3a, 0xc8, 0x05, 0x9e, 0x56, 0x2e, 0xf6, 0x69, 0x97, 0x79, 0x4a, 0x8f, 0xfc,
|
||||
0xac, 0xcf, 0x5a, 0xe9, 0x12, 0x18, 0xc7, 0x59, 0xc6, 0x1c, 0xf1, 0xe3, 0xfe, 0x8d, 0x3f, 0x6e, 0x1c, 0xfa, 0x4d,
|
||||
0xb3, 0x73, 0xa4, 0x9b, 0x77, 0xd9, 0x20, 0xf8, 0x86, 0xf1, 0xb0, 0xac, 0x6e, 0x35, 0x62, 0x68, 0x01, 0xc6, 0xe6,
|
||||
0xee, 0x88, 0xf3, 0x30, 0x80, 0xc7, 0x8b, 0xd2, 0x0a, 0xb5, 0xe3, 0x17, 0x0e, 0xf8, 0xa6, 0xdf, 0x53, 0x22, 0x5f,
|
||||
0x3d, 0x4d, 0xae, 0xc7, 0xd1, 0x4b, 0xb9, 0xd0, 0xe6, 0x63, 0x80, 0x6e, 0x4c, 0x1d, 0x9d, 0x28, 0xf4, 0xe2, 0x01,
|
||||
0x62, 0x4d, 0x23, 0xb0, 0x67, 0x61, 0x49, 0x3f, 0x39, 0xae, 0x5f, 0x0f, 0x18, 0xc5, 0x81, 0x47, 0xd4, 0xa5, 0x04,
|
||||
0x93, 0xd3, 0xd5, 0x63, 0x9e, 0xe2, 0x43, 0xec, 0x50, 0xbc, 0xdf, 0x2e, 0xc7, 0x5a, 0xf7, 0x21, 0x17, 0x00, 0x51,
|
||||
0x14, 0xc2, 0x04, 0x0a, 0x57, 0xf6, 0x5d, 0x31, 0xac, 0x89, 0xef, 0x22, 0x6e, 0xb5, 0x98, 0xa4, 0x7f, 0x93, 0xfa,
|
||||
0xc2, 0xf3, 0x58, 0x07, 0x6a, 0x7e, 0x53, 0x4e, 0xc7, 0x9e, 0xa2, 0x07, 0x5e, 0x87, 0xb7, 0xdf, 0xea, 0xd3, 0x8f,
|
||||
0x84, 0xf8, 0xe6, 0xac, 0xf0, 0x42, 0x22, 0xd7, 0x76, 0xcd, 0xd0, 0x97, 0x8e, 0xc1, 0xc9, 0x6f, 0x61, 0x0c, 0xe8,
|
||||
0xdb, 0x13, 0xb5, 0x2f, 0x09, 0xd2, 0x00, 0xbc, 0x58, 0xa3, 0x47, 0x6a, 0x91, 0xe7, 0x93, 0x46, 0xe7, 0xb0, 0xfd,
|
||||
0xb8, 0xb0, 0xc6, 0xc7, 0x6d, 0xe1, 0x77, 0x39, 0x07, 0x4c, 0x22, 0x3d, 0x84, 0x98, 0x82, 0xba, 0x6f, 0x04, 0xbb,
|
||||
0xb0, 0xc9, 0xdb, 0x26, 0x18, 0xb4, 0xb6, 0xdd, 0x79, 0xe5, 0x74, 0x05, 0x4a, 0xc7, 0xfa, 0xde, 0x0b, 0x70, 0xab,
|
||||
0x8c, 0x22, 0x45, 0xa6, 0xa3, 0x76, 0x6b, 0xc2, 0x74, 0xcd, 0xf4, 0x4b, 0x19, 0xe6, 0xc6, 0x79, 0x42, 0x56, 0x4b,
|
||||
0xb4, 0xbe, 0x23, 0xdb, 0x8a, 0xc9, 0x22, 0xe9, 0x6c, 0x18, 0x10, 0xef, 0x2c, 0xa5, 0x73, 0x1a, 0x37, 0x3a, 0x2e,
|
||||
0xb5, 0x92, 0xc0, 0xe7, 0x57, 0x95, 0x45, 0x19, 0xcd, 0x6a, 0xce, 0xf8, 0xc5, 0x45, 0xe2, 0x08, 0x3f, 0x61, 0xd3,
|
||||
0x7b, 0xb3, 0xbe, 0xcb, 0x4f, 0x03, 0x9b, 0x9a, 0x24, 0x1f, 0xb5, 0x41, 0xde, 0x7a, 0x3e, 0x3e, 0xff, 0x30, 0x9c,
|
||||
0x95, 0xc1, 0xf4, 0xe6, 0xa3, 0xf6, 0xbb, 0x1f, 0xa2, 0xa0, 0x2a, 0x30, 0xfb, 0x50, 0xc0, 0xa7, 0x4f, 0x3f, 0xc3,
|
||||
0x32, 0x43, 0x2e, 0xec, 0xd9, 0x0f, 0xfd, 0xd1, 0xff, 0xc6, 0x0d, 0x16, 0x17, 0x63, 0x57, 0x53, 0x25, 0x97, 0x49,
|
||||
0xc4, 0xfb, 0x55, 0x64, 0xb4, 0x31, 0x19, 0x97, 0xa6, 0xe1, 0xfc, 0x59, 0x48, 0x27, 0x79, 0x88, 0x18, 0x5c, 0xfd,
|
||||
0x04, 0xd3, 0x0d, 0x30, 0x0a, 0xa2, 0xbd, 0x06, 0x52, 0x04, 0x5f, 0x84, 0xe8, 0x7a, 0x92, 0x52, 0xcc, 0xf4, 0x9a,
|
||||
0x06, 0x48, 0xfe, 0x22, 0x2a, 0x13, 0xaa, 0xb4, 0x13, 0x2a, 0x3c, 0xf4, 0x61, 0x6a, 0x98, 0xa0, 0x55, 0xee, 0x6c,
|
||||
0x48, 0xd6, 0x2b, 0x1d, 0xcc, 0x41, 0xcc, 0x73, 0x9a, 0xf4, 0x4e, 0x03, 0xa4, 0x98, 0x5c, 0x60, 0x24, 0x32, 0xe8,
|
||||
0x00, 0x0f, 0xe9, 0x0d, 0xf0, 0x5a, 0x95, 0xa8, 0x38, 0xef, 0x0b, 0xb0, 0x0c, 0x6e, 0x2b, 0x59, 0xb7, 0xa4, 0xa6,
|
||||
0xed, 0x47, 0xcc, 0x90, 0x65, 0xf9, 0x88, 0xa9, 0xca, 0x63, 0x9d, 0x6b, 0x4e, 0x64, 0x71, 0xd3, 0x29, 0xcb, 0x46,
|
||||
0xf0, 0x34, 0xca, 0x34, 0x2c, 0x48, 0x19, 0xe8, 0x5d, 0x55, 0xe9, 0x1f, 0x8b, 0x41, 0x8f, 0xe0, 0x7f, 0x86, 0xed,
|
||||
0x7c, 0x10, 0x8a, 0xa0, 0x46, 0x2d, 0xbd, 0xeb, 0x13, 0xa9, 0x75, 0x66, 0x70, 0xca, 0x89, 0x66, 0xa6, 0x8f, 0xaf,
|
||||
0x2b, 0x6e, 0xc5, 0x96, 0xcd, 0x74, 0xc8, 0xc2, 0xe3, 0x8e, 0x1a, 0x2b, 0xf8, 0xb9, 0xa5, 0x95, 0xaf, 0xd8, 0xd0,
|
||||
0xae, 0xb0, 0x2b, 0x44, 0xe9, 0x59, 0x90, 0x7b, 0x1d, 0x26, 0x7d, 0x0d, 0xdc, 0xdc, 0xb9, 0xba, 0x94, 0x78, 0xdd,
|
||||
0x36, 0x68, 0xdb, 0x8e, 0xb8, 0xcc, 0x32, 0x77, 0x41, 0xe3, 0x27, 0x09, 0xac, 0x98, 0xc9, 0xf2, 0x7a, 0x7e, 0x3c,
|
||||
0x5e, 0x5b, 0x68, 0x20, 0xf0, 0x7c, 0xc8, 0xc2, 0x7f, 0x94, 0xc8, 0x05, 0x91, 0xac, 0x2a, 0x0e, 0x98, 0xb1, 0xe0,
|
||||
0xb3, 0xee, 0xba, 0x6a, 0xa1, 0x92, 0x1a, 0xf3, 0x4f, 0xa0, 0x25, 0x2b, 0xa6, 0x92, 0xc3, 0xe3, 0x6d, 0x39, 0xb6,
|
||||
0x6a, 0x76, 0x79, 0xab, 0xa9, 0x8a, 0xc7, 0x02, 0x00, 0x15, 0x64, 0x5d, 0xdf, 0x92, 0x56, 0xea, 0x25, 0xb5, 0x2b,
|
||||
0x71, 0x39, 0x5a, 0x6d, 0x1c, 0xe9, 0x30, 0xe4, 0x0a, 0x05, 0x24, 0x8e, 0x92, 0xba, 0x90, 0x5f, 0x57, 0xb0, 0x20,
|
||||
0xb9, 0x10, 0xe3, 0x88, 0xa9, 0x14, 0x42, 0xe8, 0xe7, 0xa8, 0x09, 0xfa, 0x8c, 0x09, 0xdf, 0x01, 0x91, 0x38, 0xaf,
|
||||
0x71, 0x0b, 0x88, 0x46, 0x5f, 0x29, 0xe8, 0xb1, 0xa7, 0xe9, 0xf1, 0x54, 0xdf, 0x9d, 0xae, 0xca, 0xff, 0x21, 0x2f,
|
||||
0x80, 0xcc, 0xd9, 0xcc, 0x11, 0x09, 0x6d, 0x3a, 0x5f, 0x5d, 0x44, 0x4f, 0x75, 0x03, 0xc1, 0x95, 0x1f, 0x33, 0x40,
|
||||
0x90, 0x47, 0x54, 0xcf, 0xcf, 0x0e, 0xde, 0xac, 0xb5, 0xae, 0x11, 0x83, 0x05, 0x12, 0x57, 0x64, 0x2f, 0x32, 0x7a,
|
||||
0x86, 0x5c, 0x30, 0x86, 0xbe, 0x3b, 0x34, 0x7c, 0xbd, 0x89, 0x08, 0xbb, 0x0c, 0x63, 0x72, 0x2e, 0xc9, 0x4b, 0x55,
|
||||
0x36, 0x1f, 0xfc, 0x86, 0x56, 0x86, 0xed, 0x6f, 0xa1, 0xc0, 0xa7, 0x66, 0xe4, 0x8f, 0xc7, 0xcb, 0x93, 0x36, 0xd2,
|
||||
0xe4, 0x74, 0xb6, 0xbd, 0x78, 0x0d, 0xc5, 0x37, 0x2d, 0xcf, 0x8e, 0xbc, 0xa1, 0xff, 0x2c, 0x5a, 0x4b, 0x68, 0xe4,
|
||||
0x42, 0x10, 0xc2, 0x24, 0x0e, 0xa5, 0xe0, 0x3f, 0xd4, 0xa7, 0x9d, 0xe6, 0xee, 0x7b, 0x85, 0x66, 0x87, 0xe7, 0x42,
|
||||
0xa4, 0xe6, 0x6d, 0xcd, 0xd8, 0x7a, 0x6b, 0x50, 0x0c, 0xaa, 0xf4, 0xdd, 0x53, 0x59, 0x6c, 0xd1, 0xca, 0x3b, 0xe6,
|
||||
0x06, 0x50, 0xda, 0x3d, 0xbc, 0x05, 0x67, 0xf4, 0x45, 0x83, 0x41, 0xce, 0x72, 0xdf, 0x47, 0x2c, 0xc0, 0x5e, 0xde,
|
||||
0x2b, 0x42, 0xf7, 0xc1, 0x7a, 0x4e, 0x8d, 0x8b, 0xfc, 0xe7, 0xe9, 0x06, 0x21, 0xee, 0x79, 0x31, 0x04, 0x07, 0xca,
|
||||
0x13, 0x44, 0x4c, 0xc7, 0x42, 0xa4, 0xb4, 0xaf, 0x32, 0x62, 0x42, 0xd7, 0x3f, 0x58, 0xed, 0x25, 0x78, 0xd3, 0x52,
|
||||
0x48, 0x31, 0x36, 0x1c, 0xb8, 0x47, 0x4f, 0x2f, 0x71, 0x31, 0x75, 0x97, 0x6f, 0xcc, 0x66, 0x34, 0xe8, 0xe3, 0xe2,
|
||||
0x21, 0x4c, 0x7f, 0x06, 0xe9, 0xfd, 0xf4, 0x6a, 0x69, 0xce, 0xa8, 0xfa, 0x83, 0x3d, 0xa2, 0x95, 0x87, 0x70, 0xd6,
|
||||
0x67, 0x62, 0xf2, 0x33, 0x06, 0x84, 0x2a, 0x68, 0x4f, 0x93, 0x9f, 0x7a, 0xff, 0xd1, 0xee, 0x07, 0x10, 0x4d, 0x86,
|
||||
0x57, 0xda, 0xcd, 0x2a, 0x28, 0xe5, 0x77, 0x24, 0x36, 0x8f, 0x41, 0x4a, 0x8c, 0x62, 0x1e, 0xb2, 0x8d, 0x32, 0xe6,
|
||||
0x34, 0xc1, 0xf9, 0x01, 0xa4, 0x61, 0x73, 0xdb, 0xcc, 0xa1, 0x79, 0x08, 0x2c, 0xc0, 0x1f, 0x32, 0x71, 0xd0, 0x8c,
|
||||
0xc6, 0xb5, 0x45, 0x9c, 0x62, 0x7c, 0x40, 0x37, 0xfd, 0x6a, 0x99, 0x6f, 0xd4, 0xc6, 0xae, 0x5c, 0x18, 0xaf, 0xef,
|
||||
0xb7, 0x59, 0x20, 0x25, 0x39, 0xb0, 0xa5, 0x85, 0xb1, 0xaf, 0xda, 0x78, 0xe8, 0x09, 0xa9, 0x28, 0x34, 0x02, 0x3f,
|
||||
0xca, 0xde, 0x95, 0xd3, 0xae, 0x35, 0x67, 0x16, 0x08, 0x6f, 0x6e, 0x78, 0x7c, 0x43, 0x60, 0x21, 0x6b, 0x46, 0xde,
|
||||
0xaa, 0x00, 0x09, 0x5d, 0x9f, 0xf8, 0xf0, 0xd8, 0xb6, 0x35, 0xd1, 0x38, 0xcc, 0x02, 0x50, 0x7e, 0xa3, 0x24, 0xfd,
|
||||
0xb6, 0x5d, 0xa5, 0x2a, 0x9c, 0xb7, 0x60, 0x8d, 0x0b, 0x07, 0x4b, 0x9b, 0x29, 0xf4, 0x87, 0x9f, 0x54, 0xf7, 0x29,
|
||||
0x9c, 0xcb, 0x94, 0xc6, 0xe8, 0x1d, 0x59, 0xc1, 0x85, 0x52, 0x93, 0x54, 0x3e, 0xda, 0xd3, 0x5f, 0x86, 0x39, 0x24,
|
||||
0xf3, 0x10, 0x70, 0x09, 0x9e, 0x2d, 0x2a, 0x39, 0x74, 0xea, 0xac, 0x19, 0xe2, 0xe5, 0x79, 0xfd, 0x32, 0xf3, 0xc8,
|
||||
0x1f, 0xdd, 0x74, 0xa7, 0x5b, 0x94, 0x70, 0xeb, 0xb0, 0x0d, 0x7a, 0xde, 0x77, 0x8e, 0x41, 0xa8, 0x61, 0x3a, 0xf6,
|
||||
0x79, 0x32, 0x1e, 0x62, 0x7c, 0x3f, 0xfb, 0xc3, 0x7b, 0xa8, 0xd1, 0x6a, 0x52, 0x56, 0x30, 0x15, 0x15, 0xb3, 0x3e,
|
||||
0x0d, 0x01, 0x2c, 0x87, 0xff, 0x23, 0x68, 0x88, 0x9b, 0xca, 0x45, 0xf9, 0xee, 0xae, 0x97, 0xde, 0x80, 0xdd, 0x9f,
|
||||
0x4c, 0x2d, 0x69, 0x4d, 0xfc, 0xc3, 0xd3, 0x36, 0x91, 0x61, 0x36, 0x53, 0xa9, 0x8a, 0x33, 0x7f, 0xa5, 0x0c, 0xa0,
|
||||
0xaf, 0x39, 0x93, 0xef, 0xee, 0xb2, 0x0c, 0x60, 0x20, 0x0a, 0xbb, 0xe7, 0x73, 0xad, 0x1b, 0xdb, 0xa4, 0x09, 0x79,
|
||||
0x63, 0xf7, 0xd8, 0x4f, 0xa0, 0xf3, 0x2d, 0x61, 0x9a, 0xe1, 0x40, 0x99, 0xec, 0x03, 0xf9, 0xa3, 0x2f, 0x95, 0x3d,
|
||||
0x48, 0x28, 0x85, 0xfc, 0x8b, 0xb1, 0xf1, 0xf7, 0x86, 0x68, 0x64, 0x52, 0x01, 0x19, 0xd0, 0xdd, 0xd7, 0xec, 0x4b,
|
||||
0x22, 0x09, 0x4e, 0x8a, 0x2e, 0x9f, 0xa0, 0x1e, 0x20, 0x43, 0xa2, 0x70, 0x73, 0x9b, 0x9e, 0x7d, 0x0b, 0x69, 0x54,
|
||||
0x52, 0x01, 0x65, 0xa8, 0x47, 0x2e, 0xd6, 0x0e, 0xfb, 0x79, 0xf2, 0xb1, 0x13, 0xe1, 0xfb, 0x66, 0xb1, 0x49, 0x16,
|
||||
0x54, 0xb6, 0x07, 0xe9, 0x5f, 0xb8, 0x2a, 0xfc, 0x59, 0xc2, 0x8f, 0xcd, 0xe8, 0xce, 0xa3, 0xb0, 0x35, 0x03, 0xf5,
|
||||
0x6d, 0x59, 0xaf, 0x83, 0x98, 0x88, 0x03, 0xc0, 0xae, 0xfb, 0x90, 0x68, 0xf4, 0x9f, 0x85, 0x04, 0x10, 0x7f, 0xa5,
|
||||
0x3c, 0x64, 0xf2, 0x0b, 0xe0, 0x2e, 0x84, 0x9e, 0xb5, 0x60, 0xa5, 0x52, 0xff, 0x40, 0x14, 0x23, 0xd3, 0xc0, 0x09,
|
||||
0xd2, 0x19, 0x9b, 0x25, 0x5e, 0x63, 0x34, 0x1a, 0x41, 0x71, 0x2e, 0x1a, 0x70, 0x80, 0xca, 0x01, 0x10, 0x57, 0x04,
|
||||
0x41, 0xf8, 0x0f, 0x6d, 0x91, 0xea, 0xa5, 0xc7, 0x55, 0x91, 0x41, 0x62, 0xcc, 0x3a, 0x43, 0x39, 0xfb, 0x52, 0x13,
|
||||
0xcb, 0x5d, 0xa4, 0xa5, 0xbb, 0x47, 0x4f, 0xce, 0x27, 0x1d, 0x88, 0x4a, 0xc8, 0x37, 0x15, 0x89, 0xce, 0x90, 0x84,
|
||||
0x3e, 0x79, 0x34, 0x2f, 0xa5, 0x77, 0x68, 0xf1, 0x96, 0x01, 0x96, 0xa6, 0x4e, 0xb3, 0xff, 0x64, 0xf9, 0x2f, 0x1f,
|
||||
0x7b, 0xff, 0x72, 0xdf, 0x2d, 0x13, 0xf1, 0x7d, 0x2d, 0xfc, 0x7b, 0x3c, 0xd9, 0x21, 0xce, 0xcb, 0x37, 0xd7, 0xf6,
|
||||
0x76, 0xb1, 0x6b, 0x69, 0x0e, 0x90, 0xee, 0xe4, 0x99, 0x3a, 0xc3, 0x04, 0xba, 0x2b, 0xe9, 0xa3, 0xdf, 0xb5, 0x09,
|
||||
0xf8, 0x2c, 0x78, 0x3f, 0x6d, 0x2b, 0x9f, 0xa8, 0x3f, 0x30, 0xdf, 0xb0, 0xd8, 0x8d, 0x30, 0x19, 0xec, 0x7b, 0x4a,
|
||||
0xca, 0x55, 0xc3, 0xde, 0x5d, 0xaf, 0x2f, 0x99, 0x66, 0xb3, 0xb4, 0xda, 0x84, 0x54, 0x5c, 0x2d, 0x2a, 0x9b, 0xf5,
|
||||
0x8f, 0x2b, 0x43, 0x7c, 0x61, 0x07, 0x14, 0x46, 0x28, 0xf7, 0x00, 0xf4, 0x0a, 0xba, 0xff, 0x14, 0x08, 0x1e, 0xaa,
|
||||
0x97, 0xfc, 0x4d, 0x99, 0xf7, 0x52, 0xdd, 0x7e, 0x25, 0x86, 0x95, 0xfe, 0x33, 0x61, 0x00, 0x53, 0xc6, 0xf3, 0xb4,
|
||||
0xa2, 0x39, 0x4f, 0x5f, 0x1d, 0x92, 0x09, 0x96, 0xd8, 0x25, 0x70, 0x5e, 0x6c, 0x0a, 0x8c, 0x76, 0x4e, 0x40, 0xd6,
|
||||
0x6a, 0xed, 0x9e, 0xba, 0x59, 0x8d, 0x7d, 0xcd, 0x81, 0xe6, 0x27, 0xce, 0x8f, 0x7b, 0xfe, 0x5e, 0xa8, 0xbc, 0x6c,
|
||||
0x7f, 0x37, 0xdb, 0x8e, 0xce, 0x75, 0x6e, 0xa0, 0x80, 0xb9, 0xc9, 0x27, 0x1e, 0x85, 0x19, 0xe0, 0x00, 0x4c, 0x41,
|
||||
0x6b, 0x8b, 0x05, 0x74, 0xb7, 0x2a, 0x70, 0x97, 0x2c, 0x0d, 0x2b, 0x54, 0x9b, 0xa4, 0x74, 0xfe, 0xe7, 0xda, 0xbb,
|
||||
0x27, 0x70, 0x12, 0xef, 0x03, 0x43, 0xe2, 0x85, 0x7b, 0x02, 0x92, 0x48, 0x11, 0x94, 0x53, 0xe4, 0x76, 0x8d, 0x3f,
|
||||
0x30, 0xed, 0x5c, 0x15, 0x3e, 0x5d, 0x37, 0xb0, 0x24, 0x39, 0xc7, 0x4c, 0x89, 0x10, 0x93, 0x79, 0xef, 0xf3, 0x2d,
|
||||
0xdb, 0xc2, 0x8a, 0x00, 0x00, 0x00, 0xff, 0xff, 0x1b, 0xed, 0xbb, 0x8c, 0x8a, 0x8d, 0x8a, 0xe1, 0x16, 0x03, 0x00,
|
||||
};
|
||||
|
||||
static constexpr size_t WINDOW = 4096;
|
||||
static constexpr size_t PLAIN_SIZE = 16000;
|
||||
|
||||
@@ -223,18 +757,55 @@ static std::vector<uint8_t> build_plain() {
|
||||
return plain;
|
||||
}
|
||||
|
||||
// Text, incompressible noise that zlib stores, then text again
|
||||
static std::vector<uint8_t> build_mixed() {
|
||||
std::vector<uint8_t> plain;
|
||||
const char *text = "esphome ota deflate ";
|
||||
for (int i = 0; i < 60; i++)
|
||||
plain.insert(plain.end(), text, text + strlen(text));
|
||||
uint32_t x = 3;
|
||||
for (int i = 0; i < 3000; i++)
|
||||
plain.push_back(lcg_next(x));
|
||||
for (int i = 0; i < 40; i++)
|
||||
plain.insert(plain.end(), text, text + strlen(text));
|
||||
return plain;
|
||||
}
|
||||
|
||||
static std::vector<uint8_t> build_skewed() {
|
||||
std::vector<uint8_t> plain;
|
||||
const char *common = "etaoin ";
|
||||
uint32_t x = 7;
|
||||
for (int i = 0; i < 6000; i++) {
|
||||
uint8_t r = lcg_next(x);
|
||||
plain.push_back(r < 230 ? common[r % 7] : lcg_next(x));
|
||||
}
|
||||
return plain;
|
||||
}
|
||||
|
||||
// Mirrors the OTA session: chunked input through the read callback, window as output
|
||||
struct Session : OtaInflateState {
|
||||
const uint8_t *in;
|
||||
size_t in_len;
|
||||
size_t in_pos;
|
||||
size_t chunk;
|
||||
// Like the session where the ack follows the write: output is taken
|
||||
// before each read, up to dest
|
||||
bool flush_in_read;
|
||||
size_t flushed;
|
||||
std::vector<uint8_t> out;
|
||||
uint8_t window[WINDOW];
|
||||
};
|
||||
|
||||
static void take_output(Session &s) {
|
||||
const size_t produced = s.dest - s.window;
|
||||
s.out.insert(s.out.end(), s.window + s.flushed, s.window + produced);
|
||||
s.flushed = produced;
|
||||
}
|
||||
|
||||
static int read_cb(OtaInflateState *d) {
|
||||
auto *s = static_cast<Session *>(d);
|
||||
if (s->flush_in_read)
|
||||
take_output(*s);
|
||||
if (s->in_pos >= s->in_len)
|
||||
return -1;
|
||||
size_t n = std::min(s->chunk, s->in_len - s->in_pos);
|
||||
@@ -245,23 +816,26 @@ static int read_cb(OtaInflateState *d) {
|
||||
}
|
||||
|
||||
// Inflates the whole input; returns the decoder result and fills s.out
|
||||
static int inflate_all(Session &s, const uint8_t *in, size_t in_len, size_t chunk) {
|
||||
static int inflate_all(Session &s, const uint8_t *in, size_t in_len, size_t chunk, bool flush_in_read = false) {
|
||||
s.in = in;
|
||||
s.in_len = in_len;
|
||||
s.in_pos = 0;
|
||||
s.chunk = chunk;
|
||||
s.flush_in_read = flush_in_read;
|
||||
s.flushed = 0;
|
||||
s.out.clear();
|
||||
memset(s.window, 0, sizeof(s.window));
|
||||
ota_inflate_init(&s, s.window, WINDOW);
|
||||
s.source_read_cb = read_cb;
|
||||
int res;
|
||||
do {
|
||||
s.dest = s.window;
|
||||
s.dest_limit = s.window + WINDOW;
|
||||
// Starts the window over once the previous call filled it
|
||||
res = ota_inflate(&s);
|
||||
if (res < 0 || s.eof)
|
||||
return res < 0 ? res : OTA_INFLATE_DATA_ERROR;
|
||||
s.out.insert(s.out.end(), s.window, s.dest);
|
||||
take_output(s);
|
||||
// A full window is taken; the next call starts it over
|
||||
s.flushed = 0;
|
||||
if (s.out.size() > PLAIN_SIZE)
|
||||
return OTA_INFLATE_DATA_ERROR;
|
||||
} while (res != OTA_INFLATE_DONE);
|
||||
@@ -289,6 +863,36 @@ TEST(OtaInflate, StoredBlock) {
|
||||
EXPECT_EQ(s->out, plain);
|
||||
}
|
||||
|
||||
// Dynamic blocks, fixed Huffman blocks, codes longer than the lookup tables, and
|
||||
// stored blocks that start with read ahead bits in the bit buffer
|
||||
struct Stream {
|
||||
const uint8_t *data;
|
||||
size_t len;
|
||||
std::vector<uint8_t> (*plain)();
|
||||
};
|
||||
static const Stream STREAMS[] = {{DEFLATED, sizeof(DEFLATED), build_plain},
|
||||
{FIXED, sizeof(FIXED), build_plain},
|
||||
{SKEWED, sizeof(SKEWED), build_skewed},
|
||||
{MIXED, sizeof(MIXED), build_mixed}};
|
||||
|
||||
TEST(OtaInflate, EveryReadSizeGivesTheSameOutput) {
|
||||
// 1 byte reads never leave enough input for the fast loop, 11 to 13 straddle
|
||||
// its threshold, and the larger ones run it across read and window boundaries
|
||||
auto s = std::make_unique<Session>();
|
||||
for (const auto &stream : STREAMS) {
|
||||
const auto plain = stream.plain();
|
||||
for (bool flush_in_read : {false, true}) {
|
||||
for (size_t chunk : {size_t{1}, size_t{11}, size_t{12}, size_t{13}, size_t{300}, size_t{1040}, stream.len}) {
|
||||
ASSERT_EQ(inflate_all(*s, stream.data, stream.len, chunk, flush_in_read), OTA_INFLATE_DONE)
|
||||
<< "stream of " << stream.len << " B, read size " << chunk << ", flush in read " << flush_in_read;
|
||||
EXPECT_EQ(s->out, plain) << "stream of " << stream.len << " B, read size " << chunk << ", flush in read "
|
||||
<< flush_in_read;
|
||||
EXPECT_EQ(s->in_pos, stream.len);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(OtaInflate, TruncatedStreamFails) {
|
||||
auto s = std::make_unique<Session>();
|
||||
for (size_t cut : {size_t{1}, size_t{100}, size_t{1000}, sizeof(DEFLATED) - 1}) {
|
||||
@@ -305,12 +909,14 @@ TEST(OtaInflate, TruncatedStoredBlockFails) {
|
||||
TEST(OtaInflate, CorruptStreamsNeverEscapeTheWindow) {
|
||||
// Flipped bytes and garbage; the sanitizers check the decoder stays in bounds
|
||||
auto s = std::make_unique<Session>();
|
||||
std::vector<uint8_t> bad(DEFLATED, DEFLATED + sizeof(DEFLATED));
|
||||
// A coarse, non-aligned stride: neighbouring offsets hit the same paths
|
||||
for (size_t i = 0; i < bad.size(); i += 29) {
|
||||
bad[i] ^= 0x5a;
|
||||
inflate_all(*s, bad.data(), bad.size(), 1040);
|
||||
bad[i] ^= 0x5a;
|
||||
for (const auto &stream : STREAMS) {
|
||||
std::vector<uint8_t> bad(stream.data, stream.data + stream.len);
|
||||
// A coarse, non-aligned stride: neighbouring offsets hit the same paths
|
||||
for (size_t i = 0; i < bad.size(); i += 29) {
|
||||
bad[i] ^= 0x5a;
|
||||
inflate_all(*s, bad.data(), bad.size(), 1040);
|
||||
bad[i] ^= 0x5a;
|
||||
}
|
||||
}
|
||||
uint32_t x = 99;
|
||||
std::vector<uint8_t> garbage(2000);
|
||||
|
||||
@@ -8,6 +8,8 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
manifest.dependencies = manifest.dependencies + ["climate_ir", "climate"]
|
||||
|
||||
async def to_code_testing(config: ConfigType) -> None:
|
||||
# Fahrenheit support is define-gated; emit it so the tests cover that path.
|
||||
cg.add_define("USE_GREE_FAHRENHEIT")
|
||||
# The YAW1F follow-up message is compiled in only for configs that use that model
|
||||
cg.add_define("USE_GREE_YAW1F")
|
||||
|
||||
|
||||
@@ -4,6 +4,13 @@ climate:
|
||||
id: my_gree_ac
|
||||
model: YAN
|
||||
transmitter_id: xmitr
|
||||
# YX1FF, the model use_fahrenheit was verified on; it has no mode switches, so they stay on YAN
|
||||
- platform: gree
|
||||
name: GREE Fahrenheit
|
||||
id: my_gree_ac_fahrenheit
|
||||
model: YX1FF
|
||||
use_fahrenheit: true
|
||||
transmitter_id: xmitr
|
||||
|
||||
switch:
|
||||
- platform: gree
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include "esphome/components/gree/gree.h"
|
||||
#include "esphome/components/remote_base/remote_base.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
|
||||
namespace esphome::gree::testing {
|
||||
|
||||
@@ -24,6 +26,75 @@ TEST(GreeClimateTest, HeatCoolOverrideAdvertisedWithoutHeat) {
|
||||
EXPECT_FALSE(traits.supports_mode(climate::CLIMATE_MODE_HEAT));
|
||||
}
|
||||
|
||||
#ifdef USE_GREE_FAHRENHEIT
|
||||
// Expectations from frames captured off a real YX1FF remote and the panel of a Blueridge mini-split.
|
||||
TEST(GreeClimateTest, FahrenheitHalfStepsMatchCapturedRemoteFrames) {
|
||||
struct Expectation {
|
||||
float celsius;
|
||||
uint8_t temp_field;
|
||||
bool extra_degree;
|
||||
};
|
||||
const Expectation expectations[] = {
|
||||
{18.33f, 2, true}, // 65 F -- captured remote frame b1=02 b3=5C
|
||||
{18.89f, 3, false}, // 66 F -- captured remote frame b1=03 b3=58
|
||||
{21.67f, 6, false}, // 71 F
|
||||
{22.22f, 6, true}, // 72 F
|
||||
{25.0f, 9, false}, // 77 F
|
||||
{25.56f, 10, false}, // 78 F
|
||||
};
|
||||
for (const auto &expectation : expectations) {
|
||||
const uint8_t half_steps = fahrenheit_half_steps(expectation.celsius);
|
||||
EXPECT_EQ(expectation.temp_field, half_steps >> 1) << "celsius " << expectation.celsius;
|
||||
EXPECT_EQ(expectation.extra_degree, (half_steps & 1) != 0) << "celsius " << expectation.celsius;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GreeClimateTest, FahrenheitHalfStepsCoverEveryWholeDegreeInRange) {
|
||||
// 61..86 F is the range a YX1FF remote offers.
|
||||
for (int fahrenheit = 61; fahrenheit <= 86; fahrenheit++) {
|
||||
const uint8_t half_steps = fahrenheit_half_steps(fahrenheit_to_celsius(static_cast<float>(fahrenheit)));
|
||||
const float encoded = static_cast<float>(GREE_TEMP_MIN) + 0.5f * static_cast<float>(half_steps);
|
||||
EXPECT_EQ(fahrenheit, static_cast<int>(celsius_to_fahrenheit(encoded))) << "fahrenheit " << fahrenheit;
|
||||
}
|
||||
}
|
||||
|
||||
TEST(GreeClimateTest, FahrenheitHalfStepsHandleNonFiniteTarget) {
|
||||
// transmit_state() can run from setup() via restore_state_(), when the target may still be NaN.
|
||||
EXPECT_EQ(0, fahrenheit_half_steps(NAN));
|
||||
}
|
||||
#endif // USE_GREE_FAHRENHEIT
|
||||
|
||||
// Byte 7 is zeroed in these captured YX1FF frames because frame_checksum() computes it.
|
||||
TEST(GreeClimateTest, FrameChecksumMatchesCapturedFrames) {
|
||||
const uint8_t frame_65f[GREE_STATE_FRAME_SIZE] = {0x09, 0x02, 0x60, 0x5C, 0x00, 0x20, 0x00, 0x00};
|
||||
EXPECT_EQ(0x30, frame_checksum(frame_65f));
|
||||
|
||||
const uint8_t frame_78f[GREE_STATE_FRAME_SIZE] = {0x09, 0x0A, 0x60, 0x58, 0x00, 0x20, 0x00, 0x00};
|
||||
EXPECT_EQ(0x70, frame_checksum(frame_78f));
|
||||
}
|
||||
|
||||
// The removed yan/yx1ff checksum `(b0 << 4) + (b1 << 4) + 0xC0` equals frame_checksum() for every frame they emit.
|
||||
TEST(GreeClimateTest, FrameChecksumMatchesTheRemovedFoldedFormula) {
|
||||
int mismatches = 0;
|
||||
for (int byte0 = 0; byte0 < 256; byte0++) {
|
||||
for (int byte1 = 0; byte1 < 256; byte1++) {
|
||||
// byte 2 low nibble is always 0
|
||||
static constexpr uint8_t BYTE2_VALUES[] = {0x00, 0x20, 0x60, 0x70};
|
||||
for (const uint8_t byte2 : BYTE2_VALUES) {
|
||||
for (uint8_t byte4 = 0; byte4 <= GREE_VDIR_DOWN; byte4++) {
|
||||
const uint8_t frame[GREE_STATE_FRAME_SIZE] = {
|
||||
static_cast<uint8_t>(byte0), static_cast<uint8_t>(byte1), byte2, 0x50, byte4, 0x20, 0x00, 0x00};
|
||||
const uint8_t folded = static_cast<uint8_t>((byte0 << 4) + (byte1 << 4) + 0xC0);
|
||||
if (frame_checksum(frame) != folded) {
|
||||
mismatches++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
EXPECT_EQ(0, mismatches);
|
||||
}
|
||||
|
||||
class CapturingGreeTransmitter final : public remote_base::RemoteTransmitterBase {
|
||||
public:
|
||||
CapturingGreeTransmitter() : RemoteTransmitterBase(nullptr) {}
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
|
||||
#include "common.h"
|
||||
#include "esphome/components/modbus/modbus.h"
|
||||
#include "esphome/components/modbus_client/modbus_client.h"
|
||||
#include "esphome/core/base_automation.h"
|
||||
#include "esphome/core/hal.h"
|
||||
|
||||
namespace esphome::modbus::testing {
|
||||
@@ -2545,4 +2547,116 @@ TEST(ModbusClientHubAddress, BroadcastReportsAddressZero) {
|
||||
hub.send_next_for_test();
|
||||
EXPECT_EQ(device.sent_, BROADCAST_ADDRESS);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// A handler on one modbus_client trigger that records the address argument of every firing.
|
||||
template<typename... Ts> struct AddressRecorder {
|
||||
explicit AddressRecorder(Trigger<uint8_t, Ts...> *trigger) : automation(trigger) {
|
||||
this->automation.add_action(&this->action);
|
||||
}
|
||||
std::vector<uint8_t> addresses;
|
||||
LambdaAction<uint8_t, Ts...> action{[this](uint8_t address, Ts...) { this->addresses.push_back(address); }};
|
||||
Automation<uint8_t, Ts...> automation;
|
||||
};
|
||||
} // namespace
|
||||
|
||||
// One action, overlapping sends with a moving address: each handler reports its own request's address, not the
|
||||
// action's latest stamp. The requests dropped in between cover on_not_sent from the hub.
|
||||
TEST(ModbusClientHubRequestAddress, ActionHandlersReportTheirOwnRequestsAddress) {
|
||||
NullUART uart;
|
||||
NoResponseProbeHub hub;
|
||||
hub.set_uart_parent(&uart);
|
||||
hub.setup();
|
||||
modbus_client::ModbusClientSendAction<> action;
|
||||
action.set_parent(&hub);
|
||||
action.set_pdu([]() { return read_pdu(); });
|
||||
static uint8_t target;
|
||||
action.set_target_address([]() -> uint8_t { return target; });
|
||||
AddressRecorder<std::span<const uint8_t>> sent(action.get_sent_trigger());
|
||||
AddressRecorder<std::span<const uint8_t>, std::span<const uint8_t>> answered(action.get_response_trigger());
|
||||
AddressRecorder<std::span<const uint8_t>, ExceptionCode> failed(action.get_error_trigger());
|
||||
AddressRecorder<std::span<const uint8_t>> unanswered(action.get_no_response_trigger());
|
||||
AddressRecorder<std::span<const uint8_t>> dropped(action.get_not_sent_trigger());
|
||||
const uint8_t exception_response[] = {0x83, 0x02};
|
||||
auto drop = [&hub](uint8_t address) {
|
||||
hub.clear_tx_queue_for_address(address);
|
||||
hub.sweep_for_test();
|
||||
};
|
||||
|
||||
for (target = 0x02; target <= 0x08; target++)
|
||||
action.play_complex(); // the stamp ends at 0x08, which no handler below may report
|
||||
hub.send_next_for_test();
|
||||
drop(0x05);
|
||||
hub.receive_frame_for_test(0x02, OK_RESPONSE);
|
||||
hub.send_next_for_test();
|
||||
drop(0x06);
|
||||
hub.receive_frame_for_test(0x03, exception_response);
|
||||
hub.send_next_for_test();
|
||||
drop(0x07);
|
||||
hub.timeout_waiting();
|
||||
|
||||
EXPECT_EQ(sent.addresses, (std::vector<uint8_t>{0x02, 0x03, 0x04}));
|
||||
EXPECT_EQ(answered.addresses, std::vector<uint8_t>{0x02});
|
||||
EXPECT_EQ(failed.addresses, std::vector<uint8_t>{0x03});
|
||||
EXPECT_EQ(unanswered.addresses, std::vector<uint8_t>{0x04});
|
||||
EXPECT_EQ(dropped.addresses, (std::vector<uint8_t>{0x05, 0x06, 0x07}));
|
||||
}
|
||||
|
||||
// A full queue refuses the send at the door: on_not_sent reports the address of the refused request, also
|
||||
// while the action's address moves from one play to the next.
|
||||
TEST(ModbusClientHubRequestAddress, FullQueueReportsTheRefusedRequestsAddress) {
|
||||
NullUART uart;
|
||||
NoResponseProbeHub hub;
|
||||
hub.set_uart_parent(&uart);
|
||||
hub.setup();
|
||||
modbus_client::ModbusClientSendAction<> action;
|
||||
action.set_parent(&hub);
|
||||
action.set_pdu([]() { return read_pdu(); });
|
||||
static uint8_t target;
|
||||
action.set_target_address([]() -> uint8_t { return target; });
|
||||
AddressRecorder<std::span<const uint8_t>> dropped(action.get_not_sent_trigger());
|
||||
|
||||
for (target = 1; target <= MODBUS_TX_BUFFER_SIZE; target++)
|
||||
action.play_complex(); // one entry per address fills the queue
|
||||
ASSERT_TRUE(dropped.addresses.empty());
|
||||
target = 200;
|
||||
action.play_complex();
|
||||
target = 201;
|
||||
action.play_complex();
|
||||
|
||||
EXPECT_EQ(dropped.addresses, (std::vector<uint8_t>{200, 201}));
|
||||
}
|
||||
|
||||
// The on_no_response handler re-plays the action at 0x03 and the hub refuses it (empty PDU), which reports
|
||||
// 0x03 to on_not_sent. The retry lambda that runs after the handler still gets 0x02, the timed-out request.
|
||||
TEST(ModbusClientHubRequestAddress, RetryGetsTheTimedOutRequestsAddress) {
|
||||
NullUART uart;
|
||||
NoResponseProbeHub hub;
|
||||
hub.set_uart_parent(&uart);
|
||||
hub.setup();
|
||||
modbus_client::ModbusClientSendAction<> action;
|
||||
action.set_parent(&hub);
|
||||
action.set_pdu([]() { return read_pdu(); });
|
||||
action.set_target_address([]() -> uint8_t { return 0x02; });
|
||||
static uint8_t retry_address;
|
||||
action.set_retry([](uint8_t address, std::span<const uint8_t>) {
|
||||
retry_address = address;
|
||||
return false;
|
||||
});
|
||||
LambdaAction<uint8_t, std::span<const uint8_t>> replay{[&action](uint8_t, std::span<const uint8_t>) {
|
||||
action.set_target_address([]() -> uint8_t { return 0x03; });
|
||||
action.set_pdu([]() { return helpers::PduBuffer{}; });
|
||||
action.play_complex();
|
||||
}};
|
||||
Automation<uint8_t, std::span<const uint8_t>> on_no_response(action.get_no_response_trigger());
|
||||
on_no_response.add_action(&replay);
|
||||
AddressRecorder<std::span<const uint8_t>> dropped(action.get_not_sent_trigger());
|
||||
|
||||
action.play_complex();
|
||||
hub.send_next_for_test();
|
||||
hub.timeout_waiting();
|
||||
|
||||
EXPECT_EQ(dropped.addresses, std::vector<uint8_t>{0x03});
|
||||
EXPECT_EQ(retry_address, 0x02);
|
||||
}
|
||||
} // namespace esphome::modbus::testing
|
||||
@@ -1,6 +1,7 @@
|
||||
# The modbus_client actions are self-contained hub devices: each takes the hub (auto-resolved when there
|
||||
# is a single modbus client hub) and a templatable device address; no component block is needed. The
|
||||
# address is not passed back to reply handlers - recompute the configured expression if needed.
|
||||
# shared handlers (on_sent, on_error, on_no_response, on_not_sent) and send's on_response get `address`
|
||||
# first: the address of their own request, even when the template has moved on.
|
||||
# The hub does not bound retries, so a retry lambda must (here: a counter capped at 3), or a dead
|
||||
# device is retried forever. Reset the counter before the send or on a terminal outcome (on_response)
|
||||
# so the cap is per transaction, not per device lifetime. Never reset in on_sent: it fires again on
|
||||
@@ -47,7 +48,7 @@ button:
|
||||
# on_no_response lambda form: return true to retry. `request` is the timed-out PDU.
|
||||
on_no_response: !lambda "return !request.empty() && request[0] == 0x03 && id(read_retries)++ < 3;"
|
||||
# Per-send inline reply handlers (fire-and-continue): they run when this send's outcome is known;
|
||||
# the targeted address is not passed back - recompute the configured expression if needed.
|
||||
# `address` is the address this send went to.
|
||||
# A pdu lambda can hand-assemble bytes or return a modbus::helpers::create_*_pdu() builder result.
|
||||
- modbus_client.send:
|
||||
address: 0x01
|
||||
@@ -58,23 +59,23 @@ button:
|
||||
pdu: !lambda "return {0x03, 0x00, 0x10, 0x00, 0x01};"
|
||||
on_sent:
|
||||
then:
|
||||
- lambda: 'ESP_LOGI("modbus_client.test", "sent fc 0x%X", request.empty() ? 0 : request[0]);'
|
||||
- lambda: 'ESP_LOGI("modbus_client.test", "sent fc 0x%X to %u", request.empty() ? 0 : request[0], address);'
|
||||
on_response:
|
||||
then:
|
||||
- lambda: |-
|
||||
id(combined_retries) = 0;
|
||||
ESP_LOGI("modbus_client.test", "got %d bytes", (int) response.size());
|
||||
ESP_LOGI("modbus_client.test", "device %u: got %d bytes", address, (int) response.size());
|
||||
on_error:
|
||||
then:
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "fc 0x%X exception %d", request.empty() ? 0 : request[0], (int) exception_code);'
|
||||
# on_no_response combined form: run actions on timeout AND decide the retry via nested retry:.
|
||||
on_no_response:
|
||||
then:
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "no reply for fc 0x%X", request.empty() ? 0 : request[0]);'
|
||||
retry: !lambda "return !request.empty() && request[0] == 0x03 && id(combined_retries)++ < 3;"
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "no reply from %u for fc 0x%X", address, request.empty() ? 0 : request[0]);'
|
||||
retry: !lambda "return address != 0 && !request.empty() && request[0] == 0x03 && id(combined_retries)++ < 3;"
|
||||
on_not_sent:
|
||||
then:
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "not sent fc 0x%X", request.empty() ? 0 : request[0]);'
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "not sent to %u: fc 0x%X", address, request.empty() ? 0 : request[0]);'
|
||||
- platform: template
|
||||
name: "Typed Actions"
|
||||
on_press:
|
||||
@@ -100,7 +101,7 @@ button:
|
||||
- lambda: 'ESP_LOGI("modbus_client.test", "first=%u n=%u", values[0], (unsigned) values.size());'
|
||||
on_no_response:
|
||||
then:
|
||||
- logger.log: "typed read timeout"
|
||||
- lambda: 'ESP_LOGW("modbus_client.test", "typed read timeout from %u", address);'
|
||||
- modbus_client.read_input_registers:
|
||||
continuous: !lambda "return false;"
|
||||
address: 0x01
|
||||
|
||||
@@ -42,6 +42,7 @@ image:
|
||||
format: PNG
|
||||
type: RGB
|
||||
transparency: chroma_key
|
||||
double_buffer: true
|
||||
- platform: online_image
|
||||
id: online_rgb111_image
|
||||
url: http://www.libpng.org/pub/png/img_png/pnglogo-blk-tiny.png
|
||||
|
||||
@@ -23,6 +23,7 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
"pioneer_wyt",
|
||||
"drayton",
|
||||
"panasonic",
|
||||
"cardin_s438",
|
||||
):
|
||||
request_protocol(protocol)
|
||||
# receiver_dumpers_test.cpp registers dumpers by hand
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
#include <array>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "esphome/components/remote_base/cardin_s438_protocol.h"
|
||||
|
||||
namespace esphome::remote_base::testing {
|
||||
namespace {
|
||||
|
||||
constexpr int32_t SYNC_MARK_US = 6400;
|
||||
constexpr int32_t SHORT_US = 750;
|
||||
constexpr int32_t LONG_US = 1460;
|
||||
constexpr int32_t FRAME_GAP_US = 63650;
|
||||
|
||||
constexpr size_t FRAME_TIMINGS_SIZE = 1 + 5 * 8 * 2 + 1;
|
||||
|
||||
// Captured frame: FA C3 EC A9 02, seed 0x65.
|
||||
constexpr std::array<uint8_t, 5> CAPTURED_FRAME = {0xFA, 0xC3, 0xEC, 0xA9, 0x02};
|
||||
|
||||
// Reference frame for code 0x11FF, channel A, seed 0x65.
|
||||
constexpr std::array<uint8_t, 5> REFERENCE_FRAME = {0xC9, 0x05, 0xEC, 0xBA, 0x01};
|
||||
|
||||
RawTimings encode(const CardinS438Data &data) {
|
||||
RemoteTransmitData tx;
|
||||
CardinS438Protocol().encode(&tx, data);
|
||||
return tx.get_data();
|
||||
}
|
||||
|
||||
optional<CardinS438Data> decode(const RawTimings &timings) {
|
||||
return CardinS438Protocol().decode(RemoteReceiveData(timings, 25, TOLERANCE_MODE_PERCENTAGE));
|
||||
}
|
||||
|
||||
RawTimings frame_timings(const std::array<uint8_t, 5> &wire) {
|
||||
RawTimings timings{SYNC_MARK_US};
|
||||
|
||||
for (uint8_t byte : wire) {
|
||||
for (uint8_t bit = 0; bit < 8; ++bit) {
|
||||
if ((byte >> bit) & 1U) {
|
||||
timings.insert(timings.end(), {-SHORT_US, LONG_US});
|
||||
} else {
|
||||
timings.insert(timings.end(), {-LONG_US, SHORT_US});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
timings.push_back(-FRAME_GAP_US);
|
||||
return timings;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(CardinS438ProtocolTest, DecodesCapturedFrame) {
|
||||
auto decoded = decode(frame_timings(CAPTURED_FRAME));
|
||||
|
||||
ASSERT_TRUE(decoded.has_value());
|
||||
ASSERT_EQ(decoded->seed, 0x65);
|
||||
EXPECT_EQ(decoded->code, 0x2E12);
|
||||
EXPECT_EQ(decoded->channel, 0x02);
|
||||
}
|
||||
|
||||
TEST(CardinS438ProtocolTest, DecodesReferenceFrame) {
|
||||
auto decoded = decode(frame_timings(REFERENCE_FRAME));
|
||||
|
||||
ASSERT_TRUE(decoded.has_value());
|
||||
EXPECT_EQ(decoded->code, 0x11FF);
|
||||
EXPECT_EQ(decoded->channel, 0x02);
|
||||
EXPECT_EQ(decoded->seed, 0x65);
|
||||
}
|
||||
|
||||
TEST(CardinS438ProtocolTest, RoundTrips) {
|
||||
for (uint16_t code : {0x0000, 0x0001, 0x11FF, 0x2AAA, 0x3FFF}) {
|
||||
for (uint8_t channel = 0; channel < 4; ++channel) {
|
||||
CardinS438Data sent{};
|
||||
sent.code = code;
|
||||
sent.channel = channel;
|
||||
sent.repeats = 1;
|
||||
|
||||
const RawTimings timings = encode(sent);
|
||||
ASSERT_EQ(timings.size(), FRAME_TIMINGS_SIZE);
|
||||
|
||||
auto decoded = decode(timings);
|
||||
ASSERT_TRUE(decoded.has_value());
|
||||
EXPECT_TRUE(*decoded == sent);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CardinS438ProtocolTest, RejectsBadChecksum) {
|
||||
auto wire = CAPTURED_FRAME;
|
||||
wire[3] ^= 1;
|
||||
|
||||
EXPECT_FALSE(decode(frame_timings(wire)).has_value());
|
||||
}
|
||||
|
||||
TEST(CardinS438ProtocolTest, SkipsLeadingNoise) {
|
||||
RawTimings timings{300, -1200, 150, -90};
|
||||
const RawTimings frame = frame_timings(CAPTURED_FRAME);
|
||||
timings.insert(timings.end(), frame.begin(), frame.end());
|
||||
|
||||
auto decoded = decode(timings);
|
||||
ASSERT_TRUE(decoded.has_value());
|
||||
EXPECT_EQ(decoded->code, 0x2E12);
|
||||
}
|
||||
|
||||
TEST(CardinS438ProtocolTest, FindsValidFrameAfterBadChecksum) {
|
||||
auto bad_wire = CAPTURED_FRAME;
|
||||
bad_wire[3] ^= 1;
|
||||
|
||||
RawTimings timings = frame_timings(bad_wire);
|
||||
const RawTimings good = frame_timings(CAPTURED_FRAME);
|
||||
timings.insert(timings.end(), good.begin(), good.end());
|
||||
|
||||
EXPECT_TRUE(decode(timings).has_value());
|
||||
}
|
||||
|
||||
TEST(CardinS438ProtocolTest, RejectsBadStart) {
|
||||
RawTimings timings = frame_timings(CAPTURED_FRAME);
|
||||
timings[0] = 2000;
|
||||
|
||||
EXPECT_FALSE(decode(timings).has_value());
|
||||
}
|
||||
|
||||
TEST(CardinS438ProtocolTest, RejectsTruncatedFrame) {
|
||||
RawTimings timings = frame_timings(CAPTURED_FRAME);
|
||||
timings.resize(timings.size() - 6);
|
||||
|
||||
EXPECT_FALSE(decode(timings).has_value());
|
||||
}
|
||||
|
||||
TEST(CardinS438ProtocolTest, RejectsBadBitTiming) {
|
||||
RawTimings timings = frame_timings(CAPTURED_FRAME);
|
||||
timings[1] = -2000;
|
||||
|
||||
EXPECT_FALSE(decode(timings).has_value());
|
||||
}
|
||||
|
||||
TEST(CardinS438ProtocolTest, DecodesSeedCarryBoundaries) {
|
||||
struct SeedVector {
|
||||
uint8_t seed;
|
||||
std::array<uint8_t, 5> wire;
|
||||
};
|
||||
|
||||
// All vectors encode code=0x11FF, channel A.
|
||||
// 0x78: marker sum is 0xFF; second carry occurs.
|
||||
// 0x79: marker and second carry both occur.
|
||||
// 0xFF: marker carry wraps the key to zero.
|
||||
constexpr SeedVector frames[] = {
|
||||
{0x78, {0xDC, 0x18, 0xFF, 0xF3, 0x01}},
|
||||
{0x79, {0xDE, 0x1A, 0x00, 0xF8, 0x00}},
|
||||
{0xFF, {0x63, 0xA0, 0x86, 0x89, 0x01}},
|
||||
};
|
||||
|
||||
for (const auto &frame : frames) {
|
||||
const auto decoded = decode(frame_timings(frame.wire));
|
||||
|
||||
ASSERT_TRUE(decoded.has_value()) << "seed=0x" << std::hex << unsigned(frame.seed);
|
||||
EXPECT_EQ(decoded->code, 0x11FF);
|
||||
EXPECT_EQ(decoded->channel, 0x02);
|
||||
EXPECT_EQ(decoded->seed, frame.seed);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(CardinS438ProtocolTest, EncodesRequestedFrameCount) {
|
||||
for (uint8_t repeats : {1, 3, 5}) {
|
||||
CardinS438Data sent{};
|
||||
sent.code = 0x11FF;
|
||||
sent.channel = 0x02;
|
||||
sent.repeats = repeats;
|
||||
|
||||
const RawTimings timings = encode(sent);
|
||||
|
||||
ASSERT_EQ(timings.size(), FRAME_TIMINGS_SIZE * repeats);
|
||||
|
||||
// Check each independently seeded frame, without assuming seeds differ.
|
||||
for (size_t i = 0; i < repeats; ++i) {
|
||||
const auto first = timings.begin() + i * FRAME_TIMINGS_SIZE;
|
||||
const RawTimings frame(first, first + FRAME_TIMINGS_SIZE);
|
||||
const auto decoded = decode(frame);
|
||||
|
||||
ASSERT_TRUE(decoded.has_value()) << "frame=" << i;
|
||||
EXPECT_EQ(decoded->code, sent.code);
|
||||
EXPECT_EQ(decoded->channel, sent.channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::remote_base::testing
|
||||
@@ -207,3 +207,8 @@ on_ira211:
|
||||
- logger.log:
|
||||
format: "on_ira211: %u %.1f"
|
||||
args: ["static_cast<uint8_t>(x.command)", "x.get_temperature()"]
|
||||
on_cardin_s438:
|
||||
then:
|
||||
- logger.log:
|
||||
format: "on_cardin_s438: %u %u"
|
||||
args: ["x.code", "x.channel"]
|
||||
@@ -564,6 +564,12 @@ button:
|
||||
on_press:
|
||||
- remote_transmitter.transmit_pd_pioneer:
|
||||
code: [0x23, 0xCB, 0x26, 0x01, 0x00, 0x24, 0x03, 0x0B, 0x05, 0x00, 0x00, 0x00, 0x80]
|
||||
- platform: template
|
||||
name: Cardin S438
|
||||
on_press:
|
||||
remote_transmitter.transmit_cardin_s438:
|
||||
code: "01001001110100"
|
||||
channel: B
|
||||
- platform: template
|
||||
name: Repeat on every protocol
|
||||
on_press:
|
||||
@@ -613,6 +619,12 @@ button:
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_cardin_s438:
|
||||
code: "01001001110100"
|
||||
channel: B
|
||||
repeat:
|
||||
times: 2
|
||||
wait_time: 10ms
|
||||
- remote_transmitter.transmit_coolix:
|
||||
first: 0xB2BF40
|
||||
repeat:
|
||||
@@ -895,6 +907,10 @@ button:
|
||||
device: !lambda return 0x25;
|
||||
address: !lambda return 0x01;
|
||||
command: 0x1C
|
||||
- remote_transmitter.transmit_cardin_s438:
|
||||
code: !lambda return 0x1274;
|
||||
channel: !lambda return 1;
|
||||
command_repeats: !lambda return 3;
|
||||
- remote_transmitter.transmit_coolix:
|
||||
first: !lambda return 0xB2BF40;
|
||||
second: !lambda return 0xB2BF40;
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components.lvgl import LVGL_VERSION
|
||||
from esphome.components.runtime_image import enable_format
|
||||
from esphome.types import ConfigType
|
||||
from tests.testing_helpers import ComponentManifestOverride
|
||||
@@ -12,5 +14,11 @@ def override_manifest(manifest: ComponentManifestOverride) -> None:
|
||||
enable_format("JPEG")
|
||||
enable_format("PNG")
|
||||
enable_format("QOI")
|
||||
# The LVGL image descriptor is tested too, so build with the LVGL library
|
||||
cg.add_library("lvgl/lvgl", LVGL_VERSION)
|
||||
cg.add_build_flag("-DLV_CONF_SKIP=1")
|
||||
cg.add_define("USE_LVGL")
|
||||
cg.add_define("USE_LVGL_IMAGE")
|
||||
|
||||
manifest.to_code = to_code_testing
|
||||
manifest.dependencies = manifest.dependencies + ["lvgl"]
|
||||
@@ -0,0 +1,146 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/components/runtime_image/runtime_image.h"
|
||||
|
||||
namespace esphome::runtime_image::testing {
|
||||
|
||||
// 3x2 24bpp BMP (rows padded to 4 bytes)
|
||||
static const uint8_t BMP_3X2[] = {
|
||||
0x42, 0x4D, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x22, 0x11, 0x77, 0x88, 0x99, 0xEF, 0xCD, 0xAB, 0x00,
|
||||
0x00, 0x00, 0x20, 0x10, 0xE0, 0x40, 0xC0, 0x30, 0xA0, 0x60, 0x50, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// 2x1 24bpp BMP (row padded to 8 bytes)
|
||||
static const uint8_t BMP_2X1[] = {
|
||||
0x42, 0x4D, 0x3E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x01, 0x06, 0x05, 0x04, 0x00, 0x00,
|
||||
};
|
||||
|
||||
template<size_t N> static void load(RuntimeImage &img, const uint8_t (&bmp)[N]) {
|
||||
std::vector<uint8_t> data(bmp, bmp + N);
|
||||
ASSERT_TRUE(img.begin_decode(data.size()));
|
||||
ASSERT_EQ(img.feed_data(data.data(), data.size()), static_cast<int>(data.size()));
|
||||
ASSERT_TRUE(img.end_decode());
|
||||
}
|
||||
|
||||
class DoubleBufferedImage : public RuntimeImage {
|
||||
public:
|
||||
DoubleBufferedImage() : RuntimeImage(BMP, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE) {
|
||||
this->set_double_buffer(true);
|
||||
}
|
||||
};
|
||||
|
||||
// The image on show is not decoded over: the new one appears only when it is complete
|
||||
TEST(RuntimeImageDoubleBuffer, ImageOnShowStaysUntilTheNextIsDecoded) {
|
||||
DoubleBufferedImage img;
|
||||
load(img, BMP_3X2);
|
||||
const uint8_t *first = img.get_data_start();
|
||||
ASSERT_NE(first, nullptr);
|
||||
const std::vector<uint8_t> shown(first, first + 3 * 2 * 3);
|
||||
|
||||
std::vector<uint8_t> data(BMP_2X1, BMP_2X1 + sizeof(BMP_2X1));
|
||||
ASSERT_TRUE(img.begin_decode(data.size()));
|
||||
ASSERT_EQ(img.feed_data(data.data(), data.size()), static_cast<int>(data.size()));
|
||||
EXPECT_EQ(img.get_data_start(), first);
|
||||
EXPECT_EQ(img.get_width(), 3);
|
||||
EXPECT_EQ(img.get_height(), 2);
|
||||
EXPECT_EQ(std::vector<uint8_t>(first, first + shown.size()), shown);
|
||||
|
||||
ASSERT_TRUE(img.end_decode());
|
||||
EXPECT_NE(img.get_data_start(), first);
|
||||
EXPECT_EQ(img.get_width(), 2);
|
||||
EXPECT_EQ(img.get_height(), 1);
|
||||
}
|
||||
|
||||
// Images of one size take turns in the same two buffers
|
||||
TEST(RuntimeImageDoubleBuffer, SameSizeImagesReuseBothBuffers) {
|
||||
DoubleBufferedImage img;
|
||||
load(img, BMP_3X2);
|
||||
const uint8_t *first = img.get_data_start();
|
||||
load(img, BMP_3X2);
|
||||
const uint8_t *second = img.get_data_start();
|
||||
EXPECT_NE(second, first);
|
||||
load(img, BMP_3X2);
|
||||
EXPECT_EQ(img.get_data_start(), first);
|
||||
load(img, BMP_3X2);
|
||||
EXPECT_EQ(img.get_data_start(), second);
|
||||
}
|
||||
|
||||
TEST(RuntimeImageDoubleBuffer, AbortedDecodeLeavesTheImageOnShow) {
|
||||
DoubleBufferedImage img;
|
||||
load(img, BMP_3X2);
|
||||
const uint8_t *first = img.get_data_start();
|
||||
|
||||
std::vector<uint8_t> data(BMP_2X1, BMP_2X1 + sizeof(BMP_2X1));
|
||||
ASSERT_TRUE(img.begin_decode(data.size()));
|
||||
img.feed_data(data.data(), data.size() / 2 + 8);
|
||||
img.abort_decode();
|
||||
EXPECT_FALSE(img.is_decoding());
|
||||
EXPECT_EQ(img.get_data_start(), first);
|
||||
EXPECT_EQ(img.get_width(), 3);
|
||||
EXPECT_TRUE(img.is_loaded());
|
||||
|
||||
load(img, BMP_2X1);
|
||||
EXPECT_EQ(img.get_width(), 2);
|
||||
}
|
||||
|
||||
// An aborted first decode leaves nothing on show
|
||||
TEST(RuntimeImageDoubleBuffer, AbortedFirstDecodeIsNotLoaded) {
|
||||
DoubleBufferedImage img;
|
||||
std::vector<uint8_t> data(BMP_3X2, BMP_3X2 + sizeof(BMP_3X2));
|
||||
ASSERT_TRUE(img.begin_decode(data.size()));
|
||||
img.feed_data(data.data(), data.size() / 2 + 8);
|
||||
img.abort_decode();
|
||||
EXPECT_EQ(img.get_data_start(), nullptr);
|
||||
EXPECT_FALSE(img.is_loaded());
|
||||
}
|
||||
|
||||
// Without double buffering an aborted decode has drawn over the image, so it goes
|
||||
TEST(RuntimeImageDoubleBuffer, AbortedDecodeWithOneBufferReleasesTheImage) {
|
||||
RuntimeImage img(BMP, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE);
|
||||
load(img, BMP_3X2);
|
||||
std::vector<uint8_t> data(BMP_3X2, BMP_3X2 + sizeof(BMP_3X2));
|
||||
ASSERT_TRUE(img.begin_decode(data.size()));
|
||||
img.feed_data(data.data(), data.size() / 2 + 8);
|
||||
img.abort_decode();
|
||||
EXPECT_EQ(img.get_data_start(), nullptr);
|
||||
EXPECT_FALSE(img.is_loaded());
|
||||
}
|
||||
|
||||
// Both buffers are freed; LeakSanitizer reports one that is not
|
||||
TEST(RuntimeImageDoubleBuffer, ReleaseFreesBothBuffers) {
|
||||
DoubleBufferedImage img;
|
||||
load(img, BMP_3X2);
|
||||
load(img, BMP_3X2);
|
||||
img.release();
|
||||
EXPECT_EQ(img.get_data_start(), nullptr);
|
||||
EXPECT_EQ(img.get_width(), 0);
|
||||
EXPECT_FALSE(img.is_loaded());
|
||||
}
|
||||
|
||||
TEST(RuntimeImageDoubleBuffer, ExternalBufferIsRefused) {
|
||||
DoubleBufferedImage img;
|
||||
std::vector<uint8_t> buffer(img.get_buffer_size(3, 2));
|
||||
EXPECT_FALSE(img.set_external_buffer(buffer.data(), 3, 2));
|
||||
load(img, BMP_3X2);
|
||||
EXPECT_NE(img.get_data_start(), buffer.data());
|
||||
}
|
||||
|
||||
// Progressive display turns double buffering off, so an external buffer is allowed
|
||||
TEST(RuntimeImageDoubleBuffer, ExternalBufferIsAllowedWithProgressiveDisplay) {
|
||||
DoubleBufferedImage img;
|
||||
img.set_progressive_display(true);
|
||||
std::vector<uint8_t> buffer(img.get_buffer_size(3, 2));
|
||||
EXPECT_TRUE(img.set_external_buffer(buffer.data(), 3, 2));
|
||||
img.release();
|
||||
}
|
||||
|
||||
} // namespace esphome::runtime_image::testing
|
||||
@@ -0,0 +1,39 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "esphome/components/runtime_image/jpeg_decoder.h"
|
||||
#include "esphome/components/runtime_image/runtime_image.h"
|
||||
|
||||
#ifdef USE_RUNTIME_IMAGE_JPEG
|
||||
|
||||
namespace esphome::runtime_image::testing {
|
||||
|
||||
// A decoder whose JPEGDEC state is missing, as when its allocation fails. The state is handed back before the base
|
||||
// destructor runs, so the teardown stays with JpegDecoder.
|
||||
class StatelessJpegDecoder : public JpegDecoder {
|
||||
public:
|
||||
explicit StatelessJpegDecoder(RuntimeImage *image) : JpegDecoder(image), state_(this->jpeg_) {
|
||||
this->jpeg_ = nullptr;
|
||||
}
|
||||
~StatelessJpegDecoder() override { this->jpeg_ = this->state_; }
|
||||
|
||||
protected:
|
||||
JPEGDEC *state_;
|
||||
};
|
||||
|
||||
// The ~18 KB of JPEGDEC state lives in its own allocation, so it can come from PSRAM
|
||||
TEST(JpegOutOfMemory, DecoderStateIsAllocatedSeparately) { EXPECT_LT(sizeof(JpegDecoder), sizeof(JPEGDEC) / 4); }
|
||||
|
||||
TEST(JpegOutOfMemory, MissingStateFailsWithoutStartingASession) {
|
||||
RuntimeImage img(JPEG, image::IMAGE_TYPE_RGB565, image::TRANSPARENCY_OPAQUE);
|
||||
StatelessJpegDecoder decoder(&img);
|
||||
EXPECT_EQ(decoder.prepare(100), DECODE_ERROR_OUT_OF_MEMORY);
|
||||
EXPECT_FALSE(decoder.is_active());
|
||||
uint8_t data[4] = {};
|
||||
EXPECT_EQ(decoder.decode(data, sizeof(data)), DECODE_ERROR_OUT_OF_MEMORY);
|
||||
EXPECT_EQ(decoder.begin_source(nullptr), DECODE_ERROR_OUT_OF_MEMORY);
|
||||
decoder.reset();
|
||||
}
|
||||
|
||||
} // namespace esphome::runtime_image::testing
|
||||
|
||||
#endif // USE_RUNTIME_IMAGE_JPEG
|
||||
@@ -0,0 +1,217 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "esphome/components/runtime_image/runtime_image.h"
|
||||
|
||||
#ifdef USE_LVGL
|
||||
|
||||
namespace esphome::runtime_image::testing {
|
||||
|
||||
// 3x2 24bpp BMP (rows padded to 4 bytes)
|
||||
static const uint8_t BMP_24BPP[] = {
|
||||
0x42, 0x4D, 0x4E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x36, 0x00, 0x00, 0x00, 0x28, 0x00,
|
||||
0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x01, 0x00, 0x18, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0xC4, 0x0E, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x33, 0x22, 0x11, 0x77, 0x88, 0x99, 0xEF, 0xCD, 0xAB, 0x00,
|
||||
0x00, 0x00, 0x20, 0x10, 0xE0, 0x40, 0xC0, 0x30, 0xA0, 0x60, 0x50, 0x00, 0x00, 0x00,
|
||||
};
|
||||
|
||||
// A widget keeps the descriptor it was given and reads it on every draw. Once the image lets go of its pixels,
|
||||
// for example when it starts loading another image, the descriptor must still be one LVGL can draw.
|
||||
TEST(RuntimeImageLvgl, ReleasedImageLeavesADrawableDescriptor) {
|
||||
lv_init();
|
||||
RuntimeImage img(BMP, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE);
|
||||
std::vector<uint8_t> data(BMP_24BPP, BMP_24BPP + sizeof(BMP_24BPP));
|
||||
ASSERT_TRUE(img.begin_decode(data.size()));
|
||||
ASSERT_EQ(img.feed_data(data.data(), data.size()), static_cast<int>(data.size()));
|
||||
ASSERT_TRUE(img.end_decode());
|
||||
|
||||
lv_image_dsc_t *dsc = img.get_lv_image_dsc();
|
||||
lv_image_header_t header;
|
||||
ASSERT_EQ(lv_image_decoder_get_info(dsc, &header), LV_RESULT_OK);
|
||||
EXPECT_EQ(header.w, 3);
|
||||
EXPECT_EQ(header.h, 2);
|
||||
|
||||
img.release();
|
||||
EXPECT_EQ(lv_image_decoder_get_info(dsc, &header), LV_RESULT_OK);
|
||||
EXPECT_EQ(header.w, 1);
|
||||
EXPECT_EQ(header.h, 1);
|
||||
lv_deinit();
|
||||
}
|
||||
|
||||
// Before the first image is loaded, a widget gets a descriptor it can draw too
|
||||
TEST(RuntimeImageLvgl, UnloadedImageHasADrawableDescriptor) {
|
||||
lv_init();
|
||||
RuntimeImage img(BMP, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE);
|
||||
lv_image_header_t header;
|
||||
ASSERT_EQ(lv_image_decoder_get_info(img.get_lv_image_dsc(), &header), LV_RESULT_OK);
|
||||
EXPECT_EQ(header.w, 1);
|
||||
EXPECT_EQ(header.h, 1);
|
||||
lv_deinit();
|
||||
}
|
||||
|
||||
// With a placeholder, a widget shows it before the first image and while the next one loads
|
||||
TEST(RuntimeImageLvgl, PlaceholderIsShownWithoutAnImage) {
|
||||
lv_init();
|
||||
static constexpr uint8_t PLACEHOLDER_PIXELS[4 * 2 * 3] = {};
|
||||
image::Image placeholder(PLACEHOLDER_PIXELS, 4, 2, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE);
|
||||
RuntimeImage img(BMP, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE, &placeholder);
|
||||
lv_image_dsc_t *dsc = img.get_lv_image_dsc();
|
||||
EXPECT_EQ(dsc->data, PLACEHOLDER_PIXELS);
|
||||
lv_image_header_t header;
|
||||
ASSERT_EQ(lv_image_decoder_get_info(dsc, &header), LV_RESULT_OK);
|
||||
EXPECT_EQ(header.w, 4);
|
||||
EXPECT_EQ(header.h, 2);
|
||||
|
||||
std::vector<uint8_t> data(BMP_24BPP, BMP_24BPP + sizeof(BMP_24BPP));
|
||||
ASSERT_TRUE(img.begin_decode(data.size()));
|
||||
ASSERT_EQ(img.feed_data(data.data(), data.size()), static_cast<int>(data.size()));
|
||||
ASSERT_TRUE(img.end_decode());
|
||||
EXPECT_EQ(img.get_lv_image_dsc(), dsc);
|
||||
EXPECT_EQ(dsc->data, img.get_data_start());
|
||||
EXPECT_EQ(dsc->header.w, 3);
|
||||
|
||||
img.release();
|
||||
EXPECT_EQ(dsc->data, PLACEHOLDER_PIXELS);
|
||||
EXPECT_EQ(dsc->header.w, 4);
|
||||
EXPECT_EQ(dsc->header.h, 2);
|
||||
lv_deinit();
|
||||
}
|
||||
|
||||
#ifdef USE_LVGL_IMAGE
|
||||
static constexpr int32_t SCREEN_WIDTH = 16;
|
||||
static constexpr int32_t SCREEN_HEIGHT = 8;
|
||||
|
||||
static void flush_ready(lv_display_t *display, const lv_area_t *area, uint8_t *px_map) {
|
||||
lv_display_flush_ready(display);
|
||||
}
|
||||
|
||||
// A display to draw on, so ASan sees any read LVGL makes past an image's pixels
|
||||
class RuntimeImageLvglWidget : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
lv_init();
|
||||
this->display_ = lv_display_create(SCREEN_WIDTH, SCREEN_HEIGHT);
|
||||
lv_display_set_flush_cb(this->display_, flush_ready);
|
||||
lv_display_set_buffers(this->display_, this->draw_buffer_.data(), nullptr, this->draw_buffer_.size(),
|
||||
LV_DISPLAY_RENDER_MODE_FULL);
|
||||
this->widget_ = lv_image_create(lv_screen_active());
|
||||
}
|
||||
void TearDown() override { lv_deinit(); }
|
||||
|
||||
// What the lvgl component's lv_image_set_src() overload does
|
||||
void set_src_(image::Image *image) {
|
||||
::lv_image_set_src(this->widget_, image->get_lv_image_dsc());
|
||||
image->add_lvgl_widget(this->widget_);
|
||||
}
|
||||
|
||||
static void load(RuntimeImage &img) {
|
||||
std::vector<uint8_t> data(BMP_24BPP, BMP_24BPP + sizeof(BMP_24BPP));
|
||||
ASSERT_TRUE(img.begin_decode(data.size()));
|
||||
ASSERT_EQ(img.feed_data(data.data(), data.size()), static_cast<int>(data.size()));
|
||||
ASSERT_TRUE(img.end_decode());
|
||||
}
|
||||
|
||||
// Redraw the widget, as something drawn over it would make LVGL do during a load
|
||||
void draw_() {
|
||||
lv_obj_invalidate(this->widget_);
|
||||
lv_refr_now(this->display_);
|
||||
}
|
||||
|
||||
lv_display_t *display_{};
|
||||
lv_obj_t *widget_{};
|
||||
std::vector<uint8_t> draw_buffer_ = std::vector<uint8_t>(SCREEN_WIDTH * SCREEN_HEIGHT * 4);
|
||||
};
|
||||
|
||||
// The widget keeps the size it read when given the descriptor, and draws that much of whatever the descriptor holds.
|
||||
// The image gives the widget its source again whenever what it shows changes size.
|
||||
TEST_F(RuntimeImageLvglWidget, WidgetFollowsTheImageSize) {
|
||||
RuntimeImage img(BMP, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE);
|
||||
this->set_src_(&img);
|
||||
EXPECT_EQ(lv_image_get_src_width(this->widget_), 1);
|
||||
|
||||
load(img);
|
||||
EXPECT_EQ(lv_image_get_src_width(this->widget_), 3);
|
||||
EXPECT_EQ(lv_image_get_src_height(this->widget_), 2);
|
||||
this->draw_();
|
||||
|
||||
img.release();
|
||||
EXPECT_EQ(lv_image_get_src_width(this->widget_), 1);
|
||||
EXPECT_EQ(lv_image_get_src_height(this->widget_), 1);
|
||||
this->draw_();
|
||||
|
||||
load(img);
|
||||
EXPECT_EQ(lv_image_get_src_width(this->widget_), 3);
|
||||
this->draw_();
|
||||
}
|
||||
|
||||
// The placeholder is smaller than the image, and on the heap, so drawing it at the image's size trips ASan
|
||||
TEST_F(RuntimeImageLvglWidget, WidgetFollowsThePlaceholderSize) {
|
||||
std::vector<uint8_t> placeholder_pixels(2 * 1 * 3);
|
||||
image::Image placeholder(placeholder_pixels.data(), 2, 1, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE);
|
||||
RuntimeImage img(BMP, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE, &placeholder);
|
||||
load(img);
|
||||
this->set_src_(&img);
|
||||
this->draw_();
|
||||
|
||||
img.release();
|
||||
EXPECT_EQ(lv_image_get_src_width(this->widget_), 2);
|
||||
EXPECT_EQ(lv_image_get_src_height(this->widget_), 1);
|
||||
this->draw_();
|
||||
}
|
||||
|
||||
// A widget given another image is left alone, however many times it was given this one
|
||||
TEST_F(RuntimeImageLvglWidget, WidgetGivenAnotherImageIsLeftAlone) {
|
||||
static constexpr uint8_t OTHER_PIXELS[4 * 5 * 3] = {};
|
||||
image::Image other(OTHER_PIXELS, 4, 5, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE);
|
||||
RuntimeImage img(BMP, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE);
|
||||
load(img);
|
||||
this->set_src_(&img);
|
||||
this->set_src_(&img);
|
||||
this->set_src_(&other);
|
||||
|
||||
img.release();
|
||||
EXPECT_EQ(lv_image_get_src(this->widget_), other.get_lv_image_dsc());
|
||||
EXPECT_EQ(lv_image_get_src_width(this->widget_), 4);
|
||||
|
||||
// Given back, it is followed again
|
||||
this->set_src_(&img);
|
||||
load(img);
|
||||
EXPECT_EQ(lv_image_get_src_width(this->widget_), 3);
|
||||
}
|
||||
|
||||
// With double buffering the widget shows the old image while the new one decodes, then the new one at its size
|
||||
TEST_F(RuntimeImageLvglWidget, DoubleBufferedWidgetKeepsTheImageWhileDecoding) {
|
||||
RuntimeImage img(BMP, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE);
|
||||
img.set_double_buffer(true);
|
||||
load(img);
|
||||
this->set_src_(&img);
|
||||
const void *first = static_cast<const lv_image_dsc_t *>(lv_image_get_src(this->widget_))->data;
|
||||
|
||||
std::vector<uint8_t> data(BMP_24BPP, BMP_24BPP + sizeof(BMP_24BPP));
|
||||
ASSERT_TRUE(img.begin_decode(data.size()));
|
||||
ASSERT_EQ(img.feed_data(data.data(), data.size()), static_cast<int>(data.size()));
|
||||
EXPECT_EQ(static_cast<const lv_image_dsc_t *>(lv_image_get_src(this->widget_))->data, first);
|
||||
EXPECT_EQ(lv_image_get_src_width(this->widget_), 3);
|
||||
this->draw_();
|
||||
|
||||
ASSERT_TRUE(img.end_decode());
|
||||
EXPECT_NE(static_cast<const lv_image_dsc_t *>(lv_image_get_src(this->widget_))->data, first);
|
||||
EXPECT_EQ(lv_image_get_src_width(this->widget_), 3);
|
||||
this->draw_();
|
||||
}
|
||||
|
||||
TEST_F(RuntimeImageLvglWidget, DeletedWidgetIsForgotten) {
|
||||
RuntimeImage img(BMP, image::IMAGE_TYPE_RGB, image::TRANSPARENCY_OPAQUE);
|
||||
load(img);
|
||||
this->set_src_(&img);
|
||||
lv_obj_delete(this->widget_);
|
||||
img.release();
|
||||
}
|
||||
#endif // USE_LVGL_IMAGE
|
||||
|
||||
} // namespace esphome::runtime_image::testing
|
||||
|
||||
#endif // USE_LVGL
|
||||
@@ -0,0 +1,56 @@
|
||||
esphome:
|
||||
name: test-source-entity-follow
|
||||
|
||||
host:
|
||||
api:
|
||||
batch_delay: 0ms
|
||||
logger:
|
||||
|
||||
switch:
|
||||
- platform: template
|
||||
id: source_switch
|
||||
name: Source Switch
|
||||
optimistic: true
|
||||
restore_mode: ALWAYS_ON
|
||||
# Publishes nothing until commanded
|
||||
- platform: template
|
||||
id: quiet_switch
|
||||
name: Quiet Switch
|
||||
optimistic: true
|
||||
restore_mode: DISABLED
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
id: source_number
|
||||
name: Source Number
|
||||
optimistic: true
|
||||
min_value: 0
|
||||
max_value: 10
|
||||
step: 0.5
|
||||
initial_value: 4
|
||||
|
||||
text:
|
||||
- platform: template
|
||||
id: source_text
|
||||
name: Source Text
|
||||
optimistic: true
|
||||
mode: text
|
||||
initial_value: hello
|
||||
|
||||
binary_sensor:
|
||||
- platform: switch
|
||||
source_id: source_switch
|
||||
name: Switch State
|
||||
- platform: switch
|
||||
source_id: quiet_switch
|
||||
name: Quiet Switch State
|
||||
|
||||
sensor:
|
||||
- platform: number
|
||||
source_id: source_number
|
||||
name: Number Value
|
||||
|
||||
text_sensor:
|
||||
- platform: text
|
||||
source_id: source_text
|
||||
name: Text Value
|
||||
@@ -1,195 +1,208 @@
|
||||
{
|
||||
"tests/integration/test_action_concurrent_reentry.py": 14.84,
|
||||
"tests/integration/test_addressable_light_transition.py": 17.96,
|
||||
"tests/integration/test_alarm_control_panel_state_transitions.py": 16.42,
|
||||
"tests/integration/test_api_action_metadata.py": 12.08,
|
||||
"tests/integration/test_api_action_responses.py": 16.62,
|
||||
"tests/integration/test_api_action_timeout.py": 17.38,
|
||||
"tests/integration/test_api_conditional_memory.py": 12.24,
|
||||
"tests/integration/test_api_custom_services.py": 10.53,
|
||||
"tests/integration/test_api_decode_wire_types.py": 17.97,
|
||||
"tests/integration/test_api_empty_message_roundtrip.py": 12.54,
|
||||
"tests/integration/test_api_encode_boundaries.py": 14.46,
|
||||
"tests/integration/test_api_get_time_response_timezone.py": 16.43,
|
||||
"tests/integration/test_api_homeassistant.py": 17.44,
|
||||
"tests/integration/test_api_homeassistant_action_no_subscriber.py": 11.63,
|
||||
"tests/integration/test_api_homeassistant_binary_sensor_initial_state.py": 12.61,
|
||||
"tests/integration/test_api_homeassistant_entities.py": 10.45,
|
||||
"tests/integration/test_api_list_entities_backpressure.py": 13.7,
|
||||
"tests/integration/test_api_message_size_batching.py": 9.04,
|
||||
"tests/integration/test_api_outgoing_connection.py": 26.32,
|
||||
"tests/integration/test_api_reboot_timeout.py": 11.82,
|
||||
"tests/integration/test_api_string_lambda.py": 11.94,
|
||||
"tests/integration/test_api_vv_logging.py": 13.47,
|
||||
"tests/integration/test_api_zero_psk_provisioning.py": 27.07,
|
||||
"tests/integration/test_areas_and_devices.py": 9.9,
|
||||
"tests/integration/test_automation_wait_actions.py": 13.22,
|
||||
"tests/integration/test_automations.py": 26.92,
|
||||
"tests/integration/test_batch_delay_zero_rapid_transitions.py": 14.21,
|
||||
"tests/integration/test_binary_sensor_autorepeat_filter.py": 13.7,
|
||||
"tests/integration/test_binary_sensor_invalidate_state.py": 13.52,
|
||||
"tests/integration/test_blocking_warning_log_time_not_charged_to_next_operation.py": 12.48,
|
||||
"tests/integration/test_branch_resume_async.py": 16.18,
|
||||
"tests/integration/test_build_info.py": 11.15,
|
||||
"tests/integration/test_camera_mock.py": 13.74,
|
||||
"tests/integration/test_climate_control_action.py": 14.33,
|
||||
"tests/integration/test_climate_custom_modes.py": 13.85,
|
||||
"tests/integration/test_continuation_actions.py": 12.9,
|
||||
"tests/integration/test_cover_control_action.py": 10.2,
|
||||
"tests/integration/test_crc8_helper.py": 11.98,
|
||||
"tests/integration/test_device_id_in_state.py": 25.5,
|
||||
"tests/integration/test_duplicate_entities.py": 14.91,
|
||||
"tests/integration/test_entity_icon.py": 15.33,
|
||||
"tests/integration/test_entity_missing_state.py": 8.06,
|
||||
"tests/integration/test_fan_turn_on_action.py": 13.45,
|
||||
"tests/integration/test_fnv1_hash_object_id.py": 10.16,
|
||||
"tests/integration/test_fnv1a_hash.py": 9.94,
|
||||
"tests/integration/test_gpio_expander_cache.py": 10.52,
|
||||
"tests/integration/test_host_logger_thread_safety.py": 11.38,
|
||||
"tests/integration/test_host_mode_basic.py": 10.96,
|
||||
"tests/integration/test_host_mode_batch_delay.py": 13.46,
|
||||
"tests/integration/test_host_mode_climate_basic_state.py": 7.78,
|
||||
"tests/integration/test_host_mode_climate_control.py": 14.03,
|
||||
"tests/integration/test_host_mode_empty_string_options.py": 12.45,
|
||||
"tests/integration/test_host_mode_entity_fields.py": 15.66,
|
||||
"tests/integration/test_host_mode_fan_preset.py": 9.7,
|
||||
"tests/integration/test_host_mode_many_entities.py": 23.55,
|
||||
"tests/integration/test_host_mode_many_entities_multiple_connections.py": 14.87,
|
||||
"tests/integration/test_host_mode_noise_encryption.py": 29.31,
|
||||
"tests/integration/test_host_mode_reconnect.py": 7.15,
|
||||
"tests/integration/test_host_mode_sensor.py": 12.09,
|
||||
"tests/integration/test_host_ota.py": 307.27,
|
||||
"tests/integration/test_host_preferences.py": 13.14,
|
||||
"tests/integration/test_host_preferences_suspend_resume.py": 14.41,
|
||||
"tests/integration/test_host_write_after_peer_close.py": 13.85,
|
||||
"tests/integration/test_improv_serial_uart.py": 14.8,
|
||||
"tests/integration/test_in_range_conditions.py": 13.63,
|
||||
"tests/integration/test_ir_rf_transmit_complete.py": 17.32,
|
||||
"tests/integration/test_large_message_batching.py": 15.13,
|
||||
"tests/integration/test_legacy_area.py": 11.23,
|
||||
"tests/integration/test_light_automations.py": 8.52,
|
||||
"tests/integration/test_light_binary_effect_off_phase.py": 20.04,
|
||||
"tests/integration/test_light_calls.py": 16.91,
|
||||
"tests/integration/test_light_constant_brightness.py": 10.63,
|
||||
"tests/integration/test_light_control_action.py": 14.43,
|
||||
"tests/integration/test_light_dim_relative_action.py": 16.59,
|
||||
"tests/integration/test_light_effect_zero_brightness.py": 17.36,
|
||||
"tests/integration/test_light_initial_state.py": 13.38,
|
||||
"tests/integration/test_light_repeat_effect.py": 13.04,
|
||||
"tests/integration/test_light_restore_state.py": 14.38,
|
||||
"tests/integration/test_light_resume_effect.py": 17.23,
|
||||
"tests/integration/test_light_toggle_action.py": 15.35,
|
||||
"tests/integration/test_light_transition_state_publish_interval.py": 44.06,
|
||||
"tests/integration/test_lock_automations.py": 13.01,
|
||||
"tests/integration/test_logger_buffered_recursion_guard.py": 8.93,
|
||||
"tests/integration/test_loop_disable_enable.py": 12.38,
|
||||
"tests/integration/test_loop_interval_decoupling.py": 15.54,
|
||||
"tests/integration/test_loop_interval_default_not_pulled_forward.py": 13.16,
|
||||
"tests/integration/test_lvgl_color_picker_render.py": 53.51,
|
||||
"tests/integration/test_lvgl_headless_render.py": 58.96,
|
||||
"tests/integration/test_micros_to_millis.py": 2.84,
|
||||
"tests/integration/test_multi_click_trigger.py": 8.88,
|
||||
"tests/integration/test_multi_device_preferences.py": 14.72,
|
||||
"tests/integration/test_noise_encryption_key_protection.py": 21.32,
|
||||
"tests/integration/test_object_id_api_verification.py": 12.83,
|
||||
"tests/integration/test_object_id_friendly_name_no_mac_suffix.py": 7.81,
|
||||
"tests/integration/test_object_id_no_friendly_name.py": 24.12,
|
||||
"tests/integration/test_online_image_auto_detects_image_bmp_mime.py": 25.21,
|
||||
"tests/integration/test_online_image_auto_detects_redirected_image_bmp_mime.py": 25.14,
|
||||
"tests/integration/test_online_image_bmp.py": 16.77,
|
||||
"tests/integration/test_online_image_stalled_download.py": 17.06,
|
||||
"tests/integration/test_oversized_payloads.py": 37.98,
|
||||
"tests/integration/test_preference_key_stability.py": 13.43,
|
||||
"tests/integration/test_runtime_stats.py": 12.02,
|
||||
"tests/integration/test_safe_mode_loop_runs.py": 11.39,
|
||||
"tests/integration/test_scheduler_background_wake.py": 17.85,
|
||||
"tests/integration/test_scheduler_blocking_warning.py": 30.7,
|
||||
"tests/integration/test_scheduler_bulk_cleanup.py": 12.73,
|
||||
"tests/integration/test_scheduler_defer_cancel.py": 13.05,
|
||||
"tests/integration/test_scheduler_defer_cancel_regular.py": 12.12,
|
||||
"tests/integration/test_scheduler_defer_fifo_simple.py": 12.73,
|
||||
"tests/integration/test_scheduler_defer_stress.py": 12.9,
|
||||
"tests/integration/test_scheduler_heap_stress.py": 13.61,
|
||||
"tests/integration/test_scheduler_internal_id_no_collision.py": 9.06,
|
||||
"tests/integration/test_scheduler_interval_reschedule.py": 12.19,
|
||||
"tests/integration/test_scheduler_interval_zero_coerced.py": 3.33,
|
||||
"tests/integration/test_scheduler_loop_phase_timeout.py": 10.81,
|
||||
"tests/integration/test_scheduler_null_name.py": 3.45,
|
||||
"tests/integration/test_scheduler_numeric_id_test.py": 13.88,
|
||||
"tests/integration/test_scheduler_pool.py": 13.86,
|
||||
"tests/integration/test_scheduler_rapid_cancellation.py": 4.54,
|
||||
"tests/integration/test_scheduler_recursive_timeout.py": 4.32,
|
||||
"tests/integration/test_scheduler_removed_item_race.py": 13.06,
|
||||
"tests/integration/test_scheduler_self_keyed.py": 4.29,
|
||||
"tests/integration/test_scheduler_simultaneous_callbacks.py": 12.79,
|
||||
"tests/integration/test_scheduler_string_test.py": 8.51,
|
||||
"tests/integration/test_script_array_params.py": 8.05,
|
||||
"tests/integration/test_script_delay_params.py": 14.57,
|
||||
"tests/integration/test_script_on_stop_and_choose.py": 14.11,
|
||||
"tests/integration/test_script_queued.py": 15.68,
|
||||
"tests/integration/test_script_queued_idle_loop.py": 8.46,
|
||||
"tests/integration/test_script_wait_on_boot.py": 12.91,
|
||||
"tests/integration/test_sdl_headless_screenshot.py": 15.05,
|
||||
"tests/integration/test_select_stringref_trigger.py": 11.48,
|
||||
"tests/integration/test_sensor_filters_delta.py": 13.28,
|
||||
"tests/integration/test_sensor_filters_ring_buffer.py": 13.95,
|
||||
"tests/integration/test_sensor_filters_sliding_window.py": 40.27,
|
||||
"tests/integration/test_sensor_filters_value_list.py": 14.95,
|
||||
"tests/integration/test_sensor_raw_state.py": 24.93,
|
||||
"tests/integration/test_sensor_timeout_filter.py": 14.93,
|
||||
"tests/integration/test_sensor_timeout_throttle_filter.py": 16.23,
|
||||
"tests/integration/test_set_internal_at_boot.py": 12.69,
|
||||
"tests/integration/test_snapshot_display.py": 15.83,
|
||||
"tests/integration/test_socket_ipv4_resolve.py": 10.41,
|
||||
"tests/integration/test_socket_tcp_client_link.py": 12.96,
|
||||
"tests/integration/test_socket_wake_gate_tcp.py": 11.61,
|
||||
"tests/integration/test_socket_wake_gate_udp.py": 10.45,
|
||||
"tests/integration/test_status_flags.py": 20.81,
|
||||
"tests/integration/test_status_momentary.py": 15.46,
|
||||
"tests/integration/test_strftime_to.py": 12.97,
|
||||
"tests/integration/test_syslog.py": 21.29,
|
||||
"tests/integration/test_tcp_uart_bridge.py": 16.41,
|
||||
"tests/integration/test_tcp_uart_server.py": 13.43,
|
||||
"tests/integration/test_template_alarm_control_panel_many_sensors.py": 13.34,
|
||||
"tests/integration/test_template_climate_basic.py": 12.78,
|
||||
"tests/integration/test_template_climate_custom_modes.py": 14.81,
|
||||
"tests/integration/test_template_climate_nonoptimistic.py": 8.48,
|
||||
"tests/integration/test_template_climate_on_control_ordering.py": 7.38,
|
||||
"tests/integration/test_template_climate_publish_all_fields.py": 8.89,
|
||||
"tests/integration/test_template_climate_sensor_push.py": 15.73,
|
||||
"tests/integration/test_template_climate_set_actions.py": 11.23,
|
||||
"tests/integration/test_template_climate_two_point_temperature.py": 9.47,
|
||||
"tests/integration/test_template_text_save.py": 13.65,
|
||||
"tests/integration/test_template_time.py": 9.1,
|
||||
"tests/integration/test_text_command.py": 12.29,
|
||||
"tests/integration/test_text_sensor_raw_state.py": 13.19,
|
||||
"tests/integration/test_uart_mock_bl0906_stray_rx.py": 13.99,
|
||||
"tests/integration/test_uart_mock_dfrobot_sen0465.py": 20.14,
|
||||
"tests/integration/test_uart_mock_dfrobot_sen0609.py": 10.57,
|
||||
"tests/integration/test_uart_mock_ld2410.py": 31.61,
|
||||
"tests/integration/test_uart_mock_ld2410s.py": 49.83,
|
||||
"tests/integration/test_uart_mock_ld2412.py": 89.93,
|
||||
"tests/integration/test_uart_mock_ld2420.py": 20.19,
|
||||
"tests/integration/test_uart_mock_ld2450.py": 16.45,
|
||||
"tests/integration/test_uart_mock_ld2450_has_target_only.py": 17.25,
|
||||
"tests/integration/test_uart_mock_ld2450_polygon_zone_restore.py": 10.7,
|
||||
"tests/integration/test_uart_mock_ld2450_polygon_zone_timeout.py": 15.95,
|
||||
"tests/integration/test_uart_mock_ld2450_polygon_zones.py": 11.68,
|
||||
"tests/integration/test_uart_mock_ld2460.py": 15.46,
|
||||
"tests/integration/test_uart_mock_modbus.py": 214.58,
|
||||
"tests/integration/test_uart_mock_pylontech.py": 14.66,
|
||||
"tests/integration/test_uart_mock_ufm01.py": 70.02,
|
||||
"tests/integration/test_uart_tcp_bridge.py": 10.46,
|
||||
"tests/integration/test_udp.py": 9.38,
|
||||
"tests/integration/test_udp_ipv6.py": 8.74,
|
||||
"tests/integration/test_udp_multicast.py": 11.71,
|
||||
"tests/integration/test_use_address_runtime.py": 15.24,
|
||||
"tests/integration/test_valve_control_action.py": 13.75,
|
||||
"tests/integration/test_varint_five_byte_device_id.py": 13.56,
|
||||
"tests/integration/test_wait_until_mid_loop_timing.py": 8.57,
|
||||
"tests/integration/test_wait_until_on_boot.py": 9.51,
|
||||
"tests/integration/test_wait_until_ordering.py": 4.48,
|
||||
"tests/integration/test_wait_until_reentrant_restart.py": 5.63,
|
||||
"tests/integration/test_wake_loop_forces_phase_b.py": 9.4,
|
||||
"tests/integration/test_water_heater_template.py": 28.16
|
||||
"tests/integration/test_action_concurrent_reentry.py": 15.02,
|
||||
"tests/integration/test_addressable_light_transition.py": 14.54,
|
||||
"tests/integration/test_alarm_control_panel_state_transitions.py": 15.84,
|
||||
"tests/integration/test_api_action_metadata.py": 14.04,
|
||||
"tests/integration/test_api_action_responses.py": 17.62,
|
||||
"tests/integration/test_api_action_timeout.py": 15.17,
|
||||
"tests/integration/test_api_conditional_memory.py": 12.16,
|
||||
"tests/integration/test_api_cover_toggle.py": 13.64,
|
||||
"tests/integration/test_api_custom_services.py": 11.96,
|
||||
"tests/integration/test_api_decode_wire_types.py": 12.88,
|
||||
"tests/integration/test_api_empty_message_roundtrip.py": 11.72,
|
||||
"tests/integration/test_api_encode_boundaries.py": 9.99,
|
||||
"tests/integration/test_api_get_time_response_timezone.py": 12.23,
|
||||
"tests/integration/test_api_homeassistant.py": 17.98,
|
||||
"tests/integration/test_api_homeassistant_action_no_subscriber.py": 7.67,
|
||||
"tests/integration/test_api_homeassistant_binary_sensor_initial_state.py": 13.2,
|
||||
"tests/integration/test_api_homeassistant_entities.py": 10.65,
|
||||
"tests/integration/test_api_homeassistant_light.py": 33.13,
|
||||
"tests/integration/test_api_list_entities_backpressure.py": 10.9,
|
||||
"tests/integration/test_api_message_size_batching.py": 8.36,
|
||||
"tests/integration/test_api_outgoing_connection.py": 34.71,
|
||||
"tests/integration/test_api_reboot_timeout.py": 12.36,
|
||||
"tests/integration/test_api_string_lambda.py": 9.26,
|
||||
"tests/integration/test_api_vv_logging.py": 14.12,
|
||||
"tests/integration/test_api_zero_psk_provisioning.py": 18.15,
|
||||
"tests/integration/test_areas_and_devices.py": 14.82,
|
||||
"tests/integration/test_automation_wait_actions.py": 13.62,
|
||||
"tests/integration/test_automations.py": 22.32,
|
||||
"tests/integration/test_batch_delay_zero_rapid_transitions.py": 10.22,
|
||||
"tests/integration/test_binary_sensor_autorepeat_filter.py": 11.78,
|
||||
"tests/integration/test_binary_sensor_invalidate_state.py": 10.5,
|
||||
"tests/integration/test_blocking_warning_log_time_not_charged_to_next_operation.py": 8.28,
|
||||
"tests/integration/test_branch_resume_async.py": 13.54,
|
||||
"tests/integration/test_build_info.py": 8.43,
|
||||
"tests/integration/test_camera_mock.py": 9.04,
|
||||
"tests/integration/test_climate_control_action.py": 15.04,
|
||||
"tests/integration/test_climate_custom_modes.py": 11.12,
|
||||
"tests/integration/test_continuation_actions.py": 8.87,
|
||||
"tests/integration/test_copy_entity_state.py": 9.64,
|
||||
"tests/integration/test_copy_initially_off.py": 10.97,
|
||||
"tests/integration/test_cover_control_action.py": 13.41,
|
||||
"tests/integration/test_cover_stop_tilt_cancel.py": 16.62,
|
||||
"tests/integration/test_crc8_helper.py": 7.35,
|
||||
"tests/integration/test_device_id_in_state.py": 18.29,
|
||||
"tests/integration/test_duplicate_entities.py": 12.49,
|
||||
"tests/integration/test_entity_icon.py": 12.18,
|
||||
"tests/integration/test_entity_missing_state.py": 9.01,
|
||||
"tests/integration/test_fan_turn_on_action.py": 12.41,
|
||||
"tests/integration/test_fnv1_hash_object_id.py": 6.14,
|
||||
"tests/integration/test_fnv1a_hash.py": 8.0,
|
||||
"tests/integration/test_gpio_expander_cache.py": 9.51,
|
||||
"tests/integration/test_host_logger_thread_safety.py": 9.08,
|
||||
"tests/integration/test_host_mode_basic.py": 10.07,
|
||||
"tests/integration/test_host_mode_batch_delay.py": 10.04,
|
||||
"tests/integration/test_host_mode_climate_basic_state.py": 10.36,
|
||||
"tests/integration/test_host_mode_climate_control.py": 10.68,
|
||||
"tests/integration/test_host_mode_empty_string_options.py": 9.22,
|
||||
"tests/integration/test_host_mode_entity_fields.py": 12.65,
|
||||
"tests/integration/test_host_mode_fan_preset.py": 9.37,
|
||||
"tests/integration/test_host_mode_many_entities.py": 23.2,
|
||||
"tests/integration/test_host_mode_many_entities_multiple_connections.py": 14.92,
|
||||
"tests/integration/test_host_mode_noise_encryption.py": 27.22,
|
||||
"tests/integration/test_host_mode_reconnect.py": 7.22,
|
||||
"tests/integration/test_host_mode_sensor.py": 9.89,
|
||||
"tests/integration/test_host_ota.py": 93.33,
|
||||
"tests/integration/test_host_preferences.py": 9.34,
|
||||
"tests/integration/test_host_preferences_suspend_resume.py": 10.46,
|
||||
"tests/integration/test_host_write_after_peer_close.py": 12.93,
|
||||
"tests/integration/test_improv_serial_uart.py": 10.68,
|
||||
"tests/integration/test_in_range_conditions.py": 13.84,
|
||||
"tests/integration/test_includes_lambda_order.py": 9.07,
|
||||
"tests/integration/test_ir_rf_transmit_complete.py": 15.53,
|
||||
"tests/integration/test_large_message_batching.py": 13.44,
|
||||
"tests/integration/test_legacy_area.py": 8.07,
|
||||
"tests/integration/test_light_automations.py": 14.26,
|
||||
"tests/integration/test_light_binary_effect_off_phase.py": 20.03,
|
||||
"tests/integration/test_light_calls.py": 16.45,
|
||||
"tests/integration/test_light_constant_brightness.py": 13.09,
|
||||
"tests/integration/test_light_control_action.py": 13.41,
|
||||
"tests/integration/test_light_dim_relative_action.py": 10.41,
|
||||
"tests/integration/test_light_effect_zero_brightness.py": 14.36,
|
||||
"tests/integration/test_light_initial_state.py": 9.84,
|
||||
"tests/integration/test_light_repeat_effect.py": 16.56,
|
||||
"tests/integration/test_light_restore_state.py": 13.07,
|
||||
"tests/integration/test_light_resume_effect.py": 10.81,
|
||||
"tests/integration/test_light_toggle_action.py": 10.35,
|
||||
"tests/integration/test_light_transition_state_publish_interval.py": 43.17,
|
||||
"tests/integration/test_lock_automations.py": 8.08,
|
||||
"tests/integration/test_logger_buffered_recursion_guard.py": 11.54,
|
||||
"tests/integration/test_loop_disable_enable.py": 9.37,
|
||||
"tests/integration/test_loop_failed_before_setup.py": 6.93,
|
||||
"tests/integration/test_loop_interval_decoupling.py": 9.68,
|
||||
"tests/integration/test_loop_interval_default_not_pulled_forward.py": 13.06,
|
||||
"tests/integration/test_lvgl_color_picker_render.py": 39.23,
|
||||
"tests/integration/test_lvgl_headless_render.py": 40.05,
|
||||
"tests/integration/test_micros_to_millis.py": 6.53,
|
||||
"tests/integration/test_multi_click_trigger.py": 11.46,
|
||||
"tests/integration/test_multi_device_preferences.py": 11.03,
|
||||
"tests/integration/test_noise_encryption_key_protection.py": 16.83,
|
||||
"tests/integration/test_object_id_api_verification.py": 8.86,
|
||||
"tests/integration/test_object_id_friendly_name_no_mac_suffix.py": 8.93,
|
||||
"tests/integration/test_object_id_no_friendly_name.py": 11.88,
|
||||
"tests/integration/test_online_image_auto_detects_image_bmp_mime.py": 24.18,
|
||||
"tests/integration/test_online_image_auto_detects_redirected_image_bmp_mime.py": 20.36,
|
||||
"tests/integration/test_online_image_bmp.py": 16.14,
|
||||
"tests/integration/test_online_image_stalled_download.py": 19.52,
|
||||
"tests/integration/test_oversized_payloads.py": 50.86,
|
||||
"tests/integration/test_preference_key_stability.py": 14.0,
|
||||
"tests/integration/test_runtime_stats.py": 13.09,
|
||||
"tests/integration/test_safe_mode_loop_runs.py": 6.98,
|
||||
"tests/integration/test_scheduler_background_wake.py": 19.01,
|
||||
"tests/integration/test_scheduler_blocking_warning.py": 20.57,
|
||||
"tests/integration/test_scheduler_bulk_cleanup.py": 7.57,
|
||||
"tests/integration/test_scheduler_defer_cancel.py": 12.57,
|
||||
"tests/integration/test_scheduler_defer_cancel_regular.py": 8.4,
|
||||
"tests/integration/test_scheduler_defer_fifo_simple.py": 9.31,
|
||||
"tests/integration/test_scheduler_defer_stress.py": 10.83,
|
||||
"tests/integration/test_scheduler_heap_stress.py": 8.74,
|
||||
"tests/integration/test_scheduler_internal_id_no_collision.py": 13.5,
|
||||
"tests/integration/test_scheduler_interval_reschedule.py": 8.17,
|
||||
"tests/integration/test_scheduler_interval_zero_coerced.py": 9.21,
|
||||
"tests/integration/test_scheduler_loop_phase_timeout.py": 14.95,
|
||||
"tests/integration/test_scheduler_null_name.py": 13.19,
|
||||
"tests/integration/test_scheduler_numeric_id_test.py": 9.63,
|
||||
"tests/integration/test_scheduler_pool.py": 10.74,
|
||||
"tests/integration/test_scheduler_rapid_cancellation.py": 8.55,
|
||||
"tests/integration/test_scheduler_recursive_timeout.py": 13.76,
|
||||
"tests/integration/test_scheduler_removed_item_race.py": 14.2,
|
||||
"tests/integration/test_scheduler_self_keyed.py": 9.51,
|
||||
"tests/integration/test_scheduler_simultaneous_callbacks.py": 13.19,
|
||||
"tests/integration/test_scheduler_string_test.py": 10.9,
|
||||
"tests/integration/test_script_array_params.py": 12.44,
|
||||
"tests/integration/test_script_delay_params.py": 12.44,
|
||||
"tests/integration/test_script_on_stop_and_choose.py": 10.69,
|
||||
"tests/integration/test_script_queued.py": 11.25,
|
||||
"tests/integration/test_script_queued_idle_loop.py": 15.05,
|
||||
"tests/integration/test_script_wait_on_boot.py": 11.97,
|
||||
"tests/integration/test_sdl_headless_screenshot.py": 14.47,
|
||||
"tests/integration/test_select_stringref_trigger.py": 13.37,
|
||||
"tests/integration/test_sensor_filters_delta.py": 13.76,
|
||||
"tests/integration/test_sensor_filters_rh_correction.py": 9.72,
|
||||
"tests/integration/test_sensor_filters_ring_buffer.py": 13.07,
|
||||
"tests/integration/test_sensor_filters_sliding_window.py": 28.7,
|
||||
"tests/integration/test_sensor_filters_value_list.py": 9.61,
|
||||
"tests/integration/test_sensor_raw_state.py": 24.98,
|
||||
"tests/integration/test_sensor_timeout_filter.py": 10.01,
|
||||
"tests/integration/test_sensor_timeout_throttle_filter.py": 9.86,
|
||||
"tests/integration/test_set_internal_at_boot.py": 9.58,
|
||||
"tests/integration/test_snapshot_display.py": 14.37,
|
||||
"tests/integration/test_socket_ipv4_resolve.py": 11.12,
|
||||
"tests/integration/test_socket_tcp_client_link.py": 13.42,
|
||||
"tests/integration/test_socket_wake_gate_tcp.py": 12.61,
|
||||
"tests/integration/test_socket_wake_gate_udp.py": 8.99,
|
||||
"tests/integration/test_source_entity_follow.py": 14.73,
|
||||
"tests/integration/test_status_flags.py": 16.3,
|
||||
"tests/integration/test_status_momentary.py": 15.28,
|
||||
"tests/integration/test_strftime_to.py": 10.03,
|
||||
"tests/integration/test_syslog.py": 17.43,
|
||||
"tests/integration/test_tcp_uart_bridge.py": 16.25,
|
||||
"tests/integration/test_tcp_uart_server.py": 6.52,
|
||||
"tests/integration/test_template_alarm_control_panel_many_sensors.py": 8.11,
|
||||
"tests/integration/test_template_climate_basic.py": 13.7,
|
||||
"tests/integration/test_template_climate_custom_modes.py": 6.88,
|
||||
"tests/integration/test_template_climate_nonoptimistic.py": 12.68,
|
||||
"tests/integration/test_template_climate_on_control_ordering.py": 12.72,
|
||||
"tests/integration/test_template_climate_publish_all_fields.py": 10.76,
|
||||
"tests/integration/test_template_climate_sensor_push.py": 13.52,
|
||||
"tests/integration/test_template_climate_set_actions.py": 13.41,
|
||||
"tests/integration/test_template_climate_two_point_temperature.py": 8.4,
|
||||
"tests/integration/test_template_text_save.py": 12.87,
|
||||
"tests/integration/test_template_time.py": 13.35,
|
||||
"tests/integration/test_text_command.py": 11.78,
|
||||
"tests/integration/test_text_sensor_raw_state.py": 8.44,
|
||||
"tests/integration/test_uart_mock_bl0906_stray_rx.py": 13.96,
|
||||
"tests/integration/test_uart_mock_dfrobot_sen0465.py": 18.9,
|
||||
"tests/integration/test_uart_mock_dfrobot_sen0609.py": 12.01,
|
||||
"tests/integration/test_uart_mock_duco.py": 37.61,
|
||||
"tests/integration/test_uart_mock_ld2410.py": 33.42,
|
||||
"tests/integration/test_uart_mock_ld2410_query_ack.py": 10.85,
|
||||
"tests/integration/test_uart_mock_ld2410s.py": 49.7,
|
||||
"tests/integration/test_uart_mock_ld2412.py": 72.78,
|
||||
"tests/integration/test_uart_mock_ld2420.py": 21.52,
|
||||
"tests/integration/test_uart_mock_ld2420_config_exit.py": 39.94,
|
||||
"tests/integration/test_uart_mock_ld2450.py": 11.62,
|
||||
"tests/integration/test_uart_mock_ld2450_has_target_only.py": 15.91,
|
||||
"tests/integration/test_uart_mock_ld2450_polygon_zone_restore.py": 9.66,
|
||||
"tests/integration/test_uart_mock_ld2450_polygon_zone_timeout.py": 14.46,
|
||||
"tests/integration/test_uart_mock_ld2450_polygon_zones.py": 16.34,
|
||||
"tests/integration/test_uart_mock_ld2460.py": 13.22,
|
||||
"tests/integration/test_uart_mock_modbus.py": 249.46,
|
||||
"tests/integration/test_uart_mock_pylontech.py": 14.81,
|
||||
"tests/integration/test_uart_mock_ufm01.py": 69.05,
|
||||
"tests/integration/test_uart_tcp_bridge.py": 7.58,
|
||||
"tests/integration/test_udp.py": 10.0,
|
||||
"tests/integration/test_udp_ipv6.py": 11.71,
|
||||
"tests/integration/test_udp_multicast.py": 12.0,
|
||||
"tests/integration/test_use_address_runtime.py": 16.14,
|
||||
"tests/integration/test_valve_control_action.py": 12.37,
|
||||
"tests/integration/test_varint_five_byte_device_id.py": 9.46,
|
||||
"tests/integration/test_vnc_client.py": 19.94,
|
||||
"tests/integration/test_wait_until_mid_loop_timing.py": 8.72,
|
||||
"tests/integration/test_wait_until_on_boot.py": 4.54,
|
||||
"tests/integration/test_wait_until_ordering.py": 10.39,
|
||||
"tests/integration/test_wait_until_reentrant_restart.py": 13.99,
|
||||
"tests/integration/test_wake_loop_forces_phase_b.py": 10.26,
|
||||
"tests/integration/test_water_heater_template.py": 20.13
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
"""Integration test for the switch binary sensor, number sensor and text text sensor platforms."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
|
||||
from aioesphomeapi import (
|
||||
BinarySensorState,
|
||||
EntityState,
|
||||
NumberInfo,
|
||||
SensorState,
|
||||
SwitchInfo,
|
||||
TextInfo,
|
||||
TextSensorState,
|
||||
)
|
||||
import pytest
|
||||
|
||||
from .state_utils import require_entity, subscribe_after_initial_states
|
||||
from .types import APIClientConnectedFactory, RunCompiledFunction
|
||||
|
||||
INITIAL: dict[str, tuple[type[EntityState], float | bool | str | None]] = {
|
||||
"switch_state": (BinarySensorState, True),
|
||||
# A switch that has not published leaves its binary sensor unknown
|
||||
"quiet_switch_state": (BinarySensorState, None),
|
||||
"number_value": (SensorState, 4.0),
|
||||
"text_value": (TextSensorState, "hello"),
|
||||
}
|
||||
UPDATED: dict[str, tuple[type[EntityState], float | bool | str | None]] = {
|
||||
"switch_state": (BinarySensorState, False),
|
||||
"quiet_switch_state": (BinarySensorState, True),
|
||||
"number_value": (SensorState, 6.5),
|
||||
"text_value": (TextSensorState, "bye"),
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_source_entity_follow(
|
||||
yaml_config: str,
|
||||
run_compiled: RunCompiledFunction,
|
||||
api_client_connected: APIClientConnectedFactory,
|
||||
) -> None:
|
||||
"""Each entity holds its source's state from boot, then follows it."""
|
||||
async with run_compiled(yaml_config), api_client_connected() as client:
|
||||
entities, _ = await client.list_entities_services()
|
||||
keys = {
|
||||
name: require_entity(entities, name, exact=True).key for name in INITIAL
|
||||
}
|
||||
switch = require_entity(entities, "source_switch", SwitchInfo)
|
||||
quiet = require_entity(entities, "quiet_switch", SwitchInfo)
|
||||
number = require_entity(entities, "source_number", NumberInfo)
|
||||
text = require_entity(entities, "source_text", TextInfo)
|
||||
|
||||
sub = await subscribe_after_initial_states(client, entities)
|
||||
for name, (cls, state) in INITIAL.items():
|
||||
key = keys[name]
|
||||
expected = (
|
||||
cls(key=key, missing_state=True)
|
||||
if state is None
|
||||
else cls(key=key, state=state)
|
||||
)
|
||||
assert sub.initial_states[key] == expected, name
|
||||
|
||||
names = list(UPDATED)
|
||||
waits = [sub.waiter.next_state(keys[name]) for name in names]
|
||||
client.switch_command(switch.key, False)
|
||||
client.switch_command(quiet.key, True)
|
||||
client.number_command(number.key, 6.5)
|
||||
client.text_command(text.key, "bye")
|
||||
for name, got in zip(names, await asyncio.gather(*waits), strict=True):
|
||||
cls, state = UPDATED[name]
|
||||
assert got == cls(key=keys[name], state=state), name
|
||||
@@ -486,3 +486,45 @@ def test_populate_registers_platform_for_platform_entry() -> None:
|
||||
assert registered == ["sensor", "binary_sensor"]
|
||||
assert config["sensor"] == [{"platform": "gpio"}]
|
||||
assert config["binary_sensor"] == [{"platform": "gpio"}]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# resolve_dependencies_with_overrides
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_resolve_includes_dependency_added_by_dependency_override() -> None:
|
||||
"""An override on a component found only as a dependency can add a
|
||||
dependency of its own, and that one must be included too.
|
||||
"""
|
||||
graph: dict[str, set[str]] = {"tested": {"middle"}, "middle": set(), "extra": set()}
|
||||
loaded: list[list[str]] = []
|
||||
|
||||
def loader(components: list[str]) -> None:
|
||||
loaded.append(list(components))
|
||||
if "middle" in components:
|
||||
graph["middle"] = {"extra"}
|
||||
|
||||
def all_deps(names: set[str]) -> set[str]:
|
||||
result = set(names)
|
||||
for name in names:
|
||||
result |= graph[name]
|
||||
return result
|
||||
|
||||
with patch("build_helpers.get_all_dependencies", side_effect=all_deps):
|
||||
result = build_helpers.resolve_dependencies_with_overrides(["tested"], loader)
|
||||
|
||||
assert result == ["extra", "middle", "tested"]
|
||||
assert loaded[-1] == ["extra", "middle", "tested"]
|
||||
|
||||
|
||||
def test_resolve_stops_when_no_new_dependencies() -> None:
|
||||
loader = MagicMock()
|
||||
|
||||
with patch(
|
||||
"build_helpers.get_all_dependencies", side_effect=lambda names: names | {"dep"}
|
||||
):
|
||||
result = build_helpers.resolve_dependencies_with_overrides(["tested"], loader)
|
||||
|
||||
assert result == ["dep", "tested"]
|
||||
assert loader.call_count == 2
|
||||
Reference in new issue
Block a user