Monitoring UniFi Access Points using SNMP, Telegraf, Influx and Grafana

Monitoring your network is useful but also fun for many reasons. It can help you to detect anomalies but also show interesting facts about your network. And finally it is just fun to have everything monitored. This post will talk about how I use my existing monitoring setup to get statistics about my recently installed UniFi nanoHD access point.

First, I shouldn’t have to mention that this post covers monitoring in my setup. However, the SNMP MIBs and OIDs can be used to get information without having Telegraf, Influx or Grafana set up as I have. Therefore, let’s start by getting information without all of these tools.

SNMP crash course

For everyone that hasn’t heard of SNMP here is a short introduction. SNMP is an abbrevation for Simple Network Management Protocol. Therefore it was designed for management, and not for monitoring exclusively. It is designed around Object Identifiers (OIDs) which are used to address properties in a device. They are managed centrally and therefore do not overlap between manufacturers.

There are different representations of them. One being numerical, e.g.


and another one is textual:


They are defined in a Management Information Base (MIB). These MIBs are used to define the OIDs. For the textual example the suffix is the MIB’s name. At this point, this is all you need to know for the remainder of this post.


There are many snmp tools such as snmpstatus, snmptable, snmpget and even snmpset (which you need for actual management tasks). snmpwalk is the most important one in my opinion, as it let’s you walk through the SNMP tree. To install these tools, you should install both the snmp package and the snmp-mibs-downloader package.

Let’s have a look at snmpwalk and use the example from above. The command looks like this:

snmpwalk -v2c -c public UBNT-UniFi-MIB::unifiApSystemModel

Which then returns

UBNT-UniFi-MIB::unifiApSystemModel.0 = STRING: UAP-nanoHD

Cool! We can already read the AP’s system model. But well, I have to confess, you need to know two more details for snmpwalk.
First, there are different versions of SNMP, in this case we use 2c. And second, the community string, that is used to define access rights, which is public in the example. Be careful here, as the versions can be enabled separately and the community string can be changed, as you can see below in the settings of the UniFi controller:

UniFi Controller settings to enable SNMP (Settings -> Services -> SNMP)

With snmpwalk you can now even omit the OID, but be aware that this produces a lot of output (and traffic depending on your uplink!). I will omit both the command and output here. I will however talk about the some of the output. When scrolling throughthere, you might find lines such as:

IF-MIB::ifName.2 = STRING: eth0
IF-MIB::ifHCOutOctets.31 = Counter64: 35484074
IF-MIB::ifConnectorPresent.2 = INTEGER: true(1)

As you should know from before IF-MIB::ifName.2 is the full IOD and IF-MIB is the MIB. IF stands for interface in this case, and returns information about one or more interfaces present on the device. From the output above there is a interface eth0, which has send 35484074 bytes and it has a connector! At this point you could go ahead, enable SNMP on all your devices (if possible), and see what information they return.
But careful, snmpwalk does not include vendor specific MIBs, such as those from Cisco, UniFi and others. It will only traverse to the common OID tree here. We did confirm though, that the UniFi APs also support IF-MIB, a commonly used MIB for information about interfaces. The MIB contents can be looked up at different sites. So let’s talk about the MIBs.

Available MIBs: IF-MIB & UBNT-UniFi-MIB

IF-MIB is supported by the UniFi AP as we have seen through snmpwalk. As mentioned above, there are also vendor specific MIBs. For UBNT there are UBNT-MIB and UBNT-UniFi-MIB and they can be downloaded here:

Those might have to be downloaded and saved on your system. You can either put them locally in $HOME/.snmp/mibs/ or in /usr/share/snmp/mibs. Please note, that there are many more locations, which you can see using snmpwalk –help. The mibs downloader should download them as required automatically, is possible. But putting them there puts you in a safe space.

Now that we have installed all the MIBs, we can talk about their contents and information they give us. I will only talk about two types of information here, one being single information such hostname or uptime of your system. The other one is called a table. Each table contains one or more entries. For fun, you can have a look at the following command (note that we are using smnptable here and for this to work the MIB has to be installed!):

snmptable -v2c -c public IF-MIB::ifXTable

The command above should give you a table-like overview of all available interfaces on your UniFi AP! Important: I use ifXTable and not ifTable here. The difference is quite small but very important! ifTable and ifXTable both store information about transmitted, received and dropped packets and bytes. The advantage of ifXtable is that it uses 64bit counters, while ifTable only uses 32bit counters, which will overflow much faster. Therefore you should always use ifXTable if possible! Some of the information available are:

