Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# 변경 이력

## Home Assistant 통합 0.6.16 / 펌웨어 0.4.1 — 2026-11-12

- 펌웨어: 등록되지 않은 기기가 BLE로 접속했다가 거부된 뒤 광고가 다시 시작되지 않던 문제를 고쳤습니다. 0.4.0은 이 상태가 되면 재부팅 전까지 iPhone이 다시 연결할 수 없었습니다.
- 펌웨어: 광고 주기를 Apple 액세서리 권장값으로 바꿨습니다. 부팅, 연결 끊김, 등록 요청 뒤 30초 동안은 20 ms, 그 뒤에는 152.5 ms로 광고합니다(0.4.0은 160 ms 고정).
- 펌웨어: 연결이 없는 동안 60초마다 광고를 다시 설정해, 광고 상태가 어긋나도 스스로 복구합니다.
- 펌웨어: 인증이 끝나면 연결 간격 30~45 ms, 감시 타임아웃 6초를 iPhone에 요청합니다. iPhone이 받아들이면 신호가 잠깐 약해져도 연결이 덜 끊깁니다.
- 설정 AP, Bluetooth PIN `123456`, 등록 창, MQTT 토픽과 Discovery, Home Assistant 엔티티, 저장된 설정 형식, 파티션 표는 바꾸지 않았습니다. 통합(HACS) 코드는 버전만 올렸습니다.
- 실제 보드에서는 시험하지 않았습니다. 문제가 있으면 v0.6.15 릴리스의 펌웨어 0.4.0 이미지로 되돌릴 수 있습니다.

## Home Assistant 통합 0.6.15 / 펌웨어 0.4.0 — 2026-10-30

- 펌웨어: MQTT 서버에 한 번 연결된 설정은 기억해 둡니다(설정 파티션의 `mqtt_ok`). 그 뒤 브로커가 꺼지거나 재시작하거나 네트워크가 끊기면 설정 AP를 열지 않고 5초, 15초, 30초, 이후 60초 간격으로 계속 다시 연결합니다. 0.3.9는 이때 설정 AP를 바로 열어, 브로커가 재시작하는 동안 근처 누구나 접속해 설정을 바꿀 수 있었습니다.
Expand Down
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ cmake_minimum_required(VERSION 3.16)
set(MINIMAL_BUILD ON)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
set(PROJECT_VER "0.4.0")
set(PROJECT_VER "0.4.1")
project(ios_ancs_capture_c6)
24 changes: 12 additions & 12 deletions README.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,21 +45,21 @@ From firmware 0.4.0 the relay remembers MQTT settings once they have connected.
- Changing the host or port of settings that connected before requires entering the MQTT password again.
- Devices updated from 0.3.9 follow these rules after their first successful MQTT connection on 0.4.0.

## Supported Boards And v0.4.0 Build Facts
## Supported Boards And v0.4.1 Build Facts

The shared firmware uses a 4 MB minimum flash layout. All v0.4.0 images shown in the installer are compile, link, partition, and merged-image verified. ESP32-S2 is excluded because it has no BLE. ESP32-H2 is excluded because it has no Wi-Fi, and ESP32-P4 has no integrated Wi-Fi/BLE radio.
The shared firmware uses a 4 MB minimum flash layout. All v0.4.1 images shown in the installer are compile, link, partition, and merged-image verified. ESP32-S2 is excluded because it has no BLE. ESP32-H2 is excluded because it has no Wi-Fi, and ESP32-P4 has no integrated Wi-Fi/BLE radio.

| Target | Typical module/board | Factory image | v0.4.0 status |
| Target | Typical module/board | Factory image | v0.4.1 status |
| --- | --- | ---: | --- |
| `esp32` | ESP32-WROOM-32 / WROOM-D32 | 1,451,936 bytes | Build verified; this version not hardware validated |
| `esp32c2` | ESP32-C2 | 1,474,224 bytes | Build verified; this version not hardware validated |
| `esp32c3` | ESP32-C3 | 1,663,232 bytes | Build verified; this version not hardware validated |
| `esp32c5` | ESP32-C5 | 1,808,480 bytes | Build verified; this version not hardware validated |
| `esp32c6` | ESP32-C6 | 1,809,136 bytes | Build verified; this version not hardware validated |
| `esp32c61` | ESP32-C61 | 1,751,520 bytes | Build verified; this version not hardware validated |
| `esp32s3` | ESP32-S3 | 1,434,416 bytes | Build verified; this version not hardware validated |

