|
1 /* |
|
2 * ESPRESSIF MIT License |
|
3 * |
|
4 * Copyright (c) 2018 <ESPRESSIF SYSTEMS (SHANGHAI) PTE LTD> |
|
5 * |
|
6 * Permission is hereby granted for use on ESPRESSIF SYSTEMS products only, in which case, |
|
7 * it is free of charge, to any person obtaining a copy of this software and associated |
|
8 * documentation files (the "Software"), to deal in the Software without restriction, including |
|
9 * without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, |
|
10 * and/or sell copies of the Software, and to permit persons to whom the Software is furnished |
|
11 * to do so, subject to the following conditions: |
|
12 * |
|
13 * The above copyright notice and this permission notice shall be included in all copies or |
|
14 * substantial portions of the Software. |
|
15 * |
|
16 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
|
17 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS |
|
18 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR |
|
19 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER |
|
20 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
|
21 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
|
22 * |
|
23 */ |
|
24 /* HomeKit Door Homekit |
|
25 */ |
|
26 #include <stdio.h> |
|
27 #include <string.h> |
|
28 #include <freertos/FreeRTOS.h> |
|
29 #include <freertos/task.h> |
|
30 #include <freertos/timers.h> |
|
31 #include <freertos/queue.h> |
|
32 #include <esp_log.h> |
|
33 #include <driver/gpio.h> |
|
34 |
|
35 #include <hap.h> |
|
36 |
|
37 #include <hap_apple_servs.h> |
|
38 #include <hap_apple_chars.h> |
|
39 |
|
40 #include <app_wifi.h> |
|
41 #include <app_hap_setup_payload.h> |
|
42 |
|
43 static const char *TAG = "HAP door"; |
|
44 |
|
45 #define DOOR_TASK_PRIORITY 1 |
|
46 #define DOOR_TASK_STACKSIZE 4 * 1024 |
|
47 #define DOOR_TASK_NAME "hap_door" |
|
48 |
|
49 #define DOOR_LOCK_GPIO_PIN GPIO_NUM_21 |
|
50 #define DOOR_BELL_GPIO_PIN GPIO_NUM_14 |
|
51 #define DOOR_LOCK_GPIO_LOCKED 0 |
|
52 #define DOOR_LOCK_GPIO_UNLOCKED 1 |
|
53 |
|
54 #define HAP_LOCK_TARGET_STATE_UNSECURED 0 |
|
55 #define HAP_LOCK_TARGET_STATE_SECURED 1 |
|
56 |
|
57 static hap_val_t HAP_LOCK_CURRENT_STATE_UNSECURED = {.u = 0}; |
|
58 static hap_val_t HAP_LOCK_CURRENT_STATE_SECURED = {.u = 1}; |
|
59 |
|
60 static hap_val_t HAP_PROGRAMMABLE_SWITCH_EVENT_SINGLE_PRESS = {.u = 0}; |
|
61 |
|
62 #define ESP_INTR_FLAG_DEFAULT 0 |
|
63 |
|
64 static const uint8_t DOOR_EVENT_QUEUE_BELL = 1; |
|
65 static const uint8_t DOOR_EVENT_QUEUE_UNLOCK = 2; |
|
66 static const uint8_t DOOR_EVENT_QUEUE_LOCK = 3; |
|
67 static const uint8_t DOOR_EVENT_QUEUE_LOCK_TIMEOUT = 4; |
|
68 |
|
69 static xQueueHandle door_event_queue = NULL; |
|
70 static TimerHandle_t door_lock_timer = NULL; |
|
71 |
|
72 /** |
|
73 * @brief the recover door bell gpio interrupt function |
|
74 */ |
|
75 static void IRAM_ATTR door_bell_isr(void* arg) { |
|
76 xQueueSendFromISR(door_event_queue, (void*) &DOOR_EVENT_QUEUE_BELL, NULL); |
|
77 } |
|
78 |
|
79 /** |
|
80 * Enable a GPIO Pin for Door Bell |
|
81 */ |
|
82 static void door_bell_init(uint32_t key_gpio_pin) { |
|
83 gpio_config_t io_conf; |
|
84 |
|
85 io_conf.intr_type = GPIO_INTR_NEGEDGE; /* Interrupt for falling edge */ |
|
86 io_conf.pin_bit_mask = 1 << key_gpio_pin; /* Bit mask of the pins */ |
|
87 io_conf.mode = GPIO_MODE_INPUT; /* Set as input mode */ |
|
88 io_conf.pull_up_en = GPIO_PULLUP_DISABLE; /* Disable internal pull-up */ |
|
89 io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; /* Disable internal pull-down */ |
|
90 |
|
91 gpio_config(&io_conf); /* Set the GPIO configuration */ |
|
92 |
|
93 gpio_install_isr_service(ESP_INTR_FLAG_DEFAULT); /* Install gpio isr service */ |
|
94 gpio_isr_handler_add(key_gpio_pin, door_bell_isr, (void*)key_gpio_pin); /* Hook isr handler for specified gpio pin */ |
|
95 } |
|
96 |
|
97 /** |
|
98 * Enable a GPIO Pin for Door Lock |
|
99 */ |
|
100 static void door_lock_init(uint32_t key_gpio_pin) { |
|
101 gpio_config_t io_conf; |
|
102 |
|
103 io_conf.intr_type = GPIO_INTR_DISABLE; /* Interrupt for falling edge */ |
|
104 io_conf.pin_bit_mask = 1 << key_gpio_pin; /* Bit mask of the pins */ |
|
105 io_conf.mode = GPIO_MODE_OUTPUT; /* Set as input mode */ |
|
106 io_conf.pull_up_en = GPIO_PULLUP_DISABLE; /* Disable internal pull-up */ |
|
107 io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; /* Disable internal pull-down */ |
|
108 |
|
109 gpio_config(&io_conf); /* Set the GPIO configuration */ |
|
110 } |
|
111 |
|
112 /** |
|
113 * Initialize the Door Hardware. Here, we just enebale the Door Bell detection. |
|
114 */ |
|
115 void door_hardware_init(gpio_num_t door_bell_gpio_num, gpio_num_t door_lock_gpio_num) { |
|
116 int queue_len = 4; |
|
117 int queue_item_size = sizeof(uint8_t); |
|
118 |
|
119 door_event_queue = xQueueCreate(queue_len, queue_item_size); |
|
120 if (door_event_queue != NULL) { |
|
121 door_bell_init(door_bell_gpio_num); |
|
122 door_lock_init(door_lock_gpio_num); |
|
123 } |
|
124 } |
|
125 |
|
126 /* Mandatory identify routine for the accessory. |
|
127 * In a real accessory, something like LED blink should be implemented |
|
128 * got visual identification |
|
129 */ |
|
130 static int door_identify(hap_acc_t *ha) { |
|
131 ESP_LOGI(TAG, "Accessory identified"); |
|
132 return HAP_SUCCESS; |
|
133 } |
|
134 |
|
135 static void door_bell_ring(hap_char_t *door_bell_current_state) { |
|
136 ESP_LOGI(TAG, "Door bell ring event processed"); |
|
137 |
|
138 hap_char_update_val(door_bell_current_state, &HAP_PROGRAMMABLE_SWITCH_EVENT_SINGLE_PRESS); |
|
139 } |
|
140 |
|
141 static void door_unlock(hap_char_t *door_lock_current_state) { |
|
142 ESP_LOGI(TAG, "Door unlock event processed"); |
|
143 |
|
144 gpio_set_level(DOOR_LOCK_GPIO_PIN, DOOR_LOCK_GPIO_UNLOCKED); |
|
145 hap_char_update_val(door_lock_current_state, &HAP_LOCK_CURRENT_STATE_UNSECURED); |
|
146 |
|
147 xTimerReset(door_lock_timer, 10); |
|
148 } |
|
149 |
|
150 static void door_lock(hap_char_t *door_lock_current_state) { |
|
151 ESP_LOGI(TAG, "Door lock event processed"); |
|
152 |
|
153 gpio_set_level(DOOR_LOCK_GPIO_PIN, DOOR_LOCK_GPIO_LOCKED); |
|
154 hap_char_update_val(door_lock_current_state, &HAP_LOCK_CURRENT_STATE_SECURED); |
|
155 } |
|
156 |
|
157 static void door_lock_timeout(hap_char_t *door_lock_target_state) { |
|
158 ESP_LOGI(TAG, "Door lock timeout event processed"); |
|
159 |
|
160 xQueueSendToBack(door_event_queue, (void*) &DOOR_EVENT_QUEUE_LOCK, 10); |
|
161 hap_val_t target_lock_secured = {.u = HAP_LOCK_TARGET_STATE_SECURED}; |
|
162 hap_char_update_val(door_lock_target_state, &target_lock_secured); |
|
163 } |
|
164 |
|
165 static int door_lock_write_cb(hap_write_data_t write_data[], int count, void *serv_priv, void *write_priv) { |
|
166 int i, ret = HAP_SUCCESS; |
|
167 hap_write_data_t *write; |
|
168 for (i = 0; i < count; i++) { |
|
169 write = &write_data[i]; |
|
170 if (!strcmp(hap_char_get_type_uuid(write->hc), HAP_CHAR_UUID_LOCK_TARGET_STATE)) { |
|
171 ESP_LOGI(TAG, "Received Write. Door lock %d", write->val.u); |
|
172 |
|
173 switch (write->val.u) { |
|
174 case HAP_LOCK_TARGET_STATE_UNSECURED: |
|
175 xQueueSendToBack(door_event_queue, (void*) &DOOR_EVENT_QUEUE_UNLOCK, 10); |
|
176 break; |
|
177 case HAP_LOCK_TARGET_STATE_SECURED: |
|
178 xQueueSendToBack(door_event_queue, (void*) &DOOR_EVENT_QUEUE_LOCK, 10); |
|
179 break; |
|
180 } |
|
181 |
|
182 /* Update target state */ |
|
183 hap_char_update_val(write->hc, &(write->val)); |
|
184 *(write->status) = HAP_STATUS_SUCCESS; |
|
185 } else { |
|
186 *(write->status) = HAP_STATUS_RES_ABSENT; |
|
187 } |
|
188 } |
|
189 return ret; |
|
190 } |
|
191 |
|
192 static void door_lock_timer_cb(TimerHandle_t timer) { |
|
193 ESP_LOGI(TAG, "Door lock timer fired - event queued"); |
|
194 xQueueSendToBack(door_event_queue, (void*) &DOOR_EVENT_QUEUE_LOCK_TIMEOUT, 10); |
|
195 } |
|
196 |
|
197 /*The main thread for handling the Door Accessory */ |
|
198 static void door_thread_entry(void *p) { |
|
199 hap_init(HAP_TRANSPORT_WIFI); /* Initialize the HAP core */ |
|
200 |
|
201 /* Initialise the mandatory parameters for Accessory which will be added as |
|
202 * the mandatory services internally |
|
203 */ |
|
204 hap_acc_cfg_t cfg = { |
|
205 .name = "Door", |
|
206 .manufacturer = "Luke Hoersten", |
|
207 .model = "Esp32Door01", |
|
208 .serial_num = "001122334455", |
|
209 .fw_rev = "0.1.0", |
|
210 .hw_rev = NULL, |
|
211 .pv = "1.1.0", |
|
212 .identify_routine = door_identify, |
|
213 .cid = HAP_CID_DOOR, |
|
214 }; |
|
215 |
|
216 hap_acc_t *door_accessory = hap_acc_create(&cfg); /* Create accessory object */ |
|
217 |
|
218 /* Add a dummy Product Data */ |
|
219 uint8_t product_data[] = {'E','S','P','3','2','H','A','P'}; |
|
220 hap_acc_add_product_data(door_accessory, product_data, sizeof(product_data)); |
|
221 |
|
222 hap_serv_t *door_bell_service = hap_serv_stateless_programmable_switch_create(0); |
|
223 hap_serv_add_char(door_bell_service, hap_char_name_create("Doorbell")); |
|
224 hap_char_t *door_bell_current_state = hap_serv_get_char_by_uuid(door_bell_service, HAP_CHAR_UUID_PROGRAMMABLE_SWITCH_EVENT); |
|
225 |
|
226 hap_serv_t *door_lock_service = hap_serv_lock_mechanism_create(HAP_LOCK_CURRENT_STATE_SECURED.u, HAP_LOCK_TARGET_STATE_SECURED); |
|
227 hap_serv_add_char(door_lock_service, hap_char_name_create("Door Lock")); |
|
228 hap_char_t *door_lock_current_state = hap_serv_get_char_by_uuid(door_lock_service, HAP_CHAR_UUID_LOCK_CURRENT_STATE); |
|
229 hap_char_t *door_lock_target_state = hap_serv_get_char_by_uuid(door_lock_service, HAP_CHAR_UUID_LOCK_TARGET_STATE); |
|
230 |
|
231 /* Get pointer to the door in use characteristic which we need to monitor for state changes */ |
|
232 hap_serv_set_write_cb(door_lock_service, door_lock_write_cb); /* Set the write callback for the service */ |
|
233 |
|
234 hap_acc_add_serv(door_accessory, door_bell_service); |
|
235 hap_acc_add_serv(door_accessory, door_lock_service); |
|
236 |
|
237 hap_add_accessory(door_accessory); /* Add the Accessory to the HomeKit Database */ |
|
238 |
|
239 door_hardware_init(DOOR_BELL_GPIO_PIN, DOOR_LOCK_GPIO_PIN); /* Initialize the appliance specific hardware. This enables out-in-use detection */ |
|
240 |
|
241 /* For production accessories, the setup code shouldn't be programmed on to |
|
242 * the device. Instead, the setup info, derived from the setup code must |
|
243 * be used. Use the factory_nvs_gen utility to generate this data and then |
|
244 * flash it into the factory NVS partition. |
|
245 * |
|
246 * By default, the setup ID and setup info will be read from the factory_nvs |
|
247 * Flash partition and so, is not required to set here explicitly. |
|
248 * |
|
249 * However, for testing purpose, this can be overridden by using hap_set_setup_code() |
|
250 * and hap_set_setup_id() APIs, as has been done here. |
|
251 */ |
|
252 #ifdef CONFIG_HOMEKIT_USE_HARDCODED_SETUP_CODE |
|
253 /* Unique Setup code of the format xxx-xx-xxx. Default: 111-22-333 */ |
|
254 hap_set_setup_code(CONFIG_HOMEKIT_SETUP_CODE); |
|
255 /* Unique four character Setup Id. Default: ES32 */ |
|
256 hap_set_setup_id(CONFIG_HOMEKIT_SETUP_ID); |
|
257 #ifdef CONFIG_APP_WIFI_USE_WAC_PROVISIONING |
|
258 app_hap_setup_payload(CONFIG_HOMEKIT_SETUP_CODE, CONFIG_HOMEKIT_SETUP_ID, true, cfg.cid); |
|
259 #else |
|
260 app_hap_setup_payload(CONFIG_HOMEKIT_SETUP_CODE, CONFIG_HOMEKIT_SETUP_ID, false, cfg.cid); |
|
261 #endif |
|
262 #endif |
|
263 |
|
264 hap_enable_mfi_auth(HAP_MFI_AUTH_HW); /* Enable Hardware MFi authentication (applicable only for MFi variant of SDK) */ |
|
265 |
|
266 app_wifi_init(); /* Initialize Wi-Fi */ |
|
267 hap_start(); /* After all the initializations are done, start the HAP core */ |
|
268 app_wifi_start(portMAX_DELAY); /* Start Wi-Fi */ |
|
269 |
|
270 door_lock_timer = xTimerCreate("door_lock_timer", pdMS_TO_TICKS(CONFIG_HOMEKIT_DOOR_LOCK_TIMEOUT), pdFALSE, 0, door_lock_timer_cb); |
|
271 |
|
272 /* Listen for doorbell state change events. Other read/write functionality will be handled by the HAP Core. When the |
|
273 * doorbell in Use GPIO goes low, it means doorbell is not ringing. When the Door in Use GPIO goes high, it means |
|
274 * the doorbell is ringing. Applications can define own logic as per their hardware. |
|
275 */ |
|
276 uint8_t door_event_queue_item = DOOR_EVENT_QUEUE_LOCK; |
|
277 |
|
278 while (1) { |
|
279 if (xQueueReceive(door_event_queue, &door_event_queue_item, portMAX_DELAY) == pdFALSE) { |
|
280 ESP_LOGI(TAG, "Door event queue trigger FAIL"); |
|
281 } else { |
|
282 switch(door_event_queue_item) { |
|
283 case DOOR_EVENT_QUEUE_BELL: |
|
284 door_bell_ring(door_bell_current_state); |
|
285 break; |
|
286 case DOOR_EVENT_QUEUE_UNLOCK: |
|
287 door_unlock(door_lock_current_state); |
|
288 break; |
|
289 case DOOR_EVENT_QUEUE_LOCK: |
|
290 door_lock(door_lock_current_state); |
|
291 break; |
|
292 case DOOR_EVENT_QUEUE_LOCK_TIMEOUT: |
|
293 door_lock_timeout(door_lock_target_state); |
|
294 break; |
|
295 } |
|
296 } |
|
297 } |
|
298 } |
|
299 |
|
300 void app_main() { |
|
301 xTaskCreate(door_thread_entry, DOOR_TASK_NAME, DOOR_TASK_STACKSIZE, NULL, DOOR_TASK_PRIORITY, NULL); |
|
302 } |