OID nameDescription
ifNameName of the interface
ifHCInOctetsReceived bytes
ifHCOutOctetsTransmitted bytes
ifConnectorPresentIndicator of a connector
ifAliasAlias for the interface (not importanthere)

For most network devices this is all you need to monitor interfaces. But there are many more vendor specific MIBs that we have a look at next.


Far more interesting for this post is the UBNT-UniFi-MIB. It contains a lot of information which can be separated into three groups that are defined in the MIB:

  • unifiApWireless
  • unifiApIf
  • unifiApSystem

unifiApIf is ignored from this point on, as IF-MIB::ifXTable gives a lot more information, especially if you plan on using VLANs for your SSIDs.
Let’s have a look at unifiApSystem first, as the only really unique information here is unifiApSystemUptime, which gives you the system’s uptime.
More interesting is unifiApWireless, which gives you a lot of information about the wireless interfaces:

OID name Description
unifiVapEssId Wireless SSID
unifiVapNumStations Number of clients
unifiVapRadio na or ng for 2.4 GHz or 5 GHz
unifiVapRxBytes Received Bytes
unifiVapTxBytes Transmitted Bytes
unifiVapChannel Channel
unifiVapUsage Policy (user or guest)
unifiVapTxPower Transmit Power
unifiVapUp State
unifiVapName Internal Interface Name

Setting up system information

With these information we can finally start monitoring our APs. But before actually setting up Telegraf for the SNMP monitoring, we have to setup each APs information. First, you should not only set the device name correctly, but also its location and contact name. In my monitoring I use the location as the primary name, as it turns out that spaces in the device’s name are not correctly reflected by SNMP. Using the location instead works even with spaces! In the image below you can see the device’s settings.

Unifi device list and settings of a single Access Point

Putting it together

Now that we have setup the AP with the information required, we can start monitoring it. In my setup I use three tools for this purpose:

  1. Telegraf for data collection
  2. Influx for data storage
  3. Grafana for visualization

Following from now on, I will only talk about the Telegraf configuration files. If you want to know more about the further processing and visualization, please read my coming article about my monitoring setup.

In Telegraf we can use the snmp input module to query information about each AP. Below you can see my configuration file to monitor the uptime.


# configure snmp module
  agents = [ "" ]
  version = 2
  community = "public"
  interval = "60s"
  timeout = "30s"
  name = "unifi_ap"

# tags
  oid = "RFC1213-MIB::sysName.0"
  name = "name"
  is_tag = true

  oid = "RFC1213-MIB::sysLocation.0"
  name = "location"
  is_tag = true

# fields
  oid = "UBNT-UniFi-MIB::unifiApSystemUptime.0"
  name = "uptime"

Line two contains all IP address of your APs. Line seven defines the name of the target (e.g. influx measurement name). Line 12 define the oid. If name in line 13 was omitted, the target name would be sysName, but I prefer using my own names here! This continues for all fields. In the above example I use both the sysName and sysLocation as tags, as these are meta information I can do filtering on. The only actual metric that changes over time is the uptime!

Testing telegraf configuration files

You can go ahead and test it by using the following command:

telegraf --test --config unifi_ap.conf

And tadaaaa, we have a working monitoring for our access point:

2019-06-01T19:47:40Z I! Starting Telegraf 1.9.0
> unifi_ap,agent_host=,host=kuschelpi,location=Dachboden\ Nord,name=Dachboden-Nord uptime=188760i 1559418461000000000

Next on the list is IF-MIB. Once again you will find the configuration below. This time we are using some more advanced features. First of all, we don’t use a name for the snmp module this time, as we use a table. But before that, we have defined two tags, that we want to automatically added to our data. The definition is identical to the one from above.

The table is defines with inputs.snmp.table, note that we have set the inherit_tags property, so all entries in the table are correctly tagged. The table is then followed by the target name (unifi_ap_if) in line 27. And finally, all fields and tags have been defined. Once again, I put everything as tags, which shouldn’t change very often, and can be used for filtering later. In the example below, the only thing that should change is the number of bytes received and transmitted.


# general information
  agents = [ "" ]
  version = 2
  community = "public"
  interval = "60s"
  timeout = "30s"

# meta information
# this will be added automatically as a tag to the next snmp.table
  oid = "RFC1213-MIB::sysName.0"
  name = "ap_name"
  is_tag = true

  oid = "RFC1213-MIB::sysLocation.0"
  name = "ap_location"
  is_tag = true

# table
# Using the oid here will query all fields automatically!
# Be careful when using the oid on inputs.snmp.table as this will
# produce a lot of information
# oid = "IF-MIB::ifXTable"
  name = "unifi_ap_if"
  inherit_tags = ["ap_name", "ap_location"]

