1801 lines
72 KiB
Lua
1801 lines
72 KiB
Lua
--- === hs.tangent ===
|
||
---
|
||
--- Tangent Control Surface Extension
|
||
---
|
||
--- **API Version:** TUBE Version 3.2 - TIPC Rev 4 (22nd February 2017)
|
||
---
|
||
--- This plugin allows Hammerspoon to communicate with Tangent's range of panels, such as their Element, Virtual Element Apps, Wave, Ripple and any future panels.
|
||
---
|
||
--- The Tangent Unified Bridge Engine (TUBE) is made up of two software elements, the Mapper and the Hub. The Hub communicates with your application via the
|
||
--- TUBE Inter Process Communications (TIPC). TIPC is a standardised protocol to allow any application that supports it to communicate with any current and
|
||
--- future panels produced by Tangent via the TUBE Hub.
|
||
---
|
||
--- You can download the Tangent Developer Support Pack & Tangent Hub Installer for Mac [here](http://www.tangentwave.co.uk/developer-support/).
|
||
---
|
||
--- This extension was thrown together by [Chris Hocking](https://github.com/latenitefilms), then dramatically improved by [David Peterson](https://github.com/randomeizer) for [CommandPost](http://commandpost.io).
|
||
|
||
local log = require("hs.logger").new("tangent")
|
||
local inspect = require("hs.inspect")
|
||
|
||
local fs = require("hs.fs")
|
||
local socket = require("hs.socket")
|
||
local timer = require("hs.timer")
|
||
|
||
local unpack, pack, format = string.unpack, string.pack, string.format
|
||
|
||
--------------------------------------------------------------------------------
|
||
--
|
||
-- THE MODULE:
|
||
--
|
||
--------------------------------------------------------------------------------
|
||
local mod = {}
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- MODULE CONSTANTS:
|
||
--------------------------------------------------------------------------------
|
||
|
||
--- hs.tangent.fromHub -> table
|
||
--- Constant
|
||
--- Definitions for IPC Commands from the HUB to Hammerspoon.
|
||
---
|
||
--- Notes:
|
||
--- * `connected` - a connection is established with the Hub.
|
||
--- * `disconnected` - the connection is dropped with the Hub.
|
||
--- * `initiateComms` - sent when the Hub wants to initiate communications.
|
||
--- * `parameterChange` - a parameter was incremented.
|
||
--- * `parameterReset` - a parameter was reset.
|
||
--- * `parameterValueRequest` - the Hub wants the current value of the parameter.
|
||
--- * `menuChange` - The menu was changed, `+1` or `-1`.
|
||
--- * `menuReset` - The menu was reset.
|
||
--- * `menuStringRequest` - The application should send a `menuString` with the current value.
|
||
--- * `actionOn` - An action button was pressed.
|
||
--- * `actionOff` - An action button was released.
|
||
--- * `modeChange` - The current mode was changed.
|
||
--- * `transport` - The transport.
|
||
--- * `unmanagedPanelCapabilities` - Send by the Hub to advertise an unmanaged panel.
|
||
--- * `unmanagedButtonDown` - A button on an unmanaged panel was pressed.
|
||
--- * `unmanagedButtonUp` - A button on an unmanaged panel was released.
|
||
--- * `unmanagedEncoderChange` - An encoder (dial/wheel) on an unmanaged panel changed.
|
||
--- * `unmanagedDisplayRefresh` - Triggered when an unmanaged panel's display needs to update.
|
||
--- * `panelConnectionState` - A panel's connection state changed.
|
||
mod.fromHub = {
|
||
--------------------------------------------------------------------------------
|
||
-- Custom Notifications:
|
||
--------------------------------------------------------------------------------
|
||
connected = 0xFF01,
|
||
disconnected = 0xFF02,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Official Definitions:
|
||
--------------------------------------------------------------------------------
|
||
initiateComms = 0x01,
|
||
parameterChange = 0x02,
|
||
parameterReset = 0x03,
|
||
parameterValueRequest = 0x04,
|
||
menuChange = 0x05,
|
||
menuReset = 0x06,
|
||
menuStringRequest = 0x07,
|
||
actionOn = 0x08,
|
||
modeChange = 0x09,
|
||
transport = 0x0A,
|
||
actionOff = 0x0B,
|
||
unmanagedPanelCapabilities = 0x30,
|
||
unmanagedButtonDown = 0x31,
|
||
unmanagedButtonUp = 0x32,
|
||
unmanagedEncoderChange = 0x33,
|
||
unmanagedDisplayRefresh = 0x34,
|
||
panelConnectionState = 0x35,
|
||
}
|
||
|
||
--- hs.tangent.toHub -> table
|
||
--- Constant
|
||
--- Definitions for IPC Commands from Hammerspoon to the HUB.
|
||
mod.toHub = {
|
||
applicationDefinition = 0x81,
|
||
parameterValue = 0x82,
|
||
menuString = 0x83,
|
||
allChange = 0x84,
|
||
modeValue = 0x85,
|
||
displayText = 0x86,
|
||
unmanagedPanelCapabilitiesRequest = 0xA0,
|
||
unmanagedDisplayWrite = 0xA1,
|
||
renameControl = 0xA2,
|
||
highlightControl = 0xA3,
|
||
indicateControl = 0xA4,
|
||
panelConnectionStatesRequest = 0xA5,
|
||
}
|
||
|
||
mod.reserved = {
|
||
--- hs.tangent.reserved.action -> table
|
||
--- Constant
|
||
--- Definitions for reserved action IDs.
|
||
---
|
||
--- Notes:
|
||
--- * `alt` - toggles the 'ALT' function.
|
||
--- * `nextKnobBank` - switches to the next knob bank.
|
||
--- * `prevKnobBank` - switches to the previous knob bank.
|
||
--- * `nextButtonBank` - switches to the next button bank.
|
||
--- * `prevBasketBank` - switches to the previous button bank.
|
||
--- * `nextTrackerballBank` - switches to the next trackerball bank.
|
||
--- * `prevTrackerballBank` - switches to the previous trackerball bank.
|
||
--- * `nextMode` - switches to the next mode.
|
||
--- * `prevMode` - switches to the previous mode.
|
||
--- * `goToMode` - switches to the specified mode, requiring a Argument with the mode ID.
|
||
--- * `toggleJogShuttle` - toggles jog/shuttle mode.
|
||
--- * `toggleMouseEmulation` - toggles mouse emulation.
|
||
--- * `fakeKeypress` - generates a keypress, requiring an Argument with the key code.
|
||
--- * `showHUD` - shows the HUD on screen.
|
||
--- * `goToKnobBank` - goes to the specific knob bank, requiring an Argument with the bank number.
|
||
--- * `goToButtonBank` - goes to the specific button bank, requiring an Argument with the bank number.
|
||
--- * `goToTrackerballBank` - goes to the specific trackerball bank, requiring an Argument with the bank number.
|
||
action = {
|
||
_ = 0x80000000,
|
||
alt = 0x80000001,
|
||
nextKnobBank = 0x80000002,
|
||
prevKnobBank = 0x80000003,
|
||
nextButtonBank = 0x80000004,
|
||
prevButtonBank = 0x80000005,
|
||
nextTrackerballBank = 0x80000006,
|
||
prevTrackerballBank = 0x80000007,
|
||
nextMode = 0x80000009,
|
||
prevMode = 0x8000000A,
|
||
goToMode = 0x8000000B,
|
||
toggleJogShuttle = 0x8000000C,
|
||
toggleMouseEmulation = 0x8000000D,
|
||
fakeKeypress = 0x8000000E,
|
||
showHUD = 0x8000000F,
|
||
goToKnobBank = 0x80000010,
|
||
goToButtonBank = 0x80000011,
|
||
goToTrackerballBank = 0x80000012,
|
||
},
|
||
|
||
--- hs.tangent.reserved.parameter -> table
|
||
--- Constant
|
||
--- A table of reserved parameter IDs.
|
||
---
|
||
--- Notes:
|
||
--- * `transportRing` - transport ring.
|
||
--- * `fakeKeypress` - sends a fake keypress.
|
||
parameter = {
|
||
_ = 0x81000000,
|
||
transportRing = 0x81000001,
|
||
fakeKeypress = 0x81000002,
|
||
},
|
||
menu = {
|
||
_ = 0x82000000,
|
||
}
|
||
}
|
||
|
||
--- hs.tangent.panelType -> table
|
||
--- Constant
|
||
--- Tangent Panel Types.
|
||
mod.panelType = {
|
||
[0x03] = "CP200-BK",
|
||
[0x04] = "CP200-K",
|
||
[0x05] = "CP200-TS",
|
||
[0x09] = "CP200-S",
|
||
[0x0A] = "Wave",
|
||
[0x0C] = "Element-Tk",
|
||
[0x0D] = "Element-Mf",
|
||
[0x0E] = "Element-Kb",
|
||
[0x0F] = "Element-Bt",
|
||
[0x11] = "Ripple",
|
||
}
|
||
|
||
-- ERROR_OFFSET -> number
|
||
-- Constant
|
||
-- Error Offset.
|
||
local ERROR_OFFSET = -1
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- HELPER FUNCTIONS:
|
||
--------------------------------------------------------------------------------
|
||
|
||
-- isNumber(value) -> boolean
|
||
-- Function
|
||
-- Checks to see whether or not `value` is a number.
|
||
--
|
||
-- Parameters:
|
||
-- * value - The value to check.
|
||
--
|
||
-- Returns:
|
||
-- * A boolean.
|
||
local function isNumber(value)
|
||
return type(value) == "number"
|
||
end
|
||
|
||
-- isNotTable(value) -> boolean
|
||
-- Function
|
||
-- Checks to see whether or not `value` is not a table.
|
||
--
|
||
-- Parameters:
|
||
-- * value - The value to check.
|
||
--
|
||
-- Returns:
|
||
-- * A boolean.
|
||
local function isNotTable(value)
|
||
return type(value) ~= "table"
|
||
end
|
||
|
||
-- isNotList(value) -> boolean
|
||
-- Function
|
||
-- Checks to see whether or not `value` is not a list.
|
||
--
|
||
-- Parameters:
|
||
-- * value - The value to check.
|
||
--
|
||
-- Returns:
|
||
-- * A boolean.
|
||
local function isNotList(value)
|
||
return isNotTable(value) or #value == 0
|
||
end
|
||
|
||
-- doesDirectoryExist(path) -> string
|
||
-- Function
|
||
-- Returns whether or not a directory exists.
|
||
--
|
||
-- Parameters:
|
||
-- * path - the path of the directory you want to check as a string.
|
||
--
|
||
-- Returns:
|
||
-- * `true` if the directory exists otherwise `false`
|
||
local function doesDirectoryExist(path)
|
||
if path then
|
||
local attr = fs.attributes(path)
|
||
return attr and attr.mode == 'directory'
|
||
else
|
||
return false
|
||
end
|
||
end
|
||
|
||
-- doesFileExist(path) -> boolean
|
||
-- Function
|
||
-- Returns whether or not a file exists.
|
||
--
|
||
-- Parameters:
|
||
-- * path - Path to the file
|
||
--
|
||
-- Returns:
|
||
-- * `true` if the file exists otherwise `false`
|
||
local function doesFileExist(path)
|
||
if path == nil then return nil end
|
||
local attr = fs.attributes(path)
|
||
if type(attr) == "table" then
|
||
return true
|
||
else
|
||
return false
|
||
end
|
||
end
|
||
|
||
-- getPanelType(id) -> string
|
||
-- Function
|
||
-- Returns the Panel Type based on an ID
|
||
--
|
||
-- Parameters:
|
||
-- * id - ID of the Panel Type you want to return
|
||
--
|
||
-- Returns:
|
||
-- * Panel Type as string
|
||
local function getPanelType(id)
|
||
return mod.panelType[id]
|
||
end
|
||
|
||
-- byteStringToNumber(str, offset, numberOfBytes[, signed]) -> number, number
|
||
-- Function
|
||
-- Translates a Byte String into a Number
|
||
--
|
||
-- Parameters:
|
||
-- * str - The string you want to translate
|
||
-- * offset - An offset
|
||
-- * numberOfBytes - Number of bytes
|
||
-- * signed - `true` if it's a signed integer otherwise `false`. Defaults to `false`.
|
||
--
|
||
-- Returns:
|
||
-- * A number value
|
||
-- * The new offset
|
||
local function byteStringToNumber(str, offset, numberOfBytes, signed)
|
||
local fmt = (signed and ">i" or ">I") .. tostring(numberOfBytes)
|
||
return unpack(fmt, str, offset)
|
||
end
|
||
|
||
-- byteStringToFloat(str, offset) -> number, number
|
||
-- Function
|
||
-- Translates a Byte String into a Float Number
|
||
--
|
||
-- Parameters:
|
||
-- * str - The string you want to translate
|
||
-- * offset - An offset
|
||
--
|
||
-- Returns:
|
||
-- * A number value
|
||
-- * The new offset
|
||
local function byteStringToFloat(str, offset)
|
||
return unpack(">f", str, offset)
|
||
end
|
||
|
||
-- byteStringToBoolean(str, offset, numberOfBytes) -> boolean, number
|
||
-- Function
|
||
-- Translates a Byte String into a Boolean
|
||
--
|
||
-- Parameters:
|
||
-- * str - The string you want to translate
|
||
-- * offset - An offset
|
||
-- * numberOfBytes - Number of bytes
|
||
--
|
||
-- Returns:
|
||
-- * A boolean value
|
||
-- * The new offset
|
||
local function byteStringToBoolean(str, offset, numberOfBytes)
|
||
local x = byteStringToNumber(str, offset, numberOfBytes)
|
||
return x == 1 or false, offset + numberOfBytes
|
||
end
|
||
|
||
-- numberToByteString(n) -> string
|
||
-- Function
|
||
-- Translates a number into a byte string.
|
||
--
|
||
-- Parameters:
|
||
-- * n - The number you want to translate
|
||
--
|
||
-- Returns:
|
||
-- * A string
|
||
local function numberToByteString(n)
|
||
if type(n) ~= "number" then
|
||
log.ef("numberToByteString() was fed something other than a number")
|
||
return nil
|
||
end
|
||
return pack(">I4", n)
|
||
end
|
||
|
||
-- floatToByteString(n) -> string
|
||
-- Function
|
||
-- Translates a float number into a byte string.
|
||
--
|
||
-- Parameters:
|
||
-- * n - The number you want to translate
|
||
--
|
||
-- Returns:
|
||
-- * A string
|
||
local function floatToByteString(n)
|
||
if type(n) ~= "number" then
|
||
log.ef("floatToByteString() was fed something other than a number")
|
||
return nil
|
||
end
|
||
return pack(">f", n)
|
||
end
|
||
|
||
-- booleanToByteString(value) -> string
|
||
-- Function
|
||
-- Translates a boolean into a byte string.
|
||
--
|
||
-- Parameters:
|
||
-- * value - The boolean you want to translate
|
||
--
|
||
-- Returns:
|
||
-- * A string
|
||
local function booleanToByteString(value)
|
||
if value == true then
|
||
return numberToByteString(1)
|
||
else
|
||
return numberToByteString(0)
|
||
end
|
||
end
|
||
|
||
-- validCallback() -> boolean
|
||
-- Function
|
||
-- Checks to see if the callback is valid.
|
||
--
|
||
-- Parameters:
|
||
-- * None
|
||
--
|
||
-- Returns:
|
||
-- * `true` if valid otherwise `false`.
|
||
local function validCallback()
|
||
return type(mod._callback) == "function"
|
||
end
|
||
|
||
-- processCommands(commands) -> none
|
||
-- Function
|
||
-- Triggers the callback using the contents of the buffer.
|
||
--
|
||
-- Parameters:
|
||
-- * None
|
||
--
|
||
-- Returns:
|
||
-- * None
|
||
local function processCommands(commands)
|
||
--------------------------------------------------------------------------------
|
||
-- Trigger the callback:
|
||
--------------------------------------------------------------------------------
|
||
if mod._callback then
|
||
local success, result = xpcall(function() mod._callback(commands) end, debug.traceback)
|
||
if not success then
|
||
log.ef("Error in Tangent Callback: %s", result)
|
||
end
|
||
end
|
||
end
|
||
|
||
-- errorResponse(message) -> nil, number
|
||
-- Function
|
||
-- Writes an error message to the Hammerspoon Console.
|
||
--
|
||
-- Parameters:
|
||
-- * message - The error message.
|
||
--
|
||
-- Returns:
|
||
-- * `nil`
|
||
-- * The error offset number.
|
||
local function errorResponse(message)
|
||
log.ef(message)
|
||
return nil, ERROR_OFFSET
|
||
end
|
||
|
||
-- receiveHandler -> table
|
||
-- Variable
|
||
-- Collection of handlers for messages received from the Hub.
|
||
local receiveHandler = {
|
||
--------------------------------------------------------------------------------
|
||
-- InitiateComms (0x01)
|
||
-- * Initiates communication between the Hub and the application.
|
||
-- * Communicates the quantity, type and IDs of the panels which are
|
||
-- configured to be connected in the panel-list.xml file. Note that this is
|
||
-- not the same as the panels which are actually connected – just those
|
||
-- which are expected to be connected.
|
||
-- * The length is dictated by the number of panels connected as the details
|
||
-- of each panel occupies 5 bytes.
|
||
-- * On receipt the application should respond with the
|
||
-- ApplicationDefinition (0x81) command.
|
||
--
|
||
-- Format: 0x01, <protocolRev>, <numPanels>, (<mod.panelType>, <panelID>)...
|
||
--
|
||
-- protocolRev: The revision number of the protocol (Unsigned Int)
|
||
-- numPanels: The number of panels connected (Unsigned Int)
|
||
-- panelType: The code for the type of panel connected (Unsigned Int)
|
||
-- panelID: The ID of the panel (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.initiateComms] = function(data, offset)
|
||
--------------------------------------------------------------------------------
|
||
-- Send Application Definition?
|
||
--------------------------------------------------------------------------------
|
||
if mod.automaticallySendApplicationDefinition == true then
|
||
mod.sendApplicationDefinition()
|
||
end
|
||
|
||
local protocolRev, numberOfPanels
|
||
protocolRev, offset = byteStringToNumber(data, offset, 4)
|
||
numberOfPanels, offset = byteStringToNumber(data, offset, 4)
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Trigger callback:
|
||
--------------------------------------------------------------------------------
|
||
if protocolRev and numberOfPanels and validCallback() then
|
||
local panels = {}
|
||
for _ = 1,numberOfPanels do
|
||
local currentPanelID, currentPanelType
|
||
currentPanelType, offset = byteStringToNumber(data, offset, 4)
|
||
currentPanelID, offset = byteStringToNumber(data, offset, 4)
|
||
table.insert(panels, {
|
||
panelID = currentPanelID,
|
||
panelType = getPanelType(currentPanelType),
|
||
})
|
||
end
|
||
|
||
return {
|
||
protocolRev = protocolRev,
|
||
numberOfPanels = numberOfPanels,
|
||
panels = panels,
|
||
}, offset
|
||
else
|
||
return nil, ERROR_OFFSET
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- ParameterChange (0x02)
|
||
-- * Requests that the application increment a parameter. The application needs
|
||
-- to constrain the value to remain within its maximum and minimum values.
|
||
-- * On receipt the application should respond to the Hub with the new
|
||
-- absolute parameter value using the ParameterValue (0x82) command,
|
||
-- if the value has changed.
|
||
--
|
||
-- Format: 0x02, <paramID>, <increment>
|
||
--
|
||
-- paramID: The ID value of the parameter (Unsigned Int)
|
||
-- increment: The incremental value which should be applied to the parameter (Float)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.parameterChange] = function(data, offset)
|
||
local paramID, increment
|
||
paramID, offset = byteStringToNumber(data, offset, 4)
|
||
increment, offset = byteStringToFloat(data, offset)
|
||
if paramID and increment and validCallback() then
|
||
return {
|
||
paramID = paramID,
|
||
increment = increment,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating parameterChange.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- ParameterReset (0x03)
|
||
-- * Requests that the application changes a parameter to its reset value.
|
||
-- * On receipt the application should respond to the Hub with the new absolute
|
||
-- parameter value using the ParameterValue (0x82) command, if the value
|
||
-- has changed.
|
||
--
|
||
-- Format: 0x03, <paramID>
|
||
--
|
||
-- paramID: The ID value of the parameter (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.parameterReset] = function(data, offset)
|
||
local paramID
|
||
paramID, offset = byteStringToNumber(data, offset, 4)
|
||
if paramID then
|
||
return {
|
||
paramID = paramID,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating parameterReset.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- ParameterValueRequest (0x04)
|
||
-- * Requests that the application sends a ParameterValue (0x82) command
|
||
-- to the Hub.
|
||
--
|
||
-- Format: 0x04, <paramID>
|
||
--
|
||
-- paramID: The ID value of the parameter (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.parameterValueRequest] = function(data, offset)
|
||
local paramID
|
||
paramID, offset = byteStringToNumber(data, offset, 4)
|
||
if paramID then
|
||
return {
|
||
paramID = paramID,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating parameterValueRequest.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- MenuChange (0x05)
|
||
-- * Requests the application change a menu index by +1 or -1.
|
||
-- * We recommend that menus that only have two values (e.g. on/off) should
|
||
-- toggle their state on receipt of either a +1 or -1 increment value.
|
||
-- This will allow a single button to toggle the state of such an item
|
||
-- without the need for separate ‘up’ and ‘down’ buttons.
|
||
--
|
||
-- Format: 0x05, <menuID>, <increment>
|
||
--
|
||
-- menuID: The ID value of the menu (Unsigned Int)
|
||
-- increment: The incremental amount by which the menu index should be changed which will always be an integer value of +1 or -1 (Signed Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.menuChange] = function(data, offset)
|
||
local menuID, increment
|
||
menuID, offset = byteStringToNumber(data, offset, 4)
|
||
increment, offset = byteStringToNumber(data, offset, 4)
|
||
if menuID and increment then
|
||
return {
|
||
menuID = menuID,
|
||
increment = increment,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating menuChange.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- MenuReset (0x06)
|
||
-- * Requests that the application sends a MenuString (0x83) command to the Hub.
|
||
--
|
||
-- Format: 0x06, <menuID>
|
||
--
|
||
-- menuID: The ID value of the menu (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.menuReset] = function(data, offset)
|
||
local menuID
|
||
menuID, offset = byteStringToNumber(data, offset, 4)
|
||
if menuID then
|
||
return {
|
||
menuID = menuID,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating menuReset.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- MenuStringRequest (0x07)
|
||
-- * Requests that the application sends a MenuString (0x83) command to the Hub.
|
||
-- * On receipt, the application should respond to the Hub with the new menu
|
||
-- value using the MenuString (0x83) command, if the menu has changed.
|
||
--
|
||
-- Format: 0x07, <menuID>
|
||
--
|
||
-- menuID: The ID value of the menu (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.menuStringRequest] = function(data, offset)
|
||
local menuID
|
||
menuID, offset = byteStringToNumber(data, offset, 4)
|
||
if menuID then
|
||
return {
|
||
menuID = menuID,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating menuStringRequest.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Action On (0x08)
|
||
-- * Requests that the application performs the specified action.
|
||
--
|
||
-- Format: 0x08, <actionID>
|
||
--
|
||
-- actionID: The ID value of the action (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.actionOn] = function(data, offset)
|
||
local actionID
|
||
actionID, offset = byteStringToNumber(data, offset, 4)
|
||
if actionID then
|
||
return {
|
||
actionID = actionID,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating actionOn.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- ModeChange (0x09)
|
||
-- * Requests that the application changes to the specified mode.
|
||
--
|
||
-- Format: 0x09, <modeID>
|
||
--
|
||
-- modeID: The ID value of the mode (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.modeChange] = function(data, offset)
|
||
local modeID
|
||
modeID, offset = byteStringToNumber(data, offset, 4)
|
||
if modeID then
|
||
return {
|
||
modeID = modeID,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating modeChange.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Transport (0x0A)
|
||
-- * Requests the application to move the currently active transport.
|
||
-- * jogValue or shuttleValue will never both be set simultaneously
|
||
-- * One revolution of the control represents 32 counts by default.
|
||
-- The user will be able to adjust the sensitivity of Jog & Shuttle
|
||
-- independently in the TUBE Mapper tool to send more or less than
|
||
-- 32 counts per revolution.
|
||
--
|
||
-- Format: 0x0A, <jogValue>, <shuttleValue>
|
||
--
|
||
-- jogValue: The number of jog steps to move the transport (Signed Int)
|
||
-- shuttleValue: An incremental value to add to the shuttle speed (Signed Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.transport] = function(data, offset)
|
||
local jogValue, shuttleValue
|
||
jogValue, offset = byteStringToNumber(data, offset, 4, true)
|
||
shuttleValue, offset = byteStringToNumber(data, offset, 4, true)
|
||
if jogValue and shuttleValue then
|
||
return {
|
||
jogValue = jogValue,
|
||
shuttleValue = shuttleValue,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating transport.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- ActionOff (0x0B)
|
||
-- * Requests that the application cancels the specified action.
|
||
-- * This is typically sent when a button is released.
|
||
--
|
||
-- Format: 0x0B, <actionID>
|
||
--
|
||
-- actionID: The ID value of the action (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.actionOff] = function(data, offset)
|
||
local actionID
|
||
actionID, offset = byteStringToNumber(data, offset, 4)
|
||
if actionID then
|
||
return {
|
||
actionID = actionID,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating actionOff.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- UnmanagedPanelCapabilities (0x30)
|
||
-- * Only used when working in Unmanaged panel mode.
|
||
-- * Sent in response to a UnmanagedPanelCapabilitiesRequest (0xA0) command.
|
||
-- * The values returned are those given in the table in Section 18.
|
||
-- Panel Data for Unmanaged Mode.
|
||
--
|
||
-- Format: 0x30, <panelID>, <numButtons>, <numEncoders>, <numDisplays>, <numDisplayLines>, <numDisplayChars>
|
||
--
|
||
-- panelID: The ID of the panel as reported in the InitiateComms command (Unsigned Int)
|
||
-- numButtons: The number of buttons on the panel (Unsigned Int)
|
||
-- numEncoders: The number of encoders on the panel (Unsigned Int)
|
||
-- numDisplays: The number of displays on the panel (Unsigned Int)
|
||
-- numDisplayLines: The number of lines for each display on the panel (Unsigned Int)
|
||
-- numDisplayChars: The number of characters on each line of each display on the panel (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.unmanagedPanelCapabilities] = function(data, offset)
|
||
local panelID, numButtons, numEncoders, numDisplays, numDisplayLines, numDisplayChars
|
||
panelID, offset = byteStringToNumber(data, offset, 4)
|
||
numButtons, offset = byteStringToNumber(data, offset, 4)
|
||
numEncoders, offset = byteStringToNumber(data, offset, 4)
|
||
numDisplays, offset = byteStringToNumber(data, offset, 4)
|
||
numDisplayLines, offset = byteStringToNumber(data, offset, 4)
|
||
numDisplayChars, offset = byteStringToNumber(data, offset, 4)
|
||
if panelID and numButtons and numEncoders and numDisplays and numDisplayLines and numDisplayChars then
|
||
return {
|
||
panelID = panelID,
|
||
numButtons = numButtons,
|
||
numEncoders = numEncoders,
|
||
numDisplays = numDisplays,
|
||
numDisplayLines = numDisplayLines,
|
||
numDisplayChars = numDisplayChars,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating unmanagedPanelCapabilities.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- UnmanagedButtonDown (0x31)
|
||
-- * Only used when working in Unmanaged panel mode
|
||
-- * Issued when a button has been pressed
|
||
--
|
||
-- Format: 0x31, <panelID>, <buttonID>
|
||
--
|
||
-- panelID: The ID of the panel as reported in the InitiateComms command (Unsigned Int)
|
||
-- buttonID: The hardware ID of the button (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.unmanagedButtonDown] = function(data, offset)
|
||
local panelID, buttonID
|
||
panelID, offset = byteStringToNumber(data, offset, 4)
|
||
buttonID, offset = byteStringToNumber(data, offset, 4)
|
||
if panelID and buttonID then
|
||
return {
|
||
panelID = panelID,
|
||
buttonID = buttonID,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating unmanagedButtonDown.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- UnmanagedButtonUp (0x32)
|
||
-- * Only used when working in Unmanaged panel mode.
|
||
-- * Issued when a button has been released
|
||
--
|
||
-- Format: 0x32, <panelID>, <buttonID>
|
||
--
|
||
-- panelID: The ID of the panel as reported in the InitiateComms command (Unsigned Int)
|
||
-- buttonID: The hardware ID of the button (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.unmanagedButtonUp] = function(data, offset)
|
||
local panelID, buttonID
|
||
panelID, offset = byteStringToNumber(data, offset, 4)
|
||
buttonID, offset = byteStringToNumber(data, offset, 4)
|
||
if panelID and buttonID then
|
||
return {
|
||
panelID = panelID,
|
||
buttonID = buttonID,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating unmanagedButtonUp.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- UnmanagedEncoderChange (0x33)
|
||
-- * Only used when working in Unmanaged panel mode.
|
||
-- * Issued when an encoder has been moved.
|
||
--
|
||
-- Format: 0x33, <panelID>, <encoderID>, <increment>
|
||
--
|
||
-- panelID: The ID of the panel as reported in the InitiateComms command (Unsigned Int)
|
||
-- paramID: The hardware ID of the encoder (Unsigned Int)
|
||
-- increment: The incremental value (Float)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.unmanagedEncoderChange] = function(data, offset)
|
||
local panelID, encoderID, increment
|
||
panelID, offset = byteStringToNumber(data, offset, 4)
|
||
encoderID, offset = byteStringToNumber(data, offset, 4)
|
||
increment, offset = byteStringToFloat(data, offset)
|
||
if panelID and encoderID and increment then
|
||
return {
|
||
panelID = panelID,
|
||
encoderID = encoderID,
|
||
increment = increment,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating unmanagedEncoderChange.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- UnmanagedDisplayRefresh (0x34)
|
||
-- * Only used when working in Unmanaged panel mode
|
||
-- * Issued when a panel has been connected or the focus of the panel has
|
||
-- been returned to your application.
|
||
-- * On receipt your application should send all the current information to
|
||
-- each display on the panel in question.
|
||
--
|
||
-- Format: 0x34, <panelID>
|
||
--
|
||
-- panelID: The ID of the panel as reported in the InitiateComms command (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.unmanagedDisplayRefresh] = function(data, offset)
|
||
local panelID
|
||
panelID, offset = byteStringToNumber(data, offset, 4)
|
||
if panelID then
|
||
return {
|
||
panelID = panelID,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating unmanagedDisplayRefresh.")
|
||
end
|
||
end,
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- PanelConnectionState (0x35)
|
||
-- * Sent in response to a PanelConnectionStatesRequest (0xA5) command to
|
||
-- report the current connected/disconnected status of a configured panel.
|
||
--
|
||
-- Format: 0x35, <panelID>, <state>
|
||
--
|
||
-- panelID: The ID of the panel as reported in the InitiateComms command (Unsigned Int)
|
||
-- state: The connected state of the panel: 1 if connected, 0 if disconnected (Bool)
|
||
--------------------------------------------------------------------------------
|
||
[mod.fromHub.panelConnectionState] = function(data, offset)
|
||
local panelID, state
|
||
panelID, offset = byteStringToNumber(data, offset, 4)
|
||
state, offset = byteStringToBoolean(data, offset, 4)
|
||
if panelID and state then
|
||
return {
|
||
panelID = panelID,
|
||
state = state,
|
||
}, offset
|
||
else
|
||
return errorResponse("Error translating panelConnectionState.")
|
||
end
|
||
end,
|
||
}
|
||
|
||
-- processHubCommand(data) -> none
|
||
-- Function
|
||
-- Processes a single HUB Command.
|
||
--
|
||
-- Parameters:
|
||
-- * data - The raw data from the socket.
|
||
--
|
||
-- Returns:
|
||
-- * None
|
||
local function processHubCommand(data, offset)
|
||
local id, command
|
||
|
||
id, offset = byteStringToNumber(data, offset, 4)
|
||
-- log.df("Processing command %#010x, offset: %d", id, offset)
|
||
|
||
local fn = receiveHandler[id]
|
||
if fn then
|
||
local result
|
||
result, offset = fn(data, offset)
|
||
if offset == ERROR_OFFSET then
|
||
command = {
|
||
id = ERROR_OFFSET,
|
||
metadata = {
|
||
details = format("Error while processing command ID: %#010x", id),
|
||
data = data,
|
||
offset = offset,
|
||
}
|
||
}
|
||
else
|
||
command = {
|
||
id = id,
|
||
metadata = result
|
||
}
|
||
end
|
||
else
|
||
command = {
|
||
id = ERROR_OFFSET,
|
||
metadata = {
|
||
details = format("Unrecognised command ID: %#010x", id),
|
||
data = data,
|
||
offset = offset,
|
||
}
|
||
}
|
||
end
|
||
|
||
return command, offset
|
||
end
|
||
|
||
-- processDataFromHub(data) -> none
|
||
-- Function
|
||
-- Separates multiple Hub Commands for processing.
|
||
--
|
||
-- Parameters:
|
||
-- * data - The raw data from the socket.
|
||
--
|
||
-- Returns:
|
||
-- * None
|
||
local function processDataFromHub(data)
|
||
if not validCallback() then
|
||
--------------------------------------------------------------------------------
|
||
-- There's no callback setup, so abort:
|
||
--------------------------------------------------------------------------------
|
||
return
|
||
end
|
||
local commands = {}
|
||
local len = string.len(data)
|
||
local offset = 1
|
||
while offset > 0 and offset < len do
|
||
local command
|
||
command, offset = processHubCommand(data, offset)
|
||
if command then
|
||
commands[#commands + 1] = command
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Process the buffer:
|
||
--------------------------------------------------------------------------------
|
||
processCommands(commands)
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- PRIVATE VARIABLES:
|
||
--------------------------------------------------------------------------------
|
||
|
||
-- hs.tangent._buffer -> table
|
||
-- Variable
|
||
-- The commands buffer.
|
||
mod._buffer = {}
|
||
|
||
-- hs.tangent._readBytesRemaining -> number
|
||
-- Variable
|
||
-- Number of read bytes remaining.
|
||
mod._readBytesRemaining = 0
|
||
|
||
-- hs.tangent._applicationName -> number
|
||
-- Variable
|
||
-- Application name as specified in `hs.tangent.connect()`
|
||
mod._applicationName = nil
|
||
|
||
-- hs.tangent._systemPath -> number
|
||
-- Variable
|
||
-- A string containing the absolute path of the directory that contains the Controls and Default Map XML files.
|
||
mod._systemPath = nil
|
||
|
||
-- hs.tangent._userPath -> number
|
||
-- Variable
|
||
-- A string containing the absolute path of the directory that contains the User’s Default Map XML files.
|
||
mod._userPath = nil
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- PUBLIC FUNCTIONS & METHODS:
|
||
--------------------------------------------------------------------------------
|
||
|
||
--- hs.tangent.ipAddress -> number
|
||
--- Variable
|
||
--- IP Address that the Tangent Hub is located at. Defaults to 127.0.0.1.
|
||
mod.ipAddress = "127.0.0.1"
|
||
|
||
--- hs.tangent.port -> number
|
||
--- Variable
|
||
--- The port that Tangent Hub monitors. Defaults to 64246.
|
||
mod.port = 64246
|
||
|
||
--- hs.tangent.automaticallySendApplicationDefinition -> boolean
|
||
--- Variable
|
||
--- Automatically send the "Application Definition" response. Defaults to `true`.
|
||
mod.automaticallySendApplicationDefinition = true
|
||
|
||
--- hs.tangent.setLogLevel(loglevel) -> none
|
||
--- Function
|
||
--- Sets the Log Level.
|
||
---
|
||
--- Parameters:
|
||
--- * loglevel - can be 'nothing', 'error', 'warning', 'info', 'debug', or 'verbose'; or a corresponding number between 0 and 5
|
||
---
|
||
--- Returns:
|
||
--- * None
|
||
function mod.setLogLevel(loglevel)
|
||
log:setLogLevel(loglevel)
|
||
socket.setLogLevel(loglevel)
|
||
end
|
||
|
||
--- hs.tangent.isTangentHubInstalled() -> boolean
|
||
--- Function
|
||
--- Checks to see whether or not the Tangent Hub software is installed.
|
||
---
|
||
--- Parameters:
|
||
--- * None
|
||
---
|
||
--- Returns:
|
||
--- * `true` if Tangent Hub is installed otherwise `false`.
|
||
function mod.isTangentHubInstalled()
|
||
if doesFileExist("/Library/Application Support/Tangent/Hub/TangentHub") then
|
||
return true
|
||
else
|
||
return false
|
||
end
|
||
end
|
||
|
||
--- hs.tangent.callback() -> boolean
|
||
--- Function
|
||
--- Sets a callback when new messages are received.
|
||
---
|
||
--- Parameters:
|
||
--- * callbackFn - a function to set as the callback for `hs.tangent`. If the value provided is `nil`, any currently existing callback function is removed.
|
||
---
|
||
--- Returns:
|
||
--- * `true` if successful otherwise `false`
|
||
---
|
||
--- Notes:
|
||
--- * Full documentation for the Tangent API can be downloaded [here](http://www.tangentwave.co.uk/download/developer-support-pack/).
|
||
--- * The callback function should expect 1 argument and should not return anything.
|
||
--- * The 1 argument will be a table, which can contain one or many commands. Each command is it's own table with the following contents:
|
||
--- * id - the message ID of the incoming message
|
||
--- * metadata - A table of data for the Tangent command (see below).
|
||
--- * The metadata table will return the following, depending on the `id` for the callback:
|
||
--- * `connected` - Connection to Tangent Hub successfully established.
|
||
--- * `disconnected` - The connection to Tangent Hub was dropped.
|
||
--- * `initiateComms` - Initiates communication between the Hub and the application.
|
||
--- * `protocolRev` - The revision number of the protocol.
|
||
--- * `numPanels` - The number of panels connected.
|
||
--- * `panels`
|
||
--- * `panelID` - The ID of the panel.
|
||
--- * `panelType` - The type of panel connected.
|
||
--- * `data` - The raw data from the Tangent Hub
|
||
--- * `parameterChange` - Requests that the application increment a parameter.
|
||
--- * `paramID` - The ID value of the parameter.
|
||
--- * `increment` - The incremental value which should be applied to the parameter.
|
||
--- * `parameterReset` - Requests that the application changes a parameter to its reset value.
|
||
--- * `paramID` - The ID value of the parameter.
|
||
--- * `parameterValueRequest` - Requests that the application sends a `ParameterValue (0x82)` command to the Hub.
|
||
--- * `paramID` - The ID value of the parameter.
|
||
--- * `menuChange` - Requests the application change a menu index by +1 or -1.
|
||
--- * `menuID` - The ID value of the menu.
|
||
--- * `increment` - The incremental amount by which the menu index should be changed which will always be an integer value of +1 or -1.
|
||
--- * `menuReset` - Requests that the application changes a menu to its reset value.
|
||
--- * `menuID` - The ID value of the menu.
|
||
--- * `menuStringRequest` - Requests that the application sends a `MenuString (0x83)` command to the Hub.
|
||
--- * `menuID` - The ID value of the menu.
|
||
--- * `actionOn` - Requests that the application performs the specified action.
|
||
--- * `actionID` - The ID value of the action.
|
||
--- * `modeChange` - Requests that the application changes to the specified mode.
|
||
--- * `modeID` - The ID value of the mode.
|
||
--- * `transport` - Requests the application to move the currently active transport.
|
||
--- * `jogValue` - The number of jog steps to move the transport.
|
||
--- * `shuttleValue` - An incremental value to add to the shuttle speed.
|
||
--- * `actionOff` - Requests that the application cancels the specified action.
|
||
--- * `actionID` - The ID value of the action.
|
||
--- * `unmanagedPanelCapabilities` - Only used when working in Unmanaged panel mode. Sent in response to a `UnmanagedPanelCapabilitiesRequest (0xA0)` command.
|
||
--- * `panelID` - The ID of the panel as reported in the `InitiateComms` command.
|
||
--- * `numButtons` - The number of buttons on the panel.
|
||
--- * `numEncoders` - The number of encoders on the panel.
|
||
--- * `numDisplays` - The number of displays on the panel.
|
||
--- * `numDisplayLines` - The number of lines for each display on the panel.
|
||
--- * `numDisplayChars` - The number of characters on each line of each display on the panel.
|
||
--- * `unmanagedButtonDown` - Only used when working in Unmanaged panel mode. Issued when a button has been pressed.
|
||
--- * `panelID` - The ID of the panel as reported in the `InitiateComms` command.
|
||
--- * `buttonID` - The hardware ID of the button
|
||
--- * `unmanagedButtonUp` - Only used when working in Unmanaged panel mode. Issued when a button has been released.
|
||
--- * `panelID` - The ID of the panel as reported in the `InitiateComms` command.
|
||
--- * `buttonID` - The hardware ID of the button.
|
||
--- * `unmanagedEncoderChange` - Only used when working in Unmanaged panel mode. Issued when an encoder has been moved.
|
||
--- * `panelID` - The ID of the panel as reported in the `InitiateComms` command.
|
||
--- * `paramID` - The hardware ID of the encoder.
|
||
--- * `increment` - The incremental value.
|
||
--- * `unmanagedDisplayRefresh` - Only used when working in Unmanaged panel mode. Issued when a panel has been connected or the focus of the panel has been returned to your application.
|
||
--- * `panelID` - The ID of the panel as reported in the `InitiateComms` command.
|
||
--- * `panelConnectionState`
|
||
--- * `panelID` - The ID of the panel as reported in the `InitiateComms` command.
|
||
--- * `state` - The connected state of the panel, `true` if connected, `false` if disconnected.
|
||
function mod.callback(callbackFn)
|
||
if type(callbackFn) == "function" then
|
||
mod._callback = callbackFn
|
||
return true
|
||
elseif type(callbackFn) == "nil" then
|
||
mod._callback = nil
|
||
return true
|
||
else
|
||
log.ef("Callback recieved an invalid type: %s", type(callbackFn))
|
||
return false
|
||
end
|
||
end
|
||
|
||
--- hs.tangent.connected() -> boolean
|
||
--- Function
|
||
--- Checks to see whether or not you're successfully connected to the Tangent Hub.
|
||
---
|
||
--- Parameters:
|
||
--- * None
|
||
---
|
||
--- Returns:
|
||
--- * `true` if connected, otherwise `false`
|
||
function mod.connected()
|
||
return mod._socket ~= nil and mod._socket:connected()
|
||
end
|
||
|
||
--- hs.tangent.send(byteString) -> boolean, string
|
||
--- Function
|
||
--- Sends a "bytestring" message to the Tangent Hub.
|
||
---
|
||
--- Parameters:
|
||
--- * byteString - The string of bytes to send to tangent.
|
||
---
|
||
--- Returns:
|
||
--- * success - `true` if connected, otherwise `false`
|
||
--- * errorMessage - An error message if an error occurs, as a string
|
||
---
|
||
--- Notes:
|
||
--- * This should be a full encoded string for the command you want to send, withouth the leading 'size' section, which the function will calculate automatically.
|
||
--- * In general, you should use the more specific functions that package the command for you, such as `sendParameterValue(...)`. This function can be used to send a message that this API doesn't yet support.
|
||
--- * Full documentation for the Tangent API can be downloaded [here](http://www.tangentwave.co.uk/download/developer-support-pack/).
|
||
function mod.send(byteString)
|
||
if mod.connected() then
|
||
if byteString == nil or #byteString == 0 then
|
||
return false, "No byte string provided"
|
||
end
|
||
|
||
mod._socket:send(numberToByteString(#byteString)..byteString)
|
||
return true
|
||
end
|
||
return false, "Not connected"
|
||
end
|
||
|
||
--- hs.tangent.sendApplicationDefinition([appName, systemPath, userPath]) -> boolean, string
|
||
--- Function
|
||
--- Sends the application details to the Tangent Hub.
|
||
---
|
||
--- Parameters:
|
||
--- * appName - The human-readable name of the application.
|
||
--- * systemPath - A string containing the absolute path of the directory that contains the Controls and Default Map XML files (Path String)
|
||
--- * userPath - A string containing the absolute path of the directory that contains the User’s Default Map XML files (Path String)
|
||
---
|
||
--- Returns:
|
||
--- * `true` if successful, `false` and an error message if there was a problem.
|
||
---
|
||
--- Notes:
|
||
--- * If no details are provided the ones stored in the module are used.
|
||
function mod.sendApplicationDefinition(appName, systemPath, userPath)
|
||
appName = appName or mod._applicationName
|
||
systemPath = systemPath or mod._systemPath
|
||
userPath = userPath or mod._userPath
|
||
|
||
if not appName then
|
||
return false, format("Missing or invalid application name: %s", inspect(appName))
|
||
end
|
||
if not systemPath or doesDirectoryExist(systemPath) == false then
|
||
return false, format("Missing or invalid system path: %s", inspect(systemPath))
|
||
end
|
||
if userPath and doesDirectoryExist(userPath) == false then
|
||
return false, format("Missing or invalid userPath: %s", inspect(userPath))
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Format: 0x81, <appStrLen>, < appStr>, <sysDirStrLen>, <sysDirStr>, <userDirStrLen>, <userDirStr>
|
||
--
|
||
-- appStrLen: The length of appStr (Unsigned Int)
|
||
-- appStr: A string containing the name of the application (Character String)
|
||
-- sysDirStrLen: The length of sysDirStr (Unsigned Int)
|
||
-- sysDirStr: A string containing the absolute path of the directory that contains the Controls and Default Map XML files (Path String)
|
||
-- usrDirStrLen: The length of usrDirStr (Unsigned Int)
|
||
-- usrDirStr: A string containing the absolute path of the directory that contains the User’s Default Map XML files (Path String)
|
||
--------------------------------------------------------------------------------
|
||
local byteString = numberToByteString(mod.toHub.applicationDefinition) ..
|
||
numberToByteString(#appName) ..
|
||
appName ..
|
||
numberToByteString(#systemPath) ..
|
||
systemPath ..
|
||
numberToByteString(userPath and #userPath or 0) ..
|
||
(userPath ~= nil and userPath or "")
|
||
|
||
return mod.send(byteString)
|
||
end
|
||
|
||
--- hs.tangent.sendParameterValue(paramID, value[, atDefault]) -> boolean, string
|
||
--- Function
|
||
--- Updates the Hub with a parameter value.
|
||
---
|
||
--- Parameters:
|
||
--- * paramID - The ID value of the parameter (Unsigned Int)
|
||
--- * value - The current value of the parameter (Float)
|
||
--- * atDefault - if `true` the value represents the default. Defaults to `false`.
|
||
---
|
||
--- Returns:
|
||
--- * `true` if successful, or `false` and an error message if not.
|
||
---
|
||
--- Notes:
|
||
--- * The Hub then updates the displays of any panels which are currently showing the parameter value.
|
||
function mod.sendParameterValue(paramID, value, atDefault)
|
||
--------------------------------------------------------------------------------
|
||
-- Format: 0x82, <paramID>, <value>, <atDefault>
|
||
--
|
||
-- paramID: The ID value of the parameter (Unsigned Int)
|
||
-- value: The current value of the parameter (Float)
|
||
-- atDefault: True if the value represents the default. Otherwise false (Bool)
|
||
--------------------------------------------------------------------------------
|
||
if not paramID then
|
||
return false, format("Missing or invalid parameter ID: %s", inspect(paramID))
|
||
end
|
||
if not value or type(value) ~= "number" then
|
||
return false, format("Missing or invalid value: %s", inspect(value))
|
||
end
|
||
atDefault = atDefault == true
|
||
|
||
local byteString = numberToByteString(mod.toHub.parameterValue) ..
|
||
numberToByteString(paramID) ..
|
||
floatToByteString(value) ..
|
||
booleanToByteString(atDefault)
|
||
|
||
return mod.send(byteString)
|
||
end
|
||
|
||
--- hs.tangent.sendMenuString(menuID, value[, atDefault]) -> boolean, string
|
||
--- Function
|
||
--- Updates the Hub with a menu value.
|
||
---
|
||
--- Parameters:
|
||
--- * menuID - The ID value of the menu (Unsigned Int)
|
||
--- * value - The current ‘value’ of the parameter represented as a string
|
||
--- * atDefault - if `true` the value represents the default. Otherwise `false`.
|
||
---
|
||
--- Returns:
|
||
--- * `true` if successful, or `false` and an error message if not.
|
||
---
|
||
--- Notes:
|
||
--- * The Hub then updates the displays of any panels which are currently showing the menu.
|
||
--- * If a value of `nil` is sent then the Hub will not attempt to display a value for the menu. However the `atDefault` flag will still be recognised.
|
||
function mod.sendMenuString(menuID, value, atDefault)
|
||
--------------------------------------------------------------------------------
|
||
-- Format: 0x83, <menuID>, <valueStrLen>, <valueStr>, <atDefault>
|
||
--
|
||
-- menuID: The ID value of the menu (Unsigned Int)
|
||
-- valueStrLen: The length of valueStr (Unsigned Int)
|
||
-- valueStr: The current ‘value’ of the parameter represented as a string (Character String)
|
||
-- atDefault: True if the value represents the default. Otherwise false (Bool)
|
||
--------------------------------------------------------------------------------
|
||
if not type(menuID) == "number" then
|
||
return false, format("Missing or invalid menuID: %s", inspect(menuID))
|
||
end
|
||
value = value or ""
|
||
atDefault = atDefault == true
|
||
|
||
local byteString = numberToByteString(mod.toHub.menuString) ..
|
||
numberToByteString(menuID) ..
|
||
numberToByteString(#value) ..
|
||
value ..
|
||
booleanToByteString(atDefault)
|
||
|
||
return mod.send(byteString)
|
||
end
|
||
|
||
--- hs.tangent.sendAllChange() -> boolean, string
|
||
--- Function
|
||
--- Tells the Hub that a large number of software-controls have changed.
|
||
---
|
||
--- Parameters:
|
||
--- * None
|
||
---
|
||
--- Returns:
|
||
--- * `true` if successful, or `false` and an error message if not.
|
||
---
|
||
--- Notes:
|
||
--- * The Hub responds by requesting all the current values of software-controls it is currently controlling.
|
||
function mod.sendAllChange()
|
||
--------------------------------------------------------------------------------
|
||
-- Format: 0x84
|
||
--------------------------------------------------------------------------------
|
||
local byteString = numberToByteString(mod.toHub.allChange)
|
||
return mod.send(byteString)
|
||
end
|
||
|
||
--- hs.tangent.sendModeValue(modeID) -> boolean, string
|
||
--- Function
|
||
--- Updates the Hub with a mode value.
|
||
---
|
||
--- Parameters:
|
||
--- * modeID - The ID value of the mode (Unsigned Int)
|
||
---
|
||
--- Returns:
|
||
--- * `true` if successful, or `false` and an error message if not.
|
||
---
|
||
--- Notes:
|
||
--- * The Hub then changes mode and requests all the current values of software-controls it is controlling.
|
||
function mod.sendModeValue(modeID)
|
||
--------------------------------------------------------------------------------
|
||
-- Format: 0x85, <modeID>
|
||
--
|
||
-- modeID: The ID value of the mode (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
if not isNumber(modeID) then
|
||
return false, format("Missing or invalid `modeID`: %s", inspect(modeID))
|
||
end
|
||
local byteString = numberToByteString(mod.toHub.modeValue) ..
|
||
numberToByteString(modeID)
|
||
|
||
return mod.send(byteString)
|
||
end
|
||
|
||
--- hs.tangent.sendDisplayText(messages[, doubleHeight]) -> boolean, string
|
||
--- Function
|
||
--- Updates the Hub with a number of character strings that will be displayed on connected panels if there is space.
|
||
---
|
||
--- Parameters:
|
||
--- * messages - A list of messages to send.
|
||
--- * doubleHeight - An optional list of `boolean`s indicating if the corresponding message is double-height.
|
||
---
|
||
--- Returns:
|
||
--- * `true` if successful, or `false` and an error message if not.
|
||
---
|
||
--- Notes:
|
||
--- * Strings may either be 32 character, single height or 16 character double-height. They will be displayed in the order received; the first string displayed at the top of the display.
|
||
--- * If a string is not defined as double-height then it will occupy the next line.
|
||
--- * If a string is defined as double-height then it will occupy the next 2 lines.
|
||
--- * The maximum number of lines which will be used by the application must be indicated in the Controls XML file.
|
||
--- * Text which exceeds 32 (single-height) or 16 (double-height) characters will be truncated.
|
||
--- * If all text is single-height, the `doubleHeight` table can be omitted.
|
||
---
|
||
--- Examples:
|
||
---
|
||
--- ```lua
|
||
--- hs.tangent.sendDisplayText(
|
||
--- { "Single Height", "Double Height" }, {false, true}
|
||
--- )```
|
||
function mod.sendDisplayText(messages, doubleHeight)
|
||
--------------------------------------------------------------------------------
|
||
-- DisplayText (0x86)
|
||
-- * Updates the Hub with a number of character strings that will be displayed
|
||
-- on connected panels if there is space.
|
||
-- * Strings may either be 32 character, single height or 16 character
|
||
-- double-height. They will be displayed in the order received; the first
|
||
-- string displayed at the top of the display.
|
||
-- * If a string is not defined as double-height then it will occupy the
|
||
-- next line.
|
||
-- * If a string is defined as double-height then it will occupy the next
|
||
-- 2 lines.
|
||
-- * The maximum number of lines which will be used by the application
|
||
-- must be indicated in the Controls XML file.
|
||
-- * If a stateStrLen value of 0 is passed then the line will not be
|
||
-- overwritten with any information. In this circumstance no data should be
|
||
-- passed for stateStr and doubleHeight. The next byte will be the
|
||
-- stateStrLen for the next string.
|
||
--
|
||
-- Format: 0x86, <numStrings>, (<stateStrLen>, <stateStr>, <doubleHeight>)...
|
||
--
|
||
-- numStrings: The number of strings to follow (Unsigned Int)
|
||
-- stateStrLen: The length of stateStr (Unsigned Int)
|
||
-- stateStr: A line of status text (Character String)
|
||
-- doubleHeight: True if the string is to be printed double height. Otherwise false (Bool)
|
||
--------------------------------------------------------------------------------
|
||
if isNotList(messages) then
|
||
return false, format("The `messages` must be a list of strings: %s", inspect(messages))
|
||
end
|
||
doubleHeight = doubleHeight or {}
|
||
if isNotTable(doubleHeight) then
|
||
return false, format("Invalid `doubleHeight` parameter: %s", inspect(doubleHeight))
|
||
end
|
||
|
||
local byteString = numberToByteString(mod.toHub.displayText) ..
|
||
numberToByteString(#messages)
|
||
|
||
for i,value in ipairs(messages) do
|
||
--------------------------------------------------------------------------------
|
||
-- Trim to size:
|
||
--------------------------------------------------------------------------------
|
||
if not type(value) == "string" then
|
||
return false, format("Invalid message #%s: %s", i, inspect(value))
|
||
end
|
||
local isDouble = doubleHeight[i]
|
||
local maxLength = isDouble and 16 or 32
|
||
value = #value > maxLength and value:sub(0, maxLength) or value
|
||
|
||
byteString = byteString .. numberToByteString(#value)
|
||
|
||
if #value > 0 then
|
||
byteString = byteString .. value .. booleanToByteString(isDouble)
|
||
end
|
||
end
|
||
|
||
return mod.send(byteString)
|
||
end
|
||
|
||
--- hs.tangent.sendUnmanagedPanelCapabilitiesRequest(panelID) -> boolean, string
|
||
--- Function
|
||
--- Requests the Hub to respond with an UnmanagedPanelCapabilities (0x30) command.
|
||
---
|
||
--- Parameters:
|
||
--- * panelID - The ID of the panel as reported in the InitiateComms command (Unsigned Int)
|
||
---
|
||
--- Returns:
|
||
--- * `true` if successful, or `false` and an error message if not.
|
||
---
|
||
--- Notes:
|
||
--- * Only used when working in Unmanaged panel mode
|
||
function mod.sendUnmanagedPanelCapabilitiesRequest(panelID)
|
||
--------------------------------------------------------------------------------
|
||
-- Format: 0xA0, <panelID>
|
||
--
|
||
-- panelID: The ID of the panel as reported in the InitiateComms command (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
if not isNumber(panelID) then
|
||
return false, format("Missing or invalid panel ID: %s", inspect(panelID))
|
||
end
|
||
local byteString = numberToByteString(mod.toHub.unmanagedPanelCapabilitiesRequest) ..
|
||
numberToByteString(panelID)
|
||
|
||
return mod.send(byteString)
|
||
end
|
||
|
||
--- hs.tangent.sendUnmanagedDisplayWrite(panelID, displayID, lineNum, pos, message) -> boolean, string
|
||
--- Function
|
||
--- Updates the Hub with text that will be displayed on a specific panel at the given line and starting position where supported by the panel capabilities.
|
||
---
|
||
--- Parameters:
|
||
--- * panelID - The ID of the panel as reported in the InitiateComms command (Unsigned Int)
|
||
--- * displayID - The ID of the display to be written to (Unsigned Int)
|
||
--- * lineNum - The line number of the display to be written to with `1` as the top line (Unsigned Int)
|
||
--- * pos - The position on the line to start writing from with `1` as the first column (Unsigned Int)
|
||
--- * message - A line of text (Character String)
|
||
---
|
||
--- Returns:
|
||
--- * `true` if successful, or `false` and an error message if not.
|
||
---
|
||
--- Notes:
|
||
--- * Only used when working in Unmanaged panel mode.
|
||
--- * If the most significant bit of any individual text character in `message` is set it will be displayed as inversed with dark text on a light background.
|
||
function mod.sendUnmanagedDisplayWrite(panelID, displayID, lineNum, pos, message)
|
||
--------------------------------------------------------------------------------
|
||
-- Format: 0xA1, <panelID>, <displayID>, <lineNum>, <pos>, <dispStrLen>, <dispStr>
|
||
--
|
||
-- panelID: The ID of the panel as reported in the InitiateComms command (Unsigned Int)
|
||
-- displayID: The ID of the display to be written to (Unsigned Int)
|
||
-- lineNum: The line number of the display to be written to with 0 as the top line (Unsigned Int)
|
||
-- pos: The position on the line to start writing from with 0 as the first column (Unsigned Int)
|
||
-- dispStrLen: The length of dispStr (Unsigned Int)
|
||
-- dispStr: A line of text (Character String)
|
||
--------------------------------------------------------------------------------
|
||
if not isNumber(panelID) then
|
||
return false, format("Missing or invalid panelID: %s", inspect(panelID))
|
||
end
|
||
if not isNumber(displayID) then
|
||
return false, format("Missing or invalid displayID: %s", inspect(displayID))
|
||
end
|
||
if not isNumber(lineNum) or lineNum < 1 then
|
||
return false, format("Missing or invalid lineNum: %s", inspect(lineNum))
|
||
end
|
||
if not isNumber(pos) or pos < 1 then
|
||
return false, format("Missing or invalid pos: %s", inspect(pos))
|
||
end
|
||
if not type(message) == "string" then
|
||
return false, format("Missing or invalid message: %s", inspect(message))
|
||
end
|
||
|
||
local byteString = numberToByteString(mod.toHub.unmanagedDisplayWrite) ..
|
||
numberToByteString(panelID) ..
|
||
numberToByteString(displayID) ..
|
||
numberToByteString(lineNum-1) ..
|
||
numberToByteString(pos-1) ..
|
||
numberToByteString(#message) ..
|
||
message
|
||
|
||
return mod.send(byteString)
|
||
end
|
||
|
||
--- hs.tangent.sendRenameControl(targetID, newName) -> boolean, string
|
||
--- Function
|
||
--- Renames a control dynamically.
|
||
---
|
||
--- Parameters:
|
||
--- * targetID - The id of any application defined Parameter, Menu, Action or Mode (Unsigned Int)
|
||
--- * newName - The new name to apply.
|
||
---
|
||
--- Returns:
|
||
--- * `true` if successful, `false` and an error message if not.
|
||
---
|
||
--- Notes:
|
||
--- * The string supplied will replace the normal text which has been derived from the Controls XML file.
|
||
--- * To remove any existing replacement name set `newName` to `""`, this will remove any renaming and return the system to the normal display text
|
||
--- * When applied to Modes, the string displayed on buttons which mapped to the reserved "Go To Mode" action for this particular mode will also change.
|
||
function mod.sendRenameControl(targetID, newName)
|
||
--------------------------------------------------------------------------------
|
||
-- Format: 0xA2, <targetID>, <nameStrLen>, <nameStr>
|
||
--
|
||
-- targetID: The id of any application defined Parameter, Menu, Action or Mode (Unsigned Int)
|
||
-- nameStrLen: The length of nameStr (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
if not isNumber(targetID) then
|
||
return false, format("Missing or invalid targetID: %s", inspect(targetID))
|
||
end
|
||
if not type(newName) == "string" then
|
||
return false, format("Missing or invalid name: %s", inspect(newName))
|
||
end
|
||
|
||
local byteString = numberToByteString(mod.toHub.renameControl) ..
|
||
numberToByteString(targetID) ..
|
||
numberToByteString(#newName) ..
|
||
newName
|
||
|
||
return mod.send(byteString)
|
||
end
|
||
|
||
--- hs.tangent.sendHighlightControl(targetID, active) -> boolean, string
|
||
--- Function
|
||
--- Highlights the control on any panel where this feature is available.
|
||
---
|
||
--- Parameters:
|
||
--- * targetID - The id of any application defined Parameter, Menu, Action or Mode (Unsigned Int)
|
||
--- * active - If `true`, the control is highlighted, otherwise it is not.
|
||
---
|
||
--- Returns:
|
||
--- * `true` if sent successfully, `false` and an error message if no.
|
||
---
|
||
--- Notes:
|
||
--- * When applied to Modes, buttons which are mapped to the reserved "Go To Mode" action for this particular mode will highlight.
|
||
function mod.sendHighlightControl(targetID, active)
|
||
--------------------------------------------------------------------------------
|
||
-- targetID: The id of any application defined Parameter, Menu, Action or Mode (Unsigned Int)
|
||
-- state: The state to set. 1 for highlighted, 0 for clear (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
if not isNumber(targetID) then
|
||
return false, "Missing or invalid paramater: targetID."
|
||
end
|
||
local state = active == true and 1 or 0
|
||
|
||
local byteString = numberToByteString(mod.toHub.highlightControl) ..
|
||
numberToByteString(targetID) ..
|
||
numberToByteString(state)
|
||
|
||
return mod.send(byteString)
|
||
end
|
||
|
||
--- hs.tangent.sendIndicateControl(targetID, indicated) -> boolean, string
|
||
--- Function
|
||
--- Sets the Indicator of the control on any panel where this feature is available.
|
||
---
|
||
--- Parameters:
|
||
--- * targetID - The id of any application defined Parameter, Menu, Action or Mode
|
||
--- * active - If `true`, the control is indicated, otherwise it is not.
|
||
---
|
||
--- Returns:
|
||
--- * `true` if sent successfully, `false` and an error message if no.
|
||
---
|
||
--- Notes:
|
||
--- * This indicator is driven by the `atDefault` argument for Parameters and Menus. This command therefore only applies to controls mapped to Actions and Modes.
|
||
--- * When applied to Modes, buttons which are mapped to the reserved "Go To Mode" action for this particular mode will have their indicator set.
|
||
function mod.sendIndicateControl(targetID, active)
|
||
--------------------------------------------------------------------------------
|
||
-- Format: 0xA4, <targetID>, <state>
|
||
--
|
||
-- targetID: The id of any application defined Action or Mode (Unsigned Int)
|
||
-- state: The state to set. 1 for indicated, 0 for clear (Unsigned Int)
|
||
--------------------------------------------------------------------------------
|
||
if not isNumber(targetID) then
|
||
return false, "Missing or invalid paramater: targetID."
|
||
end
|
||
local state = active == true and 1 or 0
|
||
|
||
local byteString = numberToByteString(mod.toHub.indicateControl) ..
|
||
numberToByteString(targetID) ..
|
||
numberToByteString(state)
|
||
|
||
return mod.send(byteString)
|
||
end
|
||
|
||
--- hs.tangent.sendPanelConnectionStatesRequest())
|
||
--- Function
|
||
--- Requests the Hub to respond with a sequence of PanelConnectionState (0x35) commands to report the connected/disconnected status of each configured panel
|
||
---
|
||
--- Parameters:
|
||
--- * None
|
||
---
|
||
--- Returns:
|
||
--- * `true` if sent successfully, `false` and an error message if not.
|
||
---
|
||
--- Notes:
|
||
--- * A single request may result in multiple state responses.
|
||
function mod.sendPanelConnectionStatesRequest()
|
||
--------------------------------------------------------------------------------
|
||
-- Format: 0xA5
|
||
--------------------------------------------------------------------------------
|
||
local byteString = numberToByteString(mod.toHub.panelConnectionStatesRequest)
|
||
|
||
return mod.send(byteString)
|
||
end
|
||
|
||
-- notifyDisconnected() -> none
|
||
-- Function
|
||
-- Triggers the disconnection notification callback and stops the Connection Watcher.
|
||
--
|
||
-- Parameters:
|
||
-- * None
|
||
--
|
||
-- Returns:
|
||
-- * None
|
||
local function notifyDisconnected()
|
||
if mod._callback then
|
||
mod._callback({{id=mod.fromHub.disconnected, metadata={
|
||
ipAddress = mod.ipAddress,
|
||
port = mod.port,
|
||
}}})
|
||
end
|
||
if mod._connectionWatcher then mod._connectionWatcher:stop() end
|
||
end
|
||
|
||
-- hs.tangent._connectionWatcher -> timer
|
||
-- Variable
|
||
-- Tracks the Tangent socket connection.
|
||
mod._connectionWatcher = timer.new(1.0, function()
|
||
if not mod.connected() then
|
||
mod._socket = nil
|
||
notifyDisconnected()
|
||
end
|
||
end)
|
||
|
||
--- hs.tangent.disconnect() -> none
|
||
--- Function
|
||
--- Disconnects from the Tangent Hub.
|
||
---
|
||
--- Parameters:
|
||
--- * None
|
||
---
|
||
--- Returns:
|
||
--- * None
|
||
function mod.disconnect()
|
||
if mod._socket then
|
||
mod._socket:disconnect()
|
||
mod._socket = nil
|
||
notifyDisconnected()
|
||
mod._connectionWatcher:stop()
|
||
end
|
||
end
|
||
|
||
-- MESSAGE_SIZE -> number
|
||
-- Constant
|
||
-- Message Size.
|
||
local MESSAGE_SIZE = 1
|
||
|
||
-- MESSAGE_BODY -> number
|
||
-- Constant
|
||
-- Message Body.
|
||
local MESSAGE_BODY = 2
|
||
|
||
-- socketCallback(data, tag) -> none
|
||
-- Function
|
||
-- Tangent Socket Callback Function.
|
||
--
|
||
-- Parameters:
|
||
-- * data - The data read from the socket as a string
|
||
-- * tag - The integer tag associated with the read call, which defaults to -1
|
||
--
|
||
-- Returns:
|
||
-- * None
|
||
local function socketCallback(data, tag)
|
||
--log.df("Received data: size=%s; tag=%s", #data, inspect(tag))
|
||
if tag == MESSAGE_SIZE then
|
||
--------------------------------------------------------------------------------
|
||
-- Each message starts with an integer value indicating the number of bytes.
|
||
--------------------------------------------------------------------------------
|
||
local messageSize = byteStringToNumber(data, 1, 4)
|
||
if mod._socket then
|
||
mod._socket:read(messageSize, MESSAGE_BODY)
|
||
else
|
||
log.ef("Tangent: The Socket doesn't exist anymore.")
|
||
end
|
||
elseif tag == MESSAGE_BODY then
|
||
--------------------------------------------------------------------------------
|
||
-- We've read the rest of series of commands:
|
||
--------------------------------------------------------------------------------
|
||
processDataFromHub(data)
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Get set up for the next series of commands:
|
||
--------------------------------------------------------------------------------
|
||
if mod._socket then
|
||
mod._socket:read(4, MESSAGE_SIZE)
|
||
else
|
||
log.ef("Tangent: The Socket doesn't exist anymore.")
|
||
end
|
||
else
|
||
log.ef("Tangent: Unknown Tag or Data from Socket.")
|
||
end
|
||
end
|
||
|
||
--- hs.tangent.connect(applicationName, systemPath[, userPath]) -> boolean, errorMessage
|
||
--- Function
|
||
--- Connects to the Tangent Hub.
|
||
---
|
||
--- Parameters:
|
||
--- * applicationName - Your application name as a string
|
||
--- * systemPath - A string containing the absolute path of the directory that contains the Controls and Default Map XML files.
|
||
--- * [userPath] - An optional string containing the absolute path of the directory that contains the User’s Default Map XML files.
|
||
---
|
||
--- Returns:
|
||
--- * success - `true` on success, otherwise `nil`
|
||
--- * errorMessage - The error messages as a string or `nil` if `success` is `true`.
|
||
function mod.connect(applicationName, systemPath, userPath)
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Check Paramaters:
|
||
--------------------------------------------------------------------------------
|
||
if not applicationName or type(applicationName) ~= "string" then
|
||
return nil, "applicationName is a required string."
|
||
end
|
||
if systemPath and type(systemPath) == "string" then
|
||
local attr = fs.attributes(systemPath)
|
||
if not attr or attr.mode ~= 'directory' then
|
||
return nil, "systemPath must be a valid path."
|
||
end
|
||
else
|
||
return nil, "systemPath is a required string."
|
||
end
|
||
if userPath and type(userPath) == "string" then
|
||
local attr = fs.attributes(userPath)
|
||
if not attr or attr.mode ~= 'directory' then
|
||
return nil, "userPath must be a valid path."
|
||
end
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Save values for later:
|
||
--------------------------------------------------------------------------------
|
||
mod._applicationName = applicationName
|
||
mod._systemPath = systemPath
|
||
mod._userPath = userPath
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Connect to Tangent Hub:
|
||
--------------------------------------------------------------------------------
|
||
mod._socket = socket.new()
|
||
if mod._socket then
|
||
mod._socket:setCallback(socketCallback)
|
||
:connect(mod.ipAddress, mod.port, function()
|
||
--------------------------------------------------------------------------------
|
||
-- Trigger Callback when connected:
|
||
--------------------------------------------------------------------------------
|
||
if mod._callback then
|
||
mod._callback({{id=mod.fromHub.connected, metadata={
|
||
ipAddress = mod.ipAddress,
|
||
port = mod.port,
|
||
}}})
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Watch for disconnections:
|
||
--------------------------------------------------------------------------------
|
||
mod._connectionWatcher:start()
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Read the first 4 bytes, which will trigger the callback:
|
||
--------------------------------------------------------------------------------
|
||
mod._socket:read(4, MESSAGE_SIZE)
|
||
end)
|
||
end
|
||
return mod._socket ~= nil or nil
|
||
|
||
end
|
||
|
||
return mod
|