jaruba / ha-samsungtv-tizen

📺 HomeAssistant - For Samsung TVs 2016+, Includes SmartThings API and Channel List Support
Apache License 2.0
288 stars 65 forks source link

Feature Proposal: Push states from SmartThings #22

Open caphm opened 4 years ago

caphm commented 4 years ago

Hi,

glad to see you incorporated the SmartThings stuff so quickly. This is the best component for Tizen TVs so far, thanks for your effort and great work!

I've been tinkering around with the SmartThings API as well and may have found a way to improve upon the component, albeit quite hacky:

First thing is to install this SmartApp in SmartThings and set it up to forward the switch events of your TV to your local Home Assistant instance:

/**
 *  Home Assistant Event Forwarder
 *
 *  Copyright 2020 caphm
 *
 *  Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 *  in compliance with the License. You may obtain a copy of the License at:
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software distributed under the License is distributed
 *  on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
 *  for the specific language governing permissions and limitations under the License.
 *
 */
definition(
    name: "Home Assistant Event Forwarder",
    namespace: "caphm",
    author: "caphm",
    description: "Forwards events to Home Assistant",
    category: "",
    iconUrl: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience.png",
    iconX2Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png",
    iconX3Url: "https://s3.amazonaws.com/smartapp-icons/Convenience/Cat-Convenience@2x.png")

preferences {
    section("Home Assistant"){
        input "url", "text", title: "Home Assistant URL", description: "Publicly accessible URL of your Home Assistant installation (no trailing / )", required: true
        input "accessToken", "text", title: "Access Token", description: "A long-lived access token generated in Home Assistant", required: true
        input "eventType", "text", title: "Event Type", description: "Forwarded events will show up under this event type", required: true
    }

    section("Choose what events you want to forward"){
        input "accelerationSensor", "capability.accelerationSensor", title: "Acceleration Sensor", required: false, multiple: true
        input "alarm", "capability.alarm", title: "Alarm", required: false, multiple: true
        input "battery", "capability.battery", title: "Battery", required: false, multiple: true
        input "beacon", "capability.beacon", title: "Beacon", required: false, multiple: true
        input "button", "capability.button", title: "Button", required: false, multiple: true
        input "carbonMonoxideDetector", "capability.carbonMonoxideDetector", title: "Carbon Monoxide Detector", required: false, multiple: true
        input "colorControl", "capability.colorControl", title: "Color Control", required: false, multiple: true
        input "contactSensor", "capability.contactSensor", title: "Contact Sensor", required: false, multiple: true
        input "doorControl", "capability.doorControl", title: "Door Control", required: false, multiple: true
        input "energyMeter", "capability.energyMeter", title: "Energy Meter", required: false, multiple: true
        input "illuminanceMeasurement", "capability.illuminanceMeasurement", title: "Illuminance Measurement", required: false, multiple: true
        input "imageCapture", "capability.imageCapture", title: "Image Capture", required: false, multiple: true
        input "indicator", "capability.indicator", title: "Indicator", required: false, multiple: true
        input "lock", "capability.lock", title: "Lock", required: false, multiple: true
        input "mediaController", "capability.mediaController", title: "Media Controller", required: false, multiple: true
        input "motionSensor", "capability.motionSensor", title: "Motion Sensor", required: false, multiple: true
        input "musicPlayer", "capability.musicPlayer", title: "Music Player", required: false, multiple: true
        input "powerMeter", "capability.powerMeter", title: "Power Meter", required: false, multiple: true
        input "presenceSensor", "capability.presenceSensor", title: "Presence Sensor", required: false, multiple: true
        input "relativeHumidityMeasurement", "capability.relativeHumidityMeasurement", title: "Relative Humidity Measurement", required: false, multiple: true
        input "relaySwitch", "capability.relaySwitch", title: "Relay Switch", required: false, multiple: true
        input "sensor", "capability.sensor", title: "Sensor", required: false, multiple: true
        input "signalStrength", "capability.signalStrength", title: "Signal Strength", required: false, multiple: true
        input "sleepSensor", "capability.sleepSensor", title: "Sleep Sensor", required: false, multiple: true
        input "smokeDetector", "capability.smokeDetector", title: "Smoke Detector", required: false, multiple: true
        input "speechRecognition", "capability.speechRecognition", title: "Speech Recognition", required: false, multiple: true
        input "stepSensor", "capability.stepSensor", title: "Step Sensor", required: false, multiple: true
        input "switchv", "capability.switch", title: "Switch", required: false, multiple: true
        input "switchLevel", "capability.switchLevel", title: "Switch Level", required: false, multiple: true
        input "temperatureMeasurement", "capability.temperatureMeasurement", title: "Temperature Measurement", required: false, multiple: true
        input "thermostat", "capability.thermostat", title: "Thermostat", required: false, multiple: true
        input "thermostatCoolingSetpoint", "capability.thermostatCoolingSetpoint", title: "Thermostat Cooling Setpoint", required: false, multiple: true
        input "threeAxis", "capability.threeAxis", title: "Three Axis", required: false, multiple: true
        input "touchSensor", "capability.touchSensor", title: "TouchSensor", required: false, multiple: true
        input "valve", "capability.valve", title: "Valve", required: false, multiple: true
        input "waterSensor", "capability.waterSensor", title: "Water Sensor", required: false, multiple: true
    }
}

