Projet

Général

Profil

Paste
Télécharger au format
Statistiques
| Branche: | Révision:

root / plugins / ubiquiti / unifi_api @ c0108c95

Historique | Voir | Annoter | Télécharger (38,3 ko)

1
#!/usr/bin/perl
2
# -*- perl -*-
3

    
4
=encoding utf8
5

    
6
=head1 NAME
7

    
8
unifi_api - Munin plugin to display device and network information from the 
9
            Ubiquiti unifi API
10

    
11
=head1 APPLICABLE SYSTEMS
12

    
13
Unifi controllors with direct API access
14

    
15
Controller version 5+ required (tested with 5.8.x)
16

    
17
WebRTC is not supported at this time
18

    
19
=head1 CONFIGURATION
20

    
21
This script uses the multigraph functionality to generate many graphs.  As such, there
22
are a significant amount of available configuration options
23

    
24
=head2 API Details
25

    
26
You will need to supply your API login details:
27

    
28
  [unifi_api]
29
    # User name to login to unifi controller API.  Default is "ubnt".  Ideally, this should
30
    # point to a read-only account.
31
    env.user Controller_Username
32

    
33
    # Password to login to unifi controller API. Default is "ubnt"
34
    env.pass Controller_Password
35

    
36
    # URL of the API, with port if needed.  No trailing slash.
37
    # Default is https://localhost:8443
38
    env.api_url https://unifi.fqdn.com:8443
39

    
40
    # Verify SSL certificate name against host.
41
    # Note: if using a default cloudkey certificate, this will fail unless you manually add it
42
    # to the local keystore.
43
    # Default is "yes"
44
    env.ssl_verify_host no
45

    
46
    # Verify Peer's SSL vertiicate.
47
    # Note: if using a default cloudkey certificate, this will fail
48
    # Default is "yes"
49
    env.ssl_verify_peer no 
50

    
51
    # The human readable name of the unifi site - used for graph titles
52
    env.name Site Name
53

    
54
    # "Site" string - the internal unifi API site identifier. 
55
    # default is "default" - found when you connect to the web interface
56
    # it's the term in the URL - /manage/site/site_string/dashboard
57
    env.site site_string 
58

    
59

    
60
=head2 Graph Categories / Host Management
61

    
62
Sometimes, you need more control over where the unifi graphs appear.
63

    
64
    env.force_category 0
65
    # By default, Use standard munin well know categories - 
66
    #  system: cpu, mem, load, & uptime
67
    #  network: clients, transfer statistics.
68
    #
69

    
70
To use this feature, set "force_category" to a text string (i.e. "unifi").
71

    
72
This is very helpful if your graphs are going to appear inside another host - for instance
73
if your munin graphs for that host are monitoring the host the controller is running on, and
74
the unifi API instance.
75

    
76
Sometimes however, you want to monitor either an offsite API, or a cloudkey which, at least by
77
default, does not run munin-node.  In that case, you can actually create a "virtual" munin host to
78
display only these graphs (or any combination you like).  This is documented in the main munin docs, 
79
but in a nutshell:
80

    
81
In your munin-node plugin configuration: (Something like: /etc/munin/plugin-conf.d/munin-node)
82

    
83
  [unifi_api]
84
    host_name hostname.whatever.youlike
85
    env.force_category unifi
86

    
87
And, in your munin master configuration: (Something like: /etc/munin/munin.conf)
88

    
89
  [hostname.whatever.youlike]
90
    address ip.of.munin.node
91

    
92
Make sure you do *not* set "use_node_name" on this new host.  It may be necessary to define "host_name"
93
in your munin-node configuration as well, if you have not already (Likely, on a multi-homed host, this 
94
has been done to keep munin-node from advertising itself as localhost)
95

    
96
More information:
97

    
98
 * L<host_name|http://guide.munin-monitoring.org/en/latest/plugin/use.html>
99

    
100

    
101
=head2 Toggling of graphs / Individual options
102

    
103
You can turn off individual graphs.  A few graphs have extra configuration
104
options.
105

    
106
By default, everything is enabled.  Set to "no" to disable
107

    
108
  [unifi_api]
109
    # Show device CPU utilization
110
    env.enable_device_cpu yes
111

    
112
    # Show device memory usage
113
    env.enable_device_mem yes
114

    
115
    # Show device load average (switches and APs only)
116
    env.enable_device_load yes
117

    
118
    # Show device uptime
119
    env.enable_device_uptime yes
120

    
121
    # Show number of clients connected to each device
122
    env.enable_clients_device yes
123
    # Show detailed graphs for each device (per device graphs)
124
    env.enable_detail_clients_device yes
125

    
126
    # Show number of clients connected to each network type
127
    env.enable_clients_type yes
128
    # Show detailed graphs for each client type (per type graphs)
129
    env.enable_detail_clients_type yes
130
    # Show unauthorized / authorized client list
131
    # if you are not using the guest portal, this is useless
132
    env.show_authorized_clients_type yes
133

    
134
    # Show transfer statistics on switch ports
135
    env.enable_xfer_port yes
136
    # Show detailed graphs per switch port
137
    env.enable_detail_xfer_port yes
138
    # Hide ports that have no link (When set to no, unplugged ports will transfer 0, not be undefined)
139
    env.hide_empty_xfer_port yes
140

    
141
    # Show transfer statistics per device
142
    env.enable_xfer_device yes
143
    # Show detailed graphs for each device
144
    env.enable_detail_xfer_device yes
145

    
146
    # Show transfer statistics per named network
147
    env.enable_xfer_network yes
148
    # Show detailed graphs for each named network
149
    env.enable_detail_xfer_network yes
150

    
151
    # Show transfer statistics per radio
152
    env.enable_xfer_radio yes
153
    # Show detailed graphs for each radio
154
    env.enable_detail_xfer_radio yes
155

    
156

    
157
=head1 CAPABILITIES
158

    
159
This plugin supports L<DIRTYCONFIG|http://guide.munin-monitoring.org/en/latest/plugin/protocol-dirtyconfig.html>
160

    
161
=head1 DEPENDENCIES
162

    
163
This plugin requires munin-multiugraph.
164

    
165
=over
166

    
167
=item WWW::Curl::Easy
168

    
169
Perl extension interface for libcurl
170

    
171
=item JSON
172

    
173
 JSON (JavaScript Object Notation) encoder/decoder
174

    
175
=back
176

    
177
=head1 PERFORMANCE CONCERNS
178

    
179
The main performance concern on this is the huge number of graphs that may be
180
generated.  Using the cron version of munin-graph may hurt a lot.
181

    
182
A bit of a case study:
183

    
184
                 | My Site  | UBNT Demo
