├── LICENSE ├── README.md ├── gui ├── _config.yml ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon-precomposed.png ├── apple-touch-icon.png ├── browserconfig.xml ├── color.txt ├── config.php ├── css │ ├── animate.min.css │ ├── bootstrap-toggle.min.css │ ├── bootstrap.min.css │ ├── style.css │ └── wheelcolorpicker.modified.css ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── functions.php ├── index.php ├── js │ ├── bootbox.min.js │ ├── bootstrap-notify.min.js │ ├── bootstrap-toggle.min.js │ ├── bootstrap.min.js │ ├── functions.js │ ├── html5shiv.min.js │ ├── jquery-1.12.4.min.js │ ├── jquery.wheelcolorpicker.modified.js │ └── respond.min.js ├── manifest.json ├── mstile-150x150.png ├── python │ ├── colorpicker.py │ ├── config.py │ ├── dotstar.c │ ├── dotstar.so │ ├── functions.py │ ├── io.py │ ├── motor.py │ ├── motor_down.py │ ├── motor_up.py │ ├── preset.py │ ├── reboot.py │ ├── request_reboot.py │ └── strip.py ├── safari-pinned-tab.svg └── submit.php ├── img ├── Installation-example.jpg ├── example-wiring-diagram-v2.png ├── iphone6-mockup.jpg ├── pcb-animated.gif ├── pcb-black-transfer.png ├── rainbow-rgb-led-soffit-lighting.jpg └── responsive-showcase-mockup.jpg └── pcb ├── gerbers.zip └── pcb-black-transfer.pdf /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Dan Jones 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 | ![php 5.4+](https://img.shields.io/badge/php-v5.4%2B-blue) [![maintained](https://img.shields.io/badge/maintained-yes-green.svg)](https://github.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/graphs/commit-activity) ![licence](https://img.shields.io/github/license/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller) 2 | 3 | 4 | # Raspberry Pi Relay & APA102 LED Controller 5 | 6 | ![APA102 LED Controller Responsive Web GUI Mockup](/img/responsive-showcase-mockup.jpg) 7 | 8 | Raspberry Pi Relay & APA102 LED controller allows control & switching of APA102 addressable LED strips (and LED driver) using a web GUI. Motor controller functionality is also built-in to control hard-wired home automation type blind / projector screen motors. Ideal for use in home cinema applications. Unlike regular "dumb" RGB strips, addressable strips have independently controlled LEDs, allowing for the creation of light effects & sequences. 9 | 10 | ![Home Cinema Rainbow Rotate RGB LED Effect](/img/rainbow-rgb-led-soffit-lighting.jpg) 11 | 12 | ## Features 13 | 14 | * APA102 LED control 15 | * 2x changeover relay control 16 | * 2x mains AC input detection 17 | * Motorised blind / projector screen control 18 | * 2x TTL inputs for external buttons 19 | 20 | ## Motivation 21 | 22 | This project was created for use in my own home cinema build. I wanted a single-room home automation solution that would offer addressable RGB control, with the ability to directly switch a mains AC powered LED driver. Additional relay channels where added to the prototype to allow other circuits to be switched using the controller, such as spotlights. The motorised blind control relays were added into the design during my home cinema build. 23 | 24 | Home cinema build log: https://www.avforums.com/threads/ongoing-plasmadans-living-room-cinema-office-build.1992617/ 25 | 26 | ## Responsive Web GUI 27 | 28 | ![APA102 LED Controller iPhone6 Web GUI Mockup](/img/iphone6-mockup.jpg) 29 | 30 | The GUI includes controls for the changeover relay channels, motorised blind / projector screen, a full RGB color picker for the APA102 LEDs and a preset control to cycle built-in light modes / effects. There is also the ability to reboot the Raspberry Pi directly from the GUI, making development & testing easier for your application. 31 | 32 | Built on bootstrap 3; the GUI is fully responsive and adapts to any screen size / orientation. 33 | 34 | ## App Features 35 | 36 | As well as support for mobile devices, the GUI includes modern manifest data to allow it to work more like a native app. This means when you save the GUI to the home-screen it will load & function without an address-bar, just like an app. 37 | 38 | ## Chrome Extension 39 | 40 | The specially created Chrome extension makes the web GUI even easier to use on desktop, allowing for GUI control without the need to leave the current web page. The Chrome extension also provides the ability to map keyboard shortcuts to each function of the web GUI, including toggle on / off, LED preset & blind control. 41 | 42 | https://chrome.google.com/webstore/detail/apa102-led-controller/jnmjhaaahpdapgcddlgaldjhapmoapje 43 | 44 | ## Prerequisites 45 | 46 | Raspberry Pi with Raspberry Pi OS: 47 | https://www.raspberrypi.org/downloads/raspberry-pi-os/ 48 | 49 | I recommend a clean install before proceeding. 50 | 51 | ## Dependencies 52 | 53 | APA102 LEDs require the Python port of the Adafruit DotStar library to function. This is included in this repo for completeness. 54 | 55 | https://github.com/adafruit/Adafruit_DotStar_Pi 56 | 57 | ## Build Your Own! 58 | 59 | ![APA102 LED Controller PCB Animated](/img/pcb-animated.gif) 60 | 61 | The hardware for this controller is quite simple, all the components are readily available. If you decide to build one for yourself, I have provided the necessary Gerber files for the PCB. These can either be sent to a PCB manufacturer like [PCBway](http://www.pcbway.com/setinvite.aspx?inviteid=19024), or you can etch the board yourself (see included transfer pdf). The PCB design is single-sided to make it easier to re-create yourself. The PCB was designed to fit into a small case ([CAMDENBOSS 7200-269C](http://camdenboss.com/enclosures/heavy-duty-enclosures/polycarbonate-clear-lid-cases#7200-series-grey-clear200x120x75)). If you require additional inputs / outputs, or want to make any other changes; you may want to create your own PCB instead. 62 | 63 | ![APA102 LED Controller PCB Transfer](/img/pcb-black-transfer.png) 64 | 65 | Parts list: https://goo.gl/5SdG7h 66 | 67 | ## Raspberry Pi Compatibility 68 | 69 | * All except the original Model B (rev. 1) - Although with some changes it can be made to work. 70 | 71 | The PCB design uses a 26-way header (same as the Raspberry Pi model B). A 26-way to 40-way ribbon cable will be needed to work with Raspberry Pi B+ and above. 72 | 73 | ## Wiring 74 | 75 | ![APA102 LED Controller Wired](/img/Installation-example.jpg) 76 | 77 | The controller is designed to work with 4-wire type addressable LED strips; such as APA102 (AKA Adafruit DotStars) or WS2801. Everything else on the controller is pretty-much universal in terms of wiring options. I have provided an example wiring diagram: 78 | 79 | ![APA102 LED Controller Wiring Diagram Example](/img/example-wiring-diagram-v2.png) 80 | 81 | In this example, the LED driver and halogen lighting circuits are linked to the changeover relay channels. This allows for standard 2-way / intermediate (3-way if you're outside the UK) light switches to be used in conjunction with the controller. This means that if the controller went offline for whatever reason, your lights will still work! 82 | 83 | Notice in the example that the switched-line is looped back into the AC detect circuits. This is to allow the Raspberry Pi to sense when the lights / LED driver are powered, regardless of relay / switch positions. If you don't require 2-way control you can disable this in the config. 84 | 85 | # Installation 86 | 87 | Tip: For headless setup, SSH can be enabled by placing a file named 'ssh' (no extension) onto the boot partition of the SD card. 88 | 89 | Update your Raspberry Pi to ensure all the latest packages are installed. 90 | 91 | ``` 92 | sudo apt update 93 | sudo apt upgrade 94 | ``` 95 | 96 | ## Install Apache & PHP 97 | 98 | ``` 99 | sudo apt install apache2 php libapache2-mod-php -y 100 | ``` 101 | 102 | Test the webserver is working. Navigate to http://localhost/ on the Pi itself, or http://192.168.1.10 (whatever the Pi's IP address is) from another computer on the network. Use the snippet below to get the Pi's IP address in command line. 103 | 104 | ``` 105 | hostname -I 106 | ``` 107 | 108 | ## Install WiringPi 109 | 110 | ``` 111 | sudo apt install wiringpi python-pip -y 112 | sudo pip install wiringpi 113 | ``` 114 | 115 | ## Enable SPI 116 | 117 | Needed for RGB LEDs to work. 118 | 119 | ``` 120 | sudo raspi-config 121 | ``` 122 | 123 | Scroll to "Advanced Options", "SPI", set to enabled. 124 | 125 | ## Install GUI 126 | 127 | You need to clone the web GUI files from the `/gui` subdirectory, to do that we need to install subversion. 128 | 129 | ``` 130 | sudo apt install subversion -y 131 | ``` 132 | 133 | Empty default Apache files and install GUI. 134 | 135 | ``` 136 | sudo rm -rf /var/www/html/* 137 | sudo svn checkout https://github.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/trunk/gui /var/www/html 138 | ``` 139 | 140 | ### Permissions 141 | 142 | Be sure to set file permissions to 755 in the web directory. 143 | 144 | ``` 145 | sudo chmod -R 755 /var/www 146 | ``` 147 | 148 | Before proceeding, make sure the required scripts run correctly without issue. 149 | 150 | ``` 151 | python python/preset.py 152 | ``` 153 | 154 | You should see "Preset Button Control Loaded!". Do the same test for motor.py if you require it. 155 | 156 | ``` 157 | python python/motor.py 158 | ``` 159 | 160 | You should see "Motor Button Control Loaded!". 161 | 162 | Assuming no issues, set these scripts to run automatically at startup. 163 | 164 | Edit rc.local: 165 | 166 | ``` 167 | sudo nano /etc/rc.local 168 | ``` 169 | 170 | Add before exit 0 171 | 172 | ``` 173 | python /var/www/html/python/motor.py & 174 | python /var/www/html/python/preset.py & 175 | ``` 176 | 177 | The scripts are independent from each other to allow you to just use what you need. 178 | 179 | ## Optional: Install vsftpd for Easier File Editing 180 | 181 | ``` 182 | sudo apt install vsftpd -y 183 | ``` 184 | 185 | Change user for vsftpd. 186 | 187 | ``` 188 | sudo chown -R pi /var/www 189 | ``` 190 | 191 | Edit vsftpd.conf. 192 | 193 | ``` 194 | sudo nano /etc/vsftpd.conf 195 | ``` 196 | 197 | Uncomment the following line: 198 | 199 | ``` 200 | write_enable=YES 201 | ``` 202 | 203 | Add the following line: 204 | 205 | ``` 206 | force_dot_files=YES 207 | ``` 208 | 209 | Save and exit nano, then restart vsftpd. 210 | 211 | ``` 212 | sudo service vsftpd restart 213 | ``` 214 | 215 | You should now be able to login via FTP. 216 | 217 | ## Config 218 | 219 | There are lots of user customisable options in the config file :```/python/config.py``` 220 | 221 | You can customise everything from the GPIO channels used, the layout & language of the web GUI, lighting transition effects, preset modes, button timing and a whole lot more. Get familiar with the config file. 222 | 223 | ## Automation 224 | 225 | To automate blind control actions there are 2 scripts included: 226 | 227 | ``` 228 | /python/motor_up.py 229 | /python/motor_down.py 230 | ``` 231 | 232 | These scripts simulate a button press for up & down actions. You can use crontab to automatically trigger these scripts at a time of your choosing. 233 | 234 | Run crontab with the -e flag to edit the cron table: 235 | 236 | ``` 237 | crontab -e 238 | ``` 239 | 240 | The first time you run crontab you'll be prompted to select an editor; if you are not sure which one to use, choose nano by pressing Enter. 241 | 242 | Add your scheduled tasks. For help see here: https://www.raspberrypi.org/documentation/linux/usage/cron.md 243 | 244 | For example, to schedule the blind to open at 7AM and close again at 6:30PM every day you would enter the following: 245 | 246 | ``` 247 | 0 7 * * * python /var/www/html/python/motor_up.py 248 | 30 18 * * * python /var/www/html/python/motor_down.py 249 | ``` 250 | 251 | ## License 252 | 253 | MIT © Dan Jones - [PlasmaDan.com](https://plasmadan.com) 254 | -------------------------------------------------------------------------------- /gui/_config.yml: -------------------------------------------------------------------------------- 1 | theme: jekyll-theme-cayman -------------------------------------------------------------------------------- /gui/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/f494003e680ca22862e67e3e6af6135cbd2c9c73/gui/android-chrome-192x192.png -------------------------------------------------------------------------------- /gui/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/f494003e680ca22862e67e3e6af6135cbd2c9c73/gui/android-chrome-512x512.png -------------------------------------------------------------------------------- /gui/apple-touch-icon-precomposed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/f494003e680ca22862e67e3e6af6135cbd2c9c73/gui/apple-touch-icon-precomposed.png -------------------------------------------------------------------------------- /gui/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/f494003e680ca22862e67e3e6af6135cbd2c9c73/gui/apple-touch-icon.png -------------------------------------------------------------------------------- /gui/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #2b5797 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /gui/color.txt: -------------------------------------------------------------------------------- 1 | 255 2 | 255 3 | 255 4 | 128 -------------------------------------------------------------------------------- /gui/config.php: -------------------------------------------------------------------------------- 1 | 55 | -------------------------------------------------------------------------------- /gui/css/bootstrap-toggle.min.css: -------------------------------------------------------------------------------- 1 | /*! ======================================================================== 2 | * Bootstrap Toggle: bootstrap-toggle.css v2.2.0 3 | * http://www.bootstraptoggle.com 4 | * ======================================================================== 5 | * Copyright 2014 Min Hur, The New York Times Company 6 | * Licensed under MIT 7 | * ======================================================================== */ 8 | .checkbox label .toggle,.checkbox-inline .toggle{margin-left:-20px;margin-right:5px} 9 | .toggle{position:relative;overflow:hidden} 10 | .toggle input[type=checkbox]{display:none} 11 | .toggle-group{position:absolute;width:200%;top:0;bottom:0;left:0;transition:left .35s;-webkit-transition:left .35s;-moz-user-select:none;-webkit-user-select:none} 12 | .toggle.off .toggle-group{left:-100%} 13 | .toggle-on{position:absolute;top:0;bottom:0;left:0;right:50%;margin:0;border:0;border-radius:0} 14 | .toggle-off{position:absolute;top:0;bottom:0;left:50%;right:0;margin:0;border:0;border-radius:0} 15 | .toggle-handle{position:relative;margin:0 auto;padding-top:0;padding-bottom:0;height:100%;width:0;border-width:0 1px} 16 | .toggle.btn{min-width:59px;min-height:34px} 17 | .toggle-on.btn{padding-right:24px} 18 | .toggle-off.btn{padding-left:24px} 19 | .toggle.btn-lg{min-width:79px;min-height:45px} 20 | .toggle-on.btn-lg{padding-right:31px} 21 | .toggle-off.btn-lg{padding-left:31px} 22 | .toggle-handle.btn-lg{width:40px} 23 | .toggle.btn-sm{min-width:50px;min-height:30px} 24 | .toggle-on.btn-sm{padding-right:20px} 25 | .toggle-off.btn-sm{padding-left:20px} 26 | .toggle.btn-xs{min-width:35px;min-height:22px} 27 | .toggle-on.btn-xs{padding-right:12px} 28 | .toggle-off.btn-xs{padding-left:12px} -------------------------------------------------------------------------------- /gui/css/style.css: -------------------------------------------------------------------------------- 1 | /* 2 | * style.css 3 | * 4 | * Copyright (C) 2016 Dan Jones - https://plasmadan.com 5 | * 6 | * Full project details here: 7 | * https://github.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller 8 | * https://www.avforums.com/threads/ongoing-plasmadans-living-room-cinema-office-build.1992617/ 9 | * 10 | * ----------------------------------------------------------------------------- 11 | * Permission is hereby granted, free of charge, to any person obtaining a copy 12 | * of this software and associated documentation files (the "Software"), to deal 13 | * in the Software without restriction, including without limitation the rights 14 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 15 | * copies of the Software, and to permit persons to whom the Software is 16 | * furnished to do so, subject to the following conditions: 17 | * 18 | * The above copyright notice and this permission notice shall be included in 19 | * all copies or substantial portions of the Software. 20 | * ----------------------------------------------------------------------------- 21 | */ 22 | 23 | 24 | @-ms-viewport {width: device-width;} 25 | @-o-viewport {width: device-width;} 26 | @viewport {width: device-width;} 27 | 28 | * { 29 | outline: none; 30 | border: none; 31 | } 32 | 33 | html, body { 34 | height: 100%; 35 | } 36 | 37 | body { 38 | background: #444; 39 | color: #FFF; 40 | } 41 | 42 | .navbar-static-top { 43 | margin-bottom: 19px; 44 | } 45 | 46 | .row { 47 | padding-bottom: 30px; 48 | } 49 | 50 | .wrapper { 51 | padding-bottom: 0; 52 | } 53 | 54 | .switch_label { 55 | line-height: 34px; 56 | font-size: 20px; 57 | } 58 | 59 | .no_line_height { 60 | line-height: 0; 61 | } 62 | 63 | .container label { 64 | margin: 0; 65 | } 66 | 67 | .motor_control { 68 | padding: 3px 11px 0 12px; 69 | margin-left: 15px; 70 | } 71 | 72 | .motor_control .glyphicon { 73 | font-size: 22px; 74 | } 75 | 76 | [data-notify="progressbar"] { 77 | margin-bottom: 0px; 78 | position: absolute; 79 | bottom: 0px; 80 | left: 0px; 81 | width: 100%; 82 | height: 5px; 83 | } 84 | 85 | .modal-dialog { 86 | color: #444; 87 | } 88 | 89 | /* navbar button */ 90 | @media (min-width: 768px) { 91 | .navbar-collapse button { 92 | margin-left: 15px; 93 | margin-right: 15px; 94 | } 95 | } 96 | 97 | @media (max-width: 767px) { 98 | .navbar-collapse button { 99 | margin-left: 15px; 100 | } 101 | } 102 | 103 | /* Default colorpicker size */ 104 | .jQWCP-wWidget { 105 | height: 200px; 106 | } 107 | 108 | /* Responsive colorpicker size */ 109 | @media (orientation:portrait) { 110 | /* iPhone 5 */ 111 | @media (min-width: 320px) { 112 | .colorpicker-size { 113 | height: 200px; 114 | } 115 | } 116 | 117 | /* Galaxy S5 */ 118 | @media (min-width: 360px) { 119 | .colorpicker-size { 120 | height: 220px; 121 | } 122 | } 123 | 124 | /* iPhone 6 */ 125 | @media (min-width: 375px) { 126 | .colorpicker-size { 127 | height: 240px; 128 | } 129 | } 130 | 131 | /* Nexus 5/6 */ 132 | @media (min-width: 412px) { 133 | .colorpicker-size { 134 | height: 260px; 135 | } 136 | } 137 | 138 | /* iPhone 6 Plus */ 139 | @media (min-width: 414px) { 140 | .colorpicker-size { 141 | height: 280px; 142 | } 143 | } 144 | 145 | /* Responsive Small */ 146 | @media (min-width: 569px) { 147 | .colorpicker-size { 148 | height: 340px; 149 | } 150 | } 151 | 152 | /* iPad */ 153 | @media (min-width: 768px) { 154 | .colorpicker-size { 155 | height: 540px; 156 | } 157 | } 158 | } 159 | 160 | @media (orientation:landscape) { 161 | /* iPhone 4 */ 162 | @media (min-width: 480px) { 163 | .colorpicker-size { 164 | height: 120px; 165 | } 166 | } 167 | 168 | /* iPhone 5 */ 169 | @media (min-width: 568px) { 170 | .colorpicker-size { 171 | height: 170px; 172 | } 173 | } 174 | 175 | /* Galaxy S5 */ 176 | @media (min-width: 640px) { 177 | .colorpicker-size { 178 | height: 190px; 179 | } 180 | } 181 | 182 | /* iPhone 6 */ 183 | @media (min-width: 667px) { 184 | .colorpicker-size { 185 | height: 200px; 186 | } 187 | } 188 | 189 | /* Nexus 5/6 */ 190 | @media (min-width: 732px) { 191 | .colorpicker-size { 192 | height: 230px; 193 | } 194 | } 195 | 196 | /* iPhone 6 Plus */ 197 | @media (min-width: 736px) { 198 | .colorpicker-size { 199 | height: 230px; 200 | } 201 | } 202 | 203 | /* iPad */ 204 | @media (min-width: 1024px) { 205 | .colorpicker-size { 206 | height: 320px; 207 | } 208 | } 209 | 210 | @media (max-width: 991px) { 211 | .col-md-6 { 212 | width: 50%; 213 | float: left; 214 | } 215 | 216 | .row:last-child { 217 | padding-bottom: 0; 218 | } 219 | } 220 | } 221 | 222 | /* Desktop */ 223 | @media (min-width: 1200px) { 224 | .colorpicker-size { 225 | height: 350px; 226 | } 227 | } -------------------------------------------------------------------------------- /gui/css/wheelcolorpicker.modified.css: -------------------------------------------------------------------------------- 1 | /** 2 | * jQuery Wheel Color Picker 3 | * Base Stylesheet 4 | * 5 | * http://www.jar2.net/projects/jquery-wheelcolorpicker 6 | * 7 | * Copyright © 2011-2016 Fajar Chandra. All rights reserved. 8 | * Released under MIT License. 9 | * http://www.opensource.org/licenses/mit-license.php 10 | * 11 | * Note: Width, height, left, and top properties are handled by the 12 | * plugin. These values might change on the fly. 13 | */ 14 | 15 | .jQWCP-wWidget { 16 | position: absolute; 17 | width: 100%; 18 | # height: 200px; 19 | # background: #eee; 20 | # box-shadow: 1px 1px 4px rgba(0,0,0,.5); 21 | # border-radius: 4px; 22 | # border: solid 1px #aaa; 23 | # padding: 10px; 24 | z-index: 1001; 25 | } 26 | 27 | .jQWCP-wWidget.jQWCP-block { 28 | position: relative; 29 | # border-color: #aaa; 30 | # box-shadow: inset 1px 1px 1px #ccc; 31 | } 32 | 33 | .jQWCP-wWheel { 34 | background-size: contain; 35 | position: relative; 36 | float: left; 37 | width: 250px; 38 | height: 250px; 39 | -webkit-border-radius: 90px; 40 | -moz-border-radius: 50%; 41 | border-radius: 50%; 42 | # border: solid 1px #aaa; 43 | margin: -1px; 44 | margin-right: 15px; 45 | transition: border .15s; 46 | cursor: crosshair; 47 | } 48 | 49 | .jQWCP-wWheel:hover { 50 | border-color: #666; 51 | } 52 | 53 | .jQWCP-wWheelOverlay { 54 | position: absolute; 55 | top: 0; 56 | left: 0; 57 | width: 100%; 58 | height: 100%; 59 | background: #000; 60 | opacity: 0; 61 | -webkit-border-radius: 90px; 62 | -moz-border-radius: 50%; 63 | border-radius: 50%; 64 | } 65 | 66 | .jQWCP-wWheelCursor { 67 | width: 8px; 68 | height: 8px; 69 | position: absolute; 70 | top: 50%; 71 | left: 50%; 72 | margin: -6px -6px; 73 | cursor: crosshair; 74 | border: solid 2px #fff; 75 | box-shadow: 1px 1px 2px #000; 76 | border-radius: 50%; 77 | } 78 | 79 | .jQWCP-slider-wrapper, 80 | .jQWCP-wPreview { 81 | position: relative; 82 | width: 20px; 83 | height: 180px; 84 | float: left; 85 | margin-right: 15px; 86 | } 87 | 88 | .jQWCP-wWheel:last-child, 89 | .jQWCP-slider-wrapper:last-child, 90 | .jQWCP-wPreview:last-child { 91 | margin-right: 0; 92 | } 93 | 94 | .jQWCP-slider, 95 | .jQWCP-wPreviewBox { 96 | position: absolute; 97 | width: 100%; 98 | height: 100%; 99 | left: 0; 100 | top: 0; 101 | box-sizing: border-box; 102 | border: solid 1px #aaa; 103 | margin: -1px; 104 | -moz-border-radius: 4px; 105 | border-radius: 4px; 106 | transition: border .15s; 107 | } 108 | 109 | .jQWCP-slider { 110 | cursor: crosshair; 111 | } 112 | 113 | .jQWCP-slider-wrapper:hover .jQWCP-slider { 114 | border-color: #666; 115 | } 116 | 117 | .jQWCP-scursor { 118 | position: absolute; 119 | left: 0; 120 | top: 0; 121 | right: 0; 122 | height: 6px; 123 | margin: -5px -1px -5px -3px; 124 | cursor: crosshair; 125 | border: solid 2px #fff; 126 | box-shadow: 1px 1px 2px #000; 127 | border-radius: 4px; 128 | } 129 | 130 | .jQWCP-wAlphaSlider, 131 | .jQWCP-wPreviewBox, 132 | .jQWCP-wBrightnessSlider { 133 | background: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAQMAAAAlPW0iAAAABlBMVEVAQEB/f39eaJUuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3QYRBDgK9dKdMgAAABl0RVh0Q29tbWVudABDcmVhdGVkIHdpdGggR0lNUFeBDhcAAAARSURBVAjXY/jPwIAVYRf9DwB+vw/x6vMT1wAAAABJRU5ErkJggg==') center center; 134 | } 135 | 136 | .jQWCP-overlay { 137 | position: fixed; 138 | top: 0; 139 | left: 0; 140 | bottom: 0; 141 | right: 0; 142 | z-index: 1000; 143 | } 144 | 145 | /*********************/ 146 | 147 | /* Mobile layout */ 148 | 149 | .jQWCP-mobile.jQWCP-wWidget { 150 | position: fixed; 151 | bottom: 0; 152 | left: 0 !important; 153 | top: auto !important; 154 | width: 100%; 155 | height: 75%; 156 | max-height: 240px; 157 | box-sizing: border-box; 158 | border-radius: 0; 159 | } 160 | -------------------------------------------------------------------------------- /gui/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/f494003e680ca22862e67e3e6af6135cbd2c9c73/gui/favicon-16x16.png -------------------------------------------------------------------------------- /gui/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/f494003e680ca22862e67e3e6af6135cbd2c9c73/gui/favicon-32x32.png -------------------------------------------------------------------------------- /gui/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/f494003e680ca22862e67e3e6af6135cbd2c9c73/gui/favicon.ico -------------------------------------------------------------------------------- /gui/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/f494003e680ca22862e67e3e6af6135cbd2c9c73/gui/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /gui/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/f494003e680ca22862e67e3e6af6135cbd2c9c73/gui/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /gui/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/f494003e680ca22862e67e3e6af6135cbd2c9c73/gui/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /gui/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/plasmadancom/Raspberry-Pi-Relay-APA102-LED-Controller/f494003e680ca22862e67e3e6af6135cbd2c9c73/gui/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /gui/functions.php: -------------------------------------------------------------------------------- 1 | = $lux_lowest && $val <= $lux_highest); 33 | } 34 | 35 | // Read color from file 36 | function read_color_file($file) { 37 | // Get file cotents as array 38 | $color = @file($file); 39 | 40 | return $color; 41 | } 42 | 43 | // Create color file 44 | function write_color_file($file, $fallback) { 45 | // Create file 46 | $file = fopen($file, "a"); 47 | 48 | // Write to file and close 49 | fwrite($file, implode(PHP_EOL, $fallback)); 50 | fclose($file); 51 | } 52 | 53 | // Convert 24-bit color to RGB 54 | function colorToRGB($c) { 55 | $c = (int)$c; 56 | 57 | $r = $c >> 16; 58 | $c -= $r * 65536; 59 | $g = $c / 256; 60 | $c -= $g * 256; 61 | $b = $c; 62 | 63 | return array((string)$r, (string)$g, (string)$b); 64 | } 65 | 66 | // Get color from file 67 | function GetColor() { 68 | global $numpixels; 69 | 70 | // File location 71 | $color_file = dirname(__FILE__) . '/color.txt'; 72 | 73 | // fallback color 74 | $fallback = array('r' => 255, 'g' => 255, 'b' => 255, 'l' => 128); 75 | 76 | // Check for existing color file 77 | if(!file_exists($color_file)) write_color_file($color_file, $fallback); 78 | 79 | // Check if color file is readable 80 | if(!is_readable($color_file)) die($color_file . ' ' . $lang_functions_file_not_readable); 81 | 82 | // Get color from file 83 | $color = read_color_file($color_file); 84 | 85 | // Verify color file data 86 | if (empty($color)) { 87 | write_color_file($color_file, $fallback); 88 | return $fallback; 89 | } 90 | 91 | // Single 24-bit color & brightness, convert to RGB 92 | else if (count($color) == 2) { 93 | $rgb = colorToRGB($color[0]); 94 | $color = array_map('intval', array($rgb[0], $rgb[1], $rgb[2], end($color))); 95 | } 96 | 97 | // Multiple 24-bit colors & brightness. Can't display this in the GUI (yet), just get the first color 98 | else if (count($color) == $numpixels + 1 || count($color) == $numpixels + 2) { 99 | $rgb = colorToRGB($color[0]); 100 | $color = array_map('intval', array($rgb[0], $rgb[1], $rgb[2], end($color))); 101 | } 102 | 103 | return array_map('intval', $color); 104 | } 105 | 106 | // Return GPIO status 107 | function gpio_status($pin) { 108 | exec("gpio read " . $pin, $status); 109 | return $status[0]; 110 | } 111 | 112 | // Toggle GPIO state & return 113 | function relay_toggle($pin) { 114 | $status = (bool)gpio_status($pin) ? 0 : 1; 115 | 116 | system("gpio mode " . $pin . " out"); 117 | system("gpio write " . $pin . " " . $status); 118 | 119 | return gpio_status($pin); 120 | } 121 | 122 | // Write to spare GPIO port for Python to detect 123 | function write_buffer($pin) { 124 | global $buffer_sleep; 125 | 126 | // Write high to GPIO, wait, reset 127 | system("gpio write " . $pin . " 1"); 128 | usleep($buffer_sleep); 129 | system("gpio write " . $pin . " 0"); 130 | 131 | return 1; 132 | } 133 | 134 | ?> 135 | -------------------------------------------------------------------------------- /gui/index.php: -------------------------------------------------------------------------------- 1 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | <?php echo $title; ?> 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 144 | 145 | 146 |
147 |
148 |
149 |
150 |
151 | 152 |
153 |
154 | 157 |
158 |
159 | 160 |
161 |
162 | 163 |
164 |
165 | 168 |
169 |
170 | 174 |
175 |
176 | 177 |
178 |
179 | 180 | 181 |
182 |
183 | 186 |
187 |
188 | 189 |
190 |
191 | 192 |
193 |
194 |
195 |
196 | 197 |
198 |
199 |
200 |
201 | 202 | 203 |
204 | 205 | 206 | -------------------------------------------------------------------------------- /gui/js/bootbox.min.js: -------------------------------------------------------------------------------- 1 | /** 2 | * bootbox.js v4.4.0 3 | * 4 | * http://bootboxjs.com/license.txt 5 | */ 6 | !function(a,b){"use strict";"function"==typeof define&&define.amd?define(["jquery"],b):"object"==typeof exports?module.exports=b(require("jquery")):a.bootbox=b(a.jQuery)}(this,function a(b,c){"use strict";function d(a){var b=q[o.locale];return b?b[a]:q.en[a]}function e(a,c,d){a.stopPropagation(),a.preventDefault();var e=b.isFunction(d)&&d.call(c,a)===!1;e||c.modal("hide")}function f(a){var b,c=0;for(b in a)c++;return c}function g(a,c){var d=0;b.each(a,function(a,b){c(a,b,d++)})}function h(a){var c,d;if("object"!=typeof a)throw new Error("Please supply an object of options");if(!a.message)throw new Error("Please specify a message");return a=b.extend({},o,a),a.buttons||(a.buttons={}),c=a.buttons,d=f(c),g(c,function(a,e,f){if(b.isFunction(e)&&(e=c[a]={callback:e}),"object"!==b.type(e))throw new Error("button with key "+a+" must be an object");e.label||(e.label=a),e.className||(e.className=2>=d&&f===d-1?"btn-primary":"btn-default")}),a}function i(a,b){var c=a.length,d={};if(1>c||c>2)throw new Error("Invalid argument length");return 2===c||"string"==typeof a[0]?(d[b[0]]=a[0],d[b[1]]=a[1]):d=a[0],d}function j(a,c,d){return b.extend(!0,{},a,i(c,d))}function k(a,b,c,d){var e={className:"bootbox-"+a,buttons:l.apply(null,b)};return m(j(e,d,c),b)}function l(){for(var a={},b=0,c=arguments.length;c>b;b++){var e=arguments[b],f=e.toLowerCase(),g=e.toUpperCase();a[f]={label:d(g)}}return a}function m(a,b){var d={};return g(b,function(a,b){d[b]=!0}),g(a.buttons,function(a){if(d[a]===c)throw new Error("button key "+a+" is not allowed (options are "+b.join("\n")+")")}),a}var n={dialog:"",header:"",footer:"",closeButton:"",form:"
",inputs:{text:"",textarea:"",email:"",select:"",checkbox:"
",date:"",time:"",number:"",password:""}},o={locale:"en",backdrop:"static",animate:!0,className:null,closeButton:!0,show:!0,container:"body"},p={};p.alert=function(){var a;if(a=k("alert",["ok"],["message","callback"],arguments),a.callback&&!b.isFunction(a.callback))throw new Error("alert requires callback property to be a function when provided");return a.buttons.ok.callback=a.onEscape=function(){return b.isFunction(a.callback)?a.callback.call(this):!0},p.dialog(a)},p.confirm=function(){var a;if(a=k("confirm",["cancel","confirm"],["message","callback"],arguments),a.buttons.cancel.callback=a.onEscape=function(){return a.callback.call(this,!1)},a.buttons.confirm.callback=function(){return a.callback.call(this,!0)},!b.isFunction(a.callback))throw new Error("confirm requires a callback");return p.dialog(a)},p.prompt=function(){var a,d,e,f,h,i,k;if(f=b(n.form),d={className:"bootbox-prompt",buttons:l("cancel","confirm"),value:"",inputType:"text"},a=m(j(d,arguments,["title","callback"]),["cancel","confirm"]),i=a.show===c?!0:a.show,a.message=f,a.buttons.cancel.callback=a.onEscape=function(){return a.callback.call(this,null)},a.buttons.confirm.callback=function(){var c;switch(a.inputType){case"text":case"textarea":case"email":case"select":case"date":case"time":case"number":case"password":c=h.val();break;case"checkbox":var d=h.find("input:checked");c=[],g(d,function(a,d){c.push(b(d).val())})}return a.callback.call(this,c)},a.show=!1,!a.title)throw new Error("prompt requires a title");if(!b.isFunction(a.callback))throw new Error("prompt requires a callback");if(!n.inputs[a.inputType])throw new Error("invalid prompt type");switch(h=b(n.inputs[a.inputType]),a.inputType){case"text":case"textarea":case"email":case"date":case"time":case"number":case"password":h.val(a.value);break;case"select":var o={};if(k=a.inputOptions||[],!b.isArray(k))throw new Error("Please pass an array of input options");if(!k.length)throw new Error("prompt with select requires options");g(k,function(a,d){var e=h;if(d.value===c||d.text===c)throw new Error("given options in wrong format");d.group&&(o[d.group]||(o[d.group]=b("").attr("label",d.group)),e=o[d.group]),e.append("")}),g(o,function(a,b){h.append(b)}),h.val(a.value);break;case"checkbox":var q=b.isArray(a.value)?a.value:[a.value];if(k=a.inputOptions||[],!k.length)throw new Error("prompt with checkbox requires options");if(!k[0].value||!k[0].text)throw new Error("given options in wrong format");h=b("
"),g(k,function(c,d){var e=b(n.inputs[a.inputType]);e.find("input").attr("value",d.value),e.find("label").append(d.text),g(q,function(a,b){b===d.value&&e.find("input").prop("checked",!0)}),h.append(e)})}return a.placeholder&&h.attr("placeholder",a.placeholder),a.pattern&&h.attr("pattern",a.pattern),a.maxlength&&h.attr("maxlength",a.maxlength),f.append(h),f.on("submit",function(a){a.preventDefault(),a.stopPropagation(),e.find(".btn-primary").click()}),e=p.dialog(a),e.off("shown.bs.modal"),e.on("shown.bs.modal",function(){h.focus()}),i===!0&&e.modal("show"),e},p.dialog=function(a){a=h(a);var d=b(n.dialog),f=d.find(".modal-dialog"),i=d.find(".modal-body"),j=a.buttons,k="",l={onEscape:a.onEscape};if(b.fn.modal===c)throw new Error("$.fn.modal is not defined; please double check you have included the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ for more details.");if(g(j,function(a,b){k+="",l[a]=b.callback}),i.find(".bootbox-body").html(a.message),a.animate===!0&&d.addClass("fade"),a.className&&d.addClass(a.className),"large"===a.size?f.addClass("modal-lg"):"small"===a.size&&f.addClass("modal-sm"),a.title&&i.before(n.header),a.closeButton){var m=b(n.closeButton);a.title?d.find(".modal-header").prepend(m):m.css("margin-top","-10px").prependTo(i)}return a.title&&d.find(".modal-title").html(a.title),k.length&&(i.after(n.footer),d.find(".modal-footer").html(k)),d.on("hidden.bs.modal",function(a){a.target===this&&d.remove()}),d.on("shown.bs.modal",function(){d.find(".btn-primary:first").focus()}),"static"!==a.backdrop&&d.on("click.dismiss.bs.modal",function(a){d.children(".modal-backdrop").length&&(a.currentTarget=d.children(".modal-backdrop").get(0)),a.target===a.currentTarget&&d.trigger("escape.close.bb")}),d.on("escape.close.bb",function(a){l.onEscape&&e(a,d,l.onEscape)}),d.on("click",".modal-footer button",function(a){var c=b(this).data("bb-handler");e(a,d,l[c])}),d.on("click",".bootbox-close-button",function(a){e(a,d,l.onEscape)}),d.on("keyup",function(a){27===a.which&&d.trigger("escape.close.bb")}),b(a.container).append(d),d.modal({backdrop:a.backdrop?"static":!1,keyboard:!1,show:!1}),a.show&&d.modal("show"),d},p.setDefaults=function(){var a={};2===arguments.length?a[arguments[0]]=arguments[1]:a=arguments[0],b.extend(o,a)},p.hideAll=function(){return b(".bootbox").modal("hide"),p};var q={bg_BG:{OK:"Ок",CANCEL:"Отказ",CONFIRM:"Потвърждавам"},br:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Sim"},cs:{OK:"OK",CANCEL:"Zrušit",CONFIRM:"Potvrdit"},da:{OK:"OK",CANCEL:"Annuller",CONFIRM:"Accepter"},de:{OK:"OK",CANCEL:"Abbrechen",CONFIRM:"Akzeptieren"},el:{OK:"Εντάξει",CANCEL:"Ακύρωση",CONFIRM:"Επιβεβαίωση"},en:{OK:"OK",CANCEL:"Cancel",CONFIRM:"OK"},es:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Aceptar"},et:{OK:"OK",CANCEL:"Katkesta",CONFIRM:"OK"},fa:{OK:"قبول",CANCEL:"لغو",CONFIRM:"تایید"},fi:{OK:"OK",CANCEL:"Peruuta",CONFIRM:"OK"},fr:{OK:"OK",CANCEL:"Annuler",CONFIRM:"D'accord"},he:{OK:"אישור",CANCEL:"ביטול",CONFIRM:"אישור"},hu:{OK:"OK",CANCEL:"Mégsem",CONFIRM:"Megerősít"},hr:{OK:"OK",CANCEL:"Odustani",CONFIRM:"Potvrdi"},id:{OK:"OK",CANCEL:"Batal",CONFIRM:"OK"},it:{OK:"OK",CANCEL:"Annulla",CONFIRM:"Conferma"},ja:{OK:"OK",CANCEL:"キャンセル",CONFIRM:"確認"},lt:{OK:"Gerai",CANCEL:"Atšaukti",CONFIRM:"Patvirtinti"},lv:{OK:"Labi",CANCEL:"Atcelt",CONFIRM:"Apstiprināt"},nl:{OK:"OK",CANCEL:"Annuleren",CONFIRM:"Accepteren"},no:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},pl:{OK:"OK",CANCEL:"Anuluj",CONFIRM:"Potwierdź"},pt:{OK:"OK",CANCEL:"Cancelar",CONFIRM:"Confirmar"},ru:{OK:"OK",CANCEL:"Отмена",CONFIRM:"Применить"},sq:{OK:"OK",CANCEL:"Anulo",CONFIRM:"Prano"},sv:{OK:"OK",CANCEL:"Avbryt",CONFIRM:"OK"},th:{OK:"ตกลง",CANCEL:"ยกเลิก",CONFIRM:"ยืนยัน"},tr:{OK:"Tamam",CANCEL:"İptal",CONFIRM:"Onayla"},zh_CN:{OK:"OK",CANCEL:"取消",CONFIRM:"确认"},zh_TW:{OK:"OK",CANCEL:"取消",CONFIRM:"確認"}};return p.addLocale=function(a,c){return b.each(["OK","CANCEL","CONFIRM"],function(a,b){if(!c[b])throw new Error("Please supply a translation for '"+b+"'")}),q[a]={OK:c.OK,CANCEL:c.CANCEL,CONFIRM:c.CONFIRM},p},p.removeLocale=function(a){return delete q[a],p},p.setLocale=function(a){return p.setDefaults("locale",a)},p.init=function(c){return a(c||b)},p}); -------------------------------------------------------------------------------- /gui/js/bootstrap-notify.min.js: -------------------------------------------------------------------------------- 1 | /* Project: Bootstrap Growl = v3.1.3 | Description: Turns standard Bootstrap alerts into "Growl-like" notifications. | Author: Mouse0270 aka Robert McIntosh | License: MIT License | Website: https://github.com/mouse0270/bootstrap-growl */ 2 | !function(t){"function"==typeof define&&define.amd?define(["jquery"],t):t("object"==typeof exports?require("jquery"):jQuery)}(function(t){function e(e,i,n){var i={content:{message:"object"==typeof i?i.message:i,title:i.title?i.title:"",icon:i.icon?i.icon:"",url:i.url?i.url:"#",target:i.target?i.target:"-"}};n=t.extend(!0,{},i,n),this.settings=t.extend(!0,{},s,n),this._defaults=s,"-"==this.settings.content.target&&(this.settings.content.target=this.settings.url_target),this.animations={start:"webkitAnimationStart oanimationstart MSAnimationStart animationstart",end:"webkitAnimationEnd oanimationend MSAnimationEnd animationend"},"number"==typeof this.settings.offset&&(this.settings.offset={x:this.settings.offset,y:this.settings.offset}),this.init()}var s={element:"body",position:null,type:"info",allow_dismiss:!0,newest_on_top:!1,showProgressbar:!1,placement:{from:"top",align:"right"},offset:20,spacing:10,z_index:1031,delay:5e3,timer:1e3,url_target:"_blank",mouse_over:null,animate:{enter:"animated fadeInDown",exit:"animated fadeOutUp"},onShow:null,onShown:null,onClose:null,onClosed:null,icon_type:"class",template:''};String.format=function(){for(var t=arguments[0],e=1;e .progress-bar').removeClass("progress-bar-"+t.settings.type),t.settings.type=i[e],this.$ele.addClass("alert-"+i[e]).find('[data-notify="progressbar"] > .progress-bar').addClass("progress-bar-"+i[e]);break;case"icon":var n=this.$ele.find('[data-notify="icon"]');"class"==t.settings.icon_type.toLowerCase()?n.removeClass(t.settings.content.icon).addClass(i[e]):(n.is("img")||n.find("img"),n.attr("src",i[e]));break;case"progress":var a=t.settings.delay-t.settings.delay*(i[e]/100);this.$ele.data("notify-delay",a),this.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i[e]).css("width",i[e]+"%");break;case"url":this.$ele.find('[data-notify="url"]').attr("href",i[e]);break;case"target":this.$ele.find('[data-notify="url"]').attr("target",i[e]);break;default:this.$ele.find('[data-notify="'+e+'"]').html(i[e])}var o=this.$ele.outerHeight()+parseInt(t.settings.spacing)+parseInt(t.settings.offset.y);t.reposition(o)},close:function(){t.close()}}},buildNotify:function(){var e=this.settings.content;this.$ele=t(String.format(this.settings.template,this.settings.type,e.title,e.message,e.url,e.target)),this.$ele.attr("data-notify-position",this.settings.placement.from+"-"+this.settings.placement.align),this.settings.allow_dismiss||this.$ele.find('[data-notify="dismiss"]').css("display","none"),(this.settings.delay<=0&&!this.settings.showProgressbar||!this.settings.showProgressbar)&&this.$ele.find('[data-notify="progressbar"]').remove()},setIcon:function(){"class"==this.settings.icon_type.toLowerCase()?this.$ele.find('[data-notify="icon"]').addClass(this.settings.content.icon):this.$ele.find('[data-notify="icon"]').is("img")?this.$ele.find('[data-notify="icon"]').attr("src",this.settings.content.icon):this.$ele.find('[data-notify="icon"]').append('Notify Icon')},styleURL:function(){this.$ele.find('[data-notify="url"]').css({backgroundImage:"url(data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7)",height:"100%",left:"0px",position:"absolute",top:"0px",width:"100%",zIndex:this.settings.z_index+1}),this.$ele.find('[data-notify="dismiss"]').css({position:"absolute",right:"10px",top:"5px",zIndex:this.settings.z_index+2})},placement:function(){var e=this,s=this.settings.offset.y,i={display:"inline-block",margin:"0px auto",position:this.settings.position?this.settings.position:"body"===this.settings.element?"fixed":"absolute",transition:"all .5s ease-in-out",zIndex:this.settings.z_index},n=!1,a=this.settings;switch(t('[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])').each(function(){return s=Math.max(s,parseInt(t(this).css(a.placement.from))+parseInt(t(this).outerHeight())+parseInt(a.spacing))}),1==this.settings.newest_on_top&&(s=this.settings.offset.y),i[this.settings.placement.from]=s+"px",this.settings.placement.align){case"left":case"right":i[this.settings.placement.align]=this.settings.offset.x+"px";break;case"center":i.left=0,i.right=0}this.$ele.css(i).addClass(this.settings.animate.enter),t.each(Array("webkit","moz","o","ms",""),function(t,s){e.$ele[0].style[s+"AnimationIterationCount"]=1}),t(this.settings.element).append(this.$ele),1==this.settings.newest_on_top&&(s=parseInt(s)+parseInt(this.settings.spacing)+this.$ele.outerHeight(),this.reposition(s)),t.isFunction(e.settings.onShow)&&e.settings.onShow.call(this.$ele),this.$ele.one(this.animations.start,function(){n=!0}).one(this.animations.end,function(){t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)}),setTimeout(function(){n||t.isFunction(e.settings.onShown)&&e.settings.onShown.call(this)},600)},bind:function(){var e=this;if(this.$ele.find('[data-notify="dismiss"]').on("click",function(){e.close()}),this.$ele.mouseover(function(){t(this).data("data-hover","true")}).mouseout(function(){t(this).data("data-hover","false")}),this.$ele.data("data-hover","false"),this.settings.delay>0){e.$ele.data("notify-delay",e.settings.delay);var s=setInterval(function(){var t=parseInt(e.$ele.data("notify-delay"))-e.settings.timer;if("false"===e.$ele.data("data-hover")&&"pause"==e.settings.mouse_over||"pause"!=e.settings.mouse_over){var i=(e.settings.delay-t)/e.settings.delay*100;e.$ele.data("notify-delay",t),e.$ele.find('[data-notify="progressbar"] > div').attr("aria-valuenow",i).css("width",i+"%")}t<=-e.settings.timer&&(clearInterval(s),e.close())},e.settings.timer)}},close:function(){var e=this,s=parseInt(this.$ele.css(this.settings.placement.from)),i=!1;this.$ele.data("closing","true").addClass(this.settings.animate.exit),e.reposition(s),t.isFunction(e.settings.onClose)&&e.settings.onClose.call(this.$ele),this.$ele.one(this.animations.start,function(){i=!0}).one(this.animations.end,function(){t(this).remove(),t.isFunction(e.settings.onClosed)&&e.settings.onClosed.call(this)}),setTimeout(function(){i||(e.$ele.remove(),e.settings.onClosed&&e.settings.onClosed(e.$ele))},600)},reposition:function(e){var s=this,i='[data-notify-position="'+this.settings.placement.from+"-"+this.settings.placement.align+'"]:not([data-closing="true"])',n=this.$ele.nextAll(i);1==this.settings.newest_on_top&&(n=this.$ele.prevAll(i)),n.each(function(){t(this).css(s.settings.placement.from,e),e=parseInt(e)+parseInt(s.settings.spacing)+t(this).outerHeight()})}}),t.notify=function(t,s){var i=new e(this,t,s);return i.notify},t.notifyDefaults=function(e){return s=t.extend(!0,{},s,e)},t.notifyClose=function(e){"undefined"==typeof e||"all"==e?t("[data-notify]").find('[data-notify="dismiss"]').trigger("click"):t('[data-notify-position="'+e+'"]').find('[data-notify="dismiss"]').trigger("click")}}); -------------------------------------------------------------------------------- /gui/js/bootstrap-toggle.min.js: -------------------------------------------------------------------------------- 1 | /*! ======================================================================== 2 | * Bootstrap Toggle: bootstrap-toggle.js v2.2.0 3 | * http://www.bootstraptoggle.com 4 | * ======================================================================== 5 | * Copyright 2014 Min Hur, The New York Times Company 6 | * Licensed under MIT 7 | * ======================================================================== */ 8 | +function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.toggle"),f="object"==typeof b&&b;e||d.data("bs.toggle",e=new c(this,f)),"string"==typeof b&&e[b]&&e[b]()})}var c=function(b,c){this.$element=a(b),this.options=a.extend({},this.defaults(),c),this.render()};c.VERSION="2.2.0",c.DEFAULTS={on:"On",off:"Off",onstyle:"primary",offstyle:"default",size:"normal",style:"",width:null,height:null},c.prototype.defaults=function(){return{on:this.$element.attr("data-on")||c.DEFAULTS.on,off:this.$element.attr("data-off")||c.DEFAULTS.off,onstyle:this.$element.attr("data-onstyle")||c.DEFAULTS.onstyle,offstyle:this.$element.attr("data-offstyle")||c.DEFAULTS.offstyle,size:this.$element.attr("data-size")||c.DEFAULTS.size,style:this.$element.attr("data-style")||c.DEFAULTS.style,width:this.$element.attr("data-width")||c.DEFAULTS.width,height:this.$element.attr("data-height")||c.DEFAULTS.height}},c.prototype.render=function(){this._onstyle="btn-"+this.options.onstyle,this._offstyle="btn-"+this.options.offstyle;var b="large"===this.options.size?"btn-lg":"small"===this.options.size?"btn-sm":"mini"===this.options.size?"btn-xs":"",c=a('