# fields
  oid = "IF-MIB::ifName"
  name = "name"
  is_tag = true

  oid = "IF-MIB::ifOperStatus"
  name = "status"
  is_tag = true

  oid = "IF-MIB::ifAlias"
  name = "ifAlias"
  is_tag = true

  oid = "IF-MIB::ifPromiscuousMode"
  name = "promiscuous_mode"
  is_tag = true

  oid = "IF-MIB::ifConnectorPresent"
  name = "has_connector"
  is_tag = true

  oid = "IF-MIB::ifPhysAddress"
  name = "mac"
  conversion = "hwaddr"
  is_tag = true

  oid = "IF-MIB::ifHCInOctets"
  name = "rx_bytes"

  oid = "IF-MIB::ifHCOutOctets"
  name = "tx_bytes"


At last we have the most interesting configuration file. Once again we use some meta data information and a table. But this time we use the unifiApWireless from above. It tells many information about the SSIDs. Below there is my example configuration file I use in production.

  agents = [ "" ]
  version = 2
  community = "public"
  interval = "60s"
  timeout = "30s"

# Meta information
  oid = "RFC1213-MIB::sysName.0"
  name = "name"
  is_tag = true

  oid = "RFC1213-MIB::sysLocation.0"
  name = "location"
  is_tag = true

# Table definition for radio
  name = "unifi_ap_radio"
  index_as_tag = true
  inherit_tags = ["name", "location"]

  oid = "UBNT-UniFi-MIB::unifiVapUsage"
  name = "usage"
  is_tag = true

  oid = "UBNT-UniFi-MIB::unifiVapName"
  name = "name"
  is_tag = true

  oid = "UBNT-UniFi-MIB::unifiVapRadio"
  name = "radio"
  is_tag = true

  oid = "UBNT-UniFi-MIB::unifiVapUp"
  name = "is_up"

  oid = "UBNT-UniFi-MIB::unifiVapTxPower"
  name = "tx_power"

  oid = "UBNT-UniFi-MIB::unifiVapChannel"
  name = "channel"

  oid = "UBNT-UniFi-MIB::unifiVapNumStations"
  name = "clients"

  oid = "UBNT-UniFi-MIB::unifiVapRxBytes"
  name = "rx_bytes"

  oid = "UBNT-UniFi-MIB::unifiVapRxDropped"
  name = "rx_dropped"

  oid = "UBNT-UniFi-MIB::unifiVapRxErrors"
  name = "rx_errors"

  oid = "UBNT-UniFi-MIB::unifiVapRxPackets"
  name = "rx_packets"

  oid = "UBNT-UniFi-MIB::unifiVapTxBytes"
  name = "tx_bytes"

  oid = "UBNT-UniFi-MIB::unifiVapTxDropped"
  name = "tx_dropped"

  oid = "UBNT-UniFi-MIB::unifiVapTxErrors"
  name = "tx_errors"

  oid = "UBNT-UniFi-MIB::unifiVapTxPackets"
  name = "tx_packets"

This concludes this post about how to use SNMP to query information about UniFi access points. If you want to read more about my monitoring, check out other blog posts and check back frequently if new posts have been published. In the mean time feel free to ask questions in the comments below.

Tesla Model 3: What we know!

Yes! I did it! I ordered my Model 3 🙂 It was a great day and seeing all the people talking to each other has been and extraordinary experience for me. This blog entry gives an overview of all the features that has been announced in the first unveil or by Elon Musk via Twitter – as he answers a lot of questions there!

Features from unveil:

  • 215 Miles range
  • 0-60mph (100km/h) in under 6 seconds
  • Comfortable seating for 5 adults
  • 5-star Safety Rating
  • Supercharging ‚capability‘
  • 35.000$
  • Full glass roof

Even more features

But even more features have been announced via Twitter. Here’s a list with more features!

Some speculations

As you can see in the variety of pictures, the Model 3 has a single monitor in the front. Elon says it will make sense after part 2 of the unveil. Twitter.

The actual steeing controls will be different. Twitter.

A bike will still fit in the trunk! Twitter.

Some more facts

Compiling a C++ project for STM32F4 Discovery with RAW GCC Toolchain and without any IDE


FILES="$(STDPERIPH_PATH)/src/stm32f4xx_rcc.c" $(STDPERIPH_PATH)/src/stm32f4xx_gpio.c $(STDPERIPH_PATH)/src/stm32f4xx_exti.c $(STDPERIPH_PATH)/src/stm32f4xx_syscfg.c $(STDPERIPH_PATH)/src/misc.c $(DISCOVERY_PATH)/stm32f4_discovery.c $(CMSIS_DEVICE_PATH)/Source/Templates/system_stm32f4xx.c

