Skip to content

Commit 4005094

Browse files
committed
Code commit & HACS integration.
1 parent e8156e2 commit 4005094

8 files changed

Lines changed: 256 additions & 1 deletion

File tree

CONTRIBUTING.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# Contribution guidelines
2+
3+
Contributing to this project should be as easy and transparent as possible, whether it's:
4+
5+
- Reporting a bug
6+
- Discussing the current state of the code
7+
- Submitting a fix
8+
- Proposing new features
9+
10+
## Github is used for everything
11+
12+
Github is used to host code, to track issues and feature requests, as well as accept pull requests.
13+
14+
Pull requests are the best way to propose changes to the codebase.
15+
16+
1. Fork the repo and create your branch from `master`.
17+
2. If you've changed something, update the documentation.
18+
3. Make sure your code lints (using black).
19+
4. Issue that pull request!
20+
21+
## Any contributions you make will be under the MIT Software License
22+
23+
In short, when you submit code changes, your submissions are understood to be under the same [MIT License](http://choosealicense.com/licenses/mit/) that covers the project. Feel free to contact the maintainers if that's a concern.
24+
25+
## Report bugs using Github's [issues](../../issues)
26+
27+
GitHub issues are used to track public bugs.
28+
Report a bug by [opening a new issue](../../issues/new/choose); it's that easy!
29+
30+
## Write bug reports with detail, background, and sample code
31+
32+
**Great Bug Reports** tend to have:
33+
34+
- A quick summary and/or background
35+
- Steps to reproduce
36+
- Be specific!
37+
- Give sample code if you can.
38+
- What you expected would happen
39+
- What actually happens
40+
- Notes (possibly including why you think this might be happening, or stuff you tried that didn't work)
41+
42+
People *love* thorough bug reports. I'm not even kidding.
43+
44+
## Use a Consistent Coding Style
45+
46+
Use [black](https://github.com/ambv/black) to make sure the code follows the style.
47+
48+
## License
49+
50+
By contributing, you agree that your contributions will be licensed under its Apache 2.0 License.

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@
186186
same "printed page" as the copyright notice for easier
187187
identification within third-party archives.
188188

189-
Copyright [yyyy] [name of copyright owner]
189+
Copyright 2020 Adam Najmanowicz
190190

191191
Licensed under the Apache License, Version 2.0 (the "License");
192192
you may not use this file except in compliance with the License.

README.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,44 @@
11
# linksys_velop
22
The linksys_velop platform allows for presence detection by listing devices connected to your Linksys Velop router.
3+
4+
It was tested with a Linksys Velop WHW03v1 Firmware version 1.1.11.197735
5+
6+
## Installation
7+
8+
1. Using the tool of choice open the directory (folder) for your HA configuration (where you find `configuration.yaml`).
9+
2. If you do not have a `custom_components` directory (folder) there, you need to create it.
10+
3. In the `custom_components` directory (folder) create a new folder called `linksys_velop`.
11+
4. Download _all_ the files from the `custom_components/linksys_velop/` directory (folder) in this repository.
12+
5. Place the files you downloaded in the new directory (folder) you created.
13+
6. Restart Home Assistant.
14+
7. Move on to the configuration.
15+
16+
Using your HA configuration directory (folder) as a starting point you should now also have this:
17+
18+
```text
19+
custom_components/linksys_velop/__init__.py
20+
custom_components/linksys_velop/device_tracker.py
21+
custom_components/linksys_velop/manifest.json
22+
```
23+
24+
## Example configuration.yaml
25+
26+
```yaml
27+
device_tracker:
28+
- platform: linksys_velop
29+
host: 192.168.1.1
30+
username: admin
31+
password: YOUR_PASSWORD
32+
```
33+
34+
### Configuration options
35+
36+
Key | Type | Required | Description
37+
-- | -- | -- | --
38+
`host` | `string` | `True` | The hostname or IP address of your access point, e.g., 192.168.1.1.
39+
`username` | `string` | `False` | Defaults to `admin`. You should not have to customize it as Velops deveult to `admin` on login and only allow you to specify password.
40+
`password` | `string` | `True` | The password for your given local admin account.
41+
42+
## Contributions are welcome!
43+
44+
If you want to contribute to this please read the [Contribution guidelines](CONTRIBUTING.md)
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""The linksys_velop component."""
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
"""Support for Linksys Velop routers."""
2+
import logging
3+
4+
import requests
5+
import voluptuous as vol
6+
7+
import homeassistant.helpers.config_validation as cv
8+
from homeassistant.components.device_tracker import (
9+
DOMAIN,
10+
PLATFORM_SCHEMA,
11+
DeviceScanner,
12+
)
13+
14+
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
15+
from base64 import b64encode as b64
16+
17+
DEFAULT_TIMEOUT = 10
18+
19+
_LOGGER = logging.getLogger(__name__)
20+
21+
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
22+
{
23+
vol.Required(CONF_HOST): cv.string,
24+
vol.Required(CONF_PASSWORD): cv.string,
25+
vol.Optional(CONF_USERNAME, default="admin"): cv.string,
26+
}
27+
)
28+
29+
30+
def get_scanner(hass, config):
31+
"""Validate the configuration and return a Linksys AP scanner."""
32+
try:
33+
return LinksysSmartWifiDeviceScanner(config[DOMAIN])
34+
except ConnectionError:
35+
return None
36+
37+
38+
class LinksysSmartWifiDeviceScanner(DeviceScanner):
39+
"""This class queries a Linksys Access Point."""
40+
41+
def __init__(self, config):
42+
"""Initialize the scanner."""
43+
self.host = config[CONF_HOST]
44+
self.username = config[CONF_USERNAME]
45+
self.password = config[CONF_PASSWORD]
46+
self.last_results = {}
47+
48+
# Check if the access point is accessible
49+
response = self._make_request()
50+
if not response.status_code == 200:
51+
raise ConnectionError("Cannot connect to Linksys Access Point")
52+
53+
def scan_devices(self):
54+
"""Scan for new devices and return a list with device IDs (MACs)."""
55+
self._update_info()
56+
57+
return self.last_results.keys()
58+
59+
def get_device_name(self, device):
60+
"""Return the name (if known) of the device."""
61+
return self.last_results.get(device)
62+
63+
def _update_info(self):
64+
"""Check for connected devices."""
65+
_LOGGER.info("Checking Linksys Smart Wifi")
66+
67+
self.last_results = {}
68+
response = self._make_request()
69+
if response.status_code != 200:
70+
_LOGGER.error(
71+
"Got HTTP status code %d when getting device list", response.status_code
72+
)
73+
return False
74+
try:
75+
data = response.json()
76+
result = data["responses"][0]
77+
devices = result["output"]["devices"]
78+
for device in devices:
79+
macs = device["knownMACAddresses"]
80+
if not macs:
81+
_LOGGER.warning("Skipping device without known MAC address")
82+
continue
83+
mac = macs[-1]
84+
connections = device["connections"]
85+
if not connections:
86+
_LOGGER.debug("Device %s is not connected", mac)
87+
continue
88+
89+
name = None
90+
for prop in device["properties"]:
91+
if prop["name"] == "userDeviceName":
92+
name = prop["value"]
93+
if not name:
94+
name = device.get("friendlyName", device["deviceID"])
95+
96+
_LOGGER.debug("Device %s is connected", mac)
97+
self.last_results[mac] = name
98+
except (KeyError, IndexError):
99+
_LOGGER.exception("Router returned unexpected response")
100+
return False
101+
return True
102+
103+
def _make_request(self):
104+
# Weirdly enough, this doesn't seem to require authentication
105+
data = [
106+
{
107+
"request": {"sinceRevision": 0},
108+
"action": "http://linksys.com/jnap/devicelist/GetDevices",
109+
}
110+
]
111+
112+
token = b64(bytes(self.username + ":" + self.password, "utf8")).decode("ascii");
113+
114+
headers = {
115+
"X-JNAP-Action": "http://linksys.com/jnap/core/Transaction",
116+
"X-JNAP-Authorization": "Basic " + token
117+
}
118+
119+
120+
return requests.post(
121+
f"http://{self.host}/JNAP/",
122+
timeout=DEFAULT_TIMEOUT,
123+
headers=headers,
124+
json=data,
125+
)
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"domain": "linksys_velop",
3+
"name": "Linksys Velop",
4+
"documentation": "https://www.home-assistant.io/components/linksys_velop",
5+
"requirements": [],
6+
"dependencies": [],
7+
"codeowners": ["@adamnaj"],
8+
"homeassistant": "0.100.0"
9+
}

hacs.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"name": "linksys_velop"
3+
}

info.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# linksys_velop
2+
The linksys_velop platform allows for presence detection by listing devices connected to your Linksys Velop router.
3+
4+
It was tested with a Linksys Velop WHW03v1 Firmware version 1.1.11.197735
5+
6+
## Example configuration.yaml
7+
8+
```yaml
9+
device_tracker:
10+
- platform: linksys_velop
11+
host: 192.168.1.1
12+
password: YOUR_PASSWORD
13+
```
14+
15+
### Configuration options
16+
17+
Key | Type | Required | Description
18+
-- | -- | -- | --
19+
`host` | `string` | `True` | The hostname or IP address of your access point, e.g., 192.168.1.1.
20+
`username` | `string` | `False` | Defaults to `admin`. You should not have to customize it as Velops deveult to `admin` on login and only allow you to specify password.
21+
`password` | `string` | `True` | The password for your given local admin account.
22+
23+
## Contributions are welcome!
24+
25+
If you want to contribute to this please read the [Contribution guidelines](CONTRIBUTING.md)

0 commit comments

Comments
 (0)