Skip to content

Instantly share code, notes, and snippets.

@MohammadAG
Last active August 17, 2019 19:50
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MohammadAG/b5355503ece2be98a431aadf82046319 to your computer and use it in GitHub Desktop.
Save MohammadAG/b5355503ece2be98a431aadf82046319 to your computer and use it in GitHub Desktop.
webOS 3 accessory plugin for HAP-NodeJS
var Accessory = require('../').Accessory;
var Service = require('../').Service;
var Characteristic = require('../').Characteristic;
var uuid = require('../').uuid;
var ping = require ("net-ping");
var http = require('http');
var url = require("url");
var util = require("util");
var wsUrl = "ws://IP_ADDRESS_HERE:3000";
var tvMacAddress = "MAC_ADDRESS_HERE";
var lgtv;
var wol = require('wake_on_lan');
var on = false;
var connected = false;
var volume = 0;
var muted = false;
// Generate a consistent UUID for TV that will remain the same even when
// restarting our server. We use the `uuid.generate` helper function to create a deterministic
// UUID based on an arbitrary "namespace" and the word "tv".
var tvUUID = uuid.generate('hap-nodejs:accessories:tv');
// This is the Accessory that we'll return to HAP-NodeJS.
var tv = exports.accessory = new Accessory('TV', tvUUID);
// Add properties for publishing (in case we're using Core.js and not BridgedCore.js)
tv.username = "A3:FB:3D:4D:2E:AC";
tv.pincode = "031-45-154";
var idList = [
"com.webos.app.livetv",
"com.webos.app.hdmi1",
"com.webos.app.hdmi2",
"netflix",
"spotify-beehive",
"youtube.leanback.v4"
];
var mouseSocket = null;
var powerOnCallback = null;
lgtv = require('lgtv2')({
url: wsUrl,
timeout: 5000,
reconnect: 3000
});
lgtv.on('connect', function() {
console.log('Connected to webOS TV');
connected = true;
on = 1
tv.getService(Service.Television).getCharacteristic(Characteristic.Active).updateValue(on);
lgtv.subscribe('ssap://audio/getVolume', function (err, res) {
if (res.changed.indexOf('volume') !== -1) {
console.log('volume changed', res.volume);
volume = res.volume;
}
if (res.changed.indexOf('muted') !== -1) {
console.log('mute changed', res.muted);
muted = res.muted;
}
tv.getService(Service.TelevisionSpeaker).getCharacteristic(Characteristic.Volume).updateValue(volume);
tv.getService(Service.TelevisionSpeaker).getCharacteristic(Characteristic.Mute).updateValue(muted);
});
lgtv.getSocket('ssap://com.webos.service.networkinput/getPointerInputSocket', function(err, sock) {
if (!err) {
mouseSocket = sock;
}
});
lgtv.subscribe('ssap://com.webos.applicationManager/getForegroundAppInfo', function (err, res) {
var index = idList.indexOf(res.appId);
console.log("App changed to " + res.appId);
if (index != -1) {
tv.getService(Service.Television).getCharacteristic(Characteristic.ActiveIdentifier).updateValue(index);
}
});
lgtv.request('ssap://com.webos.applicationManager/listLaunchPoints', function (err, res) {
var launchPoints = res.launchPoints;
console.log(util.inspect(launchPoints));
});
lgtv.request('api/getServiceList', function (err, res) {
console.log(util.inspect(res));
});
if (powerOnCallback) {
powerOnCallback();
powerOnCallback = true;
}
});
lgtv.on('close', function() {
console.log('Disconnected from webOS TV');
connected = false;
on = 0
tv.getService(Service.Television).getCharacteristic(Characteristic.Active).updateValue(on);
mouseSocket = null;
});
lgtv.on('error', function(error) {
console.log('webOS error %s', error);
connected = false;
//setTimeout(lgtv.connect(this.url), 5000);
});
lgtv.on('prompt', function() {
console.log('webOS prompt for confirmation');
connected = false;
});
lgtv.on('connecting', function() {
console.log('webOS connecting to TV');
connected = false;
});
var retryCount = 0;
function adjustVolume(increment, callback) {
if (!connected) {
callback(null);
return;
}
var url = increment ? "ssap://audio/volumeUp" : "ssap://audio/volumeDown";
lgtv.request(url, function(err, res) {
if (err) {
console.log("Error adjusting volume");
return;
};
console.log("Volume adjusted to " + util.inspect(res));
callback(true);
});
}
function setMute(value, callback) {
if (!connected) {
callback(null);
return;
}
lgtv.request('ssap://audio/setMute', {mute: value}, function(err, res) {
if (err) {
console.log("Error changing mute: " + err);
return;
}
console.log("Volume mute set to " + res);
console.log(util.inspect(res));
callback(null);
});
}
function setVolume(value, callback) {
if (!connected) {
callback(null);
return;
}
lgtv.request("ssap://audio/setVolume", {volume: value}, function(err, res) {
if (err) {
console.log("Error changing volume: " + err);
return;
}
console.log("Volume changed to " + res);
console.log(util.inspect(res));
callback(null);
});
}
function checkWakeOnLan(callback) {
if (connected) {
console.log("TV now connected, notifying HomeKit");
callback(1);
retryCount = 0;
} else {
if (retryCount < 10) {
console.log("TV not connected, trying again in 2 seconds");
lgtv.connect(wsUrl);
setTimeout(checkWakeOnLan.bind(this, callback), 2000);
retryCount ++;
}
}
}
function powerOnTv(callback) {
wol.wake(tvMacAddress, function(error) {
if (error) return callback(new Error('webOS wake on lan error'));
retryCount = 0;
powerOnCallback = callback;
lgtv.connect(wsUrl);
//setTimeout(checkWakeOnLan.bind(this, callback), 3000);
})
}
function powerOffTv(callback) {
lgtv.request('ssap://system/turnOff', function(err, res) {
if (err) return callback(null);
lgtv.disconnect();
connected = false ;
on = false;
return callback(null);
})
}
function ensureOnAndThen(callback) {
if (on) {
callback();
} else {
powerOnTv(callback);
}
}
// Add the actual TV Service and listen for change events from iOS.
// We can see the complete list of Services and Characteristics in `lib/gen/HomeKitTypes.js`
var televisionService = tv.addService(Service.Television, "Television", "Television");
tv
.getService(Service.AccessoryInformation)
.setCharacteristic(Characteristic.Manufacturer, "LG")
.setCharacteristic(Characteristic.Model, "60UH651Y-TE")
.setCharacteristic(Characteristic.FirmwareRevision, "3.3.3-4205");
televisionService
.setCharacteristic(Characteristic.ConfiguredName, "TV");
televisionService
.setCharacteristic(
Characteristic.SleepDiscoveryMode,
Characteristic.SleepDiscoveryMode.ALWAYS_DISCOVERABLE
);
televisionService
.getCharacteristic(Characteristic.Active)
.on('set', function(newValue, callback) {
console.log("set Active => setNewValue: " + newValue);
if (newValue && !on) {
powerOnTv(callback);
} else if (!newValue && on) {
powerOffTv(callback);
} else {
callback(null);
}
});
televisionService
.setCharacteristic(Characteristic.ActiveIdentifier, 1);
televisionService
.getCharacteristic(Characteristic.ActiveIdentifier)
.on('set', function(newValue, callback) {
console.log("set Active Identifier => setNewValue: " + newValue);
ensureOnAndThen(function() {
lgtv.request('ssap://system.launcher/launch', {id: idList[newValue]}, function(err, res) {
if (err) {
console.log("Error launching app: " + idList[newValue]);
callback(null);
return;
}
callback();
});
});
});
televisionService
.getCharacteristic(Characteristic.RemoteKey)
.on('set', function(newValue, callback) {
console.log("set Remote Key => setNewValue: " + newValue);
if (mouseSocket != null) {
var keyName = null;
if (newValue == Characteristic.RemoteKey.ARROW_UP) {
keyName = "UP";
} else if (newValue == Characteristic.RemoteKey.ARROW_LEFT) {
keyName = "LEFT";
} else if (newValue == Characteristic.RemoteKey.ARROW_RIGHT) {
keyName = "RIGHT";
} else if (newValue == Characteristic.RemoteKey.ARROW_DOWN) {
keyName = "DOWN";
} else if (newValue == Characteristic.RemoteKey.BACK) {
keyName = "BACK";
} else if (newValue == Characteristic.RemoteKey.EXIT) {
keyName = "EXIT";
} else if (newValue == Characteristic.RemoteKey.INFORMATION) {
keyName = "HOME";
} else if (newValue == Characteristic.RemoteKey.SELECT) {
mouseSocket.send('click');
}
if (keyName != null) {
mouseSocket.send("button\n" + "name:" + keyName + "\n" + "\n");
}
}
if (newValue == Characteristic.RemoteKey.PLAY_PAUSE) {
lgtv.request('ssap://media.controls/pause');
}
callback(null);
});
televisionService
.getCharacteristic(Characteristic.PictureMode)
.on('set', function(newValue, callback) {
console.log("set PictureMode => setNewValue: " + newValue);
callback(null);
});
televisionService
.getCharacteristic(Characteristic.PowerModeSelection)
.on('set', function(newValue, callback) {
console.log("set PowerModeSelection => setNewValue: " + newValue);
if (newValue == Characteristic.PowerModeSelection.SHOW) {
if (mouseSocket != null) {
mouseSocket.send("button\n" + "name:" + "SETTINGS" + "\n" + "\n");
}
}
callback(null);
});
// Speaker
var speakerService = tv.addService(Service.TelevisionSpeaker)
speakerService
.setCharacteristic(Characteristic.Active, Characteristic.Active.ACTIVE)
.setCharacteristic(Characteristic.VolumeControlType, Characteristic.VolumeControlType.ABSOLUTE);
// TV widget sends a duplicate set here, which causes volume increments to be +/- 2 all the time.
// This ignores one of the values.
var ignoreVolumeDuplicate = false;
speakerService.getCharacteristic(Characteristic.VolumeSelector)
.on('set', function(newValue, callback) {
console.log("set VolumeSelector => setNewValue: " + newValue);
if (ignoreVolumeDuplicate) {
callback(null);
} else {
adjustVolume(newValue == Characteristic.VolumeSelector.INCREMENT, callback);
}
ignoreVolumeDuplicate = !ignoreVolumeDuplicate;
});
speakerService.getCharacteristic(Characteristic.Volume)
.on('get', function(callback) {
callback(null, volume);
})
.on('set', function(newValue, callback) {
setVolume(newValue, callback);
});
speakerService.getCharacteristic(Characteristic.Mute)
.on('get', function(callback) {
callback(null, muted);
})
.on('set', function(newValue, callback) {
setMute(newValue, callback);
});
// Live TV
var inputLiveTV = tv.addService(Service.InputSource, "livetv", "Live TV");
inputLiveTV
.setCharacteristic(Characteristic.Identifier, 0)
.setCharacteristic(Characteristic.ConfiguredName, "Live TV")
.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
.setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.TUNER);
// HDMI 1
var inputHDMI1 = tv.addService(Service.InputSource, "hdmi1", "HDMI 1");
inputHDMI1
.setCharacteristic(Characteristic.Identifier, 1)
.setCharacteristic(Characteristic.ConfiguredName, "HDMI 1")
.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
.setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.HDMI);
// HDMI 2
var inputHDMI2 = tv.addService(Service.InputSource, "hdmi2", "HDMI 2");
inputHDMI2
.setCharacteristic(Characteristic.Identifier, 2)
.setCharacteristic(Characteristic.ConfiguredName, "HDMI 2")
.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
.setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.HDMI);
// Netflix
var inputNetflix = tv.addService(Service.InputSource, "netflix", "Netflix");
inputNetflix
.setCharacteristic(Characteristic.Identifier, 3)
.setCharacteristic(Characteristic.ConfiguredName, "Netflix")
.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
.setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.APPLICATION);
var inputSpotify = tv.addService(Service.InputSource, "spotify", "Spotify");
inputSpotify
.setCharacteristic(Characteristic.Identifier, 4)
.setCharacteristic(Characteristic.ConfiguredName, "Spotify")
.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
.setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.APPLICATION);
var inputYouTube = tv.addService(Service.InputSource, "youtube", "YouTube");
inputYouTube
.setCharacteristic(Characteristic.Identifier, 5)
.setCharacteristic(Characteristic.ConfiguredName, "YouTube")
.setCharacteristic(Characteristic.IsConfigured, Characteristic.IsConfigured.CONFIGURED)
.setCharacteristic(Characteristic.InputSourceType, Characteristic.InputSourceType.APPLICATION);
televisionService.addLinkedService(inputLiveTV);
televisionService.addLinkedService(inputHDMI1);
televisionService.addLinkedService(inputHDMI2);
televisionService.addLinkedService(inputNetflix);
televisionService.addLinkedService(inputSpotify);
televisionService.addLinkedService(inputYouTube);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment