My bad… should be - key: sensor.unifi.ap*. Anyway… you got it working :-).
Actually, it works without the key:
Maybe I’d only need that if I wanted to give the attribute a specific name - will play with it when I have some time,
I have wired devices that show up as connected to my PoE switch and not an AP, is that what you mean?
yes! I didn’t thought about that as I don’t have other “wired” devices! I put a python script here that will print your devices information in json. At the bottom of the file you should edit your user/pass/ip at the line “api = API(username=“admin”, password=“your_password”, baseurl=“https://your_ip:8443”, verify_ssl=False)”.
You may run it with “python3 unifi_helper.py” or “python3 unifi_helper.py > output.txt” to save the output to a file. If you share the output (edit/change sensitive information, of course) I should be able to correct my code.
output.yaml (78.1 KB)
Here is the output. It’s a txt file, but those don’t seem to be allowed, so I changed the extension to .yaml. I hope that will work.
Thanks for looking at this, If I can’t contribute code yet, I’m more than happy to break things
Thanks,
Denny
Thanks! I should have a new version tomorrow.
One more thing… How should be the better way to present information about wired devices? From your image it seems that they use the “network” attribute (LAN or Cameras in your case) or we may just list them as “wired”.
Well LAN or Camera refer to the VLANs that they are connected to. I could see how that would be nice to use, but I don’t see why just Wired wouldn’t work.
You rock, thanks!
Hi,
New version o github. May you try, plz?
It works! Very awesome. Thanks for doing this.
Hi @clyra,
I’m using your customer sensor and don’t work.
I have this error in my logs:
File "/config/custom_components/sensor/unifi.py", line 261, in <module>
self.logout()
NameError: name 'self' is not defined
2018-09-22 11:45:44 ERROR (MainThread) [homeassistant.core] Error doing job: Task exception was never retrieved
My configuration is this one:
- platform: unifi
name: unifi
region: default
username: !secret unifi_user
password: !secret unifi_pass
url: https://192.168.1.100:8443
I’m using HA version 0.77.3.
And this is the python code that i’m using:
"""
Unifi sensor. Shows the total number os devices connected. Also shows the number of devices per
AP and per essid as attributes.
with code from https://github.com/frehov/Unifi-Python-API
Version 0.2
"""
from datetime import timedelta
from requests import Session
import json
import re
from typing import Pattern, Dict, Union
import logging
import voluptuous as vol
from homeassistant.helpers.entity import Entity
import homeassistant.helpers.config_validation as cv
from homeassistant.components.sensor import PLATFORM_SCHEMA
from homeassistant.const import (
CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_REGION,
CONF_URL, CONF_VERIFY_SSL, STATE_UNKNOWN, PRECISION_WHOLE)
from homeassistant.exceptions import TemplateError
from homeassistant.helpers import template
REQUIREMENTS = []
_LOGGER = logging.getLogger(__name__)
DOMAIN = 'sensor'
ENTITY_ID_FORMAT = DOMAIN + '.{}'
DEFAULT_NAME = 'Unifi'
DEFAULT_SITE = 'default'
DEFAULT_VERIFYSSL = False
SCAN_INTERVAL = timedelta(seconds=60)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({
vol.Required(CONF_USERNAME): cv.string,
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_URL): cv.url,
vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string,
vol.Optional(CONF_REGION, default=DEFAULT_SITE): cv.string,
vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFYSSL): cv.boolean
})
def setup_platform(hass, config, add_devices, discovery_info=None):
"""Set up the Unifi Sensor."""
name = config.get(CONF_NAME)
username = config.get(CONF_USERNAME)
password = config.get(CONF_PASSWORD)
baseurl = config.get(CONF_URL)
site = config.get(CONF_REGION)
verify_ssl = config.get(CONF_VERIFY_SSL)
data = UnifiSensorData(hass, username, password, site, baseurl, verify_ssl)
add_devices([UnifiSensor(hass, data, name)], True)
class UnifiSensor(Entity):
"""Representation of a Unifi Sensor."""
def __init__(self, hass, data, name):
"""Initialize the sensor."""
self._hass = hass
self._data = data
self._name= name
self._state = None
self._attributes = None
self._unit_of_measurement = 'devices'
@property
def name(self):
"""Return the name of the sensor."""
return self._name
@property
def unit_of_measurement(self):
"""Return the unit of measurement."""
return self._unit_of_measurement
@property
def precision(self):
"""Return the precision of the system."""
return PRECISION_WHOLE
@property
def state(self):
"""Return the state of the sensor."""
return self._state
@property
def device_state_attributes(self):
"""Return the state attributes."""
return self._attributes
def update(self):
"""Fetch new state data for the sensor."""
self._data.update()
value = self._data.total
if value is None:
value = STATE_UNKNOWN
self._attributes = {}
else:
self._state = value
self._attributes = self._data.attrs
class UnifiSensorData(object):
"""
Unifi API for the Unifi Controller.
"""
_login_data = {}
_current_status_code = None
def __init__(self, hass, username, password, site, baseurl, verify_ssl):
"""
Initiates tha api with default settings if none other are set.
:param username: username for the controller user
:param password: password for the controller user
:param site: which site to connect to (Not the name you've given the site, but the url-defined name)
:param baseurl: where the controller is located
:param verify_ssl: Check if certificate is valid or not, throws warning if set to False
"""
self._hass = hass
self._login_data['username'] = username
self._login_data['password'] = password
self._site = site
self._verify_ssl = verify_ssl
self._baseurl = baseurl
self._session = Session()
self._ap_list = {}
self.total = 0
self.attrs = {}
def __enter__(self):
"""
Contextmanager entry handle
:return: isntance object of class
"""
self.login()
return self
def __exit__(self, *args):
"""
Contextmanager exit handle
:return: None
"""
self.logout()
def login(self):
"""
Log the user in
:return: None
"""
self._current_status_code = self._session.post("{}/api/login".format(self._baseurl), data=json.dumps(self._login_data), verify=self._verify_ssl).status_code
if self._current_status_code == 400:
_LOGGER.error("Failed to log in to api with provided credentials")
def logout(self):
"""
Log the user out
:return: None
"""
self._session.get("{}/logout".format(self._baseurl))
self._session.close()
def list_clients(self) -> list:
"""
List all available clients from the api
:return: A list of clients on the format of a dict
"""
r = self._session.get("{}/api/s/{}/stat/sta".format(self._baseurl, self._site, verify=self._verify_ssl), data="json={}")
self._current_status_code = r.status_code
if self._current_status_code == 401:
_LOGGER.error("Unifi: Invalid login, or login has expired")
return None
data = r.json()['data']
return data
def list_devices(self, mac=None) -> list:
"""
List all available devices from the api
:param mac: if defined, return information for this device only
:return: A list of devices on the format of a dict
"""
r = self._session.get("{}/api/s/{}/stat/device/{}".format(self._baseurl, self._site, mac, selfverify=self._verify_ssl), data="json={}")
self._current_status_code = r.status_code
if self._current_status_code == 401:
_LOGGER.error("Unifi: Invalid login, or login has expired")
return None
data = r.json()['data']
return data
def update_ap_list(self, newmac):
device_info = (self.list_devices(mac=newmac))
try:
self._ap_list[newmac] = "AP_" + device_info[0]['name']
except:
self._ap_list[newmac] = newmac
def update(self):
self.login()
self.total = 0
self.attrs = {}
devices_per_essid = {}
devices_per_ap = {}
devices_per_ap_name = {}
devices_wired = 0
device_list = (self.list_clients())
for device in device_list:
self.total += 1
try:
if device['is_wired']:
devices_wired += 1
else:
if device['essid'] in devices_per_essid.keys():
devices_per_essid[device['essid']] += 1
else:
devices_per_essid[device['essid']] = 1
if device['ap_mac'] in devices_per_ap.keys():
devices_per_ap[device['ap_mac']] += 1
else:
devices_per_ap[device['ap_mac']] = 1
except:
_LOGGER.error("error processing device %s", device["mac"])
for ap in devices_per_ap.keys():
if ap in self._ap_list.keys():
devices_per_ap_name[self._ap_list[ap]] = devices_per_ap[ap]
else:
self.update_ap_list(ap)
devices_per_ap_name[self._ap_list[ap]] = devices_per_ap[ap]
#update attrs
for key in devices_per_essid.keys():
self.attrs[key] = devices_per_essid[key]
for key in devices_per_ap_name.keys():
self.attrs[key] = devices_per_ap_name[key]
if devices_wired > 0:
self.attrs['wired'] =devices_wired
self.logout()
thanks
Hi,
hm… not sure what’s happening. But please try to remove the “region:” key from config.
This sensor is awesome! @clyra
-
Does “region” = “site” in UniFi? If so, what’s the exact syntax that should be used? the name of the site or the code in the URL? e.g. I’m not using the default site for my set up.
UPDATE - I’ve confirmed this requires the SITE ID when not using the default. This would be a good thing to note for those not using the default site. -
URL & SSL - my setup does not have an SSL cert configured, but I can access it externally through SSL via reverse proxy. Should I be using the external URL or the local one with port number?
UPDATE - once I changed the “region” to the SITE ID, I confirmed this works for either URL.
Would it be possible to pull out a single AP from this sensor so you can quickly see if its status?
Hi,
yes, region = site. I just re-used a know configuration key from HA. Nice you get it working. There are a lot of things I just didn’t thought people would do/want :-). By seeing the status of a single ap, what you mean?
I have an AP that is meshed to the rest of my network. It’d be great if I could build automation around its status. If offline, do x.
Excited to see this working!
hm… ok. I will look into it, meanwhile you have two other options: 1) use the current sensor and see if there’s one or more clients connected to it (look into the attributes), or )2) use the ping sensor and see if it’s online.
Thanks for this. Going to try it out tonight. Cheers
Initially had trouble, but got it going by removing ssl.
@clyra thank you for sharing this component.
would you please consider adding the json tracker to use with the below component which provides immediate updates on any changes you may effect.
Sure! I guess that next week I will have time to look how to do it.
Hi,
Thanks for sharing this component! I was trying to set this up to simply track when guests are present or not but am finding that only networks with devices connected show up in the attributes of the sensor, but if no-one is connected to the guest network the attribute for that network disappears.
This has meant that my binary template sensor runs into errors whenever no-one is connected to the guest network because the attribute no longer exists
Could this behaviour change in the sensor so that all networks still exist as attributes but show as ‘0’ until a device connects?
Hope this makes sense!
Cheers,
James