185
---------------------------------------
186
Devices          |       8  |     126
187
AP's             |       4  |     118
188
24xSwitch        |       1  |       5
189
8xSwitch         |       2  |       2
190
Output Bytes     |  64,262  | 431,434
191
Output Lines     |   1,761  |  14,586
192
Output Graphs    |      77  |     530
193

    
194
So, just note that the growth in the amount of graphed date can be extreme.
195

    
196

    
197
=head1 LICENSE
198

    
199
Copyright (C) 2018 J.T.Sage (jtsage@gmail.com)
200

    
201
This program is free software: you can redistribute it and/or modify
202
it under the terms of the GNU General Public License as published by
203
the Free Software Foundation, either version 3 of the License, or
204
any later version.
205

    
206
This program is distributed in the hope that it will be useful,
207
but WITHOUT ANY WARRANTY; without even the implied warranty of
208
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
209
GNU General Public License for more details.
210

    
211
You should have received a copy of the GNU General Public License
212
along with this program.  If not, see L<http://www.gnu.org/licenses/>.
213

    
214
=head1 MAGIC MARKERS
215

    
216
  #%# family=manual
217
  #%# capabilities=
218

    
219
=cut
220

    
221
use warnings;
222
use strict;
223
use utf8;
224
use Munin::Plugin;
225

    
226
# Check dependencies
227
my @errorCode;
228
my $me = (split '/', $0)[-1];
229

    
230
if (! eval {require JSON; JSON->import(); 1; } ) {
231
	push @errorCode, "JSON module not found";
232
}
233
if (! eval {require WWW::Curl::Easy; 1;} ) {
234
	push @errorCode, "WWW::Curl::Easy module not found";
235
}
236

    
237
# Fail on not found dependencies
238
if ( @errorCode != 0 ) {
239
	die "FATAL:$me: Perl dependencies not installed (", join(", " => @errorCode), ")\n";
240
}
241

    
242
# Multigraph cabability is required for this plugin
243
need_multigraph();
244

    
245
# Somewhat (in)sane defaults for host, pass, etc
246
my %APIconfig = (
247
	'user'            => env_default_text('user'           , 'ubnt'),
248
	'pass'            => env_default_text('pass'           , 'ubnt'),
249
	'api_url'         => env_default_text('api_url'        , 'https://localhost:8443'),
250
	'site'            => env_default_text('site'           , 'default'),
251
	'ssl_verify_host' => env_default_text('ssl_verify_host', 'yes'),
252
	'ssl_verify_peer' => env_default_text('ssl_verify_peer', 'yes'),
253
	'name'            => env_default_text('name'           , 'Unnamed Site'),
254
);
255

    
256
# The big table of plugin options - see POD documentation for what these do.
257
my %PluginConfig = (
258
	'enable_device_cpu'             => env_default_bool_true('enable_device_cpu'),
259
	'enable_device_mem'             => env_default_bool_true('enable_device_mem'),
260
	'enable_device_load'            => env_default_bool_true('enable_device_load'),
261
	'enable_device_uptime'          => env_default_bool_true('enable_device_uptime'),
262
	'enable_clients_device'         => env_default_bool_true('enable_clients_device'),
263
	'enable_clients_type'           => env_default_bool_true('enable_clients_network'),
264
	'enable_xfer_port'              => env_default_bool_true('enable_xfer_port'),
265
	'enable_xfer_device'            => env_default_bool_true('enable_xfer_device'),
266
	'enable_xfer_network'           => env_default_bool_true('enable_xfer_network'),
267
	'enable_xfer_radio'             => env_default_bool_true('enable_xfer_radio'),
268
	'enable_detail_xfer_port'       => env_default_bool_true('enable_detail_xfer_port'),
269
	'enable_detail_xfer_device'     => env_default_bool_true('enable_detail_xfer_device'),
270
	'enable_detail_xfer_network'    => env_default_bool_true('enable_detail_xfer_network'),
271
	'enable_detail_xfer_radio'      => env_default_bool_true('enable_detail_xfer_radio'),
272
	'enable_detail_clients_device'  => env_default_bool_true('enable_detail_clients_device'),
273
	'enable_detail_clients_type'    => env_default_bool_true('enable_detail_clients_network'),
274
	'hide_empty_xfer_port'          => env_default_bool_true('hide_empty_xfer_port'),
275
	'show_authorized_clients_type'  => env_default_bool_true('show_authorized_clients_type'),
276
	'force_category'                => env_default_text('force_category', 0),
277
);
278

    
279
# Set up needed API endpoints
280
my %APIPoint = (
281
	'login'  => $APIconfig{"api_url"} . "/api/login",
282
	'device' => $APIconfig{"api_url"} . "/api/s/" . $APIconfig{"site"} . "/stat/device",
283
	'wlan'   => $APIconfig{"api_url"} . "/api/s/" . $APIconfig{"site"} . "/rest/wlanconf",
284
	'sta'    => $APIconfig{"api_url"} . "/api/s/" . $APIconfig{"site"} . "/stat/sta",
285
);
286

    
287
my %APIResponse;
288
my %APIJsonResponse;
289
my %Data;
290
my $retcode;
291

    
292
# Init curl and JSON
293
my $curl = WWW::Curl::Easy->new() or die "FATAL:$me: WWW::Curl::Easy init failed!\n";
294
my $jsonOBJ = JSON->new() or die "FATAL:$me: JSON init failed!\n";
295

    
296

    
297
## Fetch the data from the API
298

    
299
# The rest is a way to use local files from the local disk. Undocumented and not really supported.
300

    
301
if ( !env_default_bool_true('USE_API') ) {
302
	if (! eval {require File::Slurp; File::Slurp->import(); 1; } ) {
303
		die "Local debug unavailable, File::Slurp CPAN module required\n";
304
	}
305
	foreach ( "./demo-test-files/device", "./demo-test-files/sta", "./demo-test-files/wlanconf" ) {
306
		if ( ! -f $_ ) { die "File not found: $_\n"; }
307
	}
308
	$APIJsonResponse{'device'} = $jsonOBJ->allow_nonref->utf8->relaxed->decode(read_file('./demo-test-files/device'));
309
	$APIJsonResponse{'sta'} = $jsonOBJ->allow_nonref->utf8->relaxed->decode(read_file('./demo-test-files/sta'));
310
	$APIJsonResponse{'wlan'} = $jsonOBJ->allow_nonref->utf8->relaxed->decode(read_file('./demo-test-files/wlanconf'));
311
} else {
312
	fetch_data();
313
}
314

    
315

    
316
## Process the data
317

    
318
make_data();
319

    
320
if ( defined($ARGV[0]) && $ARGV[0] eq "config" ) {
321
	# Do the config step for each set of graphs
322
	do_config_mem();
323
	do_config_cpu();
324
	do_config_load();
325
	do_config_uptime();
326
	do_config_xfer_by_device();
327
	do_config_xfer_by_uplink();
328
	do_config_xfer_by_port();
329
	do_config_xfer_by_network();
330
	do_config_xfer_by_radio();
331
	do_config_clients_by_device();
332
	do_config_clients_by_type();
333

    
334
	# If dirtyconfig is not supported, or turned off, exit here.  Otherwise, continue to fetch section
335
	if ( !defined($ENV{'MUNIN_CAP_DIRTYCONFIG'}) || !$ENV{'MUNIN_CAP_DIRTYCONFIG'} ) {  exit 0; }
336
}
337

    
338
# Do the fetch step for each set of graphs
339
do_values_cpu();
340
do_values_mem();
341
do_values_load();
342
do_values_uptime();
343
do_values_xfer_by_device();
344
do_values_xfer_by_uplink();
345
do_values_xfer_by_port();
346
do_values_xfer_by_network();
347
do_values_xfer_by_radio();
348
do_values_clients_by_device();
349
do_values_clients_by_type();
350

    
351

    
352

    
353

    
354

    
355

    
356

    
357
#######################
358
# SUBROUTINES  CONFIG #
359
#######################
360

    
361
sub do_config_clients_by_type {
362
	# Provide client count by type - CONFIG
363
	if ( !$PluginConfig{'enable_clients_type'} ) { return 0; }
364

    
365
	graph_prologue(
366
		'unifi_clients_per_network',
367
		'Clients Connected / Network',
368
		'-l 0 --base 1000',
369
		'clients',
370
		'network',
371
		'Clients connected per type - manually summing these numbers may be meaningful, as clients are often of multiple types'
372
	);
373

    
374
	foreach ( @{$Data{'typesOrder'}} ) {
375
		print $_ , ".label " , $Data{'types'}{$_}[0] , "\n";
376
	}
377

    
378
	if ( ! $PluginConfig{'enable_detail_clients_type'} ) { return 1; }
379

    
380
	foreach ( @{$Data{'typesOrder'}} ) {
381
		if ( $Data{'types'}{$_}[1] == 1 ) {
382
			graph_prologue(
383
				'unifi_clients_per_network.' . $_,
384
				'Clients Connected : ' . $Data{'types'}{$_}[0],
385
				'-l 0 --base 1000',
386
				'clients',
387
				'network',
388
				'Clients connected via that are of type: ' . $Data{'types'}{$_}[0]
389
			);
390
			print "users.label Users\n";
391
			print "guests.label Guests\n";
392
		}
393
	}
394
	return 1;
395
}
396

    
397
sub do_config_clients_by_device {
398
	# Provide client count by device - CONFIG
399
	if ( !$PluginConfig{'enable_clients_device'} ) { return 0; }
400

    
401
	graph_prologue(
402
		'unifi_clients_per_device',
403
		'Clients Connected / Device',
404
		'-l 0 --base 1000',
405
		'clients',
406
		'network',
407
		'Clients connected to each unifi device'
408
	);
409

    
410
	foreach ( sort keys %{$Data{'device'}} ) {
411
		print $_ , ".label " , $Data{'device'}{$_}->{'label'} , "\n";
412
	}
413

    
414
	if ( ! $PluginConfig{'enable_detail_clients_device'} ) { return 1; }
415

    
416
	foreach ( sort keys %{$Data{'device'}} ) {
417
		graph_prologue(
418
			'unifi_clients_per_device.' . $_,
419
			'Clients / Device : ' . $Data{'device'}{$_}->{'label'},
420
			'-l 0 --base 1000',
421
			'clients',
422
			'network',
423
			'Clients connected to the ' . $Data{'device'}{$_}->{'label'} . " unifi device"
424
		);
425
		print "users.label Users\n";
426
		print "guests.label Guests\n";
427
	}
428
	return 1;
429
}
430

    
431
sub do_config_xfer_by_radio {
432
	# Provide transfer for radios - CONFIG
433
	if ( !$PluginConfig{'enable_xfer_radio'} ) { return 0; }
434

    
435
	graph_prologue(
436
		'unifi_xfer_per_radio',
437
		'Transfer / radio',
438
		'--base 1000',
439
		'Packets/${graph_period}',
440
		'network',
441
		'Number of packets transferred per individual radio band'
442
	);
443

    
444
	foreach my $thisDevice ( sort keys %{$Data{'device'}} ) {
445
		if ( $Data{'device'}{$thisDevice}->{'type'} ne "uap" ) { next; }
446

    
447
		foreach ( @{$Data{'device'}{$thisDevice}{'radio'}} ) {
448
			print $thisDevice , "_" , $_->{"name"} , "_pack.label " , $_->{"label"} , "\n";
449
			print $thisDevice , "_" , $_->{"name"} , "_pack.type DERIVE\n";
450
			print $thisDevice , "_" , $_->{"name"} , "_pack.min 0\n";
451
		}
452
	}
453

    
454
	if ( ! $PluginConfig{'enable_detail_xfer_radio'} ) { return 1; }
455

    
456
	foreach my $thisDevice ( sort keys %{$Data{'device'}} ) {
457
		if ( $Data{'device'}{$thisDevice}->{'type'} ne "uap" ) { next; }
458

    
459
		graph_prologue(
460
			'unifi_xfer_per_radio.' .  $thisDevice,
461
			'Transfer / radio : ' . $Data{'device'}{$thisDevice}->{'name'},
462
			'--base 1000',
463
			'Packets/${graph_period}',
464
			'network',
465
			'Transfered Packets, Dropped / Retried Packets, and Error Packets for the WLAN device: ' . $Data{'device'}{$thisDevice}->{'name'}
466
		);
467

    
468
		foreach ( @{$Data{'device'}{$thisDevice}{'radio'}} ) {
469
			print $_->{"name"} , "_pkt.label " , $_->{"type"} , " Packets\n";
470
			print $_->{"name"} , "_pkt.type DERIVE\n";
471
			print $_->{"name"} , "_pkt.min 0\n";
472
			print $_->{"name"} , "_dret.label " , $_->{"type"} , " Dropped / Retries\n";
473
			print $_->{"name"} , "_dret.type DERIVE\n";
474
			print $_->{"name"} , "_dret.min 0\n";
475
			print $_->{"name"} , "_err.label " , $_->{"type"} , " Errors\n";
476
			print $_->{"name"} , "_err.type DERIVE\n";
477
			print $_->{"name"} , "_err.min 0\n";
478
		}
479
	}
480
	return 1;
481
}
482

    
483
sub do_config_xfer_by_network {
484
	# Provide transfer for named networks - CONFIG
485
	if ( !$PluginConfig{'enable_xfer_network'} ) { return 0; }
486

    
487
	graph_prologue(
488
		'unifi_xfer_per_network',
489
		'Transfer / named network',
490
		'--base 1000',
491
		'Bytes/${graph_period} rcvd (-) / trans (+)',
492
		'network',
493
		'Bytes sent and received per each named network'
494
	);
495

    
496
	foreach my $thisNet ( sort keys %{$Data{'networks'}} ) {
497
		foreach ( "_rxbytes", "_txbytes" ) {
498
			print $thisNet , $_ , ".label " , $Data{'networks'}{$thisNet}->{"label"} . "\n";
499
			print $thisNet , $_ , ".type DERIVE\n";
500
			print $thisNet , $_ , ".min 0\n";
501
		}
502
		print $thisNet , "_rxbytes.graph no\n";
503
		print $thisNet , "_txbytes.negative " , $thisNet , "_rxbytes\n";
504
	}
505

    
506
	if ( ! $PluginConfig{'enable_detail_xfer_network'} ) { return 1; }
507

    
508
	foreach my $thisNet ( sort keys %{$Data{'networks'}} ) {
509
		graph_prologue(
510
			'unifi_xfer_per_network.' . $thisNet,
511
			'Transfer / named network : ' . $Data{'networks'}{$thisNet}->{'label'},
512
			'--base 1000',
513
			'Bytes/${graph_period} rcvd (-) / trans (+)',
514
			'network',
515
			'Bytes sent and received for the network named: ' . $Data{'networks'}{$thisNet}->{'label'}
516
		);
517
		foreach ( "rxbyte", "txbyte" ) {
518
			print $_ , ".label Bytes\n";
519
			print $_ , ".type DERIVE\n";
520
			print $_ , ".min 0\n";
521
		}
522
		print "rxbyte.graph no\n";
523
		print "txbyte.negative rxbyte\n";
524
	}
525
	return 1;
526
}
527

    
528
sub do_config_xfer_by_port {
529
	# Provide transfer for switch ports - CONFIG
530
	if ( !$PluginConfig{'enable_xfer_port'} ) { return 0; }
531

    
532
	foreach my $thisDevice ( sort keys %{$Data{'device'}} ) {
533
		if ( $Data{'device'}{$thisDevice}->{'type'} ne "usw" ) { next; }
534
		graph_prologue(
535
			'unifi_xfer_per_port_' .  $thisDevice,
536
			'Transfer / port : ' . $Data{'device'}{$thisDevice}->{'label'},
537
			'--base 1000',
538
			'Bytes/${graph_period} rcvd (-) / trans (+)',
539
			'network',
540
			'Bytes sent and received per port on the switch named: ' . $Data{'device'}{$thisDevice}->{'label'}
541
		);
542
		foreach my $thisPort ( @{$Data{'device'}{$thisDevice}{'ports'}} ) {
543
			foreach ( "_rxbytes", "_txbytes" ) {
544
				print $thisDevice , "_" , $thisPort->{"name"} , $_ , ".label " , $thisPort->{"label"} . "\n";
545
				print $thisDevice , "_" , $thisPort->{"name"} , $_ , ".type DERIVE\n";
546
				print $thisDevice , "_" , $thisPort->{"name"} , $_ , ".min 0\n";
547
			}
548
			print $thisDevice , "_" , $thisPort->{"name"} , "_rxbytes.graph no\n";
549
			print $thisDevice , "_" , $thisPort->{"name"} , "_txbytes.negative " , $thisDevice , "_" , $thisPort->{"name"} , "_rxbytes\n";
550
		}
551
	}
552

    
553
	if ( ! $PluginConfig{'enable_detail_xfer_port'} ) { return 1; }
554

    
555
	# Extended graphs
556
	foreach my $thisDevice ( sort keys %{$Data{'device'}} ) {
557
		if ( $Data{'device'}{$thisDevice}->{'type'} ne "usw" ) { next; }
558
		foreach my $thisPort ( @{$Data{'device'}{$thisDevice}{'ports'}} ) {
559
			graph_prologue(
560
				'unifi_xfer_per_port_' .  $thisDevice . "." . $thisPort->{'name'},
561
				'Transfer / port : ' . $Data{'device'}{$thisDevice}->{'label'} . " : " . $thisPort->{'label'},
562
				'--base 1000',
563
				'Bytes/${graph_period} rcvd (-) / trans (+)',
564
				'network',
565
				'Bytes sent and received on port "' . $thisPort->{'label'} . '" of the switch "' . $Data{'device'}{$thisDevice}->{'label'} . '"'
566
			);
567
			foreach ( "rxbyte", "txbyte" ) {
568
				print $_ . ".label Bytes\n";
569
				print $_ . ".type DERIVE\n";
570
				print $_ . ".min 0\n";
571
			}
572
			print "rxbyte.graph no\n";
573
			print "txbyte.negative rxbyte\n";
574
		}
575
	}
576
	return 1;
577
}
578

    
579
sub do_config_xfer_by_uplink {
580
	# Provide transfer for unifi uplink - CONFIG
581
	if ( !$PluginConfig{'enable_xfer_device'} ) { return 0; }
582

    
583
	graph_prologue(
584
		'unifi_xfer_by_uplink',
585
		'Transfer on uplink : ' . $Data{'uplink'}{'devName'},
586
		'--base 1000',
587
		'Bytes/${graph_period} rcvd (-) / trans (+)',
588
		'network',
589
		'Bytes sent and received on the WAN port of the USG, and the speedtest result of the same port'
590
	);
591

    
592
	foreach ( "rx", "tx" ) {
593
		print $_ , "_speed.label Speedtest\n";
594
		print $_ , "_bytes.label Transferred\n";
595
		print $_ , "_speed.type GAUGE\n";
596
		print $_ , "_bytes.type DERIVE\n";
597
		print $_ , "_speed.min 0\n";
598
		print $_ , "_bytes.min 0\n";
599
	}
600

    
601
	print "rx_speed.graph no\n";
602
	print "rx_bytes.graph no\n";
603
	print "tx_speed.negative rx_speed\n";
604
	print "tx_bytes.negative rx_bytes\n";
605

    
606
	return 1;
607
}
608

    
609
sub do_config_xfer_by_device {
610
	# Provide transfer for each unifi device - CONFIG
611
	if ( !$PluginConfig{'enable_xfer_device'} ) { return 0; }
612

    
613
	graph_prologue(
614
		'unifi_xfer_per_device',
615
		'Transfer / device',
616
		'--base 1000',
617
		'Bytes/${graph_period} rcvd (-) / trans (+)',
618
		'network',
619
		'Bytes sent and received per unifi device'
620
	);
621

    
622
	foreach my $thisDevice ( sort keys %{$Data{'device'}} ) {
623
		foreach ( "_rxbytes", "_txbytes" ) {
624
			print $thisDevice , $_ , ".label " , $Data{'device'}{$thisDevice}->{'label'} , "\n";
625
			print $thisDevice , $_ , ".type DERIVE\n";
626
			print $thisDevice , $_ , ".min 0\n";
627
		}
628
		print $thisDevice , "_rxbytes.graph no\n";
629
		print $thisDevice , "_txbytes.negative " , $thisDevice , "_rxbytes\n";
630
	}
631

    
632
	if ( $PluginConfig{'enable_detail_xfer_device'} ) {
633
		foreach my $thisDevice ( sort keys %{$Data{'device'}} ) {
634
			graph_prologue(
635
				'unifi_xfer_per_device.' . $thisDevice,
636
				'Transfer / device : ' . $Data{'device'}{$thisDevice}->{'label'},
637
				'--base 1000',
638
				'Bytes/${graph_period} rcvd (-) / trans (+)',
639
				'network',
640
				'Bytes sent and received on the unifi device named: ' . $Data{'device'}{$thisDevice}->{'label'}
641
			);
642
			foreach ( "rxbyte", "txbyte" ) {
643
				print $_ , ".label Bytes\n";
644
				print $_ , ".type DERIVE\n";
645
				print $_ , ".min 0\n";
646
			}
647
			print "rxbyte.graph no\n";
648
			print "txbyte.negative rxbyte\n";
649
		}
650
	}
651
	return 1;
652
}
653

    
654
sub do_config_uptime {
655
	# Provide device uptime for each unifi device - CONFIG
656
	if ( !$PluginConfig{'enable_device_uptime'} ) { return 0; }
657
	graph_prologue(
658
		'unifi_device_uptime',
659
		'Uptime',
660
		'--base 1000 -r --lower-limit 0',
661
		'days',
662
		'system',
663
		'Uptime in days for each unifi device'
664
	);
665

    
666
	foreach ( sort keys %{$Data{'device'}} ) {
667
		print $_ , ".label " , $Data{'device'}{$_}->{"name"} , "\n";
668
	}
669
	return 1;
670
}
671

    
672
sub do_config_cpu {
673
	# Provide device CPU usage for each unifi device - CONFIG
674
	if ( !$PluginConfig{'enable_device_cpu'} ) { return 0; }
675
	graph_prologue(
676
		'unifi_device_cpu',
677
		'CPU Usage',
678
		'--base 1000 -r --lower-limit 0 --upper-limit 100',
679
		'%',
680
		'system',
681
		'CPU usage as a percentage for each unifi device'
682
	);
683

    
684
	foreach ( sort keys %{$Data{'device'}} ) {
685
		print $_ , ".label " , $Data{'device'}{$_}->{"name"} , "\n";
686
	}
687
	return 1;
688
}
689

    
690
sub do_config_load {
691
	# Provide device load average for each unifi device - CONFIG
692
	if ( !$PluginConfig{'enable_device_load'} ) { return 0; }
693
	graph_prologue(
694
		'unifi_device_load',
695
		'Load Average',
696
		'-l 0 --base 1000',
697
		'load',
698
		'system',
699
		'Load average for each unifi Access Point or Switch'
700
	);
701

    
702
	foreach ( sort keys %{$Data{'device'}} ) {
703
		if ( $Data{'device'}{$_}->{'type'} eq 'ugw' ) { next; }
704
		print $_ , ".label " , $Data{'device'}{$_}->{"name"} , "\n";
705
	}
706
	return 1;
707
}
708

    
709
sub do_config_mem {
710
	# Provide device memory usage for each unifi device - CONFIG
711
	if ( !$PluginConfig{'enable_device_mem'} ) { return 0; }
712
	graph_prologue(
713
		'unifi_device_mem',
714
		'Memory Usage',
715
		'--base 1000 -r --lower-limit 0 --upper-limit 100',
716
		'%',
717
		'system',
718
		'Memory usage as a percentage for each unifi device'
719
	);
720

    
721
	foreach ( sort keys %{$Data{'device'}} ) {
722
		print $_ , ".label " , $Data{'device'}{$_}->{"name"} , "\n";
723
	}
724
	return 1;
725
}
726

    
727

    
728

    
729

    
730

    
731

    
732

    
733

    
734

    
735

    
736
#########################
737
# SUBROUTINES    VALUES #
738
#########################
739

    
740
sub do_values_clients_by_type {
741
	# Provide client count by type - VALUES
742
	if ( !$PluginConfig{'enable_clients_type'} ) { return 0; }
743

    
744
	print "multigraph unifi_clients_per_network\n";
745

    
746
	foreach ( @{$Data{'typesOrder'}} ) {
747
		print $_ , ".value " , ( $Data{'types'}{$_}[2] + $Data{'types'}{$_}[3] ) , "\n";
748
	}
749

    
750
	if ( ! $PluginConfig{'enable_detail_clients_type'} ) { return 1; }
751

    
752
	foreach ( @{$Data{'typesOrder'}} ) {
753
		if ( $Data{'types'}{$_}[1] == 1 ) {
754
			print "multigraph unifi_clients_per_network.$_\n";
755
			print "users.value "  , $Data{'types'}{$_}[2] , "\n";
756
			print "guests.value " , $Data{'types'}{$_}[3] , "\n";
757
		}
758
	}
759
	return 1;
760
}
761

    
762
sub do_values_clients_by_device {
763
	# Provide client count by device - VALUES
764
	if ( !$PluginConfig{'enable_clients_device'} ) { return 0; }
765

    
766
	print "multigraph unifi_clients_per_device\n";
767

    
768
	foreach ( sort keys %{$Data{'device'}} ) {
769
		print $_ , ".value " , $Data{'device'}{$_}->{'clients'} , "\n";
770
	}
771

    
772
	if ( ! $PluginConfig{'enable_detail_clients_device'} ) { return 1; }
773

    
774
	foreach ( sort keys %{$Data{'device'}} ) {
775
		print "multigraph unifi_clients_per_device.$_\n";
776
		print "users.value "  , $Data{'device'}{$_}->{'users'}  , "\n";
777
		print "guests.value " , $Data{'device'}{$_}->{'guests'} , "\n";
778
	}
779
	return 1;
780
}
781

    
782
sub do_values_xfer_by_radio {
783
	# Provide transfer for radios - VALUES
784
	if ( !$PluginConfig{'enable_xfer_radio'} ) { return 0; }
785

    
786
	print "multigraph unifi_xfer_per_radio\n";
787

    
788
	foreach my $thisDevice ( sort keys %{$Data{'device'}} ) {
789
		if ( $Data{'device'}{$thisDevice}->{'type'} ne "uap" ) { next; }
790

    
791
		foreach ( @{$Data{'device'}{$thisDevice}{'radio'}} ) {
792
			print $thisDevice , "_" , $_->{"name"} , "_pack.value " , ($_->{"pckt"} // 0), "\n";;
793
		}
794
	}
795

    
796
	if ( ! $PluginConfig{'enable_detail_xfer_radio'} ) { return 1; }
797

    
798
	foreach my $thisDevice ( sort keys %{$Data{'device'}} ) {
799
		if ( $Data{'device'}{$thisDevice}->{'type'} ne "uap" ) { next; }
800

    
801
		print "multigraph unifi_xfer_per_radio.$thisDevice\n";
802

    
803
		foreach ( @{$Data{'device'}{$thisDevice}{'radio'}} ) {
804
			print $_->{"name"} , "_pkt.value "  , ($_->{"pckt"} // 0) , "\n";
805
			print $_->{"name"} , "_dret.value " , ($_->{"dret"} // 0) , "\n";
806
			print $_->{"name"} , "_err.value "  , ($_->{"err"} // 0) , "\n";
807
		}
808
	}
809
	return 1;
810
}
811

    
812
sub do_values_xfer_by_network {
813
	# Provide transfer for named networks - CONFIG
814
	if ( !$PluginConfig{'enable_xfer_network'} ) { return 0; }
815

    
816
	print "multigraph unifi_xfer_per_network\n";
817

    
818
	foreach my $thisNet ( sort keys %{$Data{'networks'}} ) {
819
		print $thisNet , "_rxbytes.value " , ($Data{'networks'}{$thisNet}->{"rx"} // 0) , "\n";
820
		print $thisNet , "_txbytes.value " , ($Data{'networks'}{$thisNet}->{"tx"} // 0) , "\n";
821
	}
822

    
823
	if ( ! $PluginConfig{'enable_detail_xfer_network'} ) { return 1; }
824

    
825
	foreach my $thisNet ( sort keys %{$Data{'networks'}} ) {
826
		print "multigraph unifi_xfer_per_network.$thisNet\n";
827
		print "rxbyte.value " , ($Data{'networks'}{$thisNet}->{"rx"} // 0) , "\n";
828
		print "txbyte.value " , ($Data{'networks'}{$thisNet}->{"tx"} // 0) , "\n";
829
	}
830
	return 1;
831
}
832

    
833
sub do_values_xfer_by_port {
834
	# Provide transfer for switch ports - VALUES
835
	if ( !$PluginConfig{'enable_xfer_port'} ) { return 0; }
836

    
837
	foreach my $thisDevice ( sort keys %{$Data{'device'}} ) {
838
		if ( $Data{'device'}{$thisDevice}->{'type'} ne "usw" ) { next; }
839
		print "multigraph unifi_xfer_per_port_$thisDevice\n";
840

    
841
		foreach ( @{$Data{'device'}{$thisDevice}{'ports'}} ) {
842
			print $thisDevice , "_" , $_->{"name"} , "_rxbytes.value " , $_->{"rx"} , "\n";
843
			print $thisDevice , "_" , $_->{"name"} , "_txbytes.value " , $_->{"tx"} , "\n";
844
		}
845
	}
846

    
847
	if ( ! $PluginConfig{'enable_detail_xfer_port'} ) { return 1; }
848

    
849
	# Extended graphs
850
	foreach my $thisDevice ( sort keys %{$Data{'device'}} ) {
851
		if ( $Data{'device'}{$thisDevice}->{'type'} ne "usw" ) { next; }
852
		foreach ( @{$Data{'device'}{$thisDevice}{'ports'}} ) {
853
			print 'multigraph unifi_xfer_per_port_' .  $thisDevice . "." . $_->{'name'} . "\n";
854
			print "rxbyte.value " , $_->{"rx"} , "\n";
855
			print "txbyte.value " , $_->{"tx"} , "\n";
856
		}
857
	}
858
	return 1;
859
}
860

    
861
sub do_values_xfer_by_uplink {
862
	# Provide transfer for unifi uplink - CONFIG
863
	if ( !$PluginConfig{'enable_xfer_device'} ) { return 0; }
864

    
865
	print "multigraph unifi_xfer_by_uplink\n";
866
	print "rx_speed.value " . $Data{'uplink'}{"rx_speed"} . "\n";
867
	print "tx_speed.value " . $Data{'uplink'}{"tx_speed"} . "\n";
868
	print "rx_bytes.value " . $Data{'uplink'}{"rx_bytes"} . "\n";
869
	print "tx_bytes.value " . $Data{'uplink'}{"tx_bytes"} . "\n";
870
	return 1;
871
}
872

    
873
sub do_values_xfer_by_device {
874
	# Provide transfer for each unifi device - CONFIG
875
	if ( !$PluginConfig{'enable_xfer_device'} ) { return 0; }
876

    
877
	print "multigraph unifi_xfer_per_device\n";
878
	foreach ( sort keys %{$Data{'device'}} ) {
879
		print $_ . "_rxbytes.value " . $Data{'device'}{$_}->{"rx"} , "\n";
880
		print $_ . "_txbytes.value " . $Data{'device'}{$_}->{"tx"} , "\n";
881
	}
882
	if ( $PluginConfig{'enable_detail_xfer_device'} ) {
883
		foreach ( sort keys %{$Data{'device'}} ) {
884
			print "multigraph unifi_xfer_per_device." , $_ , "\n";
885
			print "rxbyte.value " , $Data{'device'}{$_}->{"rx"} , "\n";
886
			print "txbyte.value " , $Data{'device'}{$_}->{"tx"} , "\n";
887
		}
888
	}
889
	return 1;
890
}
891

    
892
sub do_values_cpu {
893
	# Provide device CPU usage for each unifi device - VALUES
894
	if ( !$PluginConfig{'enable_device_cpu'} ) { return 0; }
895

    
896
	print "multigraph unifi_device_cpu\n";
897
	foreach ( sort keys %{$Data{'device'}} ) {
898
		print $_ , ".value " , ( $Data{'device'}{$_}->{"cpu"} ) , "\n";
899
	}
900
	return 1;
901
}
902

    
903
sub do_values_mem {
904
	# Provide device memory usage for each unifi device - VALUES
905
	if ( !$PluginConfig{'enable_device_mem'} ) { return 0; }
906

    
907
	print "multigraph unifi_device_mem\n";
908
	foreach ( sort keys %{$Data{'device'}} ) {
909
		print $_ , ".value " , ( $Data{'device'}{$_}->{"mem"} ) , "\n";
910
	}
911
	return 1;
912
}
913

    
914
sub do_values_load {
915
	# Provide device load average for each unifi device - VALUES
916
	if ( !$PluginConfig{'enable_device_load'} ) { return 0; }
917

    
918
	print "multigraph unifi_device_load\n";
919
	foreach ( sort keys %{$Data{'device'}} ) {
920
		if ( $Data{'device'}{$_}->{'type'} eq 'ugw' ) { next; }
921
		print $_ , ".value " , ( $Data{'device'}{$_}->{"load"} ) , "\n";
922
	}
923
	return 1;
924
}
925

    
926
sub do_values_uptime {
927
	# Provide device uptime for each unifi device - VALUES
928
	if ( !$PluginConfig{'enable_device_uptime'} ) { return 0; }
929

    
930
	print "multigraph unifi_device_uptime\n";
931
	foreach ( sort keys %{$Data{'device'}} ) {
932
		print $_ , ".value " , ( $Data{'device'}{$_}->{"uptime"} / 86400 ) , "\n";
933
	}
934
	return 1;
935
}
936

    
937

    
938

    
939

    
940

    
941

    
942

    
943
#########################
944
# SUBROUTINES   GENERAL #
945
#########################
946

    
947
sub graph_prologue {
948
	# Generate graph prologues - slightly less copy-pasta, and less chance for things to go wrong
949
	my ( $id, $title, $args, $vlabel, $category, $info ) = (@_);
950

    
951
	print "multigraph $id\n";
952
	print 'graph_title ' , $title , ' : ' , $APIconfig{"name"} , "\n";
953
	print "graph_args $args\n";
954
	print "graph_vlabel $vlabel\n";
955
	if ( $PluginConfig{'force_category'} ) {
956
		print "graph_category ", $PluginConfig{'force_category'}, "\n";
957
	} else {
958
		print "graph_category $category\n";
959
	}
960
	if ( $info ) {
961
		print 'graph_info For the unifi site named "' , $APIconfig{"name"} , "\", $info\n";
962
	}
963
	return 1;
964
}
965

    
966
# Collate all collected data into something we can use.
967
sub make_data {
968
	foreach my $thisDevice ( @{$APIJsonResponse{'device'}->{'data'}} ) {
969
		# Grab everything we care to know about each device.
970
		$Data{'device'}{ make_safe($thisDevice->{'name'}, $thisDevice->{'serial'}) } = {
971
			'label'   => $thisDevice->{'name'},
972
			'users'   => ($thisDevice->{'user-num_sta'} || 0),
973
			'guests'  => ($thisDevice->{'guest-num_sta'} || 0),
974
			'clients' => ($thisDevice->{'user-num_sta'} + $thisDevice->{'guest-num_sta'} || 0),
975
			'tx'      => $thisDevice->{'rx_bytes'},
976
			'rx'      => $thisDevice->{'tx_bytes'},
977
			'name'    => $thisDevice->{'name'},
978
			'uptime'  => $thisDevice->{'uptime'},
979
			'cpu'     => $thisDevice->{'system-stats'}->{'cpu'},
980
			'mem'     => $thisDevice->{'system-stats'}->{'mem'},
981
			'load'    => ( $thisDevice->{'type'} eq 'ugw' ? 'U' : $thisDevice->{'sys_stats'}->{'loadavg_1'} ),
982
			'type'    => $thisDevice->{'type'}
983
		};
984

    
985
		if ( $thisDevice->{'type'} eq 'ugw' ) { # Handle firewall specially, record uplink and networks
986
			foreach my $thisNet ( @{$thisDevice->{'network_table'}} ) {
987
				$Data{'networks'}{ make_safe($thisNet->{'name'}, $thisNet->{'_id'} ) } = {
988
					'label' => $thisNet->{'name'},
989
					'tx'    => $thisNet->{'tx_bytes'},
990
					'rx'    => $thisNet->{'rx_bytes'}
991
				}
992
			}
993

    
994
			$Data{'uplink'}{'devName'}  = $thisDevice->{'name'};
995
			$Data{'uplink'}{'rx_speed'} = $thisDevice->{'speedtest-status'}->{'xput_download'} * 1000000;
996
			$Data{'uplink'}{'tx_speed'} = $thisDevice->{'speedtest-status'}->{'xput_upload'} * 1000000;
997

    
998
			foreach ( @{$thisDevice->{"port_table"}} ) {
999
				if ( $_->{name} eq "wan" ) {
1000
					$Data{'uplink'}{'rx_bytes'} = $_->{'rx_bytes'};
1001
					$Data{'uplink'}{'tx_bytes'} = $_->{'tx_bytes'};
1002
				}
1003
			}
1004
		}
1005

    
1006
		if ( $thisDevice->{'type'} eq 'usw' ) { # Handle swiches specially - record port stats
1007
			my @port_list;
1008

    
1009
			foreach my $port ( @{$thisDevice->{'port_table'}} ) {
1010
				if ( !$PluginConfig{'hide_empty_xfer_port'} || $port->{'up'} ) {
1011
					push @port_list , {
1012
						'name'  => 'port_' . zPad($port->{'port_idx'}),
1013
						'label' => zPad($port->{'port_idx'}) . '-' . $port->{'name'},
1014
						'rx'    => $port->{'rx_bytes'},
1015
						'tx'    => $port->{'tx_bytes'}
1016
					};
1017
				}
1018
			}
1019
			$Data{'device'}{ make_safe($thisDevice->{'name'}, $thisDevice->{'serial'}) }{'ports'} = \@port_list;
1020
		}
1021

    
1022
		if ( $thisDevice->{'type'} eq 'uap' ) { # Handle APS specially - record radio stats
1023
			my @theseRadios;
1024

    
1025
			foreach my $thisRadio ( @{$thisDevice->{'radio_table_stats'}} ) {
1026
				my $name  = make_safe( $thisRadio->{'name'}, "" );
1027
				my $label = ( $thisRadio->{'channel'} < 12 ) ? '2.4Ghz' : '5Ghz';
1028

    
1029
				$_ = $thisDevice->{'stat'}->{'ap'};
1030

    
1031
				push @theseRadios, {
1032
					'name'  => $name,
1033
					'label' => $label . '-' . $thisDevice->{'name'},
1034
					'pckt'  => ($_->{$name . '-rx_packets'} // 0) + ($_->{$name . '-tx_packets'} // 0),
1035
					'dret'  => ($_->{$name . '-rx_dropped'} // 0) + ($_->{$name . '-tx_retries'} // 0) + ($_->{$name . '-tx_dropped'} // 0),
1036
					'err'   => ($_->{$name . '-rx_errors'} // 0) + ($_->{$name . '-tx_errors'} // 0),
1037
					'type'  => $label
1038
				};
1039
			}
1040
			$Data{'device'}{ make_safe($thisDevice->{'name'}, $thisDevice->{'serial'}) }{'radio'} = \@theseRadios;
1041
		}
1042
	} # END PROCESSING OF DEVICE DATA
1043

    
1044

    
1045
	# PROCESS NETWORK TYPE DATA
1046

    
1047
	# -> UNLESS, type graph is disabled.
1048
	#
1049
	# WHY: if the client list is large (huge.  10,000+), this is CPU intensive
1050
	if ( !$PluginConfig{'enable_clients_type'} ) { return 1; }
1051

    
1052
	$Data{'types'} = {
1053
		"wired"  => ["Wired Connection", 1, 0, 0],
1054
		"wifi"   => ["Wireless Connection", 1, 0, 0],
1055
		"tuser"   => ["Total Users", 0, 0, 0],
1056
		"tguest"  => ["Total Guests", 0, 0, 0],
1057
		"authed" => ["Authorized Guests", 0, 0, 0],
1058
		"unauth" => ["Unauthorized Guests", 0, 0, 0],
1059
	};
1060

    
1061
	$Data{'typesOrder'} = ( $PluginConfig{'show_authorized_clients_type'} ) ?
1062
		[ "wired", "wifi", "tuser", "tguest", "authed", "unauth"] :
1063
		[ "wired", "wifi", "tuser", "tguest" ];
1064

    
1065

    
1066
	my @wlans;
1067

    
1068
	foreach my $thisNet ( @{$APIJsonResponse{'wlan'}->{'data'}} ) {
1069
		$Data{'types'}{ make_safe($thisNet->{'name'}, "") } = [ $thisNet->{'name'}, 1, 0, 0 ];
1070
		push @wlans, make_safe($thisNet->{'name'}, "");
1071
	}
1072

    
1073
	foreach ( sort @wlans ) {
1074
		push @{$Data{'typesOrder'}}, $_;
1075
	}
1076

    
1077
	foreach my $client ( @{$APIJsonResponse{'sta'}->{'data'}} ) {
1078
		if ( $client->{"is_wired"} ) {
1079
			if ( $client->{"is_guest"} ) {
1080
				$Data{'types'}->{'wired'}[3]++;
1081
				$Data{'types'}->{'guest'}[3]++;
1082
			} else {
1083
				$Data{'types'}->{'wired'}[2]++;
1084
				$Data{'types'}->{'user'}[2]++;
1085
			}
1086
		} else {
1087
			if ( $client->{"is_guest"} ) {
1088
				$Data{'types'}->{make_safe($client->{"essid"}, "")}[3]++;
1089
				$Data{'types'}->{'wifi'}[3]++;
1090
				$Data{'types'}->{'guest'}[3]++;
1091
				if ( $client->{"authorized"} ) {
1092
					$Data{'types'}->{'authed'}[3]++;
1093
				} else {
1094
					$Data{'types'}->{'unauth'}[3]++;
1095
				}
1096
			} else {
1097
				$Data{'types'}->{make_safe($client->{"essid"}, "")}[2]++;
1098
				$Data{'types'}->{'wifi'}[2]++;
1099
				$Data{'types'}->{'user'}[2]++;
1100
			}
1101
		}
1102
	}
1103

    
1104
	return 1;
1105
}
1106

    
1107

    
1108
sub fetch_data {
1109
	# Set up curl, and login to API
1110
	$curl->setopt($curl->CURLOPT_POST,1);
1111
	$curl->setopt($curl->CURLOPT_COOKIEFILE,"");  # Session only cookie
1112
	$curl->setopt($curl->CURLOPT_SSL_VERIFYPEER, (( $APIconfig{"ssl_verify_peer"} =~ m/no/i ) ? 0 : 1) );
1113
	$curl->setopt($curl->CURLOPT_SSL_VERIFYHOST, (( $APIconfig{"ssl_verify_host"} =~ m/no/i ) ? 0 : 2) );
1114
	$curl->setopt($curl->CURL_SSLVERSION_TLSv1, 1);
1115
	$curl->setopt($curl->CURLOPT_URL, $APIPoint{'login'});
1116
	$curl->setopt($curl->CURLOPT_POSTFIELDS, q[{"username":"] . $APIconfig{"user"} . q[", "password":"] . $APIconfig{"pass"} . q["}] );
1117
	$curl->setopt($curl->CURLOPT_WRITEDATA, \$APIResponse{'login'});
1118
	$retcode = $curl->perform;
1119

    
1120
	if ( $retcode != 0 ) {
1121
		die "FATAL:$me: Unable to connect to API: " . $curl->strerror($retcode) . " " . $curl->errbuf . "\n";
1122
	}
1123

    
1124
	$APIJsonResponse{'login'} = $jsonOBJ->allow_nonref->utf8->relaxed->decode($APIResponse{'login'});
1125

    
1126
	if ( $APIJsonResponse{'login'}->{'meta'}->{'rc'} ne 'ok' ) {
1127
		die "FATAL:$me: Unable to login to API - it said: " , $APIJsonResponse{'login'}->{'meta'}->{'msg'} , "\n";
1128
	}
1129

    
1130
	# Change method to GET
1131
	$curl->setopt($curl->CURLOPT_HTTPGET,1);
1132

    
1133

    
1134
	# Get some API data.
1135

    
1136
	# Device data
1137
	$curl->setopt($curl->CURLOPT_WRITEDATA, \$APIResponse{'device'});
1138
	$curl->setopt($curl->CURLOPT_URL, $APIPoint{'device'});
1139
	$retcode = $curl->perform;
1140

    
1141
	if ( $retcode != 0 ) {
1142
		die "FATAL:$me: Unable to connect to API: " . $curl->strerror($retcode) . " " . $curl->errbuf . "\n";
1143
	}
1144

    
1145
	$APIJsonResponse{'device'} = $jsonOBJ->allow_nonref->utf8->relaxed->decode($APIResponse{'device'});
1146

    
1147
	if ( $APIJsonResponse{'device'}->{'meta'}->{'rc'} ne 'ok' ) {
1148
		die "FATAL:$me: Unable get device data from API - it said: " , $APIJsonResponse{'device'}->{'meta'}->{'msg'} , "\n";
1149
	}
1150

    
1151
	# STA (client) data
1152
	$curl->setopt($curl->CURLOPT_WRITEDATA, \$APIResponse{'sta'});
1153
	$curl->setopt($curl->CURLOPT_URL, $APIPoint{'sta'});
1154
	$retcode = $curl->perform;
1155

    
1156
	if ( $retcode != 0 ) {
1157
		die "FATAL:$me: Unable to connect to API: " . $curl->strerror($retcode) . " " . $curl->errbuf . "\n";
1158
	}
1159

    
1160
	$APIJsonResponse{'sta'} = $jsonOBJ->allow_nonref->utf8->relaxed->decode($APIResponse{'sta'});
1161

    
1162
	if ( $APIJsonResponse{'sta'}->{'meta'}->{'rc'} ne 'ok' ) {
1163
		die "FATAL:$me: Unable get sta data from API - it said: " , $APIJsonResponse{'sta'}->{'meta'}->{'msg'} , "\n";
1164
	}
1165

    
1166
	# WLAN data
1167
	$curl->setopt($curl->CURLOPT_WRITEDATA, \$APIResponse{'wlan'});
1168
	$curl->setopt($curl->CURLOPT_URL, $APIPoint{'wlan'});
1169
	$retcode = $curl->perform;
1170

    
1171
	if ( $retcode != 0 ) {
1172
		die "FATAL:$me: Unable to connect to API: " . $curl->strerror($retcode) . " " . $curl->errbuf . "\n";
1173
	}
1174

    
1175
	$APIJsonResponse{'wlan'} = $jsonOBJ->allow_nonref->utf8->relaxed->decode($APIResponse{'wlan'});
1176

    
1177
	if ( $APIJsonResponse{'wlan'}->{'meta'}->{'rc'} ne 'ok' ) {
1178
		die "FATAL:$me: Unable get wlan data from API - it said: " , $APIJsonResponse{'wlan'}->{'meta'}->{'msg'} , "\n";
1179
	}
1180
}
1181

    
1182
# Make field names safe, and lowercase.
1183
# 
1184
# Typically, $extraName should be the MAC address of the unique ID identifier as the unifi
1185
# controller software does not enforce that device names or network names are unique.
1186
sub make_safe {
1187
	my ( $name, $extraName ) = ( @_ );
1188
	if ( $extraName ne "" ) {
1189
		return clean_fieldname(lc($name) . "_" . $extraName);
1190
	} else {
1191
		return lc(clean_fieldname($name));
1192
	}
1193
}
1194

    
1195
# Get a default from an environmental variable - return text
1196
#
1197
# env_default(<variable name>, <default>)
1198
sub env_default_text {
1199
	my ( $env_var, $default ) = (@_);
1200
	return ( ( defined $ENV{$env_var} ) ? $ENV{$env_var} : $default ),
1201
}
1202

    
1203
# Get a default from an environmental variable - boolean true
1204
#
1205
# env_default_bool_true (<variable name>, <default>)
1206
sub env_default_bool_true {
1207
	my $env_var = $_[0];
1208
	return ( ( defined $ENV{$env_var} && $ENV{$env_var} =~ m/no/i ) ? 0 : 1 );
1209
}
1210

    
1211
# Quick 2 digit zero pad
1212
sub zPad { return sprintf("%02d", $_[0]); }