9#include "lwip/ip_addr.h"
18#include <wlan_ui_pub.h>
25#include <wifi_structures.h>
37static const char *
const TAG =
"wifi_lt";
52static constexpr size_t EVENT_QUEUE_SIZE = 16;
53static QueueHandle_t s_event_queue =
nullptr;
54static volatile uint32_t s_event_queue_overflow_count =
60 arduino_event_id_t event_id;
78 } sta_authmode_change;
103static uint8_t s_ignored_disconnect_count = 0;
108static constexpr uint8_t IGNORED_DISCONNECT_THRESHOLD = 3;
111 uint8_t current_mode = WiFi.getMode();
112 bool current_sta = current_mode & 0b01;
113 bool current_ap = current_mode & 0b10;
114 bool enable_sta = sta.
value_or(current_sta);
115 bool enable_ap = ap.
value_or(current_ap);
116 if (current_sta == enable_sta && current_ap == enable_ap)
119 if (enable_sta && !current_sta) {
120 ESP_LOGV(TAG,
"Enabling STA");
121 }
else if (!enable_sta && current_sta) {
122 ESP_LOGV(TAG,
"Disabling STA");
124 if (enable_ap && !current_ap) {
125 ESP_LOGV(TAG,
"Enabling AP");
126 }
else if (!enable_ap && current_ap) {
127 ESP_LOGV(TAG,
"Disabling AP");
135 bool ret = WiFi.mode(
static_cast<wifi_mode_t
>(
mode));
138 ESP_LOGW(TAG,
"Setting mode failed");
147 int8_t
val =
static_cast<int8_t
>(output_power * 4);
148 return WiFi.setTxPower(
val);
154 WiFi.setAutoReconnect(
false);
160#ifdef USE_WIFI_POWER_SAVE_LISTENERS
174 if (!manual_ip.has_value()) {
178 WiFi.config(manual_ip->static_ip, manual_ip->gateway, manual_ip->subnet, manual_ip->dns1, manual_ip->dns2);
187 addresses[0] = WiFi.localIP();
190 auto v6_addresses = WiFi.allLocalIPv6();
191 for (
auto address : v6_addresses) {
192 addresses[i++] = network::IPAddress(
address.toString().c_str());
208 String ssid = WiFi.SSID();
209 if (ssid && strcmp(ssid.c_str(), ap.ssid_.c_str()) != 0) {
213#ifdef USE_WIFI_MANUAL_IP
227 s_ignored_disconnect_count = 0;
229 WiFiStatus
status = WiFi.begin(ap.ssid_.c_str(), ap.password_.empty() ? NULL : ap.password_.c_str(),
231 ap.has_bssid() ? ap.get_bssid().data() : NULL);
232 if (status != WL_CONNECTED) {
233 ESP_LOGW(TAG,
"esp_wifi_connect failed: %d", status);
245 case WIFI_AUTH_WPA_PSK:
247 case WIFI_AUTH_WPA2_PSK:
249 case WIFI_AUTH_WPA_WPA2_PSK:
250 return "WPA/WPA2 PSK";
272 case WIFI_REASON_AUTH_EXPIRE:
273 return "Auth Expired";
274 case WIFI_REASON_AUTH_LEAVE:
276 case WIFI_REASON_ASSOC_EXPIRE:
277 return "Association Expired";
278 case WIFI_REASON_ASSOC_TOOMANY:
279 return "Too Many Associations";
280 case WIFI_REASON_NOT_AUTHED:
281 return "Not Authenticated";
282 case WIFI_REASON_NOT_ASSOCED:
283 return "Not Associated";
284 case WIFI_REASON_ASSOC_LEAVE:
285 return "Association Leave";
286 case WIFI_REASON_ASSOC_NOT_AUTHED:
287 return "Association not Authenticated";
288 case WIFI_REASON_DISASSOC_PWRCAP_BAD:
289 return "Disassociate Power Cap Bad";
290 case WIFI_REASON_DISASSOC_SUPCHAN_BAD:
291 return "Disassociate Supported Channel Bad";
292 case WIFI_REASON_IE_INVALID:
294 case WIFI_REASON_MIC_FAILURE:
295 return "Mic Failure";
296 case WIFI_REASON_4WAY_HANDSHAKE_TIMEOUT:
297 return "4-Way Handshake Timeout";
298 case WIFI_REASON_GROUP_KEY_UPDATE_TIMEOUT:
299 return "Group Key Update Timeout";
300 case WIFI_REASON_IE_IN_4WAY_DIFFERS:
301 return "IE In 4-Way Handshake Differs";
302 case WIFI_REASON_GROUP_CIPHER_INVALID:
303 return "Group Cipher Invalid";
304 case WIFI_REASON_PAIRWISE_CIPHER_INVALID:
305 return "Pairwise Cipher Invalid";
306 case WIFI_REASON_AKMP_INVALID:
307 return "AKMP Invalid";
308 case WIFI_REASON_UNSUPP_RSN_IE_VERSION:
309 return "Unsupported RSN IE version";
310 case WIFI_REASON_INVALID_RSN_IE_CAP:
311 return "Invalid RSN IE Cap";
312 case WIFI_REASON_802_1X_AUTH_FAILED:
313 return "802.1x Authentication Failed";
314 case WIFI_REASON_CIPHER_SUITE_REJECTED:
315 return "Cipher Suite Rejected";
316 case WIFI_REASON_BEACON_TIMEOUT:
317 return "Beacon Timeout";
318 case WIFI_REASON_NO_AP_FOUND:
319 return "AP Not Found";
320 case WIFI_REASON_AUTH_FAIL:
321 return "Authentication Failed";
322 case WIFI_REASON_ASSOC_FAIL:
323 return "Association Failed";
324 case WIFI_REASON_HANDSHAKE_TIMEOUT:
325 return "Handshake Failed";
326 case WIFI_REASON_CONNECTION_FAIL:
327 return "Connection Failed";
328 case WIFI_REASON_UNSPECIFIED:
330 return "Unspecified";
334#define ESPHOME_EVENT_ID_WIFI_READY ARDUINO_EVENT_WIFI_READY
335#define ESPHOME_EVENT_ID_WIFI_SCAN_DONE ARDUINO_EVENT_WIFI_SCAN_DONE
336#define ESPHOME_EVENT_ID_WIFI_STA_START ARDUINO_EVENT_WIFI_STA_START
337#define ESPHOME_EVENT_ID_WIFI_STA_STOP ARDUINO_EVENT_WIFI_STA_STOP
338#define ESPHOME_EVENT_ID_WIFI_STA_CONNECTED ARDUINO_EVENT_WIFI_STA_CONNECTED
339#define ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED ARDUINO_EVENT_WIFI_STA_DISCONNECTED
340#define ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE ARDUINO_EVENT_WIFI_STA_AUTHMODE_CHANGE
341#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP ARDUINO_EVENT_WIFI_STA_GOT_IP
342#define ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6 ARDUINO_EVENT_WIFI_STA_GOT_IP6
343#define ESPHOME_EVENT_ID_WIFI_STA_LOST_IP ARDUINO_EVENT_WIFI_STA_LOST_IP
344#define ESPHOME_EVENT_ID_WIFI_AP_START ARDUINO_EVENT_WIFI_AP_START
345#define ESPHOME_EVENT_ID_WIFI_AP_STOP ARDUINO_EVENT_WIFI_AP_STOP
346#define ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED ARDUINO_EVENT_WIFI_AP_STACONNECTED
347#define ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED ARDUINO_EVENT_WIFI_AP_STADISCONNECTED
348#define ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED ARDUINO_EVENT_WIFI_AP_STAIPASSIGNED
349#define ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED ARDUINO_EVENT_WIFI_AP_PROBEREQRECVED
350#define ESPHOME_EVENT_ID_WIFI_AP_GOT_IP6 ARDUINO_EVENT_WIFI_AP_GOT_IP6
357 if (s_event_queue ==
nullptr) {
362 auto *to_send =
new LTWiFiEvent{};
363 to_send->event_id = event;
367 case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: {
368 auto &it = info.wifi_sta_connected;
369 to_send->data.sta_connected.ssid_len = it.ssid_len;
370 memcpy(to_send->data.sta_connected.ssid, it.ssid,
371 std::min(
static_cast<size_t>(it.ssid_len),
sizeof(to_send->data.sta_connected.ssid) - 1));
372 memcpy(to_send->data.sta_connected.bssid, it.bssid, 6);
373 to_send->data.sta_connected.channel = it.channel;
374 to_send->data.sta_connected.authmode = it.authmode;
377 case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: {
378 auto &it = info.wifi_sta_disconnected;
379 to_send->data.sta_disconnected.ssid_len = it.ssid_len;
380 memcpy(to_send->data.sta_disconnected.ssid, it.ssid,
381 std::min(
static_cast<size_t>(it.ssid_len),
sizeof(to_send->data.sta_disconnected.ssid) - 1));
382 memcpy(to_send->data.sta_disconnected.bssid, it.bssid, 6);
383 to_send->data.sta_disconnected.reason = it.reason;
386 case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: {
387 auto &it = info.wifi_sta_authmode_change;
388 to_send->data.sta_authmode_change.old_mode = it.old_mode;
389 to_send->data.sta_authmode_change.new_mode = it.new_mode;
392 case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: {
393 auto &it = info.wifi_scan_done;
394 to_send->data.scan_done.status = it.status;
395 to_send->data.scan_done.number = it.number;
396 to_send->data.scan_done.scan_id = it.scan_id;
399 case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
400 auto &it = info.wifi_ap_probereqrecved;
401 memcpy(to_send->data.ap_probe_req.mac, it.mac, 6);
402 to_send->data.ap_probe_req.rssi = it.rssi;
405 case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
406 auto &it = info.wifi_sta_connected;
407 memcpy(to_send->data.sta_connected.bssid, it.bssid, 6);
410 case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
411 auto &it = info.wifi_sta_disconnected;
412 memcpy(to_send->data.sta_disconnected.bssid, it.bssid, 6);
415 case ESPHOME_EVENT_ID_WIFI_READY:
416 case ESPHOME_EVENT_ID_WIFI_STA_START:
417 case ESPHOME_EVENT_ID_WIFI_STA_STOP:
418 case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP:
419 case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6:
420 case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP:
421 case ESPHOME_EVENT_ID_WIFI_AP_START:
422 case ESPHOME_EVENT_ID_WIFI_AP_STOP:
423 case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED:
433 if (xQueueSend(s_event_queue, &to_send, 0) != pdPASS) {
435 s_event_queue_overflow_count++;
444 switch (event->event_id) {
445 case ESPHOME_EVENT_ID_WIFI_READY: {
446 ESP_LOGV(TAG,
"Ready");
449 case ESPHOME_EVENT_ID_WIFI_SCAN_DONE: {
450 auto &it =
event->data.scan_done;
451 ESP_LOGV(TAG,
"Scan done: status=%" PRIu32
" number=%u scan_id=%u", it.status, it.number, it.scan_id);
455 case ESPHOME_EVENT_ID_WIFI_STA_START: {
456 ESP_LOGV(TAG,
"STA start");
460 case ESPHOME_EVENT_ID_WIFI_STA_STOP: {
461 ESP_LOGV(TAG,
"STA stop");
465 case ESPHOME_EVENT_ID_WIFI_STA_CONNECTED: {
466 auto &it =
event->data.sta_connected;
467 char bssid_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
469 ESP_LOGV(TAG,
"Connected ssid='%.*s' bssid=" LOG_SECRET(
"%s")
" channel=%u, authmode=%s", it.ssid_len,
474#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
477 this->
pending_.connect_state =
true;
480#ifdef USE_WIFI_MANUAL_IP
483#ifdef USE_WIFI_IP_STATE_LISTENERS
490 case ESPHOME_EVENT_ID_WIFI_STA_DISCONNECTED: {
491 auto &it =
event->data.sta_disconnected;
505 s_ignored_disconnect_count++;
506 if (s_ignored_disconnect_count >= IGNORED_DISCONNECT_THRESHOLD) {
507 ESP_LOGW(TAG,
"Too many disconnect events (%u) while connecting, treating as failure (reason=%s)",
514 ESP_LOGV(TAG,
"Ignoring disconnect event with empty ssid while connecting (reason=%s, count=%u)",
520 if (it.reason == WIFI_REASON_NO_AP_FOUND) {
521 ESP_LOGW(TAG,
"Disconnected ssid='%.*s' reason='Probe Request Unsuccessful'", it.ssid_len,
522 (
const char *) it.ssid);
525 char bssid_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
527 ESP_LOGW(TAG,
"Disconnected ssid='%.*s' bssid=" LOG_SECRET(
"%s")
" reason='%s'", it.ssid_len,
532 uint8_t reason = it.reason;
533 if (reason == WIFI_REASON_AUTH_EXPIRE || reason == WIFI_REASON_BEACON_TIMEOUT ||
534 reason == WIFI_REASON_NO_AP_FOUND || reason == WIFI_REASON_ASSOC_FAIL ||
535 reason == WIFI_REASON_HANDSHAKE_TIMEOUT) {
540#ifdef USE_WIFI_CONNECT_STATE_LISTENERS
545 case ESPHOME_EVENT_ID_WIFI_STA_AUTHMODE_CHANGE: {
546 auto &it =
event->data.sta_authmode_change;
550 if (it.old_mode != WIFI_AUTH_OPEN && it.new_mode == WIFI_AUTH_OPEN) {
551 ESP_LOGW(TAG,
"Potential Authmode downgrade detected, disconnecting");
558 case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP: {
559 char ip_buf[network::IP_ADDRESS_BUFFER_SIZE], gw_buf[network::IP_ADDRESS_BUFFER_SIZE];
560 ESP_LOGV(TAG,
"static_ip=%s gateway=%s",
network::IPAddress(WiFi.localIP()).str_to(ip_buf),
563#ifdef USE_WIFI_IP_STATE_LISTENERS
568 case ESPHOME_EVENT_ID_WIFI_STA_GOT_IP6: {
569 ESP_LOGV(TAG,
"Got IPv6");
570#ifdef USE_WIFI_IP_STATE_LISTENERS
575 case ESPHOME_EVENT_ID_WIFI_STA_LOST_IP: {
576 ESP_LOGV(TAG,
"Lost IP");
580 case ESPHOME_EVENT_ID_WIFI_AP_START: {
581 ESP_LOGV(TAG,
"AP start");
584 case ESPHOME_EVENT_ID_WIFI_AP_STOP: {
585 ESP_LOGV(TAG,
"AP stop");
588 case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: {
589#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
590 auto &it =
event->data.sta_connected;
591 char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
593 ESP_LOGV(TAG,
"AP client connected MAC=%s", mac_buf);
597 case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: {
598#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
599 auto &it =
event->data.sta_disconnected;
600 char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
602 ESP_LOGV(TAG,
"AP client disconnected MAC=%s", mac_buf);
606 case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: {
607 ESP_LOGV(TAG,
"AP client assigned IP");
610 case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: {
611#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
612 auto &it =
event->data.ap_probe_req;
613 char mac_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
615 ESP_LOGVV(TAG,
"AP receive Probe Request MAC=%s RSSI=%d", mac_buf, it.rssi);
626 s_event_queue = xQueueCreate(EVENT_QUEUE_SIZE,
sizeof(LTWiFiEvent *));
627 if (s_event_queue ==
nullptr) {
628 ESP_LOGE(TAG,
"Failed to create event queue");
640 switch (s_sta_state) {
664 int16_t err = WiFi.scanNetworks(
true,
true, passive, 200);
665 if (err != WIFI_SCAN_RUNNING) {
666 ESP_LOGV(TAG,
"WiFi.scanNetworks failed: %d", err);
676 int16_t num = WiFi.scanComplete();
684 auto *scan = WiFi.scan;
688 for (
int i = 0; i < num; i++) {
689 const char *ssid_cstr = scan->ap[i].ssid;
698 for (
int i = 0; i < num; i++) {
699 const char *ssid_cstr = scan->ap[i].ssid;
701 auto &ap = scan->ap[i];
702 this->
scan_result_.emplace_back(
bssid_t{ap.bssid.addr[0], ap.bssid.addr[1], ap.bssid.addr[2], ap.bssid.addr[3],
703 ap.bssid.addr[4], ap.bssid.addr[5]},
704 ssid_cstr, strlen(ssid_cstr), ap.channel, ap.rssi, ap.auth != WIFI_AUTH_OPEN,
705 ssid_cstr[0] ==
'\0');
707 auto &ap = scan->ap[i];
711 ESP_LOGV(TAG,
"Scan complete: %d found, %zu stored%s", num, this->
scan_result_.size(),
712 needs_full ?
"" :
" (filtered)");
714#ifdef USE_WIFI_SCAN_RESULTS_LISTENERS
726 return WiFi.softAPConfig(manual_ip->static_ip, manual_ip->gateway, manual_ip->subnet);
728 return WiFi.softAPConfig(IPAddress(192, 168, 4, 1), IPAddress(192, 168, 4, 1), IPAddress(255, 255, 255, 0));
737#ifdef USE_WIFI_MANUAL_IP
739 ESP_LOGV(TAG,
"wifi_ap_ip_config_ failed");
744 ESP_LOGV(TAG,
"wifi_ap_ip_config_ failed");
751 return WiFi.softAP(ap.ssid_.c_str(), ap.password_.empty() ? NULL : ap.password_.c_str(),
752 ap.has_channel() ? ap.get_channel() : 1, ap.get_hidden());
762 return WiFi.disconnect();
767 uint8_t *raw_bssid = WiFi.BSSID();
768 if (raw_bssid !=
nullptr) {
769 for (
size_t i = 0; i < bssid.size(); i++)
770 bssid[i] = raw_bssid[i];
774std::string WiFiComponent::wifi_ssid() {
return WiFi.SSID().c_str(); }
777 LinkStatusTypeDef link_status{};
778 bk_wlan_get_link_status(&link_status);
779 size_t len = strnlen(
reinterpret_cast<const char *
>(link_status.ssid), SSID_BUFFER_SIZE - 1);
780 memcpy(buffer.data(), link_status.ssid,
len);
781#elif defined(USE_RTL87XX)
782 rtw_wifi_setting_t setting{};
783 wifi_get_setting(
"wlan0", &setting);
784 size_t len = strnlen(
reinterpret_cast<const char *
>(setting.ssid), SSID_BUFFER_SIZE - 1);
785 memcpy(buffer.data(), setting.ssid,
len);
788 String ssid = WiFi.SSID();
789 size_t len = std::min(
static_cast<size_t>(ssid.length()), SSID_BUFFER_SIZE - 1);
790 memcpy(buffer.data(), ssid.c_str(),
len);
793 return buffer.data();
802 if (s_event_queue ==
nullptr) {
807 if (s_event_queue_overflow_count > 0) {
808 ESP_LOGW(TAG,
"Event queue overflow, %" PRIu32
" events dropped", s_event_queue_overflow_count);
809 s_event_queue_overflow_count = 0;
814 if (xQueueReceive(s_event_queue, &event, 0) != pdTRUE) {
BedjetMode mode
BedJet operating mode.
const std::string & get_name() const
Get the name of this Application set by pre_setup().
ESPDEPRECATED("set_retry is deprecated and will be removed in 2026.8.0. Use set_timeout or set_interval instead.", "2026.2.0") void set_retry(const std uint32_t uint8_t std::function< RetryResult(uint8_t)> && f
value_type value_or(U const &v) const
const optional< ManualIP > & get_manual_ip() const
void notify_scan_results_listeners_()
Notify scan results listeners with current scan results.
bool error_from_callback_
bool wifi_apply_power_save_()
WiFiPowerSaveMode power_save_
const WiFiAP * get_selected_sta_() const
WiFiSTAConnectStatus wifi_sta_connect_status_() const
struct esphome::wifi::WiFiComponent::@175 pending_
wifi_scan_vector_t< WiFiScanResult > scan_result_
void notify_ip_state_listeners_()
Notify IP state listeners with current addresses.
bool wifi_sta_ip_config_(const optional< ManualIP > &manual_ip)
void wifi_process_event_(IDFWiFiEvent *data)
void notify_disconnect_state_listeners_()
Notify connect state listeners of disconnection.
bool wifi_start_ap_(const WiFiAP &ap)
void log_discarded_scan_result_(const char *ssid, const uint8_t *bssid, int8_t rssi, uint8_t channel)
Log a discarded scan result at VERBOSE level (skipped during roaming scans to avoid log overflow)
int32_t get_wifi_channel()
ESPDEPRECATED("Use wifi_ssid_to() instead. Removed in 2026.9.0", "2026.3.0") std const char * wifi_ssid_to(std::span< char, SSID_BUFFER_SIZE > buffer)
Write SSID to buffer without heap allocation.
network::IPAddress wifi_subnet_mask_()
network::IPAddress wifi_soft_ap_ip()
void wifi_event_callback_(arduino_event_id_t event, arduino_event_info_t info)
network::IPAddress wifi_gateway_ip_()
network::IPAddress wifi_dns_ip_(int num)
bool wifi_apply_hostname_()
bool wifi_sta_pre_setup_()
bool matches_configured_network_(const char *ssid, const uint8_t *bssid) const
Check if network matches any configured network (for scan result filtering) Matches by SSID when conf...
bool wifi_ap_ip_config_(const optional< ManualIP > &manual_ip)
bool needs_full_scan_results_() const
Check if full scan results are needed (captive portal active, improv, listeners)
StaticVector< WiFiPowerSaveListener *, ESPHOME_WIFI_POWER_SAVE_LISTENERS > power_save_listeners_
bool wifi_apply_output_power_(float output_power)
bool wifi_sta_connect_(const WiFiAP &ap)
bool wifi_mode_(optional< bool > sta, optional< bool > ap)
network::IPAddresses wifi_sta_ip_addresses()
bool wifi_scan_start_(bool passive)
void wifi_scan_done_callback_()
std::array< IPAddress, 5 > IPAddresses
std::array< uint8_t, 6 > bssid_t
const LogString * get_auth_mode_str(uint8_t mode)
arduino_event_info_t esphome_wifi_event_info_t
const LogString * get_disconnect_reason_str(uint8_t reason)
@ ERROR_NETWORK_NOT_FOUND
arduino_event_id_t esphome_wifi_event_id_t
const LogString * get_op_mode_str(uint8_t mode)
void HOT delay(uint32_t ms)
Application App
Global storage of Application pointer - only one Application can exist.
char * format_mac_addr_upper(const uint8_t *mac, char *output)
Format MAC address as XX:XX:XX:XX:XX:XX (uppercase, colon separators)