Skip to content

Commit

Permalink
JBL driver clean commit
Browse files Browse the repository at this point in the history
  • Loading branch information
varzac committed Sep 14, 2023
1 parent 05381c5 commit 8538948
Show file tree
Hide file tree
Showing 17 changed files with 2,172 additions and 0 deletions.
7 changes: 7 additions & 0 deletions drivers/SmartThings/jbl/config.yml
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"
30 changes: 30 additions & 0 deletions drivers/SmartThings/jbl/profiles/jbl.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: jbl
components:
- id: main
capabilities:
- id: mediaPlayback
version: 1
config:
values:
- key: "playbackStatus.value"
enabledValues:
- 'playing'
- 'paused'
- key: "{{enumCommands}}"
enabledValues:
- 'play'
- 'pause'
- 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
3 changes: 3 additions & 0 deletions drivers/SmartThings/jbl/search-parameters.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
mdns:
- service: "_jbl._tcp"
104 changes: 104 additions & 0 deletions drivers/SmartThings/jbl/src/discovery.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
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)

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 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
log.info(string.format("discovery_device dni, ip = %s, %s", dni, ip))
if not known_devices or 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)
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
150 changes: 150 additions & 0 deletions drivers/SmartThings/jbl/src/discovery_mdns.lua
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 discovery_mdns = {}

local function byte_array_to_plain_text(byte_array)
local str = ""
for _, value in pairs(byte_array) do
str = str .. string.char(value)
end
return str
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_servie_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_servie_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
16 changes: 16 additions & 0 deletions drivers/SmartThings/jbl/src/fields.lua
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
Loading

0 comments on commit 8538948

Please sign in to comment.