def installed() {
    log.debug "Installed, forwarding events to ${settings.url}"
    subscribeToEvents()
    initialize()
}

def updated() {
    log.debug "Updated, forwarding events to ${settings.url}"
    unsubscribe()
    subscribeToEvents()
    initialize()
}

def initialize() {
    subscribe(app, eventHandler)
}

def subscribeToEvents() {
    subscribe(accelerationSensor, "acceleration ", eventHandler)

    subscribe(alarm, "alarm", eventHandler)
    subscribe(battery, "battery", eventHandler)
    subscribe(beacon, "presence", eventHandler)
    subscribe(button, "button", eventHandler)
    subscribe(carbonMonoxideDetector, "carbonMonoxide", eventHandler)
    subscribe(colorControl, "hue", eventHandler)
    subscribe(colorControl, "saturation", eventHandler)

    subscribe(contactSensor, "contact", eventHandler)
    subscribe(doorControl, "door", eventHandler)
    subscribe(energyMeter, "energy", eventHandler)
    subscribe(illuminanceMeasurement, "illuminance", eventHandler)
    subscribe(imageCapture, "image", eventHandler)
    subscribe(indicator, "indicatorStatus", eventHandler)
    subscribe(lock, "lock", eventHandler)

    subscribe(mediaController, "activities", eventHandler)
    subscribe(mediaController, "currentActivity", eventHandler)

    subscribe(motionSensor, "motion", eventHandler)
    subscribe(musicPlayer, "status", eventHandler)
    subscribe(musicPlayer, "level", eventHandler)
    subscribe(musicPlayer, "trackDescription", eventHandler)
    subscribe(musicPlayer, "trackData", eventHandler)
    subscribe(musicPlayer, "mute", eventHandler)

    subscribe(powerMeter, "power", eventHandler)
    subscribe(presenceSensor, "presence", eventHandler)

    subscribe(relativeHumidityMeasurement, "humidity", eventHandler)
    subscribe(relaySwitch, "switch", eventHandler)
    subscribe(sensor, "sensor", eventHandler)
    subscribe(signalStrength, "lqi", eventHandler)
    subscribe(signalStrength, "rssi", eventHandler)
    subscribe(sleepSensor, "sleeping", eventHandler)
    subscribe(smokeDetector, "smoke", eventHandler)
    subscribe(speechRecognition, "phraseSpoken", eventHandler)

    subscribe(stepSensor, "goals", eventHandler)
    subscribe(stepSensor, "steps", eventHandler)
    subscribe(switchv, "switch", eventHandler)
    subscribe(switchLevel, "level", eventHandler)
    subscribe(temperatureMeasurement, "temperature", eventHandler)

    subscribe(thermostat, "temperature", eventHandler)
    subscribe(thermostat, "heatingSetpoint", eventHandler)
    subscribe(thermostat, "coolingSetpoint", eventHandler)
    subscribe(thermostat, "thermostatSetpoint", eventHandler)
    subscribe(thermostat, "thermostatMode", eventHandler)
    subscribe(thermostat, "thermostatFanMode", eventHandler)
    subscribe(thermostat, "thermostatOperatingState", eventHandler)

    subscribe(thermostatCoolingSetpoint, "coolingSetpoint", eventHandler)
    subscribe(threeAxis, "threeAxis", eventHandler)

    subscribe(touchSensor, "touch", eventHandler)
    subscribe(valve, "contact", eventHandler)
    subscribe(waterSensor, "water", eventHandler)
}

