-
Notifications
You must be signed in to change notification settings - Fork 463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Aqara] Presence Sensor FP2 #1546
Changes from 9 commits
238ec62
14e259b
8a24c90
a1b63db
48493fa
3099b1c
04533f9
63246c2
a3857c2
640f066
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
name: 'Aqara Presence Sensor' | ||
packageKey: 'aqara-presence-sensor' | ||
permissions: | ||
lan: {} | ||
discovery: {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
name: aqara-fp2-fallDetection | ||
components: | ||
- id: main | ||
capabilities: | ||
- id: activitySensor | ||
version: 1 | ||
- id: presenceSensor | ||
version: 1 | ||
- id: illuminanceMeasurement | ||
version: 1 | ||
- id: refresh | ||
version: 1 | ||
categories: | ||
- name: PresenceSensor | ||
- id: mode | ||
capabilities: | ||
- id: stse.deviceMode | ||
version: 1 | ||
categories: | ||
- name: PresenceSensor |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
name: aqara-fp2-sleepMonitoring | ||
components: | ||
- id: main | ||
capabilities: | ||
- id: illuminanceMeasurement | ||
version: 1 | ||
- id: refresh | ||
version: 1 | ||
categories: | ||
- name: PresenceSensor | ||
- id: mode | ||
capabilities: | ||
- id: stse.deviceMode | ||
version: 1 | ||
categories: | ||
- name: PresenceSensor |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
name: aqara-fp2-zoneDetection | ||
components: | ||
- id: main | ||
capabilities: | ||
- id: presenceSensor | ||
version: 1 | ||
- id: movementSensor | ||
version: 1 | ||
- id: multipleZonePresence | ||
version: 1 | ||
- id: illuminanceMeasurement | ||
version: 1 | ||
- id: refresh | ||
version: 1 | ||
categories: | ||
- name: PresenceSensor | ||
- id: mode | ||
capabilities: | ||
- id: stse.deviceMode | ||
version: 1 | ||
categories: | ||
- name: PresenceSensor |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
mdns: | ||
- service: "_Aqara-FP2._tcp" |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
local log = require "log" | ||
local discovery = {} | ||
local fields = require "fields" | ||
local discovery_mdns = require "discovery_mdns" | ||
local socket = require "cosock.socket" | ||
|
||
-- mapping from device DNI to info needed at discovery/init time | ||
local device_discovery_cache = {} | ||
|
||
local function set_device_field(driver, device) | ||
local device_cache_value = device_discovery_cache[device.device_network_id] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: should check for |
||
|
||
-- persistent fields | ||
if device_cache_value ~= nil then | ||
device:set_field(fields.DEVICE_IPV4, device_cache_value.ip, { persist = true }) | ||
device:set_field(fields.DEVICE_INFO, device_cache_value.device_info, { persist = true }) | ||
device:set_field(fields.CREDENTIAL, device_cache_value.credential, { persist = true }) | ||
end | ||
end | ||
|
||
local function update_device_discovery_cache(driver, dni, ip, credential) | ||
local device_info = driver.discovery_helper.get_device_info(driver, dni, ip) | ||
device_discovery_cache[dni] = { | ||
ip = ip, | ||
device_info = device_info, | ||
credential = credential, | ||
} | ||
end | ||
|
||
local function try_add_device(driver, device_dni, device_ip) | ||
log.trace(string.format("try_add_device : dni= %s, ip= %s", device_dni, device_ip)) | ||
|
||
local credential = driver.discovery_helper.get_credential(driver, device_dni, device_ip) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: error messages are typically included after the value, this will make line 34 more helpful |
||
|
||
if not credential then | ||
log.error(string.format("failed to get credential. dni= %s, ip= %s", device_dni, device_ip)) | ||
return | ||
end | ||
|
||
update_device_discovery_cache(driver, device_dni, device_ip, credential) | ||
local create_device_msg = driver.discovery_helper.get_device_create_msg(driver, device_dni, device_ip) | ||
driver:try_create_device(create_device_msg) | ||
end | ||
|
||
function discovery.device_added(driver, device) | ||
set_device_field(driver, device) | ||
device_discovery_cache[device.device_network_id] = nil | ||
driver.lifecycle_handlers.init(driver, device) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't believe you should be calling lifecycle handlers directly, this will be called by the framework There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like this is needed in order to make sure init is called after added. Init requires the device fields to have been populated. |
||
end | ||
|
||
function discovery.find_ip_table(driver) | ||
return discovery_mdns.find_ip_table_by_mdns(driver) | ||
end | ||
|
||
local function discovery_device(driver) | ||
local unknown_discovered_devices = {} | ||
local known_discovered_devices = {} | ||
local known_devices = {} | ||
|
||
for _, device in pairs(driver:get_devices()) do | ||
known_devices[device.device_network_id] = device | ||
end | ||
|
||
local ip_table = discovery.find_ip_table(driver) | ||
|
||
for dni, ip in pairs(ip_table) do | ||
if not known_devices[dni] then | ||
unknown_discovered_devices[dni] = ip | ||
else | ||
known_discovered_devices[dni] = ip | ||
end | ||
end | ||
|
||
for dni, ip in pairs(known_discovered_devices) do | ||
log.trace(string.format("known dni= %s, ip= %s", dni, ip)) | ||
end | ||
|
||
for dni, ip in pairs(unknown_discovered_devices) do | ||
log.trace(string.format("unknown dni= %s, ip= %s", dni, ip)) | ||
if not device_discovery_cache[dni] then | ||
try_add_device(driver, dni, ip) | ||
end | ||
end | ||
end | ||
|
||
function discovery.do_network_discovery(driver, _, should_continue) | ||
while should_continue() do | ||
discovery_device(driver) | ||
socket.sleep(0.2) | ||
end | ||
end | ||
|
||
return discovery |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
local log = require "log" | ||
local mdns = require "st.mdns" | ||
local net_utils = require "st.net_utils" | ||
|
||
local discovery_mdns = {} | ||
|
||
local function byte_array_to_plain_text(byte_array) | ||
return string.char(table.unpack(byte_array)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I am not sure why this would be needed? I thought all the responses from the mdns API would be strings |
||
end | ||
|
||
local function get_text_by_srvname(srvname, discovery_responses) | ||
for _, answer_item in pairs(discovery_responses.answers or {}) do | ||
if answer_item.kind.TxtRecord ~= nil and answer_item.name == srvname then | ||
return answer_item.kind.TxtRecord.text | ||
end | ||
end | ||
end | ||
|
||
local function get_srvname_by_hostname(hostname, discovery_responses) | ||
for _, answer_item in pairs(discovery_responses.answers or {}) do | ||
if answer_item.kind.SrvRecord ~= nil and answer_item.kind.SrvRecord.target == hostname then | ||
return answer_item.name | ||
end | ||
end | ||
end | ||
|
||
local function get_hostname_by_ip(ip, discovery_responses) | ||
for _, answer_item in pairs(discovery_responses.answers or {}) do | ||
if answer_item.kind.ARecord ~= nil and answer_item.kind.ARecord.ipv4 == ip then | ||
return answer_item.name | ||
end | ||
end | ||
end | ||
|
||
|
||
local function find_text_in_answers_by_ip(ip, discovery_responses) | ||
local hostname = get_hostname_by_ip(ip, discovery_responses) | ||
local srvname = get_srvname_by_hostname(hostname, discovery_responses) | ||
local text = get_text_by_srvname(srvname, discovery_responses) | ||
|
||
return text | ||
end | ||
|
||
function discovery_mdns.find_text_list_in_mdns_response(driver, ip, discovery_responses) | ||
local text_list = {} | ||
|
||
for _, found_item in pairs(discovery_responses.found or {}) do | ||
if found_item.host_info.address == ip then | ||
for _, raw_text_array in pairs(found_item.txt.text or {}) do | ||
local text_item = byte_array_to_plain_text(raw_text_array) | ||
table.insert(text_list, text_item) | ||
end | ||
end | ||
end | ||
|
||
local answer_text = find_text_in_answers_by_ip(ip, discovery_responses) | ||
for _, text_item in pairs(answer_text or {}) do | ||
table.insert(text_list, text_item) | ||
end | ||
return text_list | ||
end | ||
|
||
local function filter_response_by_service_name(service_type, domain, discovery_responses) | ||
local filtered_responses = { | ||
answers = {}, | ||
found = {} | ||
} | ||
|
||
for _, answer in pairs(discovery_responses.answers or {}) do | ||
table.insert(filtered_responses.answers, answer) | ||
end | ||
|
||
for _, additional in pairs(discovery_responses.additional or {}) do | ||
table.insert(filtered_responses.answers, additional) | ||
end | ||
|
||
for _, found in pairs(discovery_responses.found or {}) do | ||
if found.service_info.service_type == service_type then | ||
table.insert(filtered_responses.found, found) | ||
end | ||
end | ||
|
||
return filtered_responses | ||
end | ||
|
||
local function insert_dni_ip_from_answers(driver, filtered_responses, target_table) | ||
for _, answer in pairs(filtered_responses.answers) do | ||
local dni, ip | ||
log.info("answer_name, arecod = " .. tostring(answer.name) .. ", " .. tostring(answer.kind.ARecord)) | ||
|
||
if answer.kind.ARecord ~= nil then | ||
ip = answer.kind.ARecord.ipv4 | ||
end | ||
|
||
if ip ~= nil then | ||
dni = driver.discovery_helper.get_dni(driver, ip, filtered_responses) | ||
|
||
if dni ~= nil then | ||
target_table[dni] = ip | ||
end | ||
end | ||
end | ||
end | ||
|
||
local function insert_dni_ip_from_found(driver, filtered_responses, target_table) | ||
for _, found in pairs(filtered_responses.found) do | ||
local dni, ip | ||
log.info("found_name = " .. tostring(found.service_info.service_type)) | ||
if found.host_info.address ~= nil and net_utils.validate_ipv4_string(found.host_info.address) then | ||
log.info("ip = " .. tostring(found.host_info.address)) | ||
ip = found.host_info.address | ||
end | ||
|
||
if ip ~= nil then | ||
dni = driver.discovery_helper.get_dni(driver, ip, filtered_responses) | ||
|
||
if dni ~= nil then | ||
target_table[dni] = ip | ||
end | ||
end | ||
end | ||
end | ||
|
||
local function get_dni_ip_table_from_mdns_responses(driver, service_type, domain, discovery_responses) | ||
local dni_ip_table = {} | ||
|
||
local filtered_responses = filter_response_by_service_name(service_type, domain, discovery_responses) | ||
|
||
insert_dni_ip_from_answers(driver, filtered_responses, dni_ip_table) | ||
insert_dni_ip_from_found(driver, filtered_responses, dni_ip_table) | ||
|
||
return dni_ip_table | ||
end | ||
|
||
function discovery_mdns.find_ip_table_by_mdns(driver) | ||
log.info("discovery_mdns.find_device_ips") | ||
local service_type, domain = driver.discovery_helper.get_service_type_and_domain() | ||
local discovery_responses = mdns.discover(service_type, domain) or { found = {} } | ||
local dni_ip_table = get_dni_ip_table_from_mdns_responses(driver, service_type, domain, discovery_responses) | ||
return dni_ip_table | ||
end | ||
|
||
return discovery_mdns |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
--- Table of constants used to index in to device store fields | ||
--- @module "fields" | ||
--- @class table | ||
--- @field IPV4 string the ipV4 address of the device | ||
|
||
local fields = { | ||
DEVICE_IPV4 = "device_ipv4", | ||
DEVICE_INFO = "device_info", | ||
CONN_INFO = "conn_info", | ||
EVENT_SOURCE = "eventsource", | ||
MONITORING_TIMER = "monitoring_timer", | ||
CREDENTIAL = "credential", | ||
_INIT = "init" | ||
} | ||
|
||
return fields |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice discovery module. Very easy to review and understand what is happening.