OPTIONS=-nostartfiles -mfpu=fpv4-sp-d16 -mthumb –specs=nosys.specs -ffunction-sections -fdata-sections -Os -flto -T STM32F407VG_FLASH.ld –std=c++11 -v -Wall

%.elf : main.c $(STARTUP)
„$(GCC_PATH)“\arm-none-eabi-g++.exe $(OPTIONS) $(DEFINES) $(LIBRARIES) -o $@ $? $(FILES)

%.bin : %.elf
„$(GCC_PATH)“\arm-none-eabi-objcopy.exe -O binary $< $@

%.upload : %.bin
$(STLINK_PATH)/ST-LINK_CLI.exe -V -P $< 0x08000000

all: blinky.upload

Using HTML5 MediaSource API as Video Source

More than half a year ago I developed a HTML5 Video Player that is available on github. The webserver (in our case lighttpd) accepts (byte) range requests. So we can feed the source (mp4, webm or ogg) directly as a source to the player/video element. So the video gets loaded as needed. This worked great for the last months and works automatically.

I recently came across a problem with Internet Explorer 11 on Windows 8.1. The browser does not play the video. It completely freezes instantly or after a few seconds. There is no chance to view the video except if you disable byte range request and load the video completely. It does this even on the popular videoplayer websites like flowplayer and jwplayer. But it didnt happen with YouTube videos.
After doing some research I found out that the source of the video is not a single URL but a blob (blob:<identifier>). shows the availability of HTML5 Video in your Browser and it also shows the availability of a feature called Media Source Extension (MSE). The MSE is used as a source for the HTML Media Elements (like <video> and <audio>).

Note that MSE is not available in IE11 on Windows 7 (with identical IE version as on Windows 8.1) for unknown reason.

The procedure for using MSE is the following:

  1. Create a new MediaSource (MS) object
  2. Create a blob URL for the MS object
  3. Set src of <video> to the blob URL
  4. Wait for the ’sourceopen‘ event to be fired on MS
  5. Create a new SourceBuffer (SB)
  6. Append data to the SB periodically

This works great for webm and ogg files. The only problem here is how do determine when new data has to be loaded.

Mp4 video files cannot be used in general for the MSE purposes. The keyword here is MPEG DASH (Dynamic Adaptive Streaming over HTTP). This is a technique that allows two things.

  1. It allows to split a mp4 file into segments that can be feed into the MS object
  2. It allows to switch quality while playing without the need two stop playback.

1) Is what we are most interested in because we want to load mp4 video data periodically if needed.

2) is a nice side effect that you may have encountered as well. When switching quality of a video on YouTube, you might have noticed that it take a few seconds until the video has better quality. This is because the current fragment will be played from the SB and after that the fragment with better quality is loaded and played.

I used GPAC’s MP4Box to create a fragmented mp4 file, that can be used for streaming. Mp4Box also created a XML file that contains the fragments. Not only the byte ranges are included but also the time offset in the video files. This way you can load a new segment when the segment is almost finished playing (e.g. 80% of the time of the segment is reached).

I hope this gives you a short introduction. I will post more blog posts when I have some demos/code I can present.

Mixing styles from an attribute and .style in dart in a polymer element

I came across this behaviour by chance. Imagine the following code that is used in a custom polymer element:


<div id="foo" style="width: {{ totalWidth }}px;"></div>


And in Dart I tried the following:

$[‚foo‘].style.backgroundImage = ‚url(‚+imgAsBase64+‘)‘;

I tried all different syntaxes of URL()… but never got it working. What happens here is pretty simple: I tried to set the backgroudn image via the style attribute before the style attribute was evaluated by polymer. So the re-rendering (because I changed totalWidth also) overwrites my changes. Mixing is complicated that’s why I finally put it all into the HTML Code:


<div id="foo" style="width: {{ totalWidth }}px; background-image: url({{ img }});"></div>


Spawning isolates in Dart

Spawning new isolates in Dart is easy if you know how to do it:


[dart]import ‚dart:isolate‘;
import ‚dart:async‘;