def eventHandler(evt) {
    def state_changed = evt.isStateChange()
    def json_body = [
            id: evt.deviceId, 
            date: evt.isoDate,
            value: evt.value, 
            name: evt.name, 
            display_name: evt.displayName, 
            description: evt.descriptionText,
            source: evt.source, 
            state_changed: evt.isStateChange(),
            physical: evt.isPhysical(),
            location_id: evt.locationId,
            hub_id: evt.hubId, 
            smartapp_id: evt.installedSmartAppId
        ]
    log.debug("Forwarding $json_body")

    def json_params = [
        uri: settings.url + "/api/events/" + settings.eventType,
        success: successClosure,
        headers: [
            Authorization: "Bearer " + settings.accessToken
        ],
        body: json_body
    ]

    try {
        httpPostJson(json_params)
    } catch (e) {
        log.error "Event forwarding failed: $e"
    }
}

def successClosure = { response ->
  log.debug "Event forwarded successfully, $response"
}

As the name says, this will forward events to Home Assistant and publish them on the event bus under the event type specified in the settings.

If you could add an event listener that intercepts these events, the state updates for on/off could become as good as realtime. Because the event type can be chosen freely when installing the smartapp, could you make this a configuration option to set it accordingly?

If you don't have any time on your hands, I could probably implement this on the weekend and createa PR.

jaruba commented 4 years ago

glad to see you incorporated the SmartThings stuff so quickly. This is the best component for Tizen TVs so far, thanks for your effort and great work!

I'm happy you find it useful. The SmartThings API seemed to be the only way to get advanced media titles (channel names, hdmi sources), and also the only easy way of change between TV and HDMI.

All the work on the SmartThings API was done by a contributor ( @pegatron89 ) following a discussion in a issue thread from this component. I added his work to this component to fill in the blanks from the other APIs used.

I've been tinkering around with the SmartThings API as well and may have found a way to improve upon the component, albeit quite hacky

As far as I can tell, every component from HA is based on 100% hacks. :))

This component currently uses the local WS API, UPnP and SmartThings in order to accomplish all it does now. I don't mind adding more to this stack as long as it will work for the large majority of the users.

If you could add an event listener that intercepts these events, the state updates for on/off could become as good as realtime.

I would really like the on / off states to work better.. It currently has 3 ways of checking on / off states. The default method is by checking the WS API (worst solution, takes the longest), then there's the ping method, which is faster at recognising on / off states, but still far from a good solution, and lastly, if the SmartThings API is enabled, it uses that to get the states. (which seems to be the best solution we have now)

I've found no other way of getting better on / off states, so I'm interested.

If you don't have any time on your hands, I could probably implement this on the weekend and createa PR.

I am quite limited in time indeed, and currently away from home (in a different country) for the next 7 days. So any help would be greatly appreciated.

I'm unfamiliar with SmartThings SmartApps though, so I have a few questions regarding it:

As long as we can add this functionality without breaking anything else I'm not against it, I just hope the setup won't be too complex to explain to users.

This component's purpose is to attempt to be the perfect solution for all newly built (and future) Samsung TVs, so I more then welcome improvements.

caphm commented 4 years ago

I am quite limited in time indeed, and currently away from home (in a different country) for the next 7 days. So any help would be greatly appreciated.

I'll have a go at it on the weekend then.

  • do SmartApps require a SmartThings Hub? (i'm presuming they don't, as I seem to be able to add SmartApps in the SmartThings app on Android)

No they don't. You can install them via the app. But because the app I posted is not published yet, you need to create it via the API Webinterface first.

  • does a SmartApp need to be added manually? or can it also be added through the SmartThings API?

Don't know, will find out :) Would be sweet if that were possible.

  • what permissions are required from the SmartThings API for this to work? (if any.. permissions are set when creating SmartThings API tokens)

None, because it's a push by SmartThings. You need to supply a long-lived access token for the SmartApp to be able to access your Home Assistant instance.

  • am I correct in understanding that SmartThings would need to make a HTTP POST request in order to update HA? i guess this presumes a few things: creating a HTTP server in this component to listen for requests, having HA exposed externally (to the internet? or are SmartApps running locally on the phone?) in order for it to be accessible by SmartThings

