43 |
44 |
--------------------------------------------------------------------------------
/DeviceWheel/DeviceWheel_arduino/data/script.js:
--------------------------------------------------------------------------------
1 | var maxWifiNetworks = 5;
2 | var peers = [];
3 |
4 | function init() {
5 | $('#config').hide();
6 | $('#nextstep').hide();
7 | $('#alert-text').hide();
8 |
9 | $('#save-button').click(onSaveButtonClicked);
10 | $('#password').keypress(onKeyPressed);
11 |
12 | $('#networks-list-select').attr('disabled', true);
13 | $('#password').attr('disabled', true);
14 |
15 | $.getJSON('/yoyo/credentials', function (json) {
16 | $('#config').show();
17 | configure(json);
18 | }).fail(function() {
19 | $('#alert-text').show();
20 | $('#alert-text').addClass('alert-danger');
21 | $('#alert-text').text('Error');
22 | });
23 | }
24 |
25 | function configure(json) {
26 | $('#config').show();
27 |
28 | console.log(json);
29 |
30 | populateNetworksList();
31 | populatePeersList();
32 | }
33 |
34 | function onKeyPressed(event) {
35 | if (event.keyCode == 13) {
36 | onSaveButtonClicked(event);
37 | }
38 | }
39 |
40 | function populateNetworksList(selectedNetwork) {
41 | let networks = $('#networks-list-select');
42 |
43 | $.getJSON('/yoyo/networks', function (json) {
44 | if(json.length > 0) {
45 | networks.empty();
46 | //Order the networks by signal strength and limit to top n
47 | json = json.sort((a, b) => parseInt(b.RSSI) - parseInt(a.RSSI));
48 | var ssidList = json.slice(0, maxWifiNetworks).map(i => {
49 | return i.SSID;
50 | });
51 |
52 | //The selected network will always remain:
53 | if(selectedNetwork && !ssidList.includes(selectedNetwork)) ssidList.push(selectedNetwork);
54 |
55 | $.each(ssidList, function (key, entry) {
56 | let network = $('');
57 |
58 | network.attr('value', entry).text(entry);
59 | if(entry == selectedNetwork) network.attr('selected', true);
60 |
61 | networks.append(network);
62 | });
63 |
64 | $('#networks-list-select').attr('disabled', false);
65 | $('#password').attr('disabled', false);
66 | }
67 |
68 | if($('#networks-list-select option').length == 0) {
69 | networks.append('');
70 | }
71 |
72 | setTimeout(function() {
73 | populateNetworksList($('#networks-list-select').children("option:selected").val());
74 | }, 10000);
75 | });
76 | }
77 |
78 | function populatePeersList() {
79 | $.getJSON('/yoyo/peers', function (json) {
80 | if(json.length > 0) {
81 | var newPeers = json.map(i => { return i.IP;});
82 | newPeers.forEach(ip => { if(!peers.includes(ip)) addPeer(ip); });
83 | peers.forEach(ip => { if(!newPeers.includes(ip)) removePeer(ip); });
84 |
85 | peers = newPeers;
86 | }
87 | });
88 |
89 | setTimeout(function() {
90 | populatePeersList();
91 | }, 15000);
92 | }
93 |
94 | function addPeer(ip) {
95 | console.log("addPeer > " + ip);
96 |
97 | let peersListDiv = $('#peers-list');
98 |
99 | let span = $('');
100 | span.attr('id', ip);
101 |
102 | let image = $('');
103 | image.attr('class', 'peer-image');
104 | image.attr('src', "http://" + ip + "/icon.svg");
105 |
106 | peersListDiv.append(span);
107 | span.append(image);
108 | }
109 |
110 | function removePeer(ip) {
111 | console.log("removePeer > " + ip);
112 |
113 | $('span[id="' + ip + '"]').remove();
114 | }
115 |
116 | function onSaveButtonClicked(event) {
117 | event.preventDefault();
118 |
119 | var data = {
120 | ssid: $('#networks-list-select').children("option:selected").val(),
121 | password: $('#password').val()
122 | };
123 |
124 | var request = {
125 | type: "POST",
126 | url: "/yoyo/credentials",
127 | data: JSON.stringify(data),
128 | dataType: 'json',
129 | contentType: 'application/json; charset=utf-8',
130 | cache: false,
131 | timeout: 15000,
132 | async: false,
133 | success: function(response, textStatus, jqXHR) {
134 | console.log(response);
135 | $('#config').hide();
136 | $('#alert-text').show();
137 | $('#alert-text').removeClass('alert-danger');
138 | $('#alert-text').addClass('alert-success');
139 | $('#alert-text').text('Saved');
140 | $('#nextstep').show();
141 | },
142 | error: function (jqXHR, textStatus, errorThrown) {
143 | console.log(jqXHR);
144 | console.log(textStatus);
145 | console.log(errorThrown);
146 | $('#alert-text').show();
147 | $('#alert-text').addClass('alert-danger');
148 | $('#alert-text').text('Couldn\'t Save');
149 | }
150 | }
151 |
152 | //json validation fails on Safari - but if defaults to text then fails on Windows/Android
153 | if (navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1 && navigator.userAgent.indexOf('Chromium') == -1) {
154 | request.dataType = 'text';
155 | }
156 |
157 | $.ajax(request);
158 | }
--------------------------------------------------------------------------------
/DeviceWheel/DeviceWheel_arduino/data/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | padding-top: 50px;
3 | background-color: #f5f5f5;
4 | }
5 |
6 | canvas {
7 | display: block;
8 | }
9 |
10 | .yo-yo-form {
11 | width: 100%;
12 | max-width: 330px;
13 | padding: 15px;
14 | margin: 0 auto;
15 | }
16 | .yo-yo-form .checkbox {
17 | font-weight: 400;
18 | }
19 | .yo-yo-form .form-control {
20 | position: relative;
21 | box-sizing: border-box;
22 | padding: 10px;
23 | font-size: 16px;
24 | }
25 | .yo-yo-form .form-control:focus {
26 | z-index: 2;
27 | }
28 | .yo-yo-form input[type="email"], .yo-yo-form select {
29 | margin-bottom: -1px;
30 | border-bottom-right-radius: 0;
31 | border-bottom-left-radius: 0;
32 | }
33 | .yo-yo-form input[type="password"] {
34 | margin-bottom: 10px;
35 | border-top-left-radius: 0;
36 | border-top-right-radius: 0;
37 | }
38 |
39 | .dropdown-menu.pull-left {
40 | left: 0;
41 | }
42 |
43 | .peers {
44 | height: 130px;
45 | }
46 |
47 | .peer-image {
48 | height: 100%;
49 | padding-left: 10px;
50 | padding-right: 10px;
51 | }
--------------------------------------------------------------------------------
/DeviceWheel/README.md:
--------------------------------------------------------------------------------
1 | # Device Wheel
2 | Watch an individual device's use of the network with this Device Wheel. Once the WiFi is configured by joining the "Home Network Study" network and setting the credentials via the captive portal, bringing an IoT device in proximity of the Device Wheel will cause it to pair and the wheel will spin whenever the is network activity - clockwise for downloads, anti-clockwise for uploads.
3 |
4 | ## Hardware
5 | * Adafruit HUZZAH32 – ESP32 Feather Board - https://www.adafruit.com/product/3405
6 | * 3800 RPM 1.5mm Diameter Shaft 2V DC Motor for Walkman
7 | * 1.5A Mini Speed Control Dual Channel Motor Driver MX1508
8 | * SPDT Mini Power Switch - https://shop.pimoroni.com/products/spdt-mini-power-switch
9 | * 3mm blue LED
10 | * 1K Ohm resistor
11 | * LiPo Battery Pack 3.7V 500mAh - https://shop.pimoroni.com/products/lipo-battery-pack?variant=20429082055
12 | * Micro USB male to Micro USB female charge + data adapter cable
13 |
14 |
15 |
16 | The circuit shows an Adafruit HUZZAH32, but the code will compile for any ESP8266 or ESP32 (pin assignments will need to change of course).
17 |
18 | ## Software
19 | ### Arduino
20 | The Arduino core for the ESP8266 or ESP32 must be installed for the Arduino IDE:
21 | * ESP8266 - https://github.com/esp8266/Arduino#installing-with-boards-manager
22 | * ESP32 - https://github.com/espressif/arduino-esp32/blob/master/docs/arduino-ide/boards_manager.md
23 |
24 | And the Sketch Data Folder Uploader Tool:
25 | * ESP8266 - https://randomnerdtutorials.com/install-esp8266-filesystem-uploader-arduino-ide/
26 | * ESP32 - https://randomnerdtutorials.com/install-esp32-filesystem-uploader-arduino-ide/
27 |
28 | And following Arduino libraries are required:
29 | * Approximate - https://github.com/davidchatting/Approximate/
30 | * YoYoWiFiManager - https://github.com/interactionresearchstudio/YoYoWiFiManager
31 | * ListLib - https://github.com/luisllamasbinaburo/Arduino-List
32 |
33 | From the *Tools* menu then select either `Generic ESP822 Module` or `ESP32 Dev Module` and then for the ESP8266 select `4MB (FS:3MB OTA:~512KB)` for *Flash Size* and for the ESP32 select a *Partition Scheme* of `Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS)` - assuming a *Flash Size* of 4MB. Then upload the associated `data` folder using the uploader tool - also found under the *Tools* menu. The data folder contains the HTML, JavaScript and image files for the captive portal that configures the WiFi. If you don't upload the data folder the portal will say, *Yo Yo Machines default HTML*.
34 |
35 | ## WiFi Set-up
36 | The WiFi is configured by joining the *Home Network Study* network with the password *blinkblink* and entering the details of your network. If multiple meters are started once they will automaically discover each other and the set-up will shared between them. This is enabled by the [YoYoWiFiManager](https://github.com/interactionresearchstudio/YoYoWiFiManager).
37 |
38 | ## Author
39 | The Three WiFi Meters were created by David Chatting ([@davidchatting](https://twitter.com/davidchatting)) as part of the [A Network of One's Own](http://davidchatting.com/nooo/) project. This code is licensed under the [MIT License](LICENSE.txt).
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 David Chatting - github.com/davidchatting/ThreeWiFiMeters
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Three WiFi Meters
2 |
3 |
4 |
5 | The Three WiFi Meters are three ways of experiencing WiFi networks, each attempts to disclose properties of this near ubiquitous technology. They are built for the [ESP8266](https://en.wikipedia.org/wiki/ESP8266) or [ESP32](https://en.wikipedia.org/wiki/ESP32) using the [Approximate](https://github.com/davidchatting/Approximate) and [YoYoWiFiManager](https://github.com/interactionresearchstudio/YoYoWiFiManager) Arduino libraries.
6 |
7 | ## [ Signal Strength](SignalStrength)
8 | Measure the signal strength of your router around the house with the [Signal Strength Meter](SignalStrength).
9 |
10 | ## [ Device Wheel](DeviceWheel)
11 | Watch an individual device's use of the network with the [Device Wheel](DeviceWheel).
12 |
13 | ## [ Traffic Monitor](TrafficMonitor)
14 | See the traffic on your home WiFi network with the [Traffic Monitor](TrafficMonitor).
15 |
16 | ## Limitations
17 | The meters work with 2.4GHz WiFi networks, but not 5GHz networks - as neither ESP8266 or ESP32 support this technology.
18 |
19 | ## A Little History
20 | The Three WiFi Meters are inspired by multiple sources, not least Natalie Jeremijenko's Live Wire (also known as Dangling String) [(Weiser and Brown, 1995)](https://web.archive.org/web/19970624041814/http://www.powergrid.com/1.01/calmtech.html) and the Tangible Media Group's Pinwheels [(Dahley, Wisneski and Ishii, 1998)](https://tangible.media.mit.edu/project/pinwheels/).
21 |
22 | ## Author
23 | The Three WiFi Meters were created by David Chatting ([@davidchatting](https://twitter.com/davidchatting)) as part of the [A Network of One's Own](http://davidchatting.com/nooo/) project. This code is licensed under the [MIT License](LICENSE.txt).
--------------------------------------------------------------------------------
/SignalStrength/README.md:
--------------------------------------------------------------------------------
1 | # Signal Strength
2 | Measure the signal strength of your router around the house with this Signal Strength Meter. Once the WiFi is configured by joining the "Home Network Study" network and setting the credentials via the captive portal, the analogue meter will reflect the signal strength (RSSI) of the router at that location.
3 |
4 | ## Hardware
5 | * Adafruit HUZZAH32 – ESP32 Feather Board - https://www.adafruit.com/product/3405
6 | * Eisco 0-30V Single Range Moving Coil Voltmeter - https://www.rapidonline.com/eisco-0-30v-single-range-moving-coil-voltmeter-52-3502 (series resistor needs to be removed)
7 | * SPDT Mini Power Switch - https://shop.pimoroni.com/products/spdt-mini-power-switch
8 | * 3mm blue LED
9 | * 1K Ohm resistor
10 | * LiPo Battery Pack 3.7V 500mAh - https://shop.pimoroni.com/products/lipo-battery-pack?variant=20429082055
11 | * Micro USB male to Micro USB female charge + data adapter cable
12 |
13 |
14 |
15 | The circuit shows an Adafruit HUZZAH32, but the code will compile for any ESP8266 or ESP32 (pin assignments will need to change of course).
16 |
17 | ## Software
18 | ### Arduino
19 | The Arduino core for the ESP8266 or ESP32 must be installed for the Arduino IDE:
20 | * ESP8266 - https://github.com/esp8266/Arduino#installing-with-boards-manager
21 | * ESP32 - https://github.com/espressif/arduino-esp32/blob/master/docs/arduino-ide/boards_manager.md
22 |
23 | And the Sketch Data Folder Uploader Tool:
24 | * ESP8266 - https://randomnerdtutorials.com/install-esp8266-filesystem-uploader-arduino-ide/
25 | * ESP32 - https://randomnerdtutorials.com/install-esp32-filesystem-uploader-arduino-ide/
26 |
27 | And following Arduino libraries are required:
28 | * Approximate - https://github.com/davidchatting/Approximate/
29 | * YoYoWiFiManager - https://github.com/interactionresearchstudio/YoYoWiFiManager
30 | * ListLib - https://github.com/luisllamasbinaburo/Arduino-List
31 |
32 | From the *Tools* menu then select either `Generic ESP822 Module` or `ESP32 Dev Module` and then for the ESP8266 select `4MB (FS:3MB OTA:~512KB)` for *Flash Size* and for the ESP32 select a *Partition Scheme* of `Default 4MB with spiffs (1.2MB APP/1.5MB SPIFFS)` - assuming a *Flash Size* of 4MB. Then upload the associated `data` folder using the uploader tool - also found under the *Tools* menu. The data folder contains the HTML, JavaScript and image files for the captive portal that configures the WiFi. If you don't upload the data folder the portal will say, *Yo Yo Machines default HTML*.
33 |
34 | ## WiFi Set-up
35 | The WiFi is configured by joining the *Home Network Study* network with the password *blinkblink* and entering the details of your network. If multiple meters are started once they will automaically discover each other and the set-up will shared between them. This is enabled by the [YoYoWiFiManager](https://github.com/interactionresearchstudio/YoYoWiFiManager).
36 |
37 | ## Author
38 | The Three WiFi Meters were created by David Chatting ([@davidchatting](https://twitter.com/davidchatting)) as part of the [A Network of One's Own](http://davidchatting.com/nooo/) project. This code is licensed under the [MIT License](LICENSE.txt).
--------------------------------------------------------------------------------
/SignalStrength/SignalStrength-circuit.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidchatting/ThreeWiFiMeters/2214543ea08e8626c542736f0be82f6d92079a61/SignalStrength/SignalStrength-circuit.fzz
--------------------------------------------------------------------------------
/SignalStrength/SignalStrength-circuit.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidchatting/ThreeWiFiMeters/2214543ea08e8626c542736f0be82f6d92079a61/SignalStrength/SignalStrength-circuit.png
--------------------------------------------------------------------------------
/SignalStrength/SignalStrength-icon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/SignalStrength/SignalStrength.fzz:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/davidchatting/ThreeWiFiMeters/2214543ea08e8626c542736f0be82f6d92079a61/SignalStrength/SignalStrength.fzz
--------------------------------------------------------------------------------
/SignalStrength/SignalStrength_arduino/SignalStrength_arduino.ino:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | Three WiFi Meters - Signal Strength
4 | -
5 | David Chatting - github.com/davidchatting/ThreeWiFiMeters
6 | MIT License - Copyright (c) March 2021
7 | Documented here > https://github.com/davidchatting/ThreeWiFiMeters#-signal-strength
8 | */
9 |
10 | #include
11 | #include
12 |
13 | YoYoWiFiManager wifiManager;
14 | YoYoSettings *settings;
15 |
16 | const int ledPin = 12;
17 | const int gaguePin = 5;
18 | const int maxGagueValue = 255;
19 |
20 | #if defined(ESP32)
21 | const int gagueChannel = 0;
22 | #endif
23 |
24 | const int minRSSI = -80;
25 | const int maxRSSI = -20;
26 |
27 | void setup() {
28 | Serial.begin(115200);
29 |
30 | pinMode(ledPin, OUTPUT);
31 | digitalWrite(ledPin, LOW);
32 |
33 | pinMode(gaguePin, OUTPUT);
34 | #if defined(ESP32)
35 | ledcSetup(gagueChannel, 1000, 8);
36 | ledcAttachPin(gaguePin, gagueChannel);
37 | #endif
38 |
39 | setGague(255);
40 | delay(150);
41 | setGague(0);
42 |
43 | settings = new YoYoSettings(512); //Settings must be created here in Setup() as contains call to EEPROM.begin() which will otherwise fail
44 | wifiManager.init(settings, onceConnected, NULL, NULL, false, 80, -1);
45 |
46 | //Attempt to connect to a WiFi network previously saved in the settings,
47 | //if one can not be found start a captive portal called "YoYoMachines",
48 | //with a password of "blinkblink" to configure a new one:
49 | wifiManager.begin("Home Network Study", "blinkblink");
50 | }
51 |
52 | void onceConnected() {
53 | }
54 |
55 | void loop() {
56 | uint8_t wifiStatus = wifiManager.loop();
57 |
58 | if(wifiStatus == YY_CONNECTED) {
59 | digitalWrite(ledPin, HIGH);
60 | displayRSSI();
61 | }
62 | else {
63 | switch(wifiManager.currentMode) {
64 | case YoYoWiFiManager::YY_MODE_PEER_CLIENT:
65 | digitalWrite(ledPin, blink(1000));
66 | break;
67 | default: //YY_MODE_PEER_SERVER
68 | digitalWrite(ledPin, blink(500));
69 | break;
70 | }
71 | setGague(0);
72 | }
73 | }
74 |
75 | bool blink(int periodMs) {
76 | return(((millis() / periodMs) % 2) == 0);
77 | }
78 |
79 | void displayRSSI() {
80 | int32_t rssi = getRSSI(WiFi.SSID());
81 | if(rssi == 0){
82 | setGague(0);
83 | }
84 | else{
85 | int valueToDisplay = map(rssi, maxRSSI, minRSSI, 255, 0);
86 | valueToDisplay = min(max(valueToDisplay, 0), 255);
87 | setGague(valueToDisplay);
88 | }
89 | }
90 |
91 | void setGague(int value) {
92 | value = map(value, 0, 255, 0, maxGagueValue);
93 |
94 | #if defined(ESP32)
95 | ledcWrite(gagueChannel, value);
96 | #else
97 | analogWrite(gaguePin, value);
98 | #endif
99 | }
100 |
101 | // Return RSSI or 0 if target SSID not found
102 | int32_t getRSSI(String target_ssid) {
103 | int32_t result = 0;
104 |
105 | byte available_networks = WiFi.scanNetworks();
106 |
107 | for (int network = 0; network < available_networks && result == 0; network++) {
108 | if (WiFi.SSID(network).equals(target_ssid)) {
109 | result = WiFi.RSSI(network);
110 | }
111 | }
112 | return(result);
113 | }
114 |
--------------------------------------------------------------------------------
/SignalStrength/SignalStrength_arduino/data/bootstrap-4.4.1.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * Bootstrap v4.4.1 (https://getbootstrap.com/)
3 | * Copyright 2011-2019 The Bootstrap Authors (https://github.com/twbs/bootstrap/graphs/contributors)
4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
5 | */
6 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports,require("jquery"),require("popper.js")):"function"==typeof define&&define.amd?define(["exports","jquery","popper.js"],e):e((t=t||self).bootstrap={},t.jQuery,t.Popper)}(this,function(t,g,u){"use strict";function i(t,e){for(var n=0;nthis._items.length-1||t<0))if(this._isSliding)g(this._element).one(Y.SLID,function(){return e.to(t)});else{if(n===t)return this.pause(),void this.cycle();var i=ndocument.documentElement.clientHeight;!this._isBodyOverflowing&&t&&(this._element.style.paddingLeft=this._scrollbarWidth+"px"),this._isBodyOverflowing&&!t&&(this._element.style.paddingRight=this._scrollbarWidth+"px")},t._resetAdjustments=function(){this._element.style.paddingLeft="",this._element.style.paddingRight=""},t._checkScrollbar=function(){var t=document.body.getBoundingClientRect();this._isBodyOverflowing=t.left+t.right',trigger:"hover focus",title:"",delay:0,html:!1,selector:!1,placement:"top",offset:0,container:!1,fallbackPlacement:"flip",boundary:"scrollParent",sanitize:!0,sanitizeFn:null,whiteList:Se,popperConfig:null},Fe="show",Ue="out",We={HIDE:"hide"+Oe,HIDDEN:"hidden"+Oe,SHOW:"show"+Oe,SHOWN:"shown"+Oe,INSERTED:"inserted"+Oe,CLICK:"click"+Oe,FOCUSIN:"focusin"+Oe,FOCUSOUT:"focusout"+Oe,MOUSEENTER:"mouseenter"+Oe,MOUSELEAVE:"mouseleave"+Oe},qe="fade",Me="show",Ke=".tooltip-inner",Qe=".arrow",Be="hover",Ve="focus",Ye="click",ze="manual",Xe=function(){function i(t,e){if("undefined"==typeof u)throw new TypeError("Bootstrap's tooltips require Popper.js (https://popper.js.org/)");this._isEnabled=!0,this._timeout=0,this._hoverState="",this._activeTrigger={},this._popper=null,this.element=t,this.config=this._getConfig(e),this.tip=null,this._setListeners()}var t=i.prototype;return t.enable=function(){this._isEnabled=!0},t.disable=function(){this._isEnabled=!1},t.toggleEnabled=function(){this._isEnabled=!this._isEnabled},t.toggle=function(t){if(this._isEnabled)if(t){var e=this.constructor.DATA_KEY,n=g(t.currentTarget).data(e);n||(n=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(e,n)),n._activeTrigger.click=!n._activeTrigger.click,n._isWithActiveTrigger()?n._enter(null,n):n._leave(null,n)}else{if(g(this.getTipElement()).hasClass(Me))return void this._leave(null,this);this._enter(null,this)}},t.dispose=function(){clearTimeout(this._timeout),g.removeData(this.element,this.constructor.DATA_KEY),g(this.element).off(this.constructor.EVENT_KEY),g(this.element).closest(".modal").off("hide.bs.modal",this._hideModalHandler),this.tip&&g(this.tip).remove(),this._isEnabled=null,this._timeout=null,this._hoverState=null,this._activeTrigger=null,this._popper&&this._popper.destroy(),this._popper=null,this.element=null,this.config=null,this.tip=null},t.show=function(){var e=this;if("none"===g(this.element).css("display"))throw new Error("Please use show on visible elements");var t=g.Event(this.constructor.Event.SHOW);if(this.isWithContent()&&this._isEnabled){g(this.element).trigger(t);var n=_.findShadowRoot(this.element),i=g.contains(null!==n?n:this.element.ownerDocument.documentElement,this.element);if(t.isDefaultPrevented()||!i)return;var o=this.getTipElement(),r=_.getUID(this.constructor.NAME);o.setAttribute("id",r),this.element.setAttribute("aria-describedby",r),this.setContent(),this.config.animation&&g(o).addClass(qe);var s="function"==typeof this.config.placement?this.config.placement.call(this,o,this.element):this.config.placement,a=this._getAttachment(s);this.addAttachmentClass(a);var l=this._getContainer();g(o).data(this.constructor.DATA_KEY,this),g.contains(this.element.ownerDocument.documentElement,this.tip)||g(o).appendTo(l),g(this.element).trigger(this.constructor.Event.INSERTED),this._popper=new u(this.element,o,this._getPopperConfig(a)),g(o).addClass(Me),"ontouchstart"in document.documentElement&&g(document.body).children().on("mouseover",null,g.noop);var c=function(){e.config.animation&&e._fixTransition();var t=e._hoverState;e._hoverState=null,g(e.element).trigger(e.constructor.Event.SHOWN),t===Ue&&e._leave(null,e)};if(g(this.tip).hasClass(qe)){var h=_.getTransitionDurationFromElement(this.tip);g(this.tip).one(_.TRANSITION_END,c).emulateTransitionEnd(h)}else c()}},t.hide=function(t){function e(){n._hoverState!==Fe&&i.parentNode&&i.parentNode.removeChild(i),n._cleanTipClass(),n.element.removeAttribute("aria-describedby"),g(n.element).trigger(n.constructor.Event.HIDDEN),null!==n._popper&&n._popper.destroy(),t&&t()}var n=this,i=this.getTipElement(),o=g.Event(this.constructor.Event.HIDE);if(g(this.element).trigger(o),!o.isDefaultPrevented()){if(g(i).removeClass(Me),"ontouchstart"in document.documentElement&&g(document.body).children().off("mouseover",null,g.noop),this._activeTrigger[Ye]=!1,this._activeTrigger[Ve]=!1,this._activeTrigger[Be]=!1,g(this.tip).hasClass(qe)){var r=_.getTransitionDurationFromElement(i);g(i).one(_.TRANSITION_END,e).emulateTransitionEnd(r)}else e();this._hoverState=""}},t.update=function(){null!==this._popper&&this._popper.scheduleUpdate()},t.isWithContent=function(){return Boolean(this.getTitle())},t.addAttachmentClass=function(t){g(this.getTipElement()).addClass(Pe+"-"+t)},t.getTipElement=function(){return this.tip=this.tip||g(this.config.template)[0],this.tip},t.setContent=function(){var t=this.getTipElement();this.setElementContent(g(t.querySelectorAll(Ke)),this.getTitle()),g(t).removeClass(qe+" "+Me)},t.setElementContent=function(t,e){"object"!=typeof e||!e.nodeType&&!e.jquery?this.config.html?(this.config.sanitize&&(e=we(e,this.config.whiteList,this.config.sanitizeFn)),t.html(e)):t.text(e):this.config.html?g(e).parent().is(t)||t.empty().append(e):t.text(g(e).text())},t.getTitle=function(){var t=this.element.getAttribute("data-original-title");return t=t||("function"==typeof this.config.title?this.config.title.call(this.element):this.config.title)},t._getPopperConfig=function(t){var e=this;return l({},{placement:t,modifiers:{offset:this._getOffset(),flip:{behavior:this.config.fallbackPlacement},arrow:{element:Qe},preventOverflow:{boundariesElement:this.config.boundary}},onCreate:function(t){t.originalPlacement!==t.placement&&e._handlePopperPlacementChange(t)},onUpdate:function(t){return e._handlePopperPlacementChange(t)}},{},this.config.popperConfig)},t._getOffset=function(){var e=this,t={};return"function"==typeof this.config.offset?t.fn=function(t){return t.offsets=l({},t.offsets,{},e.config.offset(t.offsets,e.element)||{}),t}:t.offset=this.config.offset,t},t._getContainer=function(){return!1===this.config.container?document.body:_.isElement(this.config.container)?g(this.config.container):g(document).find(this.config.container)},t._getAttachment=function(t){return Re[t.toUpperCase()]},t._setListeners=function(){var i=this;this.config.trigger.split(" ").forEach(function(t){if("click"===t)g(i.element).on(i.constructor.Event.CLICK,i.config.selector,function(t){return i.toggle(t)});else if(t!==ze){var e=t===Be?i.constructor.Event.MOUSEENTER:i.constructor.Event.FOCUSIN,n=t===Be?i.constructor.Event.MOUSELEAVE:i.constructor.Event.FOCUSOUT;g(i.element).on(e,i.config.selector,function(t){return i._enter(t)}).on(n,i.config.selector,function(t){return i._leave(t)})}}),this._hideModalHandler=function(){i.element&&i.hide()},g(this.element).closest(".modal").on("hide.bs.modal",this._hideModalHandler),this.config.selector?this.config=l({},this.config,{trigger:"manual",selector:""}):this._fixTitle()},t._fixTitle=function(){var t=typeof this.element.getAttribute("data-original-title");!this.element.getAttribute("title")&&"string"==t||(this.element.setAttribute("data-original-title",this.element.getAttribute("title")||""),this.element.setAttribute("title",""))},t._enter=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusin"===t.type?Ve:Be]=!0),g(e.getTipElement()).hasClass(Me)||e._hoverState===Fe?e._hoverState=Fe:(clearTimeout(e._timeout),e._hoverState=Fe,e.config.delay&&e.config.delay.show?e._timeout=setTimeout(function(){e._hoverState===Fe&&e.show()},e.config.delay.show):e.show())},t._leave=function(t,e){var n=this.constructor.DATA_KEY;(e=e||g(t.currentTarget).data(n))||(e=new this.constructor(t.currentTarget,this._getDelegateConfig()),g(t.currentTarget).data(n,e)),t&&(e._activeTrigger["focusout"===t.type?Ve:Be]=!1),e._isWithActiveTrigger()||(clearTimeout(e._timeout),e._hoverState=Ue,e.config.delay&&e.config.delay.hide?e._timeout=setTimeout(function(){e._hoverState===Ue&&e.hide()},e.config.delay.hide):e.hide())},t._isWithActiveTrigger=function(){for(var t in this._activeTrigger)if(this._activeTrigger[t])return!0;return!1},t._getConfig=function(t){var e=g(this.element).data();return Object.keys(e).forEach(function(t){-1!==je.indexOf(t)&&delete e[t]}),"number"==typeof(t=l({},this.constructor.Default,{},e,{},"object"==typeof t&&t?t:{})).delay&&(t.delay={show:t.delay,hide:t.delay}),"number"==typeof t.title&&(t.title=t.title.toString()),"number"==typeof t.content&&(t.content=t.content.toString()),_.typeCheckConfig(Ae,t,this.constructor.DefaultType),t.sanitize&&(t.template=we(t.template,t.whiteList,t.sanitizeFn)),t},t._getDelegateConfig=function(){var t={};if(this.config)for(var e in this.config)this.constructor.Default[e]!==this.config[e]&&(t[e]=this.config[e]);return t},t._cleanTipClass=function(){var t=g(this.getTipElement()),e=t.attr("class").match(Le);null!==e&&e.length&&t.removeClass(e.join(""))},t._handlePopperPlacementChange=function(t){var e=t.instance;this.tip=e.popper,this._cleanTipClass(),this.addAttachmentClass(this._getAttachment(t.placement))},t._fixTransition=function(){var t=this.getTipElement(),e=this.config.animation;null===t.getAttribute("x-placement")&&(g(t).removeClass(qe),this.config.animation=!1,this.hide(),this.show(),this.config.animation=e)},i._jQueryInterface=function(n){return this.each(function(){var t=g(this).data(Ne),e="object"==typeof n&&n;if((t||!/dispose|hide/.test(n))&&(t||(t=new i(this,e),g(this).data(Ne,t)),"string"==typeof n)){if("undefined"==typeof t[n])throw new TypeError('No method named "'+n+'"');t[n]()}})},s(i,null,[{key:"VERSION",get:function(){return"4.4.1"}},{key:"Default",get:function(){return xe}},{key:"NAME",get:function(){return Ae}},{key:"DATA_KEY",get:function(){return Ne}},{key:"Event",get:function(){return We}},{key:"EVENT_KEY",get:function(){return Oe}},{key:"DefaultType",get:function(){return He}}]),i}();g.fn[Ae]=Xe._jQueryInterface,g.fn[Ae].Constructor=Xe,g.fn[Ae].noConflict=function(){return g.fn[Ae]=ke,Xe._jQueryInterface};var $e="popover",Ge="bs.popover",Je="."+Ge,Ze=g.fn[$e],tn="bs-popover",en=new RegExp("(^|\\s)"+tn+"\\S+","g"),nn=l({},Xe.Default,{placement:"right",trigger:"click",content:"",template:'