Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update: Added option to use SmartThings to fetch device power state #679

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
ehthumbs.db
Thumbs.db
Icon
.vscode

node_modules
dist
33 changes: 20 additions & 13 deletions config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@
"wait_time": {"type": "integer"},
"api_key": {"type": "string"},
"device_id": {"type": "string"},

"use_smartthings_power": {"type": "boolean"},
"name": {
"type": "string",
"required": true
Expand Down Expand Up @@ -240,8 +240,8 @@
"default": "Bedroom TV"
},
{
"type": "help",
"helpvalue": "<p class='help-block'>Make sure that your device have a static IP address. You can set it from your router admin interface!</p>"
"type": "help",
"helpvalue": "<p class='help-block'>Make sure that your device has a static IP address. You can set it from your router admin interface!</p>"
},
{
"type": "flex",
Expand All @@ -264,8 +264,8 @@
"title": "SmartThings API",
"items": [
{
"type": "help",
"helpvalue": "<p class='help-block'>SmartThings API is <strong>optional</strong>. You will need to set these parameters only if you want to use inputs! Please <a href='https://tavicu.github.io/homebridge-samsung-tizen/configuration/smartthings-api.html' target='_blank'>follow this tutorial</a>.</p>"
"type": "help",
"helpvalue": "<p class='help-block'>SmartThings API is <strong>optional</strong> but highly recommended for more reliable power state (see <i>\"Use SmartThings for Power State\"</i>) detection and input management. If provided, it will be used for enhanced functionality such as fetching the power state more effectively and managing inputs. Please <a href='https://tavicu.github.io/homebridge-samsung-tizen/configuration/smartthings-api.html' target='_blank'>follow this tutorial</a> to set it up.</p>"
},
{
"key": "devices[].api_key",
Expand All @@ -274,7 +274,14 @@
{
"key": "devices[].device_id",
"title": "Device ID"
}
},
{
"key": "devices[].use_smartthings_power",
"type": "boolean",
"default": false,
"title": "Use SmartThings for Power State",
"description": "Enable to use the SmartThings API to check the power state of the TV. This could provide more accurate power readings. Ensure you have provided a valid API Key and Device ID."
}
]
},
{
Expand All @@ -285,7 +292,7 @@
"items": [
{
"type": "help",
"helpvalue": "<p class='help-block'>By default no inputs are set. Use this section to set your own inputs. You can find more informations on our <a href='https://tavicu.github.io/homebridge-samsung-tizen/features/inputs.html' target='_blank'>documentation</a>.</p>"
"helpvalue": "<p class='help-block'>By default, no inputs are set. Use this section to configure your own inputs. For more information, please refer to our <a href='https://tavicu.github.io/homebridge-samsung-tizen/features/inputs.html' target='_blank'>documentation</a>.</p>"
},
{
"type": "array",
Expand Down Expand Up @@ -355,7 +362,7 @@
"items": [
{
"type": "help",
"helpvalue": "<p class='help-block'>This section give you the option to create custom switches (separated accessories) that make specific actions. You can find more informations on our <a href='https://tavicu.github.io/homebridge-samsung-tizen/features/switches.html' target='_blank'>documentation</a>.</p>"
"helpvalue": "<p class='help-block'>This section allows you to create custom switches (separate accessories) that perform specific actions. For more information, please visit our <a href='https://tavicu.github.io/homebridge-samsung-tizen/features/switches.html' target='_blank'>documentation</a>.</p>"
},
{
"type": "array",
Expand Down Expand Up @@ -477,7 +484,7 @@
"wss": "Default",
"frame": "Frame"
},
"description": "The plugin detects the type of TV <b>automaticaly</b>. Leave this option set to <b>None</b> unless you are having problems and the plugin don't detect your TV type correctly."
"description": "The plugin detects the type of TV <b>automatically</b>. Leave this option set to <b>None</b> unless you are having problems and the plugin don't detect your TV type correctly."
},
{
"key": "devices[].uuid",
Expand All @@ -489,14 +496,14 @@
"key": "devices[].delay",
"title": "Command Delay Interval",
"placeholder": "400",
"description": "This is the delay between each command when you send multiple commands. By lowering the value you risk the commands not being executed. Value is in <b>miliseconds</b>."
"description": "This is the delay between each command when you send multiple commands. By lowering the value you risk the commands not being executed. Value is in <b>milliseconds</b>."
},
{
"key": "devices[].options",
"title": "Options",
"titleMap": {
"Switch.DeviceName.Disable": "Disable prepending device name on custom switches",
"Frame.RealPowerMode": "Display and control Real Power with Main Acccessory (for Frame TVs)",
"Switch.DeviceName.Disable": "Disable prepending the device name on custom switches",
"Frame.RealPowerMode": "Display and control Real Power with Main Accessory (for Frame TVs)",
"Frame.ArtSwitch.Disable": "Disable Art Switch (for Frame TVs)",
"Frame.PowerSwitch.Disable": "Disable Power Switch (for Frame TVs)"
}
Expand All @@ -511,7 +518,7 @@
"items": [
{
"type": "help",
"helpvalue": "<p class='help-block'>You don't have to edit this section unless you want to change the default commands for <b>Remote Control</b> buttons. You can find more informations on our <a href='https://tavicu.github.io/homebridge-samsung-tizen/features/keys.html' target='_blank'>documentation</a>.</p>",
"helpvalue": "<p class='help-block'>You do not need to edit this section unless you want to change the default commands for <b>Remote Control</b> buttons. For more information, please refer to our <a href='https://tavicu.github.io/homebridge-samsung-tizen/features/keys.html' target='_blank'>documentation</a>.</p>",
"flex": "1 1 100%"
},
{
Expand Down
3 changes: 2 additions & 1 deletion lib/device.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ module.exports = class Device extends EventEmitter {
wol : {},
options : [],
api_key : null,
device_id : null
device_id : null,
use_smartthings_power : false
},
Platform.config,
config
Expand Down
56 changes: 35 additions & 21 deletions lib/methods/base.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,21 +19,32 @@ module.exports = class Base {
this.cache = device.cache;
}

/**
* Cleans up resources or state.
*/
destroy() {
this.destroyed = true;
}

/**
* Simulates a pairing delay.
* @return {Promise<void>}
*/
pair() {
return utils.delay(500);
}

/**
* Get state of TV with cache
* @return {Cache}
* Retrieves the TV's power state by first attempting a network ping.
* If the ping succeeds, it validates the power state via HTTP.
* If the ping fails, it assumes the TV is off, unless previously recorded as on.
* @return {Promise<boolean>} Resolves with true if on, false if off.
*/
getState() {
return this.cache.get('state', () => this.getStatePing().then(status => {
this.device.log.debug(`Ping status: ${status}`);
if (status && this.device.storage.powerstate !== false) {
this.device.log.debug('Ping succeeded, fetching power state via HTTP...');
return this.cache.get('state-http', () => this.getStateHttp(status), 2500);
} else {
this.cache.forget('state-http');
Expand All @@ -44,24 +55,27 @@ module.exports = class Base {
}

/**
* Get state of TV by sending a Ping
* @return {Promise}
* Checks TV availability via network ping.
* @return {Promise<boolean>}
*/
getStatePing() {
this.device.log.debug(`Pinging ${this.ip} with timeout ${this.timeout}ms.`);
return isPortReachable(8001, {
host: this.ip,
timeout: this.timeout
});
}

/**
* Get state of TV from PowerState response
* @param {boolean} fallback
* @return {Promise}
* Fetches the TV's power state via its local API.
* @param {boolean} fallback - Fallback value if HTTP request fails.
* @return {Promise<boolean>}
*/
getStateHttp(fallback = false) {
this.device.log.debug(`Fetching power state from ${this.ip} local TV API.`);
return this.getInfo().then(data => {
if (data.device && data.device.PowerState) {
this.device.log.debug(`Power state: ${data.device.PowerState}`);
return data.device.PowerState == 'on';
}

Expand All @@ -71,8 +85,8 @@ module.exports = class Base {
}

/**
* Turn the TV On
* @return {Promise}
* Powers on the TV. Uses Wake-on-LAN if necessary.
* @return {Promise<void>}
*/
async setStateOn() {
// If TV is in Sleep mode just send command
Expand All @@ -90,16 +104,16 @@ module.exports = class Base {
}

/**
* Turn the TV Off
* @return {Promise}
* Powers off the TV.
* @return {Promise<void>}
*/
setStateOff() {
return this.click('KEY_POWER');
}

/**
* Get TV informations
* @return {Promise}
* Retrieves device information from the TV's API.
* @return {Promise<Object>}
*/
getInfo() {
return fetch(`http://${this.ip}:8001/api/v2/`, {
Expand All @@ -110,9 +124,9 @@ module.exports = class Base {
}

/**
* Get Application Informations
* @param {Number} appId
* @return {Promise}
* Retrieves information about a specific application.
* @param {number} appId - The application ID.
* @return {Promise<Object>}
*/
getApplication(appId) {
return fetch(`http://${this.ip}:8001/api/v2/applications/${appId}`, {
Expand All @@ -122,9 +136,9 @@ module.exports = class Base {
}

/**
* Launch Application
* @param {Number} appId
* @return {Promise}
* Launches an application on the TV.
* @param {number} appId - The application ID to launch.
* @return {Promise<void>}
*/
startApplication(appId) {
return fetch(`http://${this.ip}:8001/api/v2/applications/${appId}`, {
Expand All @@ -135,8 +149,8 @@ module.exports = class Base {
}

/**
* Encode TV name to base64
* @return {string}
* Encodes the TV name to base64.
* @return {string} The encoded TV name.
*/
_encodeName() {
return new Buffer.from(this.name).toString('base64');
Expand Down
14 changes: 12 additions & 2 deletions lib/remote.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ module.exports = class Remote {

let method = MethodWSS;

switch(this.device.config.method) {
switch (this.device.config.method) {
case 'ws':
method = MethodWS;
break;
Expand Down Expand Up @@ -119,6 +119,16 @@ module.exports = class Remote {
if (this.turningOff !== null) { return false; }
if (this.standbyMode !== null) { return false; }

// If SmartThings API is available, use it to get the power state reliably, otherwise fall back to default API method
if (this.smartthings.available && this.device.config.use_smartthings_power) {
return this.cache.get('smartthings-power-state', () => this.smartthings.getPowerState(), 2500)
.then(powerState => {
if (powerState !== null) { return powerState; }
this.device.log.debug("SmartThings API call failed, falling back to default API method.");
return this.api.getState();
});
}

return await this.api.getState();
}

Expand Down Expand Up @@ -339,7 +349,7 @@ module.exports = class Remote {

if (split[1]) {
if (/^.*\*[0-9]*[.]?[0-9]+s$/.test(cmd)) {
return {key: split[0], time: parseFloat(split[1])};
return { key: split[0], time: parseFloat(split[1]) };
}

return Array(parseInt(split[1])).fill(split[0]);
Expand Down
Loading