void main() {
Future isolate1 = Isolate.spawnUri(Uri.parse(‚isolate.dart‘), [], null);
Future isolate2 = Isolate.spawnUri(Uri.parse(‚isolate.dart‘), [], null);

Future.wait only waits until the two isolates are spawned. If the main application stops running, the two spawned isolates will stop running as well. That’s why we need a while(true)-loop here.

The isolate itself looks like this:

[dart]library isolate;

void main() {

The two arguments after the URI are used for a list of arguments and an optional message. If you want to use this, just modify the main method of your isolate:

void main(args) {…}
void main(args, message) {…}[/dart]

Nested Polymer Elements and Data Binding in Google’s Dart

Nesting Polymer Elements in Dart can be difficult sometimes. I want to give you some ideas how to nest polymer elements in Dart and how to realize data binding between multiple nested elements. Let’s get started:

Nested Polymer Elements and Data Binding in Google’s Dart weiterlesen

MySQL: ORDER BY RAND() – a case study of alternatives

I finally had the time to translate my post from german to english – so here we go 🙂

Das Problem

Many of you reading this post might already stopped by this problem – you just want a few random rows from your database and most of the times you might have used „ORDER BY RAND()“ and not thought about, what’s happening there. Just add all the columns you want to select, add JOINS, WHERE, etc. and add ORDER BY RAND() and LIMIT.

For small applications this might even scale well, but what happens in large applications with hundreds, thousands or millions of rows in a table?

I want to look a little closer into this. While developing my deprecated rhCMS (a content management system), I asked myself the same question.

MySQL: ORDER BY RAND() – a case study of alternatives weiterlesen

[Dart+Polymer] Styling Elements

Styling custom elements is sometimes tricky. In general the website styles do NOT apply to the custom element. There is a special attribute called „apply-author-styles“ that can be set to „true“ to apply your website’s styles to your custom element.

In general each custom element should have its  styles separated from the website so it can be reused easily. Let’s have a look at a simple example:

[Dart+Polymer] Styling Elements weiterlesen

Animatr – GUI & Structure: Nesting Polymer Elements

The first step of rebuilding animatr was to remove all code and totally rebuild the GUI using a single custom element: <animatr-gui> using polymer. The GUI itself has three nested custom elements:

<animatr-titlebar> – Containins the windows‘ title, close button, …
<animatr-toolbars> – Wrapper for the toolbars
<animatr-panels> – A wrapper to create a GUI using pannels that can be dragged.

The HTML code of the root element looks like this:

[code language=“html“]
<link href="packages/animatr_gui/elements/animatr-ui.html" rel="import" />

The corresponding element HTML code is:

[code language=“html“]
<polymer-element name="animatr-ui">
<meta charset="utf-8"/>
<link rel="import" href="animatr-titlebar.html">
<link rel="import" href="animatr-toolbars.html">
<link rel="import" href="animatr-panels.html">
<link rel="import" href="animatr-panel-….html">
<animatr-titlebar id="animatr-titlebar"></animatr-titlebar>
<animatr-toolbars id="animatr-toolbars"></animatr-toolbars>
<animatr-panels id="animatr-panels"></animatr-panels>
<script type="application/dart" src="animatr-ui.dart"></script>

The Dart code for the custom element is:

import 'package:polymer/polymer.dart';

import 'dart:html';

import 'package:animatr_gui/elements/animatr-titlebar.dart';
import 'package:animatr_gui/elements/animatr-toolbars.dart';
import 'package:animatr_gui/elements/animatr-panels.dart';

import 'animatr-panel-....dart';

class AnimatrUiElement extends PolymerElement {
  bool get applyAuthorStyles => true;

  ...PanelElement panel_...;

  AnimatrUiElement.created() : super.created() {
    panel_... = document.createElement('animatr-panel-...');

  AnimatrTitlebarElement titlebar;

  AnimatrToolbarsElement toolbars;

  AnimatrPanelsElement panels;

  @observable Project currentProject;

  void enteredView() {

    titlebar = $['animatr-titlebar'];
    toolbars = $['animatr-toolbars'];
    panels = $['animatr-panels'];

    titlebar.ui = this;
    panels.ui = this;

    panels.addPanel(panel_..., 'north/east/south/west/center');

Nesting elements was complicated first. Assuming you have your project’s root file (mine is called animatr.html), you could import all elements in this file, at a central place. But this had some strange behaviour to me:
The outer elements get created first and thus, before all nested elements. Thus I wasn’t able to correctly query for the nested elements. Because the custom element wasn’t registered Google Dart had some type errors.

The solution was to import other elements within the custom element itself, right before the <template> tag.

I had another problem using special characters. It turned out that the charset settings (e.g. by using <meta charset=“utf-8″/>) is not propagated to custom elements. Thus make sure to include a <meta>-tag in your custom elements if you want to use custom elements.