Partly correct. SmartThings does a post to HA, but uses the existing events endpoint of the HA REST-API. One the app is set up, there's nothing to add on the HA side, except for listening for the events from the integration.

  • what if users have multiple Samsung TVs? can we match these events to SmartThings device_ids?

Yes, the device ID is part of the event that is forwarded.

As long as we can add this functionality without breaking anything else I'm not against it, I just hope the setup won't be too complex to explain to users.

Compared to some other solutions for Home Assistant I believe this is quite doable ;)

pegatron89 commented 4 years ago

Hi

I might be missing something here but not entirely sure what this will achieve? It has the same capabilities I have already got through the API. The code you copied on your first post shows capabilities for other Samsung products not TVs. A few of them are compatible yeah eg: switch. Which other switch events are possible except on / off which the component already covers? mediaController won't work as it's not in the compatability list (on my TVs atleast) And can see no reason to add thermostat either.

@jaruba I will have a look at your integration tonight and test it out. The smart things API takes roughly 20seconds to update the on off state, exact same length of time my TVs take to sign in. If I do ping 192.168.0.6 -t I get a response from the TV after 2 seconds. So ping method would work better for me.

jaruba commented 4 years ago

@pegatron89 The ping method doesn't do ping -t 192.168.0.6, it checks if http://192.168.0.6:8001/api/v2/ is available.

We can't just ping the TV IP like that, because they support WOL and WOW, so the network cards will still be active after the TV state becomes "off" in many cases.

I think the code he pasted was more of an example of pushing states, not TV specific.

@caphm I'd still be glad to see an implementation of this working.

Just y-day I had a situation in which I tried turning the TV off through HA, and it seemed like it didn't work. The TV showed as being "on" all day, although when I got home it was actually "off" (but still showing as "on" in HA), and it was getting the state from the SmartThings API. So there are definitely still some issues with state changes.

pegatron89 commented 4 years ago

My bad then apologies @caphm . Read my comment again didn't mean to come across as a dick. Sorry!

@jaruba one of my Samsung TVs done that last week. It was switched off, reporting as on. No matter how much i physically turned the TV on or off nothing changed. I did do it after midnight UK time, so I just thought maybe maintenance somewhere as even on the smart things app it was showing as on, even though it was off at the mains. it was ok in the morning again.

I haven't tried your ping_method as the default state method on either the Samsungtv_tizen or samsungtv. I just tried pinging my wired TV with -t and got a response when on. I get request timed out when off, straight away, and then when I switch it on I get a response before the TV has even finished loading up.

On the WiFi it's different, slower. It took about 5 seconds to realise the TV was on then about 15 to turn off.

jaruba commented 4 years ago

@pegatron89 I've made the wrong decision of using the SmartThings API for on / off states and overwriting the user's choice of the update method. So the SmartThings API is used for this purpose if it is enabled, even if u set a different update method..

And being away from country I can't change it until I get back.. :))

In my tests it seemed to work better with the SmartThings API, but I was wrong.. Right now, the ping method seems to be the best way of getting that data.

I really hope @caphm 's method makes all these state issues a thing of the past.

caphm commented 4 years ago

My bad then apologies @caphm . Read my comment again didn't mean to come across as a dick. Sorry!

You're good ;)

What I proposed is basically what the built in SmartThings integration already does, except the built in integration does not publish events.

I have to do some more testing, but I'm currently leaning towards having the local WS connection only be established when the TV is actually reported as on by SmartThings. I'll dig into is in the weekend.

jonnykking commented 3 years ago

Hi, Unfortunately im not of a level to help contribute so more here to cheer you on haha.

Would the additions you are working on allow us to use the motion and light sensor? For FrameTV users I think this would be fantastic.

Provided the instructions are basic enough im happy to help test. recently dove into the HA world and have a FrameTV at home.

caphm commented 3 years ago

I've moved away from integrating it into this component and extended the built-in SmartThings integration instead, adding support to expose TVs as media_player entities: https://gist.github.com/caphm/fbbb8a876e860d93a474630a94fe3363 This way I can setup SmartThings once and have all my other devices also show up.

I've coded my own version of the websocket connection stuff (https://github.com/caphm/tizenws) which is streamlined and provides much better error handling. It's only definitively compatible with the 2018 models though as this is the only one I have for testing. This is integrated into the extension for the SmartThings integration above.

It's not the complete picture, but the relevant parts are there. You should be able to puzzle it together yourself if you choose to go that route.