The table describes the published v0.4.0 images. Build verification, physical flashing, BLE enrollment, and live iPhone notification capture are separate validation scopes; boards without physical-device evidence are labeled as build verified only.
| `esp32` | ESP32-WROOM-32 / WROOM-D32 | 1,452,672 bytes | Build verified; this version not hardware validated |
| `esp32c2` | ESP32-C2 | 1,475,008 bytes | Build verified; this version not hardware validated |
| `esp32c3` | ESP32-C3 | 1,664,016 bytes | Build verified; this version not hardware validated |
| `esp32c5` | ESP32-C5 | 1,809,264 bytes | Build verified; this version not hardware validated |
| `esp32c6` | ESP32-C6 | 1,809,920 bytes | Build verified; this version not hardware validated |
| `esp32c61` | ESP32-C61 | 1,752,304 bytes | Build verified; this version not hardware validated |
| `esp32s3` | ESP32-S3 | 1,435,184 bytes | Build verified; this version not hardware validated |

The table describes the published v0.4.1 images. Build verification, physical flashing, BLE enrollment, and live iPhone notification capture are separate validation scopes; boards without physical-device evidence are labeled as build verified only.

## Home Assistant And HACS

Expand Down
22 changes: 11 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,21 +71,21 @@ BOOT 버튼을 3초 눌러 설정 AP 또는 iPhone 등록 창을 엽니다. 저

설정 포털의 **iPhone 기기 등록** 버튼, Home Assistant의 **iPhone 등록 시작** 버튼 또는 BOOT 버튼 3초 누르기로 120초 등록 창을 엽니다. 설정 포털에 표시되는 고정 PIN `123456`를 iOS Bluetooth 설정에 입력하고 알림 공유를 허용합니다. 저장된 기존 iPhone 페어링 정보가 있으면 같은 동작은 기존 iPhone 재연결만 요청합니다.

## 지원 보드와 v0.4.0 빌드 사실
## 지원 보드와 v0.4.1 빌드 사실

공통 펌웨어는 최소 4 MB 플래시 레이아웃을 사용합니다. ESP32-S2는 BLE가 없고, ESP32-H2는 Wi-Fi가 없으며, ESP32-P4는 내장 Wi-Fi/BLE 라디오가 없어 제외됩니다.

| Target | 일반 모듈/보드 | Factory 이미지 | v0.4.0 상태 |
| Target | 일반 모듈/보드 | Factory 이미지 | v0.4.1 상태 |
| --- | --- | ---: | --- |
| `esp32` | ESP32-WROOM-32 / WROOM-D32 | 1,451,936 bytes | 빌드 검증 · 이 버전 실기기 미검증 |
| `esp32c2` | ESP32-C2 | 1,474,224 bytes | 빌드 검증 · 이 버전 실기기 미검증 |
| `esp32c3` | ESP32-C3 | 1,663,232 bytes | 빌드 검증 · 이 버전 실기기 미검증 |
| `esp32c5` | ESP32-C5 | 1,808,480 bytes | 빌드 검증 · 이 버전 실기기 미검증 |
| `esp32c6` | ESP32-C6 | 1,809,136 bytes | 빌드 검증 · 이 버전 실기기 미검증 |
| `esp32c61` | ESP32-C61 | 1,751,520 bytes | 빌드 검증 · 이 버전 실기기 미검증 |
| `esp32s3` | ESP32-S3 | 1,434,416 bytes | 빌드 검증 · 이 버전 실기기 미검증 |

표의 상태는 공개된 v0.4.0 이미지 기준입니다. 빌드 검증, 실제 보드 플래시, BLE 등록, iPhone 알림 수신은 서로 다른 검증 범위이며 보드별 실기기 결과가 없는 항목은 빌드 검증으로만 표시합니다.
| `esp32` | ESP32-WROOM-32 / WROOM-D32 | 1,452,672 bytes | 빌드 검증 · 이 버전 실기기 미검증 |
| `esp32c2` | ESP32-C2 | 1,475,008 bytes | 빌드 검증 · 이 버전 실기기 미검증 |
| `esp32c3` | ESP32-C3 | 1,664,016 bytes | 빌드 검증 · 이 버전 실기기 미검증 |
| `esp32c5` | ESP32-C5 | 1,809,264 bytes | 빌드 검증 · 이 버전 실기기 미검증 |
| `esp32c6` | ESP32-C6 | 1,809,920 bytes | 빌드 검증 · 이 버전 실기기 미검증 |
| `esp32c61` | ESP32-C61 | 1,752,304 bytes | 빌드 검증 · 이 버전 실기기 미검증 |
| `esp32s3` | ESP32-S3 | 1,435,184 bytes | 빌드 검증 · 이 버전 실기기 미검증 |

표의 상태는 공개된 v0.4.1 이미지 기준입니다. 빌드 검증, 실제 보드 플래시, BLE 등록, iPhone 알림 수신은 서로 다른 검증 범위이며 보드별 실기기 결과가 없는 항목은 빌드 검증으로만 표시합니다.

## Home Assistant와 HACS

Expand Down
2 changes: 1 addition & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## 지원 버전

보안 수정은 최신 GitHub 릴리스에만 제공합니다. 릴리스마다 Home Assistant 통합 버전과, 함께 배포하는 ESP32 펌웨어 버전이 제목에 있습니다(예: `iOS ANCS 0.6.15 / Firmware 0.4.0`).
보안 수정은 최신 GitHub 릴리스에만 제공합니다. 릴리스마다 Home Assistant 통합 버전과, 함께 배포하는 ESP32 펌웨어 버전이 제목에 있습니다(예: `iOS ANCS 0.6.16 / Firmware 0.4.1`).

| 구성 요소 | 지원 |
| --- | --- |
Expand Down
83 changes: 83 additions & 0 deletions components/ancs_client/ancs_client.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,16 @@
#define SCAN_RESPONSE_PENDING (1U << 1)
#define CONTROL_REQUEST_MAX (CONFIG_ANCS_APP_ID_MAX + 3U)
#define ADVERTISING_BACKOFF_MAX_MS 4000U
/* Apple Accessory Design Guidelines: 20 ms for the first 30 s, then one of the
* recommended slow intervals (152.5 ms) so iOS background scanning finds us. */
#define ADVERTISING_FAST_INTERVAL_UNITS 0x0020U /* 20 ms */
#define ADVERTISING_SLOW_INTERVAL_UNITS 0x00F4U /* 152.5 ms */
#define ADVERTISING_FAST_WINDOW_MS INT64_C(30000)
#define ADVERTISING_REFRESH_MS INT64_C(60000)
/* 30-45 ms interval, no latency, 6 s supervision timeout (Apple maximum). */
#define CONNECTION_INTERVAL_MIN_UNITS 0x0018U
#define CONNECTION_INTERVAL_MAX_UNITS 0x0024U
#define CONNECTION_SUPERVISION_TIMEOUT_UNITS 600U
#define DISCOVERY_BACKOFF_MAX_MS 5000U
#define AUTH_FAILURE_REPAIR_THRESHOLD 3U
#define AUTH_FAILURE_WINDOW_MS 120000LL
Expand Down Expand Up @@ -155,6 +165,8 @@ typedef struct {
uint8_t advertising_attempt;
uint8_t discovery_attempt;
uint8_t adv_config_pending;
int64_t advertising_fast_until_ms;
int64_t advertising_refresh_due_ms;

QueueHandle_t work_queue;
TaskHandle_t worker_task;
Expand Down Expand Up @@ -227,6 +239,9 @@ static esp_ble_adv_params_t s_adv_params = {

static void start_advertising(void);
static void stop_advertising(void);
static void begin_fast_advertising_window(void);
static void note_advertising_stopped_by_controller(void);
static void check_advertising_refresh(void);
static void start_discovery(void);
static int sync_existing_ble_bond(void);
static esp_err_t open_replacement_window(void);
Expand Down Expand Up @@ -897,6 +912,7 @@ static void worker_task(void *argument)

for (;;) {
check_connection_deadline();
check_advertising_refresh();
enroll_timeout_callback(NULL); /* Also covers lost/old timer wakeups. */
const uint32_t generation = current_generation();
if (s_worker.generation != generation) {
Expand Down Expand Up @@ -1118,6 +1134,15 @@ static void start_advertising(void)
return;
}
s_client.advertising_starting = true;
const int64_t now = now_ms();
const bool fast = now < s_client.advertising_fast_until_ms;
const uint16_t interval = fast ? ADVERTISING_FAST_INTERVAL_UNITS
: ADVERTISING_SLOW_INTERVAL_UNITS;
s_adv_params.adv_int_min = interval;
s_adv_params.adv_int_max = interval;
/* Re-arm periodically: switches fast->slow and heals lost stack state. */
s_client.advertising_refresh_due_ms =
fast ? s_client.advertising_fast_until_ms : now + ADVERTISING_REFRESH_MS;
taskEXIT_CRITICAL(&s_shared_state_lock);
const esp_err_t error = esp_ble_gap_start_advertising(&s_adv_params);
if (error != ESP_OK) {
Expand All @@ -1141,6 +1166,44 @@ static void stop_advertising(void)
}
}

static void begin_fast_advertising_window(void)
{
taskENTER_CRITICAL(&s_shared_state_lock);
s_client.advertising_fast_until_ms = now_ms() + ADVERTISING_FAST_WINDOW_MS;
taskEXIT_CRITICAL(&s_shared_state_lock);
}

/* The controller stops legacy advertising on every incoming connection, even
* one we reject. Forget the stale flags so advertising can be started again. */
static void note_advertising_stopped_by_controller(void)
{
taskENTER_CRITICAL(&s_shared_state_lock);
s_client.advertising = false;
s_client.advertising_starting = false;
taskEXIT_CRITICAL(&s_shared_state_lock);
}

static void check_advertising_refresh(void)
{
const int64_t now = now_ms();
taskENTER_CRITICAL(&s_shared_state_lock);
const bool wanted = !s_client.connected &&
s_client.adv_config_pending == 0U &&
ble_enroll_should_advertise(&s_enroll, now);
const bool armed = s_client.advertising || s_client.advertising_starting;
const bool due = wanted && armed &&
s_client.advertising_refresh_due_ms != 0 &&
now >= s_client.advertising_refresh_due_ms;
if (due) {
s_client.advertising_refresh_due_ms = 0;
}
taskEXIT_CRITICAL(&s_shared_state_lock);
if (due) {
stop_advertising();
start_advertising();
}
}

static void maybe_start_discovery(void)
{
start_discovery();
Expand Down Expand Up @@ -1559,6 +1622,18 @@ static void gap_event_handler(esp_gap_ble_cb_event_t event,
taskEXIT_CRITICAL(&s_shared_state_lock);
(void)esp_timer_stop(s_client.enroll_timer);
stop_advertising();
esp_ble_conn_update_params_t connection_params = {
.min_int = CONNECTION_INTERVAL_MIN_UNITS,
.max_int = CONNECTION_INTERVAL_MAX_UNITS,
.latency = 0,
.timeout = CONNECTION_SUPERVISION_TIMEOUT_UNITS,
};
memcpy(connection_params.bda,
parameter->ble_security.auth_cmpl.bd_addr,
sizeof(esp_bd_addr_t));
if (esp_ble_gap_update_conn_params(&connection_params) != ESP_OK) {
ESP_LOGW(TAG, "connection parameter update request failed");
}
transition(ANCS_SIGNAL_BONDED);
ESP_LOGI(TAG,
"encryption and bonding complete auth_mode=0x%x addr_type=%u",
Expand Down Expand Up @@ -1680,13 +1755,16 @@ static void handle_gattc_event(esp_gattc_cb_event_t event,
s_client.gattc_if = gattc_if;
taskEXIT_CRITICAL(&s_shared_state_lock);
ESP_ERROR_CHECK(esp_ble_gap_set_device_name(s_client.device_name));
begin_fast_advertising_window();
ESP_ERROR_CHECK(esp_ble_gap_config_local_privacy(true));
break;

case ESP_GATTC_CONNECT_EVT: {
if (!connection_peer_allowed(parameter->connect.remote_bda)) {
ESP_LOGW(TAG, "rejected connection from unauthorized peer");
(void)esp_ble_gap_disconnect(parameter->connect.remote_bda);
note_advertising_stopped_by_controller();
schedule_advertising_retry();
break;
}
taskENTER_CRITICAL(&s_shared_state_lock);
Expand Down Expand Up @@ -2062,6 +2140,8 @@ static void handle_gattc_event(esp_gattc_cb_event_t event,
parameter->disconnect.conn_id,
parameter->disconnect.remote_bda)) {
ESP_LOGW(TAG, "ignored disconnect event from stale peer");
/* A rejected peer never became the session; resume advertising. */
schedule_advertising_retry();
break;
}
ESP_LOGW(TAG,
Expand All @@ -2071,6 +2151,7 @@ static void handle_gattc_event(esp_gattc_cb_event_t event,
s_client.last_disconnect_reason = parameter->disconnect.reason;
taskEXIT_CRITICAL(&s_shared_state_lock);
reset_connection_session();
begin_fast_advertising_window();
transition(ANCS_SIGNAL_DISCONNECTED);
taskENTER_CRITICAL(&s_shared_state_lock);
const bool reopen = s_client.reopen_after_disconnect;
Expand Down Expand Up @@ -2539,6 +2620,7 @@ esp_err_t ancs_client_request_enroll(void)
taskENTER_CRITICAL(&s_shared_state_lock);
ble_enroll_close_window(&s_enroll);
taskEXIT_CRITICAL(&s_shared_state_lock);
begin_fast_advertising_window();
start_advertising();
return ESP_OK;
}
Expand Down Expand Up @@ -2566,6 +2648,7 @@ esp_err_t ancs_client_request_enroll(void)
taskEXIT_CRITICAL(&s_shared_state_lock);
return timer_error;
}
begin_fast_advertising_window();
start_advertising();
return ESP_OK;
}
Expand Down
2 changes: 1 addition & 1 deletion custom_components/ha_ios_ancs/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,5 @@
"iot_class": "local_push",
"issue_tracker": "https://github.com/1bobby-git/HA-iOS-ANCS/issues",
"requirements": [],
"version": "0.6.15"
"version": "0.6.16"
}
40 changes: 20 additions & 20 deletions docs/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,65 +3,65 @@ const boards = {
chip: "32",
name: "ESP32 / WROOM-32",
boot: "GPIO 0",
validation: "v0.4.0 build verified",
validation: "v0.4.1 build verified",
status: "BUILD VERIFIED",
description: "ESP32/WROOM-32 계열용 v0.4.0 ANCS-to-MQTT factory build입니다.",
hash: "SHA256 A2F1AA9088ED",
description: "ESP32/WROOM-32 계열용 v0.4.1 ANCS-to-MQTT factory build입니다.",
hash: "SHA256 F86B0A9065E9",
},
esp32c2: {
chip: "C2",
name: "ESP32-C2",
boot: "GPIO 9",
validation: "v0.4.0 build verified",
validation: "v0.4.1 build verified",
status: "BUILD VERIFIED",
description: "ESP32-C2 계열용 v0.4.0 ANCS-to-MQTT factory build입니다.",
hash: "SHA256 D2899E6C9E4D",
description: "ESP32-C2 계열용 v0.4.1 ANCS-to-MQTT factory build입니다.",
hash: "SHA256 732C3CAB0875",
},
esp32c3: {
chip: "C3",
name: "ESP32-C3",
boot: "GPIO 9",
validation: "v0.4.0 build verified",
validation: "v0.4.1 build verified",
status: "BUILD VERIFIED",
description: "ESP32-C3 계열용 v0.4.0 ANCS-to-MQTT factory build입니다.",
hash: "SHA256 D768DDE94A94",
description: "ESP32-C3 계열용 v0.4.1 ANCS-to-MQTT factory build입니다.",
hash: "SHA256 6FB59E05B67E",
},
esp32c5: {
chip: "C5",
name: "ESP32-C5",
boot: "GPIO 28",
validation: "v0.4.0 build verified",
validation: "v0.4.1 build verified",
status: "BUILD VERIFIED",
description: "ESP32-C5 계열용 v0.4.0 ANCS-to-MQTT factory build입니다.",
hash: "SHA256 0C481C3C416C",
description: "ESP32-C5 계열용 v0.4.1 ANCS-to-MQTT factory build입니다.",
hash: "SHA256 59C68304980B",
},
esp32c6: {
chip: "C6",
name: "ESP32-C6",
boot: "GPIO 9",
validation: "v0.4.0 build verified",
validation: "v0.4.1 build verified",
status: "BUILD VERIFIED",
description: "ESP32-C6 계열용 v0.4.0 ANCS-to-MQTT factory build입니다.",
hash: "SHA256 334472B2C151",
description: "ESP32-C6 계열용 v0.4.1 ANCS-to-MQTT factory build입니다.",
hash: "SHA256 A09177FD031D",
},
esp32c61: {
chip: "C61",
name: "ESP32-C61",
boot: "GPIO 9",
validation: "v0.4.0 build verified",
validation: "v0.4.1 build verified",
status: "BUILD VERIFIED",
description:
"ESP32-C61은 ESP32-C6의 리비전이 아니라 별도 최신 칩입니다. 실제 보드 각인이 C61인 경우에 선택하세요.",
hash: "SHA256 687ECDADCB18",
hash: "SHA256 7B4E1CDB986F",
},
esp32s3: {
chip: "S3",
name: "ESP32-S3",
boot: "GPIO 0",
validation: "v0.4.0 build verified",
validation: "v0.4.1 build verified",
status: "BUILD VERIFIED",
description: "ESP32-S3 계열용 v0.4.0 ANCS-to-MQTT factory build입니다.",
hash: "SHA256 6D165A01729B",
description: "ESP32-S3 계열용 v0.4.1 ANCS-to-MQTT factory build입니다.",
hash: "SHA256 E9E8F72B4EA7",
},
};

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading
Loading