Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
Dmitry Mamontov committed Mar 10, 2021
0 parents commit e32b3fd
Show file tree
Hide file tree
Showing 19 changed files with 1,900 additions and 0 deletions.
129 changes: 129 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
pip-wheel-metadata/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pyenv
.python-version

# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock

# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/
101 changes: 101 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# MiWiFi for Home Assistant
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg)](https://github.com/custom-components/hacs)
[![donate paypal](https://img.shields.io/badge/Donate-PayPal-blue.svg)](https://paypal.me/dslonyara)
[![donate tinkoff](https://img.shields.io/badge/Donate-Tinkoff-yellow.svg)](https://www.tinkoff.ru/sl/3FteV5DtBOV)

Component for tracking devices and managing routers based on [MiWiFi](http://miwifi.com/) from [Home Assistant](https://www.home-assistant.io/).

## Table of Contents
- [FAQ](#faq)
- [Install](#install)
- [Config](#config)
- [Advanced config](#advanced-config)
- [Services](#services)
- [Performance table](#performance-table)
- [Routers tested](#routers-tested)

## FAQ
**Q. Do I need to get telnet or ssh?**

A. Not. integration works through Luci-API

**Q. How often are states updated?**

A. Once every 10 seconds. This is the most optimal time for correct work.

**Q. In addition to tracking devices, what else does the integration allow you to do?**

A. The integration creates sensors to track the number of connected devices through different types of connections (`5 Ghz`, `2.4 Ghz`, `lan`). Creates binary sensors that track (`repeater mode`, `wifi state`, `wan state`). Creates switches to control `LEDs` and `reboot` the router. It also collects statistics on connected devices (Signal, Uptime, etc.)

**Q. Does the integration support legacy device tracking via `known_devices.yaml`?**

A. This is a legacy device tracking option. But the integration allows importing names, dev_id, icon from the file `known_devices.yaml` and associating with new devices by mac-address. To do this, simply create or rename the file to `legacy_known_devices.yaml`

**Q. Does the integration support routers connected in `repeater mode`?**

A. Yes, the integration supports devices connected in `repeater mode`. But to get the number of devices and their tracking, you will also need to connect and configure the parent router.

## Install
Installed through the custom repository [HACS](https://hacs.xyz/) - `dmamontov/hass-miwifi`

Or by copying the `miwifi` folder from [the latest release](https://github.com/dmamontov/hass-miwifi/releases/latest) to the custom_components folder (create if necessary) of the configs directory.

## Config
**Via GUI (Recommended)**
`Settings` > `Integrations` > `Plus` > `MiWiFi`

For authorization, use the ip of your router and its password

**Via YAML (legacy way)**
```yaml
miwifi:
ip_address: router_ip
password: router_pass
```
## Advanced config
#### Automatically remove devices
The component supports automatic deletion of monitored devices after a specified number of days (Default: 30 days) after the last activity. If you specify 0, then automatic deletion will be disabled.
**Via GUI (Recommended)**
`Settings` > `Integrations` > `Your integration MiWiFi` > `Settings`

**Via YAML (legacy way)**
```yaml
miwifi:
...
last_activity_days: 30
```

## Services
#### Remove devices
The component contains a service that allows you to delete a device by device_id or entity_id

**Via GUI (Recommended)**
`Developer-tools` > `Service` > `miwifi.remove_devices`

**Via YAML (legacy way)**
```yaml
service: miwifi.remove_devices
target:
device_id:
- ...
entity_id:
- device_tracker....
```

## Performance table
![](table.png)

1. Install [Auto-entities](https://github.com/thomasloven/lovelace-auto-entities) from HACS
2. Install [Flex Table](https://github.com/custom-cards/flex-table-card) from HACS
3. Add new Lovelace tab with **Panel Mode**
4. Add new Lovelace card: [example](https://gist.github.com/dmamontov/e6fa1842c486388387aaf061d3a82818)

## Routers tested
| Router | Firmware version | Region | Status |
| --------------------------------------------------------------------------------- | ---------------- | ------ | --------- |
| [Xiaomi AC2100](https://xiaomiplanets.com/review-xiaomi-ac2100-router/) | 2.0.743 | CN | Supported |
| [Xiaomi AX3600](https://xiaomiplanets.com/xiaomi-aiot-router-ax3600-performance/) | 1.0.79 | CN | Supported |

Many more Xiaomi and Redmi routers supported by MiWiFi (OpenWRT - Luci API)
119 changes: 119 additions & 0 deletions custom_components/miwifi/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import logging
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.device_registry as dr
import homeassistant.helpers.entity_registry as er

from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD, EVENT_HOMEASSISTANT_STOP
from homeassistant.helpers.json import JSONEncoder
from homeassistant.helpers.storage import Store

from .core.const import DOMAIN, CONF_LAST_ACTIVITY_DAYS, DEFAULT_LAST_ACTIVITY_DAYS, STORAGE_VERSION
from .core.luci_data import LuciData

_LOGGER = logging.getLogger(__name__)

CONFIG_SCHEMA = vol.Schema({
DOMAIN: vol.All(
cv.ensure_list,
[
vol.Schema({
vol.Required(CONF_IP_ADDRESS): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Optional(CONF_LAST_ACTIVITY_DAYS, default = DEFAULT_LAST_ACTIVITY_DAYS): cv.positive_int
})
]
)
}, extra = vol.ALLOW_EXTRA)

async def async_setup(hass: HomeAssistant, config: dict) -> bool:
success = True

if DOMAIN not in config:
return success

for router in config[DOMAIN]:
hass.data[DOMAIN][router[CONF_IP_ADDRESS]] = router

hass.async_create_task(hass.config_entries.flow.async_init(
DOMAIN, context = {'source': SOURCE_IMPORT}, data = router
))

return success

async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
if config_entry.data:
hass.config_entries.async_update_entry(config_entry, data = {} , options = config_entry.data)

store = Store(hass, STORAGE_VERSION, "{}.{}".format(DOMAIN, config_entry.entry_id), encoder = JSONEncoder)

devices = await store.async_load()
if devices is None:
devices = {}

client = LuciData(hass, config_entry, store, devices)

hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = client

async def async_close(event):
await client.save_to_store()
await client.api.logout()

hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, async_close)

await _init_services(hass)

if not await client.async_setup():
return False

return True

async def _init_services(hass: HomeAssistant):
async def remove_devices(call: ServiceCall):
data = dict(call.data)

entity_to_mac = {}

devices = data.pop('device_id', [])
entities = data.pop('entity_id', None)

if entities:
entity_registry = await er.async_get_registry(hass)

for entity_id in entities:
entity = entity_registry.async_get(entity_id)
if not entity:
continue

devices.append(entity.device_id)

if devices:
device_registry = await dr.async_get_registry(hass)

for device in devices:
device_entry = device_registry.async_get(device)
if not device_entry:
continue

device_registry.async_remove_device(device)

for entry in device_entry.config_entries:
if entry not in entity_to_mac:
entity_to_mac[entry] = []

for domain, mac in device_entry.identifiers:
entity_to_mac[entry].append(mac)

if not entity_to_mac:
return

for entry_id in entity_to_mac:
if entry_id not in hass.data[DOMAIN]:
return

for mac in entity_to_mac[entry_id]:
hass.data[DOMAIN][entry_id].remove_device(mac)

hass.services.async_register(DOMAIN, 'remove_devices', remove_devices)
Loading

0 comments on commit e32b3fd

Please sign in to comment.