-
Notifications
You must be signed in to change notification settings - Fork 475
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* add jbl driver * Remove discovery cache after device_add\n- Remove discovery cache after device_add\n- Do not update information of already registered device during discovery * rebase * Code review should be done to start UL tests. #36 Capability state is not updated as soon as a speaker is onboarded #37 * code review * fix log error --------- Co-authored-by: chcai <[email protected]> Co-authored-by: lelandblue <[email protected]>
- Loading branch information
1 parent
e672912
commit 7f8a507
Showing
18 changed files
with
2,232 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
name: 'JBL' | ||
packageKey: 'jbl' | ||
permissions: | ||
lan: {} | ||
discovery: {} | ||
description: "SmartThings driver for JBL devices" | ||
vendorSupportInformation: "https://support.smartthings.com" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
name: jbl | ||
components: | ||
- id: main | ||
capabilities: | ||
- id: mediaPlayback | ||
version: 1 | ||
config: | ||
values: | ||
- key: "playbackStatus.value" | ||
enabledValues: | ||
- 'playing' | ||
- 'paused' | ||
- 'stopped' | ||
- key: "{{enumCommands}}" | ||
enabledValues: | ||
- 'play' | ||
- 'pause' | ||
- 'stop' | ||
- id: mediaTrackControl | ||
version: 1 | ||
- id: audioMute | ||
version: 1 | ||
- id: audioVolume | ||
version: 1 | ||
- id: audioTrackData | ||
version: 1 | ||
- id: refresh | ||
version: 1 | ||
- id: audioNotification | ||
version: 1 | ||
categories: | ||
- name: Speaker |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
--- | ||
mdns: | ||
- service: "_jbl._tcp" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
local log = require "log" | ||
|
||
local fields = require "fields" | ||
local discovery_mdns = require "discovery_mdns" | ||
|
||
local socket = require "cosock.socket" | ||
local st_utils = require "st.utils" | ||
|
||
local discovery = {} | ||
|
||
-- mapping from device DNI to info needed at discovery/init time | ||
local device_discovery_cache = {} | ||
|
||
local function set_device_field(driver, device) | ||
|
||
log.info(string.format("set_device_field : %s", device.device_network_id)) | ||
local device_cache_value = device_discovery_cache[device.device_network_id] | ||
|
||
-- persistent fields | ||
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 | ||
|
||
local function update_device_discovery_cache(driver, dni, ip, credential) | ||
log.info(string.format("update_device_discovery_cache for device dni: %s, %s", dni, ip)) | ||
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) | ||
|
||
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) | ||
log.info("device_added : dni = " .. tostring(device.device_network_id)) | ||
set_device_field(driver, device) | ||
device_discovery_cache[device.device_network_id] = nil | ||
driver.lifecycle_handlers.init(driver, device) | ||
end | ||
|
||
function discovery.find_ip_table(driver) | ||
local ip_table= discovery_mdns.find_ip_table_by_mdns(driver) | ||
return ip_table | ||
end | ||
|
||
|
||
local function discovery_device(driver) | ||
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) | ||
|
||
log.debug(st_utils.stringify_table(ip_table, "DNI IP Table after processing mDNS Discovery Response", true)) | ||
|
||
for dni, ip in pairs(ip_table) do | ||
log.info(string.format("discovery_device dni, ip = %s, %s", dni, ip)) | ||
if not known_devices or not known_devices[dni] then | ||
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 | ||
else | ||
log.trace(string.format("known dni= %s, ip= %s", dni, ip)) | ||
end | ||
end | ||
end | ||
|
||
function discovery.do_network_discovery(driver, _, should_continue) | ||
log.info("discovery.do_network_discovery :Starting mDNS discovery") | ||
|
||
while should_continue() do | ||
discovery_device(driver) | ||
socket.sleep(0.2) | ||
end | ||
log.info("discovery.do_network_discovery: Ending mDNS discovery") | ||
end | ||
|
||
return discovery |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
local log = require "log" | ||
local mdns = require "st.mdns" | ||
local net_utils = require "st.net_utils" | ||
local st_utils = require "st.utils" | ||
|
||
local discovery_mdns = {} | ||
|
||
local function byte_array_to_plain_text(byte_array) | ||
return string.char(table.unpack(byte_array)) | ||
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) | ||
|
||
log.debug(st_utils.stringify_table(filtered_responses, "[get_dni_ip_table(...)] Filtered Responses", true)) | ||
|
||
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 = {} } | ||
|
||
log.debug(st_utils.stringify_table(discovery_responses, "Raw mDNS Discovery Response", true)) | ||
|
||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.