Skip to content

Commit

Permalink
Merge pull request #4 from DaviPtrs/dev
Browse files Browse the repository at this point in the history
Added multi unified remote hosts feature
  • Loading branch information
DaviPtrs authored Nov 6, 2020
2 parents a0a5066 + 272d738 commit 5226bb9
Show file tree
Hide file tree
Showing 5 changed files with 177 additions and 179 deletions.
92 changes: 57 additions & 35 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
- [Management Panel](#management-panel)
- [Setting up](#setting-up)
- [Home Assistant](#home-assistant)
- [CONFIGURATION VARIABLES](#configuration-variables)
- [CONFIGURATION VARIABLES (For unified_remote)](#configuration-variables-for-unified_remote)
- [CONFIGURATION VARIABLES (For each host)](#configuration-variables-for-each-host)
- [Getting Started](#getting-started)
- [How it works](#how-it-works)
- [Remote description](#remote-description)
Expand All @@ -23,8 +24,6 @@
- [Remotes section](#remotes-section)
- [Types section](#types-section)
- [How to use](#how-to-use)
- [Service](#service)
- [Switch Platform](#switch-platform)
- [Contribute](#contribute)
- [Submit Feedback](#submit-feedback)

Expand Down Expand Up @@ -83,10 +82,31 @@ On your `configuration.yaml` file, add the following lines:

```yaml
unified_remote:
host: UNIFIED_REMOTE_SERVER_IP
hosts:
- host: UNIFIED_REMOTE_SERVER_IP
```
#### CONFIGURATION VARIABLES
#### CONFIGURATION VARIABLES (For unified_remote)
**hosts**
*(list)(Required)*
List of hosts
============================================
**retry_delay**
*(number)(Optional)*
Time to retry connection and/or keep it alive (seconds). You **CANNOT** set a delay greater than 120 seconds, this is the max value that will keep the connection alive.
*Default value:*
retry_delay: 120
#### CONFIGURATION VARIABLES (For each host)
**host**
*(string)(Required)*
Expand All @@ -107,15 +127,15 @@ port: 9510
============================================
**retry_delay**
**name**
*(number)(Optional)*
*(string)(Optional)*
Time to retry connection and/or keep it alive (seconds). You **CANNOT** set a delay greater than 120 seconds, this is the max value that will keep the connection alive.
A name to identify the host. If unset, the name will be the host ip.
*Default value:*
retry_delay: 120
name: \<host ip\>
## Getting Started
Expand All @@ -135,7 +155,7 @@ Example:
- If you want to turn off your computer, you need to use this remote
```
remote_id: Unified.Power
id: Unified.Power
action: turn_off
```
Expand Down Expand Up @@ -393,8 +413,6 @@ With the previous cases, we just declared 2 media_video remotes, but think if we

Now we got a configured integration and declarated remotes, we have to be able to execute these action using remotes, of course, inside of Home Assistant.

### Service

This integration actualy register a service, called by `unified_remote.call`

That service allows you to call your remotes.
Expand All @@ -417,7 +435,15 @@ remote_id: remote_id
action: remote_action
```

For example:
- To specify which computer will receive the command, just add a `target` entry:

```yaml
target: computer_name
remote_id: remote_id
action: remote_action
```

Example:

This call will open Amazon Prime Video on my default browser.

Expand All @@ -433,6 +459,22 @@ remote_id: Unified.AmazonPrimeVideo
action: launch
```

The same but specifying a computer by name:

```yaml
target: PcMasterRace
remote_id: Unified.AmazonPrimeVideo
action: launch
```

If you didn't assign a computer name, the name will be same as computer ip, so:

```yaml
target: 192.168.1.2
remote_id: Unified.AmazonPrimeVideo
action: launch
```

- For adding buttons on your home assistant lovelace, use `Manual Card` element with `call-service` action, like:

```yaml
Expand All @@ -456,37 +498,17 @@ That example will restart my computer if I tap the button, after I accept the co
![demo-card](images/demo-card.png)
### Switch Platform
It also comes with a switch platform, so you can define which action will be performed when swith goes on, off, or being (toggle options are little buggy)
The following example is a switch that controls my monitor screen
```yaml
switch:
- platform: unified_remote
name: "computer_screen"
turn_on:
remote: monitor
action: turn_on
turn_off:
remote: monitor
action: turn_off
```
![demo-switch](images/demo-switch.png)
## Contribute
Contributions are always welcome!
If you need some light, read some of following guides:
If you need some light, read some of the following guides:
- [The beginner's guide to contributing to a GitHub project](https://akrabat.com/the-beginners-guide-to-contributing-to-a-github-project/)
- [First Contributions](https://github.com/firstcontributions/first-contributions)
- [How to contribute to open source](https://github.com/freeCodeCamp/how-to-contribute-to-open-source)
- [How to contribute to a project on Github](https://gist.github.com/MarcDiethelm/7303312)
## Submit Feedback
Be free to [open an issue](https://github.com/DaviPtrs/hass-unified-remote/issues/new/choose) telling your experience, suggesting new features or asking questions (there's no stupid questions, but make sure that yours cannot be answered by just reading docs)
Be free to [open an issue](https://github.com/DaviPtrs/hass-unified-remote/issues/new/choose) telling your experience, suggesting new features or asking questions (there's no stupid questions, but make sure that yours cannot be answered by just reading the docs)
You can also find me on LinkedIn [/in/davipetris](https://www.linkedin.com/in/davipetris/)
126 changes: 72 additions & 54 deletions custom_components/unified_remote/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,33 @@
import logging as log
from datetime import timedelta

from requests import ConnectionError

import homeassistant.helpers.config_validation as cv
import voluptuous as vol
from custom_components.unified_remote.cli.connection import Connection
from custom_components.unified_remote.cli.remotes import Remotes
from homeassistant.const import CONF_HOST, CONF_PORT
from homeassistant.const import CONF_HOST, CONF_HOSTS, CONF_NAME, CONF_PORT
from homeassistant.helpers.event import track_time_interval
from requests import ConnectionError

DOMAIN = "unified_remote"
from custom_components.unified_remote.cli.computer import Computer
from custom_components.unified_remote.cli.remotes import Remotes

DOMAIN = "unified_remote"
CONF_RETRY = "retry_delay"

CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_HOST, default="localhost"): cv.string,
vol.Optional(CONF_PORT, default="9510"): cv.string,
vol.Required(CONF_HOSTS): vol.Schema(
vol.All(
[
{
vol.Optional(CONF_NAME, default=""): cv.string,
vol.Required(CONF_HOST, default="localhost"): cv.string,
vol.Optional(CONF_PORT, default="9510"): cv.port,
}
]
)
),
vol.Optional(CONF_RETRY, default=120): int,
}
)
Expand All @@ -45,13 +53,29 @@
except Exception as error:
_LOGGER.error(str(error))

CONNECTION = Connection()
COMPUTERS = []


def init_computers(hosts):
for computer in hosts:
name = computer.get(CONF_NAME)
host = computer.get(CONF_HOST)
port = computer.get(CONF_PORT)

if name == "":
name = host
try:
COMPUTERS.append(Computer(name, host, port))
except (AssertionError, Exception):
return False
return True


def connect(host, port):
"""Handle with connect function and logs if was successful"""
CONNECTION.connect(host, port)
_LOGGER.info(f"Connection to {CONNECTION.get_url()} established")
def find_computer(name):
for computer in COMPUTERS:
if computer.name == name:
return computer
return None


def validate_response(response):
Expand All @@ -74,71 +98,65 @@ def validate_response(response):
raise ConnectionError()


def call_remote(id, action):
try:
CONNECTION.exe_remote(id, action)
_LOGGER.debug(f'Call -> Remote ID: "{id}"; Action: "{action}"')
# Log if request fails.
except ConnectionError:
_LOGGER.warning("Unable to call remote. Host is off")


def setup(hass, config):
"""Setting up Unified Remote Integration"""
# Fetching configuration entries.
host = config[DOMAIN].get(CONF_HOST)
port = config[DOMAIN].get(CONF_PORT)
hosts = config[DOMAIN].get(CONF_HOSTS)
retry_delay = config[DOMAIN].get(CONF_RETRY)
if retry_delay > 120:
retry_delay = 120

try:
# Establishing connection with host client.
connect(host, port)
# Handling with malformed url error.
except AssertionError as url_error:
_LOGGER.error(str(url_error))
return False
except ConnectionError:
_LOGGER.warning(
"At the first moment host seems down, but the connection will be retried."
)
except Exception as e:
_LOGGER.error(str(e))
if not init_computers(hosts):
return False

def keep_alive(call):
"""Keep host listening our requests"""
try:
response = CONNECTION.exe_remote("", "")
_LOGGER.debug("Keep alive packet sent")
_LOGGER.debug(
f"Keep alive packet response: {response.content.decode('ascii')}"
)
validate_response(response)
# If there's an connection error, try to reconnect.
except ConnectionError:
for computer in COMPUTERS:
try:
_LOGGER.debug(f"Trying to reconnect with {host}")
connect(host, port)
except Exception as error:
response = computer.connection.exe_remote("", "")
_LOGGER.debug("Keep alive packet sent")
_LOGGER.debug(
f"Unable to connect with {host}. Headers: {CONNECTION.get_headers()}"
f"Keep alive packet response: {response.content.decode('ascii')}"
)
_LOGGER.debug(f"Error: {error}")
pass
validate_response(response)
# If there's an connection error, try to reconnect.
except ConnectionError:
try:
_LOGGER.debug(f"Trying to reconnect with {computer.host}")
computer.connect()
except Exception as error:
computer.is_available = False
_LOGGER.info(f"The computer {computer.name} is now unavailable")
_LOGGER.debug(
f"Unable to connect with {computer.host}. Headers: {computer.connection.get_headers()}"
)
_LOGGER.debug(f"Error: {error}")
pass

def handle_call(call):
"""Handle the service call."""
# Fetch service data.
target = remote_name = call.data.get("target")
if target is None or target.strip() == "":
computer = COMPUTERS[0]
else:
computer = find_computer(target)

if computer is None:
_LOGGER.error(f"No such computer called {target}")
return None

if not computer.is_available:
_LOGGER.error(f"Unable to call remote. {target} is unavailable.")

remote_name = call.data.get("remote", DEFAULT_NAME)
remote_id = call.data.get("remote_id", DEFAULT_NAME)
action = call.data.get("action", DEFAULT_NAME)

# Allows user to pass remote id without declaring it on remotes.yml
if remote_id is not None:
if not (remote_id == "" or action == ""):
call_remote(remote_id, action)
computer.call_remote(remote_id, action)
return None

# Check if none or empty service data was parsed.
Expand All @@ -154,7 +172,7 @@ def handle_call(call):
remote_id = remote["id"]
# Check if given action exists in remote control list.
if action in remote["controls"]:
call_remote(remote_id, action)
computer.call_remote(remote_id, action)
else:
# Log if called remote doens't exists on remotes.yml.
_LOGGER.warning(
Expand Down
Loading

0 comments on commit 5226bb9

Please sign in to comment.