19static constexpr uint32_t LOOP_BUDGET_MS = 20;
20static constexpr uint32_t HANDSHAKE_TIMEOUT_MS = 15000;
21static constexpr uint32_t STABLE_SESSION_MS = 60000;
22static constexpr uint32_t MAX_BACKOFF_MS = 60000;
23static constexpr uint32_t CONFIG_RETRY_MS = 300000;
24static constexpr uint32_t HEARTBEAT_MS = 60000;
25static constexpr uint32_t MIN_REQUEST_SPACING_MS = 50;
27static constexpr uint32_t DISPLAY_START_MS = 5000;
29static constexpr uint32_t DISPLAY_BUSY_TIMEOUT_MS = 120000;
30static constexpr uint16_t MAX_CURSOR_SIDE = 1024;
31static constexpr uint32_t MAX_TEXT_LEN = 63;
33static constexpr uint8_t SECURITY_NONE = 1;
34static constexpr uint8_t SECURITY_VNC_AUTH = 2;
36static constexpr int32_t ENCODING_RAW = 0;
37static constexpr int32_t ENCODING_HEXTILE = 5;
38static constexpr int32_t ENCODING_RICH_CURSOR = -239;
40static constexpr int32_t ENCODINGS[] = {ENCODING_HEXTILE, ENCODING_RAW, ENCODING_RICH_CURSOR};
45static constexpr uint8_t HEXTILE_RAW = 0x01;
46static constexpr uint8_t HEXTILE_BACKGROUND = 0x02;
47static constexpr uint8_t HEXTILE_FOREGROUND = 0x04;
48static constexpr uint8_t HEXTILE_ANY_SUBRECTS = 0x08;
49static constexpr uint8_t HEXTILE_SUBRECTS_COLOURED = 0x10;
51static constexpr uint8_t MSG_FRAMEBUFFER_UPDATE = 0;
52static constexpr uint8_t MSG_SET_COLOUR_MAP = 1;
53static constexpr uint8_t MSG_BELL = 2;
54static constexpr uint8_t MSG_SERVER_CUT_TEXT = 3;
56static inline uint16_t read_be16(
const uint8_t *p) {
return encode_uint16(p[0], p[1]); }
58static inline void write_be16(uint8_t *p, uint16_t v) {
62static inline void write_be32(uint8_t *p,
uint32_t v) {
69static int parse_three_digits(
const uint8_t *p) {
71 for (
int i = 0; i < 3; i++) {
72 if (p[i] <
'0' || p[i] >
'9') {
86 const uint32_t cells = std::min<uint32_t>(wanted, UINT16_MAX);
88 ESP_LOGW(TAG,
"Display has %" PRIu32
" tiles, more than the %u the cache holds", wanted, UINT16_MAX);
93 ESP_LOGW(TAG,
"Not enough memory to skip unchanged tiles");
115 " Reconnect Interval: %" PRIu32
"ms\n"
116 " Update Delay: %" PRIu32
"ms\n"
117 " Max Update Delay: %" PRIu32
"ms\n"
118 " Pixel Format: %s\n"
119 " Tile Cache: %u tiles",
120 this->
link_.
host(), this->link_.port(), this->link_.reconnect_interval(), this->update_delay_ms_,
121 this->max_update_delay_ms_,
122 LOG_STR_ARG(PixelFormatStrings::get_log_str(
static_cast<uint8_t
>(this->
pixel_format_), 0)),
196 const bool display_starting =
240 if (!this->
link_.
connecting() && (now - this->hold_start_ms_ < this->hold_ms_ || this->link_.in_backoff())) {
245 if (!this->
link_.
connected() && !this->link_.connecting() && this->link_.in_backoff()) {
305 ESP_LOGW(TAG,
"%s after %" PRIu32
"s", LOG_STR_ARG(reason), lasted / 1000);
308 if (lasted >= STABLE_SESSION_MS) {
318 ESP_LOGE(TAG,
"%s; retrying in %" PRIu32
"s", LOG_STR_ARG(reason), this->
hold_ms_ / 1000);
327 ESP_LOGW(TAG,
"%s; retrying in %" PRIu32
"s", LOG_STR_ARG(reason), this->
hold_ms_ / 1000);
379 const uint8_t shared = 1;
380 return this->
send_(&shared, 1);
387 switch (this->
step_) {
392 const int major = parse_three_digits(p + 4);
393 const int minor = parse_three_digits(p + 8);
394 if (std::memcmp(p,
"RFB ", 4) != 0 || p[7] !=
'.' || p[11] !=
'\n' || major < 0 || minor < 0) {
400 this->
minor_ = (major > 3 || minor >= 8) ? 8 : (minor == 7 ? 7 : 3);
402 uint8_t reply[12] = {
'R',
'F',
'B',
' ',
'0',
'0',
'3',
'.',
'0',
'0',
'8',
'\n'};
403 reply[10] =
'0' + this->
minor_;
404 if (!this->
send_(reply,
sizeof(reply))) {
429 bool wants_password =
false;
430 for (uint8_t i = 0; i < this->
sec_count_; i++) {
431 if (p[i] == SECURITY_NONE) {
435 wants_password |= p[i] == SECURITY_VNC_AUTH;
440 : LOG_STR(
"No supported security type"));
442 if (!this->
send_(&chosen, 1)) {
463 }
else if (type == SECURITY_NONE) {
470 ? LOG_STR(
"The server requires a password")
471 : LOG_STR(
"No supported security type"));
479 const uint32_t result = read_be32(p);
496 this->
text_len_ =
static_cast<uint8_t
>(std::min(read_be32(p), MAX_TEXT_LEN));
505 ESP_LOGE(TAG,
"Server: %.*s",
static_cast<int>(this->text_len_),
reinterpret_cast<const char *
>(p));
512 this->
fb_w_ = read_be16(p);
513 this->
fb_h_ = read_be16(p + 2);
528 ESP_LOGI(TAG,
"Server '%.*s', %ux%u, RFB 3.%u",
static_cast<int>(this->text_len_),
545 case MSG_FRAMEBUFFER_UPDATE:
557 case MSG_SET_COLOUR_MAP:
567 case MSG_SERVER_CUT_TEXT:
575 ESP_LOGW(TAG,
"Unknown message type %u", p[0]);
588 this->
rect_y_ = read_be16(p + 2);
589 this->
rect_w_ = read_be16(p + 4);
590 this->
rect_h_ = read_be16(p + 6);
591 const auto encoding =
static_cast<int32_t
>(read_be32(p + 8));
594 if (encoding == ENCODING_RAW || encoding == ENCODING_HEXTILE) {
597 ESP_LOGW(TAG,
"Rectangle %ux%u at %u,%u is outside the %ux%u screen", this->
rect_w_, this->
rect_h_,
603 }
else if (encoding == ENCODING_RAW) {
613 }
else if (encoding == ENCODING_RICH_CURSOR) {
614 if (this->
rect_w_ > MAX_CURSOR_SIDE || this->
rect_h_ > MAX_CURSOR_SIDE) {
621 ESP_LOGW(TAG,
"Unsupported encoding %" PRId32, encoding);
633 const size_t pixels = avail / this->
bpp_;
640 const auto count =
static_cast<uint16_t
>(std::min<size_t>(pixels, this->
rect_w_ - this->
pos_x_));
662 const uint8_t
flags = p[0];
667 if (flags & HEXTILE_RAW) {
673 const size_t need = 1 + ((
flags & HEXTILE_BACKGROUND) ? this->
bpp_ : 0) +
674 ((flags & HEXTILE_FOREGROUND) ? this->
bpp_ : 0) + ((flags & HEXTILE_ANY_SUBRECTS) ? 1 : 0);
678 const uint8_t *q = p + 1;
679 if (
flags & HEXTILE_BACKGROUND) {
680 std::memcpy(this->
hex_bg_, q, this->bpp_);
683 if (flags & HEXTILE_FOREGROUND) {
684 std::memcpy(this->
hex_fg_, q, this->bpp_);
689 this->rx_pos_ += need;
701 this->rx_pos_ +=
count;
719 const uint8_t *pixel = this->
hex_fg_;
720 const uint8_t *q = p;
725 this->
fill_tile_(q[0] >> 4, q[0] & 0x0F, (q[1] >> 4) + 1, (q[1] & 0x0F) + 1, pixel);
726 this->rx_pos_ += need;
732 this->rx_pos_ +=
count;
778 ESP_LOGD(TAG,
"Update: %" PRIu32
" bytes in %" PRIu32
"ms, %u hextile tiles drawn",
788 const int visible = std::min<int>(
count, this->
vis_w_ - x);
817 if (cell !=
nullptr) {
828 for (; i + 4 <=
len; i += 4) {
830 std::memcpy(&word, this->
tile_ + i,
sizeof(word));
834 for (; i <
len; i++) {
837 return hash == 0 ? 1 : hash;
844 const uint16_t col_end = std::min<uint16_t>((
x + w - 1) / 16, this->
hash_cols_ - 1);
845 const uint16_t row_end = std::min<uint16_t>((
y +
h - 1) / 16, this->
hash_rows_ - 1);
846 for (uint16_t row =
y / 16; row <= row_end; row++) {
847 for (uint16_t col =
x / 16; col <= col_end; col++) {
857 w = std::min<uint8_t>(w, this->
tile_w_ - x);
858 h = std::min<uint8_t>(
h, this->
tile_h_ - y);
859 const size_t stride =
static_cast<size_t>(this->
tile_w_) * this->
bpp_;
860 const size_t row_len =
static_cast<size_t>(w) * this->
bpp_;
861 uint8_t *first = this->
tile_ + y * stride +
x * this->
bpp_;
862 if (this->bpp_ == 1) {
863 std::memset(first, pixel[0], w);
865 for (
size_t i = 0; i < row_len; i += 2) {
867 first[i + 1] = pixel[1];
870 for (uint8_t row = 1; row <
h; row++) {
871 std::memcpy(first + row * stride, first, row_len);
883 this->
bpp_ = rgb565 ? 2 : 1;
890 if (this->
fb_w_ != disp_w || this->
fb_h_ != disp_h) {
891 ESP_LOGW(TAG,
"Server screen is %ux%u but the display is %dx%d; showing the top-left %ux%u", this->
fb_w_,
894 const uint16_t cols = (this->
vis_w_ + 15) / 16;
895 const uint16_t rows = (this->
vis_h_ + 15) / 16;
903 ESP_LOGW(TAG,
"Screen has %u tiles, more than the %u cached; every tile is drawn", cols * rows,
914 uint8_t pixel_format[20] = {};
915 pixel_format[4] = rgb565 ? 16 : 8;
916 pixel_format[5] = rgb565 ? 16 : 8;
919 write_be16(pixel_format + 8, rgb565 ? 31 : 7);
920 write_be16(pixel_format + 10, rgb565 ? 63 : 7);
921 write_be16(pixel_format + 12, rgb565 ? 31 : 3);
922 pixel_format[14] = rgb565 ? 11 : 5;
923 pixel_format[15] = rgb565 ? 5 : 2;
924 if (!this->
send_(pixel_format,
sizeof(pixel_format))) {
929 uint8_t encodings[4 + 4 * std::size(ENCODINGS)] = {2};
930 write_be16(encodings + 2, std::size(ENCODINGS));
931 for (
size_t i = 0; i < std::size(ENCODINGS); i++) {
932 write_be32(encodings + 4 + 4 * i,
static_cast<uint32_t>(ENCODINGS[i]));
976 uint8_t msg[10] = {3,
static_cast<uint8_t
>(incremental ? 1 : 0)};
977 write_be16(msg + 6, this->
vis_w_);
978 write_be16(msg + 8, this->
vis_h_);
979 if (!this->
send_(msg,
sizeof(msg))) {
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
bool has_overridden_loop() const
void status_set_warning()
void status_clear_error()
bool status_has_warning() const
void disable_loop()
Disable this component's loop.
void status_clear_warning()
bool is_idle() const
Check if this component is idle.
void stop()
Stop running the loop continuously.
void start()
Start running the loop continuously.
An STL allocator that uses SPI or internal RAM.
virtual int get_height()
Get the calculated height of the display in pixels with rotation applied.
virtual DisplayRotation get_rotation() const
virtual int get_width()
Get the calculated width of the display in pixels with rotation applied.
virtual DisplayType get_display_type()=0
Get the type of display that the buffer corresponds to.
void draw_pixels_at(int x_start, int y_start, int w, int h, const uint8_t *ptr, ColorBitness bitness, bool big_endian, int x_offset, int y_offset, int x_pad)
Given an array of pixels encoded in the nominated format, draw these into the display's buffer.
const char * host() const
bool flush_tx()
Send the front of the buffer; true once it is empty.
uint32_t reconnect_interval() const
void close()
Close without scheduling a reconnect (shutdown).
ssize_t read(uint8_t *buf, size_t len)
Returns bytes moved, 0 when nothing can move now, -1 when the link dropped.
void begin(const char *tag)
Call from setup(). tag names this link's log lines.
void note_attempt()
Shared retry clock, also usable for a listen socket.
bool connecting() const
A connect attempt is in flight.
size_t queue(const uint8_t *data, size_t len)
Copy into the outgoing buffer; returns how many bytes fit.
void poll()
Connect state machine; call every loop while acting as a client.
LazyCallbackManager< void()> disconnect_callback_
VncFailure lost_failure_() const
uint8_t tile_[TILE_BUFFER_SIZE]
uint32_t update_start_bytes_
uint32_t first_change_ms_
LazyCallbackManager< void()> connect_callback_
void session_lost_(uint32_t now)
uint32_t max_update_delay_ms_
void forget_tiles_(uint16_t x, uint16_t y, uint16_t w, uint16_t h)
VncParse fail_(VncFailure failure, const LogString *reason)
uint32_t update_start_ms_
display::ColorBitness bitness_
display::Display * display_
socket::TcpClientLink link_
void draw_row_(uint16_t x, uint16_t y, uint16_t count, const uint8_t *pixels)
void dump_config() override
bool send_(const uint8_t *data, size_t len)
bool start_session_(uint32_t now)
void refresh()
Ask the server for the whole screen again.
uint32_t session_start_ms_
HighFrequencyLoopRequester high_freq_
uint32_t last_request_ms_
void service_rx_(uint32_t now)
uint32_t tile_hash_() const
bool request_outstanding_
void start_handshake_(uint32_t now)
static constexpr size_t RX_BUFFER_SIZE
void session_end_(uint32_t now, VncFailure failure, const LogString *reason)
void connect_step_(uint32_t now)
VncPixelFormat pixel_format_
VncFailure reason_failure_
bool budget_exhausted_() const
void on_shutdown() override
bool send_update_request_(bool incremental, uint32_t now)
void skip_(uint32_t count, VncStep next)
uint8_t rx_[RX_BUFFER_SIZE]
uint32_t update_delay_ms_
void fill_tile_(uint8_t x, uint8_t y, uint8_t w, uint8_t h, const uint8_t *pixel)
ESPHOME_ALWAYS_INLINE bool is_connected()
Return whether the node is connected to the network (through wifi, eth, ...)
value[LAYOUT.day_index()]
ESPHOME_LOG_TAG(TAG, "vnc")
@ VNC_PIXEL_FORMAT_RGB565
@ VNC_PIXEL_FORMAT_RGB332
@ VNC_STEP_SECURITY_COUNT
@ VNC_STEP_SECURITY_TYPE_33
@ VNC_STEP_SECURITY_RESULT
PROGMEM_STRING_TABLE(PixelFormatStrings, "auto", "RGB565", "RGB332")
constexpr uint32_t fnv1a_hash_extend(uint32_t hash, const char *str)
Extend a FNV-1a hash with additional string data.
constexpr uint32_t FNV1_OFFSET_BASIS
FNV-1 32-bit offset basis.
constexpr uint32_t encode_uint32(uint8_t byte1, uint8_t byte2, uint8_t byte3, uint8_t byte4)
Encode a 32-bit value given four bytes in most to least significant byte order.
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
uint32_t IRAM_ATTR HOT millis()
Application App
Global storage of Application pointer - only one Application can exist.