diff options
114 files changed, 12087 insertions, 0 deletions
diff --git a/doc/CHANGELOG b/doc/CHANGELOG new file mode 100644 index 0000000..16206fd --- /dev/null +++ b/doc/CHANGELOG | |||
| @@ -0,0 +1,84 @@ | |||
| 1 | ==0.6 (27/03/13)== | ||
| 2 | Added CoAP ([http://tools.ietf.org/html/draft-ietf-core-coap-14 draft-14]) implementation (Server and Client) | ||
| 3 | Added Python WebIOPi Client class (HTTP or CoAP with HTTP fallback) | ||
| 4 | Added Python WebIOPi MulticastClient class (CoAP) | ||
| 5 | Added Serial, I2C, SPI and 1-Wire support | ||
| 6 | Python lightweight drivers with no dependency | ||
| 7 | Automatically load required linux modules | ||
| 8 | Added many device drivers, see [DEVICES device support page] | ||
| 9 | Added [Configuration] file | ||
| 10 | Added Serial monitor web app | ||
| 11 | Added Devices monitor web app | ||
| 12 | Added Devices abstractions Javascript class | ||
| 13 | Added GPIO digitalRead/Write and getFunction methods | ||
| 14 | Added Cache-Control REST response header | ||
| 15 | Added Server logging facility | ||
| 16 | Added jQuery-mobile | ||
| 17 | Improved button up/down handling on mobile devices | ||
| 18 | Fixed error fetching python-dev package during setup | ||
| 19 | Fixed webiopi-passwd missing execution flag when installing with PiStore | ||
| 20 | Fixed binary file serving | ||
| 21 | Fixed iOS6 Safari Mobile bug | ||
| 22 | |||
| 23 | |||
| 24 | ==0.5.3 (01/07/13)== | ||
| 25 | Added board revision REST URI (submitted by Andreas Riegg) | ||
| 26 | Added encrypted passwd file to store credentials used for HTTP authentication | ||
| 27 | Added webiopi-passwd command-line program to generate passwd file | ||
| 28 | Moved demo to examples/custom folder, added examples/basic | ||
| 29 | Changed /dev/mem access to allow webiopi import without root privileges when not using GPIOs | ||
| 30 | Disabled update checker to avoid "Update available" link | ||
| 31 | Improved GPIO error handling | ||
| 32 | Fixed encoding issue with python 2.x giving a blank page | ||
| 33 | Fixed setup script | ||
| 34 | |||
| 35 | ==0.5.2 (12/21/12 - PiStore only)== | ||
| 36 | Fixed blank page and file handling when server start at boot | ||
| 37 | Improved macros handling to allow zeo, one or more args | ||
| 38 | Added server loop helper | ||
| 39 | Improved setup.sh for the PiStore | ||
| 40 | Added play.sh for the PiStore to open the browser | ||
| 41 | |||
| 42 | ==0.5.1 (11/16/12)== | ||
| 43 | ===New Features=== | ||
| 44 | REV 2 boards support | ||
| 45 | Added setup script to ease WebIOPi install | ||
| 46 | Use WebIOPi in your own Python scripts | ||
| 47 | Login/Password protection | ||
| 48 | Software PWM | ||
| 49 | Binary sequence output | ||
| 50 | |||
| 51 | ===Python Server & REST API=== | ||
| 52 | Usable as a library | ||
| 53 | Added Python 3 support | ||
| 54 | Removed RPi.GPIO library dependency | ||
| 55 | Improved security | ||
| 56 | Improved file serving | ||
| 57 | Improved REST API | ||
| 58 | Added ability to use custom REST macro | ||
| 59 | Added ability to output a single pulse | ||
| 60 | Added ability to output a binary sequence | ||
| 61 | Added software PWM | ||
| 62 | |||
| 63 | ===Javascript Library=== | ||
| 64 | Improved and simplified | ||
| 65 | Added ability to create custom buttons with one or two callbacks (mousedown and mouseup) | ||
| 66 | Added helpers for new REST functions (macro, pulse, sequence, PWM, ...) | ||
| 67 | |||
| 68 | ===Other changes=== | ||
| 69 | PHP Server discontinued | ||
| 70 | |||
| 71 | |||
| 72 | ==0.3 (08/27/12)== | ||
| 73 | Fixed security issue in the Python server | ||
| 74 | Python server general improvements | ||
| 75 | Python server display real IP | ||
| 76 | Allow PHP5 < 5.4 (Works on Debian wheezy) | ||
| 77 | Added update tracking | ||
| 78 | Refactored Javascript code to be used as a library | ||
| 79 | Changed IDs and CSS naming | ||
| 80 | Added expert app | ||
| 81 | |||
| 82 | |||
| 83 | ==0.2 (08/21/12)== | ||
| 84 | Initial release \ No newline at end of file | ||
diff --git a/doc/INSTALL b/doc/INSTALL new file mode 100644 index 0000000..e34f9db --- /dev/null +++ b/doc/INSTALL | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | WebIOPi is developed and tested on Raspbian. | ||
| 2 | |||
| 3 | You only need Python, either 2.7 or 3.2. Download, then extract and install WebIOPi. The setup script will automatically download and install required dependencies using apt-get. You may have to manually install GCC and Python development headers if you are not using Raspbian. | ||
| 4 | |||
| 5 | $ wget http://webiopi.googlecode.com/files/WebIOPi-0.6.0.tar.gz | ||
| 6 | $ tar xvzf WebIOPi-0.6.0.tar.gz | ||
| 7 | $ cd WebIOPi-0.6.0 | ||
| 8 | $ sudo ./setup.sh | ||
| 9 | |||
| 10 | Finally, run Python using webiopi command : | ||
| 11 | $ sudo webiopi [-h] [-c config] [-l log] [-s script] [-d] [port] | ||
| 12 | |||
| 13 | Options: | ||
| 14 | -h, --help Display this help | ||
| 15 | -c, --config file Load config from file | ||
| 16 | -l, --log file Log to file | ||
| 17 | -s, --script file Load script from file | ||
| 18 | -d, --debug Enable DEBUG | ||
| 19 | |||
| 20 | Arguments: | ||
| 21 | port Port to bind the HTTP Server | ||
| 22 | |||
| 23 | You're done, and ready to enjoy WebIOPi ! But the server and GPIO state will be lost when you'll stop the script (CTRL-C) or close the terminal. | ||
| 24 | |||
| 25 | You can also start/stop the background service : | ||
| 26 | $ sudo /etc/init.d/webiopi start | ||
| 27 | and | ||
| 28 | $ sudo /etc/init.d/webiopi stop | ||
| 29 | |||
| 30 | You can even setup your system to start webiopi at startup : | ||
| 31 | $ sudo update-rc.d webiopi defaults | ||
| 32 | |||
| 33 | |||
| 34 | =Usage= | ||
| 35 | If your are directly using your Raspberry Pi with keyboard/mouse/display plugged, open a browser to http://localhost:8000/ | ||
| 36 | |||
| 37 | If your Raspberry Pi is connected to your network, you can open a browser to http://raspberrypi:8000/ with any device of your network. Replace raspberrypi by its IP. | ||
| 38 | |||
| 39 | You can even add a port redirection on your router (and/or use IPv6) to control your GPIOs over Internet ! | ||
| 40 | |||
| 41 | Default user is "webiopi" and password is "raspberry" | ||
| 42 | |||
| 43 | By choosing the GPIO Header link on the main page, you will be able to control GPIO using a web UI which looks like the board header. | ||
| 44 | Click/Tap the OUT/IN button to change GPIO direction. | ||
| 45 | Click/Tap pins to change the GPIO output state. | ||
| 46 | http://trouch.com/wp-content/uploads/2012/08/webiopi-chrome.png \ No newline at end of file | ||
diff --git a/doc/LICENSE b/doc/LICENSE new file mode 100644 index 0000000..4112d3e --- /dev/null +++ b/doc/LICENSE | |||
| @@ -0,0 +1,204 @@ | |||
| 1 | |||
| 2 | |||
| 3 | Apache License | ||
| 4 | Version 2.0, January 2004 | ||
| 5 | http://www.apache.org/licenses/ | ||
| 6 | |||
| 7 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION | ||
| 8 | |||
| 9 | 1. Definitions. | ||
| 10 | |||
| 11 | "License" shall mean the terms and conditions for use, reproduction, | ||
| 12 | and distribution as defined by Sections 1 through 9 of this document. | ||
| 13 | |||
| 14 | "Licensor" shall mean the copyright owner or entity authorized by | ||
| 15 | the copyright owner that is granting the License. | ||
| 16 | |||
| 17 | "Legal Entity" shall mean the union of the acting entity and all | ||
| 18 | other entities that control, are controlled by, or are under common | ||
| 19 | control with that entity. For the purposes of this definition, | ||
| 20 | "control" means (i) the power, direct or indirect, to cause the | ||
| 21 | direction or management of such entity, whether by contract or | ||
| 22 | otherwise, or (ii) ownership of fifty percent (50%) or more of the | ||
| 23 | outstanding shares, or (iii) beneficial ownership of such entity. | ||
| 24 | |||
| 25 | "You" (or "Your") shall mean an individual or Legal Entity | ||
| 26 | exercising permissions granted by this License. | ||
| 27 | |||
| 28 | "Source" form shall mean the preferred form for making modifications, | ||
| 29 | including but not limited to software source code, documentation | ||
| 30 | source, and configuration files. | ||
| 31 | |||
| 32 | "Object" form shall mean any form resulting from mechanical | ||
| 33 | transformation or translation of a Source form, including but | ||
| 34 | not limited to compiled object code, generated documentation, | ||
| 35 | and conversions to other media types. | ||
| 36 | |||
| 37 | "Work" shall mean the work of authorship, whether in Source or | ||
| 38 | Object form, made available under the License, as indicated by a | ||
| 39 | copyright notice that is included in or attached to the work | ||
| 40 | (an example is provided in the Appendix below). | ||
| 41 | |||
| 42 | "Derivative Works" shall mean any work, whether in Source or Object | ||
| 43 | form, that is based on (or derived from) the Work and for which the | ||
| 44 | editorial revisions, annotations, elaborations, or other modifications | ||
| 45 | represent, as a whole, an original work of authorship. For the purposes | ||
| 46 | of this License, Derivative Works shall not include works that remain | ||
| 47 | separable from, or merely link (or bind by name) to the interfaces of, | ||
| 48 | the Work and Derivative Works thereof. | ||
| 49 | |||
| 50 | "Contribution" shall mean any work of authorship, including | ||
| 51 | the original version of the Work and any modifications or additions | ||
| 52 | to that Work or Derivative Works thereof, that is intentionally | ||
| 53 | submitted to Licensor for inclusion in the Work by the copyright owner | ||
| 54 | or by an individual or Legal Entity authorized to submit on behalf of | ||
| 55 | the copyright owner. For the purposes of this definition, "submitted" | ||
| 56 | means any form of electronic, verbal, or written communication sent | ||
| 57 | to the Licensor or its representatives, including but not limited to | ||
| 58 | communication on electronic mailing lists, source code control systems, | ||
| 59 | and issue tracking systems that are managed by, or on behalf of, the | ||
| 60 | Licensor for the purpose of discussing and improving the Work, but | ||
| 61 | excluding communication that is conspicuously marked or otherwise | ||
| 62 | designated in writing by the copyright owner as "Not a Contribution." | ||
| 63 | |||
| 64 | "Contributor" shall mean Licensor and any individual or Legal Entity | ||
| 65 | on behalf of whom a Contribution has been received by Licensor and | ||
| 66 | subsequently incorporated within the Work. | ||
| 67 | |||
| 68 | 2. Grant of Copyright License. Subject to the terms and conditions of | ||
| 69 | this License, each Contributor hereby grants to You a perpetual, | ||
| 70 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
| 71 | copyright license to reproduce, prepare Derivative Works of, | ||
| 72 | publicly display, publicly perform, sublicense, and distribute the | ||
| 73 | Work and such Derivative Works in Source or Object form. | ||
| 74 | |||
| 75 | 3. Grant of Patent License. Subject to the terms and conditions of | ||
| 76 | this License, each Contributor hereby grants to You a perpetual, | ||
| 77 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable | ||
| 78 | (except as stated in this section) patent license to make, have made, | ||
| 79 | use, offer to sell, sell, import, and otherwise transfer the Work, | ||
| 80 | where such license applies only to those patent claims licensable | ||
| 81 | by such Contributor that are necessarily infringed by their | ||
| 82 | Contribution(s) alone or by combination of their Contribution(s) | ||
| 83 | with the Work to which such Contribution(s) was submitted. If You | ||
| 84 | institute patent litigation against any entity (including a | ||
| 85 | cross-claim or counterclaim in a lawsuit) alleging that the Work | ||
| 86 | or a Contribution incorporated within the Work constitutes direct | ||
| 87 | or contributory patent infringement, then any patent licenses | ||
| 88 | granted to You under this License for that Work shall terminate | ||
| 89 | as of the date such litigation is filed. | ||
| 90 | |||
| 91 | 4. Redistribution. You may reproduce and distribute copies of the | ||
| 92 | Work or Derivative Works thereof in any medium, with or without | ||
| 93 | modifications, and in Source or Object form, provided that You | ||
| 94 | meet the following conditions: | ||
| 95 | |||
| 96 | (a) You must give any other recipients of the Work or | ||
| 97 | Derivative Works a copy of this License; and | ||
| 98 | |||
| 99 | (b) You must cause any modified files to carry prominent notices | ||
| 100 | stating that You changed the files; and | ||
| 101 | |||
| 102 | (c) You must retain, in the Source form of any Derivative Works | ||
| 103 | that You distribute, all copyright, patent, trademark, and | ||
| 104 | attribution notices from the Source form of the Work, | ||
| 105 | excluding those notices that do not pertain to any part of | ||
| 106 | the Derivative Works; and | ||
| 107 | |||
| 108 | (d) If the Work includes a "NOTICE" text file as part of its | ||
| 109 | distribution, then any Derivative Works that You distribute must | ||
| 110 | include a readable copy of the attribution notices contained | ||
| 111 | within such NOTICE file, excluding those notices that do not | ||
| 112 | pertain to any part of the Derivative Works, in at least one | ||
| 113 | of the following places: within a NOTICE text file distributed | ||
| 114 | as part of the Derivative Works; within the Source form or | ||
| 115 | documentation, if provided along with the Derivative Works; or, | ||
| 116 | within a display generated by the Derivative Works, if and | ||
| 117 | wherever such third-party notices normally appear. The contents | ||
| 118 | of the NOTICE file are for informational purposes only and | ||
| 119 | do not modify the License. You may add Your own attribution | ||
| 120 | notices within Derivative Works that You distribute, alongside | ||
| 121 | or as an addendum to the NOTICE text from the Work, provided | ||
| 122 | that such additional attribution notices cannot be construed | ||
| 123 | as modifying the License. | ||
| 124 | |||
| 125 | You may add Your own copyright statement to Your modifications and | ||
| 126 | may provide additional or different license terms and conditions | ||
| 127 | for use, reproduction, or distribution of Your modifications, or | ||
| 128 | for any such Derivative Works as a whole, provided Your use, | ||
| 129 | reproduction, and distribution of the Work otherwise complies with | ||
| 130 | the conditions stated in this License. | ||
| 131 | |||
| 132 | 5. Submission of Contributions. Unless You explicitly state otherwise, | ||
| 133 | any Contribution intentionally submitted for inclusion in the Work | ||
| 134 | by You to the Licensor shall be under the terms and conditions of | ||
| 135 | this License, without any additional terms or conditions. | ||
| 136 | Notwithstanding the above, nothing herein shall supersede or modify | ||
| 137 | the terms of any separate license agreement you may have executed | ||
| 138 | with Licensor regarding such Contributions. | ||
| 139 | |||
| 140 | 6. Trademarks. This License does not grant permission to use the trade | ||
| 141 | names, trademarks, service marks, or product names of the Licensor, | ||
| 142 | except as required for reasonable and customary use in describing the | ||
| 143 | origin of the Work and reproducing the content of the NOTICE file. | ||
| 144 | |||
| 145 | 7. Disclaimer of Warranty. Unless required by applicable law or | ||
| 146 | agreed to in writing, Licensor provides the Work (and each | ||
| 147 | Contributor provides its Contributions) on an "AS IS" BASIS, | ||
| 148 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or | ||
| 149 | implied, including, without limitation, any warranties or conditions | ||
| 150 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A | ||
| 151 | PARTICULAR PURPOSE. You are solely responsible for determining the | ||
| 152 | appropriateness of using or redistributing the Work and assume any | ||
| 153 | risks associated with Your exercise of permissions under this License. | ||
| 154 | |||
| 155 | 8. Limitation of Liability. In no event and under no legal theory, | ||
| 156 | whether in tort (including negligence), contract, or otherwise, | ||
| 157 | unless required by applicable law (such as deliberate and grossly | ||
| 158 | negligent acts) or agreed to in writing, shall any Contributor be | ||
| 159 | liable to You for damages, including any direct, indirect, special, | ||
| 160 | incidental, or consequential damages of any character arising as a | ||
| 161 | result of this License or out of the use or inability to use the | ||
| 162 | Work (including but not limited to damages for loss of goodwill, | ||
| 163 | work stoppage, computer failure or malfunction, or any and all | ||
| 164 | other commercial damages or losses), even if such Contributor | ||
| 165 | has been advised of the possibility of such damages. | ||
| 166 | |||
| 167 | 9. Accepting Warranty or Additional Liability. While redistributing | ||
| 168 | the Work or Derivative Works thereof, You may choose to offer, | ||
| 169 | and charge a fee for, acceptance of support, warranty, indemnity, | ||
| 170 | or other liability obligations and/or rights consistent with this | ||
| 171 | License. However, in accepting such obligations, You may act only | ||
| 172 | on Your own behalf and on Your sole responsibility, not on behalf | ||
| 173 | of any other Contributor, and only if You agree to indemnify, | ||
| 174 | defend, and hold each Contributor harmless for any liability | ||
| 175 | incurred by, or claims asserted against, such Contributor by reason | ||
| 176 | of your accepting any such warranty or additional liability. | ||
| 177 | |||
| 178 | END OF TERMS AND CONDITIONS | ||
| 179 | |||
| 180 | APPENDIX: How to apply the Apache License to your work. | ||
| 181 | |||
| 182 | To apply the Apache License to your work, attach the following | ||
| 183 | boilerplate notice, with the fields enclosed by brackets "[]" | ||
| 184 | replaced with your own identifying information. (Don't include | ||
| 185 | the brackets!) The text should be enclosed in the appropriate | ||
| 186 | comment syntax for the file format. We also recommend that a | ||
| 187 | file or class name and description of purpose be included on the | ||
| 188 | same "printed page" as the copyright notice for easier | ||
| 189 | identification within third-party archives. | ||
| 190 | |||
| 191 | Copyright [yyyy] [name of copyright owner] | ||
| 192 | |||
| 193 | Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 194 | you may not use this file except in compliance with the License. | ||
| 195 | You may obtain a copy of the License at | ||
| 196 | |||
| 197 | http://www.apache.org/licenses/LICENSE-2.0 | ||
| 198 | |||
| 199 | Unless required by applicable law or agreed to in writing, software | ||
| 200 | distributed under the License is distributed on an "AS IS" BASIS, | ||
| 201 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 202 | See the License for the specific language governing permissions and | ||
| 203 | limitations under the License. | ||
| 204 | |||
diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..f17ecc9 --- /dev/null +++ b/doc/README | |||
| @@ -0,0 +1,60 @@ | |||
| 1 | ===WebIOPi is a fully integrated Internet of Things framework for the Raspberry Pi.=== | ||
| 2 | Control, debug, and use your Pi's GPIO locally or remotely, from a browser or any app. | ||
| 3 | WebIOPi is the perfect Swiss-knife to make connected things with the Raspberry Pi. | ||
| 4 | Developed and provided by Eric PTAK, french R&D engineer in networks, software, and Internet of Things. | ||
| 5 | |||
| 6 | ===Features=== | ||
| 7 | REST API over HTTP and CoAP (draft-14) with multicast support | ||
| 8 | Server written in Python with zero dependency | ||
| 9 | Supports GPIO, Serial, I2C, SPI, 1-Wire with zero dependency | ||
| 10 | Supports more than [DEVICES 30 devices] including DAC, ADC, sensors... | ||
| 11 | Full Python library for the Server, GPIO, Serial, I2C, SPI and devices drivers | ||
| 12 | Compatible with both Python 2 and 3 | ||
| 13 | Extensible and highly customizable | ||
| 14 | Login/Password protection | ||
| 15 | Mobile devices compatible | ||
| 16 | Includes debug web apps | ||
| 17 | GPIO Header | ||
| 18 | GPIO List | ||
| 19 | Serial Monitor | ||
| 20 | Devices Monitor | ||
| 21 | Javascript client library built on top of jQuery | ||
| 22 | Python client library with HTTP and CoAP support | ||
| 23 | |||
| 24 | ===What for=== | ||
| 25 | Use webiopi.GPIO library to control GPIO in your Python scripts | ||
| 26 | Use devices drivers to interact with DAC, ADC, sensors and more in your Python scripts | ||
| 27 | Use Serial, I2C and SPI webiopi classes in your Python scripts | ||
| 28 | Use the GPIO Header web-app to play with GPIO and debug circuits | ||
| 29 | Use the Serial Monitor web-app to debug Serial devices | ||
| 30 | Use the Devices Monitor web-app to debug devices like DAC, ADC, sensors and more | ||
| 31 | Create a web-app with the Javascript library to remote controls things connected on GPIO | ||
| 32 | Add webiopi in your existing Python script to simply add all WebIOPi features | ||
| 33 | Extends webiopi with your own macro | ||
| 34 | Extends webiopi with scripts loading facilities in a Arduino fashion way (setup, loop, destroy) | ||
| 35 | Use the Python Client to make Pi-2-Pi communication through the REST API | ||
| 36 | Use the Python Multicast Client to control multiple Pis with a single REST call | ||
| 37 | Use the Java Client to use the REST API from a Java application, including Android | ||
| 38 | Use the REST API from any technology/language | ||
| 39 | |||
| 40 | ===Where to start=== | ||
| 41 | [INSTALL Installation] | ||
| 42 | [http://groups.google.com/group/webiopi Support & Discussion] | ||
| 43 | [http://trouch.com Developer's Blog (Eric/trouch)] | ||
| 44 | [http://issuu.com/themagpi/docs/issue9final?mode=window WebRobotCam Part 1 in MagPi #9] by Eric | ||
| 45 | [http://issuu.com/themagpi/docs/issue10final?mode=window WebRobotCam Part 2 in MagPi #10] by Eric | ||
| 46 | [http://www.youtube.com/watch?v=wGahWkjettw Video tutorial] by RaspberryPiBeginners | ||
| 47 | |||
| 48 | <p></p> | ||
| 49 | |||
| 50 | http://trouch.com/wp-content/uploads/2012/11/webiopi-architecture.png | ||
| 51 | |||
| 52 | http://trouch.com/wp-content/uploads/2012/08/webiopi-chrome.png | ||
| 53 | |||
| 54 | http://trouch.com/wp-content/uploads/2013/03/webiopi-0.6-serialmon.png | ||
| 55 | |||
| 56 | http://trouch.com/wp-content/uploads/2013/03/webiopi-0.6-devices1.png | ||
| 57 | |||
| 58 | <p></p> | ||
| 59 | |||
| 60 | <p align="center"><a href="https://www.paypal.com/cgi-bin/webscr?cmd=s-xclick&hostedbuttonid=79EM7ZV9FQ4HU">https://www.paypal.com/enUS/i/btn/btndonateCCLG.gif</a></p> \ No newline at end of file | ||
diff --git a/doc/updatedoc b/doc/updatedoc new file mode 100755 index 0000000..aa0e5bc --- /dev/null +++ b/doc/updatedoc | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | if [ -z "$*" ]; then | ||
| 4 | files=`ls` | ||
| 5 | else | ||
| 6 | files="$*" | ||
| 7 | fi | ||
| 8 | |||
| 9 | for file in $files; do | ||
| 10 | echo "Downloading $file..." | ||
| 11 | wget -q http://webiopi.googlecode.com/svn/wiki/$file.wiki && | ||
| 12 | echo "Processing $file..." && | ||
| 13 | mv $file.wiki $file && | ||
| 14 | sed -i 's/<font color="red">\|<\/font>//g' $file && | ||
| 15 | sed -i 's/<pre>\|<\/pre>//g' $file && | ||
| 16 | sed -i 's/[_*]//g' $file && | ||
| 17 | sed -i '/{{{\|}}}/d' $file && | ||
| 18 | echo "$file OK" || | ||
| 19 | echo "$file not on wiki" | ||
| 20 | done | ||
| 21 | |||
| 22 | echo "Uploading changes on SVN..." | ||
| 23 | svn commit -m "updated doc from wiki" \ No newline at end of file | ||
diff --git a/examples/clients/coap-client.py b/examples/clients/coap-client.py new file mode 100644 index 0000000..eba80e9 --- /dev/null +++ b/examples/clients/coap-client.py | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | from webiopi.protocols.coap import * | ||
| 2 | from time import sleep | ||
| 3 | |||
| 4 | client = COAPClient() | ||
| 5 | client.sendRequest(COAPPost("coap://224.0.1.123/GPIO/25/function/out")) | ||
| 6 | state = True | ||
| 7 | |||
| 8 | while True: | ||
| 9 | response = client.sendRequest(COAPPost("coap://224.0.1.123/GPIO/25/value/%d" % state)) | ||
| 10 | if response: | ||
| 11 | print("Received response:\n%s" % response) | ||
| 12 | state = not state | ||
| 13 | else: | ||
| 14 | print("No response received") | ||
| 15 | sleep(0.5) | ||
diff --git a/examples/clients/webiopi-client.py b/examples/clients/webiopi-client.py new file mode 100644 index 0000000..c277682 --- /dev/null +++ b/examples/clients/webiopi-client.py | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | from webiopi.clients import * | ||
| 2 | from time import sleep | ||
| 3 | |||
| 4 | # Create a WebIOPi client | ||
| 5 | client = PiHttpClient("192.168.1.234") | ||
| 6 | #client = PiMixedClient("192.168.1.234") | ||
| 7 | #client = PiCoapClient("192.168.1.234") | ||
| 8 | #client = PiMulticastClient() | ||
| 9 | |||
| 10 | client.setCredentials("webiopi", "raspberry") | ||
| 11 | |||
| 12 | # RPi native GPIO | ||
| 13 | gpio = NativeGPIO(client) | ||
| 14 | gpio.setFunction(25, "out") | ||
| 15 | state = True | ||
| 16 | |||
| 17 | # DAC named "dac1" | ||
| 18 | dac = DAC(client, "dac1") | ||
| 19 | |||
| 20 | # ADC named "adc1" | ||
| 21 | adc = ADC(client, "adc1") | ||
| 22 | value = 0.0 | ||
| 23 | |||
| 24 | # Temperature sensor named "temp0" | ||
| 25 | temp = Temperature(client, "temp0") | ||
| 26 | |||
| 27 | while True: | ||
| 28 | # toggle digital state | ||
| 29 | state = not state | ||
| 30 | gpio.digitalWrite(25, state) | ||
| 31 | |||
| 32 | # increase analog value | ||
| 33 | value += 0.01 | ||
| 34 | if value > 1.0: | ||
| 35 | value = 0.0 | ||
| 36 | dac.writeFloat(0, value) | ||
| 37 | |||
| 38 | # DAC output 0 is wired to ADC input 1 | ||
| 39 | val = adc.readFloat(1) | ||
| 40 | print("Analog = %.2f" % val) | ||
| 41 | |||
| 42 | # Retrieve temperature | ||
| 43 | t = temp.getCelsius() | ||
| 44 | print("Temperature = %.2f Celsius" % t) | ||
| 45 | |||
| 46 | sleep(1) | ||
diff --git a/examples/magpi-9-cambot/cambot.py b/examples/magpi-9-cambot/cambot.py new file mode 100644 index 0000000..ed2e499 --- /dev/null +++ b/examples/magpi-9-cambot/cambot.py | |||
| @@ -0,0 +1,139 @@ | |||
| 1 | # Imports | ||
| 2 | import webiopi | ||
| 3 | |||
| 4 | # Retrieve GPIO lib | ||
| 5 | GPIO = webiopi.GPIO | ||
| 6 | |||
| 7 | # -------------------------------------------------- # | ||
| 8 | # Constants definition # | ||
| 9 | # -------------------------------------------------- # | ||
| 10 | |||
| 11 | # Left motor GPIOs | ||
| 12 | L1=9 # H-Bridge 1 | ||
| 13 | L2=10 # H-Bridge 2 | ||
| 14 | LS=11 # H-Bridge 1,2EN | ||
| 15 | |||
| 16 | # Right motor GPIOs | ||
| 17 | R1=23 # H-Bridge 3 | ||
| 18 | R2=24 # H-Bridge 4 | ||
| 19 | RS=25 # H-Bridge 3,4EN | ||
| 20 | |||
| 21 | # -------------------------------------------------- # | ||
| 22 | # Convenient PWM Function # | ||
| 23 | # -------------------------------------------------- # | ||
| 24 | |||
| 25 | # Set the speed of two motors | ||
| 26 | def set_speed(speed): | ||
| 27 | GPIO.pulseRatio(LS, speed) | ||
| 28 | GPIO.pulseRatio(RS, speed) | ||
| 29 | |||
| 30 | # -------------------------------------------------- # | ||
| 31 | # Left Motor Functions # | ||
| 32 | # -------------------------------------------------- # | ||
| 33 | |||
| 34 | def left_stop(): | ||
| 35 | GPIO.output(L1, GPIO.LOW) | ||
| 36 | GPIO.output(L2, GPIO.LOW) | ||
| 37 | |||
| 38 | def left_forward(): | ||
| 39 | GPIO.output(L1, GPIO.HIGH) | ||
| 40 | GPIO.output(L2, GPIO.LOW) | ||
| 41 | |||
| 42 | def left_backward(): | ||
| 43 | GPIO.output(L1, GPIO.LOW) | ||
| 44 | GPIO.output(L2, GPIO.HIGH) | ||
| 45 | |||
| 46 | # -------------------------------------------------- # | ||
| 47 | # Right Motor Functions # | ||
| 48 | # -------------------------------------------------- # | ||
| 49 | def right_stop(): | ||
| 50 | GPIO.output(R1, GPIO.LOW) | ||
| 51 | GPIO.output(R2, GPIO.LOW) | ||
| 52 | |||
| 53 | def right_forward(): | ||
| 54 | GPIO.output(R1, GPIO.HIGH) | ||
| 55 | GPIO.output(R2, GPIO.LOW) | ||
| 56 | |||
| 57 | def right_backward(): | ||
| 58 | GPIO.output(R1, GPIO.LOW) | ||
| 59 | GPIO.output(R2, GPIO.HIGH) | ||
| 60 | |||
| 61 | # -------------------------------------------------- # | ||
| 62 | # Macro definition part # | ||
| 63 | # -------------------------------------------------- # | ||
| 64 | |||
| 65 | def go_forward(): | ||
| 66 | left_forward() | ||
| 67 | right_forward() | ||
| 68 | |||
| 69 | def go_backward(): | ||
| 70 | left_backward() | ||
| 71 | right_backward() | ||
| 72 | |||
| 73 | def turn_left(): | ||
| 74 | left_backward() | ||
| 75 | right_forward() | ||
| 76 | |||
| 77 | def turn_right(): | ||
| 78 | left_forward() | ||
| 79 | right_backward() | ||
| 80 | |||
| 81 | def stop(): | ||
| 82 | left_stop() | ||
| 83 | right_stop() | ||
| 84 | |||
| 85 | # -------------------------------------------------- # | ||
| 86 | # Initialization part # | ||
| 87 | # -------------------------------------------------- # | ||
| 88 | |||
| 89 | # Setup GPIOs | ||
| 90 | GPIO.setFunction(LS, GPIO.PWM) | ||
| 91 | GPIO.setFunction(L1, GPIO.OUT) | ||
| 92 | GPIO.setFunction(L2, GPIO.OUT) | ||
| 93 | |||
| 94 | GPIO.setFunction(RS, GPIO.PWM) | ||
| 95 | GPIO.setFunction(R1, GPIO.OUT) | ||
| 96 | GPIO.setFunction(R2, GPIO.OUT) | ||
| 97 | |||
| 98 | set_speed(0.5) | ||
| 99 | stop() | ||
| 100 | |||
| 101 | # -------------------------------------------------- # | ||
| 102 | # Main server part # | ||
| 103 | # -------------------------------------------------- # | ||
| 104 | |||
| 105 | |||
| 106 | # Instantiate the server on the port 8000, it starts immediately in its own thread | ||
| 107 | server = webiopi.Server(port=8000, login="cambot", password="cambot") | ||
| 108 | |||
| 109 | # Register the macros so you can call it with Javascript and/or REST API | ||
| 110 | |||
| 111 | server.addMacro(go_forward) | ||
| 112 | server.addMacro(go_backward) | ||
| 113 | server.addMacro(turn_left) | ||
| 114 | server.addMacro(turn_right) | ||
| 115 | server.addMacro(stop) | ||
| 116 | |||
| 117 | # -------------------------------------------------- # | ||
| 118 | # Loop execution part # | ||
| 119 | # -------------------------------------------------- # | ||
| 120 | |||
| 121 | # Run our loop until CTRL-C is pressed or SIGTERM received | ||
| 122 | webiopi.runLoop() | ||
| 123 | |||
| 124 | # -------------------------------------------------- # | ||
| 125 | # Termination part # | ||
| 126 | # -------------------------------------------------- # | ||
| 127 | |||
| 128 | # Stop the server | ||
| 129 | server.stop() | ||
| 130 | |||
| 131 | # Reset GPIO functions | ||
| 132 | GPIO.setFunction(LS, GPIO.IN) | ||
| 133 | GPIO.setFunction(L1, GPIO.IN) | ||
| 134 | GPIO.setFunction(L2, GPIO.IN) | ||
| 135 | |||
| 136 | GPIO.setFunction(RS, GPIO.IN) | ||
| 137 | GPIO.setFunction(R1, GPIO.IN) | ||
| 138 | GPIO.setFunction(R2, GPIO.IN) | ||
| 139 | |||
diff --git a/examples/magpi-9-cambot/index.html b/examples/magpi-9-cambot/index.html new file mode 100644 index 0000000..db69b41 --- /dev/null +++ b/examples/magpi-9-cambot/index.html | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||
| 5 | <meta name="viewport" content = "height = device-height, width = 420, user-scalable = no" /> | ||
| 6 | <title>CamBot</title> | ||
| 7 | <script type="text/javascript" src="/webiopi.js"></script> | ||
| 8 | <script type="text/javascript"> | ||
| 9 | function init() { | ||
| 10 | var button; | ||
| 11 | |||
| 12 | button = webiopi().createButton("bt_up", "/\\", go_forward, stop); | ||
| 13 | $("#up").append(button); | ||
| 14 | |||
| 15 | button = webiopi().createButton("bt_left", "<", turn_left, stop); | ||
| 16 | $("#middle").append(button); | ||
| 17 | |||
| 18 | button = webiopi().createButton("bt_stop", "X", stop); | ||
| 19 | $("#middle").append(button); | ||
| 20 | |||
| 21 | button = webiopi().createButton("bt_right", ">", turn_right, stop); | ||
| 22 | $("#middle").append(button); | ||
| 23 | |||
| 24 | button = webiopi().createButton("bt_down", "\\/", go_backward, stop); | ||
| 25 | $("#down").append(button); | ||
| 26 | } | ||
| 27 | |||
| 28 | function go_forward() { | ||
| 29 | webiopi().callMacro("go_forward"); | ||
| 30 | } | ||
| 31 | |||
| 32 | function go_backward() { | ||
| 33 | webiopi().callMacro("go_backward"); | ||
| 34 | } | ||
| 35 | |||
| 36 | function turn_right() { | ||
| 37 | webiopi().callMacro("turn_right"); | ||
| 38 | } | ||
| 39 | |||
| 40 | function turn_left() { | ||
| 41 | webiopi().callMacro("turn_left"); | ||
| 42 | } | ||
| 43 | |||
| 44 | function stop() { | ||
| 45 | webiopi().callMacro("stop"); | ||
| 46 | } | ||
| 47 | |||
| 48 | webiopi().ready(init); | ||
| 49 | |||
| 50 | </script> | ||
| 51 | <style type="text/css"> | ||
| 52 | button { | ||
| 53 | margin: 5px 5px 5px 5px; | ||
| 54 | width: 50px; | ||
| 55 | height: 50px; | ||
| 56 | font-size: 24pt; | ||
| 57 | font-weight: bold; | ||
| 58 | color: black; | ||
| 59 | } | ||
| 60 | </style> | ||
| 61 | </head> | ||
| 62 | <body> | ||
| 63 | <div id="content" align="center"> | ||
| 64 | <img width="320" height="240" src="http://raspberrypi:8001/?action=stream"><br/> | ||
| 65 | <div id="up"></div> | ||
| 66 | <div id="middle"></div> | ||
| 67 | <div id="down"></div> | ||
| 68 | </div> | ||
| 69 | </body> | ||
| 70 | </html> | ||
diff --git a/examples/magpi-9-cambot/stream.sh b/examples/magpi-9-cambot/stream.sh new file mode 100755 index 0000000..12d6579 --- /dev/null +++ b/examples/magpi-9-cambot/stream.sh | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | STREAMER=mjpg_streamer | ||
| 4 | DEVICE=/dev/video0 | ||
| 5 | RESOLUTION=320x240 | ||
| 6 | FRAMERATE=25 | ||
| 7 | HTTP_PORT=8001 | ||
| 8 | |||
| 9 | PLUGINPATH=/usr/local/lib | ||
| 10 | |||
| 11 | $STREAMER -i "$PLUGINPATH/input_uvc.so -n -d $DEVICE -r $RESOLUTION -f $FRAMERATE" -o "$PLUGINPATH/output_http.so -n -p $HTTP_PORT" | ||
diff --git a/examples/magpi-9-cambot2/cambot.py b/examples/magpi-9-cambot2/cambot.py new file mode 100644 index 0000000..f1013ab --- /dev/null +++ b/examples/magpi-9-cambot2/cambot.py | |||
| @@ -0,0 +1,117 @@ | |||
| 1 | # This version uses new-style automatic setup/destroy/mapping | ||
| 2 | # Need to change /etc/webiopi | ||
| 3 | |||
| 4 | # Imports | ||
| 5 | import webiopi | ||
| 6 | |||
| 7 | # Retrieve GPIO lib | ||
| 8 | GPIO = webiopi.GPIO | ||
| 9 | |||
| 10 | # -------------------------------------------------- # | ||
| 11 | # Constants definition # | ||
| 12 | # -------------------------------------------------- # | ||
| 13 | |||
| 14 | # Left motor GPIOs | ||
| 15 | L1=17 # H-Bridge 1 | ||
| 16 | L2=18 # H-Bridge 2 | ||
| 17 | LS=21 # H-Bridge 1,2EN | ||
| 18 | |||
| 19 | # Right motor GPIOs | ||
| 20 | R1=23 # H-Bridge 3 | ||
| 21 | R2=24 # H-Bridge 4 | ||
| 22 | RS=25 # H-Bridge 3,4EN | ||
| 23 | |||
| 24 | # -------------------------------------------------- # | ||
| 25 | # Convenient PWM Function # | ||
| 26 | # -------------------------------------------------- # | ||
| 27 | |||
| 28 | # Set the speed of two motors | ||
| 29 | def set_speed(speed): | ||
| 30 | GPIO.pulseRatio(LS, speed) | ||
| 31 | GPIO.pulseRatio(RS, speed) | ||
| 32 | |||
| 33 | # -------------------------------------------------- # | ||
| 34 | # Left Motor Functions # | ||
| 35 | # -------------------------------------------------- # | ||
| 36 | |||
| 37 | def left_stop(): | ||
| 38 | GPIO.output(L1, GPIO.LOW) | ||
| 39 | GPIO.output(L2, GPIO.LOW) | ||
| 40 | |||
| 41 | def left_forward(): | ||
| 42 | GPIO.output(L1, GPIO.HIGH) | ||
| 43 | GPIO.output(L2, GPIO.LOW) | ||
| 44 | |||
| 45 | def left_backward(): | ||
| 46 | GPIO.output(L1, GPIO.LOW) | ||
| 47 | GPIO.output(L2, GPIO.HIGH) | ||
| 48 | |||
| 49 | # -------------------------------------------------- # | ||
| 50 | # Right Motor Functions # | ||
| 51 | # -------------------------------------------------- # | ||
| 52 | def right_stop(): | ||
| 53 | GPIO.output(R1, GPIO.LOW) | ||
| 54 | GPIO.output(R2, GPIO.LOW) | ||
| 55 | |||
| 56 | def right_forward(): | ||
| 57 | GPIO.output(R1, GPIO.HIGH) | ||
| 58 | GPIO.output(R2, GPIO.LOW) | ||
| 59 | |||
| 60 | def right_backward(): | ||
| 61 | GPIO.output(R1, GPIO.LOW) | ||
| 62 | GPIO.output(R2, GPIO.HIGH) | ||
| 63 | |||
| 64 | # -------------------------------------------------- # | ||
| 65 | # Macro definition part # | ||
| 66 | # -------------------------------------------------- # | ||
| 67 | @webiopi.macro | ||
| 68 | def go_forward(): | ||
| 69 | left_forward() | ||
| 70 | right_forward() | ||
| 71 | |||
| 72 | @webiopi.macro | ||
| 73 | def go_backward(): | ||
| 74 | left_backward() | ||
| 75 | right_backward() | ||
| 76 | |||
| 77 | @webiopi.macro | ||
| 78 | def turn_left(): | ||
| 79 | left_backward() | ||
| 80 | right_forward() | ||
| 81 | |||
| 82 | @webiopi.macro | ||
| 83 | def turn_right(): | ||
| 84 | left_forward() | ||
| 85 | right_backward() | ||
| 86 | |||
| 87 | @webiopi.macro | ||
| 88 | def stop(): | ||
| 89 | left_stop() | ||
| 90 | right_stop() | ||
| 91 | |||
| 92 | # Called by WebIOPi at script loading | ||
| 93 | def setup(): | ||
| 94 | # Setup GPIOs | ||
| 95 | GPIO.setFunction(LS, GPIO.PWM) | ||
| 96 | GPIO.setFunction(L1, GPIO.OUT) | ||
| 97 | GPIO.setFunction(L2, GPIO.OUT) | ||
| 98 | |||
| 99 | GPIO.setFunction(RS, GPIO.PWM) | ||
| 100 | GPIO.setFunction(R1, GPIO.OUT) | ||
| 101 | GPIO.setFunction(R2, GPIO.OUT) | ||
| 102 | |||
| 103 | set_speed(0.5) | ||
| 104 | stop() | ||
| 105 | |||
| 106 | |||
| 107 | # Called by WebIOPi at server shutdown | ||
| 108 | def destroy(): | ||
| 109 | # Reset GPIO functions | ||
| 110 | GPIO.setFunction(LS, GPIO.IN) | ||
| 111 | GPIO.setFunction(L1, GPIO.IN) | ||
| 112 | GPIO.setFunction(L2, GPIO.IN) | ||
| 113 | |||
| 114 | GPIO.setFunction(RS, GPIO.IN) | ||
| 115 | GPIO.setFunction(R1, GPIO.IN) | ||
| 116 | GPIO.setFunction(R2, GPIO.IN) | ||
| 117 | |||
diff --git a/examples/magpi-9-cambot2/index.html b/examples/magpi-9-cambot2/index.html new file mode 100644 index 0000000..db69b41 --- /dev/null +++ b/examples/magpi-9-cambot2/index.html | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||
| 5 | <meta name="viewport" content = "height = device-height, width = 420, user-scalable = no" /> | ||
| 6 | <title>CamBot</title> | ||
| 7 | <script type="text/javascript" src="/webiopi.js"></script> | ||
| 8 | <script type="text/javascript"> | ||
| 9 | function init() { | ||
| 10 | var button; | ||
| 11 | |||
| 12 | button = webiopi().createButton("bt_up", "/\\", go_forward, stop); | ||
| 13 | $("#up").append(button); | ||
| 14 | |||
| 15 | button = webiopi().createButton("bt_left", "<", turn_left, stop); | ||
| 16 | $("#middle").append(button); | ||
| 17 | |||
| 18 | button = webiopi().createButton("bt_stop", "X", stop); | ||
| 19 | $("#middle").append(button); | ||
| 20 | |||
| 21 | button = webiopi().createButton("bt_right", ">", turn_right, stop); | ||
| 22 | $("#middle").append(button); | ||
| 23 | |||
| 24 | button = webiopi().createButton("bt_down", "\\/", go_backward, stop); | ||
| 25 | $("#down").append(button); | ||
| 26 | } | ||
| 27 | |||
| 28 | function go_forward() { | ||
| 29 | webiopi().callMacro("go_forward"); | ||
| 30 | } | ||
| 31 | |||
| 32 | function go_backward() { | ||
| 33 | webiopi().callMacro("go_backward"); | ||
| 34 | } | ||
| 35 | |||
| 36 | function turn_right() { | ||
| 37 | webiopi().callMacro("turn_right"); | ||
| 38 | } | ||
| 39 | |||
| 40 | function turn_left() { | ||
| 41 | webiopi().callMacro("turn_left"); | ||
| 42 | } | ||
| 43 | |||
| 44 | function stop() { | ||
| 45 | webiopi().callMacro("stop"); | ||
| 46 | } | ||
| 47 | |||
| 48 | webiopi().ready(init); | ||
| 49 | |||
| 50 | </script> | ||
| 51 | <style type="text/css"> | ||
| 52 | button { | ||
| 53 | margin: 5px 5px 5px 5px; | ||
| 54 | width: 50px; | ||
| 55 | height: 50px; | ||
| 56 | font-size: 24pt; | ||
| 57 | font-weight: bold; | ||
| 58 | color: black; | ||
| 59 | } | ||
| 60 | </style> | ||
| 61 | </head> | ||
| 62 | <body> | ||
| 63 | <div id="content" align="center"> | ||
| 64 | <img width="320" height="240" src="http://raspberrypi:8001/?action=stream"><br/> | ||
| 65 | <div id="up"></div> | ||
| 66 | <div id="middle"></div> | ||
| 67 | <div id="down"></div> | ||
| 68 | </div> | ||
| 69 | </body> | ||
| 70 | </html> | ||
diff --git a/examples/magpi-9-cambot2/stream.sh b/examples/magpi-9-cambot2/stream.sh new file mode 100755 index 0000000..12d6579 --- /dev/null +++ b/examples/magpi-9-cambot2/stream.sh | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | |||
| 3 | STREAMER=mjpg_streamer | ||
| 4 | DEVICE=/dev/video0 | ||
| 5 | RESOLUTION=320x240 | ||
| 6 | FRAMERATE=25 | ||
| 7 | HTTP_PORT=8001 | ||
| 8 | |||
| 9 | PLUGINPATH=/usr/local/lib | ||
| 10 | |||
| 11 | $STREAMER -i "$PLUGINPATH/input_uvc.so -n -d $DEVICE -r $RESOLUTION -f $FRAMERATE" -o "$PLUGINPATH/output_http.so -n -p $HTTP_PORT" | ||
diff --git a/examples/scripts/basic/script.py b/examples/scripts/basic/script.py new file mode 100644 index 0000000..5750a69 --- /dev/null +++ b/examples/scripts/basic/script.py | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | # Imports | ||
| 2 | import webiopi | ||
| 3 | |||
| 4 | # Enable debug output | ||
| 5 | webiopi.setDebug() | ||
| 6 | |||
| 7 | # Retrieve GPIO lib | ||
| 8 | GPIO = webiopi.GPIO | ||
| 9 | SWITCH = 21 | ||
| 10 | SERVO = 23 | ||
| 11 | LED0 = 24 | ||
| 12 | LED1 = 25 | ||
| 13 | |||
| 14 | # Called by WebIOPi at script loading | ||
| 15 | def setup(): | ||
| 16 | webiopi.debug("Basic script - Setup") | ||
| 17 | # Setup GPIOs | ||
| 18 | GPIO.setFunction(SWITCH, GPIO.IN) | ||
| 19 | GPIO.setFunction(SERVO, GPIO.PWM) | ||
| 20 | GPIO.setFunction(LED0, GPIO.PWM) | ||
| 21 | GPIO.setFunction(LED1, GPIO.OUT) | ||
| 22 | |||
| 23 | GPIO.pwmWrite(LED0, 0.5) # set to 50% ratio | ||
| 24 | GPIO.pwmWriteAngle(SERVO, 0) # set to 0 (neutral) | ||
| 25 | GPIO.digitalWrite(LED1, GPIO.HIGH) | ||
| 26 | |||
| 27 | # Called by WebIOPi at server shutdown | ||
| 28 | def destroy(): | ||
| 29 | webiopi.debug("Basic script - Destroy") | ||
| 30 | # Reset GPIO functions | ||
| 31 | GPIO.setFunction(SWITCH, GPIO.IN) | ||
| 32 | GPIO.setFunction(SERVO, GPIO.IN) | ||
| 33 | GPIO.setFunction(LED0, GPIO.IN) | ||
| 34 | GPIO.setFunction(LED1, GPIO.IN) | ||
diff --git a/examples/scripts/blink/script.py b/examples/scripts/blink/script.py new file mode 100644 index 0000000..90c6c8c --- /dev/null +++ b/examples/scripts/blink/script.py | |||
| @@ -0,0 +1,41 @@ | |||
| 1 | # Imports | ||
| 2 | import webiopi | ||
| 3 | |||
| 4 | # Enable debug output | ||
| 5 | webiopi.setDebug() | ||
| 6 | |||
| 7 | # Retrieve GPIO lib | ||
| 8 | GPIO = webiopi.GPIO | ||
| 9 | SWITCH = 21 | ||
| 10 | SERVO = 23 | ||
| 11 | LED0 = 24 | ||
| 12 | LED1 = 25 | ||
| 13 | |||
| 14 | # Called by WebIOPi at script loading | ||
| 15 | def setup(): | ||
| 16 | webiopi.debug("Blink script - Setup") | ||
| 17 | # Setup GPIOs | ||
| 18 | GPIO.setFunction(SWITCH, GPIO.IN) | ||
| 19 | GPIO.setFunction(SERVO, GPIO.PWM) | ||
| 20 | GPIO.setFunction(LED0, GPIO.PWM) | ||
| 21 | GPIO.setFunction(LED1, GPIO.OUT) | ||
| 22 | |||
| 23 | GPIO.pwmWrite(LED0, 0.5) # set to 50% ratio | ||
| 24 | GPIO.pwmWriteAngle(SERVO, 0) # set to 0 (neutral) | ||
| 25 | GPIO.digitalWrite(LED1, GPIO.HIGH) | ||
| 26 | |||
| 27 | # Looped by WebIOPi | ||
| 28 | def loop(): | ||
| 29 | # Toggle LED each 5 seconds | ||
| 30 | value = not GPIO.digitalRead(LED1) | ||
| 31 | GPIO.digitalWrite(LED1, value) | ||
| 32 | webiopi.sleep(5) | ||
| 33 | |||
| 34 | # Called by WebIOPi at server shutdown | ||
| 35 | def destroy(): | ||
| 36 | webiopi.debug("Blink script - Destroy") | ||
| 37 | # Reset GPIO functions | ||
| 38 | GPIO.setFunction(SWITCH, GPIO.IN) | ||
| 39 | GPIO.setFunction(SERVO, GPIO.IN) | ||
| 40 | GPIO.setFunction(LED0, GPIO.IN) | ||
| 41 | GPIO.setFunction(LED1, GPIO.IN) | ||
diff --git a/examples/scripts/macros/index.html b/examples/scripts/macros/index.html new file mode 100644 index 0000000..2dc118e --- /dev/null +++ b/examples/scripts/macros/index.html | |||
| @@ -0,0 +1,120 @@ | |||
| 1 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||
| 5 | <meta name="viewport" content = "height = device-height, width = 420, user-scalable = no" /> | ||
| 6 | <title>WebIOPi | Demo</title> | ||
| 7 | <script type="text/javascript" src="/webiopi.js"></script> | ||
| 8 | <script type="text/javascript"> | ||
| 9 | webiopi().ready(function() { | ||
| 10 | var content, button; | ||
| 11 | content = $("#content"); | ||
| 12 | |||
| 13 | // create a "SWITCH" labeled button for GPIO 21 | ||
| 14 | button = webiopi().createGPIOButton(21, "SWITCH"); | ||
| 15 | content.append(button); // append button to content div | ||
| 16 | |||
| 17 | // create a "LED" labeled button for GPIO 25 | ||
| 18 | button = webiopi().createGPIOButton(25, "LED1"); | ||
| 19 | content.append(button); // append button to content div | ||
| 20 | |||
| 21 | // create a button that output a single pulse | ||
| 22 | button = webiopi().createPulseButton("pulse", "Pulse", 25); | ||
| 23 | content.append(button); // append button to content div | ||
| 24 | |||
| 25 | // create a button which output a bit sequence on GPIO 25 with a 100ms period | ||
| 26 | button = webiopi().createSequenceButton("sos", "S.O.S 1", 25, 100, "01010100110011001100101010"); | ||
| 27 | content.append(button); // append button to content div | ||
| 28 | |||
| 29 | // the previous button will always output the same sequence | ||
| 30 | // you can also create a simple button with your own function | ||
| 31 | button = webiopi().createButton("sos2", "S.O.S 2", outputSequence); | ||
| 32 | content.append(button); // append button to content div | ||
| 33 | |||
| 34 | // create a button which call PrintTime | ||
| 35 | button = webiopi().createMacroButton("macro", "Print Time", "PrintTime"); | ||
| 36 | content.append(button); // append button to content div | ||
| 37 | |||
| 38 | // create a button which call HelloWorld with "User,Name" argument | ||
| 39 | button = webiopi().createMacroButton("macro", "Hello ?", "HelloWorld", ["User", "Name"]); | ||
| 40 | content.append(button); // append button to content div | ||
| 41 | |||
| 42 | // the previous button will always call HelloWorld with "User,Name" argument | ||
| 43 | // you can also create a simple button with your own function | ||
| 44 | button = webiopi().createButton("macro2", "Hello !", callMacro); | ||
| 45 | content.append(button); // append button to content div | ||
| 46 | |||
| 47 | // you can also create a button which calls a different function for mouse down and up events | ||
| 48 | button = webiopi().createButton("hold", "Hold", mousedown, mouseup); | ||
| 49 | content.append(button); | ||
| 50 | |||
| 51 | // Only for Chrome and Safari, create a slider that pulse out a -45 to +45° angle on GPIO 23 | ||
| 52 | button = webiopi().createAngleSlider(23); | ||
| 53 | content.append(button); | ||
| 54 | |||
| 55 | // Only for Chrome and Safari, create a slider that pulse out a 0-100% duty cycle ratio on GPIO 24 | ||
| 56 | button = webiopi().createRatioSlider(24); | ||
| 57 | content.append(button); | ||
| 58 | |||
| 59 | webiopi().refreshGPIO(true); | ||
| 60 | }); | ||
| 61 | |||
| 62 | function mousedown() { | ||
| 63 | webiopi().digitalWrite(25, 1); | ||
| 64 | } | ||
| 65 | |||
| 66 | function mouseup() { | ||
| 67 | webiopi().digitalWrite(25, 0); | ||
| 68 | } | ||
| 69 | |||
| 70 | function outputSequence() { | ||
| 71 | var sequence = "01010100110011001100101010" // S.O.S. morse code or whatever you want | ||
| 72 | // output sequence on gpio 25 with a 100ms period | ||
| 73 | webiopi().outputSequence(25, 100, sequence, sequenceCallback); | ||
| 74 | } | ||
| 75 | |||
| 76 | function sequenceCallback(gpio, data) { | ||
| 77 | alert("sequence on " + gpio + " finished with " + data); | ||
| 78 | } | ||
| 79 | |||
| 80 | function callMacro() { | ||
| 81 | var args = ["User","Name"] // or whatever you want | ||
| 82 | // call HelloWorld(args) | ||
| 83 | webiopi().callMacro("HelloWorld", args, macroCallback); | ||
| 84 | } | ||
| 85 | |||
| 86 | function macroCallback(macro, args, data) { | ||
| 87 | alert(data); | ||
| 88 | } | ||
| 89 | |||
| 90 | </script> | ||
| 91 | <style type="text/css"> | ||
| 92 | button { | ||
| 93 | display: block; | ||
| 94 | margin: 5px 5px 5px 5px; | ||
| 95 | width: 160px; | ||
| 96 | height: 45px; | ||
| 97 | font-size: 24pt; | ||
| 98 | font-weight: bold; | ||
| 99 | color: black; | ||
| 100 | } | ||
| 101 | |||
| 102 | input[type="range"] { | ||
| 103 | display: block; | ||
| 104 | width: 160px; | ||
| 105 | height: 45px; | ||
| 106 | } | ||
| 107 | |||
| 108 | .LOW { | ||
| 109 | background-color: White; | ||
| 110 | } | ||
| 111 | |||
| 112 | .HIGH { | ||
| 113 | background-color: Red; | ||
| 114 | } | ||
| 115 | </style> | ||
| 116 | </head> | ||
| 117 | <body> | ||
| 118 | <div id="content" align="center"></div> | ||
| 119 | </body> | ||
| 120 | </html> | ||
diff --git a/examples/scripts/macros/script.py b/examples/scripts/macros/script.py new file mode 100644 index 0000000..a469b1a --- /dev/null +++ b/examples/scripts/macros/script.py | |||
| @@ -0,0 +1,60 @@ | |||
| 1 | # Imports | ||
| 2 | import webiopi | ||
| 3 | import time | ||
| 4 | |||
| 5 | # Enable debug output | ||
| 6 | webiopi.setDebug() | ||
| 7 | |||
| 8 | # Retrieve GPIO lib | ||
| 9 | GPIO = webiopi.GPIO | ||
| 10 | |||
| 11 | SWITCH = 21 | ||
| 12 | SERVO = 23 | ||
| 13 | LED0 = 24 | ||
| 14 | LED1 = 25 | ||
| 15 | |||
| 16 | # Called by WebIOPi at script loading | ||
| 17 | def setup(): | ||
| 18 | webiopi.debug("Script with macros - Setup") | ||
| 19 | # Setup GPIOs | ||
| 20 | GPIO.setFunction(SWITCH, GPIO.IN) | ||
| 21 | GPIO.setFunction(SERVO, GPIO.PWM) | ||
| 22 | GPIO.setFunction(LED0, GPIO.PWM) | ||
| 23 | GPIO.setFunction(LED1, GPIO.OUT) | ||
| 24 | |||
| 25 | GPIO.pwmWrite(LED0, 0.5) # set to 50% ratio | ||
| 26 | GPIO.pwmWriteAngle(SERVO, 0) # set to 0 (neutral) | ||
| 27 | GPIO.digitalWrite(LED1, GPIO.HIGH) | ||
| 28 | |||
| 29 | gpio0 = webiopi.deviceInstance("gpio0") | ||
| 30 | gpio0.digitalWrite(0, 0) | ||
| 31 | |||
| 32 | # Looped by WebIOPi | ||
| 33 | def loop(): | ||
| 34 | # Toggle LED each 5 seconds | ||
| 35 | value = not GPIO.digitalRead(LED1) | ||
| 36 | GPIO.digitalWrite(LED1, value) | ||
| 37 | webiopi.sleep(5) | ||
| 38 | |||
| 39 | # Called by WebIOPi at server shutdown | ||
| 40 | def destroy(): | ||
| 41 | webiopi.debug("Script with macros - Destroy") | ||
| 42 | # Reset GPIO functions | ||
| 43 | GPIO.setFunction(SWITCH, GPIO.IN) | ||
| 44 | GPIO.setFunction(SERVO, GPIO.IN) | ||
| 45 | GPIO.setFunction(LED0, GPIO.IN) | ||
| 46 | GPIO.setFunction(LED1, GPIO.IN) | ||
| 47 | gpio0 = webiopi.deviceInstance("gpio0") | ||
| 48 | gpio0.digitalWrite(0, 1) | ||
| 49 | |||
| 50 | # A macro which says hello | ||
| 51 | @webiopi.macro | ||
| 52 | def HelloWorld(first, last): | ||
| 53 | webiopi.debug("HelloWorld(%s, %s)" % (first, last)) | ||
| 54 | return "Hello %s %s !" % (first, last) | ||
| 55 | |||
| 56 | # A macro without args which return nothing | ||
| 57 | @webiopi.macro | ||
| 58 | def PrintTime(): | ||
| 59 | webiopi.debug("PrintTime: " + time.asctime()) | ||
| 60 | |||
diff --git a/examples/scripts/simple/index.html b/examples/scripts/simple/index.html new file mode 100644 index 0000000..516ecbe --- /dev/null +++ b/examples/scripts/simple/index.html | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||
| 5 | <meta name="viewport" content = "height = device-height, width = 420, user-scalable = no" /> | ||
| 6 | <title>WebIOPi | Demo</title> | ||
| 7 | <script type="text/javascript" src="/webiopi.js"></script> | ||
| 8 | <script type="text/javascript"> | ||
| 9 | webiopi().ready(function() { | ||
| 10 | webiopi().setFunction(25, "out"); | ||
| 11 | |||
| 12 | var content, button; | ||
| 13 | content = $("#content"); | ||
| 14 | |||
| 15 | // create a "LED" labeled button for GPIO 25 | ||
| 16 | button = webiopi().createGPIOButton(25, "LED1"); | ||
| 17 | content.append(button); // append button to content div | ||
| 18 | |||
| 19 | // create a button that output a single pulse | ||
| 20 | button = webiopi().createPulseButton("pulse", "Pulse", 25); | ||
| 21 | content.append(button); // append button to content div | ||
| 22 | |||
| 23 | // you can also create a button which calls a different function for mouse down and up events | ||
| 24 | button = webiopi().createButton("hold", "Hold", mousedown, mouseup); | ||
| 25 | content.append(button); | ||
| 26 | |||
| 27 | webiopi().refreshGPIO(true); | ||
| 28 | }); | ||
| 29 | |||
| 30 | function mousedown() { | ||
| 31 | webiopi().digitalWrite(25, 1); | ||
| 32 | } | ||
| 33 | |||
| 34 | function mouseup() { | ||
| 35 | webiopi().digitalWrite(25, 0); | ||
| 36 | } | ||
| 37 | |||
| 38 | </script> | ||
| 39 | <style type="text/css"> | ||
| 40 | button { | ||
| 41 | display: block; | ||
| 42 | margin: 5px 5px 5px 5px; | ||
| 43 | width: 160px; | ||
| 44 | height: 45px; | ||
| 45 | font-size: 24pt; | ||
| 46 | font-weight: bold; | ||
| 47 | color: black; | ||
| 48 | } | ||
| 49 | |||
| 50 | input[type="range"] { | ||
| 51 | display: block; | ||
| 52 | width: 160px; | ||
| 53 | height: 45px; | ||
| 54 | } | ||
| 55 | |||
| 56 | .LOW { | ||
| 57 | background-color: White; | ||
| 58 | } | ||
| 59 | |||
| 60 | .HIGH { | ||
| 61 | background-color: Red; | ||
| 62 | } | ||
| 63 | </style> | ||
| 64 | </head> | ||
| 65 | <body> | ||
| 66 | <div id="content" align="center"></div> | ||
| 67 | </body> | ||
| 68 | </html> | ||
diff --git a/htdocs/app/devices-monitor/index.html b/htdocs/app/devices-monitor/index.html new file mode 100644 index 0000000..6e969dc --- /dev/null +++ b/htdocs/app/devices-monitor/index.html | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||
| 5 | <meta name="viewport" content = "height = device-height, width = 420, user-scalable = no" /> | ||
| 6 | <title>WebIOPi | Devices Monitor</title> | ||
| 7 | <script type="text/javascript" src="/webiopi.js"></script> | ||
| 8 | <script type="text/javascript"> | ||
| 9 | webiopi().ready(function() { | ||
| 10 | $.get("/devices/*", function(data) { | ||
| 11 | var content = $("#content"); | ||
| 12 | for (i in data) { | ||
| 13 | if (data[i].type != "Serial") { | ||
| 14 | var device = webiopi().newDevice(data[i].type, data[i].name); | ||
| 15 | if (device) { | ||
| 16 | device.element = $("<div>"); | ||
| 17 | content.append(device.element); | ||
| 18 | device.refreshUI(); | ||
| 19 | } | ||
| 20 | } | ||
| 21 | } | ||
| 22 | }); | ||
| 23 | }); | ||
| 24 | </script> | ||
| 25 | <style type="text/css"> | ||
| 26 | button, .FunctionBasic { | ||
| 27 | width: 50px; | ||
| 28 | } | ||
| 29 | </style> | ||
| 30 | </head> | ||
| 31 | <body> | ||
| 32 | <h1>Devices Monitor</h1> | ||
| 33 | <div id="content"> | ||
| 34 | </div> | ||
| 35 | </body> | ||
| 36 | </html> | ||
diff --git a/htdocs/app/gpio-header/index.html b/htdocs/app/gpio-header/index.html new file mode 100644 index 0000000..0634b1e --- /dev/null +++ b/htdocs/app/gpio-header/index.html | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||
| 5 | <meta name="viewport" content = "height = device-height, width = 420, user-scalable = no" /> | ||
| 6 | <title>WebIOPi | GPIO Header</title> | ||
| 7 | <script type="text/javascript" src="/webiopi.js"></script> | ||
| 8 | <script type="text/javascript"> | ||
| 9 | webiopi().ready(function() { | ||
| 10 | webiopi().RPiHeader().createTable("content"); | ||
| 11 | w().refreshGPIO(true); | ||
| 12 | }); | ||
| 13 | </script> | ||
| 14 | </head> | ||
| 15 | <body> | ||
| 16 | <div id="content" align="center"></div> | ||
| 17 | </body> | ||
| 18 | </html> | ||
diff --git a/htdocs/app/gpio-list/index.html b/htdocs/app/gpio-list/index.html new file mode 100644 index 0000000..6c0cf1a --- /dev/null +++ b/htdocs/app/gpio-list/index.html | |||
| @@ -0,0 +1,28 @@ | |||
| 1 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||
| 5 | <meta name="viewport" content = "height = device-height, width = 420, user-scalable = no" /> | ||
| 6 | <title>WebIOPi | GPIO List</title> | ||
| 7 | <script type="text/javascript" src="/webiopi.js"></script> | ||
| 8 | <script type="text/javascript"> | ||
| 9 | webiopi().ready(function() { | ||
| 10 | webiopi().Expert().createList("content"); | ||
| 11 | w().refreshGPIO(true); | ||
| 12 | }); | ||
| 13 | </script> | ||
| 14 | <style type="text/css"> | ||
| 15 | button { | ||
| 16 | margin: 2px 2px 2px 2px; | ||
| 17 | width: 50px; | ||
| 18 | } | ||
| 19 | |||
| 20 | .Description { | ||
| 21 | display: inline; | ||
| 22 | } | ||
| 23 | </style> | ||
| 24 | </head> | ||
| 25 | <body> | ||
| 26 | <div id="content"></div> | ||
| 27 | </body> | ||
| 28 | </html> | ||
diff --git a/htdocs/app/serial-monitor/index.html b/htdocs/app/serial-monitor/index.html new file mode 100644 index 0000000..cdc8524 --- /dev/null +++ b/htdocs/app/serial-monitor/index.html | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||
| 5 | <meta name="viewport" content = "height = device-height, width = 420, user-scalable = no" /> | ||
| 6 | <title>WebIOPi | Serial Monitor</title> | ||
| 7 | <script type="text/javascript" src="/webiopi.js"></script> | ||
| 8 | <script type="text/javascript"> | ||
| 9 | webiopi().ready(function() { | ||
| 10 | $("#inputText").keyup(function(event){ | ||
| 11 | if(event.keyCode == 13){ | ||
| 12 | sendData(); | ||
| 13 | } | ||
| 14 | }); | ||
| 15 | $.get("/devices/*", function(data) { | ||
| 16 | var devices = $("#devices"); | ||
| 17 | var added = false; | ||
| 18 | for (i in data) { | ||
| 19 | if (data[i].type=="Serial") { | ||
| 20 | added = true; | ||
| 21 | devices.append($("<option>" + data[i].name + "</option>")) | ||
| 22 | } | ||
| 23 | } | ||
| 24 | if (added) { | ||
| 25 | readData(); | ||
| 26 | } | ||
| 27 | }); | ||
| 28 | }); | ||
| 29 | |||
| 30 | function readData() { | ||
| 31 | webiopi().Serial($("#devices").val()).read(function(data) { | ||
| 32 | if (data.length > 0) { | ||
| 33 | var d = $("#output").text() + data; | ||
| 34 | $("#output").text(d); | ||
| 35 | } | ||
| 36 | }); | ||
| 37 | setTimeout(readData, 500); | ||
| 38 | } | ||
| 39 | |||
| 40 | function sendData() { | ||
| 41 | var data = $("#inputText").val() + "\n"; | ||
| 42 | webiopi().Serial($("#devices").val()).write(data); | ||
| 43 | $("#inputText").val(""); | ||
| 44 | } | ||
| 45 | |||
| 46 | function deviceChanged() { | ||
| 47 | $("#output").text(""); | ||
| 48 | } | ||
| 49 | |||
| 50 | </script> | ||
| 51 | <style type="text/css"> | ||
| 52 | #inputText { | ||
| 53 | width: 550px; | ||
| 54 | } | ||
| 55 | </style> | ||
| 56 | </head> | ||
| 57 | <body> | ||
| 58 | <h1>Serial Monitor</h1> | ||
| 59 | <span>Serial device : </span><select id="devices" onchange="deviceChanged()"></select><br/> | ||
| 60 | <span>Input : </span><br/> | ||
| 61 | <textarea id="output" rows="30" cols="100" disabled="disabled"></textarea><br/> | ||
| 62 | <span>Output : </span><input id="inputText" type="text"/> | ||
| 63 | </body> | ||
| 64 | </html> | ||
diff --git a/htdocs/index.html b/htdocs/index.html new file mode 100644 index 0000000..68c49c9 --- /dev/null +++ b/htdocs/index.html | |||
| @@ -0,0 +1,25 @@ | |||
| 1 | <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> | ||
| 2 | <html> | ||
| 3 | <head> | ||
| 4 | <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | ||
| 5 | <meta name="viewport" content = "height = device-height, width = 420, user-scalable = no" /> | ||
| 6 | <title>WebIOPi | Raspberry Pi IoT Framework</title> | ||
| 7 | </head> | ||
| 8 | <body> | ||
| 9 | |||
| 10 | <h1>WebIOPi Main Menu</h1> | ||
| 11 | |||
| 12 | <h2><a href="/app/gpio-header">GPIO Header</a></h2> | ||
| 13 | <p>Control and Debug the Raspberry Pi GPIO with a display which looks like the physical header.</p> | ||
| 14 | |||
| 15 | <h2><a href="/app/gpio-list">GPIO List</a></h2> | ||
| 16 | <p>Control and Debug the Raspberry Pi GPIO ordered in a single column.</p> | ||
| 17 | |||
| 18 | <h2><a href="/app/serial-monitor">Serial Monitor</a></h2> | ||
| 19 | <p>Use the browser to play with Serial interfaces configured in WebIOPi.</p> | ||
| 20 | |||
| 21 | <h2><a href="/app/devices-monitor">Devices Monitor</a></h2> | ||
| 22 | <p>Control and Debug devices and circuits wired to your Pi and configured in WebIOPi.</p> | ||
| 23 | |||
| 24 | </body> | ||
| 25 | </html> | ||
diff --git a/htdocs/jquery-mobile.css b/htdocs/jquery-mobile.css new file mode 100644 index 0000000..3a52bf0 --- /dev/null +++ b/htdocs/jquery-mobile.css | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | /*! jQuery Mobile vGit Build: SHA1: b49cc06499abf8f987cf90f35349cfac0918c939 <> Date: Tue Oct 2 11:22:34 2012 -0700 jquerymobile.com | jquery.org/license !*/ | ||
| 2 | .ui-mobile,.ui-mobile body{height:99.9%}.ui-mobile fieldset,.ui-page{padding:0;margin:0}.ui-mobile a img,.ui-mobile fieldset{border-width:0}.ui-mobile-viewport{margin:0;overflow-x:visible;-webkit-text-size-adjust:100%;-ms-text-size-adjust:none;-webkit-tap-highlight-color:rgba(0,0,0,0)}body.ui-mobile-viewport,div.ui-mobile-viewport{overflow-x:hidden}.ui-mobile [data-role=page],.ui-mobile [data-role=dialog],.ui-page{top:0;left:0;width:100%;min-height:100%;position:absolute;display:none;border:0}.ui-mobile .ui-page-active{display:block;overflow:visible}.ui-page{outline:none}@media screen and (orientation:portrait){.ui-mobile,.ui-mobile .ui-page{min-height:420px}}@media screen and (orientation:landscape){.ui-mobile,.ui-mobile .ui-page{min-height:300px}}.ui-loading .ui-loader{display:block}.ui-loader{display:none;z-index:9999999;position:fixed;top:50%;left:50%;border:0}.ui-loader-default{background:none;filter:Alpha(Opacity=18);opacity:.18;width:46px;height:46px;margin-left:-23px;margin-top:-23px}.ui-loader-verbose{width:200px;filter:Alpha(Opacity=88);opacity:.88;box-shadow:0 1px 1px -1px #fff;height:auto;margin-left:-110px;margin-top:-43px;padding:10px}.ui-loader-default h1{font-size:0;width:0;height:0;overflow:hidden}.ui-loader-verbose h1{font-size:16px;margin:0;text-align:center}.ui-loader .ui-icon{background-color:#000;display:block;margin:0;width:44px;height:44px;padding:1px;-webkit-border-radius:36px;-moz-border-radius:36px;border-radius:36px}.ui-loader-verbose .ui-icon{margin:0 auto 10px;filter:Alpha(Opacity=75);opacity:.75}.ui-loader-textonly{padding:15px;margin-left:-115px}.ui-loader-textonly .ui-icon{display:none}.ui-loader-fakefix{position:absolute}.ui-mobile-rendering > *{visibility:hidden}.ui-bar,.ui-body{position:relative;padding:.4em 15px;overflow:hidden;display:block;clear:both}.ui-bar{font-size:16px;margin:0}.ui-bar h1,.ui-bar h2,.ui-bar h3,.ui-bar h4,.ui-bar h5,.ui-bar h6{margin:0;padding:0;font-size:16px;display:inline-block}.ui-header,.ui-footer{position:relative;border-left-width:0;border-right-width:0;zoom:1}.ui-header .ui-btn-left,.ui-header .ui-btn-right,.ui-footer .ui-btn-left,.ui-footer .ui-btn-right{position:absolute;top:3px}.ui-header .ui-btn-left,.ui-footer .ui-btn-left{left:5px}.ui-header .ui-btn-right,.ui-footer .ui-btn-right{right:5px}.ui-footer .ui-btn-icon-notext,.ui-header .ui-btn-icon-notext{top:6px}.ui-header .ui-title,.ui-footer .ui-title{min-height:1.1em;text-align:center;font-size:16px;display:block;margin:.6em 30% .8em;padding:0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;outline:0!important}.ui-footer .ui-title{margin:.6em 15px .8em}.ui-content{border-width:0;overflow:visible;overflow-x:hidden;padding:15px}.ui-icon{width:18px;height:18px}.ui-nojs{position:absolute;left:-9999px}.ui-hide-label label.ui-input-text,.ui-hide-label label.ui-select,.ui-hide-label label.ui-slider,.ui-hide-label label.ui-submit,.ui-hide-label .ui-controlgroup-label,.ui-hidden-accessible{position:absolute!important;left:-9999px;clip:rect(1px);clip:rect(1px,1px,1px,1px)}.ui-mobile-viewport-transitioning,.ui-mobile-viewport-transitioning .ui-page{width:100%;height:100%;overflow:hidden;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ui-page-pre-in{opacity:0}.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.out{-webkit-animation-timing-function:ease-in;-webkit-animation-duration:225ms;-moz-animation-timing-function:ease-in;-moz-animation-duration:225ms}@-webkit-keyframes fadein{from{opacity:0}to{opacity:1}}@-moz-keyframes fadein{from{opacity:0}to{opacity:1}}@-webkit-keyframes fadeout{from{opacity:1}to{opacity:0}}@-moz-keyframes fadeout{from{opacity:1}to{opacity:0}}.fade.out{opacity:0;-webkit-animation-duration:125ms;-webkit-animation-name:fadeout;-moz-animation-duration:125ms;-moz-animation-name:fadeout}.fade.in{opacity:1;-webkit-animation-duration:225ms;-webkit-animation-name:fadein;-moz-animation-duration:225ms;-moz-animation-name:fadein}.pop{-webkit-transform-origin:50% 50%;-moz-transform-origin:50% 50%}.pop.in{-webkit-transform:scale(1);-moz-transform:scale(1);opacity:1;-webkit-animation-name:popin;-moz-animation-name:popin;-webkit-animation-duration:350ms;-moz-animation-duration:350ms}.pop.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;opacity:0;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.pop.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein}.pop.out.reverse{-webkit-transform:scale(.8);-moz-transform:scale(.8);-webkit-animation-name:popout;-moz-animation-name:popout}@-webkit-keyframes popin{from{-webkit-transform:scale(.8);opacity:0}to{-webkit-transform:scale(1);opacity:1}}@-moz-keyframes popin{from{-moz-transform:scale(.8);opacity:0}to{-moz-transform:scale(1);opacity:1}}@-webkit-keyframes popout{from{-webkit-transform:scale(1);opacity:1}to{-webkit-transform:scale(.8);opacity:0}}@-moz-keyframes popout{from{-moz-transform:scale(1);opacity:1}to{-moz-transform:scale(.8);opacity:0}}@-webkit-keyframes slideinfromright{from{-webkit-transform:translateX(100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromright{from{-moz-transform:translateX(100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideinfromleft{from{-webkit-transform:translateX(-100%)}to{-webkit-transform:translateX(0)}}@-moz-keyframes slideinfromleft{from{-moz-transform:translateX(-100%)}to{-moz-transform:translateX(0)}}@-webkit-keyframes slideouttoleft{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(-100%)}}@-moz-keyframes slideouttoleft{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(-100%)}}@-webkit-keyframes slideouttoright{from{-webkit-transform:translateX(0)}to{-webkit-transform:translateX(100%)}}@-moz-keyframes slideouttoright{from{-moz-transform:translateX(0)}to{-moz-transform:translateX(100%)}}.slide.out,.slide.in{-webkit-animation-timing-function:ease-out;-webkit-animation-duration:350ms;-moz-animation-timing-function:ease-out;-moz-animation-duration:350ms}.slide.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft}.slide.in{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromright;-moz-transform:translateX(0);-moz-animation-name:slideinfromright}.slide.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright}.slide.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:slideinfromleft;-moz-transform:translateX(0);-moz-animation-name:slideinfromleft}.slidefade.out{-webkit-transform:translateX(-100%);-webkit-animation-name:slideouttoleft;-moz-transform:translateX(-100%);-moz-animation-name:slideouttoleft;-webkit-animation-duration:225ms;-moz-animation-duration:225ms}.slidefade.in{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:slideouttoright;-moz-transform:translateX(100%);-moz-animation-name:slideouttoright;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidefade.in.reverse{-webkit-transform:translateX(0);-webkit-animation-name:fadein;-moz-transform:translateX(0);-moz-animation-name:fadein;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}.slidedown.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slidedown.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfromtop;-moz-transform:translateY(0);-moz-animation-name:slideinfromtop;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slidedown.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slidedown.out.reverse{-webkit-transform:translateY(-100%);-moz-transform:translateY(-100%);-webkit-animation-name:slideouttotop;-moz-animation-name:slideouttotop;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfromtop{from{-webkit-transform:translateY(-100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfromtop{from{-moz-transform:translateY(-100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttotop{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(-100%)}}@-moz-keyframes slideouttotop{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(-100%)}}.slideup.out{-webkit-animation-name:fadeout;-moz-animation-name:fadeout;-webkit-animation-duration:100ms;-moz-animation-duration:100ms}.slideup.in{-webkit-transform:translateY(0);-webkit-animation-name:slideinfrombottom;-moz-transform:translateY(0);-moz-animation-name:slideinfrombottom;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.slideup.in.reverse{-webkit-animation-name:fadein;-moz-animation-name:fadein;-webkit-animation-duration:150ms;-moz-animation-duration:150ms}.slideup.out.reverse{-webkit-transform:translateY(100%);-moz-transform:translateY(100%);-webkit-animation-name:slideouttobottom;-moz-animation-name:slideouttobottom;-webkit-animation-duration:200ms;-moz-animation-duration:200ms}@-webkit-keyframes slideinfrombottom{from{-webkit-transform:translateY(100%)}to{-webkit-transform:translateY(0)}}@-moz-keyframes slideinfrombottom{from{-moz-transform:translateY(100%)}to{-moz-transform:translateY(0)}}@-webkit-keyframes slideouttobottom{from{-webkit-transform:translateY(0)}to{-webkit-transform:translateY(100%)}}@-moz-keyframes slideouttobottom{from{-moz-transform:translateY(0)}to{-moz-transform:translateY(100%)}}.viewport-flip{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.flip{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-moz-backface-visibility:hidden;-moz-transform:translateX(0)}.flip.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-webkit-animation-duration:175ms;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-moz-animation-duration:175ms}.flip.in{-webkit-animation-name:flipintoright;-webkit-animation-duration:225ms;-moz-animation-name:flipintoright;-moz-animation-duration:225ms}.flip.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.flip.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.viewport-turn{-webkit-perspective:1000;-moz-perspective:1000;position:absolute}.turn{-webkit-backface-visibility:hidden;-webkit-transform:translateX(0);-webkit-transform-origin:0;-moz-backface-visibility:hidden;-moz-transform:translateX(0);-moz-transform-origin:0}.turn.out{-webkit-transform:rotateY(-90deg) scale(.9);-webkit-animation-name:flipouttoleft;-moz-transform:rotateY(-90deg) scale(.9);-moz-animation-name:flipouttoleft;-webkit-animation-duration:125ms;-moz-animation-duration:125ms}.turn.in{-webkit-animation-name:flipintoright;-moz-animation-name:flipintoright;-webkit-animation-duration:250ms;-moz-animation-duration:250ms}.turn.out.reverse{-webkit-transform:rotateY(90deg) scale(.9);-webkit-animation-name:flipouttoright;-moz-transform:rotateY(90deg) scale(.9);-moz-animation-name:flipouttoright}.turn.in.reverse{-webkit-animation-name:flipintoleft;-moz-animation-name:flipintoleft}@-webkit-keyframes flipouttoleft{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(-90deg) scale(.9)}}@-moz-keyframes flipouttoleft{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(-90deg) scale(.9)}}@-webkit-keyframes flipouttoright{from{-webkit-transform:rotateY(0)}to{-webkit-transform:rotateY(90deg) scale(.9)}}@-moz-keyframes flipouttoright{from{-moz-transform:rotateY(0)}to{-moz-transform:rotateY(90deg) scale(.9)}}@-webkit-keyframes flipintoleft{from{-webkit-transform:rotateY(-90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoleft{from{-moz-transform:rotateY(-90deg) scale(.9)}to{-moz-transform:rotateY(0)}}@-webkit-keyframes flipintoright{from{-webkit-transform:rotateY(90deg) scale(.9)}to{-webkit-transform:rotateY(0)}}@-moz-keyframes flipintoright{from{-moz-transform:rotateY(90deg) scale(.9)}to{-moz-transform:rotateY(0)}}.flow{-webkit-transform-origin:50% 30%;-moz-transform-origin:50% 30%;-webkit-box-shadow:0 0 20px rgba(0,0,0,.4);-moz-box-shadow:0 0 20px rgba(0,0,0,.4)}.ui-dialog.flow{-webkit-transform-origin:none;-moz-transform-origin:none;-webkit-box-shadow:none;-moz-box-shadow:none}.flow.out{-webkit-transform:translateX(-100%) scale(.7);-webkit-animation-name:flowouttoleft;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(-100%) scale(.7);-moz-animation-name:flowouttoleft;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.in{-webkit-transform:translateX(0) scale(1);-webkit-animation-name:flowinfromright;-webkit-animation-timing-function:ease;-webkit-animation-duration:350ms;-moz-transform:translateX(0) scale(1);-moz-animation-name:flowinfromright;-moz-animation-timing-function:ease;-moz-animation-duration:350ms}.flow.out.reverse{-webkit-transform:translateX(100%);-webkit-animation-name:flowouttoright;-moz-transform:translateX(100%);-moz-animation-name:flowouttoright}.flow.in.reverse{-webkit-animation-name:flowinfromleft;-moz-animation-name:flowinfromleft}@-webkit-keyframes flowouttoleft{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(-100%) scale(.7)}}@-moz-keyframes flowouttoleft{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(-100%) scale(.7)}}@-webkit-keyframes flowouttoright{0%{-webkit-transform:translateX(0) scale(1)}60%,70%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(100%) scale(.7)}}@-moz-keyframes flowouttoright{0%{-moz-transform:translateX(0) scale(1)}60%,70%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(100%) scale(.7)}}@-webkit-keyframes flowinfromleft{0%{-webkit-transform:translateX(-100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromleft{0%{-moz-transform:translateX(-100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}@-webkit-keyframes flowinfromright{0%{-webkit-transform:translateX(100%) scale(.7)}30%,40%{-webkit-transform:translateX(0) scale(.7)}100%{-webkit-transform:translateX(0) scale(1)}}@-moz-keyframes flowinfromright{0%{-moz-transform:translateX(100%) scale(.7)}30%,40%{-moz-transform:translateX(0) scale(.7)}100%{-moz-transform:translateX(0) scale(1)}}.ui-grid-a,.ui-grid-b,.ui-grid-c,.ui-grid-d{overflow:hidden}.ui-block-a,.ui-block-b,.ui-block-c,.ui-block-d,.ui-block-e{margin:0;padding:0;border:0;float:left;min-height:1px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.ui-grid-solo .ui-block-a{display:block;float:none}.ui-grid-a .ui-block-a,.ui-grid-a .ui-block-b{width:49.95%}.ui-grid-a >:nth-child(n){width:50%;margin-right:-.5px}.ui-grid-a .ui-block-a{clear:left}.ui-grid-b .ui-block-a,.ui-grid-b .ui-block-b,.ui-grid-b .ui-block-c{width:33.25%}.ui-grid-b >:nth-child(n){width:33.333%;margin-right:-.5px}.ui-grid-b .ui-block-a{clear:left}.ui-grid-c .ui-block-a,.ui-grid-c .ui-block-b,.ui-grid-c .ui-block-c,.ui-grid-c .ui-block-d{width:24.925%}.ui-grid-c >:nth-child(n){width:25%;margin-right:-.5px}.ui-grid-c .ui-block-a{clear:left}.ui-grid-d .ui-block-a,.ui-grid-d .ui-block-b,.ui-grid-d .ui-block-c,.ui-grid-d .ui-block-d,.ui-grid-d .ui-block-e{width:19.925%}.ui-grid-d >:nth-child(n){width:20%}.ui-grid-d .ui-block-a{clear:left}.ui-header-fixed,.ui-footer-fixed{left:0;right:0;width:100%;position:fixed;z-index:1000}.ui-header-fixed{top:0}.ui-footer-fixed{bottom:0}.ui-header-fullscreen,.ui-footer-fullscreen{filter:Alpha(Opacity=90);opacity:.9}.ui-page-header-fixed{padding-top:2.6875em}.ui-page-footer-fixed{padding-bottom:2.6875em}.ui-page-header-fullscreen .ui-content,.ui-page-footer-fullscreen .ui-content{padding:0}.ui-fixed-hidden{position:absolute}.ui-page-header-fullscreen .ui-fixed-hidden,.ui-page-footer-fullscreen .ui-fixed-hidden{left:-9999px}.ui-header-fixed .ui-btn,.ui-footer-fixed .ui-btn{z-index:10}.ui-navbar{max-width:100%}.ui-navbar.ui-mini{margin:0}.ui-navbar ul:before,.ui-navbar ul:after{content:" ";display:table}.ui-navbar ul:after{clear:both}.ui-navbar ul{list-style:none;margin:0;padding:0;position:relative;display:block;border:0;max-width:100%;overflow:visible;zoom:1}.ui-navbar li .ui-btn{display:block;text-align:center;margin:0 -1px 0 0;border-right-width:0}.ui-navbar li .ui-btn-icon-right .ui-icon{right:6px}.ui-navbar li:last-child .ui-btn,.ui-navbar .ui-grid-duo .ui-block-b .ui-btn{margin-right:0;border-right-width:1px}.ui-header .ui-navbar li:last-child .ui-btn,.ui-footer .ui-navbar li:last-child .ui-btn,.ui-header .ui-navbar .ui-grid-duo .ui-block-b .ui-btn,.ui-footer .ui-navbar .ui-grid-duo .ui-block-b .ui-btn{margin-right:-1px;border-right-width:0}.ui-navbar .ui-grid-duo li.ui-block-a:last-child .ui-btn{margin-right:-1px;border-right-width:1px}.ui-header .ui-navbar li .ui-btn,.ui-footer .ui-navbar li .ui-btn{border-top-width:0;border-bottom-width:0}.ui-header .ui-navbar .ui-grid-b li.ui-block-c .ui-btn,.ui-footer .ui-navbar .ui-grid-b li.ui-block-c .ui-btn{margin-right:-5px}.ui-header .ui-navbar .ui-grid-c li.ui-block-d .ui-btn,.ui-footer .ui-navbar .ui-grid-c li.ui-block-d .ui-btn,.ui-header .ui-navbar .ui-grid-d li.ui-block-e .ui-btn,.ui-footer .ui-navbar .ui-grid-d li.ui-block-e .ui-btn{margin-right:-4px}.ui-header .ui-navbar .ui-grid-b li.ui-block-c .ui-btn-icon-right .ui-icon,.ui-footer .ui-navbar .ui-grid-b li.ui-block-c .ui-btn-icon-right .ui-icon,.ui-header .ui-navbar .ui-grid-c li.ui-block-d .ui-btn-icon-right .ui-icon,.ui-footer .ui-navbar .ui-grid-c li.ui-block-d .ui-btn-icon-right .ui-icon,.ui-header .ui-navbar .ui-grid-d li.ui-block-e .ui-btn-icon-right .ui-icon,.ui-footer .ui-navbar .ui-grid-d li.ui-block-e .ui-btn-icon-right .ui-icon{right:8px}.ui-navbar li .ui-btn .ui-btn-inner{padding-top:.7em;padding-bottom:.8em}.ui-navbar li .ui-btn-icon-top .ui-btn-inner{padding-top:30px}.ui-navbar li .ui-btn-icon-bottom .ui-btn-inner{padding-bottom:30px}.ui-btn{display:block;text-align:center;cursor:pointer;position:relative;margin:.5em 0;padding:0}.ui-mini{margin-top:.25em;margin-bottom:.25em}.ui-btn-left,.ui-btn-right,.ui-input-clear,.ui-btn-inline,.ui-grid-a .ui-btn,.ui-grid-b .ui-btn,.ui-grid-c .ui-btn,.ui-grid-d .ui-btn,.ui-grid-e .ui-btn,.ui-grid-solo .ui-btn{margin-right:5px;margin-left:5px}.ui-btn-inner{font-size:16px;padding:.6em 20px;min-width:.75em;display:block;position:relative;text-overflow:ellipsis;overflow:hidden;white-space:nowrap;zoom:1}.ui-btn input,.ui-btn button{z-index:2}.ui-btn-left,.ui-btn-right,.ui-btn-inline{display:inline-block;vertical-align:middle}.ui-mobile .ui-btn-left,.ui-mobile .ui-btn-right{margin:0}.ui-btn-block{display:block}.ui-header > .ui-btn,.ui-footer > .ui-btn{display:inline-block;margin:0}.ui-header .ui-btn-block,.ui-footer .ui-btn-block{display:block}.ui-header .ui-btn-inner,.ui-footer .ui-btn-inner,.ui-mini .ui-btn-inner{font-size:12.5px;padding:.55em 11px .5em}.ui-fullsize .ui-btn-inner,.ui-fullsize .ui-btn-inner{font-size:16px;padding:.6em 20px}.ui-btn-icon-notext{width:24px;height:24px}.ui-btn-icon-notext .ui-btn-inner{padding:0;height:100%}.ui-btn-icon-notext .ui-btn-inner .ui-icon{margin:2px 1px 2px 3px;float:left}.ui-btn-text{position:relative;z-index:1;width:100%;-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none}.ui-btn-icon-notext .ui-btn-text{position:absolute;left:-9999px}.ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-btn-icon-right .ui-btn-inner{padding-right:40px}.ui-btn-icon-top .ui-btn-inner{padding-top:40px}.ui-btn-icon-bottom .ui-btn-inner{padding-bottom:40px}.ui-header .ui-btn-icon-left .ui-btn-inner,.ui-footer .ui-btn-icon-left .ui-btn-inner,.ui-mini.ui-btn-icon-left .ui-btn-inner,.ui-mini .ui-btn-icon-left .ui-btn-inner{padding-left:30px}.ui-header .ui-btn-icon-right .ui-btn-inner,.ui-footer .ui-btn-icon-right .ui-btn-inner,.ui-mini.ui-btn-icon-right .ui-btn-inner,.ui-mini .ui-btn-icon-right .ui-btn-inner{padding-right:30px}.ui-header .ui-btn-icon-top .ui-btn-inner,.ui-footer .ui-btn-icon-top .ui-btn-inner{padding:30px 3px .5em 3px}.ui-mini.ui-btn-icon-top .ui-btn-inner,.ui-mini .ui-btn-icon-top .ui-btn-inner{padding-top:30px}.ui-header .ui-btn-icon-bottom .ui-btn-inner,.ui-footer .ui-btn-icon-bottom .ui-btn-inner{padding:.55em 3px 30px 3px}.ui-mini.ui-btn-icon-bottom .ui-btn-inner,.ui-mini .ui-btn-icon-bottom .ui-btn-inner{padding-bottom:30px}.ui-btn-icon-notext .ui-icon{display:block;z-index:0}.ui-btn-icon-left > .ui-btn-inner > .ui-icon,.ui-btn-icon-right > .ui-btn-inner > .ui-icon{position:absolute;top:50%;margin-top:-9px}.ui-btn-icon-top .ui-btn-inner .ui-icon,.ui-btn-icon-bottom .ui-btn-inner .ui-icon{position:absolute;left:50%;margin-left:-9px}.ui-btn-icon-left .ui-icon{left:10px}.ui-btn-icon-right .ui-icon{right:10px}.ui-btn-icon-top .ui-icon{top:10px}.ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-header .ui-btn-icon-left .ui-icon,.ui-footer .ui-btn-icon-left .ui-icon,.ui-mini.ui-btn-icon-left .ui-icon,.ui-mini .ui-btn-icon-left .ui-icon{left:5px}.ui-header .ui-btn-icon-right .ui-icon,.ui-footer .ui-btn-icon-right .ui-icon,.ui-mini.ui-btn-icon-right .ui-icon,.ui-mini .ui-btn-icon-right .ui-icon{right:5px}.ui-header .ui-btn-icon-top .ui-icon,.ui-footer .ui-btn-icon-top .ui-icon,.ui-mini.ui-btn-icon-top .ui-icon,.ui-mini .ui-btn-icon-top .ui-icon{top:5px}.ui-header .ui-btn-icon-bottom .ui-icon,.ui-footer .ui-btn-icon-bottom .ui-icon,.ui-mini.ui-btn-icon-bottom .ui-icon,.ui-mini .ui-btn-icon-bottom .ui-icon{bottom:5px}.ui-btn-hidden{position:absolute;top:0;left:0;width:100%;height:100%;-webkit-appearance:none;cursor:pointer;background:#fff;background:rgba(255,255,255,0);filter:Alpha(Opacity=0);opacity:.1;font-size:1px;border:none;text-indent:-9999px}.ui-disabled .ui-btn-hidden{display:none}.ui-disabled{z-index:1}.ui-field-contain .ui-btn.ui-submit{margin:0}label.ui-submit{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}@media all and (min-width:450px){.ui-field-contain label.ui-submit{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-btn.ui-submit{width:78%;display:inline-block;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}.ui-hide-label .ui-btn.ui-submit{width:auto;display:block}}.ui-collapsible-inset{margin:.5em 0}.ui-collapsible-heading{font-size:16px;display:block;margin:0 -15px;padding:0;position:relative}.ui-collapsible-inset .ui-collapsible-heading{margin:0}.ui-collapsible-heading .ui-btn{text-align:left;margin:0;border-left-width:0;border-right-width:0}.ui-collapsible-inset .ui-collapsible-heading .ui-btn{border-right-width:1px;border-left-width:1px}.ui-collapsible-collapsed + .ui-collapsible:not(.ui-collapsible-inset) .ui-collapsible-heading .ui-btn{border-top-width:0}.ui-collapsible-set .ui-collapsible:not(.ui-collapsible-inset) .ui-collapsible-heading .ui-btn{border-top-width:1px}.ui-collapsible-heading .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-left .ui-btn-inner{padding-left:40px}.ui-collapsible-heading .ui-btn-icon-right .ui-btn-inner{padding-left:12px;padding-right:40px}.ui-collapsible-heading .ui-btn-icon-top .ui-btn-inner,.ui-collapsible-heading .ui-btn-icon-bottom .ui-btn-inner{padding-right:40px;text-align:center}.ui-collapsible-heading .ui-btn span.ui-btn{position:absolute;left:6px;top:50%;margin:-12px 0 0 0;width:20px;height:20px;padding:1px 0 1px 2px;text-indent:-9999px}.ui-collapsible-heading .ui-btn span.ui-btn .ui-btn-inner{padding:10px 0}.ui-collapsible-heading .ui-btn span.ui-btn .ui-icon{left:0;margin-top:-10px}.ui-collapsible-heading-status{position:absolute;top:-9999px;left:0}.ui-collapsible-content{display:block;margin:0 -15px;padding:10px 15px;border-left-width:0;border-right-width:0;border-top:none;background-image:none}.ui-collapsible-inset .ui-collapsible-content{margin:0;border-right-width:1px;border-left-width:1px}.ui-collapsible-content-collapsed{display:none}.ui-collapsible-set{margin:.5em 0}.ui-collapsible-set .ui-collapsible{margin:-1px 0 0}.ui-collapsible-set .ui-collapsible:first-child{margin-top:0}.ui-controlgroup,fieldset.ui-controlgroup{padding:0;margin:.5em 0;zoom:1}.ui-controlgroup.ui-mini,fieldset.ui-controlgroup.ui-mini{margin:.25em 0}.ui-field-contain .ui-controlgroup,.ui-field-contain fieldset.ui-controlgroup{margin:0}.ui-bar .ui-controlgroup{margin:0 5px}.ui-controlgroup-label{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .4em}.ui-controlgroup li{list-style:none}.ui-controlgroup-vertical .ui-btn,.ui-controlgroup-vertical .ui-checkbox,.ui-controlgroup-vertical .ui-radio{margin:0;border-bottom-width:0}.ui-controlgroup-vertical .ui-controlgroup-last{border-bottom-width:1px}.ui-controlgroup-controls label.ui-select{position:absolute;left:-9999px}.ui-controlgroup .ui-btn-icon-notext{width:auto;height:auto;top:auto}.ui-controlgroup .ui-btn-icon-notext .ui-btn-inner{height:20px;padding:.6em 20px .6em 20px}.ui-controlgroup-horizontal .ui-btn-icon-notext .ui-btn-inner{width:18px}.ui-controlgroup.ui-mini .ui-btn-icon-notext .ui-btn-inner,.ui-header .ui-controlgroup .ui-btn-icon-notext .ui-btn-inner,.ui-footer .ui-controlgroup .ui-btn-icon-notext .ui-btn-inner{height:16px;padding:.55em 11px .5em 11px}.ui-controlgroup .ui-btn-icon-notext .ui-btn-inner .ui-icon{position:absolute;top:50%;right:50%;margin:-9px -9px 0 0}.ui-controlgroup-horizontal .ui-controlgroup-controls:before,.ui-controlgroup-horizontal .ui-controlgroup-controls:after{content:"";display:table}.ui-controlgroup-horizontal .ui-controlgroup-controls:after{clear:both}.ui-controlgroup-horizontal .ui-controlgroup-controls{display:inline-block;vertical-align:middle;zoom:1}.ui-controlgroup-horizontal .ui-btn-inner{text-align:center}.ui-controlgroup-horizontal.ui-mini .ui-btn-inner{height:16px;line-height:16px}.ui-controlgroup-horizontal .ui-btn,.ui-controlgroup-horizontal .ui-select,.ui-controlgroup-horizontal .ui-checkbox,.ui-controlgroup-horizontal .ui-radio{float:left;clear:none;margin:0 -1px 0 0}.ui-controlgroup-horizontal .ui-select .ui-btn,.ui-controlgroup-horizontal .ui-checkbox .ui-btn,.ui-controlgroup-horizontal .ui-radio .ui-btn{float:none;margin:0}.ui-controlgroup-horizontal .ui-controlgroup-last,.ui-controlgroup-horizontal .ui-select:last-child,.ui-controlgroup-horizontal .ui-checkbox:last-child,.ui-controlgroup-horizontal .ui-radio:last-child{margin-right:0}.ui-controlgroup .ui-checkbox label,.ui-controlgroup .ui-radio label{font-size:16px}@media all and (min-width:450px){.ui-field-contain .ui-controlgroup-label{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-controlgroup-controls{width:78%;display:inline-block}.ui-field-contain .ui-controlgroup .ui-select{width:100%;display:block}.ui-field-contain .ui-controlgroup-horizontal .ui-select{width:auto}.ui-hide-label .ui-controlgroup-controls{width:100%}}.ui-dialog{background:none!important}.ui-dialog-contain{width:92.5%;max-width:500px;margin:10% auto 15px auto;padding:0;position:relative;top:-15px}.ui-dialog-contain > .ui-header,.ui-dialog-contain > .ui-content,.ui-dialog-contain > .ui-footer{display:block;position:relative;width:auto;margin:0}.ui-dialog-contain > .ui-header{border:none;overflow:hidden;z-index:10;padding:0}.ui-dialog-contain > .ui-content{padding:15px}.ui-dialog-contain > .ui-footer{z-index:10;padding:0 15px}.ui-popup-open .ui-header-fixed,.ui-popup-open .ui-footer-fixed{position:absolute!important}.ui-popup-screen{background-image:url(data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==);top:0;left:0;right:0;bottom:1px;position:absolute;filter:Alpha(Opacity=0);opacity:0;z-index:1099}.ui-popup-screen.in{opacity:0.5;filter:Alpha(Opacity=50)}.ui-popup-screen.out{opacity:0;filter:Alpha(Opacity=0)}.ui-popup-container{z-index:1100;display:inline-block;position:absolute;padding:0;outline:0}.ui-popup{position:relative}.ui-popup.ui-content,.ui-popup .ui-content{overflow:visible}.ui-popup > p,.ui-popup > h1,.ui-popup > h2,.ui-popup > h3,.ui-popup > h4,.ui-popup > h5,.ui-popup > h6{margin:.5em 7px}.ui-popup > span{display:block;margin:.5em 7px}.ui-popup .ui-title{font-size:16px;font-weight:bold;margin-top:.5em;margin-bottom:.5em}.ui-popup-container .ui-content > p,.ui-popup-container .ui-content > h1,.ui-popup-container .ui-content > h2,.ui-popup-container .ui-content > h3,.ui-popup-container .ui-content > h4,.ui-popup-container .ui-content > h5,.ui-popup-container .ui-content > h6{margin:.5em 0}.ui-popup-container .ui-content > span{margin:0}.ui-popup-container .ui-content > p:first-child,.ui-popup-container .ui-content > h1:first-child,.ui-popup-container .ui-content > h2:first-child,.ui-popup-container .ui-content > h3:first-child,.ui-popup-container .ui-content > h4:first-child,.ui-popup-container .ui-content > h5:first-child,.ui-popup-container .ui-content > h6:first-child{margin-top:0}.ui-popup-container .ui-content > p:last-child,.ui-popup-container .ui-content > h1:last-child,.ui-popup-container .ui-content > h2:last-child,.ui-popup-container .ui-content > h3:last-child,.ui-popup-container .ui-content > h4:last-child,.ui-popup-container .ui-content > h5:last-child,.ui-popup-container .ui-content > h6:last-child{margin-bottom:0}.ui-popup > img{width:auto;height:auto;max-width:100%;max-height:100%;vertical-align:middle}.ui-popup iframe{vertical-align:middle}@media all and (min-width:450px){.ui-popup .ui-field-contain label.ui-submit,.ui-popup .ui-field-contain .ui-controlgroup-label,.ui-popup .ui-field-contain label.ui-select,.ui-popup .ui-field-contain label.ui-input-text{font-size:16px;line-height:1.4;display:block;font-weight:normal;margin:0 0 .3em}.ui-popup .ui-field-contain .ui-btn.ui-submit,.ui-popup .ui-field-contain .ui-controlgroup-controls,.ui-popup .ui-field-contain .ui-select,.ui-popup .ui-field-contain input.ui-input-text,.ui-popup .ui-field-contain textarea.ui-input-text,.ui-popup .ui-field-contain .ui-input-search{width:100%;display:block}}.ui-popup > .ui-btn-left,.ui-popup > .ui-btn-right{position:absolute;top:-9px;margin:0;z-index:1101}.ui-popup > .ui-btn-left{left:-9px}.ui-popup > .ui-btn-right{right:-9px}.ui-popup.ui-corner-all > .ui-header,.ui-popup.ui-corner-all ~ .ui-content,.ui-popup.ui-corner-all > .ui-content:first-child{-webkit-border-top-left-radius:inherit;border-top-left-radius:inherit;-webkit-border-top-right-radius:inherit;border-top-right-radius:inherit}.ui-popup.ui-corner-all > .ui-content,.ui-popup.ui-corner-all > .ui-footer,.ui-popup.ui-corner-all > .ui-header:nth-child(n):last-child{-webkit-border-bottom-left-radius:inherit;border-bottom-left-radius:inherit;-webkit-border-bottom-right-radius:inherit;border-bottom-right-radius:inherit}.ui-popup.ui-corner-all > .ui-content:nth-child(2),.ui-popup.ui-corner-all > .ui-header:nth-child(2){-webkit-border-top-left-radius:0;border-top-left-radius:0;-webkit-border-top-right-radius:0;border-top-right-radius:0}.ui-popup.ui-corner-all > .ui-content:nth-last-child(1n+2),.ui-popup.ui-corner-all > .ui-footer:nth-last-child(1n+2){-webkit-border-bottom-left-radius:0;border-bottom-left-radius:0;-webkit-border-bottom-right-radius:0;border-bottom-right-radius:0}.ui-popup.ui-corner-all > .ui-header:only-child,.ui-popup.ui-corner-all > .ui-footer:only-child{-webkit-border-radius:inherit;border-radius:inherit}.ui-checkbox,.ui-radio{position:relative;clear:both;margin:0;z-index:1}.ui-checkbox .ui-btn,.ui-radio .ui-btn{margin-top:.5em;margin-bottom:.5em;text-align:left;z-index:2}.ui-checkbox .ui-btn.ui-mini,.ui-radio .ui-btn.ui-mini{margin:.25em 0}.ui-controlgroup .ui-checkbox .ui-btn,.ui-controlgroup .ui-radio .ui-btn{margin:0}.ui-checkbox .ui-btn-inner,.ui-radio .ui-btn-inner{white-space:normal}.ui-checkbox .ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-btn-icon-left .ui-btn-inner{padding-left:45px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-left .ui-btn-inner{padding-left:36px}.ui-checkbox .ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-btn-icon-right .ui-btn-inner{padding-right:45px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-btn-inner,.ui-radio .ui-mini.ui-btn-icon-right .ui-btn-inner{padding-right:36px}.ui-checkbox .ui-btn-icon-top .ui-btn-inner,.ui-radio .ui-btn-icon-top .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-btn-icon-bottom .ui-btn-inner,.ui-radio .ui-btn-icon-bottom .ui-btn-inner{padding-right:0;padding-left:0;text-align:center}.ui-checkbox .ui-icon,.ui-radio .ui-icon{top:1.1em}.ui-checkbox .ui-btn-icon-left .ui-icon,.ui-radio .ui-btn-icon-left .ui-icon{left:15px}.ui-checkbox .ui-mini.ui-btn-icon-left .ui-icon,.ui-radio .ui-mini.ui-btn-icon-left .ui-icon{left:9px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox .ui-btn-icon-top .ui-icon,.ui-radio .ui-btn-icon-top .ui-icon{top:10px}.ui-checkbox .ui-btn-icon-bottom .ui-icon,.ui-radio .ui-btn-icon-bottom .ui-icon{top:auto;bottom:10px}.ui-checkbox .ui-btn-icon-right .ui-icon,.ui-radio .ui-btn-icon-right .ui-icon{right:15px}.ui-checkbox .ui-mini.ui-btn-icon-right .ui-icon,.ui-radio .ui-mini.ui-btn-icon-right .ui-icon{right:9px}.ui-checkbox input,.ui-radio input{position:absolute;left:20px;top:50%;width:10px;height:10px;margin:-5px 0 0 0;outline:0!important;z-index:1}.ui-field-contain,fieldset.ui-field-contain{padding:.8em 0;margin:0;border-width:0 0 1px 0;overflow:visible}.ui-field-contain:last-child{border-bottom-width:0}.ui-field-contain{max-width:100%}@media all and (min-width:450px){.ui-field-contain,.ui-mobile fieldset.ui-field-contain{border-width:0;padding:0;margin:1em 0}}.ui-select{display:block;position:relative}.ui-select select{position:absolute;left:-9999px;top:-9999px}.ui-select .ui-btn{overflow:hidden;opacity:1}.ui-field-contain .ui-select .ui-btn{margin:0}.ui-select .ui-btn select{cursor:pointer;-webkit-appearance:none;left:0;top:0;width:100%;min-height:1.5em;min-height:100%;height:3em;max-height:100%;filter:Alpha(Opacity=0);opacity:0;z-index:2}.ui-select .ui-disabled{opacity:.3}.ui-select .ui-disabled select{display:none}@-moz-document url-prefix(){.ui-select .ui-btn select{opacity:0.0001}}.ui-select .ui-btn.ui-select-nativeonly{border-radius:0;border:0}.ui-select .ui-btn.ui-select-nativeonly select{opacity:1;text-indent:0;display:block}.ui-select .ui-disabled.ui-select-nativeonly .ui-btn-inner{opacity:0}.ui-select .ui-btn-icon-right .ui-btn-inner,.ui-select .ui-li-has-count .ui-btn-inner{padding-right:45px}.ui-select .ui-mini.ui-btn-icon-right .ui-btn-inner{padding-right:32px}.ui-select .ui-btn-icon-right.ui-li-has-count .ui-btn-inner{padding-right:80px}.ui-select .ui-mini.ui-btn-icon-right.ui-li-has-count .ui-btn-inner{padding-right:67px}.ui-select .ui-btn-icon-right .ui-icon{right:15px}.ui-select .ui-mini.ui-btn-icon-right .ui-icon{right:7px}.ui-select .ui-btn-icon-right.ui-li-has-count .ui-li-count{right:45px}.ui-select .ui-mini.ui-btn-icon-right.ui-li-has-count .ui-li-count{right:32px}label.ui-select{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}.ui-select .ui-btn-text,.ui-selectmenu .ui-btn-text{display:block;min-height:1em;overflow:hidden!important}.ui-select .ui-btn-text{text-overflow:ellipsis}.ui-selectmenu{padding:6px;min-width:160px}.ui-selectmenu .ui-listview{margin:0}.ui-selectmenu .ui-btn.ui-li-divider{cursor:default}.ui-selectmenu-hidden{top:-99999px;left:-9999px}.ui-screen-hidden,.ui-selectmenu-list .ui-li .ui-icon{display:none}.ui-selectmenu-list .ui-li .ui-icon{display:block}.ui-li.ui-selectmenu-placeholder{display:none}.ui-selectmenu .ui-header{margin:0;padding:0}.ui-selectmenu .ui-header .ui-title{margin:0.6em 46px 0.8em}@media all and (min-width:450px){.ui-field-contain label.ui-select{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain .ui-select{width:78%;display:inline-block}.ui-hide-label .ui-select{width:100%}}.ui-selectmenu .ui-header h1:after{content:'.';visibility:hidden}label.ui-input-text{font-size:16px;line-height:1.4;display:block;font-weight:normal;margin:0 0 .3em}input.ui-input-text,textarea.ui-input-text{background-image:none;padding:.4em;margin:.5em 0;line-height:1.4;font-size:16px;display:block;width:100%;outline:0}input.ui-input-text.ui-mini,textarea.ui-input-text.ui-mini{margin:.25em 0}.ui-field-contain input.ui-input-text,.ui-field-contain textarea.ui-input-text{margin:0}input.ui-input-text,textarea.ui-input-text,.ui-input-search{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;-ms-box-sizing:border-box;box-sizing:border-box}input.ui-input-text{-webkit-appearance:none}textarea.ui-input-text{height:50px;-webkit-transition:height 200ms linear;-moz-transition:height 200ms linear;-o-transition:height 200ms linear;transition:height 200ms linear}.ui-input-search{padding:0 30px;margin:.5em 0;background-image:none;position:relative}.ui-input-search.ui-mini{margin:.25em 0}.ui-field-contain .ui-input-search{margin:0}.ui-icon-searchfield:after{position:absolute;left:7px;top:50%;margin-top:-9px;content:"";width:18px;height:18px;opacity:.5}.ui-input-search input.ui-input-text{border:none;width:98%;padding:.4em 0;margin:0;display:block;background:transparent none;outline:0!important}.ui-input-search .ui-input-clear{position:absolute;right:0;top:50%;margin-top:-13px}.ui-mini .ui-input-clear{right:-3px}.ui-input-search .ui-input-clear-hidden{display:none}input.ui-mini,.ui-mini input,textarea.ui-mini{font-size:14px}textarea.ui-mini{height:45px}@media all and (min-width:450px){.ui-field-contain label.ui-input-text{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain input.ui-input-text,.ui-field-contain textarea.ui-input-text,.ui-field-contain .ui-input-search{width:78%;display:inline-block}.ui-hide-label input.ui-input-text,.ui-hide-label textarea.ui-input-text,.ui-hide-label .ui-input-search{width:100%}.ui-input-search input.ui-input-text{width:98%}}.ui-listview{margin:0}ol.ui-listview,ol.ui-listview .ui-li-divider{counter-reset:listnumbering}.ui-content .ui-listview{margin:-15px}.ui-collapsible-content > .ui-listview{margin:-10px -15px}.ui-content .ui-listview-inset{margin:1em 0}.ui-collapsible-content .ui-listview-inset{margin:.5em 0}.ui-listview,.ui-li{list-style:none;padding:0}.ui-li,.ui-li.ui-field-contain{display:block;margin:0;position:relative;overflow:visible;text-align:left;border-width:0;border-top-width:1px}.ui-li.ui-btn{margin:0}.ui-li .ui-btn-text a.ui-link-inherit{text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-static{background-image:none}.ui-li-divider{padding:.5em 15px;font-size:14px;font-weight:bold}ol.ui-listview .ui-link-inherit:before,ol.ui-listview .ui-li-static:before,.ui-li-dec{font-size:.8em;display:inline-block;padding-right:.3em;font-weight:normal;counter-increment:listnumbering;content:counter(listnumbering) ". "}ol.ui-listview .ui-li-jsnumbering:before{content:""!important}.ui-listview-inset .ui-li{border-right-width:1px;border-left-width:1px}.ui-li-last,.ui-li.ui-field-contain.ui-li-last{border-bottom-width:1px}.ui-collapsible [class*="ui-body"] > .ui-listview:not(.ui-listview-inset) .ui-li-last{border-bottom-width:0}.ui-collapsible-content > .ui-listview:not(.ui-listview-inset) .ui-li:first-child{border-top-width:0}.ui-collapsible-content > .ui-listview:not(.ui-listview-inset),.ui-collapsible-content > .ui-listview:not(.ui-listview-inset) .ui-li-last{-webkit-border-bottom-left-radius:inherit;-webkit-border-bottom-right-radius:inherit;border-bottom-left-radius:inherit;border-bottom-right-radius:inherit}.ui-collapsible-content > .ui-listview:not(.ui-listview-inset) .ui-li-last .ui-li-link-alt{-webkit-border-bottom-right-radius:inherit;border-bottom-right-radius:inherit}.ui-li>.ui-btn-inner{display:block;position:relative;padding:0}.ui-li .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li{padding:.7em 15px;display:block}.ui-li-has-thumb .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-thumb{min-height:60px;padding-left:100px}.ui-li-has-icon .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-icon{min-height:20px;padding-left:40px}.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-count,.ui-li-divider.ui-li-has-count{padding-right:45px}.ui-li-has-arrow .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow{padding-right:40px}.ui-li-has-arrow.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-arrow.ui-li-has-count{padding-right:75px}.ui-li-heading{font-size:16px;font-weight:bold;display:block;margin:.6em 0;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-desc{font-size:12px;font-weight:normal;display:block;margin:-.5em 0 .6em;text-overflow:ellipsis;overflow:hidden;white-space:nowrap}.ui-li-thumb,.ui-listview .ui-li-icon{position:absolute;left:1px;top:0;max-height:80px;max-width:80px}.ui-listview .ui-li-icon{max-height:16px;max-width:16px;left:10px;top:.9em}.ui-li-thumb,.ui-listview .ui-li-icon,.ui-li-content{float:left;margin-right:10px}.ui-li-aside{float:right;width:50%;text-align:right;margin:.3em 0}@media all and (min-width:480px){.ui-li-aside{width:45%}}.ui-li-divider{cursor:default}.ui-li-has-alt .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-alt{padding-right:53px}.ui-li-has-alt.ui-li-has-count .ui-btn-inner a.ui-link-inherit,.ui-li-static.ui-li-has-alt.ui-li-has-count{padding-right:88px}.ui-li-has-count .ui-li-count{position:absolute;font-size:11px;font-weight:bold;padding:.2em .5em;top:50%;margin-top:-.9em;right:10px}.ui-li-has-count.ui-li-divider .ui-li-count,.ui-li-has-count .ui-link-inherit .ui-li-count{margin-top:-.95em}.ui-li-has-arrow.ui-li-has-count .ui-li-count{right:40px}.ui-li-has-alt.ui-li-has-count .ui-li-count{right:53px}.ui-li-link-alt{position:absolute;width:40px;height:100%;border-width:0;border-left-width:1px;top:0;right:0;margin:0;padding:0;z-index:2}.ui-li-link-alt .ui-btn{overflow:hidden;position:absolute;right:8px;top:50%;margin:-13px 0 0 0;border-bottom-width:1px;z-index:-1}.ui-li-link-alt .ui-btn-inner{padding:0;height:100%;position:absolute;width:100%;top:0;left:0}.ui-li-link-alt .ui-btn .ui-icon{right:50%;margin-right:-9px}.ui-li-link-alt .ui-btn-icon-notext .ui-btn-inner .ui-icon{position:absolute;top:50%;margin-top:-9px}.ui-listview * .ui-btn-inner > .ui-btn > .ui-btn-inner{border-top:0}.ui-listview-filter{border-width:0;overflow:hidden;margin:-15px -15px 15px -15px}.ui-collapsible-content .ui-listview-filter{margin:-10px -15px 10px -15px;border-bottom:inherit}.ui-listview-filter-inset{margin:-15px -5px;background:transparent}.ui-collapsible-content .ui-listview-filter-inset{margin:-5px;border-bottom-width:0}.ui-listview-filter .ui-input-search{margin:5px;width:auto;display:block}.ui-li.ui-screen-hidden{display:none}@media only screen and (min-device-width:768px) and (max-device-width:1024px){.ui-li .ui-btn-text{overflow:visible}}label.ui-slider{font-size:16px;line-height:1.4;font-weight:normal;margin:0 0 .3em;display:block}input.ui-slider-input,.ui-field-contain input.ui-slider-input{display:inline-block;width:50px;background-image:none;padding:.4em;margin:.5em 0;line-height:1.4;font-size:16px;outline:0}input.ui-slider-input.ui-mini,.ui-field-contain input.ui-slider-input.ui-mini{width:45px;margin:.25em 0;font-size:14px}.ui-field-contain input.ui-slider-input{margin:0}input.ui-slider-input,.ui-field-contain input.ui-slider-input{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;-ms-box-sizing:content-box;box-sizing:content-box}.ui-slider-input::-webkit-outer-spin-button{margin:0}select.ui-slider-switch{display:none}div.ui-slider{position:relative;display:inline-block;overflow:visible;height:15px;padding:0;margin:0 2% 0 20px;top:4px;width:65%}div.ui-slider-mini{height:12px;margin-left:10px;top:2px}div.ui-slider-bg{border:none;height:100%;padding-right:8px}.ui-controlgroup a.ui-slider-handle,a.ui-btn.ui-slider-handle{position:absolute;z-index:1;top:50%;width:28px;height:28px;margin:-15px 0 0 -15px;outline:0}a.ui-btn.ui-slider-handle .ui-btn-inner{padding:0;height:100%}div.ui-slider-mini a.ui-slider-handle{height:14px;width:14px;margin:-8px 0 0 -7px}div.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:-9px 0 0 -9px;border-top:none}@media all and (min-width:450px){.ui-field-contain label.ui-slider{vertical-align:top;display:inline-block;width:20%;margin:0 2% 0 0}.ui-field-contain div.ui-slider{width:43%}.ui-field-contain div.ui-slider-switch{width:5.5em}}div.ui-slider-switch{height:32px;margin-left:0;width:5.8em}a.ui-slider-handle-snapping{-webkit-transition:left 70ms linear;-moz-transition:left 70ms linear}div.ui-slider-switch .ui-slider-handle{margin:1px 0 0 -15px}.ui-slider-inneroffset{margin:0 16px;position:relative;z-index:1}div.ui-slider-switch.ui-slider-mini{width:5em;height:29px}div.ui-slider-switch.ui-slider-mini .ui-slider-inneroffset{margin:0 15px 0 14px}div.ui-slider-switch.ui-slider-mini .ui-slider-handle{width:25px;height:25px;margin:1px 0 0 -13px}div.ui-slider-switch.ui-slider-mini a.ui-slider-handle .ui-btn-inner{height:30px;width:30px;padding:0;margin:0}span.ui-slider-label{position:absolute;text-align:center;width:100%;overflow:hidden;font-size:16px;top:0;line-height:2;min-height:100%;border-width:0;white-space:nowrap}.ui-slider-mini span.ui-slider-label{font-size:14px}span.ui-slider-label-a{z-index:1;left:0;text-indent:-1.5em}span.ui-slider-label-b{z-index:0;right:0;text-indent:1.5em}.ui-slider-inline{width:120px;display:inline-block} \ No newline at end of file | ||
diff --git a/htdocs/jquery-mobile.js b/htdocs/jquery-mobile.js new file mode 100644 index 0000000..e0d02a9 --- /dev/null +++ b/htdocs/jquery-mobile.js | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | /*! jQuery Mobile vGit Build: SHA1: b49cc06499abf8f987cf90f35349cfac0918c939 <> Date: Tue Oct 2 11:22:34 2012 -0700 jquerymobile.com | jquery.org/license !*/ | ||
| 2 | (function(a,b,c){typeof define=="function"&&define.amd?define(["jquery"],function(d){return c(d,a,b),d.mobile}):c(a.jQuery,a,b)})(this,document,function(a,b,c,d){(function(a,b,d){var e={};a.mobile=a.extend({},{version:"1.2.0",ns:"",subPageUrlKey:"ui-page",activePageClass:"ui-page-active",activeBtnClass:"ui-btn-active",focusClass:"ui-focus",ajaxEnabled:!0,hashListeningEnabled:!0,linkBindingEnabled:!0,defaultPageTransition:"fade",maxTransitionWidth:!1,minScrollBack:250,touchOverflowEnabled:!1,defaultDialogTransition:"pop",pageLoadErrorMessage:"Error Loading Page",pageLoadErrorMessageTheme:"e",phonegapNavigationEnabled:!1,autoInitializePage:!0,pushStateEnabled:!0,ignoreContentEnabled:!1,orientationChangeEnabled:!0,buttonMarkup:{hoverDelay:200},keyCode:{ALT:18,BACKSPACE:8,CAPS_LOCK:20,COMMA:188,COMMAND:91,COMMAND_LEFT:91,COMMAND_RIGHT:93,CONTROL:17,DELETE:46,DOWN:40,END:35,ENTER:13,ESCAPE:27,HOME:36,INSERT:45,LEFT:37,MENU:93,NUMPAD_ADD:107,NUMPAD_DECIMAL:110,NUMPAD_DIVIDE:111,NUMPAD_ENTER:108,NUMPAD_MULTIPLY:106,NUMPAD_SUBTRACT:109,PAGE_DOWN:34,PAGE_UP:33,PERIOD:190,RIGHT:39,SHIFT:16,SPACE:32,TAB:9,UP:38,WINDOWS:91},silentScroll:function(d){a.type(d)!=="number"&&(d=a.mobile.defaultHomeScroll),a.event.special.scrollstart.enabled=!1,setTimeout(function(){b.scrollTo(0,d),a(c).trigger("silentscroll",{x:0,y:d})},20),setTimeout(function(){a.event.special.scrollstart.enabled=!0},150)},nsNormalizeDict:e,nsNormalize:function(b){if(!b)return;return e[b]||(e[b]=a.camelCase(a.mobile.ns+b))},getInheritedTheme:function(a,b){var c=a[0],d="",e=/ui-(bar|body|overlay)-([a-z])\b/,f,g;while(c){f=c.className||"";if(f&&(g=e.exec(f))&&(d=g[2]))break;c=c.parentNode}return d||b||"a"},closestPageData:function(a){return a.closest(':jqmData(role="page"), :jqmData(role="dialog")').data("page")},enhanceable:function(a){return this.haveParents(a,"enhance")},hijackable:function(a){return this.haveParents(a,"ajax")},haveParents:function(b,c){if(!a.mobile.ignoreContentEnabled)return b;var d=b.length,e=a(),f,g,h;for(var i=0;i<d;i++){g=b.eq(i),h=!1,f=b[i];while(f){var j=f.getAttribute?f.getAttribute("data-"+a.mobile.ns+c):"";if(j==="false"){h=!0;break}f=f.parentNode}h||(e=e.add(g))}return e},getScreenHeight:function(){return b.innerHeight||a(b).height()}},a.mobile),a.fn.jqmData=function(b,c){var e;return typeof b!="undefined"&&(b&&(b=a.mobile.nsNormalize(b)),arguments.length<2||c===d?e=this.data(b):e=this.data(b,c)),e},a.jqmData=function(b,c,d){var e;return typeof c!="undefined"&&(e=a.data(b,c?a.mobile.nsNormalize(c):c,d)),e},a.fn.jqmRemoveData=function(b){return this.removeData(a.mobile.nsNormalize(b))},a.jqmRemoveData=function(b,c){return a.removeData(b,a.mobile.nsNormalize(c))},a.fn.removeWithDependents=function(){a.removeWithDependents(this)},a.removeWithDependents=function(b){var c=a(b);(c.jqmData("dependents")||a()).remove(),c.remove()},a.fn.addDependents=function(b){a.addDependents(a(this),b)},a.addDependents=function(b,c){var d=a(b).jqmData("dependents")||a();a(b).jqmData("dependents",a.merge(d,c))},a.fn.getEncodedText=function(){return a("<div/>").text(a(this).text()).html()},a.fn.jqmEnhanceable=function(){return a.mobile.enhanceable(this)},a.fn.jqmHijackable=function(){return a.mobile.hijackable(this)};var f=a.find,g=/:jqmData\(([^)]*)\)/g;a.find=function(b,c,d,e){return b=b.replace(g,"[data-"+(a.mobile.ns||"")+"$1]"),f.call(this,b,c,d,e)},a.extend(a.find,f),a.find.matches=function(b,c){return a.find(b,null,null,c)},a.find.matchesSelector=function(b,c){return a.find(c,null,null,[b]).length>0}})(a,this),function(a,b){var c=0,d=Array.prototype.slice,e=a.cleanData;a.cleanData=function(b){for(var c=0,d;(d=b[c])!=null;c++)try{a(d).triggerHandler("remove")}catch(f){}e(b)},a.widget=function(b,c,d){var e,f,g,h,i=b.split(".")[0];b=b.split(".")[1],e=i+"-"+b,d||(d=c,c=a.Widget),a.expr[":"][e]=function(b){return!!a.data(b,e)},a[i]=a[i]||{},f=a[i][b],g=a[i][b]=function(a,b){if(!this._createWidget)return new g(a,b);arguments.length&&this._createWidget(a,b)},a.extend(g,f,{version:d.version,_proto:a.extend({},d),_childConstructors:[]}),h=new c,h.options=a.widget.extend({},h.options),a.each(d,function(b,e){a.isFunction(e)&&(d[b]=function(){var a=function(){return c.prototype[b].apply(this,arguments)},d=function(a){return c.prototype[b].apply(this,a)};return function(){var b=this._super,c=this._superApply,f;return this._super=a,this._superApply=d,f=e.apply(this,arguments),this._super=b,this._superApply=c,f}}())}),g.prototype=a.widget.extend(h,{widgetEventPrefix:b},d,{constructor:g,namespace:i,widgetName:b,widgetBaseClass:e,widgetFullName:e}),f?(a.each(f._childConstructors,function(b,c){var d=c.prototype;a.widget(d.namespace+"."+d.widgetName,g,c._proto)}),delete f._childConstructors):c._childConstructors.push(g),a.widget.bridge(b,g)},a.widget.extend=function(c){var e=d.call(arguments,1),f=0,g=e.length,h,i;for(;f<g;f++)for(h in e[f])i=e[f][h],e[f].hasOwnProperty(h)&&i!==b&&(c[h]=a.isPlainObject(i)?a.widget.extend({},c[h],i):i);return c},a.widget.bridge=function(c,e){var f=e.prototype.widgetFullName;a.fn[c]=function(g){var h=typeof g=="string",i=d.call(arguments,1),j=this;return g=!h&&i.length?a.widget.extend.apply(null,[g].concat(i)):g,h?this.each(function(){var d,e=a.data(this,f);if(!e)return a.error("cannot call methods on "+c+" prior to initialization; "+"attempted to call method '"+g+"'");if(!a.isFunction(e[g])||g.charAt(0)==="_")return a.error("no such method '"+g+"' for "+c+" widget instance");d=e[g].apply(e,i);if(d!==e&&d!==b)return j=d&&d.jquery?j.pushStack(d.get()):d,!1}):this.each(function(){var b=a.data(this,f);b?b.option(g||{})._init():new e(g,this)}),j}},a.Widget=function(a,b){},a.Widget._childConstructors=[],a.Widget.prototype={widgetName:"widget",widgetEventPrefix:"",defaultElement:"<div>",options:{disabled:!1,create:null},_createWidget:function(b,d){d=a(d||this.defaultElement||this)[0],this.element=a(d),this.uuid=c++,this.eventNamespace="."+this.widgetName+this.uuid,this.options=a.widget.extend({},this.options,this._getCreateOptions(),b),this.bindings=a(),this.hoverable=a(),this.focusable=a(),d!==this&&(a.data(d,this.widgetName,this),a.data(d,this.widgetFullName,this),this._on({remove:"destroy"}),this.document=a(d.style?d.ownerDocument:d.document||d),this.window=a(this.document[0].defaultView||this.document[0].parentWindow)),this._create(),this._trigger("create",null,this._getCreateEventData()),this._init()},_getCreateOptions:a.noop,_getCreateEventData:a.noop,_create:a.noop,_init:a.noop,destroy:function(){this._destroy(),this.element.unbind(this.eventNamespace).removeData(this.widgetName).removeData(this.widgetFullName).removeData(a.camelCase(this.widgetFullName)),this.widget().unbind(this.eventNamespace).removeAttr("aria-disabled").removeClass(this.widgetFullName+"-disabled "+"ui-state-disabled"),this.bindings.unbind(this.eventNamespace),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")},_destroy:a.noop,widget:function(){return this.element},option:function(c,d){var e=c,f,g,h;if(arguments.length===0)return a.widget.extend({},this.options);if(typeof c=="string"){e={},f=c.split("."),c=f.shift();if(f.length){g=e[c]=a.widget.extend({},this.options[c]);for(h=0;h<f.length-1;h++)g[f[h]]=g[f[h]]||{},g=g[f[h]];c=f.pop();if(d===b)return g[c]===b?null:g[c];g[c]=d}else{if(d===b)return this.options[c]===b?null:this.options[c];e[c]=d}}return this._setOptions(e),this},_setOptions:function(a){var b;for(b in a)this._setOption(b,a[b]);return this},_setOption:function(a,b){return this.options[a]=b,a==="disabled"&&(this.widget().toggleClass(this.widgetFullName+"-disabled ui-state-disabled",!!b).attr("aria-disabled",b),this.hoverable.removeClass("ui-state-hover"),this.focusable.removeClass("ui-state-focus")),this},enable:function(){return this._setOption("disabled",!1)},disable:function(){return this._setOption("disabled",!0)},_on:function(b,c){c?(b=a(b),this.bindings=this.bindings.add(b)):(c=b,b=this.element);var d=this;a.each(c,function(c,e){function f(){if(d.options.disabled===!0||a(this).hasClass("ui-state-disabled"))return;return(typeof e=="string"?d[e]:e).apply(d,arguments)}typeof e!="string"&&(f.guid=e.guid=e.guid||f.guid||a.guid++);var g=c.match(/^(\w+)\s*(.*)$/),h=g[1]+d.eventNamespace,i=g[2];i?d.widget().delegate(i,h,f):b.bind(h,f)})},_off:function(a,b){b=(b||"").split(" ").join(this.eventNamespace+" ")+this.eventNamespace,a.unbind(b).undelegate(b)},_delay:function(a,b){function c(){return(typeof a=="string"?d[a]:a).apply(d,arguments)}var d=this;return setTimeout(c,b||0)},_hoverable:function(b){this.hoverable=this.hoverable.add(b),this._on(b,{mouseenter:function(b){a(b.currentTarget).addClass("ui-state-hover")},mouseleave:function(b){a(b.currentTarget).removeClass("ui-state-hover")}})},_focusable:function(b){this.focusable=this.focusable.add(b),this._on(b,{focusin:function(b){a(b.currentTarget).addClass("ui-state-focus")},focusout:function(b){a(b.currentTarget).removeClass("ui-state-focus")}})},_trigger:function(b,c,d){var e,f,g=this.options[b];d=d||{},c=a.Event(c),c.type=(b===this.widgetEventPrefix?b:this.widgetEventPrefix+b).toLowerCase(),c.target=this.element[0],f=c.originalEvent;if(f)for(e in f)e in c||(c[e]=f[e]);return this.element.trigger(c,d),!(a.isFunction(g)&&g.apply(this.element[0],[c].concat(d))===!1||c.isDefaultPrevented())}},a.each({show:"fadeIn",hide:"fadeOut"},function(b,c){a.Widget.prototype["_"+b]=function(d,e,f){typeof e=="string"&&(e={effect:e});var g,h=e?e===!0||typeof e=="number"?c:e.effect||c:b;e=e||{},typeof e=="number"&&(e={duration:e}),g=!a.isEmptyObject(e),e.complete=f,e.delay&&d.delay(e.delay),g&&a.effects&&(a.effects.effect[h]||a.uiBackCompat!==!1&&a.effects[h])?d[b](e):h!==b&&d[h]?d[h](e.duration,e.easing,f):d.queue(function(c){a(this)[b](),f&&f.call(d[0]),c()})}}),a.uiBackCompat!==!1&&(a.Widget.prototype._getCreateOptions=function(){return a.metadata&&a.metadata.get(this.element[0])[this.widgetName]})}(a),function(a,b){a.widget("mobile.widget",{_createWidget:function(){a.Widget.prototype._createWidget.apply(this,arguments),this._trigger("init")},_getCreateOptions:function(){var c=this.element,d={};return a.each(this.options,function(a){var e=c.jqmData(a.replace(/[A-Z]/g,function(a){return"-"+a.toLowerCase()}));e!==b&&(d[a]=e)}),d},enhanceWithin:function(b,c){this.enhance(a(this.options.initSelector,a(b)),c)},enhance:function(b,c){var d,e,f=a(b),g=this;f=a.mobile.enhanceable(f),c&&f.length&&(d=a.mobile.closestPageData(f),e=d&&d.keepNativeSelector()||"",f=f.not(e)),f[this.widgetName]()},raise:function(a){throw"Widget ["+this.widgetName+"]: "+a}})}(a),function(a,b){a.extend(a.mobile,{loadingMessageTextVisible:d,loadingMessageTheme:d,loadingMessage:d,showPageLoadingMsg:function(b,c,d){a.mobile.loading("show",b,c,d)},hidePageLoadingMsg:function(){a.mobile.loading("hide")},loading:function(){this.loaderWidget.loader.apply(this.loaderWidget,arguments)}});var c="ui-loader",e=a("html"),f=a(b);a.widget("mobile.loader",{options:{theme:"a",textVisible:!1,html:"",text:"loading"},defaultHtml:"<div class='"+c+"'>"+"<span class='ui-icon ui-icon-loading'></span>"+"<h1></h1>"+"</div>",fakeFixLoader:function(){var b=a("."+a.mobile.activeBtnClass).first();this.element.css({top:a.support.scrollTop&&f.scrollTop()+f.height()/2||b.length&&b.offset().top||100})},checkLoaderPosition:function(){var b=this.element.offset(),c=f.scrollTop(),d=a.mobile.getScreenHeight();if(b.top<c||b.top-c>d)this.element.addClass("ui-loader-fakefix"),this.fakeFixLoader(),f.unbind("scroll",this.checkLoaderPosition).bind("scroll",this.fakeFixLoader)},resetHtml:function(){this.element.html(a(this.defaultHtml).html())},show:function(b,g,h){var i,j,k,l;this.resetHtml(),a.type(b)==="object"?(l=a.extend({},this.options,b),b=l.theme||a.mobile.loadingMessageTheme):(l=this.options,b=b||a.mobile.loadingMessageTheme||l.theme),j=g||a.mobile.loadingMessage||l.text,e.addClass("ui-loading");if(a.mobile.loadingMessage!==!1||l.html)a.mobile.loadingMessageTextVisible!==d?i=a.mobile.loadingMessageTextVisible:i=l.textVisible,this.element.attr("class",c+" ui-corner-all ui-body-"+b+" ui-loader-"+(i||g||b.text?"verbose":"default")+(l.textonly||h?" ui-loader-textonly":"")),l.html?this.element.html(l.html):this.element.find("h1").text(j),this.element.appendTo(a.mobile.pageContainer),this.checkLoaderPosition(),f.bind("scroll",a.proxy(this.checkLoaderPosition,this))},hide:function(){e.removeClass("ui-loading"),a.mobile.loadingMessage&&this.element.removeClass("ui-loader-fakefix"),a(b).unbind("scroll",a.proxy(this.fakeFixLoader,this)),a(b).unbind("scroll",a.proxy(this.checkLoaderPosition,this))}}),f.bind("pagecontainercreate",function(){a.mobile.loaderWidget=a.mobile.loaderWidget||a(a.mobile.loader.prototype.defaultHtml).loader()})}(a,this),function(a,b,c,d){function x(a){while(a&&typeof a.originalEvent!="undefined")a=a.originalEvent;return a}function y(b,c){var e=b.type,f,g,i,k,l,m,n,o,p;b=a.Event(b),b.type=c,f=b.originalEvent,g=a.event.props,e.search(/^(mouse|click)/)>-1&&(g=j);if(f)for(n=g.length,k;n;)k=g[--n],b[k]=f[k];e.search(/mouse(down|up)|click/)>-1&&!b.which&&(b.which=1);if(e.search(/^touch/)!==-1){i=x(f),e=i.touches,l=i.changedTouches,m=e&&e.length?e[0]:l&&l.length?l[0]:d;if(m)for(o=0,p=h.length;o<p;o++)k=h[o],b[k]=m[k]}return b}function z(b){var c={},d,f;while(b){d=a.data(b,e);for(f in d)d[f]&&(c[f]=c.hasVirtualBinding=!0);b=b.parentNode}return c}function A(b,c){var d;while(b){d=a.data(b,e);if(d&&(!c||d[c]))return b;b=b.parentNode}return null}function B(){r=!1}function C(){r=!0}function D(){v=0,p.length=0,q=!1,C()}function E(){B()}function F(){G(),l=setTimeout(function(){l=0,D()},a.vmouse.resetTimerDuration)}function G(){l&&(clearTimeout(l),l=0)}function H(b,c,d){var e;if(d&&d[b]||!d&&A(c.target,b))e=y(c,b),a(c.target).trigger(e);return e}function I(b){var c=a.data(b.target,f);if(!q&&(!v||v!==c)){var d=H("v"+b.type,b);d&&(d.isDefaultPrevented()&&b.preventDefault(),d.isPropagationStopped()&&b.stopPropagation(),d.isImmediatePropagationStopped()&&b.stopImmediatePropagation())}}function J(b){var c=x(b).touches,d,e;if(c&&c.length===1){d=b.target,e=z(d);if(e.hasVirtualBinding){v=u++,a.data(d,f,v),G(),E(),o=!1;var g=x(b).touches[0];m=g.pageX,n=g.pageY,H("vmouseover",b,e),H("vmousedown",b,e)}}}function K(a){if(r)return;o||H("vmousecancel",a,z(a.target)),o=!0,F()}function L(b){if(r)return;var c=x(b).touches[0],d=o,e=a.vmouse.moveDistanceThreshold,f=z(b.target);o=o||Math.abs(c.pageX-m)>e||Math.abs(c.pageY-n)>e,o&&!d&&H("vmousecancel",b,f),H("vmousemove",b,f),F()}function M(a){if(r)return;C();var b=z(a.target),c;H("vmouseup",a,b);if(!o){var d=H("vclick",a,b);d&&d.isDefaultPrevented()&&(c=x(a).changedTouches[0],p.push({touchID:v,x:c.clientX,y:c.clientY}),q=!0)}H("vmouseout",a,b),o=!1,F()}function N(b){var c=a.data(b,e),d;if(c)for(d in c)if(c[d])return!0;return!1}function O(){}function P(b){var c=b.substr(1);return{setup:function(d,f){N(this)||a.data(this,e,{});var g=a.data(this,e);g[b]=!0,k[b]=(k[b]||0)+1,k[b]===1&&t.bind(c,I),a(this).bind(c,O),s&&(k.touchstart=(k.touchstart||0)+1,k.touchstart===1&&t.bind("touchstart",J).bind("touchend",M).bind("touchmove",L).bind("scroll",K))},teardown:function(d,f){--k[b],k[b]||t.unbind(c,I),s&&(--k.touchstart,k.touchstart||t.unbind("touchstart",J).unbind("touchmove",L).unbind("touchend",M).unbind("scroll",K));var g=a(this),h=a.data(this,e);h&&(h[b]=!1),g.unbind(c,O),N(this)||g.removeData(e)}}}var e="virtualMouseBindings",f="virtualTouchID",g="vmouseover vmousedown vmousemove vmouseup vclick vmouseout vmousecancel".split(" "),h="clientX clientY pageX pageY screenX screenY".split(" "),i=a.event.mouseHooks?a.event.mouseHooks.props:[],j=a.event.props.concat(i),k={},l=0,m=0,n=0,o=!1,p=[],q=!1,r=!1,s="addEventListener"in c,t=a(c),u=1,v=0,w;a.vmouse={moveDistanceThreshold:10,clickDistanceThreshold:10,resetTimerDuration:1500};for(var Q=0;Q<g.length;Q++)a.event.special[g[Q]]=P(g[Q]);s&&c.addEventListener("click",function(b){var c=p.length,d=b.target,e,g,h,i,j,k;if(c){e=b.clientX,g=b.clientY,w=a.vmouse.clickDistanceThreshold,h=d;while(h){for(i=0;i<c;i++){j=p[i],k=0;if(h===d&&Math.abs(j.x-e)<w&&Math.abs(j.y-g)<w||a.data(h,f)===j.touchID){b.preventDefault(),b.stopPropagation();return}}h=h.parentNode}}},!0)}(a,b,c),function(a,b){var d={touch:"ontouchend"in c};a.mobile=a.mobile||{},a.mobile.support=a.mobile.support||{},a.extend(a.support,d),a.extend(a.mobile.support,d)}(a),function(a,b,d){function j(b,c,d){var e=d.type;d.type=c,a.event.handle.call(b,d),d.type=e}a.each("touchstart touchmove touchend tap taphold swipe swipeleft swiperight scrollstart scrollstop".split(" "),function(b,c){a.fn[c]=function(a){return a?this.bind(c,a):this.trigger(c)},a.attrFn&&(a.attrFn[c]=!0)});var e=a.mobile.support.touch,f="touchmove scroll",g=e?"touchstart":"mousedown",h=e?"touchend":"mouseup",i=e?"touchmove":"mousemove";a.event.special.scrollstart={enabled:!0,setup:function(){function g(a,c){d=c,j(b,d?"scrollstart":"scrollstop",a)}var b=this,c=a(b),d,e;c.bind(f,function(b){if(!a.event.special.scrollstart.enabled)return;d||g(b,!0),clearTimeout(e),e=setTimeout(function(){g(b,!1)},50)})}},a.event.special.tap={tapholdThreshold:750,setup:function(){var b=this,d=a(b);d.bind("vmousedown",function(e){function i(){clearTimeout(h)}function k(){i(),d.unbind("vclick",l).unbind("vmouseup",i),a(c).unbind("vmousecancel",k)}function l(a){k(),f===a.target&&j(b,"tap",a)}if(e.which&&e.which!==1)return!1;var f=e.target,g=e.originalEvent,h;d.bind("vmouseup",i).bind("vclick",l),a(c).bind("vmousecancel",k),h=setTimeout(function(){j(b,"taphold",a.Event("taphold",{target:f}))},a.event.special.tap.tapholdThreshold)})}},a.event.special.swipe={scrollSupressionThreshold:30,durationThreshold:1e3,horizontalDistanceThreshold:30,verticalDistanceThreshold:75,setup:function(){var b=this,c=a(b);c.bind(g,function(b){function j(b){if(!f)return;var c=b.originalEvent.touches?b.originalEvent.touches[0]:b;g={time:(new Date).getTime(),coords:[c.pageX,c.pageY]},Math.abs(f.coords[0]-g.coords[0])>a.event.special.swipe.scrollSupressionThreshold&&b.preventDefault()}var e=b.originalEvent.touches?b.originalEvent.touches[0]:b,f={time:(new Date).getTime(),coords:[e.pageX,e.pageY],origin:a(b.target)},g;c.bind(i,j).one(h,function(b){c.unbind(i,j),f&&g&&g.time-f.time<a.event.special.swipe.durationThreshold&&Math.abs(f.coords[0]-g.coords[0])>a.event.special.swipe.horizontalDistanceThreshold&&Math.abs(f.coords[1]-g.coords[1])<a.event.special.swipe.verticalDistanceThreshold&&f.origin.trigger("swipe").trigger(f.coords[0]>g.coords[0]?"swipeleft":"swiperight"),f=g=d})})}},a.each({scrollstop:"scrollstart",taphold:"tap",swipeleft:"swipe",swiperight:"swipe"},function(b,c){a.event.special[b]={setup:function(){a(this).bind(c,a.noop)}}})}(a,this),function(a,c){a.extend(a.support,{orientation:"orientation"in b&&"onorientationchange"in b})}(a),function(a){a.event.special.throttledresize={setup:function(){a(this).bind("resize",c)},teardown:function(){a(this).unbind("resize",c)}};var b=250,c=function(){f=(new Date).getTime(),g=f-d,g>=b?(d=f,a(this).trigger("throttledresize")):(e&&clearTimeout(e),e=setTimeout(c,b-g))},d=0,e,f,g}(a),function(a,b){function o(){var a=g();a!==h&&(h=a,d.trigger(e))}var d=a(b),e="orientationchange",f,g,h,i,j,k={0:!0,180:!0};if(a.support.orientation){var l=b.innerWidth||a(b).width(),m=b.innerHeight||a(b).height(),n=50;i=l>m&&l-m>n,j=k[b.orientation];if(i&&j||!i&&!j)k={"-90":!0,90:!0}}a.event.special.orientationchange=a.extend({},a.event.special.orientationchange,{setup:function(){if(a.support.orientation&&!a.event.special.orientationchange.disabled)return!1;h=g(),d.bind("throttledresize",o)},teardown:function(){if(a.support.orientation&&!a.event.special.orientationchange.disabled)return!1;d.unbind("throttledresize",o)},add:function(a){var b=a.handler;a.handler=function(a){return a.orientation=g(),b.apply(this,arguments)}}}),a.event.special.orientationchange.orientation=g=function(){var d=!0,e=c.documentElement;return a.support.orientation?d=k[b.orientation]:d=e&&e.clientWidth/e.clientHeight<1.1,d?"portrait":"landscape"},a.fn[e]=function(a){return a?this.bind(e,a):this.trigger(e)},a.attrFn&&(a.attrFn[e]=!0)}(a,this),function(a,d){var e=a(b),f=a("html");a.mobile.media=function(){var b={},d=a("<div id='jquery-mediatest'></div>"),e=a("<body>").append(d);return function(a){if(!(a in b)){var g=c.createElement("style"),h="@media "+a+" { #jquery-mediatest { position:absolute; } }";g.type="text/css",g.styleSheet?g.styleSheet.cssText=h:g.appendChild(c.createTextNode(h)),f.prepend(e).prepend(g),b[a]=d.css("position")==="absolute",e.add(g).remove()}return b[a]}}()}(a),function(a,d){function e(a){var b=a.charAt(0).toUpperCase()+a.substr(1),c=(a+" "+h.join(b+" ")+b).split(" ");for(var e in c)if(g[c[e]]!==d)return!0}function m(a,b,d){var e=c.createElement("div"),f=function(a){return a.charAt(0).toUpperCase()+a.substr(1)},g=function(a){return"-"+a.charAt(0).toLowerCase()+a.substr(1)+"-"},i=function(c){var d=g(c)+a+": "+b+";",h=f(c),i=h+f(a);e.setAttribute("style",d),!e.style[i]||(k=!0)},j=d?[d]:h,k;for(var l=0;l<j.length;l++)i(j[l]);return!!k}function n(){var b="transform-3d";return m("perspective","10px","moz")||a.mobile.media("(-"+h.join("-"+b+"),(-")+"-"+b+"),("+b+")")}function o(){var b=location.protocol+"//"+location.host+location.pathname+"ui-dir/",c=a("head base"),d=null,e="",g,h;return c.length?e=c.attr("href"):c=d=a("<base>",{href:b}).appendTo("head"),g=a("<a href='testurl' />").prependTo(f),h=g[0].href,c[0].href=e||location.pathname,d&&d.remove(),h.indexOf(b)===0}function p(){var a=c.createElement("x"),d=c.documentElement,e=b.getComputedStyle,f;return"pointerEvents"in a.style?(a.style.pointerEvents="auto",a.style.pointerEvents="x",d.appendChild(a),f=e&&e(a,"").pointerEvents==="auto",d.removeChild(a),!!f):!1}function q(){var a=c.createElement("div");return typeof a.getBoundingClientRect!="undefined"}var f=a("<body>").prependTo("html"),g=f[0].style,h=["Webkit","Moz","O"],i="palmGetResource"in b,j=b.opera,k=b.operamini&&{}.toString.call(b.operamini)==="[object OperaMini]",l=b.blackberry&&!e("-webkit-transform");a.extend(a.mobile,{browser:{}}),a.mobile.browser.ie=function(){var a=3,b=c.createElement("div"),d=b.all||[];do b.innerHTML="<!--[if gt IE "+ ++a+"]><br><![endif]-->";while(d[0]);return a>4?a:!a}(),a.extend(a.support,{cssTransitions:"WebKitTransitionEvent"in b||m("transition","height 100ms linear")&&!j,pushState:"pushState"in history&&"replaceState"in history,mediaquery:a.mobile.media("only all"),cssPseudoElement:!!e("content"),touchOverflow:!!e("overflowScrolling"),cssTransform3d:n(),boxShadow:!!e("boxShadow")&&!l,scrollTop:("pageXOffset"in b||"scrollTop"in c.documentElement||"scrollTop"in f[0])&&!i&&!k,dynamicBaseTag:o(),cssPointerEvents:p(),boundingRect:q()}),f.remove();var r=function(){var a=b.navigator.userAgent;return a.indexOf("Nokia")>-1&&(a.indexOf("Symbian/3")>-1||a.indexOf("Series60/5")>-1)&&a.indexOf("AppleWebKit")>-1&&a.match(/(BrowserNG|NokiaBrowser)\/7\.[0-3]/)}();a.mobile.gradeA=function(){return(a.support.mediaquery||a.mobile.browser.ie&&a.mobile.browser.ie>=7)&&(a.support.boundingRect||a.fn.jquery.match(/1\.[0-7+]\.[0-9+]?/)!==null)},a.mobile.ajaxBlacklist=b.blackberry&&!b.WebKitPoint||k||r,r&&a(function(){a("head link[rel='stylesheet']").attr("rel","alternate stylesheet").attr("rel","stylesheet")}),a.support.boxShadow||a("html").addClass("ui-mobile-nosupport-boxshadow")}(a),function(a,b){a.widget("mobile.page",a.mobile.widget,{options:{theme:"c",domCache:!1,keepNativeDefault:":jqmData(role='none'), :jqmData(role='nojs')"},_create:function(){var a=this;if(a._trigger("beforecreate")===!1)return!1;a.element.attr("tabindex","0").addClass("ui-page ui-body-"+a.options.theme).bind("pagebeforehide",function(){a.removeContainerBackground()}).bind("pagebeforeshow",function(){a.setContainerBackground()})},removeContainerBackground:function(){a.mobile.pageContainer.removeClass("ui-overlay-"+a.mobile.getInheritedTheme(this.element.parent()))},setContainerBackground:function(b){this.options.theme&&a.mobile.pageContainer.addClass("ui-overlay-"+(b||this.options.theme))},keepNativeSelector:function(){var b=this.options,c=b.keepNative&&a.trim(b.keepNative);return c&&b.keepNative!==b.keepNativeDefault?[b.keepNative,b.keepNativeDefault].join(", "):b.keepNativeDefault}})}(a),function(a,b,d){function k(a){return a=a||location.href,"#"+a.replace(/^[^#]*#?(.*)$/,"$1")}var e="hashchange",f=c,g,h=a.event.special,i=f.documentMode,j="on"+e in b&&(i===d||i>7);a.fn[e]=function(a){return a?this.bind(e,a):this.trigger(e)},a.fn[e].delay=50,h[e]=a.extend(h[e],{setup:function(){if(j)return!1;a(g.start)},teardown:function(){if(j)return!1;a(g.stop)}}),g=function(){function n(){var c=k(),d=m(h);c!==h?(l(h=c,d),a(b).trigger(e)):d!==h&&(location.href=location.href.replace(/#.*/,"")+d),g=setTimeout(n,a.fn[e].delay)}var c={},g,h=k(),i=function(a){return a},l=i,m=i;return c.start=function(){g||n()},c.stop=function(){g&&clearTimeout(g),g=d},a.browser.msie&&!j&&function(){var b,d;c.start=function(){b||(d=a.fn[e].src,d=d&&d+k(),b=a('<iframe tabindex="-1" title="empty"/>').hide().one("load",function(){d||l(k()),n()}).attr("src",d||"javascript:0").insertAfter("body")[0].contentWindow,f.onpropertychange=function(){try{event.propertyName==="title"&&(b.document.title=f.title)}catch(a){}})},c.stop=i,m=function(){return k(b.location.href)},l=function(c,d){var g=b.document,h=a.fn[e].domain;c!==d&&(g.title=f.title,g.open(),h&&g.write('<script>document.domain="'+h+'"</script>'),g.close(),b.location.hash=c)}}(),c}()}(a,this),function(a,b,c){var d=function(d){return d===c&&(d=!0),function(c,e,f,g){var h=new a.Deferred,i=e?" reverse":"",j=a.mobile.urlHistory.getActive(),k=j.lastScroll||a.mobile.defaultHomeScroll,l=a.mobile.getScreenHeight(),m=a.mobile.maxTransitionWidth!==!1&&a(b).width()>a.mobile.maxTransitionWidth,n=!a.support.cssTransitions||m||!c||c==="none"||Math.max(a(b).scrollTop(),k)>a.mobile.getMaxScrollForTransition(),o=" ui-page-pre-in",p=function(){a.mobile.pageContainer.toggleClass("ui-mobile-viewport-transitioning viewport-"+c)},q=function(){a.event.special.scrollstart.enabled=!1,b.scrollTo(0,k),setTimeout(function(){a.event.special.scrollstart.enabled=!0},150)},r=function(){g.removeClass(a.mobile.activePageClass+" out in reverse "+c).height("")},s=function(){d?g.animationComplete(t):t(),g.height(l+a(b).scrollTop()).addClass(c+" out"+i)},t=function(){g&&d&&r(),u()},u=function(){f.css("z-index",-10),f.addClass(a.mobile.activePageClass+o),a.mobile.focusPage(f),f.height(l+k),q(),f.css("z-index",""),n||f.animationComplete(v),f.removeClass(o).addClass(c+" in"+i),n&&v()},v=function(){d||g&&r(),f.removeClass("out in reverse "+c).height(""),p(),a(b).scrollTop()!==k&&q(),h.resolve(c,e,f,g,!0)};return p(),g&&!n?s():t(),h.promise()}},e=d(),f=d(!1),g=function(){return a.mobile.getScreenHeight()*3};a.mobile.defaultTransitionHandler=e,a.mobile.transitionHandlers={"default":a.mobile.defaultTransitionHandler,sequential:e,simultaneous:f},a.mobile.transitionFallbacks={},a.mobile._maybeDegradeTransition=function(b){return b&&!a.support.cssTransform3d&&a.mobile.transitionFallbacks[b]&&(b=a.mobile.transitionFallbacks[b]),b},a.mobile.getMaxScrollForTransition=a.mobile.getMaxScrollForTransition||g}(a,this),function(a,d){function u(b){!!i&&(!i.closest("."+a.mobile.activePageClass).length||b)&&i.removeClass(a.mobile.activeBtnClass),i=null}function v(){m=!1,l.length>0&&a.mobile.changePage.apply(null,l.pop())}function z(b,c,d,e){c&&c.data("page")._trigger("beforehide",null,{nextPage:b}),b.data("page")._trigger("beforeshow",null,{prevPage:c||a("")}),a.mobile.hidePageLoadingMsg(),d=a.mobile._maybeDegradeTransition(d);var f=a.mobile.transitionHandlers[d||"default"]||a.mobile.defaultTransitionHandler,g=f(d,e,b,c);return g.done(function(){c&&c.data("page")._trigger("hide",null,{nextPage:b}),b.data("page")._trigger("show",null,{prevPage:c||a("")})}),g}function A(){var b=a("."+a.mobile.activePageClass),c=parseFloat(b.css("padding-top")),d=parseFloat(b.css("padding-bottom")),e=parseFloat(b.css("border-top-width")),f=parseFloat(b.css("border-bottom-width"));b.css("min-height",s()-c-d-e-f)}function B(b,c){c&&b.attr("data-"+a.mobile.ns+"role",c),b.page()}function C(a){while(a){if(typeof a.nodeName=="string"&&a.nodeName.toLowerCase()==="a")break;a=a.parentNode}return a}function D(b){var c=a(b).closest(".ui-page").jqmData("url"),d=q.hrefNoHash;if(!c||!h.isPath(c))c=d;return h.makeUrlAbsolute(c,d)}var e=a(b),f=a("html"),g=a("head"),h={urlParseRE:/^(((([^:\/#\?]+:)?(?:(\/\/)((?:(([^:@\/#\?]+)(?:\:([^:@\/#\?]+))?)@)?(([^:\/#\?\]\[]+|\[[^\/\]@#?]+\])(?:\:([0-9]+))?))?)?)?((\/?(?:[^\/\?#]+\/+)*)([^\?#]*)))?(\?[^#]+)?)(#.*)?/,getLocation:function(a){var b=a?this.parseUrl(a):location,c=this.parseUrl(a||location.href).hash;return c=c==="#"?"":c,b.protocol+"//"+b.host+b.pathname+b.search+c},parseLocation:function(){return this.parseUrl(this.getLocation())},parseUrl:function(b){if(a.type(b)==="object")return b;var c=h.urlParseRE.exec(b||"")||[];return{href:c[0]||"",hrefNoHash:c[1]||"",hrefNoSearch:c[2]||"",domain:c[3]||"",protocol:c[4]||"",doubleSlash:c[5]||"",authority:c[6]||"",username:c[8]||"",password:c[9]||"",host:c[10]||"",hostname:c[11]||"",port:c[12]||"",pathname:c[13]||"",directory:c[14]||"",filename:c[15]||"",search:c[16]||"",hash:c[17]||""}},makePathAbsolute:function(a,b){if(a&&a.charAt(0)==="/")return a;a=a||"",b=b?b.replace(/^\/|(\/[^\/]*|[^\/]+)$/g,""):"";var c=b?b.split("/"):[],d=a.split("/");for(var e=0;e<d.length;e++){var f=d[e];switch(f){case".":break;case"..":c.length&&c.pop();break;default:c.push(f)}}return"/"+c.join("/")},isSameDomain:function(a,b){return h.parseUrl(a).domain===h.parseUrl(b).domain},isRelativeUrl:function(a){return h.parseUrl(a).protocol===""},isAbsoluteUrl:function(a){return h.parseUrl(a).protocol!==""},makeUrlAbsolute:function(a,b){if(!h.isRelativeUrl(a))return a;b===d&&(b=q);var c=h.parseUrl(a),e=h.parseUrl(b),f=c.protocol||e.protocol,g=c.protocol?c.doubleSlash:c.doubleSlash||e.doubleSlash,i=c.authority||e.authority,j=c.pathname!=="",k=h.makePathAbsolute(c.pathname||e.filename,e.pathname),l=c.search||!j&&e.search||"",m=c.hash;return f+g+i+k+l+m},addSearchParams:function(b,c){var d=h.parseUrl(b),e=typeof c=="object"?a.param(c):c,f=d.search||"?";return d.hrefNoSearch+f+(f.charAt(f.length-1)!=="?"?"&":"")+e+(d.hash||"")},convertUrlToDataUrl:function(a){var c=h.parseUrl(a);return h.isEmbeddedPage(c)?c.hash.split(n)[0].replace(/^#/,""):h.isSameDomain(c,q)?c.hrefNoHash.replace(q.domain,"").split(n)[0]:b.decodeURIComponent(a)},get:function(a){return a===d&&(a=h.parseLocation().hash),h.stripHash(a).replace(/[^\/]*\.[^\/*]+$/,"")},getFilePath:function(b){var c="&"+a.mobile.subPageUrlKey;return b&&b.split(c)[0].split(n)[0]},set:function(a){location.hash=a},isPath:function(a){return/\//.test(a)},clean:function(a){return a.replace(q.domain,"")},stripHash:function(a){return a.replace(/^#/,"")},cleanHash:function(a){return h.stripHash(a.replace(/\?.*$/,"").replace(n,""))},isHashValid:function(a){return/^#[^#]+$/.test(a)},isExternal:function(a){var b=h.parseUrl(a);return b.protocol&&b.domain!==p.domain?!0:!1},hasProtocol:function(a){return/^(:?\w+:)/.test(a)},isFirstPageUrl:function(b){var c=h.parseUrl(h.makeUrlAbsolute(b,q)),e=c.hrefNoHash===p.hrefNoHash||r&&c.hrefNoHash===q.hrefNoHash,f=a.mobile.firstPage,g=f&&f[0]?f[0].id:d;return e&&(!c.hash||c.hash==="#"||g&&c.hash.replace(/^#/,"")===g)},isEmbeddedPage:function(a){var b=h.parseUrl(a);return b.protocol!==""?b.hash&&(b.hrefNoHash===p.hrefNoHash||r&&b.hrefNoHash===q.hrefNoHash):/^#/.test(b.href)},isPermittedCrossDomainRequest:function(b,c){return a.mobile.allowCrossDomainPages&&b.protocol==="file:"&&c.search(/^https?:/)!==-1}},i=null,j={stack:[],activeIndex:0,getActive:function(){return j.stack[j.activeIndex]},getPrev:function(){return j.stack[j.activeIndex-1]},getNext:function(){return j.stack[j.activeIndex+1]},addNew:function(a,b,c,d,e){j.getNext()&&j.clearForward(),j.stack.push({url:a,transition:b,title:c,pageUrl:d,role:e}),j.activeIndex=j.stack.length-1},clearForward:function(){j.stack=j.stack.slice(0,j.activeIndex+1)},directHashChange:function(b){var c,e,f,g=this.getActive();a.each(j.stack,function(a,d){decodeURIComponent(b.currentUrl)===decodeURIComponent(d.url)&&(c=a<j.activeIndex,e=!c,f=a)}),this.activeIndex=f!==d?f:this.activeIndex,c?(b.either||b.isBack)(!0):e&&(b.either||b.isForward)(!1)},ignoreNextHashChange:!1},k="[tabindex],a,button:visible,select:visible,input",l=[],m=!1,n="&ui-state=dialog",o=g.children("base"),p=h.parseLocation(),q=o.length?h.parseUrl(h.makeUrlAbsolute(o.attr("href"),p.href)):p,r=p.hrefNoHash!==q.hrefNoHash,s=a.mobile.getScreenHeight,t=a.support.dynamicBaseTag?{element:o.length?o:a("<base>",{href:q.hrefNoHash}).prependTo(g),set:function(a){t.element.attr("href",h.makeUrlAbsolute(a,q))},reset:function(){t.element.attr("href",q.hrefNoHash)}}:d;a.mobile.back=function(){var a=b.navigator;this.phonegapNavigationEnabled&&a&&a.app&&a.app.backHistory?a.app.backHistory():b.history.back()},a.mobile.focusPage=function(a){var b=a.find("[autofocus]"),c=a.find(".ui-title:eq(0)");if(b.length){b.focus();return}c.length?c.focus():a.focus()};var w=!0,x,y;x=function(){if(!w)return;var b=a.mobile.urlHistory.getActive();if(b){var c=e.scrollTop();b.lastScroll=c<a.mobile.minScrollBack?a.mobile.defaultHomeScroll:c}},y=function(){setTimeout(x,100)},e.bind(a.support.pushState?"popstate":"hashchange",function(){w=!1}),e.one(a.support.pushState?"popstate":"hashchange",function(){w=!0}),e.one("pagecontainercreate",function(){a.mobile.pageContainer.bind("pagechange",function(){w=!0,e.unbind("scrollstop",y),e.bind("scrollstop",y)})}),e.bind("scrollstop",y),a.mobile._maybeDegradeTransition=a.mobile._maybeDegradeTransition||function(a){return a},a.fn.animationComplete=function(b){return a.support.cssTransitions?a(this).one("webkitAnimationEnd animationend",b):(setTimeout(b,0),a(this))},a.mobile.path=h,a.mobile.base=t,a.mobile.urlHistory=j,a.mobile.dialogHashKey=n,a.mobile.allowCrossDomainPages=!1,a.mobile.getDocumentUrl=function(b){return b?a.extend({},p):p.href},a.mobile.getDocumentBase=function(b){return b?a.extend({},q):q.href},a.mobile._bindPageRemove=function(){var b=a(this);!b.data("page").options.domCache&&b.is(":jqmData(external-page='true')")&&b.bind("pagehide.remove",function(){var b=a(this),c=new a.Event("pageremove");b.trigger(c),c.isDefaultPrevented()||b.removeWithDependents()})},a.mobile.loadPage=function(b,c){var e=a.Deferred(),f=a.extend({},a.mobile.loadPage.defaults,c),g=null,i=null,j=function(){var b=a.mobile.activePage&&D(a.mobile.activePage);return b||q.hrefNoHash},k=h.makeUrlAbsolute(b,j());f.data&&f.type==="get"&&(k=h.addSearchParams(k,f.data),f.data=d),f.data&&f.type==="post"&&(f.reloadPage=!0);var l=h.getFilePath(k),m=h.convertUrlToDataUrl(k);f.pageContainer=f.pageContainer||a.mobile.pageContainer,g=f.pageContainer.children("[data-"+a.mobile.ns+"url='"+m+"']"),g.length===0&&m&&!h.isPath(m)&&(g=f.pageContainer.children("#"+m).attr("data-"+a.mobile.ns+"url",m).jqmData("url",m));if(g.length===0)if(a.mobile.firstPage&&h.isFirstPageUrl(l))a.mobile.firstPage.parent().length&&(g=a(a.mobile.firstPage));else if(h.isEmbeddedPage(l))return e.reject(k,c),e.promise();if(g.length){if(!f.reloadPage)return B(g,f.role),e.resolve(k,c,g),e.promise();i=g}var n=f.pageContainer,o=new a.Event("pagebeforeload"),r={url:b,absUrl:k,dataUrl:m,deferred:e,options:f};n.trigger(o,r);if(o.isDefaultPrevented())return e.promise();if(f.showLoadMsg)var s=setTimeout(function(){a.mobile.showPageLoadingMsg()},f.loadMsgDelay),u=function(){clearTimeout(s),a.mobile.hidePageLoadingMsg()};return t&&t.reset(),!a.mobile.allowCrossDomainPages&&!h.isSameDomain(p,k)?e.reject(k,c):a.ajax({url:l,type:f.type,data:f.data,dataType:"html",success:function(d,j,n){var o=a("<div></div>"),p=d.match(/<title[^>]*>([^<]*)/)&&RegExp.$1,q=new RegExp("(<[^>]+\\bdata-"+a.mobile.ns+"role=[\"']?page[\"']?[^>]*>)"),s=new RegExp("\\bdata-"+a.mobile.ns+"url=[\"']?([^\"'>]*)[\"']?");q.test(d)&&RegExp.$1&&s.test(RegExp.$1)&&RegExp.$1&&(b=l=h.getFilePath(a("<div>"+RegExp.$1+"</div>").text())),t&&t.set(l),o.get(0).innerHTML=d,g=o.find(":jqmData(role='page'), :jqmData(role='dialog')").first(),g.length||(g=a("<div data-"+a.mobile.ns+"role='page'>"+d.split(/<\/?body[^>]*>/gmi)[1]+"</div>")),p&&!g.jqmData("title")&&(~p.indexOf("&")&&(p=a("<div>"+p+"</div>").text()),g.jqmData("title",p));if(!a.support.dynamicBaseTag){var v=h.get(l);g.find("[src], link[href], a[rel='external'], :jqmData(ajax='false'), a[target]").each(function(){var b=a(this).is("[href]")?"href":a(this).is("[src]")?"src":"action",c=a(this).attr(b);c=c.replace(location.protocol+"//"+location.host+location.pathname,""),/^(\w+:|#|\/)/.test(c)||a(this).attr(b,v+c)})}g.attr("data-"+a.mobile.ns+"url",h.convertUrlToDataUrl(l)).attr("data-"+a.mobile.ns+"external-page",!0).appendTo(f.pageContainer),g.one("pagecreate",a.mobile._bindPageRemove),B(g,f.role),k.indexOf("&"+a.mobile.subPageUrlKey)>-1&&(g=f.pageContainer.children("[data-"+a.mobile.ns+"url='"+m+"']")),f.showLoadMsg&&u(),r.xhr=n,r.textStatus=j,r.page=g,f.pageContainer.trigger("pageload",r),e.resolve(k,c,g,i)},error:function(b,d,g){t&&t.set(h.get()),r.xhr=b,r.textStatus=d,r.errorThrown=g;var i=new a.Event("pageloadfailed");f.pageContainer.trigger(i,r);if(i.isDefaultPrevented())return;f.showLoadMsg&&(u(),a.mobile.showPageLoadingMsg(a.mobile.pageLoadErrorMessageTheme,a.mobile.pageLoadErrorMessage,!0),setTimeout(a.mobile.hidePageLoadingMsg,1500)),e.reject(k,c)}}),e.promise()},a.mobile.loadPage.defaults={type:"get",data:d,reloadPage:!1,role:d,showLoadMsg:!1,pageContainer:d,loadMsgDelay:50},a.mobile.changePage=function(b,e){if(m){l.unshift(arguments);return}var f=a.extend({},a.mobile.changePage.defaults,e);f.pageContainer=f.pageContainer||a.mobile.pageContainer,f.fromPage=f.fromPage||a.mobile.activePage;var g=f.pageContainer,i=new a.Event("pagebeforechange"),k={toPage:b,options:f};g.trigger(i,k);if(i.isDefaultPrevented())return;b=k.toPage,m=!0;if(typeof b=="string"){a.mobile.loadPage(b,f).done(function(b,c,d,e){m=!1,c.duplicateCachedPage=e,a.mobile.changePage(d,c)}).fail(function(a,b){m=!1,u(!0),v(),f.pageContainer.trigger("pagechangefailed",k)});return}b[0]===a.mobile.firstPage[0]&&!f.dataUrl&&(f.dataUrl=p.hrefNoHash);var o=f.fromPage,q=f.dataUrl&&h.convertUrlToDataUrl(f.dataUrl)||b.jqmData("url"),r=q,s=h.getFilePath(q),t=j.getActive(),w=j.activeIndex===0,x=0,y=c.title,A=f.role==="dialog"||b.jqmData("role")==="dialog";if(o&&o[0]===b[0]&&!f.allowSamePageTransition){m=!1,g.trigger("pagechange",k),f.fromHashChange&&j.directHashChange({currentUrl:q,isBack:function(){},isForward:function(){}});return}B(b,f.role),f.fromHashChange&&j.directHashChange({currentUrl:q,isBack:function(){x=-1},isForward:function(){x=1}});try{c.activeElement&&c.activeElement.nodeName.toLowerCase()!=="body"?a(c.activeElement).blur():a("input:focus, textarea:focus, select:focus").blur()}catch(C){}var D=!1;A&&t&&(t.url.indexOf(n)>-1&&!a.mobile.activePage.is(".ui-dialog")&&(f.changeHash=!1,D=!0),q=(t.url||"")+(D?"":n),j.activeIndex===0&&q===j.initialDst&&(q+=n)),f.changeHash!==!1&&q&&(j.ignoreNextHashChange=!0,h.set(q));var E=t?b.jqmData("title")||b.children(":jqmData(role='header')").find(".ui-title").getEncodedText():y;!!E&&y===c.title&&(y=E),b.jqmData("title")||b.jqmData("title",y),f.transition=f.transition||(x&&!w?t.transition:d)||(A?a.mobile.defaultDialogTransition:a.mobile.defaultPageTransition),x||(D&&(j.activeIndex=Math.max(0,j.activeIndex-1)),j.addNew(q,f.transition,y,r,f.role)),c.title=j.getActive().title,a.mobile.activePage=b,f.reverse=f.reverse||x<0,z(b,o,f.transition,f.reverse).done(function(c,d,e,h,i){u(),f.duplicateCachedPage&&f.duplicateCachedPage.remove(),i||a.mobile.focusPage(b),v(),g.trigger("pagechange",k)})},a.mobile.changePage.defaults={transition:d,reverse:!1,changeHash:!0,fromHashChange:!1,role:d,duplicateCachedPage:d,pageContainer:d,showLoadMsg:!0,dataUrl:d,fromPage:d,allowSamePageTransition:!1},a.mobile.navreadyDeferred=a.Deferred(),a.mobile.navreadyDeferred.done(function(){a(c).delegate("form","submit",function(b){var c=a(this);if(!a.mobile.ajaxEnabled||c.is(":jqmData(ajax='false')")||!c.jqmHijackable().length)return;var d=c.attr("method"),e=c.attr("target"),f=c.attr("action");f||(f=D(c),f===q.hrefNoHash&&(f=p.hrefNoSearch)),f=h.makeUrlAbsolute(f,D(c));if(h.isExternal(f)&&!h.isPermittedCrossDomainRequest(p,f)||e)return;a.mobile.changePage(f,{type:d&&d.length&&d.toLowerCase()||"get",data:c.serialize(),transition:c.jqmData("transition"),reverse:c.jqmData("direction")==="reverse",reloadPage:!0}),b.preventDefault()}),a(c).bind("vclick",function(b){if(b.which>1||!a.mobile.linkBindingEnabled)return;var c=C(b.target);if(!a(c).jqmHijackable().length)return;c&&h.parseUrl(c.getAttribute("href")||"#").hash!=="#"&&(u(!0),i=a(c).closest(".ui-btn").not(".ui-disabled"),i.addClass(a.mobile.activeBtnClass))}),a(c).bind("click",function(c){if(!a.mobile.linkBindingEnabled)return;var e=C(c.target),f=a(e),g;if(!e||c.which>1||!f.jqmHijackable().length)return;g=function(){b.setTimeout(function(){u(!0)},200)};if(f.is(":jqmData(rel='back')"))return a.mobile.back(),!1;var i=D(f),j=h.makeUrlAbsolute(f.attr("href")||"#",i);if(!a.mobile.ajaxEnabled&&!h.isEmbeddedPage(j)){g();return}if(j.search("#")!==-1){j=j.replace(/[^#]*#/,"");if(!j){c.preventDefault();return}h.isPath(j)?j=h.makeUrlAbsolute(j,i):j=h.makeUrlAbsolute("#"+j,p.hrefNoHash)}var k=f.is("[rel='external']")||f.is(":jqmData(ajax='false')")||f.is("[target]"),l=k||h.isExternal(j)&&!h.isPermittedCrossDomainRequest(p,j);if(l){g();return}var m=f.jqmData("transition"),n=f.jqmData("direction")==="reverse"||f.jqmData("back"),o=f.attr("data-"+a.mobile.ns+"rel")||d;a.mobile.changePage(j,{transition:m,reverse:n,role:o,link:f}),c.preventDefault()}),a(c).delegate(".ui-page","pageshow.prefetch",function(){var b=[];a(this).find("a:jqmData(prefetch)").each(function(){var c=a(this),d=c.attr("href");d&&a.inArray(d,b)===-1&&(b.push(d),a.mobile.loadPage(d,{role:c.attr("data-"+a.mobile.ns+"rel")}))})}),a.mobile._handleHashChange=function(c){var e=h.stripHash(c),f=a.mobile.urlHistory.stack.length===0?"none":d,g=new a.Event("navigate"),i={transition:f,changeHash:!1,fromHashChange:!0};0===j.stack.length&&(j.initialDst=e),a.mobile.pageContainer.trigger(g);if(g.isDefaultPrevented())return;if(!a.mobile.hashListeningEnabled||j.ignoreNextHashChange){j.ignoreNextHashChange=!1;return}if(j.stack.length>1&&e.indexOf(n)>-1&&j.initialDst!==e){if(!a.mobile.activePage.is(".ui-dialog")){j.directHashChange({currentUrl:e,isBack:function(){a.mobile.back()},isForward:function(){b.history.forward()}});return}j.directHashChange({currentUrl:e,either:function(b){var c=a.mobile.urlHistory.getActive();e=c.pageUrl,a.extend(i,{role:c.role,transition:c.transition,reverse:b})}})}e?(e=typeof e=="string"&&!h.isPath(e)?h.makeUrlAbsolute("#"+e,q):e,e===h.makeUrlAbsolute("#"+j.initialDst,q)&&j.stack.length&&j.stack[0].url!==j.initialDst.replace(n,"")&&(e=a.mobile.firstPage),a.mobile.changePage(e,i)):a.mobile.changePage(a.mobile.firstPage,i)},e.bind("hashchange",function(b,c){a.mobile._handleHashChange(h.parseLocation().hash)}),a(c).bind("pageshow",A),a(b).bind("throttledresize",A)})}(a),function(a,b){var e={},f=e,g=a(b),h=a.mobile.path.parseLocation(),i=a.Deferred(),j=a.Deferred();a(c).ready(a.proxy(j,"resolve")),a(c).one("mobileinit",a.proxy(i,"resolve")),a.extend(e,{initialFilePath:function(){return h.pathname+h.search}(),hashChangeTimeout:200,hashChangeEnableTimer:d,initialHref:h.hrefNoHash,state:function(){return{hash:a.mobile.path.parseLocation().hash||"#"+f.initialFilePath,title:c.title,initialHref:f.initialHref}},resetUIKeys:function(b){var c=a.mobile.dialogHashKey,d="&"+a.mobile.subPageUrlKey,e=b.indexOf(c);return e>-1?b=b.slice(0,e)+"#"+b.slice(e):b.indexOf(d)>-1&&(b=b.split(d).join("#"+d)),b},nextHashChangePrevented:function(b){a.mobile.urlHistory.ignoreNextHashChange=b,f.onHashChangeDisabled=b},onHashChange:function(b){if(f.onHashChangeDisabled)return;var d,e,g=a.mobile.path.parseLocation().hash,h=a.mobile.path.isPath(g),i=h?a.mobile.path.getLocation():a.mobile.getDocumentUrl();g=h?g.replace("#",""):g,e=f.state(),d=a.mobile.path.makeUrlAbsolute(g,i),h&&(d=f.resetUIKeys(d)),history.replaceState(e,c.title,d)},onPopState:function(b){var c=b.originalEvent.state,d,e,g;c&&(clearTimeout(f.hashChangeEnableTimer),f.nextHashChangePrevented(!1),a.mobile._handleHashChange(c.hash),f.nextHashChangePrevented(!0),f.hashChangeEnableTimer=setTimeout(function(){f.nextHashChangePrevented(!1)},f.hashChangeTimeout))},init:function(){g.bind("hashchange",f.onHashChange),g.bind("popstate",f.onPopState),location.hash===""&&history.replaceState(f.state(),c.title,a.mobile.path.getLocation())}}),a.when(j,i,a.mobile.navreadyDeferred).done(function(){a.mobile.pushStateEnabled&&a.support.pushState&&e.init()})}(a,this),function(a,b,c){a.mobile.transitionFallbacks.flip="fade"}(a,this),function(a,b,c){a.mobile.transitionFallbacks.flow="fade"}(a,this),function(a,b,c){a.mobile.transitionFallbacks.pop="fade"}(a,this),function(a,b,c){a.mobile.transitionHandlers.slide=a.mobile.transitionHandlers.simultaneous,a.mobile.transitionFallbacks.slide="fade"}(a,this),function(a,b,c){a.mobile.transitionFallbacks.slidedown="fade"}(a,this),function(a,b,c){a.mobile.transitionFallbacks.slidefade="fade"}(a,this),function(a,b,c){a.mobile.transitionFallbacks.slideup="fade"}(a,this),function(a,b,c){a.mobile.transitionFallbacks.turn="fade"}(a,this),function(a,b){a.mobile.page.prototype.options.degradeInputs={color:!1,date:!1,datetime:!1,"datetime-local":!1,email:!1,month:!1,number:!1,range:"number",search:"text",tel:!1,time:!1,url:!1,week:!1},a(c).bind("pagecreate create",function(b){var c=a.mobile.closestPageData(a(b.target)),d;if(!c)return;d=c.options,a(b.target).find("input").not(c.keepNativeSelector()).each(function(){var b=a(this),c=this.getAttribute("type"),e=d.degradeInputs[c]||"text";if(d.degradeInputs[c]){var f=a("<div>").html(b.clone()).html(),g=f.indexOf(" type=")>-1,h=g?/\s+type=["']?\w+['"]?/:/\/?>/,i=' type="'+e+'" data-'+a.mobile.ns+'type="'+c+'"'+(g?"":">");b.replaceWith(f.replace(h,i))}})})}(a),function(a,b,d){a.widget("mobile.dialog",a.mobile.widget,{options:{closeBtnText:"Close",overlayTheme:"a",initSelector:":jqmData(role='dialog')"},_create:function(){var b=this,c=this.element,d=a("<a href='#' data-"+a.mobile.ns+"icon='delete' data-"+a.mobile.ns+"iconpos='notext'>"+this.options.closeBtnText+"</a>"),e=a("<div/>",{role:"dialog","class":"ui-dialog-contain ui-corner-all ui-overlay-shadow"});c.addClass("ui-dialog ui-overlay-"+this.options.overlayTheme),c.wrapInner(e).children().find(":jqmData(role='header')").prepend(d).end().children(":first-child").addClass("ui-corner-top").end().children(":last-child").addClass("ui-corner-bottom"),d.bind("click",function(){b.close()}),c.bind("vclick submit",function(b){var c=a(b.target).closest(b.type==="vclick"?"a":"form"),d;c.length&&!c.jqmData("transition")&&(d=a.mobile.urlHistory.getActive()||{},c.attr("data-"+a.mobile.ns+"transition",d.transition||a.mobile.defaultDialogTransition).attr("data-"+a.mobile.ns+"direction","reverse"))}).bind("pagehide",function(b,c){a(this).find("."+a.mobile.activeBtnClass).not(".ui-slider-bg").removeClass(a.mobile.activeBtnClass)}).bind("pagebeforeshow",function(){b._isCloseable=!0,b.options.overlayTheme&&b.element.page("removeContainerBackground").page("setContainerBackground",b.options.overlayTheme)})},close:function(){var b;this._isCloseable&&(this._isCloseable=!1,a.mobile.hashListeningEnabled?a.mobile.back():(b=a.mobile.urlHistory.getPrev().url,a.mobile.path.isPath(b)||(b=a.mobile.path.makeUrlAbsolute("#"+b)),a.mobile.changePage(b,{changeHash:!1,fromHashChange:!0})))}}),a(c).delegate(a.mobile.dialog.prototype.options.initSelector,"pagecreate",function(){a.mobile.dialog.prototype.enhance(this)})}(a,this),function(a,b){a.mobile.page.prototype.options.backBtnText="Back",a.mobile.page.prototype.options.addBackBtn=!1,a.mobile.page.prototype.options.backBtnTheme=null,a.mobile.page.prototype.options.headerTheme="a",a.mobile.page.prototype.options.footerTheme="a",a.mobile.page.prototype.options.contentTheme=null,a(c).bind("pagecreate",function(b){var c=a(b.target),d=c.data("page").options,e=c.jqmData("role"),f=d.theme;a(":jqmData(role='header'), :jqmData(role='footer'), :jqmData(role='content')",c).jqmEnhanceable().each(function(){var b=a(this),g=b.jqmData("role"),h=b.jqmData("theme"),i=h||d.contentTheme||e==="dialog"&&f,j,k,l,m;b.addClass("ui-"+g);if(g==="header"||g==="footer"){var n=h||(g==="header"?d.headerTheme:d.footerTheme)||f;b.addClass("ui-bar-"+n).attr("role",g==="header"?"banner":"contentinfo"),g==="header"&&(j=b.children("a, button"),k=j.hasClass("ui-btn-left"),l=j.hasClass("ui-btn-right"),k=k||j.eq(0).not(".ui-btn-right").addClass("ui-btn-left").length,l=l||j.eq(1).addClass("ui-btn-right").length),d.addBackBtn&&g==="header"&&a(".ui-page").length>1&&c.jqmData("url")!==a.mobile.path.stripHash(location.hash)&&!k&&(m=a("<a href='javascript:void(0);' class='ui-btn-left' data-"+a.mobile.ns+"rel='back' data-"+a.mobile.ns+"icon='arrow-l'>"+d.backBtnText+"</a>").attr("data-"+a.mobile.ns+"theme",d.backBtnTheme||n).prependTo(b)),b.children("h1, h2, h3, h4, h5, h6").addClass("ui-title").attr({role:"heading","aria-level":"1"})}else g==="content"&&(i&&b.addClass("ui-body-"+i),b.attr("role","main"))})})}(a),function(a,b){a.fn.fieldcontain=function(a){return this.addClass("ui-field-contain ui-body ui-br").contents().filter(function(){return this.nodeType===3&&!/\S/.test(this.nodeValue)}).remove()},a(c).bind("pagecreate create",function(b){a(":jqmData(role='fieldcontain')",b.target).jqmEnhanceable().fieldcontain()})}(a),function(a,b){a.fn.grid=function(b){return this.each(function(){var c=a(this),d=a.extend({grid:null},b),e=c.children(),f={solo:1,a:2,b:3,c:4,d:5},g=d.grid,h;if(!g)if(e.length<=5)for(var i in f)f[i]===e.length&&(g=i);else g="a",c.addClass("ui-grid-duo");h=f[g],c.addClass("ui-grid-"+g),e.filter(":nth-child("+h+"n+1)").addClass("ui-block-a"),h>1&&e.filter(":nth-child("+h+"n+2)").addClass("ui-block-b"),h>2&&e.filter(":nth-child("+h+"n+3)").addClass("ui-block-c"),h>3&&e.filter(":nth-child("+h+"n+4)").addClass("ui-block-d"),h>4&&e.filter(":nth-child("+h+"n+5)").addClass("ui-block-e")})}}(a),function(a,b){a(c).bind("pagecreate create",function(b){a(":jqmData(role='nojs')",b.target).addClass("ui-nojs")})}(a),function(a,b){function d(a){var b;while(a){b=typeof a.className=="string"&&a.className+" ";if(b&&b.indexOf("ui-btn ")>-1&&b.indexOf("ui-disabled ")<0)break;a=a.parentNode}return a}a.fn.buttonMarkup=function(d){var f=this,g=function(b,c){j.setAttribute("data-"+a.mobile.ns+b,c),i.jqmData(b,c)};d=d&&a.type(d)==="object"?d:{};for(var h=0;h<f.length;h++){var i=f.eq(h),j=i[0],k=a.extend({},a.fn.buttonMarkup.defaults,{icon:d.icon!==b?d.icon:i.jqmData("icon"),iconpos:d.iconpos!==b?d.iconpos:i.jqmData("iconpos"),theme:d.theme!==b?d.theme:i.jqmData("theme")||a.mobile.getInheritedTheme(i,"c"),inline:d.inline!==b?d.inline:i.jqmData("inline"),shadow:d.shadow!==b?d.shadow:i.jqmData("shadow"),corners:d.corners!==b?d.corners:i.jqmData("corners"),iconshadow:d.iconshadow!==b?d.iconshadow:i.jqmData("iconshadow"),mini:d.mini!==b?d.mini:i.jqmData("mini")},d),l="ui-btn-inner",m="ui-btn-text",n,o,p,q,r,s;a.each(k,g),i.jqmData("rel")==="popup"&&i.attr("href")&&(j.setAttribute("aria-haspopup",!0),j.setAttribute("aria-owns",j.getAttribute("href"))),s=a.data(j.tagName==="INPUT"||j.tagName==="BUTTON"?j.parentNode:j,"buttonElements"),s?(j=s.outer,i=a(j),p=s.inner,q=s.text,a(s.icon).remove(),s.icon=null):(p=c.createElement(k.wrapperEls),q=c.createElement(k.wrapperEls)),r=k.icon?c.createElement("span"):null,e&&!s&&e(),k.theme||(k.theme=a.mobile.getInheritedTheme(i,"c")),n="ui-btn ui-btn-up-"+k.theme,n+=k.shadow?" ui-shadow":"",n+=k.corners?" ui-btn-corner-all":"",k.mini!==b&&(n+=k.mini===!0?" ui-mini":" ui-fullsize"),k.inline!==b&&(n+=k.inline===!0?" ui-btn-inline":" ui-btn-block"),k.icon&&(k.icon="ui-icon-"+k.icon,k.iconpos=k.iconpos||"left",o="ui-icon "+k.icon,k.iconshadow&&(o+=" ui-icon-shadow")),k.iconpos&&(n+=" ui-btn-icon-"+k.iconpos,k.iconpos==="notext"&&!i.attr("title")&&i.attr("title",i.getEncodedText())),l+=k.corners?" ui-btn-corner-all":"",k.iconpos&&k.iconpos==="notext"&&!i.attr("title")&&i.attr("title",i.getEncodedText()),s&&i.removeClass(s.bcls||""),i.removeClass("ui-link").addClass(n),p.className=l,q.className=m,s||p.appendChild(q);if(r){r.className=o;if(!s||!s.icon)r.innerHTML=" ",p.appendChild(r)}while(j.firstChild&&!s)q.appendChild(j.firstChild);s||j.appendChild(p),s={bcls:n,outer:j,inner:p,text:q,icon:r},a.data(j,"buttonElements",s),a.data(p,"buttonElements",s),a.data(q,"buttonElements",s),r&&a.data(r,"buttonElements",s)}return this},a.fn.buttonMarkup.defaults={corners:!0,shadow:!0,iconshadow:!0,wrapperEls:"span"};var e=function(){var b=a.mobile.buttonMarkup.hoverDelay,f,g;a(c).bind({"vmousedown vmousecancel vmouseup vmouseover vmouseout focus blur scrollstart":function(c){var e,h=a(d(c.target)),i=c.originalEvent&&/^touch/.test(c.originalEvent.type),j=c.type;if(h.length){e=h.attr("data-"+a.mobile.ns+"theme");if(j==="vmousedown")i?f=setTimeout(function(){h.removeClass("ui-btn-up-"+e).addClass("ui-btn-down-"+e)},b):h.removeClass("ui-btn-up-"+e).addClass("ui-btn-down-"+e);else if(j==="vmousecancel"||j==="vmouseup")h.removeClass("ui-btn-down-"+e).addClass("ui-btn-up-"+e);else if(j==="vmouseover"||j==="focus")i?g=setTimeout(function(){h.removeClass("ui-btn-up-"+e).addClass("ui-btn-hover-"+e)},b):h.removeClass("ui-btn-up-"+e).addClass("ui-btn-hover-"+e);else if(j==="vmouseout"||j==="blur"||j==="scrollstart")h.removeClass("ui-btn-hover-"+e+" ui-btn-down-"+e).addClass("ui-btn-up-"+e),f&&clearTimeout(f),g&&clearTimeout(g)}},"focusin focus":function(b){a(d(b.target)).addClass(a.mobile.focusClass)},"focusout blur":function(b){a(d(b.target)).removeClass(a.mobile.focusClass)}}),e=null};a(c).bind("pagecreate create",function(b){a(":jqmData(role='button'), .ui-bar > a, .ui-header > a, .ui-footer > a, .ui-bar > :jqmData(role='controlgroup') > a",b.target).jqmEnhanceable().not("button, input, .ui-btn, :jqmData(role='none'), :jqmData(role='nojs')").buttonMarkup()})}(a),function(a,b){a.widget("mobile.collapsible",a.mobile.widget,{options:{expandCueText:" click to expand contents",collapseCueText:" click to collapse contents",collapsed:!0,heading:"h1,h2,h3,h4,h5,h6,legend",theme:null,contentTheme:null,inset:!0,mini:!1,initSelector:":jqmData(role='collapsible')"},_create:function(){var c=this.element,d=this.options,e=c.addClass("ui-collapsible"),f=c.children(d.heading).first(),g=c.jqmData("collapsed-icon")||d.collapsedIcon,h=c.jqmData("expanded-icon")||d.expandedIcon,i=e.wrapInner("<div class='ui-collapsible-content'></div>").children(".ui-collapsible-content"),j=c.closest(":jqmData(role='collapsible-set')").addClass("ui-collapsible-set");f.is("legend")&&(f=a("<div role='heading'>"+f.html()+"</div>").insertBefore(f),f.next().remove()),j.length?(d.theme||(d.theme=j.jqmData("theme")||a.mobile.getInheritedTheme(j,"c")),d.contentTheme||(d.contentTheme=j.jqmData("content-theme")),d.collapsedIcon||(d.collapsedIcon=j.jqmData("collapsed-icon")),d.expandedIcon||(d.expandedIcon=j.jqmData("expanded-icon")),d.iconPos||(d.iconPos=j.jqmData("iconpos")),j.jqmData("inset")!==b?d.inset=j.jqmData("inset"):d.inset=!0,d.mini||(d.mini=j.jqmData("mini"))):d.theme||(d.theme=a.mobile.getInheritedTheme(c,"c")),!d.inset||e.addClass("ui-collapsible-inset"),i.addClass(d.contentTheme?"ui-body-"+d.contentTheme:""),g=c.jqmData("collapsed-icon")||d.collapsedIcon||"plus",h=c.jqmData("expanded-icon")||d.expandedIcon||"minus",f.insertBefore(i).addClass("ui-collapsible-heading").append("<span class='ui-collapsible-heading-status'></span>").wrapInner("<a href='#' class='ui-collapsible-heading-toggle'></a>").find("a").first().buttonMarkup({shadow:!1,corners:!1,iconpos:c.jqmData("iconpos")||d.iconPos||"left",icon:g,mini:d.mini,theme:d.theme}),!d.inset||f.find("a").first().add(".ui-btn-inner",c).addClass("ui-corner-top ui-corner-bottom"),e.bind("expand collapse",function(b){if(!b.isDefaultPrevented()){var c=a(this),k=b.type==="collapse",l=d.contentTheme;b.preventDefault(),f.toggleClass("ui-collapsible-heading-collapsed",k).find(".ui-collapsible-heading-status").text(k?d.expandCueText:d.collapseCueText).end().find(".ui-icon").toggleClass("ui-icon-"+h,!k).toggleClass("ui-icon-"+g,k||h===g).end().find("a").first().removeClass(a.mobile.activeBtnClass),c.toggleClass("ui-collapsible-collapsed",k),i.toggleClass("ui-collapsible-content-collapsed",k).attr("aria-hidden",k),l&&!!d.inset&&(!j.length||e.jqmData("collapsible-last"))&&(f.find("a").first().add(f.find(".ui-btn-inner")).toggleClass("ui-corner-bottom",k),i.toggleClass("ui-corner-bottom",!k)),i.trigger("updatelayout")}}).trigger(d.collapsed?"collapse":"expand"),f.bind("tap",function(b){f.find("a").first().addClass(a.mobile.activeBtnClass)}).bind("click",function(a){var b=f.is(".ui-collapsible-heading-collapsed")?"expand":"collapse";e.trigger(b),a.preventDefault(),a.stopPropagation()})}}),a(c).bind("pagecreate create",function(b){a.mobile.collapsible.prototype.enhanceWithin(b.target)})}(a),function(a,b){a.widget("mobile.collapsibleset",a.mobile.widget,{options:{initSelector:":jqmData(role='collapsible-set')"},_create:function(){var c=this.element.addClass("ui-collapsible-set"),d=this.options;d.theme||(d.theme=a.mobile.getInheritedTheme(c,"c")),d.contentTheme||(d.contentTheme=c.jqmData("content-theme")),c.jqmData("inset")!==b&&(d.inset=c.jqmData("inset")),d.inset=d.inset!==b?d.inset:!0,c.jqmData("collapsiblebound")||c.jqmData("collapsiblebound",!0).bind("expand collapse",function(b){var c=b.type==="collapse",e=a(b.target).closest(".ui-collapsible"),f=e.data("collapsible");e.jqmData("collapsible-last")&&!!d.inset&&(e.find(".ui-collapsible-heading").first().find("a").first().toggleClass("ui-corner-bottom",c).find(".ui-btn-inner").toggleClass("ui-corner-bottom",c),e.find(".ui-collapsible-content").toggleClass("ui-corner-bottom",!c))}).bind("expand",function(b){var c=a(b.target).closest(".ui-collapsible");c.parent().is(":jqmData(role='collapsible-set')")&&c.siblings(".ui-collapsible").trigger("collapse")})},_init:function(){var a=this.element,b=a.children(":jqmData(role='collapsible')"),c=b.filter(":jqmData(collapsed='false')");this.refresh(),c.trigger("expand")},refresh:function(){var b=this.element,c=this.options,d=b.children(":jqmData(role='collapsible')");a.mobile.collapsible.prototype.enhance(d.not(".ui-collapsible")),!c.inset||(d.each(function(){a(this).jqmRemoveData("collapsible-last").find(".ui-collapsible-heading").find("a").first().removeClass("ui-corner-top ui-corner-bottom").find(".ui-btn-inner").removeClass("ui-corner-top ui-corner-bottom")}),d.first().find("a").first().addClass("ui-corner-top").find(".ui-btn-inner").addClass("ui-corner-top"),d.last().jqmData("collapsible-last",!0).find("a").first().addClass("ui-corner-bottom").find(".ui-btn-inner").addClass("ui-corner-bottom"))}}),a(c).bind("pagecreate create",function(b){a.mobile.collapsibleset.prototype.enhanceWithin(b.target)})}(a),function(a,b){a.widget("mobile.navbar",a.mobile.widget,{options:{iconpos:"top",grid:null,initSelector:":jqmData(role='navbar')"},_create:function(){var c=this.element,d=c.find("a"),e=d.filter(":jqmData(icon)").length?this.options.iconpos:b;c.addClass("ui-navbar ui-mini").attr("role","navigation").find("ul").jqmEnhanceable().grid({grid:this.options.grid}),d.buttonMarkup({corners:!1,shadow:!1,inline:!0,iconpos:e}),c.delegate("a","vclick",function(b){a(b.target).hasClass("ui-disabled")||(d.removeClass(a.mobile.activeBtnClass),a(this).addClass(a.mobile.activeBtnClass))}),c.closest(".ui-page").bind("pagebeforeshow",function(){d.filter(".ui-state-persist").addClass(a.mobile.activeBtnClass)})}}),a(c).bind("pagecreate create",function(b){a.mobile.navbar.prototype.enhanceWithin(b.target)})}(a),function(a,b){var d={};a.widget("mobile.listview",a.mobile.widget,{options:{theme:null,countTheme:"c",headerTheme:"b",dividerTheme:"b",splitIcon:"arrow-r",splitTheme:"b",inset:!1,initSelector:":jqmData(role='listview')"},_create:function(){var a=this,b="";b+=a.options.inset?" ui-listview-inset ui-corner-all ui-shadow ":"",a.element.addClass(function(a,c){return c+" ui-listview "+b}),a.refresh(!0)},_removeCorners:function(a,b){var c="ui-corner-top ui-corner-tr ui-corner-tl",d="ui-corner-bottom ui-corner-br ui-corner-bl";a=a.add(a.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb")),b==="top"?a.removeClass(c):b==="bottom"?a.removeClass(d):a.removeClass(c+" "+d)},_refreshCorners:function(a){var b,c,d,e;b=this.element.children("li"),c=a||b.filter(":visible").length===0?b.not(".ui-screen-hidden"):b.filter(":visible"),b.filter(".ui-li-last").removeClass("ui-li-last"),this.options.inset?(this._removeCorners(b),d=c.first().addClass("ui-corner-top"),d.add(d.find(".ui-btn-inner").not(".ui-li-link-alt span:first-child")).addClass("ui-corner-top").end().find(".ui-li-link-alt, .ui-li-link-alt span:first-child").addClass("ui-corner-tr").end().find(".ui-li-thumb").not(".ui-li-icon").addClass("ui-corner-tl"),e=c.last().addClass("ui-corner-bottom ui-li-last"),e.add(e.find(".ui-btn-inner")).find(".ui-li-link-alt").addClass("ui-corner-br").end().find(".ui-li-thumb").not(".ui-li-icon").addClass("ui-corner-bl")):c.last().addClass("ui-li-last"),a||this.element.trigger("updatelayout")},_findFirstElementByTagName:function(a,b,c,d){var e={};e[c]=e[d]=!0;while(a){if(e[a.nodeName])return a;a=a[b]}return null},_getChildrenByTagName:function(b,c,d){var e=[],f={};f[c]=f[d]=!0,b=b.firstChild;while(b)f[b.nodeName]&&e.push(b),b=b.nextSibling;return a(e)},_addThumbClasses:function(b){var c,d,e=b.length;for(c=0;c<e;c++)d=a(this._findFirstElementByTagName(b[c].firstChild,"nextSibling","img","IMG")),d.length&&(d.addClass("ui-li-thumb"),a(this._findFirstElementByTagName(d[0].parentNode,"parentNode","li","LI")).addClass(d.is(".ui-li-icon")?"ui-li-has-icon":"ui-li-has-thumb"))},refresh:function(b){this.parentPage=this.element.closest(".ui-page"),this._createSubPages();var d=this.options,e=this.element,f=this,g=e.jqmData("dividertheme")||d.dividerTheme,h=e.jqmData("splittheme"),i=e.jqmData("spliticon"),j=this._getChildrenByTagName(e[0],"li","LI"),k=!!a.nodeName(e[0],"ol"),l=!a.support.cssPseudoElement,m=e.attr("start"),n={},o,p,q,r,s,t,u,v,w,x,y,z,A,B;k&&l&&e.find(".ui-li-dec").remove(),k&&(m||m===0?l?u=parseFloat(m):(v=parseFloat(m)-1,e.css("counter-reset","listnumbering "+v)):l&&(u=1)),d.theme||(d.theme=a.mobile.getInheritedTheme(this.element,"c"));for(var C=0,D=j.length;C<D;C++){o=j.eq(C),p="ui-li";if(b||!o.hasClass("ui-li")){q=o.jqmData("theme")||d.theme,r=this._getChildrenByTagName(o[0],"a","A");var E=o.jqmData("role")==="list-divider";r.length&&!E?(y=o.jqmData("icon"),o.buttonMarkup({wrapperEls:"div",shadow:!1,corners:!1,iconpos:"right",icon:r.length>1||y===!1?!1:y||"arrow-r",theme:q}),y!==!1&&r.length===1&&o.addClass("ui-li-has-arrow"),r.first().removeClass("ui-link").addClass("ui-link-inherit"),r.length>1&&(p+=" ui-li-has-alt",s=r.last(),t=h||s.jqmData("theme")||d.splitTheme,B=s.jqmData("icon"),s.appendTo(o).attr("title",s.getEncodedText()).addClass("ui-li-link-alt").empty().buttonMarkup({shadow:!1,corners:!1,theme:q,icon:!1,iconpos:"notext"}).find(".ui-btn-inner").append(a(c.createElement("span")).buttonMarkup({shadow:!0,corners:!0,theme:t,iconpos:"notext",icon:B||y||i||d.splitIcon})))):E?(p+=" ui-li-divider ui-bar-"+g,o.attr("role","heading"),k&&(m||m===0?l?u=parseFloat(m):(w=parseFloat(m)-1,o.css("counter-reset","listnumbering "+w)):l&&(u=1))):p+=" ui-li-static ui-btn-up-"+q}k&&l&&p.indexOf("ui-li-divider")<0&&(x=p.indexOf("ui-li-static")>0?o:o.find(".ui-link-inherit"),x.addClass("ui-li-jsnumbering").prepend("<span class='ui-li-dec'>"+u++ +". </span>")),n[p]||(n[p]=[]),n[p].push(o[0])}for(p in n)a(n[p]).addClass(p).children(".ui-btn-inner").addClass(p);e.find("h1, h2, h3, h4, h5, h6").addClass("ui-li-heading").end().find("p, dl").addClass("ui-li-desc").end().find(".ui-li-aside").each(function(){var b=a(this);b.prependTo(b.parent())}).end().find(".ui-li-count").each(function(){a(this).closest("li").addClass("ui-li-has-count")}).addClass("ui-btn-up-"+(e.jqmData("counttheme")||this.options.countTheme)+" ui-btn-corner-all"),this._addThumbClasses(j),this._addThumbClasses(e.find(".ui-link-inherit")),this._refreshCorners(b),this._trigger("afterrefresh")},_idStringEscape:function(a){return a.replace(/[^a-zA-Z0-9]/g,"-")},_createSubPages:function(){var b=this.element,c=b.closest(".ui-page"),e=c.jqmData("url"),f=e||c[0][a.expando],g=b.attr("id"),h=this.options,i="data-"+a.mobile.ns,j=this,k=c.find(":jqmData(role='footer')").jqmData("id"),l;typeof d[f]=="undefined"&&(d[f]=-1),g=g||++d[f],a(b.find("li>ul, li>ol").toArray().reverse()).each(function(c){var d=this,f=a(this),j=f.attr("id")||g+"-"+c,m=f.parent(),n=a(f.prevAll().toArray().reverse()),p=n.length?n:a("<span>"+a.trim(m.contents()[0].nodeValue)+"</span>"),q=p.first().getEncodedText(),r=(e||"")+"&"+a.mobile.subPageUrlKey+"="+j,s=f.jqmData("theme")||h.theme,t=f.jqmData("counttheme")||b.jqmData("counttheme")||h.countTheme,u,v;l=!0,u=f.detach().wrap("<div "+i+"role='page' "+i+"url='"+r+"' "+i+"theme='"+s+"' "+i+"count-theme='"+t+"'><div "+i+"role='content'></div></div>").parent().before("<div "+i+"role='header' "+i+"theme='"+h.headerTheme+"'><div class='ui-title'>"+q+"</div></div>").after(k?a("<div "+i+"role='footer' "+i+"id='"+k+"'>"):"").parent().appendTo(a.mobile.pageContainer),u.page(),v=m.find("a:first"),v.length||(v=a("<a/>").html(p||q).prependTo(m.empty())),v.attr("href","#"+r)}).listview();if(l&&c.is(":jqmData(external-page='true')")&&c.data("page").options.domCache===!1){var m=function(b,d){var f=d.nextPage,g,h=new a.Event("pageremove");d.nextPage&&(g=f.jqmData("url"),g.indexOf(e+"&"+a.mobile.subPageUrlKey)!==0&&(j.childPages().remove(),c.trigger(h),h.isDefaultPrevented()||c.removeWithDependents()))};c.unbind("pagehide.remove").bind("pagehide.remove",m)}},childPages:function(){var b=this.parentPage.jqmData("url");return a(":jqmData(url^='"+b+"&"+a.mobile.subPageUrlKey+"')")}}),a(c).bind("pagecreate create",function(b){a.mobile.listview.prototype.enhanceWithin(b.target)})}(a),function(a,b){a.mobile.listview.prototype.options.autodividers=!1,a.mobile.listview.prototype.options.autodividersSelector=function(a){var b=a.text()||null;return b?(b=b.slice(0,1).toUpperCase(),b):null},a(c).delegate("ul,ol","listviewcreate",function(){var b=a(this),d=b.data("listview");if(!d||!d.options.autodividers)return;var e=function(){b.find("li:jqmData(role='list-divider')").remove();var e=b.find("li"),f=null,g,h;for(var i=0;i<e.length;i++){g=e[i],h=d.options.autodividersSelector(a(g));if(h&&f!==h){var j=c.createElement("li");j.appendChild(c.createTextNode(h)),j.setAttribute("data-"+a.mobile.ns+"role","list-divider"),g.parentNode.insertBefore(j,g)}f=h}},f=function(){b.unbind("listviewafterrefresh",f),e(),d.refresh(),b.bind("listviewafterrefresh",f)};f()})}(a),function(a,b){a.widget("mobile.checkboxradio",a.mobile.widget,{options:{theme:null,initSelector:"input[type='checkbox'],input[type='radio']"},_create:function(){var d=this,e=this.element,f=function(a,b){return a.jqmData(b)||a.closest("form, fieldset").jqmData(b)},g=a(e).closest("label"),h=g.length?g:a(e).closest("form, fieldset, :jqmData(role='page'), :jqmData(role='dialog')").find("label").filter("[for='"+e[0].id+"']").first(),i=e[0].type,j=f(e,"mini"),k=i+"-on",l=i+"-off",m=e.parents(":jqmData(type='horizontal')").length?b:l,n=f(e,"iconpos"),o=m?"":" "+a.mobile.activeBtnClass,p="ui-"+k+o,q="ui-"+l,r="ui-icon-"+k,s="ui-icon-"+l;if(i!=="checkbox"&&i!=="radio")return;a.extend(this,{label:h,inputtype:i,checkedClass:p,uncheckedClass:q,checkedicon:r,uncheckedicon:s}),this.options.theme||(this.options.theme=a.mobile.getInheritedTheme(this.element,"c")),h.buttonMarkup({theme:this.options.theme,icon:m,shadow:!1,mini:j,iconpos:n});var t=c.createElement("div");t.className="ui-"+i,e.add(h).wrapAll(t),h.bind({vmouseover:function(b){a(this).parent().is(".ui-disabled")&&b.stopPropagation()},vclick:function(a){if(e.is(":disabled")){a.preventDefault();return}return d._cacheVals(),e.prop("checked",i==="radio"&&!0||!e.prop("checked")),e.triggerHandler("click"),d._getInputSet().not(e).prop("checked",!1),d._updateAll(),!1}}),e.bind({vmousedown:function(){d._cacheVals()},vclick:function(){var b=a(this);b.is(":checked")?(b.prop("checked",!0),d._getInputSet().not(b).prop("checked",!1)):b.prop("checked",!1),d._updateAll()},focus:function(){h.addClass(a.mobile.focusClass)},blur:function(){h.removeClass(a.mobile.focusClass)}}),this.refresh()},_cacheVals:function(){this._getInputSet().each(function(){a(this).jqmData("cacheVal",this.checked)})},_getInputSet:function(){return this.inputtype==="checkbox"?this.element:this.element.closest("form, fieldset, :jqmData(role='page'), :jqmData(role='dialog')").find("input[name='"+this.element[0].name+"'][type='"+this.inputtype+"']")},_updateAll:function(){var b=this;this._getInputSet().each(function(){var c=a(this);(this.checked||b.inputtype==="checkbox")&&c.trigger("change")}).checkboxradio("refresh")},refresh:function(){var a=this.element[0],b=this.label,c=b.find(".ui-icon");a.checked?(b.addClass(this.checkedClass).removeClass(this.uncheckedClass),c.addClass(this.checkedicon).removeClass(this.uncheckedicon)):(b.removeClass(this.checkedClass).addClass(this.uncheckedClass),c.removeClass(this.checkedicon).addClass(this.uncheckedicon)),a.disabled?this.disable():this.enable()},disable:function(){this.element.prop("disabled",!0).parent().addClass("ui-disabled")},enable:function(){this.element.prop("disabled",!1).parent().removeClass("ui-disabled")}}),a(c).bind("pagecreate create",function(b){a.mobile.checkboxradio.prototype.enhanceWithin(b.target,!0)})}(a),function(a,b){a.widget("mobile.button",a.mobile.widget,{options:{theme:null,icon:null,iconpos:null,corners:!0,shadow:!0,iconshadow:!0,initSelector:"button, [type='button'], [type='submit'], [type='reset']"},_create:function(){var d=this.element,e,f=this.options,g,h,i=f.inline||d.jqmData("inline"),j=f.mini||d.jqmData("mini"),k="",l;if(d[0].tagName==="A"){d.hasClass("ui-btn")||d.buttonMarkup();return}this.options.theme||(this.options.theme=a.mobile.getInheritedTheme(this.element,"c")),!~d[0].className.indexOf("ui-btn-left")||(k="ui-btn-left"),!~d[0].className.indexOf("ui-btn-right")||(k="ui-btn-right");if(d.attr("type")==="submit"||d.attr("type")==="reset")k?k+=" ui-submit":k="ui-submit";a("label[for='"+d.attr("id")+"']").addClass("ui-submit"),this.button=a("<div></div>")[d.html()?"html":"text"](d.html()||d.val()).insertBefore(d).buttonMarkup({theme:f.theme,icon:f.icon,iconpos:f.iconpos,inline:i,corners:f.corners,shadow:f.shadow,iconshadow:f.iconshadow,mini:j}).addClass(k).append(d.addClass("ui-btn-hidden")),e=this.button,g=d.attr("type"),h=d.attr("name"),g!=="button"&&g!=="reset"&&h&&d.bind("vclick",function(){l===b&&(l=a("<input>",{type:"hidden",name:d.attr("name"),value:d.attr("value")}).insertBefore(d),a(c).one("submit",function(){l.remove(),l=b}))}),d.bind({focus:function(){e.addClass(a.mobile.focusClass)},blur:function(){e.removeClass(a.mobile.focusClass)}}),this.refresh()},enable:function(){return this.element.attr("disabled",!1),this.button.removeClass("ui-disabled").attr("aria-disabled",!1),this._setOption("disabled",!1)},disable:function(){return this.element.attr("disabled",!0),this.button.addClass("ui-disabled").attr("aria-disabled",!0),this._setOption("disabled",!0)},refresh:function(){var b=this.element;b.prop("disabled")?this.disable():this.enable(),a(this.button.data("buttonElements").text)[b.html()?"html":"text"](b.html()||b.val())}}),a(c).bind("pagecreate create",function(b){a.mobile.button.prototype.enhanceWithin(b.target,!0)})}(a),function(a,b){a.fn.controlgroup=function(b){function c(a,b){a.removeClass("ui-btn-corner-all ui-corner-top ui-corner-bottom ui-corner-left ui-corner-right ui-controlgroup-last ui-shadow").eq(0).addClass(b[0]).end().last().addClass(b[1]).addClass("ui-controlgroup-last")}return this.each(function(){var d=a(this),e=a.extend({direction:d.jqmData("type")||"vertical",shadow:!1,excludeInvisible:!0,mini:d.jqmData("mini")},b),f=d.children("legend"),g=d.children(".ui-controlgroup-label"),h=d.children(".ui-controlgroup-controls"),i=e.direction==="horizontal"?["ui-corner-left","ui-corner-right"]:["ui-corner-top","ui-corner-bottom"],j=d.find("input").first().attr("type");h.length&&h.contents().unwrap(),d.wrapInner("<div class='ui-controlgroup-controls'></div>"),f.length?(a("<div role='heading' class='ui-controlgroup-label'>"+f.html()+"</div>").insertBefore(d.children(0)),f.remove()):g.length&&d.prepend(g),d.addClass("ui-corner-all ui-controlgroup ui-controlgroup-"+e.direction),c(d.find(".ui-btn"+(e.excludeInvisible?":visible":"")).not(".ui-slider-handle"),i),c(d.find(".ui-btn-inner"),i),e.shadow&&d.addClass("ui-shadow"),e.mini&&d.addClass("ui-mini")})}}(a),function(a,b){a(c).bind("pagecreate create",function(b){a(b.target).find("a").jqmEnhanceable().not(".ui-btn, .ui-link-inherit, :jqmData(role='none'), :jqmData(role='nojs')").addClass("ui-link")})}(a),function(a,d){function e(a,b,c,d){var e=d;return a<b?e=c+(a-b)/2:e=Math.min(Math.max(c,d-b/2),c+a-b),e}function f(){var c=a(b);return{x:c.scrollLeft(),y:c.scrollTop(),cx:b.innerWidth||c.width(),cy:b.innerHeight||c.height()}}a.widget("mobile.popup",a.mobile.widget,{options:{theme:null,overlayTheme:null,shadow:!0,corners:!0,transition:"none",positionTo:"origin",tolerance:null,initSelector:":jqmData(role='popup')",closeLinkSelector:"a:jqmData(rel='back')",closeLinkEvents:"click.popup",navigateEvents:"navigate.popup",closeEvents:"navigate.popup pagebeforechange.popup",history:!a.mobile.browser.ie},_eatEventAndClose:function(a){return a.preventDefault(),a.stopImmediatePropagation(),this.close(),!1},_resizeScreen:function(){var a=this._ui.container.outerHeight(!0);this._ui.screen.removeAttr("style"),a>this._ui.screen.height()&&this._ui.screen.height(a)},_handleWindowKeyUp:function(b){if(this._isOpen&&b.keyCode===a.mobile.keyCode.ESCAPE)return this._eatEventAndClose(b)},_maybeRefreshTimeout:function(){var b=f();if(this._resizeData){if(b.x===this._resizeData.winCoords.x&&b.y===this._resizeData.winCoords.y&&b.cx===this._resizeData.winCoords.cx&&b.cy===this._resizeData.winCoords.cy)return!1;clearTimeout(this._resizeData.timeoutId)}return this._resizeData={timeoutId:setTimeout(a.proxy(this,"_resizeTimeout"),200),winCoords:b},!0},_resizeTimeout:function(){this._maybeRefreshTimeout()||(this._trigger("beforeposition"),this._ui.container.removeClass("ui-selectmenu-hidden").offset(this._placementCoords(this._desiredCoords(d,d,"window"))),this._resizeScreen(),this._resizeData=null,this._orientationchangeInProgress=!1)},_handleWindowResize:function(a){this._isOpen&&this._maybeRefreshTimeout()},_handleWindowOrientationchange:function(a){this._orientationchangeInProgress||(this._ui.container.addClass("ui-selectmenu-hidden").removeAttr("style"),this._orientationchangeInProgress=!0)},_create:function(){var c={screen:a("<div class='ui-screen-hidden ui-popup-screen'></div>"),placeholder:a("<div style='display: none;'><!-- placeholder --></div>"),container:a("<div class='ui-popup-container ui-selectmenu-hidden'></div>")},e=this.element.closest(".ui-page"),f=this.element.attr("id"),g=this;this.options.history=this.options.history&&a.mobile.ajaxEnabled&&a.mobile.hashListeningEnabled,e.length===0&&(e=a("body")),this.options.container=this.options.container||a.mobile.pageContainer,e.append(c.screen),c.container.insertAfter(c.screen),c.placeholder.insertAfter(this.element),f&&(c.screen.attr("id",f+"-screen"),c.container.attr("id",f+"-popup"),c.placeholder.html("<!-- placeholder for "+f+" -->")),c.container.append(this.element),this.element.addClass("ui-popup"),a.extend(this,{_page:e,_ui:c,_fallbackTransition:"",_currentTransition:!1,_prereqs:null,_isOpen:!1,_tolerance:null,_resizeData:null,_orientationchangeInProgress:!1,_globalHandlers:[{src:a(b),handler:{orientationchange:a.proxy(this,"_handleWindowOrientationchange"),resize:a.proxy(this,"_handleWindowResize"),keyup:a.proxy(this,"_handleWindowKeyUp")}}]}),a.each(this.options,function(a,b){g.options[a]=d,g._setOption(a,b,!0)}),c.screen.bind("vclick",a.proxy(this,"_eatEventAndClose")),a.each(this._globalHandlers,function(a,b){b.src.bind(b.handler)})},_applyTheme:function(a,b,c){var d=(a.attr("class")||"").split(" "),e=!0,f=null,g,h=String(b);while(d.length>0){f=d.pop(),g=(new RegExp("^ui-"+c+"-([a-z])$")).exec(f);if(g&&g.length>1){f=g[1];break}f=null}b!==f&&(a.removeClass("ui-"+c+"-"+f),b!==null&&b!=="none"&&a.addClass("ui-"+c+"-"+h))},_setTheme:function(a){this._applyTheme(this.element,a,"body")},_setOverlayTheme:function(a){this._applyTheme(this._ui.screen,a,"overlay"),this._isOpen&&this._ui.screen.addClass("in")},_setShadow:function(a){this.element.toggleClass("ui-overlay-shadow",a)},_setCorners:function(a){this.element.toggleClass("ui-corner-all",a)},_applyTransition:function(b){this._ui.container.removeClass(this._fallbackTransition),b&&b!=="none"&&(this._fallbackTransition=a.mobile._maybeDegradeTransition(b),this._ui.container.addClass(this._fallbackTransition))},_setTransition:function(a){this._currentTransition||this._applyTransition(a)},_setTolerance:function(b){var c={t:30,r:15,b:30,l:15};if(b){var d=String(b).split(",");a.each(d,function(a,b){d[a]=parseInt(b,10)});switch(d.length){case 1:isNaN(d[0])||(c.t=c.r=c.b=c.l=d[0]);break;case 2:isNaN(d[0])||(c.t=c.b=d[0]),isNaN(d[1])||(c.l=c.r=d[1]);break;case 4:isNaN(d[0])||(c.t=d[0]),isNaN(d[1])||(c.r=d[1]),isNaN(d[2])||(c.b=d[2]),isNaN(d[3])||(c.l=d[3]);break;default:}}this._tolerance=c},_setOption:function(b,c){var e,f="_set"+b.charAt(0).toUpperCase()+b.slice(1);this[f]!==d&&this[f](c),e=["initSelector","closeLinkSelector","closeLinkEvents","navigateEvents","closeEvents","history","container"],a.mobile.widget.prototype._setOption.apply(this,arguments),a.inArray(b,e)===-1&&this.element.attr("data-"+(a.mobile.ns||"")+b.replace(/([A-Z])/,"-$1").toLowerCase(),c)},_placementCoords:function(a){var b=f(),d={x:this._tolerance.l,y:b.y+this._tolerance.t,cx:b.cx-this._tolerance.l-this._tolerance.r,cy:b.cy-this._tolerance.t-this._tolerance.b},g,h;this._ui.container.css("max-width",d.cx),g={cx:this._ui.container.outerWidth(!0),cy:this._ui.container.outerHeight(!0)},h={x:e(d.cx,g.cx,d.x,a.x),y:e(d.cy,g.cy,d.y,a.y)},h.y=Math.max(0,h.y);var i=c.documentElement,j=c.body,k=Math.max(i.clientHeight,j.scrollHeight,j.offsetHeight,i.scrollHeight,i.offsetHeight);return h.y-=Math.min(h.y,Math.max(0,h.y+g.cy-k)),{left:h.x,top:h.y}},_createPrereqs:function(b,c,d){var e=this,f;f={screen:a.Deferred(),container:a.Deferred()},f.screen.then(function(){f===e._prereqs&&b()}),f.container.then(function(){f===e._prereqs&&c()}),a.when(f.screen,f.container).done(function(){f===e._prereqs&&(e._prereqs=null,d())}),e._prereqs=f},_animate:function(b){this._ui.screen.removeClass(b.classToRemove).addClass(b.screenClassToAdd),b.prereqs.screen.resolve(),b.transition&&b.transition!=="none"?(b.applyTransition&&this._applyTransition(b.transition),this._ui.container.animationComplete(a.proxy(b.prereqs.container,"resolve")).addClass(b.containerClassToAdd).removeClass(b.classToRemove)):b.prereqs.container.resolve()},_desiredCoords:function(b,c,d){var e=null,g,h=f();if(d&&d!=="origin")if(d==="window")b=h.cx/2+h.x,c=h.cy/2+h.y;else{try{e=a(d)}catch(i){e=null}e&&(e.filter(":visible"),e.length===0&&(e=null))}e&&(g=e.offset(),b=g.left+e.outerWidth()/2,c=g.top+e.outerHeight()/2);if(a.type(b)!=="number"||isNaN(b))b=h.cx/2+h.x;if(a.type(c)!=="number"||isNaN(c))c=h.cy/2+h.y;return{x:b,y:c}},_openPrereqsComplete:function(){var a=this;a._ui.container.addClass("ui-popup-active"),a._isOpen=!0,a._resizeScreen(),setTimeout(function(){a._ui.container.attr("tabindex","0").focus(),a._trigger("afteropen")})},_open:function(c){var d,e,f=function(){var a=b,c=navigator.userAgent,d=c.match(/AppleWebKit\/([0-9\.]+)/),e=!!d&&d[1],f=c.match(/Android (\d+(?:\.\d+))/),g=!!f&&f[1],h=c.indexOf("Chrome")>-1;return f!==null&&g==="4.0"&&e&&e>534.13&&!h?!0:!1}();c=c||{},e=c.transition||this.options.transition,this._trigger("beforeposition"),d=this._placementCoords(this._desiredCoords(c.x,c.y,c.positionTo||this.options.positionTo||"origin")),this._createPrereqs(a.noop,a.noop,a.proxy(this,"_openPrereqsComplete")),e?(this._currentTransition=e,this._applyTransition(e)):e=this.options.transition,this.options.theme||this._setTheme(this._page.jqmData("theme")||a.mobile.getInheritedTheme(this._page,"c")),this._ui.screen.removeClass("ui-screen-hidden"),this._ui.container.removeClass("ui-selectmenu-hidden").offset(d),this.options.overlayTheme&&f&&this.element.closest(".ui-page").addClass("ui-popup-open"),this._animate({additionalCondition:!0,transition:e,classToRemove:"",screenClassToAdd:"in",containerClassToAdd:"in",applyTransition:!1,prereqs:this._prereqs})},_closePrereqScreen:function(){this._ui.screen.removeClass("out").addClass("ui-screen-hidden")},_closePrereqContainer:function(){this._ui.container.removeClass("reverse out").addClass("ui-selectmenu-hidden").removeAttr("style")},_closePrereqsDone:function(){var b=this,c=b.options;b._ui.container.removeAttr("tabindex"),c.container.unbind(c.closeEvents),b.element.undelegate(c.closeLinkSelector,c.closeLinkEvents),a.mobile.popup.active=d,b._trigger("afterclose")},_close:function(){this._ui.container.removeClass("ui-popup-active"),this._page.removeClass("ui-popup-open"),this._isOpen=!1,this._createPrereqs(a.proxy(this,"_closePrereqScreen"),a.proxy(this,"_closePrereqContainer"),a.proxy(this,"_closePrereqsDone")),this._animate({additionalCondition:this._ui.screen.hasClass("in"),transition:this._currentTransition||this.options.transition,classToRemove:"in",screenClassToAdd:"out",containerClassToAdd:"reverse out",applyTransition:!0,prereqs:this._prereqs})},_destroy:function(){var b=this;b._close(),b._setTheme("none"),b.element.insertAfter(b._ui.placeholder).removeClass("ui-popup ui-overlay-shadow ui-corner-all"),b._ui.screen.remove(),b._ui.container.remove(),b._ui.placeholder.remove(),a.each(b._globalHandlers,function(b,c){a.each(c.handler,function(a,b){c.src.unbind(a,b)})})},_bindContainerClose:function(){var b=this;b.options.container.one(b.options.closeEvents,a.proxy(b._close,b))},open:function(b){var c=this,e=this.options,f,g,h,i,j,k;if(a.mobile.popup.active)return;a.mobile.popup.active=this;if(!e.history){c._open(b),c._bindContainerClose(),c.element.delegate(e.closeLinkSelector,e.closeLinkEvents,function(a){return c._close(),!1});return}g=a.mobile.dialogHashKey,h=a.mobile.activePage,i=h.is(".ui-dialog"),f=a.mobile.urlHistory.getActive().url,j=f.indexOf(g)>-1&&!i,k=a.mobile.urlHistory;if(j){c._open(b),c._bindContainerClose();return}f.indexOf(g)===-1&&!i?f=f+g:f=a.mobile.path.parseLocation().hash+g,k.activeIndex===0&&f===k.initialDst&&(f+=g),e.container.one(e.navigateEvents,function(a){a.preventDefault(),c._open(b),c._bindContainerClose()}),k.ignoreNextHashChange=i,k.addNew(f,d,d,d,"dialog"),a.mobile.path.set(f)},close:function(){if(!a.mobile.popup.active)return;this.options.history?a.mobile.back():this._close()}}),a.mobile.popup.handleLink=function(b){var c=b.closest(":jqmData(role='page')"),d=c.length===0?a("body"):c,e=a(a.mobile.path.parseUrl(b.attr("href")).hash,d[0]),f;e.data("popup")&&(f=b.offset(),e.popup("open",{x:f.left+b.outerWidth()/2,y:f.top+b.outerHeight()/2,transition:b.jqmData("transition"),positionTo:b.jqmData("position-to"),link:b})),setTimeout(function(){b.removeClass(a.mobile.activeBtnClass)},300)},a(c).bind("pagebeforechange",function(b,c){c.options.role==="popup"&&(a.mobile.popup.handleLink(c.options.link),b.preventDefault())}),a(c).bind("pagecreate create",function(b){a.mobile.popup.prototype.enhanceWithin(b.target,!0)})}(a),function(a){var b=a("meta[name=viewport]"),c=b.attr("content"),d=c+",maximum-scale=1, user-scalable=no",e=c+",maximum-scale=10, user-scalable=yes",f=/(user-scalable[\s]*=[\s]*no)|(maximum-scale[\s]*=[\s]*1)[$,\s]/.test(c);a.mobile.zoom=a.extend({},{enabled:!f,locked:!1,disable:function(c){!f&&!a.mobile.zoom.locked&&(b.attr("content",d),a.mobile.zoom.enabled=!1,a.mobile.zoom.locked=c||!1)},enable:function(c){!f&&(!a.mobile.zoom.locked||c===!0)&&(b.attr("content",e),a.mobile.zoom.enabled=!0,a.mobile.zoom.locked=!1)},restore:function(){f||(b.attr("content",c),a.mobile.zoom.enabled=!0)}})}(a),function(a,d){a.widget("mobile.textinput",a.mobile.widget,{options:{theme:null,preventFocusZoom:/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")>-1,initSelector:"input[type='text'], input[type='search'], :jqmData(type='search'), input[type='number'], :jqmData(type='number'), input[type='password'], input[type='email'], input[type='url'], input[type='tel'], textarea, input[type='time'], input[type='date'], input[type='month'], input[type='week'], input[type='datetime'], input[type='datetime-local'], input[type='color'], input:not([type])",clearSearchButtonText:"clear text",disabled:!1},_create:function(){function m(){setTimeout(function(){l.toggleClass("ui-input-clear-hidden",!e.val())},0)}var d=this,e=this.element,f=this.options,g=f.theme||a.mobile.getInheritedTheme(this.element,"c"),h=" ui-body-"+g,i=e.jqmData("mini")===!0,j=i?" ui-mini":"",k,l;a("label[for='"+e.attr("id")+"']").addClass("ui-input-text"),k=e.addClass("ui-input-text ui-body-"+g),typeof e[0].autocorrect!="undefined"&&!a.support.touchOverflow&&(e[0].setAttribute("autocorrect","off"),e[0].setAttribute("autocomplete","off")),e.is("[type='search'],:jqmData(type='search')")?(k=e.wrap("<div class='ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield"+h+j+"'></div>").parent(),l=a("<a href='#' class='ui-input-clear' title='"+f.clearSearchButtonText+"'>"+f.clearSearchButtonText+"</a>").bind("click",function(a){e.val("").focus().trigger("change"),l.addClass("ui-input-clear-hidden"),a.preventDefault()}).appendTo(k).buttonMarkup({icon:"delete",iconpos:"notext",corners:!0,shadow:!0,mini:i}),m(),e.bind("paste cut keyup focus change blur",m)):e.addClass("ui-corner-all ui-shadow-inset"+h+j),e.focus(function(){k.addClass(a.mobile.focusClass)}).blur(function(){k.removeClass(a.mobile.focusClass)}).bind("focus",function(){f.preventFocusZoom&&a.mobile.zoom.disable(!0)}).bind("blur",function(){f.preventFocusZoom&&a.mobile.zoom.enable(!0)});if(e.is("textarea")){var n=15,o=100,p;this._keyup=function(){var a=e[0].scrollHeight,b=e[0].clientHeight;b<a&&e.height(a+n)},e.keyup(function(){clearTimeout(p),p=setTimeout(d._keyup,o)}),this._on(a(c),{pagechange:"_keyup"}),a.trim(e.val())&&this._on(a(b),{load:"_keyup"})}e.attr("disabled")&&this.disable()},disable:function(){var a;return this.element.attr("disabled",!0).is("[type='search'], :jqmData(type='search')")?a=this.element.parent():a=this.element,a.addClass("ui-disabled"),this._setOption("disabled",!0)},enable:function(){var a;return this.element.attr("disabled",!1).is("[type='search'], :jqmData(type='search')")?a=this.element.parent():a=this.element,a.removeClass("ui-disabled"),this._setOption("disabled",!1)}}),a(c).bind("pagecreate create",function(b){a.mobile.textinput.prototype.enhanceWithin(b.target,!0)})}(a),function(a,b){a.mobile.listview.prototype.options.filter=!1,a.mobile.listview.prototype.options.filterPlaceholder="Filter items...",a.mobile.listview.prototype.options.filterTheme="c";var d=function(a,b,c){return a.toString().toLowerCase().indexOf(b)===-1};a.mobile.listview.prototype.options.filterCallback=d,a(c).delegate(":jqmData(role='listview')","listviewcreate",function(){var b=a(this),c=b.data("listview");if(!c.options.filter)return;var e=a("<form>",{"class":"ui-listview-filter ui-bar-"+c.options.filterTheme,role:"search"}),f=a("<input>",{placeholder:c.options.filterPlaceholder}).attr("data-"+a.mobile.ns+"type","search").jqmData("lastval","").bind("keyup change",function(){var e=a(this),f=this.value.toLowerCase(),g=null,h=e.jqmData("lastval")+"",i=!1,j="",k,l=c.options.filterCallback!==d;c._trigger("beforefilter","beforefilter",{input:this}),e.jqmData("lastval",f),l||f.length<h.length||f.indexOf(h)!==0?g=b.children():g=b.children(":not(.ui-screen-hidden)");if(f){for(var m=g.length-1;m>=0;m--)k=a(g[m]),j=k.jqmData("filtertext")||k.text(),k.is("li:jqmData(role=list-divider)")?(k.toggleClass("ui-filter-hidequeue",!i),i=!1):c.options.filterCallback(j,f,k)?k.toggleClass("ui-filter-hidequeue",!0):i=!0;g.filter(":not(.ui-filter-hidequeue)").toggleClass("ui-screen-hidden",!1),g.filter(".ui-filter-hidequeue").toggleClass("ui-screen-hidden",!0).toggleClass("ui-filter-hidequeue",!1)}else g.toggleClass("ui-screen-hidden",!1);c._refreshCorners()}).appendTo(e).textinput();c.options.inset&&e.addClass("ui-listview-filter-inset"),e.bind("submit",function(){return!1}).insertBefore(b)})}(a),function(a,d){a.widget("mobile.slider",a.mobile.widget,{widgetEventPrefix:"slide",options:{theme:null,trackTheme:null,disabled:!1,initSelector:"input[type='range'], :jqmData(type='range'), :jqmData(role='slider')",mini:!1},_create:function(){var e=this,f=this.element,g=a.mobile.getInheritedTheme(f,"c"),h=this.options.theme||g,i=this.options.trackTheme||g,j=f[0].nodeName.toLowerCase(),k=j==="select"?"ui-slider-switch":"",l=f.attr("id"),m=a("[for='"+l+"']"),n=m.attr("id")||l+"-label",o=m.attr("id",n),p=function(){return j==="input"?parseFloat(f.val()):f[0].selectedIndex},q=j==="input"?parseFloat(f.attr("min")):0,r=j==="input"?parseFloat(f.attr("max")):f.find("option").length-1,s=b.parseFloat(f.attr("step")||1),t=this.options.inline||f.jqmData("inline")===!0?" ui-slider-inline":"",u=this.options.mini||f.jqmData("mini")?" ui-slider-mini":"",v=c.createElement("a"),w=a(v),x=c.createElement("div"),y=a(x),z=f.jqmData("highlight")&&j!=="select"?function(){var b=c.createElement("div");return b.className="ui-slider-bg "+a.mobile.activeBtnClass+" ui-btn-corner-all",a(b).prependTo(y)}():!1,A;this._type=j,v.setAttribute("href","#"),x.setAttribute("role","application"),x.className=["ui-slider ",k," ui-btn-down-",i," ui-btn-corner-all",t,u].join(""),v.className="ui-slider-handle",x.appendChild(v),w.buttonMarkup({corners:!0,theme:h,shadow:!0}).attr({role:"slider","aria-valuemin":q,"aria-valuemax":r,"aria-valuenow":p(),"aria-valuetext":p(),title:p(),"aria-labelledby":n}),a.extend(this,{slider:y,handle:w,valuebg:z,dragging:!1,beforeStart:null,userModified:!1,mouseMoved:!1});if(j==="select"){var B=c.createElement("div");B.className="ui-slider-inneroffset";for(var C=0,D=x.childNodes.length;C<D;C++)B.appendChild(x.childNodes[C]);x.appendChild(B),w.addClass("ui-slider-handle-snapping"),A=f.find("option");for(var E=0,F=A.length;E<F;E++){var G=E?"a":"b",H=E?" "+a.mobile.activeBtnClass:" ui-btn-down-"+i,I=c.createElement("div"),J=c.createElement("span");J.className=["ui-slider-label ui-slider-label-",G,H," ui-btn-corner-all"].join(""),J.setAttribute("role","img"),J.appendChild(c.createTextNode(A[E].innerHTML)),a(J).prependTo(y)}e._labels=a(".ui-slider-label",y)}o.addClass("ui-slider"),f.addClass(j==="input"?"ui-slider-input":"ui-slider-switch").change(function(){e.mouseMoved||e.refresh(p(),!0)}).keyup(function(){e.refresh(p(),!0,!0)}).blur(function(){e.refresh(p(),!0)}),this._preventDocumentDrag=function(a){if(e.dragging&&!e.options.disabled)return e.mouseMoved=!0,j==="select"&&w.removeClass("ui-slider-handle-snapping"),e.refresh(a),e.userModified=e.beforeStart!==f[0].selectedIndex,!1},this._on(a(c),{vmousemove:this._preventDocumentDrag}),f.bind("vmouseup",a.proxy(e._checkedRefresh,e)),y.bind("vmousedown",function(a){return e.options.disabled?!1:(e.dragging=!0,e.userModified=!1,e.mouseMoved=!1,j==="select"&&(e.beforeStart=f[0].selectedIndex),e.refresh(a),e._trigger("start"),!1)}).bind("vclick",!1),this._sliderMouseUp=function(){if(e.dragging)return e.dragging=!1,j==="select"&&(w.addClass("ui-slider-handle-snapping"),e.mouseMoved?e.userModified?e.refresh(e.beforeStart===0?1:0):e.refresh(e.beforeStart):e.refresh(e.beforeStart===0?1:0)),e.mouseMoved=!1,e._trigger("stop"),!1},this._on(y.add(c),{vmouseup:this._sliderMouseUp}),y.insertAfter(f),j==="select"&&this.handle.bind({focus:function(){y.addClass(a.mobile.focusClass)},blur:function(){y.removeClass(a.mobile.focusClass)}}),this.handle.bind({vmousedown:function(){a(this).focus()},vclick:!1,keydown:function(b){var c=p();if(e.options.disabled)return;switch(b.keyCode){case a.mobile.keyCode.HOME:case a.mobile.keyCode.END:case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:b.preventDefault(),e._keySliding||(e._keySliding=!0,a(this).addClass("ui-state-active"))}switch(b.keyCode){case a.mobile.keyCode.HOME:e.refresh(q);break;case a.mobile.keyCode.END:e.refresh(r);break;case a.mobile.keyCode.PAGE_UP:case a.mobile.keyCode.UP:case a.mobile.keyCode.RIGHT:e.refresh(c+s);break;case a.mobile.keyCode.PAGE_DOWN:case a.mobile.keyCode.DOWN:case a.mobile.keyCode.LEFT:e.refresh(c-s)}},keyup:function(b){e._keySliding&&(e._keySliding=!1,a(this).removeClass("ui-state-active"))}}),this.refresh(d,d,!0)},_checkedRefresh:function(){this.value!=this._value()&&this.refresh(this._value())},_value:function(){return this._type==="input"?parseFloat(this.element.val()):this.element[0].selectedIndex},refresh:function(b,c,d){(this.options.disabled||this.element.attr("disabled"))&&this.disable(),this.value=this._value();var e=this.element,f,g=e[0].nodeName.toLowerCase(),h=g==="input"?parseFloat(e.attr("min")):0,i=g==="input"?parseFloat(e.attr("max")):e.find("option").length-1,j=g==="input"&&parseFloat(e.attr("step"))>0?parseFloat(e.attr("step")):1;if(typeof b=="object"){var k=b,l=8;if(!this.dragging||k.pageX<this.slider.offset().left-l||k.pageX>this.slider.offset().left+this.slider.width()+l)return;f=Math.round((k.pageX-this.slider.offset().left)/this.slider.width()*100)}else b==null&&(b=g==="input"?parseFloat(e.val()||0):e[0].selectedIndex),f=(parseFloat(b)-h)/(i-h)*100;if(isNaN(f))return;f<0&&(f=0),f>100&&(f=100);var m=f/100*(i-h)+h,n=(m-h)%j,o=m-n;Math.abs(n)*2>=j&&(o+=n>0?j:-j),m=parseFloat(o.toFixed(5)),m<h&&(m=h),m>i&&(m=i),this.handle.css("left",f+"%"),this.handle.attr({"aria-valuenow":g==="input"?m:e.find("option").eq(m).attr("value"),"aria-valuetext":g==="input"?m:e.find("option").eq(m).getEncodedText(),title:g==="input"?m:e.find("option").eq(m).getEncodedText()}),this.valuebg&&this.valuebg.css("width",f+"%");if(this._labels){var p=this.handle.width()/this.slider.width()*100,q=f&&p+(100-p)*f/100,r=f===100?0:Math.min(p+100-q,100);this._labels.each(function(){var b=a(this).is(".ui-slider-label-a");a(this).width((b?q:r)+"%")})}if(!d){var s=!1;g==="input"?(s=e.val()!==m,e.val(m)):(s=e[0].selectedIndex!==m,e[0].selectedIndex=m),!c&&s&&e.trigger("change")}},enable:function(){return this.element.attr("disabled",!1),this.slider.removeClass("ui-disabled").attr("aria-disabled",!1),this._setOption("disabled",!1)},disable:function(){return this.element.attr("disabled",!0),this.slider.addClass("ui-disabled").attr("aria-disabled",!0),this._setOption("disabled",!0)}}),a(c).bind("pagecreate create",function(b){a.mobile.slider.prototype.enhanceWithin(b.target,!0)})}(a),function(a,d){a.widget("mobile.selectmenu",a.mobile.widget,{options:{theme:null,disabled:!1,icon:"arrow-d",iconpos:"right",inline:!1,corners:!0,shadow:!0,iconshadow:!0,overlayTheme:"a",hidePlaceholderMenuItems:!0,closeText:"Close",nativeMenu:!0,preventFocusZoom:/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")>-1,initSelector:"select:not( :jqmData(role='slider') )",mini:!1},_button:function(){return a("<div/>")},_setDisabled:function(a){return this.element.attr("disabled",a),this.button.attr("aria-disabled",a),this._setOption("disabled",a)},_focusButton:function(){var a=this;setTimeout(function(){a.button.focus()},40)},_selectOptions:function(){return this.select.find("option")},_preExtension:function(){var b="";!~this.element[0].className.indexOf("ui-btn-left")||(b=" ui-btn-left"),!~this.element[0].className.indexOf("ui-btn-right")||(b=" ui-btn-right"),this.select=this.element.wrap("<div class='ui-select"+b+"'>"),this.selectID=this.select.attr("id"),this.label=a("label[for='"+this.selectID+"']").addClass("ui-select"),this.isMultiple=this.select[0].multiple,this.options.theme||(this.options.theme=a.mobile.getInheritedTheme(this.select,"c"))},_create:function(){this._preExtension(),this._trigger("beforeCreate"),this.button=this._button();var c=this,d=this.options,e=d.inline||this.select.jqmData("inline"),f=d.mini||this.select.jqmData("mini"),g=d.icon?d.iconpos||this.select.jqmData("iconpos"):!1,h=this.select[0].selectedIndex===-1?0:this.select[0].selectedIndex,i=this.button.insertBefore(this.select).buttonMarkup({theme:d.theme,icon:d.icon,iconpos:g,inline:e,corners:d.corners,shadow:d.shadow,iconshadow:d.iconshadow,mini:f});this.setButtonText(),d.nativeMenu&&b.opera&&b.opera.version&&i.addClass("ui-select-nativeonly"),this.isMultiple&&(this.buttonCount=a("<span>").addClass("ui-li-count ui-btn-up-c ui-btn-corner-all").hide().appendTo(i.addClass("ui-li-has-count"))),(d.disabled||this.element.attr("disabled"))&&this.disable(),this.select.change(function(){c.refresh()}),this.build()},build:function(){var b=this;this.select.appendTo(b.button).bind("vmousedown",function(){b.button.addClass(a.mobile.activeBtnClass)}).bind("focus",function(){b.button.addClass(a.mobile.focusClass)}).bind("blur",function(){b.button.removeClass(a.mobile.focusClass)}).bind("focus vmouseover",function(){b.button.trigger("vmouseover")}).bind("vmousemove",function(){b.button.removeClass(a.mobile.activeBtnClass)}).bind("change blur vmouseout",function(){b.button.trigger("vmouseout").removeClass(a.mobile.activeBtnClass)}).bind("change blur",function(){b.button.removeClass("ui-btn-down-"+b.options.theme)}),b.button.bind("vmousedown",function(){b.options.preventFocusZoom&&a.mobile.zoom.disable(!0)}).bind("mouseup",function(){b.options.preventFocusZoom&&setTimeout(function(){a.mobile.zoom.enable(!0)},0)})},selected:function(){return this._selectOptions().filter(":selected")},selectedIndices:function(){var a=this;return this.selected().map(function(){return a._selectOptions().index(this)}).get()},setButtonText:function(){var b=this,d=this.selected(),e=this.placeholder,f=a(c.createElement("span"));this.button.find(".ui-btn-text").html(function(){return d.length?e=d.map(function(){return a(this).text()}).get().join(", "):e=b.placeholder,f.text(e).addClass(b.select.attr("class")).addClass(d.attr("class"))})},setButtonCount:function(){var a=this.selected();this.isMultiple&&this.buttonCount[a.length>1?"show":"hide"]().text(a.length)},refresh:function(){this.setButtonText(),this.setButtonCount()},open:a.noop,close:a.noop,disable:function(){this._setDisabled(!0),this.button.addClass("ui-disabled")},enable:function(){this._setDisabled(!1),this.button.removeClass("ui-disabled")}}),a(c).bind("pagecreate create",function(b){a.mobile.selectmenu.prototype.enhanceWithin(b.target,!0)})}(a),function(a,d){var e=function(d){var e=d.select,f=d.selectID,g=d.label,h=d.select.closest(".ui-page"),i=d._selectOptions(),j=d.isMultiple=d.select[0].multiple,k=f+"-button",l=f+"-menu",m=a("<div data-"+a.mobile.ns+"role='dialog' data-"+a.mobile.ns+"theme='"+d.options.theme+"' data-"+a.mobile.ns+"overlay-theme='"+d.options.overlayTheme+"'>"+"<div data-"+a.mobile.ns+"role='header'>"+"<div class='ui-title'>"+g.getEncodedText()+"</div>"+"</div>"+"<div data-"+a.mobile.ns+"role='content'></div>"+"</div>"),n=a("<div>",{"class":"ui-selectmenu"}).insertAfter(d.select).popup({theme:"a"}),o=a("<ul>",{"class":"ui-selectmenu-list",id:l,role:"listbox","aria-labelledby":k}).attr("data-"+a.mobile.ns+"theme",d.options.theme).appendTo(n),p=a("<div>",{"class":"ui-header ui-bar-"+d.options.theme}).prependTo(n),q=a("<h1>",{"class":"ui-title"}).appendTo(p),r,s,t;d.isMultiple&&(t=a("<a>",{text:d.options.closeText,href:"#","class":"ui-btn-left"}).attr("data-"+a.mobile.ns+"iconpos","notext").attr("data-"+a.mobile.ns+"icon","delete").appendTo(p).buttonMarkup()),a.extend(d,{select:d.select,selectID:f,buttonId:k,menuId:l,thisPage:h,menuPage:m,label:g,selectOptions:i,isMultiple:j,theme:d.options.theme,listbox:n,list:o,header:p,headerTitle:q,headerClose:t,menuPageContent:r,menuPageClose:s,placeholder:"",build:function(){var b=this;b.refresh(),b.select.attr("tabindex","-1").focus(function(){a(this).blur(),b.button.focus()}),b.button.bind("vclick keydown",function(c){if(c.type==="vclick"||c.keyCode&&(c.keyCode===a.mobile.keyCode.ENTER||c.keyCode===a.mobile.keyCode.SPACE))b.open(),c.preventDefault()}),b.list.attr("role","listbox").bind("focusin",function(b){a(b.target).attr("tabindex","0").trigger("vmouseover")}).bind("focusout",function(b){a(b.target).attr("tabindex","-1").trigger("vmouseout")}).delegate("li:not(.ui-disabled, .ui-li-divider)","click",function(c){var e=b.select[0].selectedIndex,f=b.list.find("li:not(.ui-li-divider)").index(this),g=b._selectOptions().eq(f)[0];g.selected=b.isMultiple?!g.selected:!0,b.isMultiple&&a(this).find(".ui-icon").toggleClass("ui-icon-checkbox-on",g.selected).toggleClass("ui-icon-checkbox-off",!g.selected),(b.isMultiple||e!==f)&&b.select.trigger("change"),b.isMultiple?b.list.find("li:not(.ui-li-divider)").eq(f).addClass("ui-btn-down-"+d.options.theme).find("a").first().focus():b.close(),c.preventDefault()}).keydown(function(b){var c=a(b.target),e=c.closest("li"),f,g;switch(b.keyCode){case 38:return f=e.prev().not(".ui-selectmenu-placeholder"),f.is(".ui-li-divider")&&(f=f.prev()),f.length&&(c.blur().attr("tabindex","-1"),f.addClass("ui-btn-down-"+d.options.theme).find("a").first().focus()),!1;case 40:return g=e.next(),g.is(".ui-li-divider")&&(g=g.next()),g.length&&(c.blur().attr("tabindex","-1"),g.addClass("ui-btn-down-"+d.options.theme).find("a").first().focus()),!1;case 13:case 32:return c.trigger("click"),!1}}),b.menuPage.bind("pagehide",function(){b.list.appendTo(b.listbox),b._focusButton(),a.mobile._bindPageRemove.call(b.thisPage)}),b.listbox.bind("popupafterclose",function(a){b.close()}),b.isMultiple&&b.headerClose.click(function(){if(b.menuType==="overlay")return b.close(),!1}),b.thisPage.addDependents(this.menuPage)},_isRebuildRequired:function(){var a=this.list.find("li"),b=this._selectOptions();return b.text()!==a.text()},selected:function(){return this._selectOptions().filter(":selected:not( :jqmData(placeholder='true') )")},refresh:function(b,c){var d=this,e=this.element,f=this.isMultiple,g;(b||this._isRebuildRequired())&&d._buildList(),g=this.selectedIndices(),d.setButtonText(),d.setButtonCount(),d.list.find("li:not(.ui-li-divider)").removeClass(a.mobile.activeBtnClass).attr("aria-selected",!1).each(function(b){if(a.inArray(b,g)>-1){var c=a(this);c.attr("aria-selected",!0),d.isMultiple?c.find(".ui-icon").removeClass("ui-icon-checkbox-off").addClass("ui-icon-checkbox-on"):c.is(".ui-selectmenu-placeholder")?c.next().addClass(a.mobile.activeBtnClass):c.addClass(a.mobile.activeBtnClass)}})},close:function(){if(this.options.disabled||!this.isOpen)return;var b=this;b.menuType==="page"?a.mobile.back():(b.listbox.popup("close"),b.list.appendTo(b.listbox),b._focusButton()),b.isOpen=!1},open:function(){function o(){var b=c.list.find("."+a.mobile.activeBtnClass+" a");b.length===0&&(b=c.list.find("li.ui-btn:not( :jqmData(placeholder='true') ) a")),b.first().focus().closest("li").addClass("ui-btn-down-"+d.options.theme)}if(this.options.disabled)return;var c=this,e=a(b),f=c.list.parent(),g=f.outerHeight(),h=f.outerWidth(),i=a("."+a.mobile.activePageClass),j=e.scrollTop(),k=c.button.offset().top,l=e.height(),n=e.width();c.button.addClass(a.mobile.activeBtnClass),setTimeout(function(){c.button.removeClass(a.mobile.activeBtnClass)},300),g>l-80||!a.support.scrollTop?(c.menuPage.appendTo(a.mobile.pageContainer).page(),c.menuPageContent=m.find(".ui-content"),c.menuPageClose=m.find(".ui-header a"),c.thisPage.unbind("pagehide.remove"),j===0&&k>l&&c.thisPage.one("pagehide",function(){a(this).jqmData("lastScroll",k)}),c.menuPage.one("pageshow",function(){o(),c.isOpen=!0}),c.menuType="page",c.menuPageContent.append(c.list),c.menuPage.find("div .ui-title").text(c.label.text()),a.mobile.changePage(c.menuPage,{transition:a.mobile.defaultDialogTransition})):(c.menuType="overlay",c.listbox.one("popupafteropen",o).popup("open",{x:c.button.offset().left+c.button.outerWidth()/2,y:c.button.offset().top+c.button.outerHeight()/2}),c.isOpen=!0)},_buildList:function(){var b=this,d=this.options,e=this.placeholder,f=!0,g=[],h=[],i=b.isMultiple?"checkbox-off":"false";b.list.empty().filter(".ui-listview").listview("destroy");var j=b.select.find("option"),k=j.length,l=this.select[0],m="data-"+a.mobile.ns,n=m+"option-index",o=m+"icon",p=m+"role",q=m+"placeholder",r=c.createDocumentFragment(),s=!1,t;for(var u=0;u<k;u++,s=!1){var v=j[u],w=a(v),x=v.parentNode,y=w.text(),z=c.createElement("a"),A=[];z.setAttribute("href","#"),z.appendChild(c.createTextNode(y));if(x!==l&&x.nodeName.toLowerCase()==="optgroup"){var B=x.getAttribute("label");if(B!==t){var C=c.createElement("li");C.setAttribute(p,"list-divider"),C.setAttribute("role","option"),C.setAttribute("tabindex","-1"),C.appendChild(c.createTextNode(B)),r.appendChild(C),t=B}}f&&(!v.getAttribute("value")||y.length===0||w.jqmData("placeholder"))&&(f=!1,s=!0,v.setAttribute(q,!0),d.hidePlaceholderMenuItems&&A.push("ui-selectmenu-placeholder"),e||(e=b.placeholder=y));var D=c.createElement("li");v.disabled&&(A.push("ui-disabled"),D.setAttribute("aria-disabled",!0)),D.setAttribute(n,u),D.setAttribute(o,i),s&&D.setAttribute(q,!0),D.className=A.join(" "),D.setAttribute("role","option"),z.setAttribute("tabindex","-1"),D.appendChild(z),r.appendChild(D)}b.list[0].appendChild(r),!this.isMultiple&&!e.length?this.header.hide():this.headerTitle.text(this.placeholder),b.list.listview()},_button:function(){return a("<a>",{href:"#",role:"button",id:this.buttonId,"aria-haspopup":"true","aria-owns":this.menuId})}})};a(c).bind("selectmenubeforecreate",function(b){var c=a(b.target).data("selectmenu");!c.options.nativeMenu&&c.element.parents(":jqmData(role='popup')").length===0&&e(c)})}(a),function(a,d){a.widget("mobile.fixedtoolbar",a.mobile.widget,{options:{visibleOnPageShow:!0,disablePageZoom:!0,transition:"slide",fullscreen:!1,tapToggle:!0,tapToggleBlacklist:"a, button, input, select, textarea, .ui-header-fixed, .ui-footer-fixed, .ui-popup",hideDuringFocus:"input, textarea, select",updatePagePadding:!0,trackPersistentToolbars:!0,supportBlacklist:function(){var a=b,c=navigator.userAgent,d=navigator.platform,e=c.match(/AppleWebKit\/([0-9]+)/),f=!!e&&e[1],g=c.match(/Fennec\/([0-9]+)/),h=!!g&&g[1],i=c.match(/Opera Mobi\/([0-9]+)/),j=!!i&&i[1];return(d.indexOf("iPhone")>-1||d.indexOf("iPad")>-1||d.indexOf("iPod")>-1)&&f&&f<534||a.operamini&&{}.toString.call(a.operamini)==="[object OperaMini]"||i&&j<7458||c.indexOf("Android")>-1&&f&&f<533||h&&h<6||"palmGetResource"in b&&f&&f<534||c.indexOf("MeeGo")>-1&&c.indexOf("NokiaBrowser/8.5.0")>-1?!0:!1},initSelector:":jqmData(position='fixed')"},_create:function(){var a=this,b=a.options,c=a.element,d=c.is(":jqmData(role='header')")?"header":"footer",e=c.closest(".ui-page");if(b.supportBlacklist()){a.destroy();return}c.addClass("ui-"+d+"-fixed"),b.fullscreen?(c.addClass("ui-"+d+"-fullscreen"),e.addClass("ui-page-"+d+"-fullscreen")):e.addClass("ui-page-"+d+"-fixed"),a._addTransitionClass(),a._bindPageEvents(),a._bindToggleHandlers()},_addTransitionClass:function(){var a=this.options.transition;a&&a!=="none"&&(a==="slide"&&(a=this.element.is(".ui-header")?"slidedown":"slideup"),this.element.addClass(a))},_bindPageEvents:function(){var c=this,d=c.options,e=c.element;e.closest(".ui-page").bind("pagebeforeshow",function(){d.disablePageZoom&&a.mobile.zoom.disable(!0),d.visibleOnPageShow||c.hide(!0)}).bind("webkitAnimationStart animationstart updatelayout",function(){var a=this;d.updatePagePadding&&c.updatePagePadding(a)}).bind("pageshow",function(){var e=this;c.updatePagePadding(e),d.updatePagePadding&&a(b).bind("throttledresize."+c.widgetName,function(){c.updatePagePadding(e)})}).bind("pagebeforehide",function(e,f){d.disablePageZoom&&a.mobile.zoom.enable(!0),d.updatePagePadding&&a(b).unbind("throttledresize."+c.widgetName);if(d.trackPersistentToolbars){var g=a(".ui-footer-fixed:jqmData(id)",this),h=a(".ui-header-fixed:jqmData(id)",this),i=g.length&&f.nextPage&&a(".ui-footer-fixed:jqmData(id='"+g.jqmData("id")+"')",f.nextPage)||a(),j=h.length&&f.nextPage&&a(".ui-header-fixed:jqmData(id='"+h.jqmData("id")+"')",f.nextPage)||a();if(i.length||j.length)i.add(j).appendTo(a.mobile.pageContainer),f.nextPage.one("pageshow",function(){i.add(j).appendTo(this)})}})},_visible:!0,updatePagePadding:function(b){var c=this.element,d=c.is(".ui-header");if(this.options.fullscreen)return;b=b||c.closest(".ui-page"),a(b).css("padding-"+(d?"top":"bottom"),c.outerHeight())},_useTransition:function(c){var d=a(b),e=this.element,f=d.scrollTop(),g=e.height(),h=e.closest(".ui-page").height(),i=a.mobile.getScreenHeight(),j=e.is(":jqmData(role='header')")?"header":"footer";return!c&&(this.options.transition&&this.options.transition!=="none"&&(j==="header"&&!this.options.fullscreen&&f>g||j==="footer"&&!this.options.fullscreen&&f+i<h-g)||this.options.fullscreen)},show:function(a){var b="ui-fixed-hidden",c=this.element;this._useTransition(a)?c.removeClass("out "+b).addClass("in"):c.removeClass(b),this._visible=!0},hide:function(a){var b="ui-fixed-hidden",c=this.element,d="out"+(this.options.transition==="slide"?" reverse":"");this._useTransition(a)?c.addClass(d).removeClass("in").animationComplete(function(){c.addClass(b).removeClass(d)}):c.addClass(b).removeClass(d),this._visible=!1},toggle:function(){this[this._visible?"hide":"show"]()},_bindToggleHandlers:function(){var b=this,c=b.options,d=b.element;d.closest(".ui-page").bind("vclick",function(d){c.tapToggle&&!a(d.target).closest(c.tapToggleBlacklist).length&&b.toggle()}).bind("focusin focusout",function(d){screen.width<500&&a(d.target).is(c.hideDuringFocus)&&!a(d.target).closest(".ui-header-fixed, .ui-footer-fixed").length&&b[d.type==="focusin"&&b._visible?"hide":"show"]()})},destroy:function(){this.element.removeClass("ui-header-fixed ui-footer-fixed ui-header-fullscreen ui-footer-fullscreen in out fade slidedown slideup ui-fixed-hidden"),this.element.closest(".ui-page").removeClass("ui-page-header-fixed ui-page-footer-fixed ui-page-header-fullscreen ui-page-footer-fullscreen")}}),a(c).bind("pagecreate create",function(b){a(b.target).jqmData("fullscreen")&&a(a.mobile.fixedtoolbar.prototype.options.initSelector,b.target).not(":jqmData(fullscreen)").jqmData("fullscreen",!0),a.mobile.fixedtoolbar.prototype.enhanceWithin(b.target)})}(a),function(a,b){function i(a){d=a.originalEvent,h=d.accelerationIncludingGravity,e=Math.abs(h.x),f=Math.abs(h.y),g=Math.abs(h.z),!b.orientation&&(e>7||(g>6&&f<8||g<8&&f>6)&&e>5)?c.enabled&&c.disable():c.enabled||c.enable()}if(!(/iPhone|iPad|iPod/.test(navigator.platform)&&navigator.userAgent.indexOf("AppleWebKit")>-1))return;var c=a.mobile.zoom,d,e,f,g,h;a(b).bind("orientationchange.iosorientationfix",c.enable).bind("devicemotion.iosorientationfix",i)}(a,this),function(a,b,d){function h(){e.removeClass("ui-mobile-rendering")}var e=a("html"),f=a("head"),g=a(b);a(b.document).trigger("mobileinit");if(!a.mobile.gradeA())return;a.mobile.ajaxBlacklist&&(a.mobile.ajaxEnabled=!1),e.addClass("ui-mobile ui-mobile-rendering"),setTimeout(h,5e3),a.extend(a.mobile,{initializePage:function(){var b=a(":jqmData(role='page'), :jqmData(role='dialog')"),d=a.mobile.path.parseLocation().hash.replace("#",""),e=c.getElementById(d);b.length||(b=a("body").wrapInner("<div data-"+a.mobile.ns+"role='page'></div>").children(0)),b.each(function(){var b=a(this);b.jqmData("url")||b.attr("data-"+a.mobile.ns+"url",b.attr("id")||location.pathname+location.search)}),a.mobile.firstPage=b.first(),a.mobile.pageContainer=b.first().parent().addClass("ui-mobile-viewport"),g.trigger("pagecontainercreate"),a.mobile.showPageLoadingMsg(),h(),!a.mobile.hashListeningEnabled||!a.mobile.path.isHashValid(location.hash)||!a(e).is(':jqmData(role="page")')&&!a.mobile.path.isPath(d)&&d!==a.mobile.dialogHashKey?(a.mobile.path.isHashValid(location.hash)&&(a.mobile.urlHistory.initialDst=d.replace("#","")),a.mobile.changePage(a.mobile.firstPage,{transition:"none",reverse:!0,changeHash:!1,fromHashChange:!0})):g.trigger("hashchange",[!0])}}),a.mobile.navreadyDeferred.resolve(),a(function(){b.scrollTo(0,1),a.mobile.defaultHomeScroll=!a.support.scrollTop||a(b).scrollTop()===1?0:1,a.fn.controlgroup&&a(c).bind("pagecreate create",function(b){a(":jqmData(role='controlgroup')",b.target).jqmEnhanceable().controlgroup({excludeInvisible:!1})}),a.mobile.autoInitializePage&&a.mobile.initializePage(),g.load(a.mobile.silentScroll),a.support.cssPointerEvents||a(c).delegate(".ui-disabled","vclick",function(a){a.preventDefault(),a.stopImmediatePropagation()})})}(a,this)}); \ No newline at end of file | ||
diff --git a/htdocs/jquery.js b/htdocs/jquery.js new file mode 100644 index 0000000..f65cf1d --- /dev/null +++ b/htdocs/jquery.js | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | /*! jQuery v1.8.2 jquery.com | jquery.org/license */ | ||
| 2 | (function(a,b){function G(a){var b=F[a]={};return p.each(a.split(s),function(a,c){b[c]=!0}),b}function J(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(I,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:+d+""===d?+d:H.test(d)?p.parseJSON(d):d}catch(f){}p.data(a,c,d)}else d=b}return d}function K(a){var b;for(b in a){if(b==="data"&&p.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function ba(){return!1}function bb(){return!0}function bh(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function bi(a,b){do a=a[b];while(a&&a.nodeType!==1);return a}function bj(a,b,c){b=b||0;if(p.isFunction(b))return p.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return p.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=p.grep(a,function(a){return a.nodeType===1});if(be.test(b))return p.filter(b,d,!c);b=p.filter(b,d)}return p.grep(a,function(a,d){return p.inArray(a,b)>=0===c})}function bk(a){var b=bl.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function bC(a,b){return a.getElementsByTagName(b)[0]||a.appendChild(a.ownerDocument.createElement(b))}function bD(a,b){if(b.nodeType!==1||!p.hasData(a))return;var c,d,e,f=p._data(a),g=p._data(b,f),h=f.events;if(h){delete g.handle,g.events={};for(c in h)for(d=0,e=h[c].length;d<e;d++)p.event.add(b,c,h[c][d])}g.data&&(g.data=p.extend({},g.data))}function bE(a,b){var c;if(b.nodeType!==1)return;b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase(),c==="object"?(b.parentNode&&(b.outerHTML=a.outerHTML),p.support.html5Clone&&a.innerHTML&&!p.trim(b.innerHTML)&&(b.innerHTML=a.innerHTML)):c==="input"&&bv.test(a.type)?(b.defaultChecked=b.checked=a.checked,b.value!==a.value&&(b.value=a.value)):c==="option"?b.selected=a.defaultSelected:c==="input"||c==="textarea"?b.defaultValue=a.defaultValue:c==="script"&&b.text!==a.text&&(b.text=a.text),b.removeAttribute(p.expando)}function bF(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bG(a){bv.test(a.type)&&(a.defaultChecked=a.checked)}function bY(a,b){if(b in a)return b;var c=b.charAt(0).toUpperCase()+b.slice(1),d=b,e=bW.length;while(e--){b=bW[e]+c;if(b in a)return b}return d}function bZ(a,b){return a=b||a,p.css(a,"display")==="none"||!p.contains(a.ownerDocument,a)}function b$(a,b){var c,d,e=[],f=0,g=a.length;for(;f<g;f++){c=a[f];if(!c.style)continue;e[f]=p._data(c,"olddisplay"),b?(!e[f]&&c.style.display==="none"&&(c.style.display=""),c.style.display===""&&bZ(c)&&(e[f]=p._data(c,"olddisplay",cc(c.nodeName)))):(d=bH(c,"display"),!e[f]&&d!=="none"&&p._data(c,"olddisplay",d))}for(f=0;f<g;f++){c=a[f];if(!c.style)continue;if(!b||c.style.display==="none"||c.style.display==="")c.style.display=b?e[f]||"":"none"}return a}function b_(a,b,c){var d=bP.exec(b);return d?Math.max(0,d[1]-(c||0))+(d[2]||"px"):b}function ca(a,b,c,d){var e=c===(d?"border":"content")?4:b==="width"?1:0,f=0;for(;e<4;e+=2)c==="margin"&&(f+=p.css(a,c+bV[e],!0)),d?(c==="content"&&(f-=parseFloat(bH(a,"padding"+bV[e]))||0),c!=="margin"&&(f-=parseFloat(bH(a,"border"+bV[e]+"Width"))||0)):(f+=parseFloat(bH(a,"padding"+bV[e]))||0,c!=="padding"&&(f+=parseFloat(bH(a,"border"+bV[e]+"Width"))||0));return f}function cb(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=!0,f=p.support.boxSizing&&p.css(a,"boxSizing")==="border-box";if(d<=0||d==null){d=bH(a,b);if(d<0||d==null)d=a.style[b];if(bQ.test(d))return d;e=f&&(p.support.boxSizingReliable||d===a.style[b]),d=parseFloat(d)||0}return d+ca(a,b,c||(f?"border":"content"),e)+"px"}function cc(a){if(bS[a])return bS[a];var b=p("<"+a+">").appendTo(e.body),c=b.css("display");b.remove();if(c==="none"||c===""){bI=e.body.appendChild(bI||p.extend(e.createElement("iframe"),{frameBorder:0,width:0,height:0}));if(!bJ||!bI.createElement)bJ=(bI.contentWindow||bI.contentDocument).document,bJ.write("<!doctype html><html><body>"),bJ.close();b=bJ.body.appendChild(bJ.createElement(a)),c=bH(b,"display"),e.body.removeChild(bI)}return bS[a]=c,c}function ci(a,b,c,d){var e;if(p.isArray(b))p.each(b,function(b,e){c||ce.test(a)?d(a,e):ci(a+"["+(typeof e=="object"?b:"")+"]",e,c,d)});else if(!c&&p.type(b)==="object")for(e in b)ci(a+"["+e+"]",b[e],c,d);else d(a,b)}function cz(a){return function(b,c){typeof b!="string"&&(c=b,b="*");var d,e,f,g=b.toLowerCase().split(s),h=0,i=g.length;if(p.isFunction(c))for(;h<i;h++)d=g[h],f=/^\+/.test(d),f&&(d=d.substr(1)||"*"),e=a[d]=a[d]||[],e[f?"unshift":"push"](c)}}function cA(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h,i=a[f],j=0,k=i?i.length:0,l=a===cv;for(;j<k&&(l||!h);j++)h=i[j](c,d,e),typeof h=="string"&&(!l||g[h]?h=b:(c.dataTypes.unshift(h),h=cA(a,c,d,e,h,g)));return(l||!h)&&!g["*"]&&(h=cA(a,c,d,e,"*",g)),h}function cB(a,c){var d,e,f=p.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((f[d]?a:e||(e={}))[d]=c[d]);e&&p.extend(!0,a,e)}function cC(a,c,d){var e,f,g,h,i=a.contents,j=a.dataTypes,k=a.responseFields;for(f in k)f in d&&(c[k[f]]=d[f]);while(j[0]==="*")j.shift(),e===b&&(e=a.mimeType||c.getResponseHeader("content-type"));if(e)for(f in i)if(i[f]&&i[f].test(e)){j.unshift(f);break}if(j[0]in d)g=j[0];else{for(f in d){if(!j[0]||a.converters[f+" "+j[0]]){g=f;break}h||(h=f)}g=g||h}if(g)return g!==j[0]&&j.unshift(g),d[g]}function cD(a,b){var c,d,e,f,g=a.dataTypes.slice(),h=g[0],i={},j=0;a.dataFilter&&(b=a.dataFilter(b,a.dataType));if(g[1])for(c in a.converters)i[c.toLowerCase()]=a.converters[c];for(;e=g[++j];)if(e!=="*"){if(h!=="*"&&h!==e){c=i[h+" "+e]||i["* "+e];if(!c)for(d in i){f=d.split(" ");if(f[1]===e){c=i[h+" "+f[0]]||i["* "+f[0]];if(c){c===!0?c=i[d]:i[d]!==!0&&(e=f[0],g.splice(j--,0,e));break}}}if(c!==!0)if(c&&a["throws"])b=c(b);else try{b=c(b)}catch(k){return{state:"parsererror",error:c?k:"No conversion from "+h+" to "+e}}}h=e}return{state:"success",data:b}}function cL(){try{return new a.XMLHttpRequest}catch(b){}}function cM(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function cU(){return setTimeout(function(){cN=b},0),cN=p.now()}function cV(a,b){p.each(b,function(b,c){var d=(cT[b]||[]).concat(cT["*"]),e=0,f=d.length;for(;e<f;e++)if(d[e].call(a,b,c))return})}function cW(a,b,c){var d,e=0,f=0,g=cS.length,h=p.Deferred().always(function(){delete i.elem}),i=function(){var b=cN||cU(),c=Math.max(0,j.startTime+j.duration-b),d=1-(c/j.duration||0),e=0,f=j.tweens.length;for(;e<f;e++)j.tweens[e].run(d);return h.notifyWith(a,[j,d,c]),d<1&&f?c:(h.resolveWith(a,[j]),!1)},j=h.promise({elem:a,props:p.extend({},b),opts:p.extend(!0,{specialEasing:{}},c),originalProperties:b,originalOptions:c,startTime:cN||cU(),duration:c.duration,tweens:[],createTween:function(b,c,d){var e=p.Tween(a,j.opts,b,c,j.opts.specialEasing[b]||j.opts.easing);return j.tweens.push(e),e},stop:function(b){var c=0,d=b?j.tweens.length:0;for(;c<d;c++)j.tweens[c].run(1);return b?h.resolveWith(a,[j,b]):h.rejectWith(a,[j,b]),this}}),k=j.props;cX(k,j.opts.specialEasing);for(;e<g;e++){d=cS[e].call(j,a,k,j.opts);if(d)return d}return cV(j,k),p.isFunction(j.opts.start)&&j.opts.start.call(a,j),p.fx.timer(p.extend(i,{anim:j,queue:j.opts.queue,elem:a})),j.progress(j.opts.progress).done(j.opts.done,j.opts.complete).fail(j.opts.fail).always(j.opts.always)}function cX(a,b){var c,d,e,f,g;for(c in a){d=p.camelCase(c),e=b[d],f=a[c],p.isArray(f)&&(e=f[1],f=a[c]=f[0]),c!==d&&(a[d]=f,delete a[c]),g=p.cssHooks[d];if(g&&"expand"in g){f=g.expand(f),delete a[d];for(c in f)c in a||(a[c]=f[c],b[c]=e)}else b[d]=e}}function cY(a,b,c){var d,e,f,g,h,i,j,k,l=this,m=a.style,n={},o=[],q=a.nodeType&&bZ(a);c.queue||(j=p._queueHooks(a,"fx"),j.unqueued==null&&(j.unqueued=0,k=j.empty.fire,j.empty.fire=function(){j.unqueued||k()}),j.unqueued++,l.always(function(){l.always(function(){j.unqueued--,p.queue(a,"fx").length||j.empty.fire()})})),a.nodeType===1&&("height"in b||"width"in b)&&(c.overflow=[m.overflow,m.overflowX,m.overflowY],p.css(a,"display")==="inline"&&p.css(a,"float")==="none"&&(!p.support.inlineBlockNeedsLayout||cc(a.nodeName)==="inline"?m.display="inline-block":m.zoom=1)),c.overflow&&(m.overflow="hidden",p.support.shrinkWrapBlocks||l.done(function(){m.overflow=c.overflow[0],m.overflowX=c.overflow[1],m.overflowY=c.overflow[2]}));for(d in b){f=b[d];if(cP.exec(f)){delete b[d];if(f===(q?"hide":"show"))continue;o.push(d)}}g=o.length;if(g){h=p._data(a,"fxshow")||p._data(a,"fxshow",{}),q?p(a).show():l.done(function(){p(a).hide()}),l.done(function(){var b;p.removeData(a,"fxshow",!0);for(b in n)p.style(a,b,n[b])});for(d=0;d<g;d++)e=o[d],i=l.createTween(e,q?h[e]:0),n[e]=h[e]||p.style(a,e),e in h||(h[e]=i.start,q&&(i.end=i.start,i.start=e==="width"||e==="height"?1:0))}}function cZ(a,b,c,d,e){return new cZ.prototype.init(a,b,c,d,e)}function c$(a,b){var c,d={height:a},e=0;b=b?1:0;for(;e<4;e+=2-b)c=bV[e],d["margin"+c]=d["padding"+c]=a;return b&&(d.opacity=d.width=a),d}function da(a){return p.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}var c,d,e=a.document,f=a.location,g=a.navigator,h=a.jQuery,i=a.$,j=Array.prototype.push,k=Array.prototype.slice,l=Array.prototype.indexOf,m=Object.prototype.toString,n=Object.prototype.hasOwnProperty,o=String.prototype.trim,p=function(a,b){return new p.fn.init(a,b,c)},q=/[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,r=/\S/,s=/\s+/,t=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,u=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,v=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,w=/^[\],:{}\s]*$/,x=/(?:^|:|,)(?:\s*\[)+/g,y=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,z=/"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,A=/^-ms-/,B=/-([\da-z])/gi,C=function(a,b){return(b+"").toUpperCase()},D=function(){e.addEventListener?(e.removeEventListener("DOMContentLoaded",D,!1),p.ready()):e.readyState==="complete"&&(e.detachEvent("onreadystatechange",D),p.ready())},E={};p.fn=p.prototype={constructor:p,init:function(a,c,d){var f,g,h,i;if(!a)return this;if(a.nodeType)return this.context=this[0]=a,this.length=1,this;if(typeof a=="string"){a.charAt(0)==="<"&&a.charAt(a.length-1)===">"&&a.length>=3?f=[null,a,null]:f=u.exec(a);if(f&&(f[1]||!c)){if(f[1])return c=c instanceof p?c[0]:c,i=c&&c.nodeType?c.ownerDocument||c:e,a=p.parseHTML(f[1],i,!0),v.test(f[1])&&p.isPlainObject(c)&&this.attr.call(a,c,!0),p.merge(this,a);g=e.getElementById(f[2]);if(g&&g.parentNode){if(g.id!==f[2])return d.find(a);this.length=1,this[0]=g}return this.context=e,this.selector=a,this}return!c||c.jquery?(c||d).find(a):this.constructor(c).find(a)}return p.isFunction(a)?d.ready(a):(a.selector!==b&&(this.selector=a.selector,this.context=a.context),p.makeArray(a,this))},selector:"",jquery:"1.8.2",length:0,size:function(){return this.length},toArray:function(){return k.call(this)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=p.merge(this.constructor(),a);return d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")"),d},each:function(a,b){return p.each(this,a,b)},ready:function(a){return p.ready.promise().done(a),this},eq:function(a){return a=+a,a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(k.apply(this,arguments),"slice",k.call(arguments).join(","))},map:function(a){return this.pushStack(p.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:j,sort:[].sort,splice:[].splice},p.fn.init.prototype=p.fn,p.extend=p.fn.extend=function(){var a,c,d,e,f,g,h=arguments[0]||{},i=1,j=arguments.length,k=!1;typeof h=="boolean"&&(k=h,h=arguments[1]||{},i=2),typeof h!="object"&&!p.isFunction(h)&&(h={}),j===i&&(h=this,--i);for(;i<j;i++)if((a=arguments[i])!=null)for(c in a){d=h[c],e=a[c];if(h===e)continue;k&&e&&(p.isPlainObject(e)||(f=p.isArray(e)))?(f?(f=!1,g=d&&p.isArray(d)?d:[]):g=d&&p.isPlainObject(d)?d:{},h[c]=p.extend(k,g,e)):e!==b&&(h[c]=e)}return h},p.extend({noConflict:function(b){return a.$===p&&(a.$=i),b&&a.jQuery===p&&(a.jQuery=h),p},isReady:!1,readyWait:1,holdReady:function(a){a?p.readyWait++:p.ready(!0)},ready:function(a){if(a===!0?--p.readyWait:p.isReady)return;if(!e.body)return setTimeout(p.ready,1);p.isReady=!0;if(a!==!0&&--p.readyWait>0)return;d.resolveWith(e,[p]),p.fn.trigger&&p(e).trigger("ready").off("ready")},isFunction:function(a){return p.type(a)==="function"},isArray:Array.isArray||function(a){return p.type(a)==="array"},isWindow:function(a){return a!=null&&a==a.window},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):E[m.call(a)]||"object"},isPlainObject:function(a){if(!a||p.type(a)!=="object"||a.nodeType||p.isWindow(a))return!1;try{if(a.constructor&&!n.call(a,"constructor")&&!n.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||n.call(a,d)},isEmptyObject:function(a){var b;for(b in a)return!1;return!0},error:function(a){throw new Error(a)},parseHTML:function(a,b,c){var d;return!a||typeof a!="string"?null:(typeof b=="boolean"&&(c=b,b=0),b=b||e,(d=v.exec(a))?[b.createElement(d[1])]:(d=p.buildFragment([a],b,c?null:[]),p.merge([],(d.cacheable?p.clone(d.fragment):d.fragment).childNodes)))},parseJSON:function(b){if(!b||typeof b!="string")return null;b=p.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(w.test(b.replace(y,"@").replace(z,"]").replace(x,"")))return(new Function("return "+b))();p.error("Invalid JSON: "+b)},parseXML:function(c){var d,e;if(!c||typeof c!="string")return null;try{a.DOMParser?(e=new DOMParser,d=e.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(f){d=b}return(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&p.error("Invalid XML: "+c),d},noop:function(){},globalEval:function(b){b&&r.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(A,"ms-").replace(B,C)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toLowerCase()===b.toLowerCase()},each:function(a,c,d){var e,f=0,g=a.length,h=g===b||p.isFunction(a);if(d){if(h){for(e in a)if(c.apply(a[e],d)===!1)break}else for(;f<g;)if(c.apply(a[f++],d)===!1)break}else if(h){for(e in a)if(c.call(a[e],e,a[e])===!1)break}else for(;f<g;)if(c.call(a[f],f,a[f++])===!1)break;return a},trim:o&&!o.call(" ")?function(a){return a==null?"":o.call(a)}:function(a){return a==null?"":(a+"").replace(t,"")},makeArray:function(a,b){var c,d=b||[];return a!=null&&(c=p.type(a),a.length==null||c==="string"||c==="function"||c==="regexp"||p.isWindow(a)?j.call(d,a):p.merge(d,a)),d},inArray:function(a,b,c){var d;if(b){if(l)return l.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=c.length,e=a.length,f=0;if(typeof d=="number")for(;f<d;f++)a[e++]=c[f];else while(c[f]!==b)a[e++]=c[f++];return a.length=e,a},grep:function(a,b,c){var d,e=[],f=0,g=a.length;c=!!c;for(;f<g;f++)d=!!b(a[f],f),c!==d&&e.push(a[f]);return e},map:function(a,c,d){var e,f,g=[],h=0,i=a.length,j=a instanceof p||i!==b&&typeof i=="number"&&(i>0&&a[0]&&a[i-1]||i===0||p.isArray(a));if(j)for(;h<i;h++)e=c(a[h],h,d),e!=null&&(g[g.length]=e);else for(f in a)e=c(a[f],f,d),e!=null&&(g[g.length]=e);return g.concat.apply([],g)},guid:1,proxy:function(a,c){var d,e,f;return typeof c=="string"&&(d=a[c],c=a,a=d),p.isFunction(a)?(e=k.call(arguments,2),f=function(){return a.apply(c,e.concat(k.call(arguments)))},f.guid=a.guid=a.guid||p.guid++,f):b},access:function(a,c,d,e,f,g,h){var i,j=d==null,k=0,l=a.length;if(d&&typeof d=="object"){for(k in d)p.access(a,c,k,d[k],1,g,e);f=1}else if(e!==b){i=h===b&&p.isFunction(e),j&&(i?(i=c,c=function(a,b,c){return i.call(p(a),c)}):(c.call(a,e),c=null));if(c)for(;k<l;k++)c(a[k],d,i?e.call(a[k],k,c(a[k],d)):e,h);f=1}return f?a:j?c.call(a):l?c(a[0],d):g},now:function(){return(new Date).getTime()}}),p.ready.promise=function(b){if(!d){d=p.Deferred();if(e.readyState==="complete")setTimeout(p.ready,1);else if(e.addEventListener)e.addEventListener("DOMContentLoaded",D,!1),a.addEventListener("load",p.ready,!1);else{e.attachEvent("onreadystatechange",D),a.attachEvent("onload",p.ready);var c=!1;try{c=a.frameElement==null&&e.documentElement}catch(f){}c&&c.doScroll&&function g(){if(!p.isReady){try{c.doScroll("left")}catch(a){return setTimeout(g,50)}p.ready()}}()}}return d.promise(b)},p.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){E["[object "+b+"]"]=b.toLowerCase()}),c=p(e);var F={};p.Callbacks=function(a){a=typeof a=="string"?F[a]||G(a):p.extend({},a);var c,d,e,f,g,h,i=[],j=!a.once&&[],k=function(b){c=a.memory&&b,d=!0,h=f||0,f=0,g=i.length,e=!0;for(;i&&h<g;h++)if(i[h].apply(b[0],b[1])===!1&&a.stopOnFalse){c=!1;break}e=!1,i&&(j?j.length&&k(j.shift()):c?i=[]:l.disable())},l={add:function(){if(i){var b=i.length;(function d(b){p.each(b,function(b,c){var e=p.type(c);e==="function"&&(!a.unique||!l.has(c))?i.push(c):c&&c.length&&e!=="string"&&d(c)})})(arguments),e?g=i.length:c&&(f=b,k(c))}return this},remove:function(){return i&&p.each(arguments,function(a,b){var c;while((c=p.inArray(b,i,c))>-1)i.splice(c,1),e&&(c<=g&&g--,c<=h&&h--)}),this},has:function(a){return p.inArray(a,i)>-1},empty:function(){return i=[],this},disable:function(){return i=j=c=b,this},disabled:function(){return!i},lock:function(){return j=b,c||l.disable(),this},locked:function(){return!j},fireWith:function(a,b){return b=b||[],b=[a,b.slice?b.slice():b],i&&(!d||j)&&(e?j.push(b):k(b)),this},fire:function(){return l.fireWith(this,arguments),this},fired:function(){return!!d}};return l},p.extend({Deferred:function(a){var b=[["resolve","done",p.Callbacks("once memory"),"resolved"],["reject","fail",p.Callbacks("once memory"),"rejected"],["notify","progress",p.Callbacks("memory")]],c="pending",d={state:function(){return c},always:function(){return e.done(arguments).fail(arguments),this},then:function(){var a=arguments;return p.Deferred(function(c){p.each(b,function(b,d){var f=d[0],g=a[b];e[d[1]](p.isFunction(g)?function(){var a=g.apply(this,arguments);a&&p.isFunction(a.promise)?a.promise().done(c.resolve).fail(c.reject).progress(c.notify):c[f+"With"](this===e?c:this,[a])}:c[f])}),a=null}).promise()},promise:function(a){return a!=null?p.extend(a,d):d}},e={};return d.pipe=d.then,p.each(b,function(a,f){var g=f[2],h=f[3];d[f[1]]=g.add,h&&g.add(function(){c=h},b[a^1][2].disable,b[2][2].lock),e[f[0]]=g.fire,e[f[0]+"With"]=g.fireWith}),d.promise(e),a&&a.call(e,e),e},when:function(a){var b=0,c=k.call(arguments),d=c.length,e=d!==1||a&&p.isFunction(a.promise)?d:0,f=e===1?a:p.Deferred(),g=function(a,b,c){return function(d){b[a]=this,c[a]=arguments.length>1?k.call(arguments):d,c===h?f.notifyWith(b,c):--e||f.resolveWith(b,c)}},h,i,j;if(d>1){h=new Array(d),i=new Array(d),j=new Array(d);for(;b<d;b++)c[b]&&p.isFunction(c[b].promise)?c[b].promise().done(g(b,j,c)).fail(f.reject).progress(g(b,i,h)):--e}return e||f.resolveWith(j,c),f.promise()}}),p.support=function(){var b,c,d,f,g,h,i,j,k,l,m,n=e.createElement("div");n.setAttribute("className","t"),n.innerHTML=" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>",c=n.getElementsByTagName("*"),d=n.getElementsByTagName("a")[0],d.style.cssText="top:1px;float:left;opacity:.5";if(!c||!c.length)return{};f=e.createElement("select"),g=f.appendChild(e.createElement("option")),h=n.getElementsByTagName("input")[0],b={leadingWhitespace:n.firstChild.nodeType===3,tbody:!n.getElementsByTagName("tbody").length,htmlSerialize:!!n.getElementsByTagName("link").length,style:/top/.test(d.getAttribute("style")),hrefNormalized:d.getAttribute("href")==="/a",opacity:/^0.5/.test(d.style.opacity),cssFloat:!!d.style.cssFloat,checkOn:h.value==="on",optSelected:g.selected,getSetAttribute:n.className!=="t",enctype:!!e.createElement("form").enctype,html5Clone:e.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",boxModel:e.compatMode==="CSS1Compat",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0,boxSizingReliable:!0,pixelPosition:!1},h.checked=!0,b.noCloneChecked=h.cloneNode(!0).checked,f.disabled=!0,b.optDisabled=!g.disabled;try{delete n.test}catch(o){b.deleteExpando=!1}!n.addEventListener&&n.attachEvent&&n.fireEvent&&(n.attachEvent("onclick",m=function(){b.noCloneEvent=!1}),n.cloneNode(!0).fireEvent("onclick"),n.detachEvent("onclick",m)),h=e.createElement("input"),h.value="t",h.setAttribute("type","radio"),b.radioValue=h.value==="t",h.setAttribute("checked","checked"),h.setAttribute("name","t"),n.appendChild(h),i=e.createDocumentFragment(),i.appendChild(n.lastChild),b.checkClone=i.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=h.checked,i.removeChild(h),i.appendChild(n);if(n.attachEvent)for(k in{submit:!0,change:!0,focusin:!0})j="on"+k,l=j in n,l||(n.setAttribute(j,"return;"),l=typeof n[j]=="function"),b[k+"Bubbles"]=l;return p(function(){var c,d,f,g,h="padding:0;margin:0;border:0;display:block;overflow:hidden;",i=e.getElementsByTagName("body")[0];if(!i)return;c=e.createElement("div"),c.style.cssText="visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px",i.insertBefore(c,i.firstChild),d=e.createElement("div"),c.appendChild(d),d.innerHTML="<table><tr><td></td><td>t</td></tr></table>",f=d.getElementsByTagName("td"),f[0].style.cssText="padding:0;margin:0;border:0;display:none",l=f[0].offsetHeight===0,f[0].style.display="",f[1].style.display="none",b.reliableHiddenOffsets=l&&f[0].offsetHeight===0,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",b.boxSizing=d.offsetWidth===4,b.doesNotIncludeMarginInBodyOffset=i.offsetTop!==1,a.getComputedStyle&&(b.pixelPosition=(a.getComputedStyle(d,null)||{}).top!=="1%",b.boxSizingReliable=(a.getComputedStyle(d,null)||{width:"4px"}).width==="4px",g=e.createElement("div"),g.style.cssText=d.style.cssText=h,g.style.marginRight=g.style.width="0",d.style.width="1px",d.appendChild(g),b.reliableMarginRight=!parseFloat((a.getComputedStyle(g,null)||{}).marginRight)),typeof d.style.zoom!="undefined"&&(d.innerHTML="",d.style.cssText=h+"width:1px;padding:1px;display:inline;zoom:1",b.inlineBlockNeedsLayout=d.offsetWidth===3,d.style.display="block",d.style.overflow="visible",d.innerHTML="<div></div>",d.firstChild.style.width="5px",b.shrinkWrapBlocks=d.offsetWidth!==3,c.style.zoom=1),i.removeChild(c),c=d=f=g=null}),i.removeChild(n),c=d=f=g=h=i=n=null,b}();var H=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,I=/([A-Z])/g;p.extend({cache:{},deletedIds:[],uuid:0,expando:"jQuery"+(p.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){return a=a.nodeType?p.cache[a[p.expando]]:a[p.expando],!!a&&!K(a)},data:function(a,c,d,e){if(!p.acceptData(a))return;var f,g,h=p.expando,i=typeof c=="string",j=a.nodeType,k=j?p.cache:a,l=j?a[h]:a[h]&&h;if((!l||!k[l]||!e&&!k[l].data)&&i&&d===b)return;l||(j?a[h]=l=p.deletedIds.pop()||p.guid++:l=h),k[l]||(k[l]={},j||(k[l].toJSON=p.noop));if(typeof c=="object"||typeof c=="function")e?k[l]=p.extend(k[l],c):k[l].data=p.extend(k[l].data,c);return f=k[l],e||(f.data||(f.data={}),f=f.data),d!==b&&(f[p.camelCase(c)]=d),i?(g=f[c],g==null&&(g=f[p.camelCase(c)])):g=f,g},removeData:function(a,b,c){if(!p.acceptData(a))return;var d,e,f,g=a.nodeType,h=g?p.cache:a,i=g?a[p.expando]:p.expando;if(!h[i])return;if(b){d=c?h[i]:h[i].data;if(d){p.isArray(b)||(b in d?b=[b]:(b=p.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,f=b.length;e<f;e++)delete d[b[e]];if(!(c?K:p.isEmptyObject)(d))return}}if(!c){delete h[i].data;if(!K(h[i]))return}g?p.cleanData([a],!0):p.support.deleteExpando||h!=h.window?delete h[i]:h[i]=null},_data:function(a,b,c){return p.data(a,b,c,!0)},acceptData:function(a){var b=a.nodeName&&p.noData[a.nodeName.toLowerCase()];return!b||b!==!0&&a.getAttribute("classid")===b}}),p.fn.extend({data:function(a,c){var d,e,f,g,h,i=this[0],j=0,k=null;if(a===b){if(this.length){k=p.data(i);if(i.nodeType===1&&!p._data(i,"parsedAttrs")){f=i.attributes;for(h=f.length;j<h;j++)g=f[j].name,g.indexOf("data-")||(g=p.camelCase(g.substring(5)),J(i,g,k[g]));p._data(i,"parsedAttrs",!0)}}return k}return typeof a=="object"?this.each(function(){p.data(this,a)}):(d=a.split(".",2),d[1]=d[1]?"."+d[1]:"",e=d[1]+"!",p.access(this,function(c){if(c===b)return k=this.triggerHandler("getData"+e,[d[0]]),k===b&&i&&(k=p.data(i,a),k=J(i,a,k)),k===b&&d[1]?this.data(d[0]):k;d[1]=c,this.each(function(){var b=p(this);b.triggerHandler("setData"+e,d),p.data(this,a,c),b.triggerHandler("changeData"+e,d)})},null,c,arguments.length>1,null,!1))},removeData:function(a){return this.each(function(){p.removeData(this,a)})}}),p.extend({queue:function(a,b,c){var d;if(a)return b=(b||"fx")+"queue",d=p._data(a,b),c&&(!d||p.isArray(c)?d=p._data(a,b,p.makeArray(c)):d.push(c)),d||[]},dequeue:function(a,b){b=b||"fx";var c=p.queue(a,b),d=c.length,e=c.shift(),f=p._queueHooks(a,b),g=function(){p.dequeue(a,b)};e==="inprogress"&&(e=c.shift(),d--),e&&(b==="fx"&&c.unshift("inprogress"),delete f.stop,e.call(a,g,f)),!d&&f&&f.empty.fire()},_queueHooks:function(a,b){var c=b+"queueHooks";return p._data(a,c)||p._data(a,c,{empty:p.Callbacks("once memory").add(function(){p.removeData(a,b+"queue",!0),p.removeData(a,c,!0)})})}}),p.fn.extend({queue:function(a,c){var d=2;return typeof a!="string"&&(c=a,a="fx",d--),arguments.length<d?p.queue(this[0],a):c===b?this:this.each(function(){var b=p.queue(this,a,c);p._queueHooks(this,a),a==="fx"&&b[0]!=="inprogress"&&p.dequeue(this,a)})},dequeue:function(a){return this.each(function(){p.dequeue(this,a)})},delay:function(a,b){return a=p.fx?p.fx.speeds[a]||a:a,b=b||"fx",this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){var d,e=1,f=p.Deferred(),g=this,h=this.length,i=function(){--e||f.resolveWith(g,[g])};typeof a!="string"&&(c=a,a=b),a=a||"fx";while(h--)d=p._data(g[h],a+"queueHooks"),d&&d.empty&&(e++,d.empty.add(i));return i(),f.promise(c)}});var L,M,N,O=/[\t\r\n]/g,P=/\r/g,Q=/^(?:button|input)$/i,R=/^(?:button|input|object|select|textarea)$/i,S=/^a(?:rea|)$/i,T=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,U=p.support.getSetAttribute;p.fn.extend({attr:function(a,b){return p.access(this,p.attr,a,b,arguments.length>1)},removeAttr:function(a){return this.each(function(){p.removeAttr(this,a)})},prop:function(a,b){return p.access(this,p.prop,a,b,arguments.length>1)},removeProp:function(a){return a=p.propFix[a]||a,this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,f,g,h;if(p.isFunction(a))return this.each(function(b){p(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(s);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{f=" "+e.className+" ";for(g=0,h=b.length;g<h;g++)f.indexOf(" "+b[g]+" ")<0&&(f+=b[g]+" ");e.className=p.trim(f)}}}return this},removeClass:function(a){var c,d,e,f,g,h,i;if(p.isFunction(a))return this.each(function(b){p(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(s);for(h=0,i=this.length;h<i;h++){e=this[h];if(e.nodeType===1&&e.className){d=(" "+e.className+" ").replace(O," ");for(f=0,g=c.length;f<g;f++)while(d.indexOf(" "+c[f]+" ")>=0)d=d.replace(" "+c[f]+" "," ");e.className=a?p.trim(d):""}}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";return p.isFunction(a)?this.each(function(c){p(this).toggleClass(a.call(this,c,this.className,b),b)}):this.each(function(){if(c==="string"){var e,f=0,g=p(this),h=b,i=a.split(s);while(e=i[f++])h=d?h:!g.hasClass(e),g[h?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&p._data(this,"__className__",this.className),this.className=this.className||a===!1?"":p._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(O," ").indexOf(b)>=0)return!0;return!1},val:function(a){var c,d,e,f=this[0];if(!arguments.length){if(f)return c=p.valHooks[f.type]||p.valHooks[f.nodeName.toLowerCase()],c&&"get"in c&&(d=c.get(f,"value"))!==b?d:(d=f.value,typeof d=="string"?d.replace(P,""):d==null?"":d);return}return e=p.isFunction(a),this.each(function(d){var f,g=p(this);if(this.nodeType!==1)return;e?f=a.call(this,d,g.val()):f=a,f==null?f="":typeof f=="number"?f+="":p.isArray(f)&&(f=p.map(f,function(a){return a==null?"":a+""})),c=p.valHooks[this.type]||p.valHooks[this.nodeName.toLowerCase()];if(!c||!("set"in c)||c.set(this,f,"value")===b)this.value=f})}}),p.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,f=a.selectedIndex,g=[],h=a.options,i=a.type==="select-one";if(f<0)return null;c=i?f:0,d=i?f+1:h.length;for(;c<d;c++){e=h[c];if(e.selected&&(p.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!p.nodeName(e.parentNode,"optgroup"))){b=p(e).val();if(i)return b;g.push(b)}}return i&&!g.length&&h.length?p(h[f]).val():g},set:function(a,b){var c=p.makeArray(b);return p(a).find("option").each(function(){this.selected=p.inArray(p(this).val(),c)>=0}),c.length||(a.selectedIndex=-1),c}}},attrFn:{},attr:function(a,c,d,e){var f,g,h,i=a.nodeType;if(!a||i===3||i===8||i===2)return;if(e&&p.isFunction(p.fn[c]))return p(a)[c](d);if(typeof a.getAttribute=="undefined")return p.prop(a,c,d);h=i!==1||!p.isXMLDoc(a),h&&(c=c.toLowerCase(),g=p.attrHooks[c]||(T.test(c)?M:L));if(d!==b){if(d===null){p.removeAttr(a,c);return}return g&&"set"in g&&h&&(f=g.set(a,d,c))!==b?f:(a.setAttribute(c,d+""),d)}return g&&"get"in g&&h&&(f=g.get(a,c))!==null?f:(f=a.getAttribute(c),f===null?b:f)},removeAttr:function(a,b){var c,d,e,f,g=0;if(b&&a.nodeType===1){d=b.split(s);for(;g<d.length;g++)e=d[g],e&&(c=p.propFix[e]||e,f=T.test(e),f||p.attr(a,e,""),a.removeAttribute(U?e:c),f&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(Q.test(a.nodeName)&&a.parentNode)p.error("type property can't be changed");else if(!p.support.radioValue&&b==="radio"&&p.nodeName(a,"input")){var c=a.value;return a.setAttribute("type",b),c&&(a.value=c),b}}},value:{get:function(a,b){return L&&p.nodeName(a,"button")?L.get(a,b):b in a?a.value:null},set:function(a,b,c){if(L&&p.nodeName(a,"button"))return L.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,f,g,h=a.nodeType;if(!a||h===3||h===8||h===2)return;return g=h!==1||!p.isXMLDoc(a),g&&(c=p.propFix[c]||c,f=p.propHooks[c]),d!==b?f&&"set"in f&&(e=f.set(a,d,c))!==b?e:a[c]=d:f&&"get"in f&&(e=f.get(a,c))!==null?e:a[c]},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):R.test(a.nodeName)||S.test(a.nodeName)&&a.href?0:b}}}}),M={get:function(a,c){var d,e=p.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;return b===!1?p.removeAttr(a,c):(d=p.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase())),c}},U||(N={name:!0,id:!0,coords:!0},L=p.valHooks.button={get:function(a,c){var d;return d=a.getAttributeNode(c),d&&(N[c]?d.value!=="":d.specified)?d.value:b},set:function(a,b,c){var d=a.getAttributeNode(c);return d||(d=e.createAttribute(c),a.setAttributeNode(d)),d.value=b+""}},p.each(["width","height"],function(a,b){p.attrHooks[b]=p.extend(p.attrHooks[b],{set:function(a,c){if(c==="")return a.setAttribute(b,"auto"),c}})}),p.attrHooks.contenteditable={get:L.get,set:function(a,b,c){b===""&&(b="false"),L.set(a,b,c)}}),p.support.hrefNormalized||p.each(["href","src","width","height"],function(a,c){p.attrHooks[c]=p.extend(p.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),p.support.style||(p.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=b+""}}),p.support.optSelected||(p.propHooks.selected=p.extend(p.propHooks.selected,{get:function(a){var b=a.parentNode;return b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex),null}})),p.support.enctype||(p.propFix.enctype="encoding"),p.support.checkOn||p.each(["radio","checkbox"],function(){p.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),p.each(["radio","checkbox"],function(){p.valHooks[this]=p.extend(p.valHooks[this],{set:function(a,b){if(p.isArray(b))return a.checked=p.inArray(p(a).val(),b)>=0}})});var V=/^(?:textarea|input|select)$/i,W=/^([^\.]*|)(?:\.(.+)|)$/,X=/(?:^|\s)hover(\.\S+|)\b/,Y=/^key/,Z=/^(?:mouse|contextmenu)|click/,$=/^(?:focusinfocus|focusoutblur)$/,_=function(a){return p.event.special.hover?a:a.replace(X,"mouseenter$1 mouseleave$1")};p.event={add:function(a,c,d,e,f){var g,h,i,j,k,l,m,n,o,q,r;if(a.nodeType===3||a.nodeType===8||!c||!d||!(g=p._data(a)))return;d.handler&&(o=d,d=o.handler,f=o.selector),d.guid||(d.guid=p.guid++),i=g.events,i||(g.events=i={}),h=g.handle,h||(g.handle=h=function(a){return typeof p!="undefined"&&(!a||p.event.triggered!==a.type)?p.event.dispatch.apply(h.elem,arguments):b},h.elem=a),c=p.trim(_(c)).split(" ");for(j=0;j<c.length;j++){k=W.exec(c[j])||[],l=k[1],m=(k[2]||"").split(".").sort(),r=p.event.special[l]||{},l=(f?r.delegateType:r.bindType)||l,r=p.event.special[l]||{},n=p.extend({type:l,origType:k[1],data:e,handler:d,guid:d.guid,selector:f,needsContext:f&&p.expr.match.needsContext.test(f),namespace:m.join(".")},o),q=i[l];if(!q){q=i[l]=[],q.delegateCount=0;if(!r.setup||r.setup.call(a,e,m,h)===!1)a.addEventListener?a.addEventListener(l,h,!1):a.attachEvent&&a.attachEvent("on"+l,h)}r.add&&(r.add.call(a,n),n.handler.guid||(n.handler.guid=d.guid)),f?q.splice(q.delegateCount++,0,n):q.push(n),p.event.global[l]=!0}a=null},global:{},remove:function(a,b,c,d,e){var f,g,h,i,j,k,l,m,n,o,q,r=p.hasData(a)&&p._data(a);if(!r||!(m=r.events))return;b=p.trim(_(b||"")).split(" ");for(f=0;f<b.length;f++){g=W.exec(b[f])||[],h=i=g[1],j=g[2];if(!h){for(h in m)p.event.remove(a,h+b[f],c,d,!0);continue}n=p.event.special[h]||{},h=(d?n.delegateType:n.bindType)||h,o=m[h]||[],k=o.length,j=j?new RegExp("(^|\\.)"+j.split(".").sort().join("\\.(?:.*\\.|)")+"(\\.|$)"):null;for(l=0;l<o.length;l++)q=o[l],(e||i===q.origType)&&(!c||c.guid===q.guid)&&(!j||j.test(q.namespace))&&(!d||d===q.selector||d==="**"&&q.selector)&&(o.splice(l--,1),q.selector&&o.delegateCount--,n.remove&&n.remove.call(a,q));o.length===0&&k!==o.length&&((!n.teardown||n.teardown.call(a,j,r.handle)===!1)&&p.removeEvent(a,h,r.handle),delete m[h])}p.isEmptyObject(m)&&(delete r.handle,p.removeData(a,"events",!0))},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,f,g){if(!f||f.nodeType!==3&&f.nodeType!==8){var h,i,j,k,l,m,n,o,q,r,s=c.type||c,t=[];if($.test(s+p.event.triggered))return;s.indexOf("!")>=0&&(s=s.slice(0,-1),i=!0),s.indexOf(".")>=0&&(t=s.split("."),s=t.shift(),t.sort());if((!f||p.event.customEvent[s])&&!p.event.global[s])return;c=typeof c=="object"?c[p.expando]?c:new p.Event(s,c):new p.Event(s),c.type=s,c.isTrigger=!0,c.exclusive=i,c.namespace=t.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+t.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,m=s.indexOf(":")<0?"on"+s:"";if(!f){h=p.cache;for(j in h)h[j].events&&h[j].events[s]&&p.event.trigger(c,d,h[j].handle.elem,!0);return}c.result=b,c.target||(c.target=f),d=d!=null?p.makeArray(d):[],d.unshift(c),n=p.event.special[s]||{};if(n.trigger&&n.trigger.apply(f,d)===!1)return;q=[[f,n.bindType||s]];if(!g&&!n.noBubble&&!p.isWindow(f)){r=n.delegateType||s,k=$.test(r+s)?f:f.parentNode;for(l=f;k;k=k.parentNode)q.push([k,r]),l=k;l===(f.ownerDocument||e)&&q.push([l.defaultView||l.parentWindow||a,r])}for(j=0;j<q.length&&!c.isPropagationStopped();j++)k=q[j][0],c.type=q[j][1],o=(p._data(k,"events")||{})[c.type]&&p._data(k,"handle"),o&&o.apply(k,d),o=m&&k[m],o&&p.acceptData(k)&&o.apply&&o.apply(k,d)===!1&&c.preventDefault();return c.type=s,!g&&!c.isDefaultPrevented()&&(!n._default||n._default.apply(f.ownerDocument,d)===!1)&&(s!=="click"||!p.nodeName(f,"a"))&&p.acceptData(f)&&m&&f[s]&&(s!=="focus"&&s!=="blur"||c.target.offsetWidth!==0)&&!p.isWindow(f)&&(l=f[m],l&&(f[m]=null),p.event.triggered=s,f[s](),p.event.triggered=b,l&&(f[m]=l)),c.result}return},dispatch:function(c){c=p.event.fix(c||a.event);var d,e,f,g,h,i,j,l,m,n,o=(p._data(this,"events")||{})[c.type]||[],q=o.delegateCount,r=k.call(arguments),s=!c.exclusive&&!c.namespace,t=p.event.special[c.type]||{},u=[];r[0]=c,c.delegateTarget=this;if(t.preDispatch&&t.preDispatch.call(this,c)===!1)return;if(q&&(!c.button||c.type!=="click"))for(f=c.target;f!=this;f=f.parentNode||this)if(f.disabled!==!0||c.type!=="click"){h={},j=[];for(d=0;d<q;d++)l=o[d],m=l.selector,h[m]===b&&(h[m]=l.needsContext?p(m,this).index(f)>=0:p.find(m,this,null,[f]).length),h[m]&&j.push(l);j.length&&u.push({elem:f,matches:j})}o.length>q&&u.push({elem:this,matches:o.slice(q)});for(d=0;d<u.length&&!c.isPropagationStopped();d++){i=u[d],c.currentTarget=i.elem;for(e=0;e<i.matches.length&&!c.isImmediatePropagationStopped();e++){l=i.matches[e];if(s||!c.namespace&&!l.namespace||c.namespace_re&&c.namespace_re.test(l.namespace))c.data=l.data,c.handleObj=l,g=((p.event.special[l.origType]||{}).handle||l.handler).apply(i.elem,r),g!==b&&(c.result=g,g===!1&&(c.preventDefault(),c.stopPropagation()))}}return t.postDispatch&&t.postDispatch.call(this,c),c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){return a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode),a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,c){var d,f,g,h=c.button,i=c.fromElement;return a.pageX==null&&c.clientX!=null&&(d=a.target.ownerDocument||e,f=d.documentElement,g=d.body,a.pageX=c.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=c.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?c.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0),a}},fix:function(a){if(a[p.expando])return a;var b,c,d=a,f=p.event.fixHooks[a.type]||{},g=f.props?this.props.concat(f.props):this.props;a=p.Event(d);for(b=g.length;b;)c=g[--b],a[c]=d[c];return a.target||(a.target=d.srcElement||e),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey=!!a.metaKey,f.filter?f.filter(a,d):a},special:{load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){p.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=p.extend(new p.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?p.event.trigger(e,null,b):p.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},p.event.handle=p.event.dispatch,p.removeEvent=e.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){var d="on"+b;a.detachEvent&&(typeof a[d]=="undefined"&&(a[d]=null),a.detachEvent(d,c))},p.Event=function(a,b){if(this instanceof p.Event)a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?bb:ba):this.type=a,b&&p.extend(this,b),this.timeStamp=a&&a.timeStamp||p.now(),this[p.expando]=!0;else return new p.Event(a,b)},p.Event.prototype={preventDefault:function(){this.isDefaultPrevented=bb;var a=this.originalEvent;if(!a)return;a.preventDefault?a.preventDefault():a.returnValue=!1},stopPropagation:function(){this.isPropagationStopped=bb;var a=this.originalEvent;if(!a)return;a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=bb,this.stopPropagation()},isDefaultPrevented:ba,isPropagationStopped:ba,isImmediatePropagationStopped:ba},p.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){p.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c,d=this,e=a.relatedTarget,f=a.handleObj,g=f.selector;if(!e||e!==d&&!p.contains(d,e))a.type=f.origType,c=f.handler.apply(this,arguments),a.type=b;return c}}}),p.support.submitBubbles||(p.event.special.submit={setup:function(){if(p.nodeName(this,"form"))return!1;p.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=p.nodeName(c,"input")||p.nodeName(c,"button")?c.form:b;d&&!p._data(d,"_submit_attached")&&(p.event.add(d,"submit._submit",function(a){a._submit_bubble=!0}),p._data(d,"_submit_attached",!0))})},postDispatch:function(a){a._submit_bubble&&(delete a._submit_bubble,this.parentNode&&!a.isTrigger&&p.event.simulate("submit",this.parentNode,a,!0))},teardown:function(){if(p.nodeName(this,"form"))return!1;p.event.remove(this,"._submit")}}),p.support.changeBubbles||(p.event.special.change={setup:function(){if(V.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")p.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),p.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1),p.event.simulate("change",this,a,!0)});return!1}p.event.add(this,"beforeactivate._change",function(a){var b=a.target;V.test(b.nodeName)&&!p._data(b,"_change_attached")&&(p.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&p.event.simulate("change",this.parentNode,a,!0)}),p._data(b,"_change_attached",!0))})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){return p.event.remove(this,"._change"),!V.test(this.nodeName)}}),p.support.focusinBubbles||p.each({focus:"focusin",blur:"focusout"},function(a,b){var c=0,d=function(a){p.event.simulate(b,a.target,p.event.fix(a),!0)};p.event.special[b]={setup:function(){c++===0&&e.addEventListener(a,d,!0)},teardown:function(){--c===0&&e.removeEventListener(a,d,!0)}}}),p.fn.extend({on:function(a,c,d,e,f){var g,h;if(typeof a=="object"){typeof c!="string"&&(d=d||c,c=b);for(h in a)this.on(h,c,d,a[h],f);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=ba;else if(!e)return this;return f===1&&(g=e,e=function(a){return p().off(a),g.apply(this,arguments)},e.guid=g.guid||(g.guid=p.guid++)),this.each(function(){p.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on(a,b,c,d,1)},off:function(a,c,d){var e,f;if(a&&a.preventDefault&&a.handleObj)return e=a.handleObj,p(a.delegateTarget).off(e.namespace?e.origType+"."+e.namespace:e.origType,e.selector,e.handler),this;if(typeof a=="object"){for(f in a)this.off(f,c,a[f]);return this}if(c===!1||typeof c=="function")d=c,c=b;return d===!1&&(d=ba),this.each(function(){p.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){return p(this.context).on(a,this.selector,b,c),this},die:function(a,b){return p(this.context).off(a,this.selector||"**",b),this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length===1?this.off(a,"**"):this.off(b,a||"**",c)},trigger:function(a,b){return this.each(function(){p.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return p.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||p.guid++,d=0,e=function(c){var e=(p._data(this,"lastToggle"+a.guid)||0)%d;return p._data(this,"lastToggle"+a.guid,e+1),c.preventDefault(),b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),p.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){p.fn[b]=function(a,c){return c==null&&(c=a,a=null),arguments.length>0?this.on(b,null,a,c):this.trigger(b)},Y.test(b)&&(p.event.fixHooks[b]=p.event.keyHooks),Z.test(b)&&(p.event.fixHooks[b]=p.event.mouseHooks)}),function(a,b){function bc(a,b,c,d){c=c||[],b=b||r;var e,f,i,j,k=b.nodeType;if(!a||typeof a!="string")return c;if(k!==1&&k!==9)return[];i=g(b);if(!i&&!d)if(e=P.exec(a))if(j=e[1]){if(k===9){f=b.getElementById(j);if(!f||!f.parentNode)return c;if(f.id===j)return c.push(f),c}else if(b.ownerDocument&&(f=b.ownerDocument.getElementById(j))&&h(b,f)&&f.id===j)return c.push(f),c}else{if(e[2])return w.apply(c,x.call(b.getElementsByTagName(a),0)),c;if((j=e[3])&&_&&b.getElementsByClassName)return w.apply(c,x.call(b.getElementsByClassName(j),0)),c}return bp(a.replace(L,"$1"),b,c,d,i)}function bd(a){return function(b){var c=b.nodeName.toLowerCase();return c==="input"&&b.type===a}}function be(a){return function(b){var c=b.nodeName.toLowerCase();return(c==="input"||c==="button")&&b.type===a}}function bf(a){return z(function(b){return b=+b,z(function(c,d){var e,f=a([],c.length,b),g=f.length;while(g--)c[e=f[g]]&&(c[e]=!(d[e]=c[e]))})})}function bg(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}function bh(a,b){var c,d,f,g,h,i,j,k=C[o][a];if(k)return b?0:k.slice(0);h=a,i=[],j=e.preFilter;while(h){if(!c||(d=M.exec(h)))d&&(h=h.slice(d[0].length)),i.push(f=[]);c=!1;if(d=N.exec(h))f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=d[0].replace(L," ");for(g in e.filter)(d=W[g].exec(h))&&(!j[g]||(d=j[g](d,r,!0)))&&(f.push(c=new q(d.shift())),h=h.slice(c.length),c.type=g,c.matches=d);if(!c)break}return b?h.length:h?bc.error(a):C(a,i).slice(0)}function bi(a,b,d){var e=b.dir,f=d&&b.dir==="parentNode",g=u++;return b.first?function(b,c,d){while(b=b[e])if(f||b.nodeType===1)return a(b,c,d)}:function(b,d,h){if(!h){var i,j=t+" "+g+" ",k=j+c;while(b=b[e])if(f||b.nodeType===1){if((i=b[o])===k)return b.sizset;if(typeof i=="string"&&i.indexOf(j)===0){if(b.sizset)return b}else{b[o]=k;if(a(b,d,h))return b.sizset=!0,b;b.sizset=!1}}}else while(b=b[e])if(f||b.nodeType===1)if(a(b,d,h))return b}}function bj(a){return a.length>1?function(b,c,d){var e=a.length;while(e--)if(!a[e](b,c,d))return!1;return!0}:a[0]}function bk(a,b,c,d,e){var f,g=[],h=0,i=a.length,j=b!=null;for(;h<i;h++)if(f=a[h])if(!c||c(f,d,e))g.push(f),j&&b.push(h);return g}function bl(a,b,c,d,e,f){return d&&!d[o]&&(d=bl(d)),e&&!e[o]&&(e=bl(e,f)),z(function(f,g,h,i){if(f&&e)return;var j,k,l,m=[],n=[],o=g.length,p=f||bo(b||"*",h.nodeType?[h]:h,[],f),q=a&&(f||!b)?bk(p,m,a,h,i):p,r=c?e||(f?a:o||d)?[]:g:q;c&&c(q,r,h,i);if(d){l=bk(r,n),d(l,[],h,i),j=l.length;while(j--)if(k=l[j])r[n[j]]=!(q[n[j]]=k)}if(f){j=a&&r.length;while(j--)if(k=r[j])f[m[j]]=!(g[m[j]]=k)}else r=bk(r===g?r.splice(o,r.length):r),e?e(null,g,r,i):w.apply(g,r)})}function bm(a){var b,c,d,f=a.length,g=e.relative[a[0].type],h=g||e.relative[" "],i=g?1:0,j=bi(function(a){return a===b},h,!0),k=bi(function(a){return y.call(b,a)>-1},h,!0),m=[function(a,c,d){return!g&&(d||c!==l)||((b=c).nodeType?j(a,c,d):k(a,c,d))}];for(;i<f;i++)if(c=e.relative[a[i].type])m=[bi(bj(m),c)];else{c=e.filter[a[i].type].apply(null,a[i].matches);if(c[o]){d=++i;for(;d<f;d++)if(e.relative[a[d].type])break;return bl(i>1&&bj(m),i>1&&a.slice(0,i-1).join("").replace(L,"$1"),c,i<d&&bm(a.slice(i,d)),d<f&&bm(a=a.slice(d)),d<f&&a.join(""))}m.push(c)}return bj(m)}function bn(a,b){var d=b.length>0,f=a.length>0,g=function(h,i,j,k,m){var n,o,p,q=[],s=0,u="0",x=h&&[],y=m!=null,z=l,A=h||f&&e.find.TAG("*",m&&i.parentNode||i),B=t+=z==null?1:Math.E;y&&(l=i!==r&&i,c=g.el);for(;(n=A[u])!=null;u++){if(f&&n){for(o=0;p=a[o];o++)if(p(n,i,j)){k.push(n);break}y&&(t=B,c=++g.el)}d&&((n=!p&&n)&&s--,h&&x.push(n))}s+=u;if(d&&u!==s){for(o=0;p=b[o];o++)p(x,q,i,j);if(h){if(s>0)while(u--)!x[u]&&!q[u]&&(q[u]=v.call(k));q=bk(q)}w.apply(k,q),y&&!h&&q.length>0&&s+b.length>1&&bc.uniqueSort(k)}return y&&(t=B,l=z),x};return g.el=0,d?z(g):g}function bo(a,b,c,d){var e=0,f=b.length;for(;e<f;e++)bc(a,b[e],c,d);return c}function bp(a,b,c,d,f){var g,h,j,k,l,m=bh(a),n=m.length;if(!d&&m.length===1){h=m[0]=m[0].slice(0);if(h.length>2&&(j=h[0]).type==="ID"&&b.nodeType===9&&!f&&e.relative[h[1].type]){b=e.find.ID(j.matches[0].replace(V,""),b,f)[0];if(!b)return c;a=a.slice(h.shift().length)}for(g=W.POS.test(a)?-1:h.length-1;g>=0;g--){j=h[g];if(e.relative[k=j.type])break;if(l=e.find[k])if(d=l(j.matches[0].replace(V,""),R.test(h[0].type)&&b.parentNode||b,f)){h.splice(g,1),a=d.length&&h.join("");if(!a)return w.apply(c,x.call(d,0)),c;break}}}return i(a,m)(d,b,f,c,R.test(a)),c}function bq(){}var c,d,e,f,g,h,i,j,k,l,m=!0,n="undefined",o=("sizcache"+Math.random()).replace(".",""),q=String,r=a.document,s=r.documentElement,t=0,u=0,v=[].pop,w=[].push,x=[].slice,y=[].indexOf||function(a){var b=0,c=this.length;for(;b<c;b++)if(this[b]===a)return b;return-1},z=function(a,b){return a[o]=b==null||b,a},A=function(){var a={},b=[];return z(function(c,d){return b.push(c)>e.cacheLength&&delete a[b.shift()],a[c]=d},a)},B=A(),C=A(),D=A(),E="[\\x20\\t\\r\\n\\f]",F="(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",G=F.replace("w","w#"),H="([*^$|!~]?=)",I="\\["+E+"*("+F+")"+E+"*(?:"+H+E+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+G+")|)|)"+E+"*\\]",J=":("+F+")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:"+I+")|[^:]|\\\\.)*|.*))\\)|)",K=":(even|odd|eq|gt|lt|nth|first|last)(?:\\("+E+"*((?:-\\d)?\\d*)"+E+"*\\)|)(?=[^-]|$)",L=new RegExp("^"+E+"+|((?:^|[^\\\\])(?:\\\\.)*)"+E+"+$","g"),M=new RegExp("^"+E+"*,"+E+"*"),N=new RegExp("^"+E+"*([\\x20\\t\\r\\n\\f>+~])"+E+"*"),O=new RegExp(J),P=/^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,Q=/^:not/,R=/[\x20\t\r\n\f]*[+~]/,S=/:not\($/,T=/h\d/i,U=/input|select|textarea|button/i,V=/\\(?!\\)/g,W={ID:new RegExp("^#("+F+")"),CLASS:new RegExp("^\\.("+F+")"),NAME:new RegExp("^\\[name=['\"]?("+F+")['\"]?\\]"),TAG:new RegExp("^("+F.replace("w","w*")+")"),ATTR:new RegExp("^"+I),PSEUDO:new RegExp("^"+J),POS:new RegExp(K,"i"),CHILD:new RegExp("^:(only|nth|first|last)-child(?:\\("+E+"*(even|odd|(([+-]|)(\\d*)n|)"+E+"*(?:([+-]|)"+E+"*(\\d+)|))"+E+"*\\)|)","i"),needsContext:new RegExp("^"+E+"*[>+~]|"+K,"i")},X=function(a){var b=r.createElement("div");try{return a(b)}catch(c){return!1}finally{b=null}},Y=X(function(a){return a.appendChild(r.createComment("")),!a.getElementsByTagName("*").length}),Z=X(function(a){return a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!==n&&a.firstChild.getAttribute("href")==="#"}),$=X(function(a){a.innerHTML="<select></select>";var b=typeof a.lastChild.getAttribute("multiple");return b!=="boolean"&&b!=="string"}),_=X(function(a){return a.innerHTML="<div class='hidden e'></div><div class='hidden'></div>",!a.getElementsByClassName||!a.getElementsByClassName("e").length?!1:(a.lastChild.className="e",a.getElementsByClassName("e").length===2)}),ba=X(function(a){a.id=o+0,a.innerHTML="<a name='"+o+"'></a><div name='"+o+"'></div>",s.insertBefore(a,s.firstChild);var b=r.getElementsByName&&r.getElementsByName(o).length===2+r.getElementsByName(o+0).length;return d=!r.getElementById(o),s.removeChild(a),b});try{x.call(s.childNodes,0)[0].nodeType}catch(bb){x=function(a){var b,c=[];for(;b=this[a];a++)c.push(b);return c}}bc.matches=function(a,b){return bc(a,null,null,b)},bc.matchesSelector=function(a,b){return bc(b,null,null,[a]).length>0},f=bc.getText=function(a){var b,c="",d=0,e=a.nodeType;if(e){if(e===1||e===9||e===11){if(typeof a.textContent=="string")return a.textContent;for(a=a.firstChild;a;a=a.nextSibling)c+=f(a)}else if(e===3||e===4)return a.nodeValue}else for(;b=a[d];d++)c+=f(b);return c},g=bc.isXML=function(a){var b=a&&(a.ownerDocument||a).documentElement;return b?b.nodeName!=="HTML":!1},h=bc.contains=s.contains?function(a,b){var c=a.nodeType===9?a.documentElement:a,d=b&&b.parentNode;return a===d||!!(d&&d.nodeType===1&&c.contains&&c.contains(d))}:s.compareDocumentPosition?function(a,b){return b&&!!(a.compareDocumentPosition(b)&16)}:function(a,b){while(b=b.parentNode)if(b===a)return!0;return!1},bc.attr=function(a,b){var c,d=g(a);return d||(b=b.toLowerCase()),(c=e.attrHandle[b])?c(a):d||$?a.getAttribute(b):(c=a.getAttributeNode(b),c?typeof a[b]=="boolean"?a[b]?b:null:c.specified?c.value:null:null)},e=bc.selectors={cacheLength:50,createPseudo:z,match:W,attrHandle:Z?{}:{href:function(a){return a.getAttribute("href",2)},type:function(a){return a.getAttribute("type")}},find:{ID:d?function(a,b,c){if(typeof b.getElementById!==n&&!c){var d=b.getElementById(a);return d&&d.parentNode?[d]:[]}}:function(a,c,d){if(typeof c.getElementById!==n&&!d){var e=c.getElementById(a);return e?e.id===a||typeof e.getAttributeNode!==n&&e.getAttributeNode("id").value===a?[e]:b:[]}},TAG:Y?function(a,b){if(typeof b.getElementsByTagName!==n)return b.getElementsByTagName(a)}:function(a,b){var c=b.getElementsByTagName(a);if(a==="*"){var d,e=[],f=0;for(;d=c[f];f++)d.nodeType===1&&e.push(d);return e}return c},NAME:ba&&function(a,b){if(typeof b.getElementsByName!==n)return b.getElementsByName(name)},CLASS:_&&function(a,b,c){if(typeof b.getElementsByClassName!==n&&!c)return b.getElementsByClassName(a)}},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(a){return a[1]=a[1].replace(V,""),a[3]=(a[4]||a[5]||"").replace(V,""),a[2]==="~="&&(a[3]=" "+a[3]+" "),a.slice(0,4)},CHILD:function(a){return a[1]=a[1].toLowerCase(),a[1]==="nth"?(a[2]||bc.error(a[0]),a[3]=+(a[3]?a[4]+(a[5]||1):2*(a[2]==="even"||a[2]==="odd")),a[4]=+(a[6]+a[7]||a[2]==="odd")):a[2]&&bc.error(a[0]),a},PSEUDO:function(a){var b,c;if(W.CHILD.test(a[0]))return null;if(a[3])a[2]=a[3];else if(b=a[4])O.test(b)&&(c=bh(b,!0))&&(c=b.indexOf(")",b.length-c)-b.length)&&(b=b.slice(0,c),a[0]=a[0].slice(0,c)),a[2]=b;return a.slice(0,3)}},filter:{ID:d?function(a){return a=a.replace(V,""),function(b){return b.getAttribute("id")===a}}:function(a){return a=a.replace(V,""),function(b){var c=typeof b.getAttributeNode!==n&&b.getAttributeNode("id");return c&&c.value===a}},TAG:function(a){return a==="*"?function(){return!0}:(a=a.replace(V,"").toLowerCase(),function(b){return b.nodeName&&b.nodeName.toLowerCase()===a})},CLASS:function(a){var b=B[o][a];return b||(b=B(a,new RegExp("(^|"+E+")"+a+"("+E+"|$)"))),function(a){return b.test(a.className||typeof a.getAttribute!==n&&a.getAttribute("class")||"")}},ATTR:function(a,b,c){return function(d,e){var f=bc.attr(d,a);return f==null?b==="!=":b?(f+="",b==="="?f===c:b==="!="?f!==c:b==="^="?c&&f.indexOf(c)===0:b==="*="?c&&f.indexOf(c)>-1:b==="$="?c&&f.substr(f.length-c.length)===c:b==="~="?(" "+f+" ").indexOf(c)>-1:b==="|="?f===c||f.substr(0,c.length+1)===c+"-":!1):!0}},CHILD:function(a,b,c,d){return a==="nth"?function(a){var b,e,f=a.parentNode;if(c===1&&d===0)return!0;if(f){e=0;for(b=f.firstChild;b;b=b.nextSibling)if(b.nodeType===1){e++;if(a===b)break}}return e-=d,e===c||e%c===0&&e/c>=0}:function(b){var c=b;switch(a){case"only":case"first":while(c=c.previousSibling)if(c.nodeType===1)return!1;if(a==="first")return!0;c=b;case"last":while(c=c.nextSibling)if(c.nodeType===1)return!1;return!0}}},PSEUDO:function(a,b){var c,d=e.pseudos[a]||e.setFilters[a.toLowerCase()]||bc.error("unsupported pseudo: "+a);return d[o]?d(b):d.length>1?(c=[a,a,"",b],e.setFilters.hasOwnProperty(a.toLowerCase())?z(function(a,c){var e,f=d(a,b),g=f.length;while(g--)e=y.call(a,f[g]),a[e]=!(c[e]=f[g])}):function(a){return d(a,0,c)}):d}},pseudos:{not:z(function(a){var b=[],c=[],d=i(a.replace(L,"$1"));return d[o]?z(function(a,b,c,e){var f,g=d(a,null,e,[]),h=a.length;while(h--)if(f=g[h])a[h]=!(b[h]=f)}):function(a,e,f){return b[0]=a,d(b,null,f,c),!c.pop()}}),has:z(function(a){return function(b){return bc(a,b).length>0}}),contains:z(function(a){return function(b){return(b.textContent||b.innerText||f(b)).indexOf(a)>-1}}),enabled:function(a){return a.disabled===!1},disabled:function(a){return a.disabled===!0},checked:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&!!a.checked||b==="option"&&!!a.selected},selected:function(a){return a.parentNode&&a.parentNode.selectedIndex,a.selected===!0},parent:function(a){return!e.pseudos.empty(a)},empty:function(a){var b;a=a.firstChild;while(a){if(a.nodeName>"@"||(b=a.nodeType)===3||b===4)return!1;a=a.nextSibling}return!0},header:function(a){return T.test(a.nodeName)},text:function(a){var b,c;return a.nodeName.toLowerCase()==="input"&&(b=a.type)==="text"&&((c=a.getAttribute("type"))==null||c.toLowerCase()===b)},radio:bd("radio"),checkbox:bd("checkbox"),file:bd("file"),password:bd("password"),image:bd("image"),submit:be("submit"),reset:be("reset"),button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&a.type==="button"||b==="button"},input:function(a){return U.test(a.nodeName)},focus:function(a){var b=a.ownerDocument;return a===b.activeElement&&(!b.hasFocus||b.hasFocus())&&(!!a.type||!!a.href)},active:function(a){return a===a.ownerDocument.activeElement},first:bf(function(a,b,c){return[0]}),last:bf(function(a,b,c){return[b-1]}),eq:bf(function(a,b,c){return[c<0?c+b:c]}),even:bf(function(a,b,c){for(var d=0;d<b;d+=2)a.push(d);return a}),odd:bf(function(a,b,c){for(var d=1;d<b;d+=2)a.push(d);return a}),lt:bf(function(a,b,c){for(var d=c<0?c+b:c;--d>=0;)a.push(d);return a}),gt:bf(function(a,b,c){for(var d=c<0?c+b:c;++d<b;)a.push(d);return a})}},j=s.compareDocumentPosition?function(a,b){return a===b?(k=!0,0):(!a.compareDocumentPosition||!b.compareDocumentPosition?a.compareDocumentPosition:a.compareDocumentPosition(b)&4)?-1:1}:function(a,b){if(a===b)return k=!0,0;if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,h=b.parentNode,i=g;if(g===h)return bg(a,b);if(!g)return-1;if(!h)return 1;while(i)e.unshift(i),i=i.parentNode;i=h;while(i)f.unshift(i),i=i.parentNode;c=e.length,d=f.length;for(var j=0;j<c&&j<d;j++)if(e[j]!==f[j])return bg(e[j],f[j]);return j===c?bg(a,f[j],-1):bg(e[j],b,1)},[0,0].sort(j),m=!k,bc.uniqueSort=function(a){var b,c=1;k=m,a.sort(j);if(k)for(;b=a[c];c++)b===a[c-1]&&a.splice(c--,1);return a},bc.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)},i=bc.compile=function(a,b){var c,d=[],e=[],f=D[o][a];if(!f){b||(b=bh(a)),c=b.length;while(c--)f=bm(b[c]),f[o]?d.push(f):e.push(f);f=D(a,bn(e,d))}return f},r.querySelectorAll&&function(){var a,b=bp,c=/'|\\/g,d=/\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,e=[":focus"],f=[":active",":focus"],h=s.matchesSelector||s.mozMatchesSelector||s.webkitMatchesSelector||s.oMatchesSelector||s.msMatchesSelector;X(function(a){a.innerHTML="<select><option selected=''></option></select>",a.querySelectorAll("[selected]").length||e.push("\\["+E+"*(?:checked|disabled|ismap|multiple|readonly|selected|value)"),a.querySelectorAll(":checked").length||e.push(":checked")}),X(function(a){a.innerHTML="<p test=''></p>",a.querySelectorAll("[test^='']").length&&e.push("[*^$]="+E+"*(?:\"\"|'')"),a.innerHTML="<input type='hidden'/>",a.querySelectorAll(":enabled").length||e.push(":enabled",":disabled")}),e=new RegExp(e.join("|")),bp=function(a,d,f,g,h){if(!g&&!h&&(!e||!e.test(a))){var i,j,k=!0,l=o,m=d,n=d.nodeType===9&&a;if(d.nodeType===1&&d.nodeName.toLowerCase()!=="object"){i=bh(a),(k=d.getAttribute("id"))?l=k.replace(c,"\\$&"):d.setAttribute("id",l),l="[id='"+l+"'] ",j=i.length;while(j--)i[j]=l+i[j].join("");m=R.test(a)&&d.parentNode||d,n=i.join(",")}if(n)try{return w.apply(f,x.call(m.querySelectorAll(n),0)),f}catch(p){}finally{k||d.removeAttribute("id")}}return b(a,d,f,g,h)},h&&(X(function(b){a=h.call(b,"div");try{h.call(b,"[test!='']:sizzle"),f.push("!=",J)}catch(c){}}),f=new RegExp(f.join("|")),bc.matchesSelector=function(b,c){c=c.replace(d,"='$1']");if(!g(b)&&!f.test(c)&&(!e||!e.test(c)))try{var i=h.call(b,c);if(i||a||b.document&&b.document.nodeType!==11)return i}catch(j){}return bc(c,null,null,[b]).length>0})}(),e.pseudos.nth=e.pseudos.eq,e.filters=bq.prototype=e.pseudos,e.setFilters=new bq,bc.attr=p.attr,p.find=bc,p.expr=bc.selectors,p.expr[":"]=p.expr.pseudos,p.unique=bc.uniqueSort,p.text=bc.getText,p.isXMLDoc=bc.isXML,p.contains=bc.contains}(a);var bc=/Until$/,bd=/^(?:parents|prev(?:Until|All))/,be=/^.[^:#\[\.,]*$/,bf=p.expr.match.needsContext,bg={children:!0,contents:!0,next:!0,prev:!0};p.fn.extend({find:function(a){var b,c,d,e,f,g,h=this;if(typeof a!="string")return p(a).filter(function(){for(b=0,c=h.length;b<c;b++)if(p.contains(h[b],this))return!0});g=this.pushStack("","find",a);for(b=0,c=this.length;b<c;b++){d=g.length,p.find(a,this[b],g);if(b>0)for(e=d;e<g.length;e++)for(f=0;f<d;f++)if(g[f]===g[e]){g.splice(e--,1);break}}return g},has:function(a){var b,c=p(a,this),d=c.length;return this.filter(function(){for(b=0;b<d;b++)if(p.contains(this,c[b]))return!0})},not:function(a){return this.pushStack(bj(this,a,!1),"not",a)},filter:function(a){return this.pushStack(bj(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?bf.test(a)?p(a,this.context).index(this[0])>=0:p.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c,d=0,e=this.length,f=[],g=bf.test(a)||typeof a!="string"?p(a,b||this.context):0;for(;d<e;d++){c=this[d];while(c&&c.ownerDocument&&c!==b&&c.nodeType!==11){if(g?g.index(c)>-1:p.find.matchesSelector(c,a)){f.push(c);break}c=c.parentNode}}return f=f.length>1?p.unique(f):f,this.pushStack(f,"closest",a)},index:function(a){return a?typeof a=="string"?p.inArray(this[0],p(a)):p.inArray(a.jquery?a[0]:a,this):this[0]&&this[0].parentNode?this.prevAll().length:-1},add:function(a,b){var c=typeof a=="string"?p(a,b):p.makeArray(a&&a.nodeType?[a]:a),d=p.merge(this.get(),c);return this.pushStack(bh(c[0])||bh(d[0])?d:p.unique(d))},addBack:function(a){return this.add(a==null?this.prevObject:this.prevObject.filter(a))}}),p.fn.andSelf=p.fn.addBack,p.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return p.dir(a,"parentNode")},parentsUntil:function(a,b,c){return p.dir(a,"parentNode",c)},next:function(a){return bi(a,"nextSibling")},prev:function(a){return bi(a,"previousSibling")},nextAll:function(a){return p.dir(a,"nextSibling")},prevAll:function(a){return p.dir(a,"previousSibling")},nextUntil:function(a,b,c){return p.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return p.dir(a,"previousSibling",c)},siblings:function(a){return p.sibling((a.parentNode||{}).firstChild,a)},children:function(a){return p.sibling(a.firstChild)},contents:function(a){return p.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:p.merge([],a.childNodes)}},function(a,b){p.fn[a]=function(c,d){var e=p.map(this,b,c);return bc.test(a)||(d=c),d&&typeof d=="string"&&(e=p.filter(d,e)),e=this.length>1&&!bg[a]?p.unique(e):e,this.length>1&&bd.test(a)&&(e=e.reverse()),this.pushStack(e,a,k.call(arguments).join(","))}}),p.extend({filter:function(a,b,c){return c&&(a=":not("+a+")"),b.length===1?p.find.matchesSelector(b[0],a)?[b[0]]:[]:p.find.matches(a,b)},dir:function(a,c,d){var e=[],f=a[c];while(f&&f.nodeType!==9&&(d===b||f.nodeType!==1||!p(f).is(d)))f.nodeType===1&&e.push(f),f=f[c];return e},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var bl="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",bm=/ jQuery\d+="(?:null|\d+)"/g,bn=/^\s+/,bo=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bp=/<([\w:]+)/,bq=/<tbody/i,br=/<|&#?\w+;/,bs=/<(?:script|style|link)/i,bt=/<(?:script|object|embed|option|style)/i,bu=new RegExp("<(?:"+bl+")[\\s/>]","i"),bv=/^(?:checkbox|radio)$/,bw=/checked\s*(?:[^=]|=\s*.checked.)/i,bx=/\/(java|ecma)script/i,by=/^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,bz={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bA=bk(e),bB=bA.appendChild(e.createElement("div"));bz.optgroup=bz.option,bz.tbody=bz.tfoot=bz.colgroup=bz.caption=bz.thead,bz.th=bz.td,p.support.htmlSerialize||(bz._default=[1,"X<div>","</div>"]),p.fn.extend({text:function(a){return p.access(this,function(a){return a===b?p.text(this):this.empty().append((this[0]&&this[0].ownerDocument||e).createTextNode(a))},null,a,arguments.length)},wrapAll:function(a){if(p.isFunction(a))return this.each(function(b){p(this).wrapAll(a.call(this,b))});if(this[0]){var b=p(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){return p.isFunction(a)?this.each(function(b){p(this).wrapInner(a.call(this,b))}):this.each(function(){var b=p(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=p.isFunction(a);return this.each(function(c){p(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){p.nodeName(this,"body")||p(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){(this.nodeType===1||this.nodeType===11)&&this.insertBefore(a,this.firstChild)})},before:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(a,this),"before",this.selector)}},after:function(){if(!bh(this[0]))return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=p.clean(arguments);return this.pushStack(p.merge(this,a),"after",this.selector)}},remove:function(a,b){var c,d=0;for(;(c=this[d])!=null;d++)if(!a||p.filter(a,[c]).length)!b&&c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),p.cleanData([c])),c.parentNode&&c.parentNode.removeChild(c);return this},empty:function(){var a,b=0;for(;(a=this[b])!=null;b++){a.nodeType===1&&p.cleanData(a.getElementsByTagName("*"));while(a.firstChild)a.removeChild(a.firstChild)}return this},clone:function(a,b){return a=a==null?!1:a,b=b==null?a:b,this.map(function(){return p.clone(this,a,b)})},html:function(a){return p.access(this,function(a){var c=this[0]||{},d=0,e=this.length;if(a===b)return c.nodeType===1?c.innerHTML.replace(bm,""):b;if(typeof a=="string"&&!bs.test(a)&&(p.support.htmlSerialize||!bu.test(a))&&(p.support.leadingWhitespace||!bn.test(a))&&!bz[(bp.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(bo,"<$1></$2>");try{for(;d<e;d++)c=this[d]||{},c.nodeType===1&&(p.cleanData(c.getElementsByTagName("*")),c.innerHTML=a);c=0}catch(f){}}c&&this.empty().append(a)},null,a,arguments.length)},replaceWith:function(a){return bh(this[0])?this.length?this.pushStack(p(p.isFunction(a)?a():a),"replaceWith",a):this:p.isFunction(a)?this.each(function(b){var c=p(this),d=c.html();c.replaceWith(a.call(this,b,d))}):(typeof a!="string"&&(a=p(a).detach()),this.each(function(){var b=this.nextSibling,c=this.parentNode;p(this).remove(),b?p(b).before(a):p(c).append(a)}))},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){a=[].concat.apply([],a);var e,f,g,h,i=0,j=a[0],k=[],l=this.length;if(!p.support.checkClone&&l>1&&typeof j=="string"&&bw.test(j))return this.each(function(){p(this).domManip(a,c,d)});if(p.isFunction(j))return this.each(function(e){var f=p(this);a[0]=j.call(this,e,c?f.html():b),f.domManip(a,c,d)});if(this[0]){e=p.buildFragment(a,this,k),g=e.fragment,f=g.firstChild,g.childNodes.length===1&&(g=f);if(f){c=c&&p.nodeName(f,"tr");for(h=e.cacheable||l-1;i<l;i++)d.call(c&&p.nodeName(this[i],"table")?bC(this[i],"tbody"):this[i],i===h?g:p.clone(g,!0,!0))}g=f=null,k.length&&p.each(k,function(a,b){b.src?p.ajax?p.ajax({url:b.src,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0}):p.error("no ajax"):p.globalEval((b.text||b.textContent||b.innerHTML||"").replace(by,"")),b.parentNode&&b.parentNode.removeChild(b)})}return this}}),p.buildFragment=function(a,c,d){var f,g,h,i=a[0];return c=c||e,c=!c.nodeType&&c[0]||c,c=c.ownerDocument||c,a.length===1&&typeof i=="string"&&i.length<512&&c===e&&i.charAt(0)==="<"&&!bt.test(i)&&(p.support.checkClone||!bw.test(i))&&(p.support.html5Clone||!bu.test(i))&&(g=!0,f=p.fragments[i],h=f!==b),f||(f=c.createDocumentFragment(),p.clean(a,c,f,d),g&&(p.fragments[i]=h&&f)),{fragment:f,cacheable:g}},p.fragments={},p.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){p.fn[a]=function(c){var d,e=0,f=[],g=p(c),h=g.length,i=this.length===1&&this[0].parentNode;if((i==null||i&&i.nodeType===11&&i.childNodes.length===1)&&h===1)return g[b](this[0]),this;for(;e<h;e++)d=(e>0?this.clone(!0):this).get(),p(g[e])[b](d),f=f.concat(d);return this.pushStack(f,a,g.selector)}}),p.extend({clone:function(a,b,c){var d,e,f,g;p.support.html5Clone||p.isXMLDoc(a)||!bu.test("<"+a.nodeName+">")?g=a.cloneNode(!0):(bB.innerHTML=a.outerHTML,bB.removeChild(g=bB.firstChild));if((!p.support.noCloneEvent||!p.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!p.isXMLDoc(a)){bE(a,g),d=bF(a),e=bF(g);for(f=0;d[f];++f)e[f]&&bE(d[f],e[f])}if(b){bD(a,g);if(c){d=bF(a),e=bF(g);for(f=0;d[f];++f)bD(d[f],e[f])}}return d=e=null,g},clean:function(a,b,c,d){var f,g,h,i,j,k,l,m,n,o,q,r,s=b===e&&bA,t=[];if(!b||typeof b.createDocumentFragment=="undefined")b=e;for(f=0;(h=a[f])!=null;f++){typeof h=="number"&&(h+="");if(!h)continue;if(typeof h=="string")if(!br.test(h))h=b.createTextNode(h);else{s=s||bk(b),l=b.createElement("div"),s.appendChild(l),h=h.replace(bo,"<$1></$2>"),i=(bp.exec(h)||["",""])[1].toLowerCase(),j=bz[i]||bz._default,k=j[0],l.innerHTML=j[1]+h+j[2];while(k--)l=l.lastChild;if(!p.support.tbody){m=bq.test(h),n=i==="table"&&!m?l.firstChild&&l.firstChild.childNodes:j[1]==="<table>"&&!m?l.childNodes:[];for(g=n.length-1;g>=0;--g)p.nodeName(n[g],"tbody")&&!n[g].childNodes.length&&n[g].parentNode.removeChild(n[g])}!p.support.leadingWhitespace&&bn.test(h)&&l.insertBefore(b.createTextNode(bn.exec(h)[0]),l.firstChild),h=l.childNodes,l.parentNode.removeChild(l)}h.nodeType?t.push(h):p.merge(t,h)}l&&(h=l=s=null);if(!p.support.appendChecked)for(f=0;(h=t[f])!=null;f++)p.nodeName(h,"input")?bG(h):typeof h.getElementsByTagName!="undefined"&&p.grep(h.getElementsByTagName("input"),bG);if(c){q=function(a){if(!a.type||bx.test(a.type))return d?d.push(a.parentNode?a.parentNode.removeChild(a):a):c.appendChild(a)};for(f=0;(h=t[f])!=null;f++)if(!p.nodeName(h,"script")||!q(h))c.appendChild(h),typeof h.getElementsByTagName!="undefined"&&(r=p.grep(p.merge([],h.getElementsByTagName("script")),q),t.splice.apply(t,[f+1,0].concat(r)),f+=r.length)}return t},cleanData:function(a,b){var c,d,e,f,g=0,h=p.expando,i=p.cache,j=p.support.deleteExpando,k=p.event.special;for(;(e=a[g])!=null;g++)if(b||p.acceptData(e)){d=e[h],c=d&&i[d];if(c){if(c.events)for(f in c.events)k[f]?p.event.remove(e,f):p.removeEvent(e,f,c.handle);i[d]&&(delete i[d],j?delete e[h]:e.removeAttribute?e.removeAttribute(h):e[h]=null,p.deletedIds.push(d))}}}}),function(){var a,b;p.uaMatch=function(a){a=a.toLowerCase();var b=/(chrome)[ \/]([\w.]+)/.exec(a)||/(webkit)[ \/]([\w.]+)/.exec(a)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(a)||/(msie) ([\w.]+)/.exec(a)||a.indexOf("compatible")<0&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},a=p.uaMatch(g.userAgent),b={},a.browser&&(b[a.browser]=!0,b.version=a.version),b.chrome?b.webkit=!0:b.webkit&&(b.safari=!0),p.browser=b,p.sub=function(){function a(b,c){return new a.fn.init(b,c)}p.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function c(c,d){return d&&d instanceof p&&!(d instanceof a)&&(d=a(d)),p.fn.init.call(this,c,d,b)},a.fn.init.prototype=a.fn;var b=a(e);return a}}();var bH,bI,bJ,bK=/alpha\([^)]*\)/i,bL=/opacity=([^)]*)/,bM=/^(top|right|bottom|left)$/,bN=/^(none|table(?!-c[ea]).+)/,bO=/^margin/,bP=new RegExp("^("+q+")(.*)$","i"),bQ=new RegExp("^("+q+")(?!px)[a-z%]+$","i"),bR=new RegExp("^([-+])=("+q+")","i"),bS={},bT={position:"absolute",visibility:"hidden",display:"block"},bU={letterSpacing:0,fontWeight:400},bV=["Top","Right","Bottom","Left"],bW=["Webkit","O","Moz","ms"],bX=p.fn.toggle;p.fn.extend({css:function(a,c){return p.access(this,function(a,c,d){return d!==b?p.style(a,c,d):p.css(a,c)},a,c,arguments.length>1)},show:function(){return b$(this,!0)},hide:function(){return b$(this)},toggle:function(a,b){var c=typeof a=="boolean";return p.isFunction(a)&&p.isFunction(b)?bX.apply(this,arguments):this.each(function(){(c?a:bZ(this))?p(this).show():p(this).hide()})}}),p.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bH(a,"opacity");return c===""?"1":c}}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":p.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!a||a.nodeType===3||a.nodeType===8||!a.style)return;var f,g,h,i=p.camelCase(c),j=a.style;c=p.cssProps[i]||(p.cssProps[i]=bY(j,i)),h=p.cssHooks[c]||p.cssHooks[i];if(d===b)return h&&"get"in h&&(f=h.get(a,!1,e))!==b?f:j[c];g=typeof d,g==="string"&&(f=bR.exec(d))&&(d=(f[1]+1)*f[2]+parseFloat(p.css(a,c)),g="number");if(d==null||g==="number"&&isNaN(d))return;g==="number"&&!p.cssNumber[i]&&(d+="px");if(!h||!("set"in h)||(d=h.set(a,d,e))!==b)try{j[c]=d}catch(k){}},css:function(a,c,d,e){var f,g,h,i=p.camelCase(c);return c=p.cssProps[i]||(p.cssProps[i]=bY(a.style,i)),h=p.cssHooks[c]||p.cssHooks[i],h&&"get"in h&&(f=h.get(a,!0,e)),f===b&&(f=bH(a,c)),f==="normal"&&c in bU&&(f=bU[c]),d||e!==b?(g=parseFloat(f),d||p.isNumeric(g)?g||0:f):f},swap:function(a,b,c){var d,e,f={};for(e in b)f[e]=a.style[e],a.style[e]=b[e];d=c.call(a);for(e in b)a.style[e]=f[e];return d}}),a.getComputedStyle?bH=function(b,c){var d,e,f,g,h=a.getComputedStyle(b,null),i=b.style;return h&&(d=h[c],d===""&&!p.contains(b.ownerDocument,b)&&(d=p.style(b,c)),bQ.test(d)&&bO.test(c)&&(e=i.width,f=i.minWidth,g=i.maxWidth,i.minWidth=i.maxWidth=i.width=d,d=h.width,i.width=e,i.minWidth=f,i.maxWidth=g)),d}:e.documentElement.currentStyle&&(bH=function(a,b){var c,d,e=a.currentStyle&&a.currentStyle[b],f=a.style;return e==null&&f&&f[b]&&(e=f[b]),bQ.test(e)&&!bM.test(b)&&(c=f.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),f.left=b==="fontSize"?"1em":e,e=f.pixelLeft+"px",f.left=c,d&&(a.runtimeStyle.left=d)),e===""?"auto":e}),p.each(["height","width"],function(a,b){p.cssHooks[b]={get:function(a,c,d){if(c)return a.offsetWidth===0&&bN.test(bH(a,"display"))?p.swap(a,bT,function(){return cb(a,b,d)}):cb(a,b,d)},set:function(a,c,d){return b_(a,c,d?ca(a,b,d,p.support.boxSizing&&p.css(a,"boxSizing")==="border-box"):0)}}}),p.support.opacity||(p.cssHooks.opacity={get:function(a,b){return bL.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?.01*parseFloat(RegExp.$1)+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=p.isNumeric(b)?"alpha(opacity="+b*100+")":"",f=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&p.trim(f.replace(bK,""))===""&&c.removeAttribute){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bK.test(f)?f.replace(bK,e):f+" "+e}}),p(function(){p.support.reliableMarginRight||(p.cssHooks.marginRight={get:function(a,b){return p.swap(a,{display:"inline-block"},function(){if(b)return bH(a,"marginRight")})}}),!p.support.pixelPosition&&p.fn.position&&p.each(["top","left"],function(a,b){p.cssHooks[b]={get:function(a,c){if(c){var d=bH(a,b);return bQ.test(d)?p(a).position()[b]+"px":d}}}})}),p.expr&&p.expr.filters&&(p.expr.filters.hidden=function(a){return a.offsetWidth===0&&a.offsetHeight===0||!p.support.reliableHiddenOffsets&&(a.style&&a.style.display||bH(a,"display"))==="none"},p.expr.filters.visible=function(a){return!p.expr.filters.hidden(a)}),p.each({margin:"",padding:"",border:"Width"},function(a,b){p.cssHooks[a+b]={expand:function(c){var d,e=typeof c=="string"?c.split(" "):[c],f={};for(d=0;d<4;d++)f[a+bV[d]+b]=e[d]||e[d-2]||e[0];return f}},bO.test(a)||(p.cssHooks[a+b].set=b_)});var cd=/%20/g,ce=/\[\]$/,cf=/\r?\n/g,cg=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,ch=/^(?:select|textarea)/i;p.fn.extend({serialize:function(){return p.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?p.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||ch.test(this.nodeName)||cg.test(this.type))}).map(function(a,b){var c=p(this).val();return c==null?null:p.isArray(c)?p.map(c,function(a,c){return{name:b.name,value:a.replace(cf,"\r\n")}}):{name:b.name,value:c.replace(cf,"\r\n")}}).get()}}),p.param=function(a,c){var d,e=[],f=function(a,b){b=p.isFunction(b)?b():b==null?"":b,e[e.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=p.ajaxSettings&&p.ajaxSettings.traditional);if(p.isArray(a)||a.jquery&&!p.isPlainObject(a))p.each(a,function(){f(this.name,this.value)});else for(d in a)ci(d,a[d],c,f);return e.join("&").replace(cd,"+")};var cj,ck,cl=/#.*$/,cm=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,cn=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,co=/^(?:GET|HEAD)$/,cp=/^\/\//,cq=/\?/,cr=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,cs=/([?&])_=[^&]*/,ct=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,cu=p.fn.load,cv={},cw={},cx=["*/"]+["*"];try{ck=f.href}catch(cy){ck=e.createElement("a"),ck.href="",ck=ck.href}cj=ct.exec(ck.toLowerCase())||[],p.fn.load=function(a,c,d){if(typeof a!="string"&&cu)return cu.apply(this,arguments);if(!this.length)return this;var e,f,g,h=this,i=a.indexOf(" ");return i>=0&&(e=a.slice(i,a.length),a=a.slice(0,i)),p.isFunction(c)?(d=c,c=b):c&&typeof c=="object"&&(f="POST"),p.ajax({url:a,type:f,dataType:"html",data:c,complete:function(a,b){d&&h.each(d,g||[a.responseText,b,a])}}).done(function(a){g=arguments,h.html(e?p("<div>").append(a.replace(cr,"")).find(e):a)}),this},p.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){p.fn[b]=function(a){return this.on(b,a)}}),p.each(["get","post"],function(a,c){p[c]=function(a,d,e,f){return p.isFunction(d)&&(f=f||e,e=d,d=b),p.ajax({type:c,url:a,data:d,success:e,dataType:f})}}),p.extend({getScript:function(a,c){return p.get(a,b,c,"script")},getJSON:function(a,b,c){return p.get(a,b,c,"json")},ajaxSetup:function(a,b){return b?cB(a,p.ajaxSettings):(b=a,a=p.ajaxSettings),cB(a,b),a},ajaxSettings:{url:ck,isLocal:cn.test(cj[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":cx},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":p.parseJSON,"text xml":p.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:cz(cv),ajaxTransport:cz(cw),ajax:function(a,c){function y(a,c,f,i){var k,s,t,u,w,y=c;if(v===2)return;v=2,h&&clearTimeout(h),g=b,e=i||"",x.readyState=a>0?4:0,f&&(u=cC(l,x,f));if(a>=200&&a<300||a===304)l.ifModified&&(w=x.getResponseHeader("Last-Modified"),w&&(p.lastModified[d]=w),w=x.getResponseHeader("Etag"),w&&(p.etag[d]=w)),a===304?(y="notmodified",k=!0):(k=cD(l,u),y=k.state,s=k.data,t=k.error,k=!t);else{t=y;if(!y||a)y="error",a<0&&(a=0)}x.status=a,x.statusText=(c||y)+"",k?o.resolveWith(m,[s,y,x]):o.rejectWith(m,[x,y,t]),x.statusCode(r),r=b,j&&n.trigger("ajax"+(k?"Success":"Error"),[x,l,k?s:t]),q.fireWith(m,[x,y]),j&&(n.trigger("ajaxComplete",[x,l]),--p.active||p.event.trigger("ajaxStop"))}typeof a=="object"&&(c=a,a=b),c=c||{};var d,e,f,g,h,i,j,k,l=p.ajaxSetup({},c),m=l.context||l,n=m!==l&&(m.nodeType||m instanceof p)?p(m):p.event,o=p.Deferred(),q=p.Callbacks("once memory"),r=l.statusCode||{},t={},u={},v=0,w="canceled",x={readyState:0,setRequestHeader:function(a,b){if(!v){var c=a.toLowerCase();a=u[c]=u[c]||a,t[a]=b}return this},getAllResponseHeaders:function(){return v===2?e:null},getResponseHeader:function(a){var c;if(v===2){if(!f){f={};while(c=cm.exec(e))f[c[1].toLowerCase()]=c[2]}c=f[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){return v||(l.mimeType=a),this},abort:function(a){return a=a||w,g&&g.abort(a),y(0,a),this}};o.promise(x),x.success=x.done,x.error=x.fail,x.complete=q.add,x.statusCode=function(a){if(a){var b;if(v<2)for(b in a)r[b]=[r[b],a[b]];else b=a[x.status],x.always(b)}return this},l.url=((a||l.url)+"").replace(cl,"").replace(cp,cj[1]+"//"),l.dataTypes=p.trim(l.dataType||"*").toLowerCase().split(s),l.crossDomain==null&&(i=ct.exec(l.url.toLowerCase())||!1,l.crossDomain=i&&i.join(":")+(i[3]?"":i[1]==="http:"?80:443)!==cj.join(":")+(cj[3]?"":cj[1]==="http:"?80:443)),l.data&&l.processData&&typeof l.data!="string"&&(l.data=p.param(l.data,l.traditional)),cA(cv,l,c,x);if(v===2)return x;j=l.global,l.type=l.type.toUpperCase(),l.hasContent=!co.test(l.type),j&&p.active++===0&&p.event.trigger("ajaxStart");if(!l.hasContent){l.data&&(l.url+=(cq.test(l.url)?"&":"?")+l.data,delete l.data),d=l.url;if(l.cache===!1){var z=p.now(),A=l.url.replace(cs,"$1_="+z);l.url=A+(A===l.url?(cq.test(l.url)?"&":"?")+"_="+z:"")}}(l.data&&l.hasContent&&l.contentType!==!1||c.contentType)&&x.setRequestHeader("Content-Type",l.contentType),l.ifModified&&(d=d||l.url,p.lastModified[d]&&x.setRequestHeader("If-Modified-Since",p.lastModified[d]),p.etag[d]&&x.setRequestHeader("If-None-Match",p.etag[d])),x.setRequestHeader("Accept",l.dataTypes[0]&&l.accepts[l.dataTypes[0]]?l.accepts[l.dataTypes[0]]+(l.dataTypes[0]!=="*"?", "+cx+"; q=0.01":""):l.accepts["*"]);for(k in l.headers)x.setRequestHeader(k,l.headers[k]);if(!l.beforeSend||l.beforeSend.call(m,x,l)!==!1&&v!==2){w="abort";for(k in{success:1,error:1,complete:1})x[k](l[k]);g=cA(cw,l,c,x);if(!g)y(-1,"No Transport");else{x.readyState=1,j&&n.trigger("ajaxSend",[x,l]),l.async&&l.timeout>0&&(h=setTimeout(function(){x.abort("timeout")},l.timeout));try{v=1,g.send(t,y)}catch(B){if(v<2)y(-1,B);else throw B}}return x}return x.abort()},active:0,lastModified:{},etag:{}});var cE=[],cF=/\?/,cG=/(=)\?(?=&|$)|\?\?/,cH=p.now();p.ajaxSetup({jsonp:"callback",jsonpCallback:function(){var a=cE.pop()||p.expando+"_"+cH++;return this[a]=!0,a}}),p.ajaxPrefilter("json jsonp",function(c,d,e){var f,g,h,i=c.data,j=c.url,k=c.jsonp!==!1,l=k&&cG.test(j),m=k&&!l&&typeof i=="string"&&!(c.contentType||"").indexOf("application/x-www-form-urlencoded")&&cG.test(i);if(c.dataTypes[0]==="jsonp"||l||m)return f=c.jsonpCallback=p.isFunction(c.jsonpCallback)?c.jsonpCallback():c.jsonpCallback,g=a[f],l?c.url=j.replace(cG,"$1"+f):m?c.data=i.replace(cG,"$1"+f):k&&(c.url+=(cF.test(j)?"&":"?")+c.jsonp+"="+f),c.converters["script json"]=function(){return h||p.error(f+" was not called"),h[0]},c.dataTypes[0]="json",a[f]=function(){h=arguments},e.always(function(){a[f]=g,c[f]&&(c.jsonpCallback=d.jsonpCallback,cE.push(f)),h&&p.isFunction(g)&&g(h[0]),h=g=b}),"script"}),p.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){return p.globalEval(a),a}}}),p.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),p.ajaxTransport("script",function(a){if(a.crossDomain){var c,d=e.head||e.getElementsByTagName("head")[0]||e.documentElement;return{send:function(f,g){c=e.createElement("script"),c.async="async",a.scriptCharset&&(c.charset=a.scriptCharset),c.src=a.url,c.onload=c.onreadystatechange=function(a,e){if(e||!c.readyState||/loaded|complete/.test(c.readyState))c.onload=c.onreadystatechange=null,d&&c.parentNode&&d.removeChild(c),c=b,e||g(200,"success")},d.insertBefore(c,d.firstChild)},abort:function(){c&&c.onload(0,1)}}}});var cI,cJ=a.ActiveXObject?function(){for(var a in cI)cI[a](0,1)}:!1,cK=0;p.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&cL()||cM()}:cL,function(a){p.extend(p.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(p.ajaxSettings.xhr()),p.support.ajax&&p.ajaxTransport(function(c){if(!c.crossDomain||p.support.cors){var d;return{send:function(e,f){var g,h,i=c.xhr();c.username?i.open(c.type,c.url,c.async,c.username,c.password):i.open(c.type,c.url,c.async);if(c.xhrFields)for(h in c.xhrFields)i[h]=c.xhrFields[h];c.mimeType&&i.overrideMimeType&&i.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(h in e)i.setRequestHeader(h,e[h])}catch(j){}i.send(c.hasContent&&c.data||null),d=function(a,e){var h,j,k,l,m;try{if(d&&(e||i.readyState===4)){d=b,g&&(i.onreadystatechange=p.noop,cJ&&delete cI[g]);if(e)i.readyState!==4&&i.abort();else{h=i.status,k=i.getAllResponseHeaders(),l={},m=i.responseXML,m&&m.documentElement&&(l.xml=m);try{l.text=i.responseText}catch(a){}try{j=i.statusText}catch(n){j=""}!h&&c.isLocal&&!c.crossDomain?h=l.text?200:404:h===1223&&(h=204)}}}catch(o){e||f(-1,o)}l&&f(h,j,l,k)},c.async?i.readyState===4?setTimeout(d,0):(g=++cK,cJ&&(cI||(cI={},p(a).unload(cJ)),cI[g]=d),i.onreadystatechange=d):d()},abort:function(){d&&d(0,1)}}}});var cN,cO,cP=/^(?:toggle|show|hide)$/,cQ=new RegExp("^(?:([-+])=|)("+q+")([a-z%]*)$","i"),cR=/queueHooks$/,cS=[cY],cT={"*":[function(a,b){var c,d,e=this.createTween(a,b),f=cQ.exec(b),g=e.cur(),h=+g||0,i=1,j=20;if(f){c=+f[2],d=f[3]||(p.cssNumber[a]?"":"px");if(d!=="px"&&h){h=p.css(e.elem,a,!0)||c||1;do i=i||".5",h=h/i,p.style(e.elem,a,h+d);while(i!==(i=e.cur()/g)&&i!==1&&--j)}e.unit=d,e.start=h,e.end=f[1]?h+(f[1]+1)*c:c}return e}]};p.Animation=p.extend(cW,{tweener:function(a,b){p.isFunction(a)?(b=a,a=["*"]):a=a.split(" ");var c,d=0,e=a.length;for(;d<e;d++)c=a[d],cT[c]=cT[c]||[],cT[c].unshift(b)},prefilter:function(a,b){b?cS.unshift(a):cS.push(a)}}),p.Tween=cZ,cZ.prototype={constructor:cZ,init:function(a,b,c,d,e,f){this.elem=a,this.prop=c,this.easing=e||"swing",this.options=b,this.start=this.now=this.cur(),this.end=d,this.unit=f||(p.cssNumber[c]?"":"px")},cur:function(){var a=cZ.propHooks[this.prop];return a&&a.get?a.get(this):cZ.propHooks._default.get(this)},run:function(a){var b,c=cZ.propHooks[this.prop];return this.options.duration?this.pos=b=p.easing[this.easing](a,this.options.duration*a,0,1,this.options.duration):this.pos=b=a,this.now=(this.end-this.start)*b+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),c&&c.set?c.set(this):cZ.propHooks._default.set(this),this}},cZ.prototype.init.prototype=cZ.prototype,cZ.propHooks={_default:{get:function(a){var b;return a.elem[a.prop]==null||!!a.elem.style&&a.elem.style[a.prop]!=null?(b=p.css(a.elem,a.prop,!1,""),!b||b==="auto"?0:b):a.elem[a.prop]},set:function(a){p.fx.step[a.prop]?p.fx.step[a.prop](a):a.elem.style&&(a.elem.style[p.cssProps[a.prop]]!=null||p.cssHooks[a.prop])?p.style(a.elem,a.prop,a.now+a.unit):a.elem[a.prop]=a.now}}},cZ.propHooks.scrollTop=cZ.propHooks.scrollLeft={set:function(a){a.elem.nodeType&&a.elem.parentNode&&(a.elem[a.prop]=a.now)}},p.each(["toggle","show","hide"],function(a,b){var c=p.fn[b];p.fn[b]=function(d,e,f){return d==null||typeof d=="boolean"||!a&&p.isFunction(d)&&p.isFunction(e)?c.apply(this,arguments):this.animate(c$(b,!0),d,e,f)}}),p.fn.extend({fadeTo:function(a,b,c,d){return this.filter(bZ).css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){var e=p.isEmptyObject(a),f=p.speed(b,c,d),g=function(){var b=cW(this,p.extend({},a),f);e&&b.stop(!0)};return e||f.queue===!1?this.each(g):this.queue(f.queue,g)},stop:function(a,c,d){var e=function(a){var b=a.stop;delete a.stop,b(d)};return typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]),this.each(function(){var b=!0,c=a!=null&&a+"queueHooks",f=p.timers,g=p._data(this);if(c)g[c]&&g[c].stop&&e(g[c]);else for(c in g)g[c]&&g[c].stop&&cR.test(c)&&e(g[c]);for(c=f.length;c--;)f[c].elem===this&&(a==null||f[c].queue===a)&&(f[c].anim.stop(d),b=!1,f.splice(c,1));(b||!d)&&p.dequeue(this,a)})}}),p.each({slideDown:c$("show"),slideUp:c$("hide"),slideToggle:c$("toggle"),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){p.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),p.speed=function(a,b,c){var d=a&&typeof a=="object"?p.extend({},a):{complete:c||!c&&b||p.isFunction(a)&&a,duration:a,easing:c&&b||b&&!p.isFunction(b)&&b};d.duration=p.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in p.fx.speeds?p.fx.speeds[d.duration]:p.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";return d.old=d.complete,d.complete=function(){p.isFunction(d.old)&&d.old.call(this),d.queue&&p.dequeue(this,d.queue)},d},p.easing={linear:function(a){return a},swing:function(a){return.5-Math.cos(a*Math.PI)/2}},p.timers=[],p.fx=cZ.prototype.init,p.fx.tick=function(){var a,b=p.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||p.fx.stop()},p.fx.timer=function(a){a()&&p.timers.push(a)&&!cO&&(cO=setInterval(p.fx.tick,p.fx.interval))},p.fx.interval=13,p.fx.stop=function(){clearInterval(cO),cO=null},p.fx.speeds={slow:600,fast:200,_default:400},p.fx.step={},p.expr&&p.expr.filters&&(p.expr.filters.animated=function(a){return p.grep(p.timers,function(b){return a===b.elem}).length});var c_=/^(?:body|html)$/i;p.fn.offset=function(a){if(arguments.length)return a===b?this:this.each(function(b){p.offset.setOffset(this,a,b)});var c,d,e,f,g,h,i,j={top:0,left:0},k=this[0],l=k&&k.ownerDocument;if(!l)return;return(d=l.body)===k?p.offset.bodyOffset(k):(c=l.documentElement,p.contains(c,k)?(typeof k.getBoundingClientRect!="undefined"&&(j=k.getBoundingClientRect()),e=da(l),f=c.clientTop||d.clientTop||0,g=c.clientLeft||d.clientLeft||0,h=e.pageYOffset||c.scrollTop,i=e.pageXOffset||c.scrollLeft,{top:j.top+h-f,left:j.left+i-g}):j)},p.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;return p.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(p.css(a,"marginTop"))||0,c+=parseFloat(p.css(a,"marginLeft"))||0),{top:b,left:c}},setOffset:function(a,b,c){var d=p.css(a,"position");d==="static"&&(a.style.position="relative");var e=p(a),f=e.offset(),g=p.css(a,"top"),h=p.css(a,"left"),i=(d==="absolute"||d==="fixed")&&p.inArray("auto",[g,h])>-1,j={},k={},l,m;i?(k=e.position(),l=k.top,m=k.left):(l=parseFloat(g)||0,m=parseFloat(h)||0),p.isFunction(b)&&(b=b.call(a,c,f)),b.top!=null&&(j.top=b.top-f.top+l),b.left!=null&&(j.left=b.left-f.left+m),"using"in b?b.using.call(a,j):e.css(j)}},p.fn.extend({position:function(){if(!this[0])return;var a=this[0],b=this.offsetParent(),c=this.offset(),d=c_.test(b[0].nodeName)?{top:0,left:0}:b.offset();return c.top-=parseFloat(p.css(a,"marginTop"))||0,c.left-=parseFloat(p.css(a,"marginLeft"))||0,d.top+=parseFloat(p.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(p.css(b[0],"borderLeftWidth"))||0,{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||e.body;while(a&&!c_.test(a.nodeName)&&p.css(a,"position")==="static")a=a.offsetParent;return a||e.body})}}),p.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(a,c){var d=/Y/.test(c);p.fn[a]=function(e){return p.access(this,function(a,e,f){var g=da(a);if(f===b)return g?c in g?g[c]:g.document.documentElement[e]:a[e];g?g.scrollTo(d?p(g).scrollLeft():f,d?f:p(g).scrollTop()):a[e]=f},a,e,arguments.length,null)}}),p.each({Height:"height",Width:"width"},function(a,c){p.each({padding:"inner"+a,content:c,"":"outer"+a},function(d,e){p.fn[e]=function(e,f){var g=arguments.length&&(d||typeof e!="boolean"),h=d||(e===!0||f===!0?"margin":"border");return p.access(this,function(c,d,e){var f;return p.isWindow(c)?c.document.documentElement["client"+a]:c.nodeType===9?(f=c.documentElement,Math.max(c.body["scroll"+a],f["scroll"+a],c.body["offset"+a],f["offset"+a],f["client"+a])):e===b?p.css(c,d,e,h):p.style(c,d,e,h)},c,g?e:b,g,null)}})}),a.jQuery=a.$=p,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return p})})(window); \ No newline at end of file | ||
diff --git a/htdocs/webiopi.css b/htdocs/webiopi.css new file mode 100644 index 0000000..40b1163 --- /dev/null +++ b/htdocs/webiopi.css | |||
| @@ -0,0 +1,93 @@ | |||
| 1 | @CHARSET "UTF-8"; | ||
| 2 | |||
| 3 | button { | ||
| 4 | -webkit-appearance: none; | ||
| 5 | font-size: 14pt; | ||
| 6 | font-weight: bold; | ||
| 7 | color: white; | ||
| 8 | border: black 3px solid; | ||
| 9 | width: 40px; | ||
| 10 | height: 30px; | ||
| 11 | padding: 0 0 0 0; | ||
| 12 | } | ||
| 13 | |||
| 14 | input[type="range"] { | ||
| 15 | -webkit-appearance: slider-horizontal; | ||
| 16 | } | ||
| 17 | |||
| 18 | .Default { | ||
| 19 | background-color: Gray; | ||
| 20 | } | ||
| 21 | |||
| 22 | .Default:active { | ||
| 23 | background-color: Silver; | ||
| 24 | } | ||
| 25 | |||
| 26 | .DNC { | ||
| 27 | background-color: Gray; | ||
| 28 | } | ||
| 29 | |||
| 30 | .GND { | ||
| 31 | background-color: Black; | ||
| 32 | } | ||
| 33 | |||
| 34 | .V33 { | ||
| 35 | background-color: Orange; | ||
| 36 | } | ||
| 37 | |||
| 38 | .V50 { | ||
| 39 | background-color: Red; | ||
| 40 | } | ||
| 41 | |||
| 42 | .LOW { | ||
| 43 | background-color: Black; | ||
| 44 | } | ||
| 45 | |||
| 46 | .HIGH { | ||
| 47 | background-color: Orange; | ||
| 48 | } | ||
| 49 | |||
| 50 | .I2C { | ||
| 51 | background-color: LightBlue; | ||
| 52 | } | ||
| 53 | |||
| 54 | .SPI { | ||
| 55 | background-color: Purple; | ||
| 56 | } | ||
| 57 | |||
| 58 | .UART { | ||
| 59 | background-color: DarkBlue; | ||
| 60 | } | ||
| 61 | |||
| 62 | .ONEWIRE { | ||
| 63 | background-color: Blue; | ||
| 64 | } | ||
| 65 | |||
| 66 | .FunctionBasic { | ||
| 67 | width: 60px; | ||
| 68 | background-color: Gray; | ||
| 69 | } | ||
| 70 | |||
| 71 | .FunctionSpecial { | ||
| 72 | width: 60px; | ||
| 73 | visibility: hidden; | ||
| 74 | } | ||
| 75 | |||
| 76 | .Description { | ||
| 77 | font-size: 12pt; | ||
| 78 | font-weight: bold; | ||
| 79 | width: 90px; | ||
| 80 | } | ||
| 81 | |||
| 82 | #update { | ||
| 83 | position: absolute; | ||
| 84 | top: 16px; | ||
| 85 | right: 12px; | ||
| 86 | } | ||
| 87 | |||
| 88 | #update > a { | ||
| 89 | font-family: sans-serif; | ||
| 90 | font-size: 12pt; | ||
| 91 | font-weight: bold; | ||
| 92 | color: FireBrick; | ||
| 93 | } | ||
diff --git a/htdocs/webiopi.js b/htdocs/webiopi.js new file mode 100644 index 0000000..c7eae66 --- /dev/null +++ b/htdocs/webiopi.js | |||
| @@ -0,0 +1,1448 @@ | |||
| 1 | /* | ||
| 2 | Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 3 | |||
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | you may not use this file except in compliance with the License. | ||
| 6 | You may obtain a copy of the License at | ||
| 7 | |||
| 8 | http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | |||
| 10 | Unless required by applicable law or agreed to in writing, software | ||
| 11 | distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | See the License for the specific language governing permissions and | ||
| 14 | limitations under the License. | ||
| 15 | */ | ||
| 16 | |||
| 17 | var _gaq = _gaq || []; | ||
| 18 | var _webiopi; | ||
| 19 | |||
| 20 | function w() { | ||
| 21 | if (_webiopi == undefined) { | ||
| 22 | _webiopi = new WebIOPi(); | ||
| 23 | } | ||
| 24 | |||
| 25 | return _webiopi; | ||
| 26 | } | ||
| 27 | |||
| 28 | function webiopi() { | ||
| 29 | return w(); | ||
| 30 | } | ||
| 31 | |||
| 32 | function isMobileUserAgent(a) { | ||
| 33 | if (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows (ce|phone)|xda|xiino/i.test(a)||/1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0,4))) | ||
| 34 | return true | ||
| 35 | } | ||
| 36 | |||
| 37 | var _isMobile = undefined; | ||
| 38 | function isMobile() { | ||
| 39 | if (_isMobile == undefined) { | ||
| 40 | _isMobile = ((navigator.userAgent != undefined && isMobileUserAgent(navigator.userAgent)) | ||
| 41 | || (navigator.vendor != undefined && isMobileUserAgent(navigator.vendor)) | ||
| 42 | || (window.opera != undefined && isMobileUserAgent(window.opera))) | ||
| 43 | } | ||
| 44 | return _isMobile | ||
| 45 | } | ||
| 46 | |||
| 47 | function WebIOPi() { | ||
| 48 | this.readyCallback = null; | ||
| 49 | this.context = "/"; | ||
| 50 | this.GPIO = Array(54); | ||
| 51 | this.PINS = Array(27); | ||
| 52 | |||
| 53 | this.TYPE = { | ||
| 54 | DNC: {value: 0, style: "DNC", label: "--"}, | ||
| 55 | GND: {value: 1, style: "GND", label: "GROUND"}, | ||
| 56 | V33: {value: 2, style: "V33", label: "3.3V"}, | ||
| 57 | V50: {value: 3, style: "V50", label: "5.0V"}, | ||
| 58 | GPIO: {value: 4, style: "GPIO", label: "GPIO"} | ||
| 59 | }; | ||
| 60 | |||
| 61 | this.ALT = { | ||
| 62 | I2C: {name: "I2C", enabled: false, gpios: []}, | ||
| 63 | SPI: {name: "SPI", enabled: false, gpios: []}, | ||
| 64 | UART: {name: "UART", enabled: false, gpios: []}, | ||
| 65 | ONEWIRE: {name: "ONEWIRE", enabled: false, gpios: []} | ||
| 66 | }; | ||
| 67 | |||
| 68 | // init GPIOs | ||
| 69 | for (var i=0; i<this.GPIO.length; i++) { | ||
| 70 | var gpio = Object(); | ||
| 71 | gpio.value = 0; | ||
| 72 | gpio.func = "IN"; | ||
| 73 | gpio.mapped = false; | ||
| 74 | this.GPIO[i] = gpio; | ||
| 75 | } | ||
| 76 | |||
| 77 | // get context | ||
| 78 | var reg = new RegExp("http://" + window.location.host + "(.*)webiopi.js"); | ||
| 79 | var scripts = document.getElementsByTagName("script"); | ||
| 80 | for(var i = 0; i < scripts.length; i++) { | ||
| 81 | var res = reg.exec(scripts[i].src); | ||
| 82 | if (res && (res.length > 1)) { | ||
| 83 | script = scripts[i]; | ||
| 84 | this.context = res[1]; | ||
| 85 | |||
| 86 | } | ||
| 87 | } | ||
| 88 | |||
| 89 | var head = document.getElementsByTagName('head')[0]; | ||
| 90 | |||
| 91 | var jquery = document.createElement('script'); | ||
| 92 | jquery.type = 'text/javascript'; | ||
| 93 | jquery.src = '/jquery.js'; | ||
| 94 | if (!isMobile()) { | ||
| 95 | jquery.onload = function() { | ||
| 96 | w().init(); | ||
| 97 | }; | ||
| 98 | } | ||
| 99 | head.appendChild(jquery); | ||
| 100 | |||
| 101 | if (isMobile()) { | ||
| 102 | console.log("load jquery mobile"); | ||
| 103 | var mobile = document.createElement('script'); | ||
| 104 | mobile.type = 'text/javascript'; | ||
| 105 | mobile.src = '/jquery-mobile.js'; | ||
| 106 | mobile.onload = function() { | ||
| 107 | w().initMobile() | ||
| 108 | }; | ||
| 109 | head.appendChild(mobile); | ||
| 110 | } | ||
| 111 | |||
| 112 | // GA | ||
| 113 | _gaq.push(['_setAccount', 'UA-33979593-2']); | ||
| 114 | _gaq.push(['_trackPageview']); | ||
| 115 | |||
| 116 | var ga = document.createElement('script'); | ||
| 117 | ga.type = 'text/javascript'; | ||
| 118 | ga.async = false; | ||
| 119 | ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js'; | ||
| 120 | head.appendChild(ga); | ||
| 121 | |||
| 122 | var style = document.createElement('link'); | ||
| 123 | style.rel = "stylesheet"; | ||
| 124 | style.type = 'text/css'; | ||
| 125 | style.href = '/webiopi.css'; | ||
| 126 | head.appendChild(style); | ||
| 127 | |||
| 128 | if (isMobile()) { | ||
| 129 | var style = document.createElement('link'); | ||
| 130 | style.rel = "stylesheet"; | ||
| 131 | style.type = 'text/css'; | ||
| 132 | style.href = '/jquery-mobile.css'; | ||
| 133 | head.appendChild(style); | ||
| 134 | } | ||
| 135 | |||
| 136 | // init ALTs | ||
| 137 | this.addALT(this.ALT.I2C, 0, "SDA"); | ||
| 138 | this.addALT(this.ALT.I2C, 1, "SCL"); | ||
| 139 | this.addALT(this.ALT.I2C, 2, "SDA"); | ||
| 140 | this.addALT(this.ALT.I2C, 3, "SCL"); | ||
| 141 | |||
| 142 | this.addALT(this.ALT.SPI, 7, "CE1"); | ||
| 143 | this.addALT(this.ALT.SPI, 8, "CE0"); | ||
| 144 | this.addALT(this.ALT.SPI, 9, "MISO"); | ||
| 145 | this.addALT(this.ALT.SPI, 10, "MOSI"); | ||
| 146 | this.addALT(this.ALT.SPI, 11, "SCLK"); | ||
| 147 | |||
| 148 | this.addALT(this.ALT.UART, 14, "TX"); | ||
| 149 | this.addALT(this.ALT.UART, 15, "RX"); | ||
| 150 | |||
| 151 | this.addALT(this.ALT.ONEWIRE, 4, ""); | ||
| 152 | } | ||
| 153 | |||
| 154 | WebIOPi.prototype.init = function() { | ||
| 155 | $.getJSON(w().context + "map", function(data) { | ||
| 156 | var count = w().PINS.length; | ||
| 157 | for (i = 0; i<count-1; i++) { | ||
| 158 | var type = w().TYPE.GPIO; | ||
| 159 | var label = data[i]; | ||
| 160 | |||
| 161 | if (label == "DNC") { | ||
| 162 | type = w().TYPE.DNC; | ||
| 163 | } | ||
| 164 | else if (label == "GND") { | ||
| 165 | type = w().TYPE.GND; | ||
| 166 | } | ||
| 167 | else if (label == "V33") { | ||
| 168 | type = w().TYPE.V33; | ||
| 169 | } | ||
| 170 | else if (label == "V50") { | ||
| 171 | type = w().TYPE.V50; | ||
| 172 | } | ||
| 173 | |||
| 174 | if (type.value != w().TYPE.GPIO.value) { | ||
| 175 | label = type.label; | ||
| 176 | } | ||
| 177 | |||
| 178 | w().map(i+1, type, label); | ||
| 179 | } | ||
| 180 | if (w().readyCallback != null) { | ||
| 181 | w().readyCallback(); | ||
| 182 | } | ||
| 183 | |||
| 184 | w().checkVersion(); | ||
| 185 | }); | ||
| 186 | } | ||
| 187 | |||
| 188 | WebIOPi.prototype.initMobile = function() { | ||
| 189 | webiopi().init(); | ||
| 190 | } | ||
| 191 | |||
| 192 | WebIOPi.prototype.ready = function (cb) { | ||
| 193 | w().readyCallback = cb; | ||
| 194 | } | ||
| 195 | |||
| 196 | WebIOPi.prototype.map = function (pin, type, value) { | ||
| 197 | w().PINS[pin] = Object(); | ||
| 198 | w().PINS[pin].type = type | ||
| 199 | w().PINS[pin].value = value; | ||
| 200 | |||
| 201 | if (type.value == w().TYPE.GPIO.value) { | ||
| 202 | w().GPIO[value].mapped = true; | ||
| 203 | } | ||
| 204 | } | ||
| 205 | |||
| 206 | WebIOPi.prototype.addALT = function (alt, gpio, name) { | ||
| 207 | var o = Object(); | ||
| 208 | o.gpio = gpio; | ||
| 209 | o.name = name; | ||
| 210 | alt.gpios.push(o); | ||
| 211 | } | ||
| 212 | |||
| 213 | WebIOPi.prototype.updateValue = function (gpio, value) { | ||
| 214 | w().GPIO[gpio].value = value; | ||
| 215 | var style = (value == 1) ? "HIGH" : "LOW"; | ||
| 216 | $("#gpio"+gpio).attr("class", style); | ||
| 217 | } | ||
| 218 | |||
| 219 | WebIOPi.prototype.updateFunction = function (gpio, func) { | ||
| 220 | w().GPIO[gpio].func = func; | ||
| 221 | $("#function"+gpio).val(func); | ||
| 222 | $("#function"+gpio).text(func); | ||
| 223 | } | ||
| 224 | |||
| 225 | WebIOPi.prototype.updateSlider = function (gpio, slider, value) { | ||
| 226 | $("#"+slider+gpio).val(value); | ||
| 227 | } | ||
| 228 | |||
| 229 | WebIOPi.prototype.updateALT = function (alt, enable) { | ||
| 230 | for (var p in alt.gpios) { | ||
| 231 | gpio = alt.gpios[p].gpio; | ||
| 232 | $("#description"+gpio).empty(); | ||
| 233 | if (enable) { | ||
| 234 | $("#description"+gpio).append(alt.name + " " + alt.gpios[p].name); | ||
| 235 | $("#gpio"+gpio).attr("class", alt.name); | ||
| 236 | $("#function"+gpio).attr("class", "FunctionSpecial"); | ||
| 237 | } | ||
| 238 | else { | ||
| 239 | $("#description"+gpio).append("GPIO " + gpio); | ||
| 240 | $("#gpio"+gpio).attr("class", ""); | ||
| 241 | $("#function"+gpio).attr("class", "FunctionBasic"); | ||
| 242 | } | ||
| 243 | } | ||
| 244 | alt.enabled = enable; | ||
| 245 | } | ||
| 246 | |||
| 247 | WebIOPi.prototype.refreshGPIO = function (repeat) { | ||
| 248 | $.getJSON(w().context + "*", function(data) { | ||
| 249 | w().updateALT(w().ALT.I2C, data["I2C"]); | ||
| 250 | w().updateALT(w().ALT.SPI, data["SPI"]); | ||
| 251 | w().updateALT(w().ALT.UART, data["UART"]); | ||
| 252 | w().updateALT(w().ALT.ONEWIRE, data["ONEWIRE"]); | ||
| 253 | |||
| 254 | $.each(data["GPIO"], function(gpio, data) { | ||
| 255 | w().updateFunction(gpio, data["function"]); | ||
| 256 | if ( ((gpio != 4) && ((data["function"] == "IN") || (data["function"] == "OUT")) | ||
| 257 | || ((gpio == 4) && (w().ALT.ONEWIRE["enabled"] == false)))){ | ||
| 258 | w().updateValue(gpio, data["value"]); | ||
| 259 | } | ||
| 260 | else if (data["function"] == "PWM") { | ||
| 261 | w().updateSlider(gpio, "ratio", data["ratio"]); | ||
| 262 | w().updateSlider(gpio, "angle", data["angle"]); | ||
| 263 | } | ||
| 264 | |||
| 265 | }); | ||
| 266 | }); | ||
| 267 | if (repeat === true) { | ||
| 268 | setTimeout(function(){w().refreshGPIO(repeat)}, 1000); | ||
| 269 | } | ||
| 270 | } | ||
| 271 | |||
| 272 | |||
| 273 | WebIOPi.prototype.checkVersion = function () { | ||
| 274 | var version; | ||
| 275 | |||
| 276 | $.get(w().context + "version", function(data) { | ||
| 277 | _gaq.push(['_trackEvent', 'version', data]); | ||
| 278 | // version = data.split("/")[2]; | ||
| 279 | // | ||
| 280 | // $.get("http://webiopi.trouch.com/version.php", function(data) { | ||
| 281 | // var lines = data.split("\n"); | ||
| 282 | // var c = version.split("."); | ||
| 283 | // var n = lines[0].split("."); | ||
| 284 | // var updated = false; | ||
| 285 | // for (i=0; i<Math.min(c.length, n.length); i++) { | ||
| 286 | // if (n[i]>c[i]) { | ||
| 287 | // updated = true; | ||
| 288 | // } | ||
| 289 | // } | ||
| 290 | // if (updated || (n.length > c.length)) { | ||
| 291 | // var div = $('<div id="update"><a href="' + lines[1] + '">Update available</a></div>'); | ||
| 292 | // $("body").append(div); | ||
| 293 | // } | ||
| 294 | // }); | ||
| 295 | }); | ||
| 296 | } | ||
| 297 | |||
| 298 | WebIOPi.prototype.digitalRead = function (gpio, callback) { | ||
| 299 | if (callback != undefined) { | ||
| 300 | $.get(w().context + 'GPIO/' + gpio + "/value", function(data) { | ||
| 301 | w().updateValue(gpio, data); | ||
| 302 | callback(gpio, data); | ||
| 303 | }); | ||
| 304 | } | ||
| 305 | return w().GPIO[gpio].value; | ||
| 306 | } | ||
| 307 | |||
| 308 | WebIOPi.prototype.digitalWrite = function (gpio, value, callback) { | ||
| 309 | if (w().GPIO[gpio].func.toUpperCase()=="OUT") { | ||
| 310 | $.post(w().context + 'GPIO/' + gpio + "/value/" + value, function(data) { | ||
| 311 | w().updateValue(gpio, data); | ||
| 312 | if (callback != undefined) { | ||
| 313 | callback(gpio, data); | ||
| 314 | } | ||
| 315 | }); | ||
| 316 | } | ||
| 317 | else { | ||
| 318 | //console.log(w().GPIO[gpio].func); | ||
| 319 | } | ||
| 320 | } | ||
| 321 | |||
| 322 | WebIOPi.prototype.getFunction = function (gpio, callback) { | ||
| 323 | if (callback != undefined) { | ||
| 324 | $.get(w().context + 'GPIO/' + gpio + "/function", function(data) { | ||
| 325 | w().updateFunction(gpio, data); | ||
| 326 | callback(gpio, data); | ||
| 327 | }); | ||
| 328 | } | ||
| 329 | return w().GPIO[gpio].func; | ||
| 330 | } | ||
| 331 | WebIOPi.prototype.setFunction = function (gpio, func, callback) { | ||
| 332 | $.post(w().context + 'GPIO/' + gpio + "/function/" + func, function(data) { | ||
| 333 | w().updateFunction(gpio, data); | ||
| 334 | if (callback != undefined) { | ||
| 335 | callback(gpio, data); | ||
| 336 | } | ||
| 337 | }); | ||
| 338 | } | ||
| 339 | |||
| 340 | WebIOPi.prototype.toggleValue = function (gpio) { | ||
| 341 | var value = (w().GPIO[gpio].value == 1) ? 0 : 1; | ||
| 342 | w().digitalWrite(gpio, value); | ||
| 343 | } | ||
| 344 | |||
| 345 | WebIOPi.prototype.toggleFunction = function (gpio) { | ||
| 346 | var value = (w().GPIO[gpio].func == "IN") ? "OUT" : "IN"; | ||
| 347 | w().setFunction(gpio, value) | ||
| 348 | } | ||
| 349 | |||
| 350 | WebIOPi.prototype.outputSequence = function (gpio, period, sequence, callback) { | ||
| 351 | $.post(w().context + 'GPIO/' + gpio + "/sequence/" + period + "," + sequence, function(data) { | ||
| 352 | w().updateValue(gpio, data); | ||
| 353 | if (callback != undefined) { | ||
| 354 | callback(gpio, data); | ||
| 355 | } | ||
| 356 | }); | ||
| 357 | } | ||
| 358 | |||
| 359 | WebIOPi.prototype.callMacro = function (macro, args, callback) { | ||
| 360 | if (args == undefined) { | ||
| 361 | args = ""; | ||
| 362 | } | ||
| 363 | $.post(w().context + 'macros/' + macro + "/" + args, function(data) { | ||
| 364 | if (callback != undefined) { | ||
| 365 | callback(macro, args, data); | ||
| 366 | } | ||
| 367 | }); | ||
| 368 | } | ||
| 369 | |||
| 370 | WebIOPi.prototype.enablePWM = function(gpio, callback) { | ||
| 371 | $.post(w().context + 'GPIO/' + gpio + "/pwm/enable", function(data) { | ||
| 372 | if (callback != undefined) { | ||
| 373 | callback(gpio, data); | ||
| 374 | } | ||
| 375 | }); | ||
| 376 | } | ||
| 377 | |||
| 378 | WebIOPi.prototype.disablePWM = function(gpio, callback) { | ||
| 379 | $.post(w().context + 'GPIO/' + gpio + "/pwm/disable", function(data) { | ||
| 380 | if (callback != undefined) { | ||
| 381 | callback(gpio, data); | ||
| 382 | } | ||
| 383 | }); | ||
| 384 | } | ||
| 385 | |||
| 386 | WebIOPi.prototype.pulse = function(gpio, callback) { | ||
| 387 | $.post(w().context + 'GPIO/' + gpio + "/pulse/", function(data) { | ||
| 388 | if (callback != undefined) { | ||
| 389 | callback(gpio, data); | ||
| 390 | } | ||
| 391 | }); | ||
| 392 | } | ||
| 393 | |||
| 394 | WebIOPi.prototype.pulseRatio = function(gpio, ratio, callback) { | ||
| 395 | $.post(w().context + 'GPIO/' + gpio + "/pulseRatio/" + ratio, function(data) { | ||
| 396 | if (callback != undefined) { | ||
| 397 | callback(gpio, data); | ||
| 398 | } | ||
| 399 | }); | ||
| 400 | } | ||
| 401 | |||
| 402 | WebIOPi.prototype.pulseAngle = function(gpio, angle, callback) { | ||
| 403 | $.post(w().context + 'GPIO/' + gpio + "/pulseAngle/" + angle, function(data) { | ||
| 404 | if (callback != undefined) { | ||
| 405 | callback(gpio, data); | ||
| 406 | } | ||
| 407 | }); | ||
| 408 | } | ||
| 409 | |||
| 410 | WebIOPi.prototype.setLabel = function (id, label) { | ||
| 411 | $("#" + id).val(label); | ||
| 412 | $("#" + id).text(label); | ||
| 413 | } | ||
| 414 | |||
| 415 | WebIOPi.prototype.setClass = function (id, cssClass) { | ||
| 416 | $("#" + id).attr("class", cssClass); | ||
| 417 | } | ||
| 418 | |||
| 419 | WebIOPi.prototype.createButton = function (id, label, callback, callbackUp) { | ||
| 420 | var button = $('<button type="button" class="Default">'); | ||
| 421 | button.attr("id", id); | ||
| 422 | button.text(label); | ||
| 423 | if ((callback != undefined) && (callbackUp == undefined)) { | ||
| 424 | button.bind("click", callback); | ||
| 425 | } | ||
| 426 | else if ((callback != undefined) && (callbackUp != undefined)) { | ||
| 427 | if (isMobile()) { | ||
| 428 | button.bind("vmousedown", callback); | ||
| 429 | button.bind("vmouseup", callbackUp); | ||
| 430 | } | ||
| 431 | else { | ||
| 432 | button.bind("mousedown", callback); | ||
| 433 | button.bind("mouseup", callbackUp); | ||
| 434 | } | ||
| 435 | } | ||
| 436 | return button; | ||
| 437 | } | ||
| 438 | |||
| 439 | WebIOPi.prototype.createGPIOButton = function (gpio, label) { | ||
| 440 | var button = w().createButton("gpio" + gpio, label); | ||
| 441 | button.bind("click", function(event) { | ||
| 442 | w().toggleValue(gpio); | ||
| 443 | }); | ||
| 444 | return button; | ||
| 445 | } | ||
| 446 | |||
| 447 | WebIOPi.prototype.createFunctionButton = function (gpio) { | ||
| 448 | var button = w().createButton("function" + gpio, " "); | ||
| 449 | button.attr("class", "FunctionBasic"); | ||
| 450 | button.bind("click", function(event) { | ||
| 451 | w().toggleFunction(gpio); | ||
| 452 | }); | ||
| 453 | return button; | ||
| 454 | } | ||
| 455 | |||
| 456 | WebIOPi.prototype.createPulseButton = function (id, label, gpio) { | ||
| 457 | var button = webiopi().createButton(id, label); | ||
| 458 | button.bind("click", function(event) { | ||
| 459 | webiopi().pulse(gpio); | ||
| 460 | }); | ||
| 461 | return button; | ||
| 462 | } | ||
| 463 | |||
| 464 | WebIOPi.prototype.createMacroButton = function (id, label, macro, args) { | ||
| 465 | var button = webiopi().createButton(id, label); | ||
| 466 | button.bind("click", function(event) { | ||
| 467 | webiopi().callMacro(macro, args); | ||
| 468 | }); | ||
| 469 | return button; | ||
| 470 | } | ||
| 471 | |||
| 472 | WebIOPi.prototype.createSequenceButton = function (id, label, gpio, period, sequence) { | ||
| 473 | var button = webiopi().createButton(id, label); | ||
| 474 | button.bind("click", function(event) { | ||
| 475 | webiopi().outputSequence(gpio, period, sequence); | ||
| 476 | }); | ||
| 477 | return button; | ||
| 478 | } | ||
| 479 | |||
| 480 | WebIOPi.prototype.createRatioSlider = function(gpio) { | ||
| 481 | var slider = $('<input type="range" min="0.0" max="1.0" step="0.01">'); | ||
| 482 | slider.attr("id", "ratio"+gpio); | ||
| 483 | slider.bind("change", function() { | ||
| 484 | w().pulseRatio(gpio, slider.val()); | ||
| 485 | }); | ||
| 486 | return slider; | ||
| 487 | } | ||
| 488 | |||
| 489 | WebIOPi.prototype.createAngleSlider = function(gpio) { | ||
| 490 | var slider = $('<input type="range" min="-45" max="45" step="1">'); | ||
| 491 | slider.attr("id", "angle"+gpio); | ||
| 492 | slider.bind("change", function() { | ||
| 493 | w().pulseAngle(gpio, slider.val()); | ||
| 494 | }); | ||
| 495 | return slider; | ||
| 496 | } | ||
| 497 | |||
| 498 | WebIOPi.prototype.RPiHeader = function () { | ||
| 499 | if (w()._header == undefined) { | ||
| 500 | w()._header = new RPiHeader(); | ||
| 501 | } | ||
| 502 | return w()._header; | ||
| 503 | } | ||
| 504 | |||
| 505 | function RPiHeader() { | ||
| 506 | |||
| 507 | } | ||
| 508 | |||
| 509 | RPiHeader.prototype.getPinCell = function (pin) { | ||
| 510 | var cell = $('<td align="center">'); | ||
| 511 | var button; | ||
| 512 | if (w().PINS[pin].type.value == w().TYPE.GPIO.value) { | ||
| 513 | button = w().createGPIOButton(w().PINS[pin].value, pin); | ||
| 514 | } | ||
| 515 | else { | ||
| 516 | var button = $('<button type="button">'); | ||
| 517 | button.val(pin); | ||
| 518 | button.text(pin); | ||
| 519 | button.attr("class", w().PINS[pin].type.style); | ||
| 520 | } | ||
| 521 | cell.append(button); | ||
| 522 | return cell; | ||
| 523 | } | ||
| 524 | |||
| 525 | RPiHeader.prototype.getDescriptionCell = function (pin, align) { | ||
| 526 | var cell = $('<td>'); | ||
| 527 | cell.attr("align", align); | ||
| 528 | |||
| 529 | var div = $('<div>'); | ||
| 530 | div.attr("class", "Description"); | ||
| 531 | if (w().PINS[pin].type.value != w().TYPE.GPIO.value) { | ||
| 532 | div.append(w().PINS[pin].value); | ||
| 533 | } | ||
| 534 | else { | ||
| 535 | div.attr("id", "description"+w().PINS[pin].value); | ||
| 536 | div.append("GPIO " + w().PINS[pin].value); | ||
| 537 | } | ||
| 538 | |||
| 539 | cell.append(div); | ||
| 540 | |||
| 541 | return cell; | ||
| 542 | } | ||
| 543 | |||
| 544 | RPiHeader.prototype.getFunctionCell = function (pin) { | ||
| 545 | var cell = $('<td align="center">'); | ||
| 546 | if (w().PINS[pin].type.value == w().TYPE.GPIO.value) { | ||
| 547 | var button = w().createFunctionButton(w().PINS[pin].value); | ||
| 548 | cell.append(button); | ||
| 549 | } | ||
| 550 | return cell; | ||
| 551 | } | ||
| 552 | |||
| 553 | RPiHeader.prototype.createTable = function (containerId) { | ||
| 554 | var table = $("<table>"); | ||
| 555 | table.attr("id", "RPiHeader") | ||
| 556 | for (var pin=1; pin<=26; pin++) { | ||
| 557 | var line = $('<tr>'); | ||
| 558 | line.append(this.getFunctionCell(pin)) | ||
| 559 | line.append(this.getDescriptionCell(pin, "right")) | ||
| 560 | line.append(this.getPinCell(pin)); | ||
| 561 | |||
| 562 | pin++; | ||
| 563 | line.append(this.getPinCell(pin)); | ||
| 564 | line.append(this.getDescriptionCell(pin, "left")) | ||
| 565 | line.append(this.getFunctionCell(pin)) | ||
| 566 | |||
| 567 | table.append(line); | ||
| 568 | } | ||
| 569 | |||
| 570 | if (containerId != undefined) { | ||
| 571 | $("#"+containerId).append(table); | ||
| 572 | } | ||
| 573 | |||
| 574 | return table; | ||
| 575 | } | ||
| 576 | |||
| 577 | WebIOPi.prototype.Expert = function () { | ||
| 578 | if (w()._expert == undefined) { | ||
| 579 | w()._expert = new Expert(); | ||
| 580 | } | ||
| 581 | return w()._expert; | ||
| 582 | } | ||
| 583 | |||
| 584 | function Expert() { | ||
| 585 | |||
| 586 | } | ||
| 587 | |||
| 588 | Expert.prototype.createGPIO = function (gpio) { | ||
| 589 | var box = $("<div>"); | ||
| 590 | box.append(w().createFunctionButton(gpio)); | ||
| 591 | box.append(w().createGPIOButton(gpio, gpio)); | ||
| 592 | |||
| 593 | div = $('<div>'); | ||
| 594 | div.attr("id", "description"+gpio); | ||
| 595 | div.attr("class", "Description"); | ||
| 596 | div.append("GPIO " + gpio); | ||
| 597 | box.append(div); | ||
| 598 | |||
| 599 | return box; | ||
| 600 | } | ||
| 601 | |||
| 602 | Expert.prototype.createList = function (containerId) { | ||
| 603 | var box = $('<div>'); | ||
| 604 | |||
| 605 | for (i = 0; i<w().GPIO.length; i++) { | ||
| 606 | if (w().GPIO[i].mapped == true) { | ||
| 607 | var gpio = w().Expert().createGPIO(i); | ||
| 608 | box.append(gpio); | ||
| 609 | } | ||
| 610 | } | ||
| 611 | |||
| 612 | if (containerId != undefined) { | ||
| 613 | $("#"+containerId).append(box); | ||
| 614 | } | ||
| 615 | |||
| 616 | return box; | ||
| 617 | } | ||
| 618 | |||
| 619 | WebIOPi.prototype.Serial = function(device) { | ||
| 620 | return new Serial(device); | ||
| 621 | } | ||
| 622 | |||
| 623 | function Serial(device) { | ||
| 624 | this.device = device; | ||
| 625 | this.url = "/devices/" + device | ||
| 626 | } | ||
| 627 | |||
| 628 | Serial.prototype.write = function(data) { | ||
| 629 | $.post(this.url, data); | ||
| 630 | } | ||
| 631 | |||
| 632 | Serial.prototype.read = function(callback) { | ||
| 633 | $.get(this.url, callback); | ||
| 634 | } | ||
| 635 | |||
| 636 | WebIOPi.prototype.newDevice = function(type, name) { | ||
| 637 | if (type == "ADC") { | ||
| 638 | return new ADC(name); | ||
| 639 | } | ||
| 640 | |||
| 641 | if (type == "DAC") { | ||
| 642 | return new DAC(name); | ||
| 643 | } | ||
| 644 | |||
| 645 | if (type == "PWM") { | ||
| 646 | return new PWM(name); | ||
| 647 | } | ||
| 648 | |||
| 649 | if (type == "GPIOPort") { | ||
| 650 | return new GPIOPort(name); | ||
| 651 | } | ||
| 652 | |||
| 653 | if (type == "Temperature") { | ||
| 654 | return new Temperature(name); | ||
| 655 | } | ||
| 656 | |||
| 657 | if (type == "Pressure") { | ||
| 658 | return new Pressure(name); | ||
| 659 | } | ||
| 660 | |||
| 661 | if (type == "Luminosity") { | ||
| 662 | return new Luminosity(name); | ||
| 663 | } | ||
| 664 | |||
| 665 | if (type == "Distance") { | ||
| 666 | return new Distance(name); | ||
| 667 | } | ||
| 668 | |||
| 669 | if (type == "PiFaceDigital") { | ||
| 670 | return new PiFaceDigital(name); | ||
| 671 | } | ||
| 672 | |||
| 673 | return undefined; | ||
| 674 | } | ||
| 675 | |||
| 676 | function GPIOPort(name) { | ||
| 677 | this.name = name; | ||
| 678 | this.url = "/devices/" + name; | ||
| 679 | this.onready = null; | ||
| 680 | this.channelCount = 0; | ||
| 681 | this.refreshTime = 1000; | ||
| 682 | |||
| 683 | var port = this; | ||
| 684 | $.get(this.url + "/count", function(data) { | ||
| 685 | port.channelCount = parseInt(data); | ||
| 686 | }); | ||
| 687 | |||
| 688 | } | ||
| 689 | |||
| 690 | GPIOPort.prototype.isReady = function() { | ||
| 691 | return (this.channelCount > 0); | ||
| 692 | } | ||
| 693 | |||
| 694 | GPIOPort.prototype.toString = function() { | ||
| 695 | if (this.channelCount > 0) | ||
| 696 | return this.name + ": GPIO Port (" + this.channelCount + "-bits)"; | ||
| 697 | return this.name + ": GPIO Port"; | ||
| 698 | } | ||
| 699 | |||
| 700 | GPIOPort.prototype.digitalRead = function(channel, callback) { | ||
| 701 | var name = this.name; | ||
| 702 | $.get(this.url + "/" + channel + "/value", function(data) { | ||
| 703 | callback(name, channel, data); | ||
| 704 | }); | ||
| 705 | } | ||
| 706 | |||
| 707 | GPIOPort.prototype.digitalWrite = function(channel, value, callback) { | ||
| 708 | var name = this.name; | ||
| 709 | $.post(this.url + "/" + channel + "/value/" + value, function(data) { | ||
| 710 | callback(name, channel, data); | ||
| 711 | }); | ||
| 712 | } | ||
| 713 | |||
| 714 | GPIOPort.prototype.setFunction = function(channel, func, callback) { | ||
| 715 | var name = this.name; | ||
| 716 | $.post(this.url + "/" + channel + "/function/" + func, function(data) { | ||
| 717 | callback(name, channel, data); | ||
| 718 | }); | ||
| 719 | } | ||
| 720 | |||
| 721 | GPIOPort.prototype.readAll = function(callback) { | ||
| 722 | var name = this.name; | ||
| 723 | $.get(this.url+ "/*", function(data) { | ||
| 724 | callback(name, data); | ||
| 725 | }); | ||
| 726 | } | ||
| 727 | |||
| 728 | GPIOPort.prototype.refreshUI = function() { | ||
| 729 | var port = this; | ||
| 730 | var element = this.element; | ||
| 731 | if ((element != undefined) && (element.header == undefined)) { | ||
| 732 | element.header = $("<h3>" + this + "</h3>"); | ||
| 733 | element.append(element.header); | ||
| 734 | } | ||
| 735 | |||
| 736 | if ((element != undefined) && (element.table == undefined) && this.isReady()) { | ||
| 737 | element.header.text(this) | ||
| 738 | element.table = $("<table>"); | ||
| 739 | element.append(element.table); | ||
| 740 | |||
| 741 | var line = $("<tr>"); | ||
| 742 | for (var i = this.channelCount-1; i>=0; i--) { | ||
| 743 | var cell = $("<td>"); | ||
| 744 | cell.text(1<<i); | ||
| 745 | line.append(cell); | ||
| 746 | } | ||
| 747 | element.table.append(line); | ||
| 748 | |||
| 749 | line = $("<tr>"); | ||
| 750 | for (var i = this.channelCount-1; i>=0; i--) { | ||
| 751 | var cell = $("<td>"); | ||
| 752 | var button = webiopi().createButton(this.name + "_" + i + "_value", i, function() { | ||
| 753 | if ($("#" + port.name + "_" + $(this).attr("channel") + "_value").attr("class") == "LOW") { | ||
| 754 | value = 1; | ||
| 755 | } | ||
| 756 | else { | ||
| 757 | value = 0; | ||
| 758 | } | ||
| 759 | port.digitalWrite($(this).attr("channel"), value, function(name, channel, data) { | ||
| 760 | if (data == "1") { | ||
| 761 | $("#" + name + "_" + channel + "_value").attr("class", "HIGH") | ||
| 762 | } | ||
| 763 | else { | ||
| 764 | $("#" + name + "_" + channel + "_value").attr("class", "LOW") | ||
| 765 | } | ||
| 766 | }); | ||
| 767 | }); | ||
| 768 | button.attr("channel", i); | ||
| 769 | button.attr("class", "LOW"); | ||
| 770 | cell.append(button); | ||
| 771 | line.append(cell); | ||
| 772 | } | ||
| 773 | element.table.append(line); | ||
| 774 | |||
| 775 | line = $("<tr>"); | ||
| 776 | for (var i = this.channelCount-1; i>=0; i--) { | ||
| 777 | var cell = $("<td>"); | ||
| 778 | var button = webiopi().createButton(port.name + "_" + i + "_func", "IN", function() { | ||
| 779 | var func = $(this).text(); | ||
| 780 | console.log(func); | ||
| 781 | if (func == "IN") { | ||
| 782 | func = "OUT"; | ||
| 783 | } | ||
| 784 | else { | ||
| 785 | func = "IN"; | ||
| 786 | } | ||
| 787 | port.setFunction($(this).attr("channel"), func, function(name, channel, func) { | ||
| 788 | $("#" + port.name + "_" + channel + "_func").text(func); | ||
| 789 | }); | ||
| 790 | }); | ||
| 791 | button.attr("class", "FunctionBasic"); | ||
| 792 | button.attr("channel", i); | ||
| 793 | cell.append(button); | ||
| 794 | line.append(cell); | ||
| 795 | } | ||
| 796 | element.table.append(line); | ||
| 797 | } | ||
| 798 | |||
| 799 | this.readAll(function(name, data) { | ||
| 800 | for (i in data) { | ||
| 801 | $("#" + name + "_" + i + "_value").attr("class", data[i]["value"] == "1" ? "HIGH" : "LOW"); | ||
| 802 | $("#" + name + "_" + i + "_func").text(data[i]["function"]); | ||
| 803 | } | ||
| 804 | setTimeout(function(){port.refreshUI()}, port.refreshTime); | ||
| 805 | }); | ||
| 806 | } | ||
| 807 | |||
| 808 | function ADC(name) { | ||
| 809 | this.name = name; | ||
| 810 | this.url = "/devices/" + name + "/analog"; | ||
| 811 | this.channelCount = 0; | ||
| 812 | this.maxInteger = 0; | ||
| 813 | this.resolution = 0; | ||
| 814 | this.refreshTime = 1000; | ||
| 815 | |||
| 816 | var adc = this; | ||
| 817 | $.get(this.url + "/count", function(data) { | ||
| 818 | adc.channelCount = parseInt(data); | ||
| 819 | }); | ||
| 820 | |||
| 821 | $.get(this.url + "/max", function(data) { | ||
| 822 | adc.maxInteger = parseInt(data); | ||
| 823 | }); | ||
| 824 | |||
| 825 | $.get(this.url + "/resolution", function(data) { | ||
| 826 | adc.resolution = parseInt(data); | ||
| 827 | }); | ||
| 828 | } | ||
| 829 | |||
| 830 | ADC.prototype.isReady = function() { | ||
| 831 | return (this.channelCount > 0 && this.maxInteger > 0 && this.resolution > 0 ); | ||
| 832 | } | ||
| 833 | |||
| 834 | ADC.prototype.toString = function() { | ||
| 835 | if (this.channelCount > 0 && this.resolution> 0) | ||
| 836 | return this.name + ": ADC (" + this.resolution + "-bits, " + this.channelCount + "-channels)"; | ||
| 837 | return this.name + ": ADC"; | ||
| 838 | } | ||
| 839 | |||
| 840 | ADC.prototype.readInteger = function(channel, callback) { | ||
| 841 | var name = this.name; | ||
| 842 | $.get(this.url + "/" + channel + "/integer", function(data) { | ||
| 843 | callback(name, channel, data); | ||
| 844 | }); | ||
| 845 | } | ||
| 846 | |||
| 847 | ADC.prototype.readFloat = function(channel, callback) { | ||
| 848 | var name = this.name; | ||
| 849 | $.get(this.url + "/" + channel + "/float", function(data) { | ||
| 850 | callback(name, channel, data); | ||
| 851 | }); | ||
| 852 | } | ||
| 853 | |||
| 854 | ADC.prototype.readVolt = function(channel, callback) { | ||
| 855 | var name = this.name; | ||
| 856 | $.get(this.url + "/" + channel + "/volt", function(data) { | ||
| 857 | callback(name, channel, data); | ||
| 858 | }); | ||
| 859 | } | ||
| 860 | |||
| 861 | ADC.prototype.readAllInteger = function(callback) { | ||
| 862 | var name = this.name; | ||
| 863 | $.get(this.url + "/*/integer", function(data) { | ||
| 864 | callback(name, data); | ||
| 865 | }); | ||
| 866 | } | ||
| 867 | |||
| 868 | ADC.prototype.readAllFloat = function(callback) { | ||
| 869 | var name = this.name; | ||
| 870 | $.get(this.url + "/*/float", function(data) { | ||
| 871 | callback(name, data); | ||
| 872 | }); | ||
| 873 | } | ||
| 874 | |||
| 875 | ADC.prototype.readAllVolt = function(callback) { | ||
| 876 | var name = this.name; | ||
| 877 | $.get(this.url + "/*/volt", function(data) { | ||
| 878 | callback(name, data); | ||
| 879 | }); | ||
| 880 | } | ||
| 881 | |||
| 882 | ADC.prototype.refreshUI = function () { | ||
| 883 | var adc = this; | ||
| 884 | var element = this.element; | ||
| 885 | |||
| 886 | if ((element != undefined) && (element.header == undefined)) { | ||
| 887 | element.header = $("<h3>" + this + "</h3>"); | ||
| 888 | element.append(element.header); | ||
| 889 | } | ||
| 890 | |||
| 891 | if ((element != undefined) && (element.channels == undefined) && this.isReady()) { | ||
| 892 | element.header.text(this); | ||
| 893 | element.channels = Array(); | ||
| 894 | for (i = 0; i<this.channelCount; i++) { | ||
| 895 | var div = $("<div>"); | ||
| 896 | div.text("Channel-" + i); | ||
| 897 | element.append(div); | ||
| 898 | element.channels[i] = div; | ||
| 899 | |||
| 900 | } | ||
| 901 | } | ||
| 902 | this.readAllVolt(function(name, data) { | ||
| 903 | for (i in data) { | ||
| 904 | if ((element != undefined) && (element.channels != undefined)) { | ||
| 905 | var div = element.channels[i]; | ||
| 906 | div.text("Channel-" + i + ": " + parseFloat(data[i]).toFixed(2) + "V") | ||
| 907 | } | ||
| 908 | } | ||
| 909 | setTimeout(function(){adc.refreshUI()}, adc.refreshTime); | ||
| 910 | }); | ||
| 911 | } | ||
| 912 | |||
| 913 | |||
| 914 | function DAC(name) { | ||
| 915 | this.name = name; | ||
| 916 | this.url = "/devices/" + name + "/analog"; | ||
| 917 | this.channelCount = 0; | ||
| 918 | this.maxInteger = 0; | ||
| 919 | this.resolution = 0; | ||
| 920 | |||
| 921 | var dac = this; | ||
| 922 | $.get(this.url + "/count", function(data) { | ||
| 923 | dac.channelCount = parseInt(data); | ||
| 924 | }); | ||
| 925 | |||
| 926 | $.get(this.url + "/max", function(data) { | ||
| 927 | dac.maxInteger = parseInt(data); | ||
| 928 | }); | ||
| 929 | |||
| 930 | $.get(this.url + "/resolution", function(data) { | ||
| 931 | dac.resolution = parseInt(data); | ||
| 932 | }); | ||
| 933 | } | ||
| 934 | |||
| 935 | DAC.prototype.isReady = function() { | ||
| 936 | return (this.channelCount > 0 && this.maxInteger > 0 && this.resolution > 0 ); | ||
| 937 | } | ||
| 938 | |||
| 939 | DAC.prototype.toString = function() { | ||
| 940 | if (this.channelCount > 0 && this.resolution> 0) | ||
| 941 | return this.name + ": DAC (" + this.resolution + "-bits, " + this.channelCount + "-channels)"; | ||
| 942 | return this.name + ": DAC"; | ||
| 943 | } | ||
| 944 | |||
| 945 | DAC.prototype.writeInteger = function(channel, value, callback) { | ||
| 946 | var name = this.name; | ||
| 947 | $.post(this.url + "/" + channel + "/integer/" + value, function(data) { | ||
| 948 | callback(name, channel, data); | ||
| 949 | }); | ||
| 950 | } | ||
| 951 | |||
| 952 | DAC.prototype.writeFloat = function(channel, value, callback) { | ||
| 953 | var name = this.name; | ||
| 954 | $.post(this.url + "/" + channel + "/float/" + value, function(data) { | ||
| 955 | callback(name, channel, data); | ||
| 956 | }); | ||
| 957 | } | ||
| 958 | |||
| 959 | DAC.prototype.readAllInteger = function(callback) { | ||
| 960 | var name = this.name; | ||
| 961 | $.get(this.url + "/*/integer", function(data) { | ||
| 962 | callback(name, data); | ||
| 963 | }); | ||
| 964 | } | ||
| 965 | |||
| 966 | DAC.prototype.readAllFloat = function(callback) { | ||
| 967 | var name = this.name; | ||
| 968 | $.get(this.url + "/*/float", function(data) { | ||
| 969 | callback(name, data); | ||
| 970 | }); | ||
| 971 | } | ||
| 972 | |||
| 973 | DAC.prototype.refreshUI = function() { | ||
| 974 | var dac = this; | ||
| 975 | var element = this.element; | ||
| 976 | |||
| 977 | if ((element != undefined) && (element.header == undefined)) { | ||
| 978 | element.header = $("<h3>" + this + "</h3>"); | ||
| 979 | element.append(element.header); | ||
| 980 | } | ||
| 981 | |||
| 982 | if ((element != undefined) && (element.table == undefined) && this.isReady()) { | ||
| 983 | element.header.text(this); | ||
| 984 | element.table = $("<table>"); | ||
| 985 | element.append(element.table); | ||
| 986 | for (var i = 0; i<this.channelCount; i++) { | ||
| 987 | var line = $("<tr>"); | ||
| 988 | var cell | ||
| 989 | cell = $("<td>"); | ||
| 990 | cell.text("Channel-" + i); | ||
| 991 | line.append(cell); | ||
| 992 | |||
| 993 | cell = $("<td>"); | ||
| 994 | var slider = $('<input type="range" min="0" max="100" step="1" value="0">') | ||
| 995 | slider.attr("channel", i); | ||
| 996 | slider.attr("id", "slider_" + this.name + "_" + i); | ||
| 997 | cell.append(slider); | ||
| 998 | line.append(cell); | ||
| 999 | |||
| 1000 | cell = $("<td>"); | ||
| 1001 | var span = $('<span>'); | ||
| 1002 | span.attr("id", "span_" + this.name + "_" + i); | ||
| 1003 | cell.append(span); | ||
| 1004 | line.append(cell); | ||
| 1005 | |||
| 1006 | slider.bind("change", function() { | ||
| 1007 | dac.writeFloat($(this).attr("channel"), $(this).val()/100, function(name, channel, data) { | ||
| 1008 | var val = (data*100).toFixed(0); | ||
| 1009 | var volts = (data*3.3).toFixed(2); | ||
| 1010 | $("#span_" + name + "_" + channel).text(volts + "V - " + val + "%"); | ||
| 1011 | $("#slider_" + name + "_" + channel).val(val); | ||
| 1012 | }); | ||
| 1013 | }); | ||
| 1014 | |||
| 1015 | element.table.append(line); | ||
| 1016 | } | ||
| 1017 | this.readAllFloat(function(name, data) { | ||
| 1018 | for (i in data) { | ||
| 1019 | var val = (data[i]*100).toFixed(0); | ||
| 1020 | var volts = (data[i]*3.3).toFixed(2); | ||
| 1021 | $("#span_" + name + "_" + i).text(volts + "V - " + val + "%"); | ||
| 1022 | $("#slider_" + name + "_" + i).val(val); | ||
| 1023 | } | ||
| 1024 | }); | ||
| 1025 | } | ||
| 1026 | else { | ||
| 1027 | setTimeout(function(){dac.refreshUI()}, 1000); | ||
| 1028 | } | ||
| 1029 | |||
| 1030 | } | ||
| 1031 | |||
| 1032 | function PWM(name) { | ||
| 1033 | this.name = name; | ||
| 1034 | this.url = "/devices/" + name + "/pwm"; | ||
| 1035 | this.channelCount = 0; | ||
| 1036 | this.maxInteger = 0; | ||
| 1037 | this.resolution = 0; | ||
| 1038 | this.refreshTime = 1000; | ||
| 1039 | |||
| 1040 | var pwm = this; | ||
| 1041 | $.get(this.url + "/count", function(data) { | ||
| 1042 | pwm.channelCount = parseInt(data); | ||
| 1043 | }); | ||
| 1044 | |||
| 1045 | $.get(this.url + "/max", function(data) { | ||
| 1046 | pwm.maxInteger = parseInt(data); | ||
| 1047 | }); | ||
| 1048 | |||
| 1049 | $.get(this.url + "/resolution", function(data) { | ||
| 1050 | pwm.resolution = parseInt(data); | ||
| 1051 | }); | ||
| 1052 | } | ||
| 1053 | |||
| 1054 | PWM.prototype.isReady = function() { | ||
| 1055 | return (this.channelCount > 0 && this.maxInteger > 0 && this.resolution > 0 ); | ||
| 1056 | } | ||
| 1057 | |||
| 1058 | PWM.prototype.toString = function() { | ||
| 1059 | if (this.channelCount > 0 && this.resolution> 0) | ||
| 1060 | return this.name + ": PWM (" + this.resolution + "-bits, " + this.channelCount + "-channels)"; | ||
| 1061 | return this.name + ": PWM"; | ||
| 1062 | } | ||
| 1063 | |||
| 1064 | PWM.prototype.writeInteger = function(channel, value, callback) { | ||
| 1065 | var name = this.name; | ||
| 1066 | $.post(this.url + "/" + channel + "/integer/" + value, function(data) { | ||
| 1067 | callback(name, channel, data); | ||
| 1068 | }); | ||
| 1069 | } | ||
| 1070 | |||
| 1071 | PWM.prototype.writeFloat = function(channel, value, callback) { | ||
| 1072 | var name = this.name; | ||
| 1073 | $.post(this.url + "/" + channel + "/float/" + value, function(data) { | ||
| 1074 | callback(name, channel, data); | ||
| 1075 | }); | ||
| 1076 | } | ||
| 1077 | |||
| 1078 | PWM.prototype.writeAngle = function(channel, value, callback) { | ||
| 1079 | var name = this.name; | ||
| 1080 | $.post(this.url + "/" + channel + "/angle/" + value, function(data) { | ||
| 1081 | callback(name, channel, data); | ||
| 1082 | }); | ||
| 1083 | } | ||
| 1084 | |||
| 1085 | PWM.prototype.readAllInteger = function(callback) { | ||
| 1086 | var name = this.name; | ||
| 1087 | $.get(this.url + "/*/integer", function(data) { | ||
| 1088 | callback(name, data); | ||
| 1089 | }); | ||
| 1090 | } | ||
| 1091 | |||
| 1092 | PWM.prototype.readAllFloat = function(callback) { | ||
| 1093 | var name = this.name; | ||
| 1094 | $.get(this.url + "/*/float", function(data) { | ||
| 1095 | callback(name, data); | ||
| 1096 | }); | ||
| 1097 | } | ||
| 1098 | |||
| 1099 | PWM.prototype.readAll = function(callback) { | ||
| 1100 | var name = this.name; | ||
| 1101 | $.get(this.url + "/*", function(data) { | ||
| 1102 | callback(name, data); | ||
| 1103 | }); | ||
| 1104 | } | ||
| 1105 | |||
| 1106 | PWM.prototype.refreshUI = function() { | ||
| 1107 | var pwm = this; | ||
| 1108 | var element = this.element; | ||
| 1109 | if ((element != undefined) && (element.header == undefined)) { | ||
| 1110 | element.header = $("<h3>" + this + "</h3>"); | ||
| 1111 | element.append(element.header); | ||
| 1112 | } | ||
| 1113 | |||
| 1114 | if ((element != undefined) && (element.table == undefined) && this.isReady()) { | ||
| 1115 | element.header.text(this); | ||
| 1116 | element.table = $("<table>"); | ||
| 1117 | element.append(element.table); | ||
| 1118 | |||
| 1119 | for (var i = 0; i<this.channelCount; i++) { | ||
| 1120 | var line = $("<tr>"); | ||
| 1121 | var cell | ||
| 1122 | cell = $("<td>"); | ||
| 1123 | cell.text("Channel-" + i); | ||
| 1124 | line.append(cell); | ||
| 1125 | |||
| 1126 | cell = $("<td>"); | ||
| 1127 | var checkbox = $('<input type="checkbox">'); | ||
| 1128 | checkbox.attr("id", "checkbox_" + this.name + "_" + i); | ||
| 1129 | checkbox.attr("channel", i); | ||
| 1130 | |||
| 1131 | var cblabel = $('<label>'); | ||
| 1132 | cblabel.append(checkbox); | ||
| 1133 | cblabel.append("Servo"); | ||
| 1134 | cell.append(cblabel); | ||
| 1135 | line.append(cell); | ||
| 1136 | |||
| 1137 | cell = $("<td>"); | ||
| 1138 | var slider = $('<input type="range" min="0" max="100" step="1" value="0">') | ||
| 1139 | slider.attr("channel", i); | ||
| 1140 | slider.attr("id", "slider_" + this.name + "_" + i); | ||
| 1141 | cell.append(slider); | ||
| 1142 | line.append(cell); | ||
| 1143 | |||
| 1144 | cell = $("<td>"); | ||
| 1145 | var span = $('<span>'); | ||
| 1146 | span.attr("id", "span_" + this.name + "_" + i); | ||
| 1147 | cell.append(span); | ||
| 1148 | line.append(cell); | ||
| 1149 | |||
| 1150 | checkbox.bind("change", function() { | ||
| 1151 | var slider = $("#slider_" + pwm.name + "_" + $(this).attr("channel")) | ||
| 1152 | slider.attr("servo", $(this).is(":checked")); | ||
| 1153 | }); | ||
| 1154 | |||
| 1155 | slider.bind("change", function() { | ||
| 1156 | if ($(this).attr("servo") == "true") { | ||
| 1157 | pwm.writeAngle($(this).attr("channel"), $(this).val(), function(name, channel, data) { | ||
| 1158 | var val = data; | ||
| 1159 | $("#span_" + name + "_" + channel).text(val + "°"); | ||
| 1160 | $("#slider_" + name + "_" + channel).val(val); | ||
| 1161 | }); | ||
| 1162 | } | ||
| 1163 | else { | ||
| 1164 | pwm.writeFloat($(this).attr("channel"), $(this).val()/100, function(name, channel, data) { | ||
| 1165 | var val = (data*100).toFixed(0); | ||
| 1166 | $("#span_" + name + "_" + channel).text(val + "%"); | ||
| 1167 | $("#slider_" + name + "_" + channel).val(val); | ||
| 1168 | }); | ||
| 1169 | } | ||
| 1170 | }); | ||
| 1171 | |||
| 1172 | element.table.append(line); | ||
| 1173 | } | ||
| 1174 | } | ||
| 1175 | |||
| 1176 | this.readAll(function(name, data) { | ||
| 1177 | for (i in data) { | ||
| 1178 | var slider = $("#slider_" + name + "_" + i); | ||
| 1179 | var span = $("#span_" + name + "_" + i); | ||
| 1180 | var val = 0; | ||
| 1181 | |||
| 1182 | if (slider.attr("servo") == "true") { | ||
| 1183 | slider.attr("min", -45); | ||
| 1184 | slider.attr("max", 45); | ||
| 1185 | val = data[i]["angle"]; | ||
| 1186 | span.text(val + "°"); | ||
| 1187 | } | ||
| 1188 | else { | ||
| 1189 | slider.attr("min", 0); | ||
| 1190 | slider.attr("max", 100); | ||
| 1191 | val = (data[i]["float"]*100).toFixed(0); | ||
| 1192 | span.text(val + "%"); | ||
| 1193 | } | ||
| 1194 | slider.val(val); | ||
| 1195 | |||
| 1196 | } | ||
| 1197 | setTimeout(function(){pwm.refreshUI()}, pwm.refreshTime); | ||
| 1198 | }); | ||
| 1199 | } | ||
| 1200 | |||
| 1201 | function Temperature(name) { | ||
| 1202 | this.name = name; | ||
| 1203 | this.url = "/devices/" + name + "/sensor"; | ||
| 1204 | this.refreshTime = 5000; | ||
| 1205 | } | ||
| 1206 | |||
| 1207 | Temperature.prototype.toString = function() { | ||
| 1208 | return this.name + ": Temperature"; | ||
| 1209 | } | ||
| 1210 | |||
| 1211 | Temperature.prototype.getKelvin = function(callback) { | ||
| 1212 | $.get(this.url + "/temperature/k", function(data) { | ||
| 1213 | callback(this.name, data); | ||
| 1214 | }); | ||
| 1215 | } | ||
| 1216 | |||
| 1217 | Temperature.prototype.getCelsius = function(callback) { | ||
| 1218 | $.get(this.url + "/temperature/c", function(data) { | ||
| 1219 | callback(this.name, data); | ||
| 1220 | }); | ||
| 1221 | } | ||
| 1222 | |||
| 1223 | Temperature.prototype.getFahrenheit = function(callback) { | ||
| 1224 | $.get(this.url + "/temperature/f", function(data) { | ||
| 1225 | callback(this.name, data); | ||
| 1226 | }); | ||
| 1227 | } | ||
| 1228 | |||
| 1229 | Temperature.prototype.refreshUI = function() { | ||
| 1230 | var temp = this; | ||
| 1231 | var element = this.element; | ||
| 1232 | if ((element != undefined) && (element.header == undefined)) { | ||
| 1233 | element.header = $("<h3>" + this + "</h3>"); | ||
| 1234 | element.append(element.header); | ||
| 1235 | } | ||
| 1236 | |||
| 1237 | this.getCelsius(function(name, data){ | ||
| 1238 | if (element != undefined) { | ||
| 1239 | element.header.text(temp + ": " + data + "°C"); | ||
| 1240 | } | ||
| 1241 | setTimeout(function(){temp.refreshUI()}, temp.refreshTime); | ||
| 1242 | }); | ||
| 1243 | } | ||
| 1244 | |||
| 1245 | function Pressure(name) { | ||
| 1246 | this.name = name; | ||
| 1247 | this.url = "/devices/" + name + "/sensor"; | ||
| 1248 | this.refreshTime = 5000; | ||
| 1249 | } | ||
| 1250 | |||
| 1251 | Pressure.prototype.toString = function() { | ||
| 1252 | return this.name + ": Pressure"; | ||
| 1253 | } | ||
| 1254 | |||
| 1255 | Pressure.prototype.getPascal = function(callback) { | ||
| 1256 | $.get(this.url + "/pressure/pa", function(data) { | ||
| 1257 | callback(this.name, data); | ||
| 1258 | }); | ||
| 1259 | } | ||
| 1260 | |||
| 1261 | Pressure.prototype.getHectoPascal = function(callback) { | ||
| 1262 | $.get(this.url + "/pressure/hpa", function(data) { | ||
| 1263 | callback(this.name, data); | ||
| 1264 | }); | ||
| 1265 | } | ||
| 1266 | |||
| 1267 | Pressure.prototype.refreshUI = function() { | ||
| 1268 | var pressure = this; | ||
| 1269 | var element = this.element; | ||
| 1270 | if ((element != undefined) && (element.header == undefined)) { | ||
| 1271 | element.header = $("<h3>" + this + "</h3>"); | ||
| 1272 | element.append(element.header); | ||
| 1273 | } | ||
| 1274 | |||
| 1275 | pressure.getHectoPascal(function(name, data){ | ||
| 1276 | if (element != undefined) { | ||
| 1277 | element.header.text(pressure + ": " + data + "hPa"); | ||
| 1278 | } | ||
| 1279 | setTimeout(function(){pressure.refreshUI()}, pressure.refreshTime); | ||
| 1280 | }); | ||
| 1281 | } | ||
| 1282 | |||
| 1283 | |||
| 1284 | function Luminosity(name) { | ||
| 1285 | this.name = name; | ||
| 1286 | this.url = "/devices/" + name + "/sensor"; | ||
| 1287 | this.refreshTime = 1000; | ||
| 1288 | } | ||
| 1289 | |||
| 1290 | Luminosity.prototype.toString = function() { | ||
| 1291 | return this.name + ": Luminosity"; | ||
| 1292 | } | ||
| 1293 | |||
| 1294 | Luminosity.prototype.getLux = function(callback) { | ||
| 1295 | $.get(this.url + "/luminosity/lx", function(data) { | ||
| 1296 | callback(this.name, data); | ||
| 1297 | }); | ||
| 1298 | } | ||
| 1299 | |||
| 1300 | Luminosity.prototype.refreshUI = function() { | ||
| 1301 | var lum = this; | ||
| 1302 | var element = this.element; | ||
| 1303 | |||
| 1304 | if ((element != undefined) && (element.header == undefined)) { | ||
| 1305 | element.header = $("<h3>" + this + "</h3>"); | ||
| 1306 | element.append(element.header); | ||
| 1307 | } | ||
| 1308 | |||
| 1309 | this.getLux(function(name, data){ | ||
| 1310 | if (element != undefined) { | ||
| 1311 | element.header.text(lum + ": " + data + "lx"); | ||
| 1312 | } | ||
| 1313 | setTimeout(function(){lum.refreshUI()}, lum.refreshTime); | ||
| 1314 | }); | ||
| 1315 | } | ||
| 1316 | |||
| 1317 | function Distance(name) { | ||
| 1318 | this.name = name; | ||
| 1319 | this.url = "/devices/" + name + "/sensor"; | ||
| 1320 | this.refreshTime = 1000; | ||
| 1321 | } | ||
| 1322 | |||
| 1323 | Distance.prototype.toString = function() { | ||
| 1324 | return this.name + ": Distance"; | ||
| 1325 | } | ||
| 1326 | |||
| 1327 | Distance.prototype.getMillimeter = function(callback) { | ||
| 1328 | $.get(this.url + "/distance/mm", function(data) { | ||
| 1329 | callback(this.name, data); | ||
| 1330 | }); | ||
| 1331 | } | ||
| 1332 | |||
| 1333 | Distance.prototype.refreshUI = function() { | ||
| 1334 | var dist = this; | ||
| 1335 | var element = this.element; | ||
| 1336 | |||
| 1337 | if ((element != undefined) && (element.header == undefined)) { | ||
| 1338 | element.header = $("<h3>" + this + "</h3>"); | ||
| 1339 | element.append(element.header); | ||
| 1340 | } | ||
| 1341 | |||
| 1342 | this.getMillimeter(function(name, data){ | ||
| 1343 | if (element != undefined) { | ||
| 1344 | element.header.text(dist + ": " + data + "mm"); | ||
| 1345 | } | ||
| 1346 | setTimeout(function(){dist.refreshUI()}, dist.refreshTime); | ||
| 1347 | }); | ||
| 1348 | } | ||
| 1349 | |||
| 1350 | function PiFaceDigital(name) { | ||
| 1351 | this.name = name; | ||
| 1352 | this.url = "/devices/" + name + "/digital"; | ||
| 1353 | this.onready = null; | ||
| 1354 | this.refreshTime = 1000; | ||
| 1355 | } | ||
| 1356 | |||
| 1357 | PiFaceDigital.prototype.toString = function() { | ||
| 1358 | return "PiFaceDigital"; | ||
| 1359 | } | ||
| 1360 | |||
| 1361 | PiFaceDigital.prototype.input = function(channel, callback) { | ||
| 1362 | var name = this.name; | ||
| 1363 | $.get(this.url + "/input/" + channel, function(data) { | ||
| 1364 | callback(name, channel, data); | ||
| 1365 | }); | ||
| 1366 | } | ||
| 1367 | |||
| 1368 | PiFaceDigital.prototype.output = function(channel, value, callback) { | ||
| 1369 | var name = this.name; | ||
| 1370 | $.post(this.url + "/output/" + channel + "/" + value, function(data) { | ||
| 1371 | callback(name, channel, data); | ||
| 1372 | }); | ||
| 1373 | } | ||
| 1374 | |||
| 1375 | PiFaceDigital.prototype.readAll = function(callback) { | ||
| 1376 | var name = this.name; | ||
| 1377 | $.get(this.url+ "/*", function(data) { | ||
| 1378 | callback(name, data); | ||
| 1379 | }); | ||
| 1380 | } | ||
| 1381 | |||
| 1382 | PiFaceDigital.prototype.refreshUI = function() { | ||
| 1383 | var port = this; | ||
| 1384 | var element = this.element; | ||
| 1385 | if ((element != undefined) && (element.header == undefined)) { | ||
| 1386 | element.header = $("<h3>" + this + "</h3>"); | ||
| 1387 | element.append(element.header); | ||
| 1388 | } | ||
| 1389 | |||
| 1390 | if ((element != undefined) && (element.table == undefined)) { | ||
| 1391 | element.header.text(this) | ||
| 1392 | element.table = $("<table>"); | ||
| 1393 | element.append(element.table); | ||
| 1394 | |||
| 1395 | var line = $("<tr>"); | ||
| 1396 | line.append($("<td><b>Outputs</b></td>")) | ||
| 1397 | for (var i = 7; i>=0; i--) { | ||
| 1398 | var cell = $("<td>"); | ||
| 1399 | var button = webiopi().createButton(this.name + "_output_" + i, i, function() { | ||
| 1400 | if ($("#" + port.name + "_output_" + $(this).attr("channel")).attr("class") == "LOW") { | ||
| 1401 | value = 1; | ||
| 1402 | } | ||
| 1403 | else { | ||
| 1404 | value = 0; | ||
| 1405 | } | ||
| 1406 | port.output($(this).attr("channel"), value, function(name, channel, data) { | ||
| 1407 | var button = $("#" + name + "_output_" + channel); | ||
| 1408 | if (data == "1") { | ||
| 1409 | button.attr("class", "HIGH") | ||
| 1410 | } | ||
| 1411 | else { | ||
| 1412 | button.attr("class", "LOW") | ||
| 1413 | } | ||
| 1414 | }); | ||
| 1415 | }); | ||
| 1416 | button.attr("channel", i); | ||
| 1417 | button.attr("class", "LOW"); | ||
| 1418 | cell.append(button); | ||
| 1419 | line.append(cell); | ||
| 1420 | } | ||
| 1421 | element.table.append(line); | ||
| 1422 | |||
| 1423 | line = $("<tr>"); | ||
| 1424 | line.append($("<td><b>Inputs</b></td>")) | ||
| 1425 | for (var i = 7; i>=0; i--) { | ||
| 1426 | var cell = $("<td>"); | ||
| 1427 | var button = webiopi().createButton(this.name + "_input_" + i, i, function() { | ||
| 1428 | }); | ||
| 1429 | button.attr("channel", i); | ||
| 1430 | button.attr("class", "LOW"); | ||
| 1431 | cell.append(button); | ||
| 1432 | line.append(cell); | ||
| 1433 | } | ||
| 1434 | element.table.append(line); | ||
| 1435 | |||
| 1436 | } | ||
| 1437 | |||
| 1438 | this.readAll(function(name, data) { | ||
| 1439 | for (i in data["input"]) { | ||
| 1440 | $("#" + name + "_input_" + i).attr("class", data["input"][i] == "1" ? "HIGH" : "LOW"); | ||
| 1441 | } | ||
| 1442 | for (i in data["output"]) { | ||
| 1443 | $("#" + name + "_output_" + i).attr("class", data["output"][i] == "1" ? "HIGH" : "LOW"); | ||
| 1444 | } | ||
| 1445 | setTimeout(function(){port.refreshUI()}, port.refreshTime); | ||
| 1446 | }); | ||
| 1447 | } | ||
| 1448 | |||
diff --git a/java/client/src/Test.java b/java/client/src/Test.java new file mode 100644 index 0000000..7165b17 --- /dev/null +++ b/java/client/src/Test.java | |||
| @@ -0,0 +1,52 @@ | |||
| 1 | import com.trouch.webiopi.client.PiClient; | ||
| 2 | import com.trouch.webiopi.client.PiCoapClient; | ||
| 3 | import com.trouch.webiopi.client.PiHttpClient; | ||
| 4 | import com.trouch.webiopi.client.PiMixedClient; | ||
| 5 | import com.trouch.webiopi.client.PiMulticastClient; | ||
| 6 | import com.trouch.webiopi.client.devices.analog.ADC; | ||
| 7 | import com.trouch.webiopi.client.devices.analog.DAC; | ||
| 8 | import com.trouch.webiopi.client.devices.analog.PWM; | ||
| 9 | import com.trouch.webiopi.client.devices.digital.GPIO; | ||
| 10 | import com.trouch.webiopi.client.devices.digital.NativeGPIO; | ||
| 11 | import com.trouch.webiopi.client.devices.sensor.Temperature; | ||
| 12 | |||
| 13 | public class Test { | ||
| 14 | |||
| 15 | public static void main(String[] args) { | ||
| 16 | String host = "192.168.1.234"; | ||
| 17 | PiClient client = new PiHttpClient(host, PiHttpClient.DEFAULT_PORT); | ||
| 18 | // PiClient client = new PiCoapClient(host, PiCoapClient.DEFAULT_PORT); | ||
| 19 | // PiClient client = new PiMixedClient(host, PiHttpClient.DEFAULT_PORT, PiCoapClient.DEFAULT_PORT); | ||
| 20 | // PiClient client = new PiMulticastClient(PiMulticastClient.DEFAULT_PORT); | ||
| 21 | |||
| 22 | client.setCredentials("webiopi", "raspberry"); | ||
| 23 | |||
| 24 | Temperature temp0 = new Temperature(client, "temp0"); | ||
| 25 | System.out.println(temp0.getCelsius() + "°C"); | ||
| 26 | |||
| 27 | NativeGPIO gpio = new NativeGPIO(client); | ||
| 28 | GPIO gpio0 = new GPIO(client, "gpio0"); | ||
| 29 | GPIO gpio2 = new GPIO(client, "gpio2"); | ||
| 30 | |||
| 31 | gpio.setFunction(25, GPIO.OUT); | ||
| 32 | gpio0.setFunction(0, GPIO.OUT); | ||
| 33 | gpio2.setFunction(12, GPIO.OUT); | ||
| 34 | |||
| 35 | DAC dac = new DAC(client, "dac1"); | ||
| 36 | ADC adc = new ADC(client, "adc0"); | ||
| 37 | PWM pwm = new PWM(client, "pwm0"); | ||
| 38 | |||
| 39 | boolean value = true; | ||
| 40 | for (int i = 0; i <= 100; i++) { | ||
| 41 | gpio.digitalWrite(25, value); | ||
| 42 | gpio0.digitalWrite(0, value); | ||
| 43 | gpio2.digitalWrite(12, value); | ||
| 44 | |||
| 45 | dac.writeFloat(0, (float) (i / 100.0)); | ||
| 46 | System.out.println("" + (adc.readFloat(1) * 3.3) + "V"); | ||
| 47 | pwm.writeAngle(7, i - 50); | ||
| 48 | value = !value; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/PiClient.java b/java/client/src/com/trouch/webiopi/client/PiClient.java new file mode 100644 index 0000000..96a4f63 --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/PiClient.java | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client; | ||
| 16 | |||
| 17 | import org.apache.commons.codec.binary.Base64; | ||
| 18 | |||
| 19 | public abstract class PiClient { | ||
| 20 | |||
| 21 | protected String urlBase; | ||
| 22 | protected String auth; | ||
| 23 | |||
| 24 | public static String encodeCredentials(String login, String password) { | ||
| 25 | return Base64.encodeBase64String((login + ":" + password).getBytes()); | ||
| 26 | } | ||
| 27 | |||
| 28 | public PiClient(String protocol, String host, int port) { | ||
| 29 | this.urlBase = protocol + "://" + host + ":" + port; | ||
| 30 | } | ||
| 31 | |||
| 32 | public void setCredentials(String login, String password) { | ||
| 33 | this.auth = "Basic " + encodeCredentials(login, password); | ||
| 34 | } | ||
| 35 | |||
| 36 | public abstract String sendRequest(String method, String path) throws Exception; | ||
| 37 | |||
| 38 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/PiCoapClient.java b/java/client/src/com/trouch/webiopi/client/PiCoapClient.java new file mode 100644 index 0000000..4a4f2d3 --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/PiCoapClient.java | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client; | ||
| 16 | |||
| 17 | import com.trouch.coap.client.CoapClient; | ||
| 18 | import com.trouch.coap.messages.CoapRequest; | ||
| 19 | import com.trouch.coap.messages.CoapResponse; | ||
| 20 | import com.trouch.coap.methods.CoapGet; | ||
| 21 | import com.trouch.coap.methods.CoapPost; | ||
| 22 | |||
| 23 | |||
| 24 | public class PiCoapClient extends PiClient { | ||
| 25 | public final static int DEFAULT_PORT = 5683; | ||
| 26 | private CoapClient client; | ||
| 27 | |||
| 28 | public PiCoapClient(String host) { | ||
| 29 | super("coap", host, DEFAULT_PORT); | ||
| 30 | client = new CoapClient(); | ||
| 31 | } | ||
| 32 | |||
| 33 | public PiCoapClient(String host, int port) { | ||
| 34 | super("coap", host, port); | ||
| 35 | client = new CoapClient(); | ||
| 36 | } | ||
| 37 | |||
| 38 | @Override | ||
| 39 | public String sendRequest(String method, String path) throws Exception { | ||
| 40 | CoapRequest request; | ||
| 41 | if (method == "GET") { | ||
| 42 | request = new CoapGet(this.urlBase + path); | ||
| 43 | } | ||
| 44 | else if (method == "POST") { | ||
| 45 | request = new CoapPost(this.urlBase + path); | ||
| 46 | } | ||
| 47 | else throw new Exception("Method not supported: " + method); | ||
| 48 | |||
| 49 | CoapResponse response = client.sendRequest(request); | ||
| 50 | if (response != null) { | ||
| 51 | return response.getPayload(); | ||
| 52 | } | ||
| 53 | |||
| 54 | return null; | ||
| 55 | } | ||
| 56 | |||
| 57 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/PiHttpClient.java b/java/client/src/com/trouch/webiopi/client/PiHttpClient.java new file mode 100644 index 0000000..6562bc7 --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/PiHttpClient.java | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client; | ||
| 16 | |||
| 17 | import java.io.BufferedReader; | ||
| 18 | import java.io.IOException; | ||
| 19 | import java.io.InputStreamReader; | ||
| 20 | import java.net.HttpURLConnection; | ||
| 21 | import java.net.URL; | ||
| 22 | |||
| 23 | public class PiHttpClient extends PiClient { | ||
| 24 | public final static int DEFAULT_PORT = 8000; | ||
| 25 | |||
| 26 | public PiHttpClient(String host) { | ||
| 27 | super("http", host, DEFAULT_PORT); | ||
| 28 | } | ||
| 29 | |||
| 30 | public PiHttpClient(String host, int port) { | ||
| 31 | super("http", host, port); | ||
| 32 | } | ||
| 33 | |||
| 34 | @Override | ||
| 35 | public String sendRequest(String method, String path) throws Exception { | ||
| 36 | BufferedReader reader = null; | ||
| 37 | try { | ||
| 38 | URL url = new URL(this.urlBase + path); | ||
| 39 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); | ||
| 40 | connection.setRequestMethod(method); | ||
| 41 | if (this.auth != null) { | ||
| 42 | connection.setRequestProperty("Authorization", this.auth); | ||
| 43 | } | ||
| 44 | |||
| 45 | // read the output from the server | ||
| 46 | reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); | ||
| 47 | StringBuilder stringBuilder = new StringBuilder(); | ||
| 48 | |||
| 49 | String line = null; | ||
| 50 | while ((line = reader.readLine()) != null) { | ||
| 51 | stringBuilder.append(line).append('\n'); | ||
| 52 | } | ||
| 53 | return stringBuilder.toString(); | ||
| 54 | } catch (Exception e) { | ||
| 55 | e.printStackTrace(); | ||
| 56 | throw e; | ||
| 57 | } finally { | ||
| 58 | if (reader != null) { | ||
| 59 | try { | ||
| 60 | reader.close(); | ||
| 61 | } catch (IOException ioe) { | ||
| 62 | ioe.printStackTrace(); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | } | ||
| 66 | } | ||
| 67 | |||
| 68 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/PiMixedClient.java b/java/client/src/com/trouch/webiopi/client/PiMixedClient.java new file mode 100644 index 0000000..8922f6f --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/PiMixedClient.java | |||
| @@ -0,0 +1,56 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client; | ||
| 16 | |||
| 17 | public class PiMixedClient extends PiClient { | ||
| 18 | private PiHttpClient http; | ||
| 19 | private PiCoapClient coap; | ||
| 20 | |||
| 21 | private int tries = 0; | ||
| 22 | private int maxTries = 3; | ||
| 23 | |||
| 24 | public PiMixedClient(String host) { | ||
| 25 | super("", "", 0); | ||
| 26 | http = new PiHttpClient(host); | ||
| 27 | coap = new PiCoapClient(host); | ||
| 28 | } | ||
| 29 | |||
| 30 | public PiMixedClient(String host, int httpPort, int coapPort) { | ||
| 31 | super("", "", 0); | ||
| 32 | http = new PiHttpClient(host, httpPort); | ||
| 33 | coap = new PiCoapClient(host, coapPort); | ||
| 34 | } | ||
| 35 | |||
| 36 | @Override | ||
| 37 | public void setCredentials(String login, String password) { | ||
| 38 | http.setCredentials(login, password); | ||
| 39 | coap.setCredentials(login, password); | ||
| 40 | } | ||
| 41 | |||
| 42 | @Override | ||
| 43 | public String sendRequest(String method, String path) throws Exception { | ||
| 44 | if (tries < maxTries) { | ||
| 45 | String response = coap.sendRequest(method, path); | ||
| 46 | if (response != null) { | ||
| 47 | tries = 0; | ||
| 48 | return response; | ||
| 49 | } | ||
| 50 | tries++; | ||
| 51 | } | ||
| 52 | |||
| 53 | return http.sendRequest(method, path); | ||
| 54 | } | ||
| 55 | |||
| 56 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/PiMulticastClient.java b/java/client/src/com/trouch/webiopi/client/PiMulticastClient.java new file mode 100644 index 0000000..22b1d9e --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/PiMulticastClient.java | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client; | ||
| 16 | |||
| 17 | public class PiMulticastClient extends PiCoapClient { | ||
| 18 | |||
| 19 | public PiMulticastClient() { | ||
| 20 | super("224.0.1.123", PiMulticastClient.DEFAULT_PORT); | ||
| 21 | } | ||
| 22 | |||
| 23 | public PiMulticastClient(int port) { | ||
| 24 | super("224.0.1.123", port); | ||
| 25 | } | ||
| 26 | |||
| 27 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/devices/Device.java b/java/client/src/com/trouch/webiopi/client/devices/Device.java new file mode 100644 index 0000000..b12139d --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/devices/Device.java | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client.devices; | ||
| 16 | |||
| 17 | import com.trouch.webiopi.client.PiClient; | ||
| 18 | |||
| 19 | public class Device { | ||
| 20 | |||
| 21 | private PiClient client; | ||
| 22 | protected String path; | ||
| 23 | |||
| 24 | public Device(PiClient client, String deviceName, String type) { | ||
| 25 | this.client = client; | ||
| 26 | if (type != null) { | ||
| 27 | this.path = "/devices/" + deviceName + "/" + type; | ||
| 28 | } | ||
| 29 | else { | ||
| 30 | this.path = "/devices/" + deviceName; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | |||
| 34 | public String sendRequest(String method, String subPath) { | ||
| 35 | try { | ||
| 36 | return this.client.sendRequest(method, this.path + subPath); | ||
| 37 | } catch (Exception e) { | ||
| 38 | // TODO Auto-generated catch block | ||
| 39 | e.printStackTrace(); | ||
| 40 | return null; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/devices/analog/ADC.java b/java/client/src/com/trouch/webiopi/client/devices/analog/ADC.java new file mode 100644 index 0000000..f8c349e --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/devices/analog/ADC.java | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client.devices.analog; | ||
| 16 | |||
| 17 | import com.trouch.webiopi.client.PiClient; | ||
| 18 | import com.trouch.webiopi.client.devices.Device; | ||
| 19 | |||
| 20 | public class ADC extends Device { | ||
| 21 | |||
| 22 | public ADC(PiClient client, String deviceName) { | ||
| 23 | super(client, deviceName, "analog"); | ||
| 24 | } | ||
| 25 | |||
| 26 | public ADC(PiClient client, String deviceName, String type) { | ||
| 27 | super(client, deviceName, type); | ||
| 28 | } | ||
| 29 | |||
| 30 | public float readFloat(int channel) { | ||
| 31 | return Float.parseFloat(this.sendRequest("GET", "/" + channel + "/float")); | ||
| 32 | } | ||
| 33 | |||
| 34 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/devices/analog/DAC.java b/java/client/src/com/trouch/webiopi/client/devices/analog/DAC.java new file mode 100644 index 0000000..901d9c4 --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/devices/analog/DAC.java | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client.devices.analog; | ||
| 16 | |||
| 17 | import com.trouch.webiopi.client.PiClient; | ||
| 18 | |||
| 19 | public class DAC extends ADC { | ||
| 20 | |||
| 21 | public DAC(PiClient client, String deviceName) { | ||
| 22 | super(client, deviceName); | ||
| 23 | } | ||
| 24 | |||
| 25 | public DAC(PiClient client, String deviceName, String type) { | ||
| 26 | super(client, deviceName, type); | ||
| 27 | } | ||
| 28 | |||
| 29 | public float writeFloat(int channel, float value) { | ||
| 30 | return Float.parseFloat(this.sendRequest("POST", "/" + channel + "/float/" + value)); | ||
| 31 | } | ||
| 32 | |||
| 33 | public float writeVolt(int channel, float value) { | ||
| 34 | return Float.parseFloat(this.sendRequest("POST", "/" + channel + "/volt/" + value)); | ||
| 35 | } | ||
| 36 | |||
| 37 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/devices/analog/PWM.java b/java/client/src/com/trouch/webiopi/client/devices/analog/PWM.java new file mode 100644 index 0000000..fdc4674 --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/devices/analog/PWM.java | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client.devices.analog; | ||
| 16 | |||
| 17 | import com.trouch.webiopi.client.PiClient; | ||
| 18 | |||
| 19 | public class PWM extends DAC { | ||
| 20 | |||
| 21 | public PWM(PiClient client, String deviceName) { | ||
| 22 | super(client, deviceName, "pwm"); | ||
| 23 | } | ||
| 24 | |||
| 25 | public float readAngle(int channel) { | ||
| 26 | return Float.parseFloat(this.sendRequest("GET", "/" + channel + "/angle")); | ||
| 27 | } | ||
| 28 | |||
| 29 | public float writeAngle(int channel, float angle) { | ||
| 30 | return Float.parseFloat(this.sendRequest("POST", "/" + channel + "/angle/" + angle)); | ||
| 31 | } | ||
| 32 | |||
| 33 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/devices/digital/GPIO.java b/java/client/src/com/trouch/webiopi/client/devices/digital/GPIO.java new file mode 100644 index 0000000..c08dff5 --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/devices/digital/GPIO.java | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client.devices.digital; | ||
| 16 | |||
| 17 | import com.trouch.webiopi.client.PiClient; | ||
| 18 | import com.trouch.webiopi.client.devices.Device; | ||
| 19 | |||
| 20 | public class GPIO extends Device { | ||
| 21 | |||
| 22 | public final static String OUT = "OUT"; | ||
| 23 | public final static String IN = "IN"; | ||
| 24 | |||
| 25 | public GPIO(PiClient client, String deviceName) { | ||
| 26 | super(client, deviceName, null); | ||
| 27 | } | ||
| 28 | |||
| 29 | public String getFunction(int channel) { | ||
| 30 | return this.sendRequest("GET", "/" + channel + "/function"); | ||
| 31 | } | ||
| 32 | |||
| 33 | public String setFunction(int channel, String function) { | ||
| 34 | return this.sendRequest("POST", "/" + channel + "/function/" + function); | ||
| 35 | } | ||
| 36 | |||
| 37 | public boolean digitalRead(int channel) { | ||
| 38 | String res = this.sendRequest("GET", "/" + channel + "/value"); | ||
| 39 | if (res.equals("1")) { | ||
| 40 | return true; | ||
| 41 | } | ||
| 42 | return false; | ||
| 43 | } | ||
| 44 | |||
| 45 | public boolean digitalWrite(int channel, boolean value) { | ||
| 46 | String res = this.sendRequest("POST", "/" + channel + "/value/" + (value ? "1" : "0")); | ||
| 47 | if (res.equals("1")) { | ||
| 48 | return true; | ||
| 49 | } | ||
| 50 | return false; | ||
| 51 | } | ||
| 52 | |||
| 53 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/devices/digital/NativeGPIO.java b/java/client/src/com/trouch/webiopi/client/devices/digital/NativeGPIO.java new file mode 100644 index 0000000..a5bc05b --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/devices/digital/NativeGPIO.java | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client.devices.digital; | ||
| 16 | |||
| 17 | import com.trouch.webiopi.client.PiClient; | ||
| 18 | |||
| 19 | public class NativeGPIO extends GPIO { | ||
| 20 | |||
| 21 | public NativeGPIO(PiClient client) { | ||
| 22 | super(client, ""); | ||
| 23 | this.path = "/GPIO"; | ||
| 24 | } | ||
| 25 | |||
| 26 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/devices/sensor/Distance.java b/java/client/src/com/trouch/webiopi/client/devices/sensor/Distance.java new file mode 100644 index 0000000..63ac485 --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/devices/sensor/Distance.java | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client.devices.sensor; | ||
| 16 | |||
| 17 | import com.trouch.webiopi.client.PiClient; | ||
| 18 | import com.trouch.webiopi.client.devices.Device; | ||
| 19 | |||
| 20 | public class Distance extends Device { | ||
| 21 | |||
| 22 | public Distance(PiClient client, String deviceName) { | ||
| 23 | super(client, deviceName, "sensor"); | ||
| 24 | } | ||
| 25 | |||
| 26 | public float getMillimeter() { | ||
| 27 | return Float.parseFloat(this.sendRequest("GET", "/distance/mm")); | ||
| 28 | } | ||
| 29 | |||
| 30 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/devices/sensor/Luminosity.java b/java/client/src/com/trouch/webiopi/client/devices/sensor/Luminosity.java new file mode 100644 index 0000000..29eda32 --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/devices/sensor/Luminosity.java | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client.devices.sensor; | ||
| 16 | |||
| 17 | import com.trouch.webiopi.client.PiClient; | ||
| 18 | import com.trouch.webiopi.client.devices.Device; | ||
| 19 | |||
| 20 | public class Luminosity extends Device { | ||
| 21 | |||
| 22 | public Luminosity(PiClient client, String deviceName) { | ||
| 23 | super(client, deviceName, "sensor"); | ||
| 24 | } | ||
| 25 | |||
| 26 | public float getLux() { | ||
| 27 | return Float.parseFloat(this.sendRequest("GET", "/luminosity/lx")); | ||
| 28 | } | ||
| 29 | |||
| 30 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/devices/sensor/Pressure.java b/java/client/src/com/trouch/webiopi/client/devices/sensor/Pressure.java new file mode 100644 index 0000000..1f766a9 --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/devices/sensor/Pressure.java | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client.devices.sensor; | ||
| 16 | |||
| 17 | import com.trouch.webiopi.client.PiClient; | ||
| 18 | import com.trouch.webiopi.client.devices.Device; | ||
| 19 | |||
| 20 | public class Pressure extends Device { | ||
| 21 | |||
| 22 | public Pressure(PiClient client, String deviceName) { | ||
| 23 | super(client, deviceName, "sensor"); | ||
| 24 | } | ||
| 25 | |||
| 26 | public float getHectoPascal() { | ||
| 27 | return Float.parseFloat(this.sendRequest("GET", "/pressure/hpa")); | ||
| 28 | } | ||
| 29 | |||
| 30 | public float getPascal() { | ||
| 31 | return Float.parseFloat(this.sendRequest("GET", "/pressure/pa")); | ||
| 32 | } | ||
| 33 | |||
| 34 | } | ||
diff --git a/java/client/src/com/trouch/webiopi/client/devices/sensor/Temperature.java b/java/client/src/com/trouch/webiopi/client/devices/sensor/Temperature.java new file mode 100644 index 0000000..160e32f --- /dev/null +++ b/java/client/src/com/trouch/webiopi/client/devices/sensor/Temperature.java | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | /* Copyright 2013 Eric Ptak - trouch.com | ||
| 2 | * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 3 | * you may not use this file except in compliance with the License. | ||
| 4 | * You may obtain a copy of the License at | ||
| 5 | * | ||
| 6 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 7 | * | ||
| 8 | * Unless required by applicable law or agreed to in writing, software | ||
| 9 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 10 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 11 | * See the License for the specific language governing permissions and | ||
| 12 | * limitations under the License. | ||
| 13 | */ | ||
| 14 | |||
| 15 | package com.trouch.webiopi.client.devices.sensor; | ||
| 16 | |||
| 17 | import com.trouch.webiopi.client.PiClient; | ||
| 18 | import com.trouch.webiopi.client.devices.Device; | ||
| 19 | |||
| 20 | public class Temperature extends Device { | ||
| 21 | |||
| 22 | public Temperature(PiClient client, String deviceName) { | ||
| 23 | super(client, deviceName, "sensor"); | ||
| 24 | } | ||
| 25 | |||
| 26 | public float getCelsius() { | ||
| 27 | return Float.parseFloat(this.sendRequest("GET", "/temperature/c")); | ||
| 28 | } | ||
| 29 | |||
| 30 | public float getFahrenheit() { | ||
| 31 | return Float.parseFloat(this.sendRequest("GET", "/temperature/f")); | ||
| 32 | } | ||
| 33 | |||
| 34 | } | ||
diff --git a/java/client/src/org/apache/commons/codec/BinaryDecoder.java b/java/client/src/org/apache/commons/codec/BinaryDecoder.java new file mode 100644 index 0000000..546ff76 --- /dev/null +++ b/java/client/src/org/apache/commons/codec/BinaryDecoder.java | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | /* | ||
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| 3 | * contributor license agreements. See the NOTICE file distributed with | ||
| 4 | * this work for additional information regarding copyright ownership. | ||
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| 6 | * (the "License"); you may not use this file except in compliance with | ||
| 7 | * the License. You may obtain a copy of the License at | ||
| 8 | * | ||
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | * | ||
| 11 | * Unless required by applicable law or agreed to in writing, software | ||
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | * See the License for the specific language governing permissions and | ||
| 15 | * limitations under the License. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package org.apache.commons.codec; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Defines common decoding methods for byte array decoders. | ||
| 22 | * | ||
| 23 | * @version $Id$ | ||
| 24 | */ | ||
| 25 | public interface BinaryDecoder extends Decoder { | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Decodes a byte array and returns the results as a byte array. | ||
| 29 | * | ||
| 30 | * @param source | ||
| 31 | * A byte array which has been encoded with the appropriate encoder | ||
| 32 | * @return a byte array that contains decoded content | ||
| 33 | * @throws DecoderException | ||
| 34 | * A decoder exception is thrown if a Decoder encounters a failure condition during the decode process. | ||
| 35 | */ | ||
| 36 | byte[] decode(byte[] source) throws DecoderException; | ||
| 37 | } | ||
| 38 | |||
diff --git a/java/client/src/org/apache/commons/codec/BinaryEncoder.java b/java/client/src/org/apache/commons/codec/BinaryEncoder.java new file mode 100644 index 0000000..65f92cc --- /dev/null +++ b/java/client/src/org/apache/commons/codec/BinaryEncoder.java | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | /* | ||
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| 3 | * contributor license agreements. See the NOTICE file distributed with | ||
| 4 | * this work for additional information regarding copyright ownership. | ||
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| 6 | * (the "License"); you may not use this file except in compliance with | ||
| 7 | * the License. You may obtain a copy of the License at | ||
| 8 | * | ||
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | * | ||
| 11 | * Unless required by applicable law or agreed to in writing, software | ||
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | * See the License for the specific language governing permissions and | ||
| 15 | * limitations under the License. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package org.apache.commons.codec; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Defines common encoding methods for byte array encoders. | ||
| 22 | * | ||
| 23 | * @version $Id$ | ||
| 24 | */ | ||
| 25 | public interface BinaryEncoder extends Encoder { | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Encodes a byte array and return the encoded data as a byte array. | ||
| 29 | * | ||
| 30 | * @param source | ||
| 31 | * Data to be encoded | ||
| 32 | * @return A byte array containing the encoded data | ||
| 33 | * @throws EncoderException | ||
| 34 | * thrown if the Encoder encounters a failure condition during the encoding process. | ||
| 35 | */ | ||
| 36 | byte[] encode(byte[] source) throws EncoderException; | ||
| 37 | } | ||
| 38 | |||
diff --git a/java/client/src/org/apache/commons/codec/CharEncoding.java b/java/client/src/org/apache/commons/codec/CharEncoding.java new file mode 100644 index 0000000..2cfafa6 --- /dev/null +++ b/java/client/src/org/apache/commons/codec/CharEncoding.java | |||
| @@ -0,0 +1,113 @@ | |||
| 1 | /* | ||
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| 3 | * contributor license agreements. See the NOTICE file distributed with | ||
| 4 | * this work for additional information regarding copyright ownership. | ||
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| 6 | * (the "License"); you may not use this file except in compliance with | ||
| 7 | * the License. You may obtain a copy of the License at | ||
| 8 | * | ||
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | * | ||
| 11 | * Unless required by applicable law or agreed to in writing, software | ||
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | * See the License for the specific language governing permissions and | ||
| 15 | * limitations under the License. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package org.apache.commons.codec; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Character encoding names required of every implementation of the Java platform. | ||
| 22 | * | ||
| 23 | * From the Java documentation <a | ||
| 24 | * href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a>: | ||
| 25 | * <p> | ||
| 26 | * <cite>Every implementation of the Java platform is required to support the following character encodings. Consult the | ||
| 27 | * release documentation for your implementation to see if any other encodings are supported. Consult the release | ||
| 28 | * documentation for your implementation to see if any other encodings are supported.</cite> | ||
| 29 | * </p> | ||
| 30 | * | ||
| 31 | * <ul> | ||
| 32 | * <li><code>US-ASCII</code><br/> | ||
| 33 | * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.</li> | ||
| 34 | * <li><code>ISO-8859-1</code><br/> | ||
| 35 | * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.</li> | ||
| 36 | * <li><code>UTF-8</code><br/> | ||
| 37 | * Eight-bit Unicode Transformation Format.</li> | ||
| 38 | * <li><code>UTF-16BE</code><br/> | ||
| 39 | * Sixteen-bit Unicode Transformation Format, big-endian byte order.</li> | ||
| 40 | * <li><code>UTF-16LE</code><br/> | ||
| 41 | * Sixteen-bit Unicode Transformation Format, little-endian byte order.</li> | ||
| 42 | * <li><code>UTF-16</code><br/> | ||
| 43 | * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order | ||
| 44 | * accepted on input, big-endian used on output.)</li> | ||
| 45 | * </ul> | ||
| 46 | * | ||
| 47 | * This perhaps would best belong in the [lang] project. Even if a similar interface is defined in [lang], it is not | ||
| 48 | * foreseen that [codec] would be made to depend on [lang]. | ||
| 49 | * | ||
| 50 | * <p> | ||
| 51 | * This class is immutable and thread-safe. | ||
| 52 | * </p> | ||
| 53 | * | ||
| 54 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 55 | * @since 1.4 | ||
| 56 | * @version $Id$ | ||
| 57 | */ | ||
| 58 | public class CharEncoding { | ||
| 59 | /** | ||
| 60 | * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. | ||
| 61 | * <p> | ||
| 62 | * Every implementation of the Java platform is required to support this character encoding. | ||
| 63 | * | ||
| 64 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 65 | */ | ||
| 66 | public static final String ISO_8859_1 = "ISO-8859-1"; | ||
| 67 | |||
| 68 | /** | ||
| 69 | * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set. | ||
| 70 | * <p> | ||
| 71 | * Every implementation of the Java platform is required to support this character encoding. | ||
| 72 | * | ||
| 73 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 74 | */ | ||
| 75 | public static final String US_ASCII = "US-ASCII"; | ||
| 76 | |||
| 77 | /** | ||
| 78 | * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial byte-order mark | ||
| 79 | * (either order accepted on input, big-endian used on output) | ||
| 80 | * <p> | ||
| 81 | * Every implementation of the Java platform is required to support this character encoding. | ||
| 82 | * | ||
| 83 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 84 | */ | ||
| 85 | public static final String UTF_16 = "UTF-16"; | ||
| 86 | |||
| 87 | /** | ||
| 88 | * Sixteen-bit Unicode Transformation Format, big-endian byte order. | ||
| 89 | * <p> | ||
| 90 | * Every implementation of the Java platform is required to support this character encoding. | ||
| 91 | * | ||
| 92 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 93 | */ | ||
| 94 | public static final String UTF_16BE = "UTF-16BE"; | ||
| 95 | |||
| 96 | /** | ||
| 97 | * Sixteen-bit Unicode Transformation Format, little-endian byte order. | ||
| 98 | * <p> | ||
| 99 | * Every implementation of the Java platform is required to support this character encoding. | ||
| 100 | * | ||
| 101 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 102 | */ | ||
| 103 | public static final String UTF_16LE = "UTF-16LE"; | ||
| 104 | |||
| 105 | /** | ||
| 106 | * Eight-bit Unicode Transformation Format. | ||
| 107 | * <p> | ||
| 108 | * Every implementation of the Java platform is required to support this character encoding. | ||
| 109 | * | ||
| 110 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 111 | */ | ||
| 112 | public static final String UTF_8 = "UTF-8"; | ||
| 113 | } | ||
diff --git a/java/client/src/org/apache/commons/codec/Charsets.java b/java/client/src/org/apache/commons/codec/Charsets.java new file mode 100644 index 0000000..73fbc79 --- /dev/null +++ b/java/client/src/org/apache/commons/codec/Charsets.java | |||
| @@ -0,0 +1,144 @@ | |||
| 1 | /* | ||
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| 3 | * contributor license agreements. See the NOTICE file distributed with | ||
| 4 | * this work for additional information regarding copyright ownership. | ||
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| 6 | * (the "License"); you may not use this file except in compliance with | ||
| 7 | * the License. You may obtain a copy of the License at | ||
| 8 | * | ||
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | * | ||
| 11 | * Unless required by applicable law or agreed to in writing, software | ||
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | * See the License for the specific language governing permissions and | ||
| 15 | * limitations under the License. | ||
| 16 | */ | ||
| 17 | package org.apache.commons.codec; | ||
| 18 | |||
| 19 | import java.nio.charset.Charset; | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Charsets required of every implementation of the Java platform. | ||
| 23 | * | ||
| 24 | * From the Java documentation <a href="http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard | ||
| 25 | * charsets</a>: | ||
| 26 | * <p> | ||
| 27 | * <cite>Every implementation of the Java platform is required to support the following character encodings. Consult the | ||
| 28 | * release documentation for your implementation to see if any other encodings are supported. Consult the release | ||
| 29 | * documentation for your implementation to see if any other encodings are supported. </cite> | ||
| 30 | * </p> | ||
| 31 | * | ||
| 32 | * <ul> | ||
| 33 | * <li><code>US-ASCII</code><br/> | ||
| 34 | * Seven-bit ASCII, a.k.a. ISO646-US, a.k.a. the Basic Latin block of the Unicode character set.</li> | ||
| 35 | * <li><code>ISO-8859-1</code><br/> | ||
| 36 | * ISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1.</li> | ||
| 37 | * <li><code>UTF-8</code><br/> | ||
| 38 | * Eight-bit Unicode Transformation Format.</li> | ||
| 39 | * <li><code>UTF-16BE</code><br/> | ||
| 40 | * Sixteen-bit Unicode Transformation Format, big-endian byte order.</li> | ||
| 41 | * <li><code>UTF-16LE</code><br/> | ||
| 42 | * Sixteen-bit Unicode Transformation Format, little-endian byte order.</li> | ||
| 43 | * <li><code>UTF-16</code><br/> | ||
| 44 | * Sixteen-bit Unicode Transformation Format, byte order specified by a mandatory initial byte-order mark (either order | ||
| 45 | * accepted on input, big-endian used on output.)</li> | ||
| 46 | * </ul> | ||
| 47 | * | ||
| 48 | * This perhaps would best belong in the Commons Lang project. Even if a similar class is defined in Commons Lang, it is | ||
| 49 | * not foreseen that Commons Codec would be made to depend on Commons Lang. | ||
| 50 | * | ||
| 51 | * <p> | ||
| 52 | * This class is immutable and thread-safe. | ||
| 53 | * </p> | ||
| 54 | * | ||
| 55 | * @see <a href="http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 56 | * @since 1.7 | ||
| 57 | * @version $Id: CharEncoding.java 1173287 2011-09-20 18:16:19Z ggregory $ | ||
| 58 | */ | ||
| 59 | public class Charsets { | ||
| 60 | |||
| 61 | // | ||
| 62 | // This class should only contain Charset instances for required encodings. This guarantees that it will load | ||
| 63 | // correctly and without delay on all Java platforms. | ||
| 64 | // | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Returns the given Charset or the default Charset if the given Charset is null. | ||
| 68 | * | ||
| 69 | * @param charset | ||
| 70 | * A charset or null. | ||
| 71 | * @return the given Charset or the default Charset if the given Charset is null | ||
| 72 | */ | ||
| 73 | public static Charset toCharset(final Charset charset) { | ||
| 74 | return charset == null ? Charset.defaultCharset() : charset; | ||
| 75 | } | ||
| 76 | |||
| 77 | /** | ||
| 78 | * Returns a Charset for the named charset. If the name is null, return the default Charset. | ||
| 79 | * | ||
| 80 | * @param charset | ||
| 81 | * The name of the requested charset, may be null. | ||
| 82 | * @return a Charset for the named charset | ||
| 83 | * @throws java.nio.charset.UnsupportedCharsetException | ||
| 84 | * If the named charset is unavailable | ||
| 85 | */ | ||
| 86 | public static Charset toCharset(final String charset) { | ||
| 87 | return charset == null ? Charset.defaultCharset() : Charset.forName(charset); | ||
| 88 | } | ||
| 89 | |||
| 90 | /** | ||
| 91 | * CharEncodingISO Latin Alphabet No. 1, a.k.a. ISO-LATIN-1. | ||
| 92 | * <p> | ||
| 93 | * Every implementation of the Java platform is required to support this character encoding. | ||
| 94 | * | ||
| 95 | * @see <a href="http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 96 | */ | ||
| 97 | public static final Charset ISO_8859_1 = Charset.forName(CharEncoding.ISO_8859_1); | ||
| 98 | |||
| 99 | /** | ||
| 100 | * Seven-bit ASCII, also known as ISO646-US, also known as the Basic Latin block of the Unicode character set. | ||
| 101 | * <p> | ||
| 102 | * Every implementation of the Java platform is required to support this character encoding. | ||
| 103 | * | ||
| 104 | * @see <a href="http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 105 | */ | ||
| 106 | public static final Charset US_ASCII = Charset.forName(CharEncoding.US_ASCII); | ||
| 107 | |||
| 108 | /** | ||
| 109 | * Sixteen-bit Unicode Transformation Format, The byte order specified by a mandatory initial byte-order mark | ||
| 110 | * (either order accepted on input, big-endian used on output) | ||
| 111 | * <p> | ||
| 112 | * Every implementation of the Java platform is required to support this character encoding. | ||
| 113 | * | ||
| 114 | * @see <a href="http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 115 | */ | ||
| 116 | public static final Charset UTF_16 = Charset.forName(CharEncoding.UTF_16); | ||
| 117 | |||
| 118 | /** | ||
| 119 | * Sixteen-bit Unicode Transformation Format, big-endian byte order. | ||
| 120 | * <p> | ||
| 121 | * Every implementation of the Java platform is required to support this character encoding. | ||
| 122 | * | ||
| 123 | * @see <a href="http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 124 | */ | ||
| 125 | public static final Charset UTF_16BE = Charset.forName(CharEncoding.UTF_16BE); | ||
| 126 | |||
| 127 | /** | ||
| 128 | * Sixteen-bit Unicode Transformation Format, little-endian byte order. | ||
| 129 | * <p> | ||
| 130 | * Every implementation of the Java platform is required to support this character encoding. | ||
| 131 | * | ||
| 132 | * @see <a href="http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 133 | */ | ||
| 134 | public static final Charset UTF_16LE = Charset.forName(CharEncoding.UTF_16LE); | ||
| 135 | |||
| 136 | /** | ||
| 137 | * Eight-bit Unicode Transformation Format. | ||
| 138 | * <p> | ||
| 139 | * Every implementation of the Java platform is required to support this character encoding. | ||
| 140 | * | ||
| 141 | * @see <a href="http://docs.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 142 | */ | ||
| 143 | public static final Charset UTF_8 = Charset.forName(CharEncoding.UTF_8); | ||
| 144 | } | ||
diff --git a/java/client/src/org/apache/commons/codec/Decoder.java b/java/client/src/org/apache/commons/codec/Decoder.java new file mode 100644 index 0000000..9f3ba60 --- /dev/null +++ b/java/client/src/org/apache/commons/codec/Decoder.java | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | /* | ||
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| 3 | * contributor license agreements. See the NOTICE file distributed with | ||
| 4 | * this work for additional information regarding copyright ownership. | ||
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| 6 | * (the "License"); you may not use this file except in compliance with | ||
| 7 | * the License. You may obtain a copy of the License at | ||
| 8 | * | ||
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | * | ||
| 11 | * Unless required by applicable law or agreed to in writing, software | ||
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | * See the License for the specific language governing permissions and | ||
| 15 | * limitations under the License. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package org.apache.commons.codec; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Provides the highest level of abstraction for Decoders. | ||
| 22 | * <p> | ||
| 23 | * This is the sister interface of {@link Encoder}. All Decoders implement this common generic interface. | ||
| 24 | * Allows a user to pass a generic Object to any Decoder implementation in the codec package. | ||
| 25 | * <p> | ||
| 26 | * One of the two interfaces at the center of the codec package. | ||
| 27 | * | ||
| 28 | * @version $Id$ | ||
| 29 | */ | ||
| 30 | public interface Decoder { | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Decodes an "encoded" Object and returns a "decoded" Object. Note that the implementation of this interface will | ||
| 34 | * try to cast the Object parameter to the specific type expected by a particular Decoder implementation. If a | ||
| 35 | * {@link ClassCastException} occurs this decode method will throw a DecoderException. | ||
| 36 | * | ||
| 37 | * @param source | ||
| 38 | * the object to decode | ||
| 39 | * @return a 'decoded" object | ||
| 40 | * @throws DecoderException | ||
| 41 | * a decoder exception can be thrown for any number of reasons. Some good candidates are that the | ||
| 42 | * parameter passed to this method is null, a param cannot be cast to the appropriate type for a | ||
| 43 | * specific encoder. | ||
| 44 | */ | ||
| 45 | Object decode(Object source) throws DecoderException; | ||
| 46 | } | ||
| 47 | |||
diff --git a/java/client/src/org/apache/commons/codec/DecoderException.java b/java/client/src/org/apache/commons/codec/DecoderException.java new file mode 100644 index 0000000..52846a5 --- /dev/null +++ b/java/client/src/org/apache/commons/codec/DecoderException.java | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | /* | ||
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| 3 | * contributor license agreements. See the NOTICE file distributed with | ||
| 4 | * this work for additional information regarding copyright ownership. | ||
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| 6 | * (the "License"); you may not use this file except in compliance with | ||
| 7 | * the License. You may obtain a copy of the License at | ||
| 8 | * | ||
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | * | ||
| 11 | * Unless required by applicable law or agreed to in writing, software | ||
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | * See the License for the specific language governing permissions and | ||
| 15 | * limitations under the License. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package org.apache.commons.codec; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Thrown when there is a failure condition during the decoding process. This exception is thrown when a {@link Decoder} | ||
| 22 | * encounters a decoding specific exception such as invalid data, or characters outside of the expected range. | ||
| 23 | * | ||
| 24 | * @version $Id$ | ||
| 25 | */ | ||
| 26 | public class DecoderException extends Exception { | ||
| 27 | |||
| 28 | /** | ||
| 29 | * Declares the Serial Version Uid. | ||
| 30 | * | ||
| 31 | * @see <a href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always Declare Serial Version Uid</a> | ||
| 32 | */ | ||
| 33 | private static final long serialVersionUID = 1L; | ||
| 34 | |||
| 35 | /** | ||
| 36 | * Constructs a new exception with {@code null} as its detail message. The cause is not initialized, and may | ||
| 37 | * subsequently be initialized by a call to {@link #initCause}. | ||
| 38 | * | ||
| 39 | * @since 1.4 | ||
| 40 | */ | ||
| 41 | public DecoderException() { | ||
| 42 | super(); | ||
| 43 | } | ||
| 44 | |||
| 45 | /** | ||
| 46 | * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently | ||
| 47 | * be initialized by a call to {@link #initCause}. | ||
| 48 | * | ||
| 49 | * @param message | ||
| 50 | * The detail message which is saved for later retrieval by the {@link #getMessage()} method. | ||
| 51 | */ | ||
| 52 | public DecoderException(final String message) { | ||
| 53 | super(message); | ||
| 54 | } | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Constructs a new exception with the specified detail message and cause. | ||
| 58 | * <p> | ||
| 59 | * Note that the detail message associated with <code>cause</code> is not automatically incorporated into this | ||
| 60 | * exception's detail message. | ||
| 61 | * | ||
| 62 | * @param message | ||
| 63 | * The detail message which is saved for later retrieval by the {@link #getMessage()} method. | ||
| 64 | * @param cause | ||
| 65 | * The cause which is saved for later retrieval by the {@link #getCause()} method. A {@code null} | ||
| 66 | * value is permitted, and indicates that the cause is nonexistent or unknown. | ||
| 67 | * @since 1.4 | ||
| 68 | */ | ||
| 69 | public DecoderException(final String message, final Throwable cause) { | ||
| 70 | super(message, cause); | ||
| 71 | } | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Constructs a new exception with the specified cause and a detail message of <code>(cause==null ? | ||
| 75 | * null : cause.toString())</code> (which typically contains the class and detail message of <code>cause</code>). | ||
| 76 | * This constructor is useful for exceptions that are little more than wrappers for other throwables. | ||
| 77 | * | ||
| 78 | * @param cause | ||
| 79 | * The cause which is saved for later retrieval by the {@link #getCause()} method. A {@code null} | ||
| 80 | * value is permitted, and indicates that the cause is nonexistent or unknown. | ||
| 81 | * @since 1.4 | ||
| 82 | */ | ||
| 83 | public DecoderException(final Throwable cause) { | ||
| 84 | super(cause); | ||
| 85 | } | ||
| 86 | } | ||
diff --git a/java/client/src/org/apache/commons/codec/Encoder.java b/java/client/src/org/apache/commons/codec/Encoder.java new file mode 100644 index 0000000..c7e99eb --- /dev/null +++ b/java/client/src/org/apache/commons/codec/Encoder.java | |||
| @@ -0,0 +1,44 @@ | |||
| 1 | /* | ||
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| 3 | * contributor license agreements. See the NOTICE file distributed with | ||
| 4 | * this work for additional information regarding copyright ownership. | ||
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| 6 | * (the "License"); you may not use this file except in compliance with | ||
| 7 | * the License. You may obtain a copy of the License at | ||
| 8 | * | ||
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | * | ||
| 11 | * Unless required by applicable law or agreed to in writing, software | ||
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | * See the License for the specific language governing permissions and | ||
| 15 | * limitations under the License. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package org.apache.commons.codec; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Provides the highest level of abstraction for Encoders. | ||
| 22 | * <p> | ||
| 23 | * This is the sister interface of {@link Decoder}. Every implementation of Encoder provides this | ||
| 24 | * common generic interface which allows a user to pass a generic Object to any Encoder implementation | ||
| 25 | * in the codec package. | ||
| 26 | * | ||
| 27 | * @version $Id$ | ||
| 28 | */ | ||
| 29 | public interface Encoder { | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Encodes an "Object" and returns the encoded content as an Object. The Objects here may just be | ||
| 33 | * <code>byte[]</code> or <code>String</code>s depending on the implementation used. | ||
| 34 | * | ||
| 35 | * @param source | ||
| 36 | * An object to encode | ||
| 37 | * @return An "encoded" Object | ||
| 38 | * @throws EncoderException | ||
| 39 | * An encoder exception is thrown if the encoder experiences a failure condition during the encoding | ||
| 40 | * process. | ||
| 41 | */ | ||
| 42 | Object encode(Object source) throws EncoderException; | ||
| 43 | } | ||
| 44 | |||
diff --git a/java/client/src/org/apache/commons/codec/EncoderException.java b/java/client/src/org/apache/commons/codec/EncoderException.java new file mode 100644 index 0000000..dfc88f7 --- /dev/null +++ b/java/client/src/org/apache/commons/codec/EncoderException.java | |||
| @@ -0,0 +1,89 @@ | |||
| 1 | /* | ||
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| 3 | * contributor license agreements. See the NOTICE file distributed with | ||
| 4 | * this work for additional information regarding copyright ownership. | ||
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| 6 | * (the "License"); you may not use this file except in compliance with | ||
| 7 | * the License. You may obtain a copy of the License at | ||
| 8 | * | ||
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | * | ||
| 11 | * Unless required by applicable law or agreed to in writing, software | ||
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | * See the License for the specific language governing permissions and | ||
| 15 | * limitations under the License. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package org.apache.commons.codec; | ||
| 19 | |||
| 20 | /** | ||
| 21 | * Thrown when there is a failure condition during the encoding process. This exception is thrown when an | ||
| 22 | * {@link Encoder} encounters a encoding specific exception such as invalid data, inability to calculate a checksum, | ||
| 23 | * characters outside of the expected range. | ||
| 24 | * | ||
| 25 | * @version $Id$ | ||
| 26 | */ | ||
| 27 | public class EncoderException extends Exception { | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Declares the Serial Version Uid. | ||
| 31 | * | ||
| 32 | * @see <a href="http://c2.com/cgi/wiki?AlwaysDeclareSerialVersionUid">Always Declare Serial Version Uid</a> | ||
| 33 | */ | ||
| 34 | private static final long serialVersionUID = 1L; | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Constructs a new exception with {@code null} as its detail message. The cause is not initialized, and may | ||
| 38 | * subsequently be initialized by a call to {@link #initCause}. | ||
| 39 | * | ||
| 40 | * @since 1.4 | ||
| 41 | */ | ||
| 42 | public EncoderException() { | ||
| 43 | super(); | ||
| 44 | } | ||
| 45 | |||
| 46 | /** | ||
| 47 | * Constructs a new exception with the specified detail message. The cause is not initialized, and may subsequently | ||
| 48 | * be initialized by a call to {@link #initCause}. | ||
| 49 | * | ||
| 50 | * @param message | ||
| 51 | * a useful message relating to the encoder specific error. | ||
| 52 | */ | ||
| 53 | public EncoderException(final String message) { | ||
| 54 | super(message); | ||
| 55 | } | ||
| 56 | |||
| 57 | /** | ||
| 58 | * Constructs a new exception with the specified detail message and cause. | ||
| 59 | * | ||
| 60 | * <p> | ||
| 61 | * Note that the detail message associated with <code>cause</code> is not automatically incorporated into this | ||
| 62 | * exception's detail message. | ||
| 63 | * </p> | ||
| 64 | * | ||
| 65 | * @param message | ||
| 66 | * The detail message which is saved for later retrieval by the {@link #getMessage()} method. | ||
| 67 | * @param cause | ||
| 68 | * The cause which is saved for later retrieval by the {@link #getCause()} method. A {@code null} | ||
| 69 | * value is permitted, and indicates that the cause is nonexistent or unknown. | ||
| 70 | * @since 1.4 | ||
| 71 | */ | ||
| 72 | public EncoderException(final String message, final Throwable cause) { | ||
| 73 | super(message, cause); | ||
| 74 | } | ||
| 75 | |||
| 76 | /** | ||
| 77 | * Constructs a new exception with the specified cause and a detail message of <code>(cause==null ? | ||
| 78 | * null : cause.toString())</code> (which typically contains the class and detail message of <code>cause</code>). | ||
| 79 | * This constructor is useful for exceptions that are little more than wrappers for other throwables. | ||
| 80 | * | ||
| 81 | * @param cause | ||
| 82 | * The cause which is saved for later retrieval by the {@link #getCause()} method. A {@code null} | ||
| 83 | * value is permitted, and indicates that the cause is nonexistent or unknown. | ||
| 84 | * @since 1.4 | ||
| 85 | */ | ||
| 86 | public EncoderException(final Throwable cause) { | ||
| 87 | super(cause); | ||
| 88 | } | ||
| 89 | } | ||
diff --git a/java/client/src/org/apache/commons/codec/binary/Base64.java b/java/client/src/org/apache/commons/codec/binary/Base64.java new file mode 100644 index 0000000..3e01916 --- /dev/null +++ b/java/client/src/org/apache/commons/codec/binary/Base64.java | |||
| @@ -0,0 +1,775 @@ | |||
| 1 | /* | ||
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| 3 | * contributor license agreements. See the NOTICE file distributed with | ||
| 4 | * this work for additional information regarding copyright ownership. | ||
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| 6 | * (the "License"); you may not use this file except in compliance with | ||
| 7 | * the License. You may obtain a copy of the License at | ||
| 8 | * | ||
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | * | ||
| 11 | * Unless required by applicable law or agreed to in writing, software | ||
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | * See the License for the specific language governing permissions and | ||
| 15 | * limitations under the License. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package org.apache.commons.codec.binary; | ||
| 19 | |||
| 20 | import java.math.BigInteger; | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Provides Base64 encoding and decoding as defined by <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a>. | ||
| 24 | * | ||
| 25 | * <p> | ||
| 26 | * This class implements section <cite>6.8. Base64 Content-Transfer-Encoding</cite> from RFC 2045 <cite>Multipurpose | ||
| 27 | * Internet Mail Extensions (MIME) Part One: Format of Internet Message Bodies</cite> by Freed and Borenstein. | ||
| 28 | * </p> | ||
| 29 | * <p> | ||
| 30 | * The class can be parameterized in the following manner with various constructors: | ||
| 31 | * <ul> | ||
| 32 | * <li>URL-safe mode: Default off.</li> | ||
| 33 | * <li>Line length: Default 76. Line length that aren't multiples of 4 will still essentially end up being multiples of | ||
| 34 | * 4 in the encoded data. | ||
| 35 | * <li>Line separator: Default is CRLF ("\r\n")</li> | ||
| 36 | * </ul> | ||
| 37 | * </p> | ||
| 38 | * <p> | ||
| 39 | * Since this class operates directly on byte streams, and not character streams, it is hard-coded to only | ||
| 40 | * encode/decode character encodings which are compatible with the lower 127 ASCII chart (ISO-8859-1, Windows-1252, | ||
| 41 | * UTF-8, etc). | ||
| 42 | * </p> | ||
| 43 | * <p> | ||
| 44 | * This class is thread-safe. | ||
| 45 | * </p> | ||
| 46 | * | ||
| 47 | * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a> | ||
| 48 | * @since 1.0 | ||
| 49 | * @version $Id$ | ||
| 50 | */ | ||
| 51 | public class Base64 extends BaseNCodec { | ||
| 52 | |||
| 53 | /** | ||
| 54 | * BASE32 characters are 6 bits in length. | ||
| 55 | * They are formed by taking a block of 3 octets to form a 24-bit string, | ||
| 56 | * which is converted into 4 BASE64 characters. | ||
| 57 | */ | ||
| 58 | private static final int BITS_PER_ENCODED_BYTE = 6; | ||
| 59 | private static final int BYTES_PER_UNENCODED_BLOCK = 3; | ||
| 60 | private static final int BYTES_PER_ENCODED_BLOCK = 4; | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Chunk separator per RFC 2045 section 2.1. | ||
| 64 | * | ||
| 65 | * <p> | ||
| 66 | * N.B. The next major release may break compatibility and make this field private. | ||
| 67 | * </p> | ||
| 68 | * | ||
| 69 | * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 2.1</a> | ||
| 70 | */ | ||
| 71 | static final byte[] CHUNK_SEPARATOR = {'\r', '\n'}; | ||
| 72 | |||
| 73 | /** | ||
| 74 | * This array is a lookup table that translates 6-bit positive integer index values into their "Base64 Alphabet" | ||
| 75 | * equivalents as specified in Table 1 of RFC 2045. | ||
| 76 | * | ||
| 77 | * Thanks to "commons" project in ws.apache.org for this code. | ||
| 78 | * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ | ||
| 79 | */ | ||
| 80 | private static final byte[] STANDARD_ENCODE_TABLE = { | ||
| 81 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', | ||
| 82 | 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', | ||
| 83 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', | ||
| 84 | 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', | ||
| 85 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/' | ||
| 86 | }; | ||
| 87 | |||
| 88 | /** | ||
| 89 | * This is a copy of the STANDARD_ENCODE_TABLE above, but with + and / | ||
| 90 | * changed to - and _ to make the encoded Base64 results more URL-SAFE. | ||
| 91 | * This table is only used when the Base64's mode is set to URL-SAFE. | ||
| 92 | */ | ||
| 93 | private static final byte[] URL_SAFE_ENCODE_TABLE = { | ||
| 94 | 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', | ||
| 95 | 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', | ||
| 96 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', | ||
| 97 | 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', | ||
| 98 | '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '-', '_' | ||
| 99 | }; | ||
| 100 | |||
| 101 | /** | ||
| 102 | * This array is a lookup table that translates Unicode characters drawn from the "Base64 Alphabet" (as specified | ||
| 103 | * in Table 1 of RFC 2045) into their 6-bit positive integer equivalents. Characters that are not in the Base64 | ||
| 104 | * alphabet but fall within the bounds of the array are translated to -1. | ||
| 105 | * | ||
| 106 | * Note: '+' and '-' both decode to 62. '/' and '_' both decode to 63. This means decoder seamlessly handles both | ||
| 107 | * URL_SAFE and STANDARD base64. (The encoder, on the other hand, needs to know ahead of time what to emit). | ||
| 108 | * | ||
| 109 | * Thanks to "commons" project in ws.apache.org for this code. | ||
| 110 | * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ | ||
| 111 | */ | ||
| 112 | private static final byte[] DECODE_TABLE = { | ||
| 113 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | ||
| 114 | -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, | ||
| 115 | -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, 62, -1, 63, 52, 53, 54, | ||
| 116 | 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1, -1, 0, 1, 2, 3, 4, | ||
| 117 | 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, | ||
| 118 | 24, 25, -1, -1, -1, -1, 63, -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, | ||
| 119 | 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51 | ||
| 120 | }; | ||
| 121 | |||
| 122 | /** | ||
| 123 | * Base64 uses 6-bit fields. | ||
| 124 | */ | ||
| 125 | /** Mask used to extract 6 bits, used when encoding */ | ||
| 126 | private static final int MASK_6BITS = 0x3f; | ||
| 127 | |||
| 128 | // The static final fields above are used for the original static byte[] methods on Base64. | ||
| 129 | // The private member fields below are used with the new streaming approach, which requires | ||
| 130 | // some state be preserved between calls of encode() and decode(). | ||
| 131 | |||
| 132 | /** | ||
| 133 | * Encode table to use: either STANDARD or URL_SAFE. Note: the DECODE_TABLE above remains static because it is able | ||
| 134 | * to decode both STANDARD and URL_SAFE streams, but the encodeTable must be a member variable so we can switch | ||
| 135 | * between the two modes. | ||
| 136 | */ | ||
| 137 | private final byte[] encodeTable; | ||
| 138 | |||
| 139 | // Only one decode table currently; keep for consistency with Base32 code | ||
| 140 | private final byte[] decodeTable = DECODE_TABLE; | ||
| 141 | |||
| 142 | /** | ||
| 143 | * Line separator for encoding. Not used when decoding. Only used if lineLength > 0. | ||
| 144 | */ | ||
| 145 | private final byte[] lineSeparator; | ||
| 146 | |||
| 147 | /** | ||
| 148 | * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. | ||
| 149 | * <code>decodeSize = 3 + lineSeparator.length;</code> | ||
| 150 | */ | ||
| 151 | private final int decodeSize; | ||
| 152 | |||
| 153 | /** | ||
| 154 | * Convenience variable to help us determine when our buffer is going to run out of room and needs resizing. | ||
| 155 | * <code>encodeSize = 4 + lineSeparator.length;</code> | ||
| 156 | */ | ||
| 157 | private final int encodeSize; | ||
| 158 | |||
| 159 | /** | ||
| 160 | * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. | ||
| 161 | * <p> | ||
| 162 | * When encoding the line length is 0 (no chunking), and the encoding table is STANDARD_ENCODE_TABLE. | ||
| 163 | * </p> | ||
| 164 | * | ||
| 165 | * <p> | ||
| 166 | * When decoding all variants are supported. | ||
| 167 | * </p> | ||
| 168 | */ | ||
| 169 | public Base64() { | ||
| 170 | this(0); | ||
| 171 | } | ||
| 172 | |||
| 173 | /** | ||
| 174 | * Creates a Base64 codec used for decoding (all modes) and encoding in the given URL-safe mode. | ||
| 175 | * <p> | ||
| 176 | * When encoding the line length is 76, the line separator is CRLF, and the encoding table is | ||
| 177 | * STANDARD_ENCODE_TABLE. | ||
| 178 | * </p> | ||
| 179 | * | ||
| 180 | * <p> | ||
| 181 | * When decoding all variants are supported. | ||
| 182 | * </p> | ||
| 183 | * | ||
| 184 | * @param urlSafe | ||
| 185 | * if {@code true}, URL-safe encoding is used. In most cases this should be set to {@code false}. | ||
| 186 | * @since 1.4 | ||
| 187 | */ | ||
| 188 | public Base64(final boolean urlSafe) { | ||
| 189 | this(MIME_CHUNK_SIZE, CHUNK_SEPARATOR, urlSafe); | ||
| 190 | } | ||
| 191 | |||
| 192 | /** | ||
| 193 | * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. | ||
| 194 | * <p> | ||
| 195 | * When encoding the line length is given in the constructor, the line separator is CRLF, and the encoding table is | ||
| 196 | * STANDARD_ENCODE_TABLE. | ||
| 197 | * </p> | ||
| 198 | * <p> | ||
| 199 | * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. | ||
| 200 | * </p> | ||
| 201 | * <p> | ||
| 202 | * When decoding all variants are supported. | ||
| 203 | * </p> | ||
| 204 | * | ||
| 205 | * @param lineLength | ||
| 206 | * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of | ||
| 207 | * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when | ||
| 208 | * decoding. | ||
| 209 | * @since 1.4 | ||
| 210 | */ | ||
| 211 | public Base64(final int lineLength) { | ||
| 212 | this(lineLength, CHUNK_SEPARATOR); | ||
| 213 | } | ||
| 214 | |||
| 215 | /** | ||
| 216 | * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. | ||
| 217 | * <p> | ||
| 218 | * When encoding the line length and line separator are given in the constructor, and the encoding table is | ||
| 219 | * STANDARD_ENCODE_TABLE. | ||
| 220 | * </p> | ||
| 221 | * <p> | ||
| 222 | * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. | ||
| 223 | * </p> | ||
| 224 | * <p> | ||
| 225 | * When decoding all variants are supported. | ||
| 226 | * </p> | ||
| 227 | * | ||
| 228 | * @param lineLength | ||
| 229 | * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of | ||
| 230 | * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when | ||
| 231 | * decoding. | ||
| 232 | * @param lineSeparator | ||
| 233 | * Each line of encoded data will end with this sequence of bytes. | ||
| 234 | * @throws IllegalArgumentException | ||
| 235 | * Thrown when the provided lineSeparator included some base64 characters. | ||
| 236 | * @since 1.4 | ||
| 237 | */ | ||
| 238 | public Base64(final int lineLength, final byte[] lineSeparator) { | ||
| 239 | this(lineLength, lineSeparator, false); | ||
| 240 | } | ||
| 241 | |||
| 242 | /** | ||
| 243 | * Creates a Base64 codec used for decoding (all modes) and encoding in URL-unsafe mode. | ||
| 244 | * <p> | ||
| 245 | * When encoding the line length and line separator are given in the constructor, and the encoding table is | ||
| 246 | * STANDARD_ENCODE_TABLE. | ||
| 247 | * </p> | ||
| 248 | * <p> | ||
| 249 | * Line lengths that aren't multiples of 4 will still essentially end up being multiples of 4 in the encoded data. | ||
| 250 | * </p> | ||
| 251 | * <p> | ||
| 252 | * When decoding all variants are supported. | ||
| 253 | * </p> | ||
| 254 | * | ||
| 255 | * @param lineLength | ||
| 256 | * Each line of encoded data will be at most of the given length (rounded down to nearest multiple of | ||
| 257 | * 4). If lineLength <= 0, then the output will not be divided into lines (chunks). Ignored when | ||
| 258 | * decoding. | ||
| 259 | * @param lineSeparator | ||
| 260 | * Each line of encoded data will end with this sequence of bytes. | ||
| 261 | * @param urlSafe | ||
| 262 | * Instead of emitting '+' and '/' we emit '-' and '_' respectively. urlSafe is only applied to encode | ||
| 263 | * operations. Decoding seamlessly handles both modes. | ||
| 264 | * <b>Note: no padding is added when using the URL-safe alphabet.</b> | ||
| 265 | * @throws IllegalArgumentException | ||
| 266 | * The provided lineSeparator included some base64 characters. That's not going to work! | ||
| 267 | * @since 1.4 | ||
| 268 | */ | ||
| 269 | public Base64(final int lineLength, final byte[] lineSeparator, final boolean urlSafe) { | ||
| 270 | super(BYTES_PER_UNENCODED_BLOCK, BYTES_PER_ENCODED_BLOCK, | ||
| 271 | lineLength, | ||
| 272 | lineSeparator == null ? 0 : lineSeparator.length); | ||
| 273 | // TODO could be simplified if there is no requirement to reject invalid line sep when length <=0 | ||
| 274 | // @see test case Base64Test.testConstructors() | ||
| 275 | if (lineSeparator != null) { | ||
| 276 | if (containsAlphabetOrPad(lineSeparator)) { | ||
| 277 | final String sep = StringUtils.newStringUtf8(lineSeparator); | ||
| 278 | throw new IllegalArgumentException("lineSeparator must not contain base64 characters: [" + sep + "]"); | ||
| 279 | } | ||
| 280 | if (lineLength > 0){ // null line-sep forces no chunking rather than throwing IAE | ||
| 281 | this.encodeSize = BYTES_PER_ENCODED_BLOCK + lineSeparator.length; | ||
| 282 | this.lineSeparator = new byte[lineSeparator.length]; | ||
| 283 | System.arraycopy(lineSeparator, 0, this.lineSeparator, 0, lineSeparator.length); | ||
| 284 | } else { | ||
| 285 | this.encodeSize = BYTES_PER_ENCODED_BLOCK; | ||
| 286 | this.lineSeparator = null; | ||
| 287 | } | ||
| 288 | } else { | ||
| 289 | this.encodeSize = BYTES_PER_ENCODED_BLOCK; | ||
| 290 | this.lineSeparator = null; | ||
| 291 | } | ||
| 292 | this.decodeSize = this.encodeSize - 1; | ||
| 293 | this.encodeTable = urlSafe ? URL_SAFE_ENCODE_TABLE : STANDARD_ENCODE_TABLE; | ||
| 294 | } | ||
| 295 | |||
| 296 | /** | ||
| 297 | * Returns our current encode mode. True if we're URL-SAFE, false otherwise. | ||
| 298 | * | ||
| 299 | * @return true if we're in URL-SAFE mode, false otherwise. | ||
| 300 | * @since 1.4 | ||
| 301 | */ | ||
| 302 | public boolean isUrlSafe() { | ||
| 303 | return this.encodeTable == URL_SAFE_ENCODE_TABLE; | ||
| 304 | } | ||
| 305 | |||
| 306 | /** | ||
| 307 | * <p> | ||
| 308 | * Encodes all of the provided data, starting at inPos, for inAvail bytes. Must be called at least twice: once with | ||
| 309 | * the data to encode, and once with inAvail set to "-1" to alert encoder that EOF has been reached, to flush last | ||
| 310 | * remaining bytes (if not multiple of 3). | ||
| 311 | * </p> | ||
| 312 | * <p><b>Note: no padding is added when encoding using the URL-safe alphabet.</b></p> | ||
| 313 | * <p> | ||
| 314 | * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. | ||
| 315 | * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ | ||
| 316 | * </p> | ||
| 317 | * | ||
| 318 | * @param in | ||
| 319 | * byte[] array of binary data to base64 encode. | ||
| 320 | * @param inPos | ||
| 321 | * Position to start reading data from. | ||
| 322 | * @param inAvail | ||
| 323 | * Amount of bytes available from input for encoding. | ||
| 324 | * @param context | ||
| 325 | * the context to be used | ||
| 326 | */ | ||
| 327 | @Override | ||
| 328 | void encode(final byte[] in, int inPos, final int inAvail, final Context context) { | ||
| 329 | if (context.eof) { | ||
| 330 | return; | ||
| 331 | } | ||
| 332 | // inAvail < 0 is how we're informed of EOF in the underlying data we're | ||
| 333 | // encoding. | ||
| 334 | if (inAvail < 0) { | ||
| 335 | context.eof = true; | ||
| 336 | if (0 == context.modulus && lineLength == 0) { | ||
| 337 | return; // no leftovers to process and not using chunking | ||
| 338 | } | ||
| 339 | final byte[] buffer = ensureBufferSize(encodeSize, context); | ||
| 340 | final int savedPos = context.pos; | ||
| 341 | switch (context.modulus) { // 0-2 | ||
| 342 | case 0 : // nothing to do here | ||
| 343 | break; | ||
| 344 | case 1 : // 8 bits = 6 + 2 | ||
| 345 | // top 6 bits: | ||
| 346 | buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 2) & MASK_6BITS]; | ||
| 347 | // remaining 2: | ||
| 348 | buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 4) & MASK_6BITS]; | ||
| 349 | // URL-SAFE skips the padding to further reduce size. | ||
| 350 | if (encodeTable == STANDARD_ENCODE_TABLE) { | ||
| 351 | buffer[context.pos++] = PAD; | ||
| 352 | buffer[context.pos++] = PAD; | ||
| 353 | } | ||
| 354 | break; | ||
| 355 | |||
| 356 | case 2 : // 16 bits = 6 + 6 + 4 | ||
| 357 | buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 10) & MASK_6BITS]; | ||
| 358 | buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 4) & MASK_6BITS]; | ||
| 359 | buffer[context.pos++] = encodeTable[(context.ibitWorkArea << 2) & MASK_6BITS]; | ||
| 360 | // URL-SAFE skips the padding to further reduce size. | ||
| 361 | if (encodeTable == STANDARD_ENCODE_TABLE) { | ||
| 362 | buffer[context.pos++] = PAD; | ||
| 363 | } | ||
| 364 | break; | ||
| 365 | default: | ||
| 366 | throw new IllegalStateException("Impossible modulus "+context.modulus); | ||
| 367 | } | ||
| 368 | context.currentLinePos += context.pos - savedPos; // keep track of current line position | ||
| 369 | // if currentPos == 0 we are at the start of a line, so don't add CRLF | ||
| 370 | if (lineLength > 0 && context.currentLinePos > 0) { | ||
| 371 | System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); | ||
| 372 | context.pos += lineSeparator.length; | ||
| 373 | } | ||
| 374 | } else { | ||
| 375 | for (int i = 0; i < inAvail; i++) { | ||
| 376 | final byte[] buffer = ensureBufferSize(encodeSize, context); | ||
| 377 | context.modulus = (context.modulus+1) % BYTES_PER_UNENCODED_BLOCK; | ||
| 378 | int b = in[inPos++]; | ||
| 379 | if (b < 0) { | ||
| 380 | b += 256; | ||
| 381 | } | ||
| 382 | context.ibitWorkArea = (context.ibitWorkArea << 8) + b; // BITS_PER_BYTE | ||
| 383 | if (0 == context.modulus) { // 3 bytes = 24 bits = 4 * 6 bits to extract | ||
| 384 | buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 18) & MASK_6BITS]; | ||
| 385 | buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 12) & MASK_6BITS]; | ||
| 386 | buffer[context.pos++] = encodeTable[(context.ibitWorkArea >> 6) & MASK_6BITS]; | ||
| 387 | buffer[context.pos++] = encodeTable[context.ibitWorkArea & MASK_6BITS]; | ||
| 388 | context.currentLinePos += BYTES_PER_ENCODED_BLOCK; | ||
| 389 | if (lineLength > 0 && lineLength <= context.currentLinePos) { | ||
| 390 | System.arraycopy(lineSeparator, 0, buffer, context.pos, lineSeparator.length); | ||
| 391 | context.pos += lineSeparator.length; | ||
| 392 | context.currentLinePos = 0; | ||
| 393 | } | ||
| 394 | } | ||
| 395 | } | ||
| 396 | } | ||
| 397 | } | ||
| 398 | |||
| 399 | /** | ||
| 400 | * <p> | ||
| 401 | * Decodes all of the provided data, starting at inPos, for inAvail bytes. Should be called at least twice: once | ||
| 402 | * with the data to decode, and once with inAvail set to "-1" to alert decoder that EOF has been reached. The "-1" | ||
| 403 | * call is not necessary when decoding, but it doesn't hurt, either. | ||
| 404 | * </p> | ||
| 405 | * <p> | ||
| 406 | * Ignores all non-base64 characters. This is how chunked (e.g. 76 character) data is handled, since CR and LF are | ||
| 407 | * silently ignored, but has implications for other bytes, too. This method subscribes to the garbage-in, | ||
| 408 | * garbage-out philosophy: it will not check the provided data for validity. | ||
| 409 | * </p> | ||
| 410 | * <p> | ||
| 411 | * Thanks to "commons" project in ws.apache.org for the bitwise operations, and general approach. | ||
| 412 | * http://svn.apache.org/repos/asf/webservices/commons/trunk/modules/util/ | ||
| 413 | * </p> | ||
| 414 | * | ||
| 415 | * @param in | ||
| 416 | * byte[] array of ascii data to base64 decode. | ||
| 417 | * @param inPos | ||
| 418 | * Position to start reading data from. | ||
| 419 | * @param inAvail | ||
| 420 | * Amount of bytes available from input for encoding. | ||
| 421 | * @param context | ||
| 422 | * the context to be used | ||
| 423 | */ | ||
| 424 | @Override | ||
| 425 | void decode(final byte[] in, int inPos, final int inAvail, final Context context) { | ||
| 426 | if (context.eof) { | ||
| 427 | return; | ||
| 428 | } | ||
| 429 | if (inAvail < 0) { | ||
| 430 | context.eof = true; | ||
| 431 | } | ||
| 432 | for (int i = 0; i < inAvail; i++) { | ||
| 433 | final byte[] buffer = ensureBufferSize(decodeSize, context); | ||
| 434 | final byte b = in[inPos++]; | ||
| 435 | if (b == PAD) { | ||
| 436 | // We're done. | ||
| 437 | context.eof = true; | ||
| 438 | break; | ||
| 439 | } else { | ||
| 440 | if (b >= 0 && b < DECODE_TABLE.length) { | ||
| 441 | final int result = DECODE_TABLE[b]; | ||
| 442 | if (result >= 0) { | ||
| 443 | context.modulus = (context.modulus+1) % BYTES_PER_ENCODED_BLOCK; | ||
| 444 | context.ibitWorkArea = (context.ibitWorkArea << BITS_PER_ENCODED_BYTE) + result; | ||
| 445 | if (context.modulus == 0) { | ||
| 446 | buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 16) & MASK_8BITS); | ||
| 447 | buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); | ||
| 448 | buffer[context.pos++] = (byte) (context.ibitWorkArea & MASK_8BITS); | ||
| 449 | } | ||
| 450 | } | ||
| 451 | } | ||
| 452 | } | ||
| 453 | } | ||
| 454 | |||
| 455 | // Two forms of EOF as far as base64 decoder is concerned: actual | ||
| 456 | // EOF (-1) and first time '=' character is encountered in stream. | ||
| 457 | // This approach makes the '=' padding characters completely optional. | ||
| 458 | if (context.eof && context.modulus != 0) { | ||
| 459 | final byte[] buffer = ensureBufferSize(decodeSize, context); | ||
| 460 | |||
| 461 | // We have some spare bits remaining | ||
| 462 | // Output all whole multiples of 8 bits and ignore the rest | ||
| 463 | switch (context.modulus) { | ||
| 464 | // case 0 : // impossible, as excluded above | ||
| 465 | case 1 : // 6 bits - ignore entirely | ||
| 466 | // TODO not currently tested; perhaps it is impossible? | ||
| 467 | break; | ||
| 468 | case 2 : // 12 bits = 8 + 4 | ||
| 469 | context.ibitWorkArea = context.ibitWorkArea >> 4; // dump the extra 4 bits | ||
| 470 | buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); | ||
| 471 | break; | ||
| 472 | case 3 : // 18 bits = 8 + 8 + 2 | ||
| 473 | context.ibitWorkArea = context.ibitWorkArea >> 2; // dump 2 bits | ||
| 474 | buffer[context.pos++] = (byte) ((context.ibitWorkArea >> 8) & MASK_8BITS); | ||
| 475 | buffer[context.pos++] = (byte) ((context.ibitWorkArea) & MASK_8BITS); | ||
| 476 | break; | ||
| 477 | default: | ||
| 478 | throw new IllegalStateException("Impossible modulus "+context.modulus); | ||
| 479 | } | ||
| 480 | } | ||
| 481 | } | ||
| 482 | |||
| 483 | /** | ||
| 484 | * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the | ||
| 485 | * method treats whitespace as valid. | ||
| 486 | * | ||
| 487 | * @param arrayOctet | ||
| 488 | * byte array to test | ||
| 489 | * @return {@code true} if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; | ||
| 490 | * {@code false}, otherwise | ||
| 491 | * @deprecated 1.5 Use {@link #isBase64(byte[])}, will be removed in 2.0. | ||
| 492 | */ | ||
| 493 | @Deprecated | ||
| 494 | public static boolean isArrayByteBase64(final byte[] arrayOctet) { | ||
| 495 | return isBase64(arrayOctet); | ||
| 496 | } | ||
| 497 | |||
| 498 | /** | ||
| 499 | * Returns whether or not the <code>octet</code> is in the base 64 alphabet. | ||
| 500 | * | ||
| 501 | * @param octet | ||
| 502 | * The value to test | ||
| 503 | * @return {@code true} if the value is defined in the the base 64 alphabet, {@code false} otherwise. | ||
| 504 | * @since 1.4 | ||
| 505 | */ | ||
| 506 | public static boolean isBase64(final byte octet) { | ||
| 507 | return octet == PAD_DEFAULT || (octet >= 0 && octet < DECODE_TABLE.length && DECODE_TABLE[octet] != -1); | ||
| 508 | } | ||
| 509 | |||
| 510 | /** | ||
| 511 | * Tests a given String to see if it contains only valid characters within the Base64 alphabet. Currently the | ||
| 512 | * method treats whitespace as valid. | ||
| 513 | * | ||
| 514 | * @param base64 | ||
| 515 | * String to test | ||
| 516 | * @return {@code true} if all characters in the String are valid characters in the Base64 alphabet or if | ||
| 517 | * the String is empty; {@code false}, otherwise | ||
| 518 | * @since 1.5 | ||
| 519 | */ | ||
| 520 | public static boolean isBase64(final String base64) { | ||
| 521 | return isBase64(StringUtils.getBytesUtf8(base64)); | ||
| 522 | } | ||
| 523 | |||
| 524 | /** | ||
| 525 | * Tests a given byte array to see if it contains only valid characters within the Base64 alphabet. Currently the | ||
| 526 | * method treats whitespace as valid. | ||
| 527 | * | ||
| 528 | * @param arrayOctet | ||
| 529 | * byte array to test | ||
| 530 | * @return {@code true} if all bytes are valid characters in the Base64 alphabet or if the byte array is empty; | ||
| 531 | * {@code false}, otherwise | ||
| 532 | * @since 1.5 | ||
| 533 | */ | ||
| 534 | public static boolean isBase64(final byte[] arrayOctet) { | ||
| 535 | for (int i = 0; i < arrayOctet.length; i++) { | ||
| 536 | if (!isBase64(arrayOctet[i]) && !isWhiteSpace(arrayOctet[i])) { | ||
| 537 | return false; | ||
| 538 | } | ||
| 539 | } | ||
| 540 | return true; | ||
| 541 | } | ||
| 542 | |||
| 543 | /** | ||
| 544 | * Encodes binary data using the base64 algorithm but does not chunk the output. | ||
| 545 | * | ||
| 546 | * @param binaryData | ||
| 547 | * binary data to encode | ||
| 548 | * @return byte[] containing Base64 characters in their UTF-8 representation. | ||
| 549 | */ | ||
| 550 | public static byte[] encodeBase64(final byte[] binaryData) { | ||
| 551 | return encodeBase64(binaryData, false); | ||
| 552 | } | ||
| 553 | |||
| 554 | /** | ||
| 555 | * Encodes binary data using the base64 algorithm but does not chunk the output. | ||
| 556 | * | ||
| 557 | * NOTE: We changed the behaviour of this method from multi-line chunking (commons-codec-1.4) to | ||
| 558 | * single-line non-chunking (commons-codec-1.5). | ||
| 559 | * | ||
| 560 | * @param binaryData | ||
| 561 | * binary data to encode | ||
| 562 | * @return String containing Base64 characters. | ||
| 563 | * @since 1.4 (NOTE: 1.4 chunked the output, whereas 1.5 does not). | ||
| 564 | */ | ||
| 565 | public static String encodeBase64String(final byte[] binaryData) { | ||
| 566 | return StringUtils.newStringUtf8(encodeBase64(binaryData, false)); | ||
| 567 | } | ||
| 568 | |||
| 569 | /** | ||
| 570 | * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The | ||
| 571 | * url-safe variation emits - and _ instead of + and / characters. | ||
| 572 | * <b>Note: no padding is added.</b> | ||
| 573 | * @param binaryData | ||
| 574 | * binary data to encode | ||
| 575 | * @return byte[] containing Base64 characters in their UTF-8 representation. | ||
| 576 | * @since 1.4 | ||
| 577 | */ | ||
| 578 | public static byte[] encodeBase64URLSafe(final byte[] binaryData) { | ||
| 579 | return encodeBase64(binaryData, false, true); | ||
| 580 | } | ||
| 581 | |||
| 582 | /** | ||
| 583 | * Encodes binary data using a URL-safe variation of the base64 algorithm but does not chunk the output. The | ||
| 584 | * url-safe variation emits - and _ instead of + and / characters. | ||
| 585 | * <b>Note: no padding is added.</b> | ||
| 586 | * @param binaryData | ||
| 587 | * binary data to encode | ||
| 588 | * @return String containing Base64 characters | ||
| 589 | * @since 1.4 | ||
| 590 | */ | ||
| 591 | public static String encodeBase64URLSafeString(final byte[] binaryData) { | ||
| 592 | return StringUtils.newStringUtf8(encodeBase64(binaryData, false, true)); | ||
| 593 | } | ||
| 594 | |||
| 595 | /** | ||
| 596 | * Encodes binary data using the base64 algorithm and chunks the encoded output into 76 character blocks | ||
| 597 | * | ||
| 598 | * @param binaryData | ||
| 599 | * binary data to encode | ||
| 600 | * @return Base64 characters chunked in 76 character blocks | ||
| 601 | */ | ||
| 602 | public static byte[] encodeBase64Chunked(final byte[] binaryData) { | ||
| 603 | return encodeBase64(binaryData, true); | ||
| 604 | } | ||
| 605 | |||
| 606 | /** | ||
| 607 | * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. | ||
| 608 | * | ||
| 609 | * @param binaryData | ||
| 610 | * Array containing binary data to encode. | ||
| 611 | * @param isChunked | ||
| 612 | * if {@code true} this encoder will chunk the base64 output into 76 character blocks | ||
| 613 | * @return Base64-encoded data. | ||
| 614 | * @throws IllegalArgumentException | ||
| 615 | * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} | ||
| 616 | */ | ||
| 617 | public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked) { | ||
| 618 | return encodeBase64(binaryData, isChunked, false); | ||
| 619 | } | ||
| 620 | |||
| 621 | /** | ||
| 622 | * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. | ||
| 623 | * | ||
| 624 | * @param binaryData | ||
| 625 | * Array containing binary data to encode. | ||
| 626 | * @param isChunked | ||
| 627 | * if {@code true} this encoder will chunk the base64 output into 76 character blocks | ||
| 628 | * @param urlSafe | ||
| 629 | * if {@code true} this encoder will emit - and _ instead of the usual + and / characters. | ||
| 630 | * <b>Note: no padding is added when encoding using the URL-safe alphabet.</b> | ||
| 631 | * @return Base64-encoded data. | ||
| 632 | * @throws IllegalArgumentException | ||
| 633 | * Thrown when the input array needs an output array bigger than {@link Integer#MAX_VALUE} | ||
| 634 | * @since 1.4 | ||
| 635 | */ | ||
| 636 | public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, final boolean urlSafe) { | ||
| 637 | return encodeBase64(binaryData, isChunked, urlSafe, Integer.MAX_VALUE); | ||
| 638 | } | ||
| 639 | |||
| 640 | /** | ||
| 641 | * Encodes binary data using the base64 algorithm, optionally chunking the output into 76 character blocks. | ||
| 642 | * | ||
| 643 | * @param binaryData | ||
| 644 | * Array containing binary data to encode. | ||
| 645 | * @param isChunked | ||
| 646 | * if {@code true} this encoder will chunk the base64 output into 76 character blocks | ||
| 647 | * @param urlSafe | ||
| 648 | * if {@code true} this encoder will emit - and _ instead of the usual + and / characters. | ||
| 649 | * <b>Note: no padding is added when encoding using the URL-safe alphabet.</b> | ||
| 650 | * @param maxResultSize | ||
| 651 | * The maximum result size to accept. | ||
| 652 | * @return Base64-encoded data. | ||
| 653 | * @throws IllegalArgumentException | ||
| 654 | * Thrown when the input array needs an output array bigger than maxResultSize | ||
| 655 | * @since 1.4 | ||
| 656 | */ | ||
| 657 | public static byte[] encodeBase64(final byte[] binaryData, final boolean isChunked, | ||
| 658 | final boolean urlSafe, final int maxResultSize) { | ||
| 659 | if (binaryData == null || binaryData.length == 0) { | ||
| 660 | return binaryData; | ||
| 661 | } | ||
| 662 | |||
| 663 | // Create this so can use the super-class method | ||
| 664 | // Also ensures that the same roundings are performed by the ctor and the code | ||
| 665 | final Base64 b64 = isChunked ? new Base64(urlSafe) : new Base64(0, CHUNK_SEPARATOR, urlSafe); | ||
| 666 | final long len = b64.getEncodedLength(binaryData); | ||
| 667 | if (len > maxResultSize) { | ||
| 668 | throw new IllegalArgumentException("Input array too big, the output array would be bigger (" + | ||
| 669 | len + | ||
| 670 | ") than the specified maximum size of " + | ||
| 671 | maxResultSize); | ||
| 672 | } | ||
| 673 | |||
| 674 | return b64.encode(binaryData); | ||
| 675 | } | ||
| 676 | |||
| 677 | /** | ||
| 678 | * Decodes a Base64 String into octets | ||
| 679 | * | ||
| 680 | * @param base64String | ||
| 681 | * String containing Base64 data | ||
| 682 | * @return Array containing decoded data. | ||
| 683 | * @since 1.4 | ||
| 684 | */ | ||
| 685 | public static byte[] decodeBase64(final String base64String) { | ||
| 686 | return new Base64().decode(base64String); | ||
| 687 | } | ||
| 688 | |||
| 689 | /** | ||
| 690 | * Decodes Base64 data into octets | ||
| 691 | * | ||
| 692 | * @param base64Data | ||
| 693 | * Byte array containing Base64 data | ||
| 694 | * @return Array containing decoded data. | ||
| 695 | */ | ||
| 696 | public static byte[] decodeBase64(final byte[] base64Data) { | ||
| 697 | return new Base64().decode(base64Data); | ||
| 698 | } | ||
| 699 | |||
| 700 | // Implementation of the Encoder Interface | ||
| 701 | |||
| 702 | // Implementation of integer encoding used for crypto | ||
| 703 | /** | ||
| 704 | * Decodes a byte64-encoded integer according to crypto standards such as W3C's XML-Signature | ||
| 705 | * | ||
| 706 | * @param pArray | ||
| 707 | * a byte array containing base64 character data | ||
| 708 | * @return A BigInteger | ||
| 709 | * @since 1.4 | ||
| 710 | */ | ||
| 711 | public static BigInteger decodeInteger(final byte[] pArray) { | ||
| 712 | return new BigInteger(1, decodeBase64(pArray)); | ||
| 713 | } | ||
| 714 | |||
| 715 | /** | ||
| 716 | * Encodes to a byte64-encoded integer according to crypto standards such as W3C's XML-Signature | ||
| 717 | * | ||
| 718 | * @param bigInt | ||
| 719 | * a BigInteger | ||
| 720 | * @return A byte array containing base64 character data | ||
| 721 | * @throws NullPointerException | ||
| 722 | * if null is passed in | ||
| 723 | * @since 1.4 | ||
| 724 | */ | ||
| 725 | public static byte[] encodeInteger(final BigInteger bigInt) { | ||
| 726 | if (bigInt == null) { | ||
| 727 | throw new NullPointerException("encodeInteger called with null parameter"); | ||
| 728 | } | ||
| 729 | return encodeBase64(toIntegerBytes(bigInt), false); | ||
| 730 | } | ||
| 731 | |||
| 732 | /** | ||
| 733 | * Returns a byte-array representation of a <code>BigInteger</code> without sign bit. | ||
| 734 | * | ||
| 735 | * @param bigInt | ||
| 736 | * <code>BigInteger</code> to be converted | ||
| 737 | * @return a byte array representation of the BigInteger parameter | ||
| 738 | */ | ||
| 739 | static byte[] toIntegerBytes(final BigInteger bigInt) { | ||
| 740 | int bitlen = bigInt.bitLength(); | ||
| 741 | // round bitlen | ||
| 742 | bitlen = ((bitlen + 7) >> 3) << 3; | ||
| 743 | final byte[] bigBytes = bigInt.toByteArray(); | ||
| 744 | |||
| 745 | if (((bigInt.bitLength() % 8) != 0) && (((bigInt.bitLength() / 8) + 1) == (bitlen / 8))) { | ||
| 746 | return bigBytes; | ||
| 747 | } | ||
| 748 | // set up params for copying everything but sign bit | ||
| 749 | int startSrc = 0; | ||
| 750 | int len = bigBytes.length; | ||
| 751 | |||
| 752 | // if bigInt is exactly byte-aligned, just skip signbit in copy | ||
| 753 | if ((bigInt.bitLength() % 8) == 0) { | ||
| 754 | startSrc = 1; | ||
| 755 | len--; | ||
| 756 | } | ||
| 757 | final int startDst = bitlen / 8 - len; // to pad w/ nulls as per spec | ||
| 758 | final byte[] resizedBytes = new byte[bitlen / 8]; | ||
| 759 | System.arraycopy(bigBytes, startSrc, resizedBytes, startDst, len); | ||
| 760 | return resizedBytes; | ||
| 761 | } | ||
| 762 | |||
| 763 | /** | ||
| 764 | * Returns whether or not the <code>octet</code> is in the Base64 alphabet. | ||
| 765 | * | ||
| 766 | * @param octet | ||
| 767 | * The value to test | ||
| 768 | * @return {@code true} if the value is defined in the the Base64 alphabet {@code false} otherwise. | ||
| 769 | */ | ||
| 770 | @Override | ||
| 771 | protected boolean isInAlphabet(final byte octet) { | ||
| 772 | return octet >= 0 && octet < decodeTable.length && decodeTable[octet] != -1; | ||
| 773 | } | ||
| 774 | |||
| 775 | } | ||
diff --git a/java/client/src/org/apache/commons/codec/binary/BaseNCodec.java b/java/client/src/org/apache/commons/codec/binary/BaseNCodec.java new file mode 100644 index 0000000..2edd754 --- /dev/null +++ b/java/client/src/org/apache/commons/codec/binary/BaseNCodec.java | |||
| @@ -0,0 +1,500 @@ | |||
| 1 | /* | ||
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| 3 | * contributor license agreements. See the NOTICE file distributed with | ||
| 4 | * this work for additional information regarding copyright ownership. | ||
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| 6 | * (the "License"); you may not use this file except in compliance with | ||
| 7 | * the License. You may obtain a copy of the License at | ||
| 8 | * | ||
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | * | ||
| 11 | * Unless required by applicable law or agreed to in writing, software | ||
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | * See the License for the specific language governing permissions and | ||
| 15 | * limitations under the License. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package org.apache.commons.codec.binary; | ||
| 19 | |||
| 20 | import org.apache.commons.codec.BinaryDecoder; | ||
| 21 | import org.apache.commons.codec.BinaryEncoder; | ||
| 22 | import org.apache.commons.codec.DecoderException; | ||
| 23 | import org.apache.commons.codec.EncoderException; | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Abstract superclass for Base-N encoders and decoders. | ||
| 27 | * | ||
| 28 | * <p> | ||
| 29 | * This class is thread-safe. | ||
| 30 | * </p> | ||
| 31 | * | ||
| 32 | * @version $Id$ | ||
| 33 | */ | ||
| 34 | public abstract class BaseNCodec implements BinaryEncoder, BinaryDecoder { | ||
| 35 | |||
| 36 | /** | ||
| 37 | * Holds thread context so classes can be thread-safe. | ||
| 38 | * | ||
| 39 | * This class is not itself thread-safe; each thread must allocate its own copy. | ||
| 40 | * | ||
| 41 | * @since 1.7 | ||
| 42 | */ | ||
| 43 | static class Context { | ||
| 44 | |||
| 45 | /** | ||
| 46 | * Place holder for the bytes we're dealing with for our based logic. | ||
| 47 | * Bitwise operations store and extract the encoding or decoding from this variable. | ||
| 48 | */ | ||
| 49 | int ibitWorkArea; | ||
| 50 | |||
| 51 | /** | ||
| 52 | * Place holder for the bytes we're dealing with for our based logic. | ||
| 53 | * Bitwise operations store and extract the encoding or decoding from this variable. | ||
| 54 | */ | ||
| 55 | long lbitWorkArea; | ||
| 56 | |||
| 57 | /** | ||
| 58 | * Buffer for streaming. | ||
| 59 | */ | ||
| 60 | byte[] buffer; | ||
| 61 | |||
| 62 | /** | ||
| 63 | * Position where next character should be written in the buffer. | ||
| 64 | */ | ||
| 65 | int pos; | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Position where next character should be read from the buffer. | ||
| 69 | */ | ||
| 70 | int readPos; | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Boolean flag to indicate the EOF has been reached. Once EOF has been reached, this object becomes useless, | ||
| 74 | * and must be thrown away. | ||
| 75 | */ | ||
| 76 | boolean eof; | ||
| 77 | |||
| 78 | /** | ||
| 79 | * Variable tracks how many characters have been written to the current line. Only used when encoding. We use | ||
| 80 | * it to make sure each encoded line never goes beyond lineLength (if lineLength > 0). | ||
| 81 | */ | ||
| 82 | int currentLinePos; | ||
| 83 | |||
| 84 | /** | ||
| 85 | * Writes to the buffer only occur after every 3/5 reads when encoding, and every 4/8 reads when decoding. This | ||
| 86 | * variable helps track that. | ||
| 87 | */ | ||
| 88 | int modulus; | ||
| 89 | |||
| 90 | Context() { | ||
| 91 | } | ||
| 92 | |||
| 93 | /** | ||
| 94 | * Returns a String useful for debugging (especially within a debugger.) | ||
| 95 | * | ||
| 96 | * @return a String useful for debugging. | ||
| 97 | */ | ||
| 98 | @SuppressWarnings("boxing") // OK to ignore boxing here | ||
| 99 | @Override | ||
| 100 | public String toString() { | ||
| 101 | return String.format("%s[buffer=%s, currentLinePos=%s, eof=%s, ibitWorkArea=%s, lbitWorkArea=%s, " + | ||
| 102 | "modulus=%s, pos=%s, readPos=%s]", this.getClass().getSimpleName(), buffer, currentLinePos, eof, | ||
| 103 | ibitWorkArea, lbitWorkArea, modulus, pos, readPos); | ||
| 104 | } | ||
| 105 | } | ||
| 106 | |||
| 107 | /** | ||
| 108 | * EOF | ||
| 109 | * | ||
| 110 | * @since 1.7 | ||
| 111 | */ | ||
| 112 | static final int EOF = -1; | ||
| 113 | |||
| 114 | /** | ||
| 115 | * MIME chunk size per RFC 2045 section 6.8. | ||
| 116 | * | ||
| 117 | * <p> | ||
| 118 | * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any | ||
| 119 | * equal signs. | ||
| 120 | * </p> | ||
| 121 | * | ||
| 122 | * @see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045 section 6.8</a> | ||
| 123 | */ | ||
| 124 | public static final int MIME_CHUNK_SIZE = 76; | ||
| 125 | |||
| 126 | /** | ||
| 127 | * PEM chunk size per RFC 1421 section 4.3.2.4. | ||
| 128 | * | ||
| 129 | * <p> | ||
| 130 | * The {@value} character limit does not count the trailing CRLF, but counts all other characters, including any | ||
| 131 | * equal signs. | ||
| 132 | * </p> | ||
| 133 | * | ||
| 134 | * @see <a href="http://tools.ietf.org/html/rfc1421">RFC 1421 section 4.3.2.4</a> | ||
| 135 | */ | ||
| 136 | public static final int PEM_CHUNK_SIZE = 64; | ||
| 137 | |||
| 138 | private static final int DEFAULT_BUFFER_RESIZE_FACTOR = 2; | ||
| 139 | |||
| 140 | /** | ||
| 141 | * Defines the default buffer size - currently {@value} | ||
| 142 | * - must be large enough for at least one encoded block+separator | ||
| 143 | */ | ||
| 144 | private static final int DEFAULT_BUFFER_SIZE = 8192; | ||
| 145 | |||
| 146 | /** Mask used to extract 8 bits, used in decoding bytes */ | ||
| 147 | protected static final int MASK_8BITS = 0xff; | ||
| 148 | |||
| 149 | /** | ||
| 150 | * Byte used to pad output. | ||
| 151 | */ | ||
| 152 | protected static final byte PAD_DEFAULT = '='; // Allow static access to default | ||
| 153 | |||
| 154 | protected final byte PAD = PAD_DEFAULT; // instance variable just in case it needs to vary later | ||
| 155 | |||
| 156 | /** Number of bytes in each full block of unencoded data, e.g. 4 for Base64 and 5 for Base32 */ | ||
| 157 | private final int unencodedBlockSize; | ||
| 158 | |||
| 159 | /** Number of bytes in each full block of encoded data, e.g. 3 for Base64 and 8 for Base32 */ | ||
| 160 | private final int encodedBlockSize; | ||
| 161 | |||
| 162 | /** | ||
| 163 | * Chunksize for encoding. Not used when decoding. | ||
| 164 | * A value of zero or less implies no chunking of the encoded data. | ||
| 165 | * Rounded down to nearest multiple of encodedBlockSize. | ||
| 166 | */ | ||
| 167 | protected final int lineLength; | ||
| 168 | |||
| 169 | /** | ||
| 170 | * Size of chunk separator. Not used unless {@link #lineLength} > 0. | ||
| 171 | */ | ||
| 172 | private final int chunkSeparatorLength; | ||
| 173 | |||
| 174 | /** | ||
| 175 | * Note <code>lineLength</code> is rounded down to the nearest multiple of {@link #encodedBlockSize} | ||
| 176 | * If <code>chunkSeparatorLength</code> is zero, then chunking is disabled. | ||
| 177 | * @param unencodedBlockSize the size of an unencoded block (e.g. Base64 = 3) | ||
| 178 | * @param encodedBlockSize the size of an encoded block (e.g. Base64 = 4) | ||
| 179 | * @param lineLength if > 0, use chunking with a length <code>lineLength</code> | ||
| 180 | * @param chunkSeparatorLength the chunk separator length, if relevant | ||
| 181 | */ | ||
| 182 | protected BaseNCodec(final int unencodedBlockSize, final int encodedBlockSize, | ||
| 183 | final int lineLength, final int chunkSeparatorLength) { | ||
| 184 | this.unencodedBlockSize = unencodedBlockSize; | ||
| 185 | this.encodedBlockSize = encodedBlockSize; | ||
| 186 | final boolean useChunking = lineLength > 0 && chunkSeparatorLength > 0; | ||
| 187 | this.lineLength = useChunking ? (lineLength / encodedBlockSize) * encodedBlockSize : 0; | ||
| 188 | this.chunkSeparatorLength = chunkSeparatorLength; | ||
| 189 | } | ||
| 190 | |||
| 191 | /** | ||
| 192 | * Returns true if this object has buffered data for reading. | ||
| 193 | * | ||
| 194 | * @param context the context to be used | ||
| 195 | * @return true if there is data still available for reading. | ||
| 196 | */ | ||
| 197 | boolean hasData(final Context context) { // package protected for access from I/O streams | ||
| 198 | return context.buffer != null; | ||
| 199 | } | ||
| 200 | |||
| 201 | /** | ||
| 202 | * Returns the amount of buffered data available for reading. | ||
| 203 | * | ||
| 204 | * @param context the context to be used | ||
| 205 | * @return The amount of buffered data available for reading. | ||
| 206 | */ | ||
| 207 | int available(final Context context) { // package protected for access from I/O streams | ||
| 208 | return context.buffer != null ? context.pos - context.readPos : 0; | ||
| 209 | } | ||
| 210 | |||
| 211 | /** | ||
| 212 | * Get the default buffer size. Can be overridden. | ||
| 213 | * | ||
| 214 | * @return {@link #DEFAULT_BUFFER_SIZE} | ||
| 215 | */ | ||
| 216 | protected int getDefaultBufferSize() { | ||
| 217 | return DEFAULT_BUFFER_SIZE; | ||
| 218 | } | ||
| 219 | |||
| 220 | /** | ||
| 221 | * Increases our buffer by the {@link #DEFAULT_BUFFER_RESIZE_FACTOR}. | ||
| 222 | * @param context the context to be used | ||
| 223 | */ | ||
| 224 | private byte[] resizeBuffer(final Context context) { | ||
| 225 | if (context.buffer == null) { | ||
| 226 | context.buffer = new byte[getDefaultBufferSize()]; | ||
| 227 | context.pos = 0; | ||
| 228 | context.readPos = 0; | ||
| 229 | } else { | ||
| 230 | final byte[] b = new byte[context.buffer.length * DEFAULT_BUFFER_RESIZE_FACTOR]; | ||
| 231 | System.arraycopy(context.buffer, 0, b, 0, context.buffer.length); | ||
| 232 | context.buffer = b; | ||
| 233 | } | ||
| 234 | return context.buffer; | ||
| 235 | } | ||
| 236 | |||
| 237 | /** | ||
| 238 | * Ensure that the buffer has room for <code>size</code> bytes | ||
| 239 | * | ||
| 240 | * @param size minimum spare space required | ||
| 241 | * @param context the context to be used | ||
| 242 | */ | ||
| 243 | protected byte[] ensureBufferSize(final int size, final Context context){ | ||
| 244 | if ((context.buffer == null) || (context.buffer.length < context.pos + size)){ | ||
| 245 | return resizeBuffer(context); | ||
| 246 | } | ||
| 247 | return context.buffer; | ||
| 248 | } | ||
| 249 | |||
| 250 | /** | ||
| 251 | * Extracts buffered data into the provided byte[] array, starting at position bPos, up to a maximum of bAvail | ||
| 252 | * bytes. Returns how many bytes were actually extracted. | ||
| 253 | * <p> | ||
| 254 | * Package protected for access from I/O streams. | ||
| 255 | * | ||
| 256 | * @param b | ||
| 257 | * byte[] array to extract the buffered data into. | ||
| 258 | * @param bPos | ||
| 259 | * position in byte[] array to start extraction at. | ||
| 260 | * @param bAvail | ||
| 261 | * amount of bytes we're allowed to extract. We may extract fewer (if fewer are available). | ||
| 262 | * @param context | ||
| 263 | * the context to be used | ||
| 264 | * @return The number of bytes successfully extracted into the provided byte[] array. | ||
| 265 | */ | ||
| 266 | int readResults(final byte[] b, final int bPos, final int bAvail, final Context context) { | ||
| 267 | if (context.buffer != null) { | ||
| 268 | final int len = Math.min(available(context), bAvail); | ||
| 269 | System.arraycopy(context.buffer, context.readPos, b, bPos, len); | ||
| 270 | context.readPos += len; | ||
| 271 | if (context.readPos >= context.pos) { | ||
| 272 | context.buffer = null; // so hasData() will return false, and this method can return -1 | ||
| 273 | } | ||
| 274 | return len; | ||
| 275 | } | ||
| 276 | return context.eof ? EOF : 0; | ||
| 277 | } | ||
| 278 | |||
| 279 | /** | ||
| 280 | * Checks if a byte value is whitespace or not. | ||
| 281 | * Whitespace is taken to mean: space, tab, CR, LF | ||
| 282 | * @param byteToCheck | ||
| 283 | * the byte to check | ||
| 284 | * @return true if byte is whitespace, false otherwise | ||
| 285 | */ | ||
| 286 | protected static boolean isWhiteSpace(final byte byteToCheck) { | ||
| 287 | switch (byteToCheck) { | ||
| 288 | case ' ' : | ||
| 289 | case '\n' : | ||
| 290 | case '\r' : | ||
| 291 | case '\t' : | ||
| 292 | return true; | ||
| 293 | default : | ||
| 294 | return false; | ||
| 295 | } | ||
| 296 | } | ||
| 297 | |||
| 298 | /** | ||
| 299 | * Encodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of | ||
| 300 | * the Encoder interface, and will throw an EncoderException if the supplied object is not of type byte[]. | ||
| 301 | * | ||
| 302 | * @param obj | ||
| 303 | * Object to encode | ||
| 304 | * @return An object (of type byte[]) containing the Base-N encoded data which corresponds to the byte[] supplied. | ||
| 305 | * @throws EncoderException | ||
| 306 | * if the parameter supplied is not of type byte[] | ||
| 307 | */ | ||
| 308 | @Override | ||
| 309 | public Object encode(final Object obj) throws EncoderException { | ||
| 310 | if (!(obj instanceof byte[])) { | ||
| 311 | throw new EncoderException("Parameter supplied to Base-N encode is not a byte[]"); | ||
| 312 | } | ||
| 313 | return encode((byte[]) obj); | ||
| 314 | } | ||
| 315 | |||
| 316 | /** | ||
| 317 | * Encodes a byte[] containing binary data, into a String containing characters in the Base-N alphabet. | ||
| 318 | * Uses UTF8 encoding. | ||
| 319 | * | ||
| 320 | * @param pArray | ||
| 321 | * a byte array containing binary data | ||
| 322 | * @return A String containing only Base-N character data | ||
| 323 | */ | ||
| 324 | public String encodeToString(final byte[] pArray) { | ||
| 325 | return StringUtils.newStringUtf8(encode(pArray)); | ||
| 326 | } | ||
| 327 | |||
| 328 | /** | ||
| 329 | * Encodes a byte[] containing binary data, into a String containing characters in the appropriate alphabet. | ||
| 330 | * Uses UTF8 encoding. | ||
| 331 | * | ||
| 332 | * @param pArray a byte array containing binary data | ||
| 333 | * @return String containing only character data in the appropriate alphabet. | ||
| 334 | */ | ||
| 335 | public String encodeAsString(final byte[] pArray){ | ||
| 336 | return StringUtils.newStringUtf8(encode(pArray)); | ||
| 337 | } | ||
| 338 | |||
| 339 | /** | ||
| 340 | * Decodes an Object using the Base-N algorithm. This method is provided in order to satisfy the requirements of | ||
| 341 | * the Decoder interface, and will throw a DecoderException if the supplied object is not of type byte[] or String. | ||
| 342 | * | ||
| 343 | * @param obj | ||
| 344 | * Object to decode | ||
| 345 | * @return An object (of type byte[]) containing the binary data which corresponds to the byte[] or String | ||
| 346 | * supplied. | ||
| 347 | * @throws DecoderException | ||
| 348 | * if the parameter supplied is not of type byte[] | ||
| 349 | */ | ||
| 350 | @Override | ||
| 351 | public Object decode(final Object obj) throws DecoderException { | ||
| 352 | if (obj instanceof byte[]) { | ||
| 353 | return decode((byte[]) obj); | ||
| 354 | } else if (obj instanceof String) { | ||
| 355 | return decode((String) obj); | ||
| 356 | } else { | ||
| 357 | throw new DecoderException("Parameter supplied to Base-N decode is not a byte[] or a String"); | ||
| 358 | } | ||
| 359 | } | ||
| 360 | |||
| 361 | /** | ||
| 362 | * Decodes a String containing characters in the Base-N alphabet. | ||
| 363 | * | ||
| 364 | * @param pArray | ||
| 365 | * A String containing Base-N character data | ||
| 366 | * @return a byte array containing binary data | ||
| 367 | */ | ||
| 368 | public byte[] decode(final String pArray) { | ||
| 369 | return decode(StringUtils.getBytesUtf8(pArray)); | ||
| 370 | } | ||
| 371 | |||
| 372 | /** | ||
| 373 | * Decodes a byte[] containing characters in the Base-N alphabet. | ||
| 374 | * | ||
| 375 | * @param pArray | ||
| 376 | * A byte array containing Base-N character data | ||
| 377 | * @return a byte array containing binary data | ||
| 378 | */ | ||
| 379 | @Override | ||
| 380 | public byte[] decode(final byte[] pArray) { | ||
| 381 | if (pArray == null || pArray.length == 0) { | ||
| 382 | return pArray; | ||
| 383 | } | ||
| 384 | final Context context = new Context(); | ||
| 385 | decode(pArray, 0, pArray.length, context); | ||
| 386 | decode(pArray, 0, EOF, context); // Notify decoder of EOF. | ||
| 387 | final byte[] result = new byte[context.pos]; | ||
| 388 | readResults(result, 0, result.length, context); | ||
| 389 | return result; | ||
| 390 | } | ||
| 391 | |||
| 392 | /** | ||
| 393 | * Encodes a byte[] containing binary data, into a byte[] containing characters in the alphabet. | ||
| 394 | * | ||
| 395 | * @param pArray | ||
| 396 | * a byte array containing binary data | ||
| 397 | * @return A byte array containing only the basen alphabetic character data | ||
| 398 | */ | ||
| 399 | @Override | ||
| 400 | public byte[] encode(final byte[] pArray) { | ||
| 401 | if (pArray == null || pArray.length == 0) { | ||
| 402 | return pArray; | ||
| 403 | } | ||
| 404 | final Context context = new Context(); | ||
| 405 | encode(pArray, 0, pArray.length, context); | ||
| 406 | encode(pArray, 0, EOF, context); // Notify encoder of EOF. | ||
| 407 | final byte[] buf = new byte[context.pos - context.readPos]; | ||
| 408 | readResults(buf, 0, buf.length, context); | ||
| 409 | return buf; | ||
| 410 | } | ||
| 411 | |||
| 412 | // package protected for access from I/O streams | ||
| 413 | abstract void encode(byte[] pArray, int i, int length, Context context); | ||
| 414 | |||
| 415 | // package protected for access from I/O streams | ||
| 416 | abstract void decode(byte[] pArray, int i, int length, Context context); | ||
| 417 | |||
| 418 | /** | ||
| 419 | * Returns whether or not the <code>octet</code> is in the current alphabet. | ||
| 420 | * Does not allow whitespace or pad. | ||
| 421 | * | ||
| 422 | * @param value The value to test | ||
| 423 | * | ||
| 424 | * @return {@code true} if the value is defined in the current alphabet, {@code false} otherwise. | ||
| 425 | */ | ||
| 426 | protected abstract boolean isInAlphabet(byte value); | ||
| 427 | |||
| 428 | /** | ||
| 429 | * Tests a given byte array to see if it contains only valid characters within the alphabet. | ||
| 430 | * The method optionally treats whitespace and pad as valid. | ||
| 431 | * | ||
| 432 | * @param arrayOctet byte array to test | ||
| 433 | * @param allowWSPad if {@code true}, then whitespace and PAD are also allowed | ||
| 434 | * | ||
| 435 | * @return {@code true} if all bytes are valid characters in the alphabet or if the byte array is empty; | ||
| 436 | * {@code false}, otherwise | ||
| 437 | */ | ||
| 438 | public boolean isInAlphabet(final byte[] arrayOctet, final boolean allowWSPad) { | ||
| 439 | for (int i = 0; i < arrayOctet.length; i++) { | ||
| 440 | if (!isInAlphabet(arrayOctet[i]) && | ||
| 441 | (!allowWSPad || (arrayOctet[i] != PAD) && !isWhiteSpace(arrayOctet[i]))) { | ||
| 442 | return false; | ||
| 443 | } | ||
| 444 | } | ||
| 445 | return true; | ||
| 446 | } | ||
| 447 | |||
| 448 | /** | ||
| 449 | * Tests a given String to see if it contains only valid characters within the alphabet. | ||
| 450 | * The method treats whitespace and PAD as valid. | ||
| 451 | * | ||
| 452 | * @param basen String to test | ||
| 453 | * @return {@code true} if all characters in the String are valid characters in the alphabet or if | ||
| 454 | * the String is empty; {@code false}, otherwise | ||
| 455 | * @see #isInAlphabet(byte[], boolean) | ||
| 456 | */ | ||
| 457 | public boolean isInAlphabet(final String basen) { | ||
| 458 | return isInAlphabet(StringUtils.getBytesUtf8(basen), true); | ||
| 459 | } | ||
| 460 | |||
| 461 | /** | ||
| 462 | * Tests a given byte array to see if it contains any characters within the alphabet or PAD. | ||
| 463 | * | ||
| 464 | * Intended for use in checking line-ending arrays | ||
| 465 | * | ||
| 466 | * @param arrayOctet | ||
| 467 | * byte array to test | ||
| 468 | * @return {@code true} if any byte is a valid character in the alphabet or PAD; {@code false} otherwise | ||
| 469 | */ | ||
| 470 | protected boolean containsAlphabetOrPad(final byte[] arrayOctet) { | ||
| 471 | if (arrayOctet == null) { | ||
| 472 | return false; | ||
| 473 | } | ||
| 474 | for (final byte element : arrayOctet) { | ||
| 475 | if (PAD == element || isInAlphabet(element)) { | ||
| 476 | return true; | ||
| 477 | } | ||
| 478 | } | ||
| 479 | return false; | ||
| 480 | } | ||
| 481 | |||
| 482 | /** | ||
| 483 | * Calculates the amount of space needed to encode the supplied array. | ||
| 484 | * | ||
| 485 | * @param pArray byte[] array which will later be encoded | ||
| 486 | * | ||
| 487 | * @return amount of space needed to encoded the supplied array. | ||
| 488 | * Returns a long since a max-len array will require > Integer.MAX_VALUE | ||
| 489 | */ | ||
| 490 | public long getEncodedLength(final byte[] pArray) { | ||
| 491 | // Calculate non-chunked size - rounded up to allow for padding | ||
| 492 | // cast to long is needed to avoid possibility of overflow | ||
| 493 | long len = ((pArray.length + unencodedBlockSize-1) / unencodedBlockSize) * (long) encodedBlockSize; | ||
| 494 | if (lineLength > 0) { // We're using chunking | ||
| 495 | // Round up to nearest multiple | ||
| 496 | len += ((len + lineLength-1) / lineLength) * chunkSeparatorLength; | ||
| 497 | } | ||
| 498 | return len; | ||
| 499 | } | ||
| 500 | } | ||
diff --git a/java/client/src/org/apache/commons/codec/binary/StringUtils.java b/java/client/src/org/apache/commons/codec/binary/StringUtils.java new file mode 100644 index 0000000..14a362f --- /dev/null +++ b/java/client/src/org/apache/commons/codec/binary/StringUtils.java | |||
| @@ -0,0 +1,343 @@ | |||
| 1 | /* | ||
| 2 | * Licensed to the Apache Software Foundation (ASF) under one or more | ||
| 3 | * contributor license agreements. See the NOTICE file distributed with | ||
| 4 | * this work for additional information regarding copyright ownership. | ||
| 5 | * The ASF licenses this file to You under the Apache License, Version 2.0 | ||
| 6 | * (the "License"); you may not use this file except in compliance with | ||
| 7 | * the License. You may obtain a copy of the License at | ||
| 8 | * | ||
| 9 | * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 10 | * | ||
| 11 | * Unless required by applicable law or agreed to in writing, software | ||
| 12 | * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 14 | * See the License for the specific language governing permissions and | ||
| 15 | * limitations under the License. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package org.apache.commons.codec.binary; | ||
| 19 | |||
| 20 | import java.io.UnsupportedEncodingException; | ||
| 21 | import java.nio.charset.Charset; | ||
| 22 | |||
| 23 | import org.apache.commons.codec.CharEncoding; | ||
| 24 | import org.apache.commons.codec.Charsets; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Converts String to and from bytes using the encodings required by the Java specification. These encodings are | ||
| 28 | * specified in <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html"> | ||
| 29 | * Standard charsets</a>. | ||
| 30 | * | ||
| 31 | * <p>This class is immutable and thread-safe.</p> | ||
| 32 | * | ||
| 33 | * @see CharEncoding | ||
| 34 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 35 | * @version $Id$ | ||
| 36 | * @since 1.4 | ||
| 37 | */ | ||
| 38 | public class StringUtils { | ||
| 39 | |||
| 40 | /** | ||
| 41 | * Calls {@link String#getBytes(Charset)} | ||
| 42 | * | ||
| 43 | * @param string | ||
| 44 | * The string to encode (if null, return null). | ||
| 45 | * @param charset | ||
| 46 | * The {@link Charset} to encode the {@code String} | ||
| 47 | * @return the encoded bytes | ||
| 48 | */ | ||
| 49 | private static byte[] getBytes(final String string, final Charset charset) { | ||
| 50 | if (string == null) { | ||
| 51 | return null; | ||
| 52 | } | ||
| 53 | return string.getBytes(charset); | ||
| 54 | } | ||
| 55 | |||
| 56 | /** | ||
| 57 | * Encodes the given string into a sequence of bytes using the ISO-8859-1 charset, storing the result into a new | ||
| 58 | * byte array. | ||
| 59 | * | ||
| 60 | * @param string | ||
| 61 | * the String to encode, may be {@code null} | ||
| 62 | * @return encoded bytes, or {@code null} if the input string was {@code null} | ||
| 63 | * @throws NullPointerException | ||
| 64 | * Thrown if {@link Charsets#ISO_8859_1} is not initialized, which should never happen since it is | ||
| 65 | * required by the Java platform specification. | ||
| 66 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException | ||
| 67 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 68 | * @see #getBytesUnchecked(String, String) | ||
| 69 | */ | ||
| 70 | public static byte[] getBytesIso8859_1(final String string) { | ||
| 71 | return getBytes(string, Charsets.ISO_8859_1); | ||
| 72 | } | ||
| 73 | |||
| 74 | |||
| 75 | /** | ||
| 76 | * Encodes the given string into a sequence of bytes using the named charset, storing the result into a new byte | ||
| 77 | * array. | ||
| 78 | * <p> | ||
| 79 | * This method catches {@link UnsupportedEncodingException} and rethrows it as {@link IllegalStateException}, which | ||
| 80 | * should never happen for a required charset name. Use this method when the encoding is required to be in the JRE. | ||
| 81 | * </p> | ||
| 82 | * | ||
| 83 | * @param string | ||
| 84 | * the String to encode, may be {@code null} | ||
| 85 | * @param charsetName | ||
| 86 | * The name of a required {@link java.nio.charset.Charset} | ||
| 87 | * @return encoded bytes, or {@code null} if the input string was {@code null} | ||
| 88 | * @throws IllegalStateException | ||
| 89 | * Thrown when a {@link UnsupportedEncodingException} is caught, which should never happen for a | ||
| 90 | * required charset name. | ||
| 91 | * @see CharEncoding | ||
| 92 | * @see String#getBytes(String) | ||
| 93 | */ | ||
| 94 | public static byte[] getBytesUnchecked(final String string, final String charsetName) { | ||
| 95 | if (string == null) { | ||
| 96 | return null; | ||
| 97 | } | ||
| 98 | try { | ||
| 99 | return string.getBytes(charsetName); | ||
| 100 | } catch (final UnsupportedEncodingException e) { | ||
| 101 | throw StringUtils.newIllegalStateException(charsetName, e); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | |||
| 105 | /** | ||
| 106 | * Encodes the given string into a sequence of bytes using the US-ASCII charset, storing the result into a new byte | ||
| 107 | * array. | ||
| 108 | * | ||
| 109 | * @param string | ||
| 110 | * the String to encode, may be {@code null} | ||
| 111 | * @return encoded bytes, or {@code null} if the input string was {@code null} | ||
| 112 | * @throws NullPointerException | ||
| 113 | * Thrown if {@link Charsets#US_ASCII} is not initialized, which should never happen since it is | ||
| 114 | * required by the Java platform specification. | ||
| 115 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException | ||
| 116 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 117 | * @see #getBytesUnchecked(String, String) | ||
| 118 | */ | ||
| 119 | public static byte[] getBytesUsAscii(final String string) { | ||
| 120 | return getBytes(string, Charsets.US_ASCII); | ||
| 121 | } | ||
| 122 | |||
| 123 | /** | ||
| 124 | * Encodes the given string into a sequence of bytes using the UTF-16 charset, storing the result into a new byte | ||
| 125 | * array. | ||
| 126 | * | ||
| 127 | * @param string | ||
| 128 | * the String to encode, may be {@code null} | ||
| 129 | * @return encoded bytes, or {@code null} if the input string was {@code null} | ||
| 130 | * @throws NullPointerException | ||
| 131 | * Thrown if {@link Charsets#UTF_16} is not initialized, which should never happen since it is | ||
| 132 | * required by the Java platform specification. | ||
| 133 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException | ||
| 134 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 135 | * @see #getBytesUnchecked(String, String) | ||
| 136 | */ | ||
| 137 | public static byte[] getBytesUtf16(final String string) { | ||
| 138 | return getBytes(string, Charsets.UTF_16); | ||
| 139 | } | ||
| 140 | |||
| 141 | /** | ||
| 142 | * Encodes the given string into a sequence of bytes using the UTF-16BE charset, storing the result into a new byte | ||
| 143 | * array. | ||
| 144 | * | ||
| 145 | * @param string | ||
| 146 | * the String to encode, may be {@code null} | ||
| 147 | * @return encoded bytes, or {@code null} if the input string was {@code null} | ||
| 148 | * @throws NullPointerException | ||
| 149 | * Thrown if {@link Charsets#UTF_16BE} is not initialized, which should never happen since it is | ||
| 150 | * required by the Java platform specification. | ||
| 151 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException | ||
| 152 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 153 | * @see #getBytesUnchecked(String, String) | ||
| 154 | */ | ||
| 155 | public static byte[] getBytesUtf16Be(final String string) { | ||
| 156 | return getBytes(string, Charsets.UTF_16BE); | ||
| 157 | } | ||
| 158 | |||
| 159 | /** | ||
| 160 | * Encodes the given string into a sequence of bytes using the UTF-16LE charset, storing the result into a new byte | ||
| 161 | * array. | ||
| 162 | * | ||
| 163 | * @param string | ||
| 164 | * the String to encode, may be {@code null} | ||
| 165 | * @return encoded bytes, or {@code null} if the input string was {@code null} | ||
| 166 | * @throws NullPointerException | ||
| 167 | * Thrown if {@link Charsets#UTF_16LE} is not initialized, which should never happen since it is | ||
| 168 | * required by the Java platform specification. | ||
| 169 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException | ||
| 170 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 171 | * @see #getBytesUnchecked(String, String) | ||
| 172 | */ | ||
| 173 | public static byte[] getBytesUtf16Le(final String string) { | ||
| 174 | return getBytes(string, Charsets.UTF_16LE); | ||
| 175 | } | ||
| 176 | |||
| 177 | /** | ||
| 178 | * Encodes the given string into a sequence of bytes using the UTF-8 charset, storing the result into a new byte | ||
| 179 | * array. | ||
| 180 | * | ||
| 181 | * @param string | ||
| 182 | * the String to encode, may be {@code null} | ||
| 183 | * @return encoded bytes, or {@code null} if the input string was {@code null} | ||
| 184 | * @throws NullPointerException | ||
| 185 | * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is | ||
| 186 | * required by the Java platform specification. | ||
| 187 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException | ||
| 188 | * @see <a href="http://download.oracle.com/javase/6/docs/api/java/nio/charset/Charset.html">Standard charsets</a> | ||
| 189 | * @see #getBytesUnchecked(String, String) | ||
| 190 | */ | ||
| 191 | public static byte[] getBytesUtf8(final String string) { | ||
| 192 | return getBytes(string, Charsets.UTF_8); | ||
| 193 | } | ||
| 194 | |||
| 195 | private static IllegalStateException newIllegalStateException(final String charsetName, | ||
| 196 | final UnsupportedEncodingException e) { | ||
| 197 | return new IllegalStateException(charsetName + ": " + e); | ||
| 198 | } | ||
| 199 | |||
| 200 | /** | ||
| 201 | * Constructs a new <code>String</code> by decoding the specified array of bytes using the given charset. | ||
| 202 | * | ||
| 203 | * @param bytes | ||
| 204 | * The bytes to be decoded into characters | ||
| 205 | * @param charset | ||
| 206 | * The {@link Charset} to encode the {@code String} | ||
| 207 | * @return A new <code>String</code> decoded from the specified array of bytes using the given charset, | ||
| 208 | * or {@code null} if the input byte array was {@code null}. | ||
| 209 | * @throws NullPointerException | ||
| 210 | * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is | ||
| 211 | * required by the Java platform specification. | ||
| 212 | */ | ||
| 213 | private static String newString(final byte[] bytes, final Charset charset) { | ||
| 214 | return bytes == null ? null : new String(bytes, charset); | ||
| 215 | } | ||
| 216 | |||
| 217 | /** | ||
| 218 | * Constructs a new <code>String</code> by decoding the specified array of bytes using the given charset. | ||
| 219 | * <p> | ||
| 220 | * This method catches {@link UnsupportedEncodingException} and re-throws it as {@link IllegalStateException}, which | ||
| 221 | * should never happen for a required charset name. Use this method when the encoding is required to be in the JRE. | ||
| 222 | * </p> | ||
| 223 | * | ||
| 224 | * @param bytes | ||
| 225 | * The bytes to be decoded into characters, may be {@code null} | ||
| 226 | * @param charsetName | ||
| 227 | * The name of a required {@link java.nio.charset.Charset} | ||
| 228 | * @return A new <code>String</code> decoded from the specified array of bytes using the given charset, | ||
| 229 | * or {@code null} if the input byte array was {@code null}. | ||
| 230 | * @throws IllegalStateException | ||
| 231 | * Thrown when a {@link UnsupportedEncodingException} is caught, which should never happen for a | ||
| 232 | * required charset name. | ||
| 233 | * @see CharEncoding | ||
| 234 | * @see String#String(byte[], String) | ||
| 235 | */ | ||
| 236 | public static String newString(final byte[] bytes, final String charsetName) { | ||
| 237 | if (bytes == null) { | ||
| 238 | return null; | ||
| 239 | } | ||
| 240 | try { | ||
| 241 | return new String(bytes, charsetName); | ||
| 242 | } catch (final UnsupportedEncodingException e) { | ||
| 243 | throw StringUtils.newIllegalStateException(charsetName, e); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | /** | ||
| 248 | * Constructs a new <code>String</code> by decoding the specified array of bytes using the ISO-8859-1 charset. | ||
| 249 | * | ||
| 250 | * @param bytes | ||
| 251 | * The bytes to be decoded into characters, may be {@code null} | ||
| 252 | * @return A new <code>String</code> decoded from the specified array of bytes using the ISO-8859-1 charset, or | ||
| 253 | * {@code null} if the input byte array was {@code null}. | ||
| 254 | * @throws NullPointerException | ||
| 255 | * Thrown if {@link Charsets#ISO_8859_1} is not initialized, which should never happen since it is | ||
| 256 | * required by the Java platform specification. | ||
| 257 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException | ||
| 258 | */ | ||
| 259 | public static String newStringIso8859_1(final byte[] bytes) { | ||
| 260 | return new String(bytes, Charsets.ISO_8859_1); | ||
| 261 | } | ||
| 262 | |||
| 263 | /** | ||
| 264 | * Constructs a new <code>String</code> by decoding the specified array of bytes using the US-ASCII charset. | ||
| 265 | * | ||
| 266 | * @param bytes | ||
| 267 | * The bytes to be decoded into characters | ||
| 268 | * @return A new <code>String</code> decoded from the specified array of bytes using the US-ASCII charset, | ||
| 269 | * or {@code null} if the input byte array was {@code null}. | ||
| 270 | * @throws NullPointerException | ||
| 271 | * Thrown if {@link Charsets#US_ASCII} is not initialized, which should never happen since it is | ||
| 272 | * required by the Java platform specification. | ||
| 273 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException | ||
| 274 | */ | ||
| 275 | public static String newStringUsAscii(final byte[] bytes) { | ||
| 276 | return new String(bytes, Charsets.US_ASCII); | ||
| 277 | } | ||
| 278 | |||
| 279 | /** | ||
| 280 | * Constructs a new <code>String</code> by decoding the specified array of bytes using the UTF-16 charset. | ||
| 281 | * | ||
| 282 | * @param bytes | ||
| 283 | * The bytes to be decoded into characters | ||
| 284 | * @return A new <code>String</code> decoded from the specified array of bytes using the UTF-16 charset | ||
| 285 | * or {@code null} if the input byte array was {@code null}. | ||
| 286 | * @throws NullPointerException | ||
| 287 | * Thrown if {@link Charsets#UTF_16} is not initialized, which should never happen since it is | ||
| 288 | * required by the Java platform specification. | ||
| 289 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException | ||
| 290 | */ | ||
| 291 | public static String newStringUtf16(final byte[] bytes) { | ||
| 292 | return new String(bytes, Charsets.UTF_16); | ||
| 293 | } | ||
| 294 | |||
| 295 | /** | ||
| 296 | * Constructs a new <code>String</code> by decoding the specified array of bytes using the UTF-16BE charset. | ||
| 297 | * | ||
| 298 | * @param bytes | ||
| 299 | * The bytes to be decoded into characters | ||
| 300 | * @return A new <code>String</code> decoded from the specified array of bytes using the UTF-16BE charset, | ||
| 301 | * or {@code null} if the input byte array was {@code null}. | ||
| 302 | * @throws NullPointerException | ||
| 303 | * Thrown if {@link Charsets#UTF_16BE} is not initialized, which should never happen since it is | ||
| 304 | * required by the Java platform specification. | ||
| 305 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException | ||
| 306 | */ | ||
| 307 | public static String newStringUtf16Be(final byte[] bytes) { | ||
| 308 | return new String(bytes, Charsets.UTF_16BE); | ||
| 309 | } | ||
| 310 | |||
| 311 | /** | ||
| 312 | * Constructs a new <code>String</code> by decoding the specified array of bytes using the UTF-16LE charset. | ||
| 313 | * | ||
| 314 | * @param bytes | ||
| 315 | * The bytes to be decoded into characters | ||
| 316 | * @return A new <code>String</code> decoded from the specified array of bytes using the UTF-16LE charset, | ||
| 317 | * or {@code null} if the input byte array was {@code null}. | ||
| 318 | * @throws NullPointerException | ||
| 319 | * Thrown if {@link Charsets#UTF_16LE} is not initialized, which should never happen since it is | ||
| 320 | * required by the Java platform specification. | ||
| 321 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException | ||
| 322 | */ | ||
| 323 | public static String newStringUtf16Le(final byte[] bytes) { | ||
| 324 | return new String(bytes, Charsets.UTF_16LE); | ||
| 325 | } | ||
| 326 | |||
| 327 | /** | ||
| 328 | * Constructs a new <code>String</code> by decoding the specified array of bytes using the UTF-8 charset. | ||
| 329 | * | ||
| 330 | * @param bytes | ||
| 331 | * The bytes to be decoded into characters | ||
| 332 | * @return A new <code>String</code> decoded from the specified array of bytes using the UTF-8 charset, | ||
| 333 | * or {@code null} if the input byte array was {@code null}. | ||
| 334 | * @throws NullPointerException | ||
| 335 | * Thrown if {@link Charsets#UTF_8} is not initialized, which should never happen since it is | ||
| 336 | * required by the Java platform specification. | ||
| 337 | * @since As of 1.7, throws {@link NullPointerException} instead of UnsupportedEncodingException | ||
| 338 | */ | ||
| 339 | public static String newStringUtf8(final byte[] bytes) { | ||
| 340 | return newString(bytes, Charsets.UTF_8); | ||
| 341 | } | ||
| 342 | |||
| 343 | } | ||
diff --git a/midori/config b/midori/config new file mode 100644 index 0000000..95c33cb --- /dev/null +++ b/midori/config | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | [settings] | ||
| 2 | default-encoding=ISO-8859-1 | ||
| 3 | enable-site-specific-quirks=true | ||
| 4 | last-window-width=800 | ||
| 5 | last-window-height=600 | ||
| 6 | show-menubar=false | ||
| 7 | show-navigationbar=false | ||
| 8 | enable-html5-database=true | ||
| 9 | user-agent=Mozilla/5.0 (Macintosh; U; Intel Mac OS X; en-gb) AppleWebKit/535+ (KHTML, like Gecko) Version/5.0 Safari/535.22+ Midori/0.4 | ||
| @@ -0,0 +1,19 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # WebIOPi PiStore launch script | ||
| 3 | |||
| 4 | # Force install | ||
| 5 | #VERSION=`python -c "import webiopi; print(webiopi.VERSION)"` | ||
| 6 | #if [ "$VERSION" != "0.5.3" ]; then | ||
| 7 | # echo "Update required..." | ||
| 8 | # chmod 777 setup.sh | ||
| 9 | # sudo ./setup.sh | ||
| 10 | #fi | ||
| 11 | |||
| 12 | # Start WebIOPi service | ||
| 13 | sudo /etc/init.d/webiopi start | ||
| 14 | |||
| 15 | # Launch the browser | ||
| 16 | midori -c `pwd`/midori -a http://localhost:8000/ | ||
| 17 | |||
| 18 | # Stop WebIOPi service | ||
| 19 | sudo /etc/init.d/webiopi stop | ||
diff --git a/python/config b/python/config new file mode 100644 index 0000000..77b99ec --- /dev/null +++ b/python/config | |||
| @@ -0,0 +1,136 @@ | |||
| 1 | [GPIO] | ||
| 2 | # Initialize following GPIOs with given function and optional value | ||
| 3 | # This is used during WebIOPi start process | ||
| 4 | #21 = IN | ||
| 5 | #23 = OUT 0 | ||
| 6 | #24 = OUT 0 | ||
| 7 | #25 = OUT 1 | ||
| 8 | |||
| 9 | #------------------------------------------------------------------------# | ||
| 10 | |||
| 11 | [~GPIO] | ||
| 12 | # Reset following GPIOs with given function and optional value | ||
| 13 | # This is used at the end of WebIOPi stop process | ||
| 14 | #21 = IN | ||
| 15 | #23 = IN | ||
| 16 | #24 = IN | ||
| 17 | #25 = OUT 0 | ||
| 18 | |||
| 19 | #------------------------------------------------------------------------# | ||
| 20 | |||
| 21 | [SCRIPTS] | ||
| 22 | # Load custom scripts syntax : | ||
| 23 | # name = sourcefile | ||
| 24 | # each sourcefile may have setup, loop and destroy functions and macros | ||
| 25 | #myscript = /home/pi/webiopi/examples/scripts/macros/script.py | ||
| 26 | |||
| 27 | #------------------------------------------------------------------------# | ||
| 28 | |||
| 29 | [HTTP] | ||
| 30 | # HTTP Server configuration | ||
| 31 | enabled = true | ||
| 32 | port = 8000 | ||
| 33 | |||
| 34 | # File containing sha256(base64("user:password")) | ||
| 35 | # Use webiopi-passwd command to generate it | ||
| 36 | passwd-file = /etc/webiopi/passwd | ||
| 37 | |||
| 38 | # Use doc-root to change default HTML and resource files location | ||
| 39 | #doc-root = /home/pi/webiopi/examples/scripts/macros | ||
| 40 | |||
| 41 | # Use welcome-file to change the default "Welcome" file | ||
| 42 | #welcome-file = index.html | ||
| 43 | |||
| 44 | #------------------------------------------------------------------------# | ||
| 45 | |||
| 46 | [COAP] | ||
| 47 | # CoAP Server configuration | ||
| 48 | enabled = true | ||
| 49 | port = 5683 | ||
| 50 | # Enable CoAP multicast | ||
| 51 | multicast = true | ||
| 52 | |||
| 53 | #------------------------------------------------------------------------# | ||
| 54 | |||
| 55 | [DEVICES] | ||
| 56 | # Device configuration syntax: | ||
| 57 | # name = device [args...] | ||
| 58 | # name : used in the URL mapping | ||
| 59 | # device : device name | ||
| 60 | # args : (optional) see device driver doc | ||
| 61 | # If enabled, devices configured here are mapped on REST API /device/name | ||
| 62 | # Devices are also accessible in custom scripts using deviceInstance(name) | ||
| 63 | # See device driver doc for methods and URI scheme available | ||
| 64 | |||
| 65 | # Raspberry native UART on GPIO, uncomment to enable | ||
| 66 | # Don't forget to remove console on ttyAMA0 in /boot/cmdline.txt | ||
| 67 | # And also disable getty on ttyAMA0 in /etc/inittab | ||
| 68 | #serial0 = Serial device:ttyAMA0 baudrate:9600 | ||
| 69 | |||
| 70 | # USB serial adapters | ||
| 71 | #usb0 = Serial device:ttyUSB0 baudrate:9600 | ||
| 72 | #usb1 = Serial device:ttyACM0 baudrate:9600 | ||
| 73 | |||
| 74 | #temp0 = TMP102 | ||
| 75 | #temp1 = TMP102 slave:0x49 | ||
| 76 | #temp2 = DS18B20 | ||
| 77 | #temp3 = DS18B20 slave:28-0000049bc218 | ||
| 78 | |||
| 79 | #bmp = BMP085 | ||
| 80 | |||
| 81 | #gpio0 = PCF8574 | ||
| 82 | #gpio1 = PCF8574 slave:0x21 | ||
| 83 | |||
| 84 | #light0 = TSL2561T | ||
| 85 | #light1 = TSL2561T slave:0b0101001 | ||
| 86 | |||
| 87 | #gpio0 = MCP23017 | ||
| 88 | #gpio1 = MCP23017 slave:0x21 | ||
| 89 | #gpio2 = MCP23017 slave:0x22 | ||
| 90 | |||
| 91 | #pwm0 = PCA9685 | ||
| 92 | #pwm1 = PCA9685 slave:0x41 | ||
| 93 | |||
| 94 | #adc = MCP3008 | ||
| 95 | #dac = MCP4922 chip:1 | ||
| 96 | |||
| 97 | #------------------------------------------------------------------------# | ||
| 98 | |||
| 99 | [REST] | ||
| 100 | # By default, REST API allows to GET/POST on all GPIOs | ||
| 101 | # Use gpio-export to limit GPIO available through REST API | ||
| 102 | #gpio-export = 21, 23, 24, 25 | ||
| 103 | |||
| 104 | # Uncomment to forbid changing GPIO values | ||
| 105 | #gpio-post-value = false | ||
| 106 | |||
| 107 | # Uncomment to forbid changing GPIO functions | ||
| 108 | #gpio-post-function = false | ||
| 109 | |||
| 110 | # Uncomment to disable automatic device mapping | ||
| 111 | #device-mapping = false | ||
| 112 | |||
| 113 | #------------------------------------------------------------------------# | ||
| 114 | |||
| 115 | [ROUTES] | ||
| 116 | # Custom REST API route syntax : | ||
| 117 | # source = destination | ||
| 118 | # source : URL to route | ||
| 119 | # destination : Resulting URL | ||
| 120 | # Adding routes allows to simplify access with Human comprehensive URLs | ||
| 121 | |||
| 122 | # In the next example with have the bedroom light connected to GPIO 25 | ||
| 123 | # and a temperature sensor named temp2, defined in [DEVICES] section | ||
| 124 | # - GET /bedroom/light => GET /GPIO/25/value, returns the light state | ||
| 125 | # - POST /bedroom/light/0 => POST /GPIO/25/value/0, turn off the light | ||
| 126 | # - POST /bedroom/light/1 => POST /GPIO/25/value/1, turn on the light | ||
| 127 | # - GET /bedroom/temperature => GET /devices/temp2/temperature/c, returns the temperature in celsius | ||
| 128 | #/bedroom/light = /GPIO/25/value | ||
| 129 | #/bedroom/temperature = /devices/temp2/temperature/c | ||
| 130 | |||
| 131 | #/livingroom/light = /devices/expander0/0 | ||
| 132 | #/livingroom/brightness = /devices/adc/0/float | ||
| 133 | #/livingroom/temperature = /devices/temp0/temperature/c | ||
| 134 | |||
| 135 | #/weather/temperature = /devices/bmp/temperature/c | ||
| 136 | #/weather/pressure = /devices/bmp/pressure/hpa | ||
diff --git a/python/native/bridge.c b/python/native/bridge.c new file mode 100644 index 0000000..8b2b8da --- /dev/null +++ b/python/native/bridge.c | |||
| @@ -0,0 +1,720 @@ | |||
| 1 | /* | ||
| 2 | Copyright (c) 2012 Ben Croston / 2012-2013 Eric PTAK | ||
| 3 | |||
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| 5 | this software and associated documentation files (the "Software"), to deal in | ||
| 6 | the Software without restriction, including without limitation the rights to | ||
| 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
| 8 | of the Software, and to permit persons to whom the Software is furnished to do | ||
| 9 | so, subject to the following conditions: | ||
| 10 | |||
| 11 | The above copyright notice and this permission notice shall be included in all | ||
| 12 | copies or substantial portions of the Software. | ||
| 13 | |||
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 20 | SOFTWARE. | ||
| 21 | */ | ||
| 22 | |||
| 23 | #include "Python.h" | ||
| 24 | #include "gpio.h" | ||
| 25 | #include "cpuinfo.h" | ||
| 26 | |||
| 27 | static PyObject *_SetupException; | ||
| 28 | static PyObject *_InvalidDirectionException; | ||
| 29 | static PyObject *_InvalidChannelException; | ||
| 30 | static PyObject *_InvalidPullException; | ||
| 31 | |||
| 32 | static PyObject *_gpioCount; | ||
| 33 | |||
| 34 | |||
| 35 | static PyObject *_low; | ||
| 36 | static PyObject *_high; | ||
| 37 | |||
| 38 | static PyObject *_in; | ||
| 39 | static PyObject *_out; | ||
| 40 | static PyObject *_alt0; | ||
| 41 | static PyObject *_alt1; | ||
| 42 | static PyObject *_alt2; | ||
| 43 | static PyObject *_alt3; | ||
| 44 | static PyObject *_alt4; | ||
| 45 | static PyObject *_alt5; | ||
| 46 | static PyObject *_pwm; | ||
| 47 | |||
| 48 | static PyObject *_pud_off; | ||
| 49 | static PyObject *_pud_up; | ||
| 50 | static PyObject *_pud_down; | ||
| 51 | |||
| 52 | static PyObject *_board_revision; | ||
| 53 | |||
| 54 | static char* FUNCTIONS[] = {"IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3", "PWM"}; | ||
| 55 | static char* PWM_MODES[] = {"none", "ratio", "angle"}; | ||
| 56 | |||
| 57 | static int module_state = -1; | ||
| 58 | |||
| 59 | // setup function run on import of the RPi.GPIO module | ||
| 60 | static int module_setup(void) | ||
| 61 | { | ||
| 62 | if (module_state == SETUP_OK) { | ||
| 63 | return SETUP_OK; | ||
| 64 | } | ||
| 65 | |||
| 66 | module_state = setup(); | ||
| 67 | if (module_state == SETUP_DEVMEM_FAIL) | ||
| 68 | { | ||
| 69 | PyErr_SetString(_SetupException, "No access to /dev/mem. Try running as root!"); | ||
| 70 | } else if (module_state == SETUP_MALLOC_FAIL) { | ||
| 71 | PyErr_NoMemory(); | ||
| 72 | } else if (module_state == SETUP_MMAP_FAIL) { | ||
| 73 | PyErr_SetString(_SetupException, "Mmap failed on module import"); | ||
| 74 | } | ||
| 75 | |||
| 76 | return module_state; | ||
| 77 | } | ||
| 78 | |||
| 79 | // python function getFunction(channel) | ||
| 80 | static PyObject *py_get_function(PyObject *self, PyObject *args) | ||
| 81 | { | ||
| 82 | if (module_setup() != SETUP_OK) { | ||
| 83 | return NULL; | ||
| 84 | } | ||
| 85 | |||
| 86 | int channel, f; | ||
| 87 | |||
| 88 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 89 | return NULL; | ||
| 90 | |||
| 91 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 92 | { | ||
| 93 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 94 | return NULL; | ||
| 95 | } | ||
| 96 | |||
| 97 | f = get_function(channel); | ||
| 98 | return Py_BuildValue("i", f); | ||
| 99 | } | ||
| 100 | |||
| 101 | // python function getFunctionString(channel) | ||
| 102 | static PyObject *py_get_function_string(PyObject *self, PyObject *args) | ||
| 103 | { | ||
| 104 | if (module_setup() != SETUP_OK) { | ||
| 105 | return NULL; | ||
| 106 | } | ||
| 107 | |||
| 108 | int channel, f; | ||
| 109 | char *str; | ||
| 110 | |||
| 111 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 112 | return NULL; | ||
| 113 | |||
| 114 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 115 | { | ||
| 116 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 117 | return NULL; | ||
| 118 | } | ||
| 119 | |||
| 120 | f = get_function(channel); | ||
| 121 | str = FUNCTIONS[f]; | ||
| 122 | return Py_BuildValue("s", str); | ||
| 123 | } | ||
| 124 | |||
| 125 | // python function setFunction(channel, direction, pull_up_down=PUD_OFF) | ||
| 126 | static PyObject *py_set_function(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 127 | { | ||
| 128 | if (module_setup() != SETUP_OK) { | ||
| 129 | return NULL; | ||
| 130 | } | ||
| 131 | |||
| 132 | int channel, function; | ||
| 133 | int pud = PUD_OFF; | ||
| 134 | static char *kwlist[] = {"channel", "function", "pull_up_down", NULL}; | ||
| 135 | |||
| 136 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii|i", kwlist, &channel, &function, &pud)) | ||
| 137 | return NULL; | ||
| 138 | |||
| 139 | if (function != IN && function != OUT && function != PWM) | ||
| 140 | { | ||
| 141 | PyErr_SetString(_InvalidDirectionException, "Invalid function"); | ||
| 142 | return NULL; | ||
| 143 | } | ||
| 144 | |||
| 145 | if (function == OUT || function == PWM) | ||
| 146 | pud = PUD_OFF; | ||
| 147 | |||
| 148 | if (pud != PUD_OFF && pud != PUD_DOWN && pud != PUD_UP) | ||
| 149 | { | ||
| 150 | PyErr_SetString(_InvalidPullException, "Invalid value for pull_up_down - should be either PUD_OFF, PUD_UP or PUD_DOWN"); | ||
| 151 | return NULL; | ||
| 152 | } | ||
| 153 | |||
| 154 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 155 | { | ||
| 156 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 157 | return NULL; | ||
| 158 | } | ||
| 159 | |||
| 160 | set_function(channel, function, pud); | ||
| 161 | |||
| 162 | Py_INCREF(Py_None); | ||
| 163 | return Py_None; | ||
| 164 | } | ||
| 165 | |||
| 166 | // python function value = input(channel) | ||
| 167 | static PyObject *py_input(PyObject *self, PyObject *args) | ||
| 168 | { | ||
| 169 | if (module_setup() != SETUP_OK) { | ||
| 170 | return NULL; | ||
| 171 | } | ||
| 172 | |||
| 173 | int channel; | ||
| 174 | |||
| 175 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 176 | return NULL; | ||
| 177 | |||
| 178 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 179 | { | ||
| 180 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 181 | return NULL; | ||
| 182 | } | ||
| 183 | |||
| 184 | if (input(channel)) | ||
| 185 | Py_RETURN_TRUE; | ||
| 186 | else | ||
| 187 | Py_RETURN_FALSE; | ||
| 188 | } | ||
| 189 | |||
| 190 | // python function output(channel, value) | ||
| 191 | static PyObject *py_output(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 192 | { | ||
| 193 | if (module_setup() != SETUP_OK) { | ||
| 194 | return NULL; | ||
| 195 | } | ||
| 196 | |||
| 197 | int channel, value; | ||
| 198 | static char *kwlist[] = {"channel", "value", NULL}; | ||
| 199 | |||
| 200 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "ii", kwlist, &channel, &value)) | ||
| 201 | return NULL; | ||
| 202 | |||
| 203 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 204 | { | ||
| 205 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 206 | return NULL; | ||
| 207 | } | ||
| 208 | |||
| 209 | if (get_function(channel) != OUT) | ||
| 210 | { | ||
| 211 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT"); | ||
| 212 | return NULL; | ||
| 213 | } | ||
| 214 | |||
| 215 | output(channel, value); | ||
| 216 | |||
| 217 | Py_INCREF(Py_None); | ||
| 218 | return Py_None; | ||
| 219 | } | ||
| 220 | |||
| 221 | // python function outputSequence(channel, period, sequence) | ||
| 222 | static PyObject *py_output_sequence(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 223 | { | ||
| 224 | if (module_setup() != SETUP_OK) { | ||
| 225 | return NULL; | ||
| 226 | } | ||
| 227 | |||
| 228 | int channel, period; | ||
| 229 | char* sequence; | ||
| 230 | static char *kwlist[] = {"channel", "period", "sequence", NULL}; | ||
| 231 | |||
| 232 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iis", kwlist, &channel, &period, &sequence)) | ||
| 233 | return NULL; | ||
| 234 | |||
| 235 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 236 | { | ||
| 237 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 238 | return NULL; | ||
| 239 | } | ||
| 240 | |||
| 241 | if (get_function(channel) != OUT) | ||
| 242 | { | ||
| 243 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT"); | ||
| 244 | return NULL; | ||
| 245 | } | ||
| 246 | |||
| 247 | outputSequence(channel, period, sequence); | ||
| 248 | |||
| 249 | Py_INCREF(Py_None); | ||
| 250 | return Py_None; | ||
| 251 | } | ||
| 252 | |||
| 253 | |||
| 254 | static PyObject *py_pulseMilli(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 255 | { | ||
| 256 | if (module_setup() != SETUP_OK) { | ||
| 257 | return NULL; | ||
| 258 | } | ||
| 259 | |||
| 260 | int channel, function, up, down; | ||
| 261 | static char *kwlist[] = {"channel", "up", "down", NULL}; | ||
| 262 | |||
| 263 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iii", kwlist, &channel, &up, &down)) | ||
| 264 | return NULL; | ||
| 265 | |||
| 266 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 267 | { | ||
| 268 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 269 | return NULL; | ||
| 270 | } | ||
| 271 | |||
| 272 | function = get_function(channel); | ||
| 273 | if ((function != OUT) && (function != PWM)) | ||
| 274 | { | ||
| 275 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT or PWM"); | ||
| 276 | return NULL; | ||
| 277 | } | ||
| 278 | |||
| 279 | pulseMilli(channel, up, down); | ||
| 280 | |||
| 281 | Py_INCREF(Py_None); | ||
| 282 | return Py_None; | ||
| 283 | } | ||
| 284 | |||
| 285 | |||
| 286 | static PyObject *py_pulseMilliRatio(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 287 | { | ||
| 288 | if (module_setup() != SETUP_OK) { | ||
| 289 | return NULL; | ||
| 290 | } | ||
| 291 | |||
| 292 | int channel, function, width; | ||
| 293 | float ratio; | ||
| 294 | static char *kwlist[] = {"channel", "width", "ratio", NULL}; | ||
| 295 | |||
| 296 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iif", kwlist, &channel, &width, &ratio)) | ||
| 297 | return NULL; | ||
| 298 | |||
| 299 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 300 | { | ||
| 301 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 302 | return NULL; | ||
| 303 | } | ||
| 304 | |||
| 305 | function = get_function(channel); | ||
| 306 | if ((function != OUT) && (function != PWM)) | ||
| 307 | { | ||
| 308 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT or PWM"); | ||
| 309 | return NULL; | ||
| 310 | } | ||
| 311 | |||
| 312 | pulseMilliRatio(channel, width, ratio); | ||
| 313 | |||
| 314 | Py_INCREF(Py_None); | ||
| 315 | return Py_None; | ||
| 316 | } | ||
| 317 | |||
| 318 | |||
| 319 | static PyObject *py_pulseMicro(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 320 | { | ||
| 321 | if (module_setup() != SETUP_OK) { | ||
| 322 | return NULL; | ||
| 323 | } | ||
| 324 | |||
| 325 | int channel, function, up, down; | ||
| 326 | static char *kwlist[] = {"channel", "up", "down", NULL}; | ||
| 327 | |||
| 328 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iii", kwlist, &channel, &up, &down)) | ||
| 329 | return NULL; | ||
| 330 | |||
| 331 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 332 | { | ||
| 333 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 334 | return NULL; | ||
| 335 | } | ||
| 336 | |||
| 337 | function = get_function(channel); | ||
| 338 | if ((function != OUT) && (function != PWM)) | ||
| 339 | { | ||
| 340 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT or PWM"); | ||
| 341 | return NULL; | ||
| 342 | } | ||
| 343 | |||
| 344 | pulseMicro(channel, up, down); | ||
| 345 | |||
| 346 | Py_INCREF(Py_None); | ||
| 347 | return Py_None; | ||
| 348 | } | ||
| 349 | |||
| 350 | static PyObject *py_pulseMicroRatio(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 351 | { | ||
| 352 | if (module_setup() != SETUP_OK) { | ||
| 353 | return NULL; | ||
| 354 | } | ||
| 355 | |||
| 356 | int channel, function, width; | ||
| 357 | float ratio; | ||
| 358 | static char *kwlist[] = {"channel", "width", "ratio", NULL}; | ||
| 359 | |||
| 360 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "iif", kwlist, &channel, &width, &ratio)) | ||
| 361 | return NULL; | ||
| 362 | |||
| 363 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 364 | { | ||
| 365 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 366 | return NULL; | ||
| 367 | } | ||
| 368 | |||
| 369 | function = get_function(channel); | ||
| 370 | if ((function != OUT) && (function != PWM)) | ||
| 371 | { | ||
| 372 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT or PWM"); | ||
| 373 | return NULL; | ||
| 374 | } | ||
| 375 | |||
| 376 | pulseMicroRatio(channel, width, ratio); | ||
| 377 | |||
| 378 | Py_INCREF(Py_None); | ||
| 379 | return Py_None; | ||
| 380 | } | ||
| 381 | |||
| 382 | static PyObject *py_pulseAngle(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 383 | { | ||
| 384 | if (module_setup() != SETUP_OK) { | ||
| 385 | return NULL; | ||
| 386 | } | ||
| 387 | |||
| 388 | int channel, function; | ||
| 389 | float angle; | ||
| 390 | static char *kwlist[] = {"channel", "angle", NULL}; | ||
| 391 | |||
| 392 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "if", kwlist, &channel, &angle)) | ||
| 393 | return NULL; | ||
| 394 | |||
| 395 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 396 | { | ||
| 397 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 398 | return NULL; | ||
| 399 | } | ||
| 400 | |||
| 401 | function = get_function(channel); | ||
| 402 | if ((function != OUT) && (function != PWM)) | ||
| 403 | { | ||
| 404 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT or PWM"); | ||
| 405 | return NULL; | ||
| 406 | } | ||
| 407 | |||
| 408 | pulseAngle(channel, angle); | ||
| 409 | |||
| 410 | Py_INCREF(Py_None); | ||
| 411 | return Py_None; | ||
| 412 | } | ||
| 413 | |||
| 414 | static PyObject *py_pulseRatio(PyObject *self, PyObject *args, PyObject *kwargs) | ||
| 415 | { | ||
| 416 | if (module_setup() != SETUP_OK) { | ||
| 417 | return NULL; | ||
| 418 | } | ||
| 419 | |||
| 420 | int channel, function; | ||
| 421 | float ratio; | ||
| 422 | static char *kwlist[] = {"channel", "ratio", NULL}; | ||
| 423 | |||
| 424 | if (!PyArg_ParseTupleAndKeywords(args, kwargs, "if", kwlist, &channel, &ratio)) | ||
| 425 | return NULL; | ||
| 426 | |||
| 427 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 428 | { | ||
| 429 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 430 | return NULL; | ||
| 431 | } | ||
| 432 | |||
| 433 | function = get_function(channel); | ||
| 434 | if ((function != OUT) && (function != PWM)) | ||
| 435 | { | ||
| 436 | PyErr_SetString(_InvalidDirectionException, "The GPIO channel is not an OUTPUT or PWM"); | ||
| 437 | return NULL; | ||
| 438 | } | ||
| 439 | |||
| 440 | pulseRatio(channel, ratio); | ||
| 441 | |||
| 442 | Py_INCREF(Py_None); | ||
| 443 | return Py_None; | ||
| 444 | } | ||
| 445 | |||
| 446 | static PyObject *py_pulse(PyObject *self, PyObject *args) | ||
| 447 | { | ||
| 448 | if (module_setup() != SETUP_OK) { | ||
| 449 | return NULL; | ||
| 450 | } | ||
| 451 | |||
| 452 | int channel; | ||
| 453 | |||
| 454 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 455 | return NULL; | ||
| 456 | |||
| 457 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 458 | { | ||
| 459 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 460 | return NULL; | ||
| 461 | } | ||
| 462 | |||
| 463 | pulseRatio(channel, 0.5); | ||
| 464 | return Py_None; | ||
| 465 | } | ||
| 466 | |||
| 467 | static PyObject *py_getPulse(PyObject *self, PyObject *args) | ||
| 468 | { | ||
| 469 | if (module_setup() != SETUP_OK) { | ||
| 470 | return NULL; | ||
| 471 | } | ||
| 472 | |||
| 473 | int channel; | ||
| 474 | char str[256]; | ||
| 475 | struct pulse *p; | ||
| 476 | |||
| 477 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 478 | return NULL; | ||
| 479 | |||
| 480 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 481 | { | ||
| 482 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 483 | return NULL; | ||
| 484 | } | ||
| 485 | |||
| 486 | p = getPulse(channel); | ||
| 487 | |||
| 488 | sprintf(str, "%s:%.2f", PWM_MODES[p->type], p->value); | ||
| 489 | #if PY_MAJOR_VERSION > 2 | ||
| 490 | return PyUnicode_FromString(str); | ||
| 491 | #else | ||
| 492 | return PyString_FromString(str); | ||
| 493 | #endif | ||
| 494 | } | ||
| 495 | |||
| 496 | static PyObject *py_enablePWM(PyObject *self, PyObject *args) | ||
| 497 | { | ||
| 498 | if (module_setup() != SETUP_OK) { | ||
| 499 | return NULL; | ||
| 500 | } | ||
| 501 | |||
| 502 | int channel; | ||
| 503 | |||
| 504 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 505 | return NULL; | ||
| 506 | |||
| 507 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 508 | { | ||
| 509 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 510 | return NULL; | ||
| 511 | } | ||
| 512 | |||
| 513 | enablePWM(channel); | ||
| 514 | return Py_None; | ||
| 515 | } | ||
| 516 | |||
| 517 | static PyObject *py_disablePWM(PyObject *self, PyObject *args) | ||
| 518 | { | ||
| 519 | if (module_setup() != SETUP_OK) { | ||
| 520 | return NULL; | ||
| 521 | } | ||
| 522 | |||
| 523 | int channel; | ||
| 524 | |||
| 525 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 526 | return NULL; | ||
| 527 | |||
| 528 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 529 | { | ||
| 530 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 531 | return NULL; | ||
| 532 | } | ||
| 533 | |||
| 534 | disablePWM(channel); | ||
| 535 | return Py_None; | ||
| 536 | } | ||
| 537 | |||
| 538 | |||
| 539 | |||
| 540 | static PyObject *py_isPWMEnabled(PyObject *self, PyObject *args) | ||
| 541 | { | ||
| 542 | if (module_setup() != SETUP_OK) { | ||
| 543 | return NULL; | ||
| 544 | } | ||
| 545 | |||
| 546 | int channel; | ||
| 547 | |||
| 548 | if (!PyArg_ParseTuple(args, "i", &channel)) | ||
| 549 | return NULL; | ||
| 550 | |||
| 551 | if (channel < 0 || channel >= GPIO_COUNT) | ||
| 552 | { | ||
| 553 | PyErr_SetString(_InvalidChannelException, "The GPIO channel is invalid"); | ||
| 554 | return NULL; | ||
| 555 | } | ||
| 556 | |||
| 557 | if (isPWMEnabled(channel)) | ||
| 558 | Py_RETURN_TRUE; | ||
| 559 | else | ||
| 560 | Py_RETURN_FALSE; | ||
| 561 | } | ||
| 562 | |||
| 563 | PyMethodDef python_methods[] = { | ||
| 564 | {"getFunction", py_get_function, METH_VARARGS, "Return the current GPIO setup (IN, OUT, ALT0)"}, | ||
| 565 | {"getSetup", py_get_function, METH_VARARGS, "Return the current GPIO setup (IN, OUT, ALT0)"}, | ||
| 566 | |||
| 567 | {"getFunctionString", py_get_function_string, METH_VARARGS, "Return the current GPIO setup (IN, OUT, ALT0) as string"}, | ||
| 568 | {"getSetupString", py_get_function_string, METH_VARARGS, "Return the current GPIO setup (IN, OUT, ALT0) as string"}, | ||
| 569 | |||
| 570 | {"setFunction", (PyCFunction)py_set_function, METH_VARARGS | METH_KEYWORDS, "Setup the GPIO channel, direction and (optional) pull/up down control\nchannel - BCM GPIO number\ndirection - IN or OUT\n[pull_up_down] - PUD_OFF (default), PUD_UP or PUD_DOWN"}, | ||
| 571 | {"setup", (PyCFunction)py_set_function, METH_VARARGS | METH_KEYWORDS, "Setup the GPIO channel, direction and (optional) pull/up down control\nchannel - BCM GPIO number\ndirection - IN or OUT\n[pull_up_down] - PUD_OFF (default), PUD_UP or PUD_DOWN"}, | ||
| 572 | |||
| 573 | {"input", py_input, METH_VARARGS, "Input from a GPIO channel - Deprecated, use digitalRead instead"}, | ||
| 574 | {"digitalRead", py_input, METH_VARARGS, "Read a GPIO channel"}, | ||
| 575 | |||
| 576 | {"output", (PyCFunction)py_output, METH_VARARGS | METH_KEYWORDS, "Output to a GPIO channel - Deprecated, use digitalWrite instead"}, | ||
| 577 | {"digitalWrite", (PyCFunction)py_output, METH_VARARGS | METH_KEYWORDS, "Write to a GPIO channel"}, | ||
| 578 | |||
| 579 | {"outputSequence", (PyCFunction)py_output_sequence, METH_VARARGS | METH_KEYWORDS, "Output a sequence to a GPIO channel"}, | ||
| 580 | |||
| 581 | {"getPulse", py_getPulse, METH_VARARGS, "Read current PWM output"}, | ||
| 582 | {"pwmRead", py_getPulse, METH_VARARGS, "Read current PWM output"}, | ||
| 583 | |||
| 584 | {"pulseMilli", (PyCFunction)py_pulseMilli, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using milliseconds for both HIGH and LOW state widths"}, | ||
| 585 | {"pulseMilliRatio", (PyCFunction)py_pulseMilliRatio, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using millisecond for the total width and a ratio (duty cycle) for the HIGH state width"}, | ||
| 586 | {"pulseMicro", (PyCFunction)py_pulseMicro, METH_VARARGS | METH_KEYWORDS, "Output a PWM pulse to a GPIO channel using microseconds for both HIGH and LOW state widths"}, | ||
| 587 | {"pulseMicroRatio", (PyCFunction)py_pulseMicroRatio, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using microseconds for the total width and a ratio (duty cycle) for the HIGH state width"}, | ||
| 588 | |||
| 589 | {"pulseAngle", (PyCFunction)py_pulseAngle, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using an angle - Deprecated, use pwmWriteAngle instead"}, | ||
| 590 | {"pwmWriteAngle", (PyCFunction)py_pulseAngle, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using an angle"}, | ||
| 591 | |||
| 592 | {"pulseRatio", (PyCFunction)py_pulseRatio, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using a ratio (duty cycle) with the default 50Hz signal - Deprecated, use pwmWrite instead"}, | ||
| 593 | {"pwmWrite", (PyCFunction)py_pulseRatio, METH_VARARGS | METH_KEYWORDS, "Output a PWM to a GPIO channel using a ratio (duty cycle) with the default 50Hz signal"}, | ||
| 594 | |||
| 595 | {"pulse", py_pulse, METH_VARARGS, "Output a PWM to a GPIO channel using a 50% ratio (duty cycle) with the default 50Hz signal"}, | ||
| 596 | |||
| 597 | {"enablePWM", py_enablePWM, METH_VARARGS, "Enable software PWM loop for a GPIO channel"}, | ||
| 598 | {"disablePWM", py_disablePWM, METH_VARARGS, "Disable software PWM loop of a GPIO channel"}, | ||
| 599 | {"isPWMEnabled", py_isPWMEnabled, METH_VARARGS, "Returns software PWM state"}, | ||
| 600 | |||
| 601 | {NULL, NULL, 0, NULL} | ||
| 602 | }; | ||
| 603 | |||
| 604 | #if PY_MAJOR_VERSION > 2 | ||
| 605 | static struct PyModuleDef python_module = { | ||
| 606 | PyModuleDef_HEAD_INIT, | ||
| 607 | "_webiopi.GPIO", /* name of module */ | ||
| 608 | NULL, /* module documentation, may be NULL */ | ||
| 609 | -1, /* size of per-interpreter state of the module, | ||
| 610 | or -1 if the module keeps state in global variables. */ | ||
| 611 | python_methods | ||
| 612 | }; | ||
| 613 | #endif | ||
| 614 | |||
| 615 | #if PY_MAJOR_VERSION > 2 | ||
| 616 | PyMODINIT_FUNC PyInit_GPIO(void) | ||
| 617 | #else | ||
| 618 | PyMODINIT_FUNC initGPIO(void) | ||
| 619 | #endif | ||
| 620 | { | ||
| 621 | PyObject *module = NULL; | ||
| 622 | int revision = -1; | ||
| 623 | |||
| 624 | #if PY_MAJOR_VERSION > 2 | ||
| 625 | if ((module = PyModule_Create(&python_module)) == NULL) | ||
| 626 | goto exit; | ||
| 627 | #else | ||
| 628 | if ((module = Py_InitModule("_webiopi.GPIO", python_methods)) == NULL) | ||
| 629 | goto exit; | ||
| 630 | #endif | ||
| 631 | |||
| 632 | _SetupException = PyErr_NewException("_webiopi.GPIO.SetupException", NULL, NULL); | ||
| 633 | PyModule_AddObject(module, "SetupException", _SetupException); | ||
| 634 | |||
| 635 | _InvalidDirectionException = PyErr_NewException("_webiopi.GPIO.InvalidDirectionException", NULL, NULL); | ||
| 636 | PyModule_AddObject(module, "InvalidDirectionException", _InvalidDirectionException); | ||
| 637 | |||
| 638 | _InvalidChannelException = PyErr_NewException("_webiopi.GPIO.InvalidChannelException", NULL, NULL); | ||
| 639 | PyModule_AddObject(module, "InvalidChannelException", _InvalidChannelException); | ||
| 640 | |||
| 641 | _InvalidPullException = PyErr_NewException("_webiopi.GPIO.InvalidPullException", NULL, NULL); | ||
| 642 | PyModule_AddObject(module, "InvalidPullException", _InvalidPullException); | ||
| 643 | |||
| 644 | _gpioCount = Py_BuildValue("i", GPIO_COUNT); | ||
| 645 | PyModule_AddObject(module, "GPIO_COUNT", _gpioCount); | ||
| 646 | |||
| 647 | _low = Py_BuildValue("i", LOW); | ||
| 648 | PyModule_AddObject(module, "LOW", _low); | ||
| 649 | |||
| 650 | _high = Py_BuildValue("i", HIGH); | ||
| 651 | PyModule_AddObject(module, "HIGH", _high); | ||
| 652 | |||
| 653 | _in = Py_BuildValue("i", IN); | ||
| 654 | PyModule_AddObject(module, "IN", _in); | ||
| 655 | |||
| 656 | _out = Py_BuildValue("i", OUT); | ||
| 657 | PyModule_AddObject(module, "OUT", _out); | ||
| 658 | |||
| 659 | _alt0 = Py_BuildValue("i", ALT0); | ||
| 660 | PyModule_AddObject(module, "ALT0", _alt0); | ||
| 661 | |||
| 662 | _alt1 = Py_BuildValue("i", ALT1); | ||
| 663 | PyModule_AddObject(module, "ALT1", _alt1); | ||
| 664 | |||
| 665 | _alt2 = Py_BuildValue("i", ALT2); | ||
| 666 | PyModule_AddObject(module, "ALT2", _alt2); | ||
| 667 | |||
| 668 | _alt3 = Py_BuildValue("i", ALT3); | ||
| 669 | PyModule_AddObject(module, "ALT3", _alt3); | ||
| 670 | |||
| 671 | _alt4 = Py_BuildValue("i", ALT4); | ||
| 672 | PyModule_AddObject(module, "ALT4", _alt4); | ||
| 673 | |||
| 674 | _alt5 = Py_BuildValue("i", ALT5); | ||
| 675 | PyModule_AddObject(module, "ALT5", _alt5); | ||
| 676 | |||
| 677 | _pwm = Py_BuildValue("i", PWM); | ||
| 678 | PyModule_AddObject(module, "PWM", _pwm); | ||
| 679 | |||
| 680 | _pud_off = Py_BuildValue("i", PUD_OFF); | ||
| 681 | PyModule_AddObject(module, "PUD_OFF", _pud_off); | ||
| 682 | |||
| 683 | _pud_up = Py_BuildValue("i", PUD_UP); | ||
| 684 | PyModule_AddObject(module, "PUD_UP", _pud_up); | ||
| 685 | |||
| 686 | _pud_down = Py_BuildValue("i", PUD_DOWN); | ||
| 687 | PyModule_AddObject(module, "PUD_DOWN", _pud_down); | ||
| 688 | |||
| 689 | // detect board revision and set up accordingly | ||
| 690 | revision = get_rpi_revision(); | ||
| 691 | if (revision == -1) | ||
| 692 | { | ||
| 693 | PyErr_SetString(_SetupException, "This module can only be run on a Raspberry Pi!"); | ||
| 694 | #if PY_MAJOR_VERSION > 2 | ||
| 695 | return NULL; | ||
| 696 | #else | ||
| 697 | return; | ||
| 698 | #endif | ||
| 699 | } | ||
| 700 | |||
| 701 | _board_revision = Py_BuildValue("i", revision); | ||
| 702 | PyModule_AddObject(module, "BOARD_REVISION", _board_revision); | ||
| 703 | |||
| 704 | if (Py_AtExit(cleanup) != 0) | ||
| 705 | { | ||
| 706 | cleanup(); | ||
| 707 | #if PY_MAJOR_VERSION > 2 | ||
| 708 | return NULL; | ||
| 709 | #else | ||
| 710 | return; | ||
| 711 | #endif | ||
| 712 | } | ||
| 713 | |||
| 714 | exit: | ||
| 715 | #if PY_MAJOR_VERSION > 2 | ||
| 716 | return module; | ||
| 717 | #else | ||
| 718 | return; | ||
| 719 | #endif | ||
| 720 | } | ||
diff --git a/python/native/cpuinfo.c b/python/native/cpuinfo.c new file mode 100644 index 0000000..a69d97e --- /dev/null +++ b/python/native/cpuinfo.c | |||
| @@ -0,0 +1,65 @@ | |||
| 1 | /* | ||
| 2 | Copyright (c) 2012 Ben Croston | ||
| 3 | |||
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| 5 | this software and associated documentation files (the "Software"), to deal in | ||
| 6 | the Software without restriction, including without limitation the rights to | ||
| 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
| 8 | of the Software, and to permit persons to whom the Software is furnished to do | ||
| 9 | so, subject to the following conditions: | ||
| 10 | |||
| 11 | The above copyright notice and this permission notice shall be included in all | ||
| 12 | copies or substantial portions of the Software. | ||
| 13 | |||
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 20 | SOFTWARE. | ||
| 21 | */ | ||
| 22 | |||
| 23 | #include <stdio.h> | ||
| 24 | #include <string.h> | ||
| 25 | #include "cpuinfo.h" | ||
| 26 | |||
| 27 | char *get_cpuinfo_revision(char *revision) | ||
| 28 | { | ||
| 29 | FILE *fp; | ||
| 30 | char buffer[1024]; | ||
| 31 | char hardware[1024]; | ||
| 32 | int rpi_found = 0; | ||
| 33 | |||
| 34 | if ((fp = fopen("/proc/cpuinfo", "r")) == NULL) | ||
| 35 | return 0; | ||
| 36 | |||
| 37 | while(!feof(fp)) { | ||
| 38 | fgets(buffer, sizeof(buffer) , fp); | ||
| 39 | sscanf(buffer, "Hardware : %s", hardware); | ||
| 40 | if (strcmp(hardware, "BCM2708") == 0) | ||
| 41 | rpi_found = 1; | ||
| 42 | sscanf(buffer, "Revision : %s", revision); | ||
| 43 | } | ||
| 44 | fclose(fp); | ||
| 45 | |||
| 46 | if (!rpi_found) | ||
| 47 | revision = NULL; | ||
| 48 | return revision; | ||
| 49 | } | ||
| 50 | |||
| 51 | int get_rpi_revision(void) | ||
| 52 | { | ||
| 53 | char revision[1024] = {'\0'}; | ||
| 54 | |||
| 55 | if (get_cpuinfo_revision(revision) == NULL) | ||
| 56 | return -1; | ||
| 57 | |||
| 58 | if ((strcmp(revision, "0002") == 0) || | ||
| 59 | (strcmp(revision, "1000002") == 0 ) || | ||
| 60 | (strcmp(revision, "0003") == 0) || | ||
| 61 | (strcmp(revision, "1000003") == 0 )) | ||
| 62 | return 1; | ||
| 63 | else // assume rev 2 (0004 0005 0006 1000004 1000005 1000006) | ||
| 64 | return 2; | ||
| 65 | } | ||
diff --git a/python/native/cpuinfo.h b/python/native/cpuinfo.h new file mode 100644 index 0000000..e84ea7d --- /dev/null +++ b/python/native/cpuinfo.h | |||
| @@ -0,0 +1,23 @@ | |||
| 1 | /* | ||
| 2 | Copyright (c) 2012 Ben Croston | ||
| 3 | |||
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| 5 | this software and associated documentation files (the "Software"), to deal in | ||
| 6 | the Software without restriction, including without limitation the rights to | ||
| 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
| 8 | of the Software, and to permit persons to whom the Software is furnished to do | ||
| 9 | so, subject to the following conditions: | ||
| 10 | |||
| 11 | The above copyright notice and this permission notice shall be included in all | ||
| 12 | copies or substantial portions of the Software. | ||
| 13 | |||
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 20 | SOFTWARE. | ||
| 21 | */ | ||
| 22 | |||
| 23 | int get_rpi_revision(void); | ||
diff --git a/python/native/gpio.c b/python/native/gpio.c new file mode 100644 index 0000000..3950b16 --- /dev/null +++ b/python/native/gpio.c | |||
| @@ -0,0 +1,329 @@ | |||
| 1 | /* | ||
| 2 | Copyright (c) 2012 Ben Croston / 2012-2013 Eric PTAK | ||
| 3 | |||
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| 5 | this software and associated documentation files (the "Software"), to deal in | ||
| 6 | the Software without restriction, including without limitation the rights to | ||
| 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
| 8 | of the Software, and to permit persons to whom the Software is furnished to do | ||
| 9 | so, subject to the following conditions: | ||
| 10 | |||
| 11 | The above copyright notice and this permission notice shall be included in all | ||
| 12 | copies or substantial portions of the Software. | ||
| 13 | |||
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 20 | SOFTWARE. | ||
| 21 | */ | ||
| 22 | |||
| 23 | #include <stdint.h> | ||
| 24 | #include <stdlib.h> | ||
| 25 | #include <string.h> | ||
| 26 | #include <fcntl.h> | ||
| 27 | #include <sys/mman.h> | ||
| 28 | #include <time.h> | ||
| 29 | #include <pthread.h> | ||
| 30 | #include "gpio.h" | ||
| 31 | |||
| 32 | #define BCM2708_PERI_BASE 0x20000000 | ||
| 33 | #define GPIO_BASE (BCM2708_PERI_BASE + 0x200000) | ||
| 34 | #define FSEL_OFFSET 0 // 0x0000 | ||
| 35 | #define SET_OFFSET 7 // 0x001c / 4 | ||
| 36 | #define CLR_OFFSET 10 // 0x0028 / 4 | ||
| 37 | #define PINLEVEL_OFFSET 13 // 0x0034 / 4 | ||
| 38 | #define EVENT_DETECT_OFFSET 16 // 0x0040 / 4 | ||
| 39 | #define RISING_ED_OFFSET 19 // 0x004c / 4 | ||
| 40 | #define FALLING_ED_OFFSET 22 // 0x0058 / 4 | ||
| 41 | #define HIGH_DETECT_OFFSET 25 // 0x0064 / 4 | ||
| 42 | #define LOW_DETECT_OFFSET 28 // 0x0070 / 4 | ||
| 43 | #define PULLUPDN_OFFSET 37 // 0x0094 / 4 | ||
| 44 | #define PULLUPDNCLK_OFFSET 38 // 0x0098 / 4 | ||
| 45 | |||
| 46 | #define PAGE_SIZE (4*1024) | ||
| 47 | #define BLOCK_SIZE (4*1024) | ||
| 48 | |||
| 49 | static volatile uint32_t *gpio_map; | ||
| 50 | |||
| 51 | struct tspair { | ||
| 52 | struct timespec up; | ||
| 53 | struct timespec down; | ||
| 54 | }; | ||
| 55 | |||
| 56 | static struct pulse gpio_pulses[GPIO_COUNT]; | ||
| 57 | static struct tspair gpio_tspairs[GPIO_COUNT]; | ||
| 58 | static pthread_t *gpio_threads[GPIO_COUNT]; | ||
| 59 | |||
| 60 | void short_wait(void) | ||
| 61 | { | ||
| 62 | int i; | ||
| 63 | |||
| 64 | for (i=0; i<150; i++) // wait 150 cycles | ||
| 65 | { | ||
| 66 | asm volatile("nop"); | ||
| 67 | } | ||
| 68 | } | ||
| 69 | |||
| 70 | int setup(void) | ||
| 71 | { | ||
| 72 | int mem_fd; | ||
| 73 | uint8_t *gpio_mem; | ||
| 74 | |||
| 75 | if ((mem_fd = open("/dev/mem", O_RDWR|O_SYNC) ) < 0) | ||
| 76 | { | ||
| 77 | return SETUP_DEVMEM_FAIL; | ||
| 78 | } | ||
| 79 | |||
| 80 | if ((gpio_mem = malloc(BLOCK_SIZE + (PAGE_SIZE-1))) == NULL) | ||
| 81 | return SETUP_MALLOC_FAIL; | ||
| 82 | |||
| 83 | if ((uint32_t)gpio_mem % PAGE_SIZE) | ||
| 84 | gpio_mem += PAGE_SIZE - ((uint32_t)gpio_mem % PAGE_SIZE); | ||
| 85 | |||
| 86 | gpio_map = (uint32_t *)mmap( (caddr_t)gpio_mem, BLOCK_SIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, mem_fd, GPIO_BASE); | ||
| 87 | |||
| 88 | if ((uint32_t)gpio_map < 0) | ||
| 89 | return SETUP_MMAP_FAIL; | ||
| 90 | |||
| 91 | return SETUP_OK; | ||
| 92 | } | ||
| 93 | |||
| 94 | void set_pullupdn(int gpio, int pud) | ||
| 95 | { | ||
| 96 | int clk_offset = PULLUPDNCLK_OFFSET + (gpio/32); | ||
| 97 | int shift = (gpio%32); | ||
| 98 | |||
| 99 | if (pud == PUD_DOWN) | ||
| 100 | *(gpio_map+PULLUPDN_OFFSET) = (*(gpio_map+PULLUPDN_OFFSET) & ~3) | PUD_DOWN; | ||
| 101 | else if (pud == PUD_UP) | ||
| 102 | *(gpio_map+PULLUPDN_OFFSET) = (*(gpio_map+PULLUPDN_OFFSET) & ~3) | PUD_UP; | ||
| 103 | else // pud == PUD_OFF | ||
| 104 | *(gpio_map+PULLUPDN_OFFSET) &= ~3; | ||
| 105 | |||
| 106 | short_wait(); | ||
| 107 | *(gpio_map+clk_offset) = 1 << shift; | ||
| 108 | short_wait(); | ||
| 109 | *(gpio_map+PULLUPDN_OFFSET) &= ~3; | ||
| 110 | *(gpio_map+clk_offset) = 0; | ||
| 111 | } | ||
| 112 | |||
| 113 | //updated Eric PTAK - trouch.com | ||
| 114 | void set_function(int gpio, int function, int pud) | ||
| 115 | { | ||
| 116 | if (function == PWM) { | ||
| 117 | function = OUT; | ||
| 118 | enablePWM(gpio); | ||
| 119 | } | ||
| 120 | else { | ||
| 121 | disablePWM(gpio); | ||
| 122 | } | ||
| 123 | |||
| 124 | int offset = FSEL_OFFSET + (gpio/10); | ||
| 125 | int shift = (gpio%10)*3; | ||
| 126 | |||
| 127 | set_pullupdn(gpio, pud); | ||
| 128 | *(gpio_map+offset) = (*(gpio_map+offset) & ~(7<<shift)) | (function<<shift); | ||
| 129 | } | ||
| 130 | |||
| 131 | //added Eric PTAK - trouch.com | ||
| 132 | int get_function(int gpio) | ||
| 133 | { | ||
| 134 | int offset = FSEL_OFFSET + (gpio/10); | ||
| 135 | int shift = (gpio%10)*3; | ||
| 136 | int value = *(gpio_map+offset); | ||
| 137 | value >>= shift; | ||
| 138 | value &= 7; | ||
| 139 | if ((value == OUT) && isPWMEnabled(gpio)) { | ||
| 140 | value = PWM; | ||
| 141 | } | ||
| 142 | return value; // 0=input, 1=output, 4=alt0 | ||
| 143 | } | ||
| 144 | |||
| 145 | //updated Eric PTAK - trouch.com | ||
| 146 | int input(int gpio) | ||
| 147 | { | ||
| 148 | int offset, value, mask; | ||
| 149 | |||
| 150 | offset = PINLEVEL_OFFSET + (gpio/32); | ||
| 151 | mask = (1 << gpio%32); | ||
| 152 | value = *(gpio_map+offset) & mask; | ||
| 153 | return value; | ||
| 154 | } | ||
| 155 | |||
| 156 | void output(int gpio, int value) | ||
| 157 | { | ||
| 158 | int offset, shift; | ||
| 159 | |||
| 160 | if (value) // value == HIGH | ||
| 161 | offset = SET_OFFSET + (gpio/32); | ||
| 162 | else // value == LOW | ||
| 163 | offset = CLR_OFFSET + (gpio/32); | ||
| 164 | |||
| 165 | shift = (gpio%32); | ||
| 166 | |||
| 167 | *(gpio_map+offset) = 1 << shift; | ||
| 168 | } | ||
| 169 | |||
| 170 | //added Eric PTAK - trouch.com | ||
| 171 | void outputSequence(int gpio, int period, char* sequence) { | ||
| 172 | int i, value; | ||
| 173 | struct timespec ts; | ||
| 174 | ts.tv_sec = period/1000; | ||
| 175 | ts.tv_nsec = (period%1000) * 1000000; | ||
| 176 | |||
| 177 | for (i=0; sequence[i] != '\0'; i++) { | ||
| 178 | if (sequence[i] == '1') { | ||
| 179 | value = 1; | ||
| 180 | } | ||
| 181 | else { | ||
| 182 | value = 0; | ||
| 183 | } | ||
| 184 | output(gpio, value); | ||
| 185 | nanosleep(&ts, NULL); | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | void resetPWM(int gpio) { | ||
| 190 | gpio_pulses[gpio].type = 0; | ||
| 191 | gpio_pulses[gpio].value = 0; | ||
| 192 | |||
| 193 | gpio_tspairs[gpio].up.tv_sec = 0; | ||
| 194 | gpio_tspairs[gpio].up.tv_nsec = 0; | ||
| 195 | gpio_tspairs[gpio].down.tv_sec = 0; | ||
| 196 | gpio_tspairs[gpio].down.tv_nsec = 0; | ||
| 197 | } | ||
| 198 | |||
| 199 | //added Eric PTAK - trouch.com | ||
| 200 | void pulseTS(int gpio, struct timespec *up, struct timespec *down) { | ||
| 201 | if ((up->tv_sec > 0) || (up->tv_nsec > 0)) { | ||
| 202 | output(gpio, 1); | ||
| 203 | nanosleep(up, NULL); | ||
| 204 | } | ||
| 205 | |||
| 206 | if ((down->tv_sec > 0) || (down->tv_nsec > 0)) { | ||
| 207 | output(gpio, 0); | ||
| 208 | nanosleep(down, NULL); | ||
| 209 | } | ||
| 210 | } | ||
| 211 | |||
| 212 | //added Eric PTAK - trouch.com | ||
| 213 | void pulseOrSaveTS(int gpio, struct timespec *up, struct timespec *down) { | ||
| 214 | if (gpio_threads[gpio] != NULL) { | ||
| 215 | memcpy(&gpio_tspairs[gpio].up, up, sizeof(struct timespec)); | ||
| 216 | memcpy(&gpio_tspairs[gpio].down, down, sizeof(struct timespec)); | ||
| 217 | } | ||
| 218 | else { | ||
| 219 | pulseTS(gpio, up, down); | ||
| 220 | } | ||
| 221 | } | ||
| 222 | |||
| 223 | //added Eric PTAK - trouch.com | ||
| 224 | void pulseMilli(int gpio, int up, int down) { | ||
| 225 | struct timespec tsUP, tsDOWN; | ||
| 226 | |||
| 227 | tsUP.tv_sec = up/1000; | ||
| 228 | tsUP.tv_nsec = (up%1000) * 1000000; | ||
| 229 | |||
| 230 | tsDOWN.tv_sec = down/1000; | ||
| 231 | tsDOWN.tv_nsec = (down%1000) * 1000000; | ||
| 232 | pulseOrSaveTS(gpio, &tsUP, &tsDOWN); | ||
| 233 | } | ||
| 234 | |||
| 235 | //added Eric PTAK - trouch.com | ||
| 236 | void pulseMilliRatio(int gpio, int width, float ratio) { | ||
| 237 | int up = ratio*width; | ||
| 238 | int down = width - up; | ||
| 239 | pulseMilli(gpio, up, down); | ||
| 240 | } | ||
| 241 | |||
| 242 | //added Eric PTAK - trouch.com | ||
| 243 | void pulseMicro(int gpio, int up, int down) { | ||
| 244 | struct timespec tsUP, tsDOWN; | ||
| 245 | |||
| 246 | tsUP.tv_sec = 0; | ||
| 247 | tsUP.tv_nsec = up * 1000; | ||
| 248 | |||
| 249 | tsDOWN.tv_sec = 0; | ||
| 250 | tsDOWN.tv_nsec = down * 1000; | ||
| 251 | pulseOrSaveTS(gpio, &tsUP, &tsDOWN); | ||
| 252 | } | ||
| 253 | |||
| 254 | //added Eric PTAK - trouch.com | ||
| 255 | void pulseMicroRatio(int gpio, int width, float ratio) { | ||
| 256 | int up = ratio*width; | ||
| 257 | int down = width - up; | ||
| 258 | pulseMicro(gpio, up, down); | ||
| 259 | } | ||
| 260 | |||
| 261 | //added Eric PTAK - trouch.com | ||
| 262 | void pulseAngle(int gpio, float angle) { | ||
| 263 | gpio_pulses[gpio].type = ANGLE; | ||
| 264 | gpio_pulses[gpio].value = angle; | ||
| 265 | int up = 1520 + (angle*400)/45; | ||
| 266 | int down = 20000-up; | ||
| 267 | pulseMicro(gpio, up, down); | ||
| 268 | } | ||
| 269 | |||
| 270 | //added Eric PTAK - trouch.com | ||
| 271 | void pulseRatio(int gpio, float ratio) { | ||
| 272 | gpio_pulses[gpio].type = RATIO; | ||
| 273 | gpio_pulses[gpio].value = ratio; | ||
| 274 | int up = ratio * 20000; | ||
| 275 | int down = 20000 - up; | ||
| 276 | pulseMicro(gpio, up, down); | ||
| 277 | } | ||
| 278 | |||
| 279 | struct pulse* getPulse(int gpio) { | ||
| 280 | return &gpio_pulses[gpio]; | ||
| 281 | } | ||
| 282 | |||
| 283 | //added Eric PTAK - trouch.com | ||
| 284 | void* pwmLoop(void* data) { | ||
| 285 | int gpio = (int)data; | ||
| 286 | |||
| 287 | while (1) { | ||
| 288 | pulseTS(gpio, &gpio_tspairs[gpio].up, &gpio_tspairs[gpio].down); | ||
| 289 | } | ||
| 290 | } | ||
| 291 | |||
| 292 | //added Eric PTAK - trouch.com | ||
| 293 | void enablePWM(int gpio) { | ||
| 294 | pthread_t *thread = gpio_threads[gpio]; | ||
| 295 | if (thread != NULL) { | ||
| 296 | return; | ||
| 297 | } | ||
| 298 | |||
| 299 | resetPWM(gpio); | ||
| 300 | |||
| 301 | thread = (pthread_t*) malloc(sizeof(pthread_t)); | ||
| 302 | pthread_create(thread, NULL, pwmLoop, (void*)gpio); | ||
| 303 | gpio_threads[gpio] = thread; | ||
| 304 | } | ||
| 305 | |||
| 306 | //added Eric PTAK - trouch.com | ||
| 307 | void disablePWM(int gpio) { | ||
| 308 | pthread_t *thread = gpio_threads[gpio]; | ||
| 309 | if (thread == NULL) { | ||
| 310 | return; | ||
| 311 | } | ||
| 312 | |||
| 313 | pthread_cancel(*thread); | ||
| 314 | gpio_threads[gpio] = NULL; | ||
| 315 | output(gpio, 0); | ||
| 316 | resetPWM(gpio); | ||
| 317 | } | ||
| 318 | |||
| 319 | //added Eric PTAK - trouch.com | ||
| 320 | int isPWMEnabled(int gpio) { | ||
| 321 | return gpio_threads[gpio] != NULL; | ||
| 322 | } | ||
| 323 | |||
| 324 | |||
| 325 | void cleanup(void) | ||
| 326 | { | ||
| 327 | // fixme - set all gpios back to input | ||
| 328 | munmap((caddr_t)gpio_map, BLOCK_SIZE); | ||
| 329 | } | ||
diff --git a/python/native/gpio.h b/python/native/gpio.h new file mode 100644 index 0000000..cb0147c --- /dev/null +++ b/python/native/gpio.h | |||
| @@ -0,0 +1,73 @@ | |||
| 1 | /* | ||
| 2 | Copyright (c) 2012 Ben Croston / 2012-2013 Eric PTAK | ||
| 3 | |||
| 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of | ||
| 5 | this software and associated documentation files (the "Software"), to deal in | ||
| 6 | the Software without restriction, including without limitation the rights to | ||
| 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies | ||
| 8 | of the Software, and to permit persons to whom the Software is furnished to do | ||
| 9 | so, subject to the following conditions: | ||
| 10 | |||
| 11 | The above copyright notice and this permission notice shall be included in all | ||
| 12 | copies or substantial portions of the Software. | ||
| 13 | |||
| 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| 20 | SOFTWARE. | ||
| 21 | */ | ||
| 22 | |||
| 23 | #define SETUP_OK 0 | ||
| 24 | #define SETUP_DEVMEM_FAIL 1 | ||
| 25 | #define SETUP_MALLOC_FAIL 2 | ||
| 26 | #define SETUP_MMAP_FAIL 3 | ||
| 27 | |||
| 28 | #define GPIO_COUNT 54 | ||
| 29 | |||
| 30 | #define IN 0 | ||
| 31 | #define OUT 1 | ||
| 32 | #define ALT5 2 | ||
| 33 | #define ALT4 3 | ||
| 34 | #define ALT0 4 | ||
| 35 | #define ALT1 5 | ||
| 36 | #define ALT2 6 | ||
| 37 | #define ALT3 7 | ||
| 38 | #define PWM 8 | ||
| 39 | |||
| 40 | #define LOW 0 | ||
| 41 | #define HIGH 1 | ||
| 42 | |||
| 43 | #define PUD_OFF 0 | ||
| 44 | #define PUD_DOWN 1 | ||
| 45 | #define PUD_UP 2 | ||
| 46 | |||
| 47 | #define RATIO 1 | ||
| 48 | #define ANGLE 2 | ||
| 49 | |||
| 50 | struct pulse { | ||
| 51 | int type; | ||
| 52 | float value; | ||
| 53 | }; | ||
| 54 | |||
| 55 | int setup(void); | ||
| 56 | int get_function(int gpio); | ||
| 57 | void set_function(int gpio, int function, int pud); | ||
| 58 | int input(int gpio); | ||
| 59 | void output(int gpio, int value); | ||
| 60 | void outputSequence(int gpio, int period, char* sequence); | ||
| 61 | struct pulse* getPulse(int gpio); | ||
| 62 | void pulseMilli(int gpio, int up, int down); | ||
| 63 | void pulseMilliRatio(int gpio, int width, float ratio); | ||
| 64 | void pulseMicro(int gpio, int up, int down); | ||
| 65 | void pulseMicroRatio(int gpio, int width, float ratio); | ||
| 66 | void pulseAngle(int gpio, float angle); | ||
| 67 | void pulseRatio(int gpio, float ratio); | ||
| 68 | void enablePWM(int gpio); | ||
| 69 | void disablePWM(int gpio); | ||
| 70 | int isPWMEnabled(int gpio); | ||
| 71 | |||
| 72 | void cleanup(void); | ||
| 73 | |||
diff --git a/python/passwd b/python/passwd new file mode 100644 index 0000000..4639dc4 --- /dev/null +++ b/python/passwd | |||
| @@ -0,0 +1 @@ | |||
| a4f849b74f8d12e35fad61c06489b70676affd6ddc599fa3de47210e351b7875 \ No newline at end of file | |||
diff --git a/python/setup.py b/python/setup.py new file mode 100644 index 0000000..671cb27 --- /dev/null +++ b/python/setup.py | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | from setuptools import setup, Extension | ||
| 2 | |||
| 3 | classifiers = ['Development Status :: 3 - Alpha', | ||
| 4 | 'Operating System :: POSIX :: Linux', | ||
| 5 | 'License :: OSI Approved :: MIT License', | ||
| 6 | 'Intended Audience :: Developers', | ||
| 7 | 'Programming Language :: Python :: 2.6', | ||
| 8 | 'Programming Language :: Python :: 2.7', | ||
| 9 | 'Programming Language :: Python :: 3', | ||
| 10 | 'Topic :: Software Development', | ||
| 11 | 'Topic :: Home Automation', | ||
| 12 | 'Topic :: System :: Hardware'] | ||
| 13 | |||
| 14 | setup(name = 'WebIOPi', | ||
| 15 | version = '0.6.2', | ||
| 16 | author = 'Eric PTAK', | ||
| 17 | author_email = 'trouch@trouch.com', | ||
| 18 | description = 'A package to control Raspberry Pi GPIO from the web', | ||
| 19 | long_description = open('../doc/README').read(), | ||
| 20 | license = 'MIT', | ||
| 21 | keywords = 'RaspberryPi GPIO Python REST', | ||
| 22 | url = 'http://code.google.com/p/webiopi/', | ||
| 23 | classifiers = classifiers, | ||
| 24 | packages = ["webiopi", | ||
| 25 | "webiopi.utils", | ||
| 26 | "webiopi.clients", | ||
| 27 | "webiopi.protocols", | ||
| 28 | "webiopi.server", | ||
| 29 | "webiopi.decorators", | ||
| 30 | "webiopi.devices", | ||
| 31 | "webiopi.devices.digital", | ||
| 32 | "webiopi.devices.analog", | ||
| 33 | "webiopi.devices.sensor", | ||
| 34 | "webiopi.devices.shield" | ||
| 35 | ], | ||
| 36 | ext_modules = [Extension('_webiopi.GPIO', ['native/bridge.c', 'native/gpio.c', 'native/cpuinfo.c'])], | ||
| 37 | ) | ||
diff --git a/python/webiopi-passwd.py b/python/webiopi-passwd.py new file mode 100755 index 0000000..7c0fbff --- /dev/null +++ b/python/webiopi-passwd.py | |||
| @@ -0,0 +1,57 @@ | |||
| 1 | #!/usr/bin/python | ||
| 2 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 3 | # | ||
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 5 | # you may not use this file except in compliance with the License. | ||
| 6 | # You may obtain a copy of the License at | ||
| 7 | # | ||
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 9 | # | ||
| 10 | # Unless required by applicable law or agreed to in writing, software | ||
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 13 | # See the License for the specific language governing permissions and | ||
| 14 | # limitations under the License. | ||
| 15 | |||
| 16 | import sys | ||
| 17 | file = None | ||
| 18 | |||
| 19 | print("WebIOPi passwd file generator") | ||
| 20 | if len(sys.argv) == 2: | ||
| 21 | file = sys.argv[1] | ||
| 22 | if file == "--help" or file == "-h": | ||
| 23 | print("Usage: webiopi-passwd [--help|file]") | ||
| 24 | print("Compute and display hash used by WebIOPi for Authentication") | ||
| 25 | print("Login and Password are prompted") | ||
| 26 | print("\t--help\tDisplay this help") | ||
| 27 | print("\t-h") | ||
| 28 | print("\tfile\tSave hash to file") | ||
| 29 | sys.exit() | ||
| 30 | else: | ||
| 31 | file = "/etc/webiopi/passwd" | ||
| 32 | |||
| 33 | f = open(file, "w") | ||
| 34 | _LOGIN = "Enter Login: " | ||
| 35 | _PASSWORD = "Enter Password: " | ||
| 36 | _CONFIRM = "Confirm password: " | ||
| 37 | _DONTMATCH = "Passwords don't match !" | ||
| 38 | |||
| 39 | import getpass | ||
| 40 | try: | ||
| 41 | login = raw_input(_LOGIN) | ||
| 42 | except NameError: | ||
| 43 | login = input(_LOGIN) | ||
| 44 | password = getpass.getpass(_PASSWORD) | ||
| 45 | password2 = getpass.getpass(_CONFIRM) | ||
| 46 | while password != password2: | ||
| 47 | print(_DONTMATCH) | ||
| 48 | password = getpass.getpass(_PASSWORD) | ||
| 49 | password2 = getpass.getpass(_CONFIRM) | ||
| 50 | |||
| 51 | from webiopi.utils.crypto import encryptCredentials | ||
| 52 | auth = encryptCredentials(login, password) | ||
| 53 | print("\nHash: %s" % auth) | ||
| 54 | if file: | ||
| 55 | f.write(auth) | ||
| 56 | f.close() | ||
| 57 | print("Saved to %s" % file) | ||
diff --git a/python/webiopi.init.sh b/python/webiopi.init.sh new file mode 100755 index 0000000..29457f5 --- /dev/null +++ b/python/webiopi.init.sh | |||
| @@ -0,0 +1,155 @@ | |||
| 1 | #! /bin/sh | ||
| 2 | ### BEGIN INIT INFO | ||
| 3 | # Provides: webiopi | ||
| 4 | # Required-Start: $remote_fs $syslog $network | ||
| 5 | # Required-Stop: $remote_fs $syslog $network | ||
| 6 | # Default-Start: 2 3 4 5 | ||
| 7 | # Default-Stop: 0 1 6 | ||
| 8 | # Short-Description: WebIOPi initscript | ||
| 9 | # Description: WebIOPi initscript | ||
| 10 | ### END INIT INFO | ||
| 11 | |||
| 12 | # Author: trouch <trouch@trouch.com> | ||
| 13 | LOG_FILE=/var/log/webiopi | ||
| 14 | CONFIG_FILE=/etc/webiopi/config | ||
| 15 | |||
| 16 | PATH=/sbin:/usr/sbin:/bin:/usr/bin | ||
| 17 | DESC="WebIOPi" | ||
| 18 | NAME=webiopi | ||
| 19 | HOME=/usr/share/webiopi/htdocs | ||
| 20 | DAEMON=/usr/bin/python | ||
| 21 | DAEMON_ARGS="-m webiopi -l $LOG_FILE -c $CONFIG_FILE" | ||
| 22 | PIDFILE=/var/run/$NAME.pid | ||
| 23 | SCRIPTNAME=/etc/init.d/$NAME | ||
| 24 | |||
| 25 | # Exit if the package is not installed | ||
| 26 | [ -x "$DAEMON" ] || exit 0 | ||
| 27 | |||
| 28 | # Read configuration variable file if it is present | ||
| 29 | [ -r /etc/default/$NAME ] && . /etc/default/$NAME | ||
| 30 | |||
| 31 | # Load the VERBOSE setting and other rcS variables | ||
| 32 | . /lib/init/vars.sh | ||
| 33 | |||
| 34 | # Define LSB log_* functions. | ||
| 35 | # Depend on lsb-base (>= 3.2-14) to ensure that this file is present | ||
| 36 | # and status_of_proc is working. | ||
| 37 | . /lib/lsb/init-functions | ||
| 38 | |||
| 39 | # | ||
| 40 | # Function that starts the daemon/service | ||
| 41 | # | ||
| 42 | do_start() | ||
| 43 | { | ||
| 44 | # Return | ||
| 45 | # 0 if daemon has been started | ||
| 46 | # 1 if daemon was already running | ||
| 47 | # 2 if daemon could not be started | ||
| 48 | start-stop-daemon --start --quiet --chdir $HOME --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \ | ||
| 49 | || return 1 | ||
| 50 | start-stop-daemon --start --quiet --chdir $HOME --pidfile $PIDFILE --exec $DAEMON --background --make-pidfile -- \ | ||
| 51 | $DAEMON_ARGS \ | ||
| 52 | || return 2 | ||
| 53 | # Add code here, if necessary, that waits for the process to be ready | ||
| 54 | # to handle requests from services started subsequently which depend | ||
| 55 | # on this one. As a last resort, sleep for some time. | ||
| 56 | } | ||
| 57 | |||
| 58 | # | ||
| 59 | # Function that stops the daemon/service | ||
| 60 | # | ||
| 61 | do_stop() | ||
| 62 | { | ||
| 63 | # Return | ||
| 64 | # 0 if daemon has been stopped | ||
| 65 | # 1 if daemon was already stopped | ||
| 66 | # 2 if daemon could not be stopped | ||
| 67 | # other if a failure occurred | ||
| 68 | start-stop-daemon --stop --quiet --pidfile $PIDFILE --name $NAME | ||
| 69 | RETVAL="$?" | ||
| 70 | [ "$RETVAL" = 2 ] && return 2 | ||
| 71 | # Wait for children to finish too if this is a daemon that forks | ||
| 72 | # and if the daemon is only ever run from this initscript. | ||
| 73 | # If the above conditions are not satisfied then add some other code | ||
| 74 | # that waits for the process to drop all resources that could be | ||
| 75 | # needed by services started subsequently. A last resort is to | ||
| 76 | # sleep for some time. | ||
| 77 | start-stop-daemon --stop --quiet --exec $DAEMON | ||
| 78 | [ "$?" = 2 ] && return 2 | ||
| 79 | # Many daemons don't delete their pidfiles when they exit. | ||
| 80 | rm -f $PIDFILE | ||
| 81 | return "$RETVAL" | ||
| 82 | } | ||
| 83 | |||
| 84 | # | ||
| 85 | # Function that sends a SIGHUP to the daemon/service | ||
| 86 | # | ||
| 87 | do_reload() { | ||
| 88 | # | ||
| 89 | # If the daemon can reload its configuration without | ||
| 90 | # restarting (for example, when it is sent a SIGHUP), | ||
| 91 | # then implement that here. | ||
| 92 | # | ||
| 93 | start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME | ||
| 94 | return 0 | ||
| 95 | } | ||
| 96 | |||
| 97 | case "$1" in | ||
| 98 | start) | ||
| 99 | [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME" | ||
| 100 | do_start | ||
| 101 | case "$?" in | ||
| 102 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; | ||
| 103 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; | ||
| 104 | esac | ||
| 105 | ;; | ||
| 106 | stop) | ||
| 107 | [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME" | ||
| 108 | do_stop | ||
| 109 | case "$?" in | ||
| 110 | 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;; | ||
| 111 | 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;; | ||
| 112 | esac | ||
| 113 | ;; | ||
| 114 | status) | ||
| 115 | status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $? | ||
| 116 | ;; | ||
| 117 | #reload|force-reload) | ||
| 118 | # | ||
| 119 | # If do_reload() is not implemented then leave this commented out | ||
| 120 | # and leave 'force-reload' as an alias for 'restart'. | ||
| 121 | # | ||
| 122 | #log_daemon_msg "Reloading $DESC" "$NAME" | ||
| 123 | #do_reload | ||
| 124 | #log_end_msg $? | ||
| 125 | #;; | ||
| 126 | restart|force-reload) | ||
| 127 | # | ||
| 128 | # If the "reload" option is implemented then remove the | ||
| 129 | # 'force-reload' alias | ||
| 130 | # | ||
| 131 | log_daemon_msg "Restarting $DESC" "$NAME" | ||
| 132 | do_stop | ||
| 133 | case "$?" in | ||
| 134 | 0|1) | ||
| 135 | do_start | ||
| 136 | case "$?" in | ||
| 137 | 0) log_end_msg 0 ;; | ||
| 138 | 1) log_end_msg 1 ;; # Old process is still running | ||
| 139 | *) log_end_msg 1 ;; # Failed to start | ||
| 140 | esac | ||
| 141 | ;; | ||
| 142 | *) | ||
| 143 | # Failed to stop | ||
| 144 | log_end_msg 1 | ||
| 145 | ;; | ||
| 146 | esac | ||
| 147 | ;; | ||
| 148 | *) | ||
| 149 | #echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2 | ||
| 150 | echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2 | ||
| 151 | exit 3 | ||
| 152 | ;; | ||
| 153 | esac | ||
| 154 | |||
| 155 | : | ||
diff --git a/python/webiopi.sh b/python/webiopi.sh new file mode 100755 index 0000000..0cf7bb5 --- /dev/null +++ b/python/webiopi.sh | |||
| @@ -0,0 +1,2 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | python -m webiopi $* | ||
diff --git a/python/webiopi/__init__.py b/python/webiopi/__init__.py new file mode 100644 index 0000000..c0f811e --- /dev/null +++ b/python/webiopi/__init__.py | |||
| @@ -0,0 +1,34 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | |||
| 16 | from time import sleep | ||
| 17 | |||
| 18 | from webiopi.utils.version import BOARD_REVISION, VERSION | ||
| 19 | from webiopi.utils.logger import setInfo, setDebug, info, debug, warn, error, exception | ||
| 20 | from webiopi.utils.thread import runLoop | ||
| 21 | from webiopi.server import Server | ||
| 22 | from webiopi.devices.instance import deviceInstance | ||
| 23 | from webiopi.decorators.rest import macro | ||
| 24 | |||
| 25 | from webiopi.devices import bus as _bus | ||
| 26 | |||
| 27 | try: | ||
| 28 | import _webiopi.GPIO as GPIO | ||
| 29 | except: | ||
| 30 | pass | ||
| 31 | |||
| 32 | |||
| 33 | setInfo() | ||
| 34 | _bus.checkAllBus() | ||
diff --git a/python/webiopi/__main__.py b/python/webiopi/__main__.py new file mode 100644 index 0000000..dc57bc2 --- /dev/null +++ b/python/webiopi/__main__.py | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import sys | ||
| 16 | from webiopi.server import Server | ||
| 17 | from webiopi.utils.loader import loadScript | ||
| 18 | from webiopi.utils.logger import exception, setDebug, info, logToFile | ||
| 19 | from webiopi.utils.version import VERSION_STRING | ||
| 20 | from webiopi.utils.thread import runLoop, stop | ||
| 21 | |||
| 22 | def displayHelp(): | ||
| 23 | print("WebIOPi command-line usage") | ||
| 24 | print("webiopi [-h] [-c config] [-l log] [-s script] [-d] [port]") | ||
| 25 | print("") | ||
| 26 | print("Options:") | ||
| 27 | print(" -h, --help Display this help") | ||
| 28 | print(" -c, --config file Load config from file") | ||
| 29 | print(" -l, --log file Log to file") | ||
| 30 | print(" -s, --script file Load script from file") | ||
| 31 | print(" -d, --debug Enable DEBUG") | ||
| 32 | print("") | ||
| 33 | print("Arguments:") | ||
| 34 | print(" port Port to bind the HTTP Server") | ||
| 35 | exit() | ||
| 36 | |||
| 37 | def main(argv): | ||
| 38 | port = 8000 | ||
| 39 | configfile = None | ||
| 40 | logfile = None | ||
| 41 | |||
| 42 | i = 1 | ||
| 43 | while i < len(argv): | ||
| 44 | if argv[i] in ["-c", "-C", "--config-file"]: | ||
| 45 | configfile = argv[i+1] | ||
| 46 | i+=1 | ||
| 47 | elif argv[i] in ["-l", "-L", "--log-file"]: | ||
| 48 | logfile = argv[i+1] | ||
| 49 | i+=1 | ||
| 50 | elif argv[i] in ["-s", "-S", "--script-file"]: | ||
| 51 | scriptfile = argv[i+1] | ||
| 52 | scriptname = scriptfile.split("/")[-1].split(".")[0] | ||
| 53 | loadScript(scriptname, scriptfile) | ||
| 54 | i+=1 | ||
| 55 | elif argv[i] in ["-h", "-H", "--help"]: | ||
| 56 | displayHelp() | ||
| 57 | elif argv[i] in ["-d", "--debug"]: | ||
| 58 | setDebug() | ||
| 59 | else: | ||
| 60 | try: | ||
| 61 | port = int(argv[i]) | ||
| 62 | except ValueError: | ||
| 63 | displayHelp() | ||
| 64 | i+=1 | ||
| 65 | |||
| 66 | if logfile: | ||
| 67 | logToFile(logfile) | ||
| 68 | |||
| 69 | info("Starting %s" % VERSION_STRING) | ||
| 70 | server = Server(port=port, configfile=configfile) | ||
| 71 | runLoop() | ||
| 72 | server.stop() | ||
| 73 | |||
| 74 | if __name__ == "__main__": | ||
| 75 | try: | ||
| 76 | main(sys.argv) | ||
| 77 | except Exception as e: | ||
| 78 | exception(e) | ||
| 79 | stop() | ||
diff --git a/python/webiopi/clients/__init__.py b/python/webiopi/clients/__init__.py new file mode 100644 index 0000000..d8527af --- /dev/null +++ b/python/webiopi/clients/__init__.py | |||
| @@ -0,0 +1,209 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.logger import LOGGER | ||
| 16 | from webiopi.utils.version import PYTHON_MAJOR | ||
| 17 | from webiopi.utils.crypto import encodeCredentials | ||
| 18 | from webiopi.protocols.coap import COAPClient, COAPGet, COAPPost, COAPPut, COAPDelete | ||
| 19 | |||
| 20 | if PYTHON_MAJOR >= 3: | ||
| 21 | import http.client as httplib | ||
| 22 | else: | ||
| 23 | import httplib | ||
| 24 | |||
| 25 | class PiMixedClient(): | ||
| 26 | def __init__(self, host, port=8000, coap=5683): | ||
| 27 | self.host = host | ||
| 28 | if coap > 0: | ||
| 29 | self.coapport = coap | ||
| 30 | self.coapclient = COAPClient() | ||
| 31 | else: | ||
| 32 | self.coapclient = None | ||
| 33 | if port > 0: | ||
| 34 | self.httpclient = httplib.HTTPConnection(host, port) | ||
| 35 | else: | ||
| 36 | self.httpclient = None | ||
| 37 | self.forceHttp = False | ||
| 38 | self.coapfailure = 0 | ||
| 39 | self.maxfailure = 2 | ||
| 40 | self.auth= None; | ||
| 41 | |||
| 42 | def setCredentials(self, login, password): | ||
| 43 | self.auth = "Basic " + encodeCredentials(login, password) | ||
| 44 | |||
| 45 | def sendRequest(self, method, uri): | ||
| 46 | if self.coapclient != None and not self.forceHttp: | ||
| 47 | if method == "GET": | ||
| 48 | response = self.coapclient.sendRequest(COAPGet("coap://%s:%d%s" % (self.host, self.coapport, uri))) | ||
| 49 | elif method == "POST": | ||
| 50 | response = self.coapclient.sendRequest(COAPPost("coap://%s:%d%s" % (self.host, self.coapport, uri))) | ||
| 51 | |||
| 52 | if response: | ||
| 53 | return str(response.payload) | ||
| 54 | elif self.httpclient != None: | ||
| 55 | self.coapfailure += 1 | ||
| 56 | print("No CoAP response, fall-back to HTTP") | ||
| 57 | if (self.coapfailure > self.maxfailure): | ||
| 58 | self.forceHttp = True | ||
| 59 | self.coapfailure = 0 | ||
| 60 | print("Too many CoAP failure forcing HTTP") | ||
| 61 | |||
| 62 | if self.httpclient != None: | ||
| 63 | headers = {} | ||
| 64 | if self.auth != None: | ||
| 65 | headers["Authorization"] = self.auth | ||
| 66 | |||
| 67 | self.httpclient.request(method, uri, None, headers) | ||
| 68 | response = self.httpclient.getresponse() | ||
| 69 | if response.status == 200: | ||
| 70 | data = response.read() | ||
| 71 | return data | ||
| 72 | elif response.status == 401: | ||
| 73 | raise Exception("Missing credentials") | ||
| 74 | else: | ||
| 75 | raise Exception("Unhandled HTTP Response %d %s" % (response.status, response.reason)) | ||
| 76 | |||
| 77 | raise Exception("No data received") | ||
| 78 | |||
| 79 | class PiHttpClient(PiMixedClient): | ||
| 80 | def __init__(self, host, port=8000): | ||
| 81 | PiMixedClient.__init__(self, host, port, -1) | ||
| 82 | |||
| 83 | class PiCoapClient(PiMixedClient): | ||
| 84 | def __init__(self, host, port=5683): | ||
| 85 | PiMixedClient.__init__(self, host, -1, port) | ||
| 86 | |||
| 87 | class PiMulticastClient(PiMixedClient): | ||
| 88 | def __init__(self, port=5683): | ||
| 89 | PiMixedClient.__init__(self, "224.0.1.123", -1, port) | ||
| 90 | |||
| 91 | class RESTAPI(): | ||
| 92 | def __init__(self, client, path): | ||
| 93 | self.client = client | ||
| 94 | self.path = path | ||
| 95 | |||
| 96 | def sendRequest(self, method, path): | ||
| 97 | return self.client.sendRequest(method, self.path + path) | ||
| 98 | |||
| 99 | class Macro(RESTAPI): | ||
| 100 | def __init__(self, client, name): | ||
| 101 | RESTAPI.__init__(self, client, "/macros/" + name + "/") | ||
| 102 | |||
| 103 | def call(self, *args): | ||
| 104 | values = ",".join(["%s" % i for i in args]) | ||
| 105 | if values == None: | ||
| 106 | values = "" | ||
| 107 | return self.sendRequest("POST", values) | ||
| 108 | |||
| 109 | class Device(RESTAPI): | ||
| 110 | def __init__(self, client, name, category): | ||
| 111 | RESTAPI.__init__(self, client, "/devices/" + name + "/" + category) | ||
| 112 | |||
| 113 | class GPIO(Device): | ||
| 114 | def __init__(self, client, name): | ||
| 115 | Device.__init__(self, client, name, "digital") | ||
| 116 | |||
| 117 | def getFunction(self, channel): | ||
| 118 | return self.sendRequest("GET", "/%d/function" % channel) | ||
| 119 | |||
| 120 | def setFunction(self, channel, func): | ||
| 121 | return self.sendRequest("POST", "/%d/function/%s" % (channel, func)) | ||
| 122 | |||
| 123 | def digitalRead(self, channel): | ||
| 124 | return int(self.sendRequest("GET", "/%d/value" % channel)) | ||
| 125 | |||
| 126 | def digitalWrite(self, channel, value): | ||
| 127 | return int(self.sendRequest("POST", "/%d/value/%d" % (channel, value))) | ||
| 128 | |||
| 129 | def portRead(self): | ||
| 130 | return int(self.sendRequest("GET", "/integer")) | ||
| 131 | |||
| 132 | def portWrite(self, value): | ||
| 133 | return int(self.sendRequest("POST", "/integer/%d" % value)) | ||
| 134 | |||
| 135 | class NativeGPIO(GPIO): | ||
| 136 | def __init__(self, client): | ||
| 137 | RESTAPI.__init__(self, client, "/GPIO") | ||
| 138 | |||
| 139 | class ADC(Device): | ||
| 140 | def __init__(self, client, name): | ||
| 141 | Device.__init__(self, client, name, "analog") | ||
| 142 | |||
| 143 | def read(self, channel): | ||
| 144 | return float(self.sendRequest("GET", "/%d/integer" % channel)) | ||
| 145 | |||
| 146 | def readFloat(self, channel): | ||
| 147 | return float(self.sendRequest("GET", "/%d/float" % channel)) | ||
| 148 | |||
| 149 | def readVolt(self, channel): | ||
| 150 | return float(self.sendRequest("GET", "/%d/volt" % channel)) | ||
| 151 | |||
| 152 | class DAC(ADC): | ||
| 153 | def __init__(self, client, name): | ||
| 154 | Device.__init__(self, client, name, "analog") | ||
| 155 | |||
| 156 | def write(self, channel, value): | ||
| 157 | return float(self.sendRequest("POST", "/%d/integer/%d" % (channel, value))) | ||
| 158 | |||
| 159 | def writeFloat(self, channel, value): | ||
| 160 | return float(self.sendRequest("POST", "/%d/float/%f" % (channel, value))) | ||
| 161 | |||
| 162 | def writeVolt(self, channel, value): | ||
| 163 | return float(self.sendRequest("POST", "/%d/volt/%f" % (channel, value))) | ||
| 164 | |||
| 165 | class PWM(DAC): | ||
| 166 | def __init__(self, client, name): | ||
| 167 | Device.__init__(self, client, name, "pwm") | ||
| 168 | |||
| 169 | def readAngle(self, channel, value): | ||
| 170 | return float(self.sendRequest("GET", "/%d/angle" % (channel))) | ||
| 171 | |||
| 172 | def writeAngle(self, channel, value): | ||
| 173 | return float(self.sendRequest("POST", "/%d/angle/%f" % (channel, value))) | ||
| 174 | |||
| 175 | class Sensor(Device): | ||
| 176 | def __init__(self, client, name): | ||
| 177 | Device.__init__(self, client, name, "sensor") | ||
| 178 | |||
| 179 | class Temperature(Sensor): | ||
| 180 | def getKelvin(self): | ||
| 181 | return float(self.sendRequest("GET", "/temperature/k")) | ||
| 182 | |||
| 183 | def getCelsius(self): | ||
| 184 | return float(self.sendRequest("GET", "/temperature/c")) | ||
| 185 | |||
| 186 | def getFahrenheit(self): | ||
| 187 | return float(self.sendRequest("GET", "/temperature/f")) | ||
| 188 | |||
| 189 | class Pressure(Sensor): | ||
| 190 | def getPascal(self): | ||
| 191 | return float(self.sendRequest("GET", "/pressure/pa")) | ||
| 192 | |||
| 193 | def getHectoPascal(self): | ||
| 194 | return float(self.sendRequest("GET", "/pressure/hpa")) | ||
| 195 | |||
| 196 | class Luminosity(Sensor): | ||
| 197 | def getLux(self): | ||
| 198 | return float(self.sendRequest("GET", "/luminosity/lux")) | ||
| 199 | |||
| 200 | class Distance(Sensor): | ||
| 201 | def getMillimeter(self): | ||
| 202 | return float(self.sendRequest("GET", "/distance/mm")) | ||
| 203 | |||
| 204 | def getCentimeter(self): | ||
| 205 | return float(self.sendRequest("GET", "/distance/cm")) | ||
| 206 | |||
| 207 | def getInch(self): | ||
| 208 | return float(self.sendRequest("GET", "/distance/in")) | ||
| 209 | |||
diff --git a/python/webiopi/decorators/__init__.py b/python/webiopi/decorators/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/python/webiopi/decorators/__init__.py | |||
diff --git a/python/webiopi/decorators/rest.py b/python/webiopi/decorators/rest.py new file mode 100644 index 0000000..7fffacc --- /dev/null +++ b/python/webiopi/decorators/rest.py | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | def request(method="GET", path="", data=None): | ||
| 2 | def wrapper(func): | ||
| 3 | func.routed = True | ||
| 4 | func.method = method | ||
| 5 | func.path = path | ||
| 6 | func.data = data | ||
| 7 | return func | ||
| 8 | return wrapper | ||
| 9 | |||
| 10 | def response(fmt="%s", contentType="text/plain"): | ||
| 11 | def wrapper(func): | ||
| 12 | func.format = fmt | ||
| 13 | func.contentType = contentType | ||
| 14 | return func | ||
| 15 | return wrapper | ||
| 16 | |||
| 17 | def macro(func): | ||
| 18 | func.macro = True | ||
| 19 | return func | ||
diff --git a/python/webiopi/devices/__init__.py b/python/webiopi/devices/__init__.py new file mode 100644 index 0000000..ecd45ec --- /dev/null +++ b/python/webiopi/devices/__init__.py | |||
| @@ -0,0 +1,13 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
diff --git a/python/webiopi/devices/analog/__init__.py b/python/webiopi/devices/analog/__init__.py new file mode 100644 index 0000000..4c94b9c --- /dev/null +++ b/python/webiopi/devices/analog/__init__.py | |||
| @@ -0,0 +1,267 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.decorators.rest import request, response | ||
| 16 | from webiopi.utils.types import M_JSON | ||
| 17 | |||
| 18 | class ADC(): | ||
| 19 | def __init__(self, channelCount, resolution, vref): | ||
| 20 | self._analogCount = channelCount | ||
| 21 | self._analogResolution = resolution | ||
| 22 | self._analogMax = 2**resolution - 1 | ||
| 23 | self._analogRef = vref | ||
| 24 | |||
| 25 | def __family__(self): | ||
| 26 | return "ADC" | ||
| 27 | |||
| 28 | def checkAnalogChannel(self, channel): | ||
| 29 | if not 0 <= channel < self._analogCount: | ||
| 30 | raise ValueError("Channel %d out of range [%d..%d]" % (channel, 0, self._analogCount-1)) | ||
| 31 | |||
| 32 | def checkAnalogValue(self, value): | ||
| 33 | if not 0 <= value <= self._analogMax: | ||
| 34 | raise ValueError("Value %d out of range [%d..%d]" % (value, 0, self._analogMax)) | ||
| 35 | |||
| 36 | @request("GET", "analog/count") | ||
| 37 | @response("%d") | ||
| 38 | def analogCount(self): | ||
| 39 | return self._analogCount | ||
| 40 | |||
| 41 | @request("GET", "analog/resolution") | ||
| 42 | @response("%d") | ||
| 43 | def analogResolution(self): | ||
| 44 | return self._analogResolution | ||
| 45 | |||
| 46 | @request("GET", "analog/max") | ||
| 47 | @response("%d") | ||
| 48 | def analogMaximum(self): | ||
| 49 | return int(self._analogMax) | ||
| 50 | |||
| 51 | @request("GET", "analog/vref") | ||
| 52 | @response("%.2f") | ||
| 53 | def analogReference(self): | ||
| 54 | return self._analogRef | ||
| 55 | |||
| 56 | def __analogRead__(self, channel, diff): | ||
| 57 | raise NotImplementedError | ||
| 58 | |||
| 59 | @request("GET", "analog/%(channel)d/integer") | ||
| 60 | @response("%d") | ||
| 61 | def analogRead(self, channel, diff=False): | ||
| 62 | self.checkAnalogChannel(channel) | ||
| 63 | return self.__analogRead__(channel, diff) | ||
| 64 | |||
| 65 | @request("GET", "analog/%(channel)d/float") | ||
| 66 | @response("%.2f") | ||
| 67 | def analogReadFloat(self, channel, diff=False): | ||
| 68 | return self.analogRead(channel, diff) / float(self._analogMax) | ||
| 69 | |||
| 70 | @request("GET", "analog/%(channel)d/volt") | ||
| 71 | @response("%.2f") | ||
| 72 | def analogReadVolt(self, channel, diff=False): | ||
| 73 | if self._analogRef == 0: | ||
| 74 | raise NotImplementedError | ||
| 75 | return self.analogReadFloat(channel, diff) * self._analogRef | ||
| 76 | |||
| 77 | @request("GET", "analog/*/integer") | ||
| 78 | @response(contentType=M_JSON) | ||
| 79 | def analogReadAll(self): | ||
| 80 | values = {} | ||
| 81 | for i in range(self._analogCount): | ||
| 82 | values[i] = self.analogRead(i) | ||
| 83 | return values | ||
| 84 | |||
| 85 | @request("GET", "analog/*/float") | ||
| 86 | @response(contentType=M_JSON) | ||
| 87 | def analogReadAllFloat(self): | ||
| 88 | values = {} | ||
| 89 | for i in range(self._analogCount): | ||
| 90 | values[i] = float("%.2f" % self.analogReadFloat(i)) | ||
| 91 | return values | ||
| 92 | |||
| 93 | @request("GET", "analog/*/volt") | ||
| 94 | @response(contentType=M_JSON) | ||
| 95 | def analogReadAllVolt(self): | ||
| 96 | values = {} | ||
| 97 | for i in range(self._analogCount): | ||
| 98 | values[i] = float("%.2f" % self.analogReadVolt(i)) | ||
| 99 | return values | ||
| 100 | |||
| 101 | class DAC(ADC): | ||
| 102 | def __init__(self, channelCount, resolution, vref): | ||
| 103 | ADC.__init__(self, channelCount, resolution, vref) | ||
| 104 | |||
| 105 | def __family__(self): | ||
| 106 | return "DAC" | ||
| 107 | |||
| 108 | def __analogWrite__(self, channel, value): | ||
| 109 | raise NotImplementedError | ||
| 110 | |||
| 111 | @request("POST", "analog/%(channel)d/integer/%(value)d") | ||
| 112 | @response("%d") | ||
| 113 | def analogWrite(self, channel, value): | ||
| 114 | self.checkAnalogChannel(channel) | ||
| 115 | self.checkAnalogValue(value) | ||
| 116 | self.__analogWrite__(channel, value) | ||
| 117 | return self.analogRead(channel) | ||
| 118 | |||
| 119 | @request("POST", "analog/%(channel)d/float/%(value)f") | ||
| 120 | @response("%.2f") | ||
| 121 | def analogWriteFloat(self, channel, value): | ||
| 122 | self.analogWrite(channel, int(value * self._analogMax)) | ||
| 123 | return self.analogReadFloat(channel) | ||
| 124 | |||
| 125 | @request("POST", "analog/%(channel)d/volt/%(value)f") | ||
| 126 | @response("%.2f") | ||
| 127 | def analogWriteVolt(self, channel, value): | ||
| 128 | self.analogWriteFloat(channel, value /self._analogRef) | ||
| 129 | return self.analogReadVolt(channel) | ||
| 130 | |||
| 131 | |||
| 132 | class PWM(): | ||
| 133 | def __init__(self, channelCount, resolution, frequency): | ||
| 134 | self._pwmCount = channelCount | ||
| 135 | self._pwmResolution = resolution | ||
| 136 | self._pwmMax = 2**resolution - 1 | ||
| 137 | self.frequency = frequency | ||
| 138 | self.period = 1.0/frequency | ||
| 139 | |||
| 140 | # Futaba servos standard | ||
| 141 | self.servo_neutral = 0.00152 | ||
| 142 | self.servo_travel_time = 0.0004 | ||
| 143 | self.servo_travel_angle = 45.0 | ||
| 144 | |||
| 145 | self.reverse = [False for i in range(channelCount)] | ||
| 146 | |||
| 147 | def __family__(self): | ||
| 148 | return "PWM" | ||
| 149 | |||
| 150 | def checkPWMChannel(self, channel): | ||
| 151 | if not 0 <= channel < self._pwmCount: | ||
| 152 | raise ValueError("Channel %d out of range [%d..%d]" % (channel, 0, self._pwmCount-1)) | ||
| 153 | |||
| 154 | def checkPWMValue(self, value): | ||
| 155 | if not 0 <= value <= self._pwmMax: | ||
| 156 | raise ValueError("Value %d out of range [%d..%d]" % (value, 0, self._pwmMax)) | ||
| 157 | |||
| 158 | def __pwmRead__(self, channel): | ||
| 159 | raise NotImplementedError | ||
| 160 | |||
| 161 | def __pwmWrite__(self, channel, value): | ||
| 162 | raise NotImplementedError | ||
| 163 | |||
| 164 | @request("GET", "pwm/count") | ||
| 165 | @response("%d") | ||
| 166 | def pwmCount(self): | ||
| 167 | return self._pwmCount | ||
| 168 | |||
| 169 | @request("GET", "pwm/resolution") | ||
| 170 | @response("%d") | ||
| 171 | def pwmResolution(self): | ||
| 172 | return self._pwmResolution | ||
| 173 | |||
| 174 | @request("GET", "pwm/max") | ||
| 175 | @response("%d") | ||
| 176 | def pwmMaximum(self): | ||
| 177 | return int(self._pwmMax) | ||
| 178 | |||
| 179 | @request("GET", "pwm/%(channel)d/integer") | ||
| 180 | @response("%d") | ||
| 181 | def pwmRead(self, channel): | ||
| 182 | self.checkPWMChannel(channel) | ||
| 183 | return self.__pwmRead__(channel) | ||
| 184 | |||
| 185 | @request("GET", "pwm/%(channel)d/float") | ||
| 186 | @response("%.2f") | ||
| 187 | def pwmReadFloat(self, channel): | ||
| 188 | return self.pwmRead(channel) / float(self._pwmMax) | ||
| 189 | |||
| 190 | @request("POST", "pwm/%(channel)d/integer/%(value)d") | ||
| 191 | @response("%d") | ||
| 192 | def pwmWrite(self, channel, value): | ||
| 193 | self.checkPWMChannel(channel) | ||
| 194 | self.checkPWMValue(value) | ||
| 195 | self.__pwmWrite__(channel, value) | ||
| 196 | return self.pwmRead(channel) | ||
| 197 | |||
| 198 | @request("POST", "pwm/%(channel)d/float/%(value)f") | ||
| 199 | @response("%.2f") | ||
| 200 | def pwmWriteFloat(self, channel, value): | ||
| 201 | self.pwmWrite(channel, int(value * self._pwmMax)) | ||
| 202 | return self.pwmReadFloat(channel) | ||
| 203 | |||
| 204 | def getReverse(self, channel): | ||
| 205 | self.checkChannel(channel) | ||
| 206 | return self.reverse[channel] | ||
| 207 | |||
| 208 | def setReverse(self, channel, value): | ||
| 209 | self.checkChannel(channel) | ||
| 210 | self.reverse[channel] = value | ||
| 211 | return value | ||
| 212 | |||
| 213 | def RatioToAngle(self, value): | ||
| 214 | f = value | ||
| 215 | f *= self.period | ||
| 216 | f -= self.servo_neutral | ||
| 217 | f *= self.servo_travel_angle | ||
| 218 | f /= self.servo_travel_time | ||
| 219 | return f | ||
| 220 | |||
| 221 | def AngleToRatio(self, value): | ||
| 222 | f = value | ||
| 223 | f *= self.servo_travel_time | ||
| 224 | f /= self.servo_travel_angle | ||
| 225 | f += self.servo_neutral | ||
| 226 | f /= self.period | ||
| 227 | return f | ||
| 228 | |||
| 229 | @request("GET", "pwm/%(channel)d/angle") | ||
| 230 | @response("%.2f") | ||
| 231 | def pwmReadAngle(self, channel): | ||
| 232 | f = self.pwmReadFloat(channel) | ||
| 233 | f = self.RatioToAngle(f) | ||
| 234 | if self.reverse[channel]: | ||
| 235 | f = -f | ||
| 236 | else: | ||
| 237 | f = f | ||
| 238 | return f | ||
| 239 | |||
| 240 | @request("POST", "pwm/%(channel)d/angle/%(value)f") | ||
| 241 | @response("%.2f") | ||
| 242 | def pwmWriteAngle(self, channel, value): | ||
| 243 | if self.reverse[channel]: | ||
| 244 | f = -value | ||
| 245 | else: | ||
| 246 | f = value | ||
| 247 | f = self.AngleToRatio(f) | ||
| 248 | self.pwmWriteFloat(channel, f) | ||
| 249 | return self.pwmReadAngle(channel) | ||
| 250 | |||
| 251 | @request("GET", "pwm/*") | ||
| 252 | @response(contentType=M_JSON) | ||
| 253 | def pwmWildcard(self): | ||
| 254 | values = {} | ||
| 255 | for i in range(self._pwmCount): | ||
| 256 | val = self.pwmReadFloat(i) | ||
| 257 | values[i] = {} | ||
| 258 | values[i]["float"] = float("%.2f" % val) | ||
| 259 | values[i]["angle"] = float("%.2f" % self.RatioToAngle(val)) | ||
| 260 | return values | ||
| 261 | |||
| 262 | DRIVERS = {} | ||
| 263 | DRIVERS["ads1x1x"] = ["ADS1014", "ADS1015", "ADS1114", "ADS1115"] | ||
| 264 | DRIVERS["mcp3x0x"] = ["MCP3004", "MCP3008", "MCP3204", "MCP3208"] | ||
| 265 | DRIVERS["mcp4725"] = ["MCP4725"] | ||
| 266 | DRIVERS["mcp492X"] = ["MCP4921", "MCP4922"] | ||
| 267 | DRIVERS["pca9685"] = ["PCA9685"] | ||
diff --git a/python/webiopi/devices/analog/ads1x1x.py b/python/webiopi/devices/analog/ads1x1x.py new file mode 100644 index 0000000..969fd8f --- /dev/null +++ b/python/webiopi/devices/analog/ads1x1x.py | |||
| @@ -0,0 +1,82 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from time import sleep | ||
| 16 | from webiopi.utils.types import toint, signInteger | ||
| 17 | from webiopi.devices.i2c import I2C | ||
| 18 | from webiopi.devices.analog import ADC | ||
| 19 | |||
| 20 | |||
| 21 | class ADS1X1X(ADC, I2C): | ||
| 22 | VALUE = 0x00 | ||
| 23 | CONFIG = 0x01 | ||
| 24 | LO_THRESH = 0x02 | ||
| 25 | HI_THRESH = 0x03 | ||
| 26 | |||
| 27 | CONFIG_STATUS_MASK = 0x80 | ||
| 28 | CONFIG_CHANNEL_MASK = 0x70 | ||
| 29 | CONFIG_GAIN_MASK = 0x0E | ||
| 30 | CONFIG_MODE_MASK = 0x01 | ||
| 31 | |||
| 32 | def __init__(self, slave, channelCount, resolution, name): | ||
| 33 | I2C.__init__(self, toint(slave)) | ||
| 34 | ADC.__init__(self, channelCount, resolution, 4.096) | ||
| 35 | self._analogMax = 2**(resolution-1) | ||
| 36 | self.name = name | ||
| 37 | |||
| 38 | config = self.readRegisters(self.CONFIG, 2) | ||
| 39 | |||
| 40 | mode = 0 # continuous | ||
| 41 | config[0] &= ~self.CONFIG_MODE_MASK | ||
| 42 | config[0] |= mode | ||
| 43 | |||
| 44 | gain = 0x1 # FS = +/- 4.096V | ||
| 45 | config[0] &= ~self.CONFIG_GAIN_MASK | ||
| 46 | config[0] |= gain << 1 | ||
| 47 | |||
| 48 | self.writeRegisters(self.CONFIG, config) | ||
| 49 | |||
| 50 | def __str__(self): | ||
| 51 | return "%s(slave=0x%02X)" % (self.name, self.slave) | ||
| 52 | |||
| 53 | def __analogRead__(self, channel, diff=False): | ||
| 54 | config = self.readRegisters(self.CONFIG, 2) | ||
| 55 | config[0] &= ~self.CONFIG_CHANNEL_MASK | ||
| 56 | if diff: | ||
| 57 | config[0] |= channel << 4 | ||
| 58 | else: | ||
| 59 | config[0] |= (channel + 4) << 4 | ||
| 60 | self.writeRegisters(self.CONFIG, config) | ||
| 61 | sleep(0.001) | ||
| 62 | d = self.readRegisters(self.VALUE, 2) | ||
| 63 | value = (d[0] << 8 | d[1]) >> (16-self._analogResolution) | ||
| 64 | return signInteger(value, self._analogResolution) | ||
| 65 | |||
| 66 | |||
| 67 | class ADS1014(ADS1X1X): | ||
| 68 | def __init__(self, slave=0x48): | ||
| 69 | ADS1X1X.__init__(self, slave, 1, 12, "ADS1014") | ||
| 70 | |||
| 71 | class ADS1015(ADS1X1X): | ||
| 72 | def __init__(self, slave=0x48): | ||
| 73 | ADS1X1X.__init__(self, slave, 4, 12, "ADS1015") | ||
| 74 | |||
| 75 | class ADS1114(ADS1X1X): | ||
| 76 | def __init__(self, slave=0x48): | ||
| 77 | ADS1X1X.__init__(self, slave, 1, 16, "ADS1114") | ||
| 78 | |||
| 79 | class ADS1115(ADS1X1X): | ||
| 80 | def __init__(self, slave=0x48): | ||
| 81 | ADS1X1X.__init__(self, slave, 4, 16, "ADS1115") | ||
| 82 | |||
diff --git a/python/webiopi/devices/analog/mcp3x0x.py b/python/webiopi/devices/analog/mcp3x0x.py new file mode 100644 index 0000000..6a23bf0 --- /dev/null +++ b/python/webiopi/devices/analog/mcp3x0x.py | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint | ||
| 16 | from webiopi.devices.spi import SPI | ||
| 17 | from webiopi.devices.analog import ADC | ||
| 18 | |||
| 19 | class MCP3X0X(SPI, ADC): | ||
| 20 | def __init__(self, chip, channelCount, resolution, name): | ||
| 21 | SPI.__init__(self, toint(chip), 0, 8, 10000) | ||
| 22 | ADC.__init__(self, channelCount, resolution, 3.3) | ||
| 23 | self.name = name | ||
| 24 | self.MSB_MASK = 2**(resolution-8) - 1 | ||
| 25 | |||
| 26 | def __str__(self): | ||
| 27 | return "%s(chip=%d)" % (self.name, self.chip) | ||
| 28 | |||
| 29 | def __analogRead__(self, channel, diff): | ||
| 30 | data = self.__command__(channel, diff) | ||
| 31 | r = self.xfer(data) | ||
| 32 | return ((r[1] & self.MSB_MASK) << 8) | r[2] | ||
| 33 | |||
| 34 | class MCP300X(MCP3X0X): | ||
| 35 | def __init__(self, chip, channelCount, name): | ||
| 36 | MCP3X0X.__init__(self, chip, channelCount, 10, name) | ||
| 37 | |||
| 38 | def __command__(self, channel, diff): | ||
| 39 | d = [0x00, 0x00, 0x00] | ||
| 40 | d[0] |= 1 | ||
| 41 | d[1] |= (not diff) << 7 | ||
| 42 | d[1] |= ((channel >> 2) & 0x01) << 6 | ||
| 43 | d[1] |= ((channel >> 1) & 0x01) << 5 | ||
| 44 | d[1] |= ((channel >> 0) & 0x01) << 4 | ||
| 45 | return d | ||
| 46 | |||
| 47 | class MCP3004(MCP300X): | ||
| 48 | def __init__(self, chip=0): | ||
| 49 | MCP300X.__init__(self, chip, 4, "MCP3004") | ||
| 50 | |||
| 51 | class MCP3008(MCP300X): | ||
| 52 | def __init__(self, chip=0): | ||
| 53 | MCP300X.__init__(self, chip, 8, "MCP3008") | ||
| 54 | |||
| 55 | class MCP320X(MCP3X0X): | ||
| 56 | def __init__(self, chip, channelCount, name): | ||
| 57 | MCP3X0X.__init__(self, chip, channelCount, 12, name) | ||
| 58 | |||
| 59 | def __command__(self, channel, diff): | ||
| 60 | d = [0x00, 0x00, 0x00] | ||
| 61 | d[0] |= 1 << 2 | ||
| 62 | d[0] |= (not diff) << 1 | ||
| 63 | d[0] |= (channel >> 2) & 0x01 | ||
| 64 | d[1] |= ((channel >> 1) & 0x01) << 7 | ||
| 65 | d[1] |= ((channel >> 0) & 0x01) << 6 | ||
| 66 | return d | ||
| 67 | |||
| 68 | class MCP3204(MCP320X): | ||
| 69 | def __init__(self, chip=0): | ||
| 70 | MCP320X.__init__(self, chip, 4, "MCP3204") | ||
| 71 | |||
| 72 | class MCP3208(MCP320X): | ||
| 73 | def __init__(self, chip=0): | ||
| 74 | MCP320X.__init__(self, chip, 8, "MCP3208") | ||
| 75 | |||
diff --git a/python/webiopi/devices/analog/mcp4725.py b/python/webiopi/devices/analog/mcp4725.py new file mode 100644 index 0000000..a46337d --- /dev/null +++ b/python/webiopi/devices/analog/mcp4725.py | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint | ||
| 16 | from webiopi.devices.i2c import I2C | ||
| 17 | from webiopi.devices.analog import DAC | ||
| 18 | |||
| 19 | |||
| 20 | class MCP4725(DAC, I2C): | ||
| 21 | def __init__(self, slave=0x60): | ||
| 22 | I2C.__init__(self, toint(slave)) | ||
| 23 | DAC.__init__(self, 1, 12, 3.3) | ||
| 24 | |||
| 25 | def __str__(self): | ||
| 26 | return "MCP4725(slave=0x%02X)" % self.slave | ||
| 27 | |||
| 28 | def __analogRead__(self, channel, diff=False): | ||
| 29 | d = self.readBytes(3) | ||
| 30 | value = (d[1] << 8 | d[2]) >> 4 | ||
| 31 | return value | ||
| 32 | |||
| 33 | |||
| 34 | def __analogWrite__(self, channel, value): | ||
| 35 | d = bytearray(2) | ||
| 36 | d[0] = (value >> 8) & 0x0F | ||
| 37 | d[1] = value & 0xFF | ||
| 38 | self.writeBytes(d) \ No newline at end of file | ||
diff --git a/python/webiopi/devices/analog/mcp492X.py b/python/webiopi/devices/analog/mcp492X.py new file mode 100644 index 0000000..4489149 --- /dev/null +++ b/python/webiopi/devices/analog/mcp492X.py | |||
| @@ -0,0 +1,53 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint | ||
| 16 | from webiopi.devices.spi import SPI | ||
| 17 | from webiopi.devices.analog import DAC | ||
| 18 | |||
| 19 | class MCP492X(SPI, DAC): | ||
| 20 | def __init__(self, chip, channelCount): | ||
| 21 | SPI.__init__(self, toint(chip), 0, 8, 10000000) | ||
| 22 | DAC.__init__(self, channelCount, 12, 3.3) | ||
| 23 | self.buffered=False | ||
| 24 | self.gain=False | ||
| 25 | self.shutdown=False | ||
| 26 | self.values = [0 for i in range(channelCount)] | ||
| 27 | |||
| 28 | def __str__(self): | ||
| 29 | return "MCP492%d(chip=%d)" % (self._analogCount, self.chip) | ||
| 30 | |||
| 31 | def __analogRead__(self, channel, diff=False): | ||
| 32 | return self.values[channel] | ||
| 33 | |||
| 34 | def __analogWrite__(self, channel, value): | ||
| 35 | d = bytearray(2) | ||
| 36 | d[0] = 0 | ||
| 37 | d[0] |= (channel & 0x01) << 7 | ||
| 38 | d[0] |= (self.buffered & 0x01) << 6 | ||
| 39 | d[0] |= (not self.gain & 0x01) << 5 | ||
| 40 | d[0] |= (not self.shutdown & 0x01) << 4 | ||
| 41 | d[0] |= (value >> 8) & 0x0F | ||
| 42 | d[1] = value & 0xFF | ||
| 43 | self.writeBytes(d) | ||
| 44 | self.values[channel] = value | ||
| 45 | |||
| 46 | class MCP4921(MCP492X): | ||
| 47 | def __init__(self, chip=0): | ||
| 48 | MCP492X.__init__(self, chip, 1) | ||
| 49 | |||
| 50 | class MCP4922(MCP492X): | ||
| 51 | def __init__(self, chip=0): | ||
| 52 | MCP492X.__init__(self, chip, 2) | ||
| 53 | |||
diff --git a/python/webiopi/devices/analog/pca9685.py b/python/webiopi/devices/analog/pca9685.py new file mode 100644 index 0000000..3b91121 --- /dev/null +++ b/python/webiopi/devices/analog/pca9685.py | |||
| @@ -0,0 +1,63 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import time | ||
| 16 | from webiopi.utils.types import toint | ||
| 17 | from webiopi.devices.i2c import I2C | ||
| 18 | from webiopi.devices.analog import PWM | ||
| 19 | |||
| 20 | class PCA9685(PWM, I2C): | ||
| 21 | MODE1 = 0x00 | ||
| 22 | PWM_BASE = 0x06 | ||
| 23 | PRESCALE = 0xFE | ||
| 24 | |||
| 25 | M1_SLEEP = 1<<4 | ||
| 26 | M1_AI = 1<<5 | ||
| 27 | M1_RESTART = 1<<7 | ||
| 28 | |||
| 29 | def __init__(self, slave=0x40, frequency=50): | ||
| 30 | I2C.__init__(self, toint(slave)) | ||
| 31 | PWM.__init__(self, 16, 12, toint(frequency)) | ||
| 32 | self.VREF = 0 | ||
| 33 | |||
| 34 | self.prescale = int(25000000.0/((2**12)*self.frequency)) | ||
| 35 | self.mode1 = self.M1_RESTART | self.M1_AI | ||
| 36 | |||
| 37 | self.writeRegister(self.MODE1, self.M1_SLEEP) | ||
| 38 | self.writeRegister(self.PRESCALE, self.prescale) | ||
| 39 | time.sleep(0.01) | ||
| 40 | |||
| 41 | self.writeRegister(self.MODE1, self.mode1) | ||
| 42 | |||
| 43 | def __str__(self): | ||
| 44 | return "PCA9685(slave=0x%02X)" % self.slave | ||
| 45 | |||
| 46 | def getChannelAddress(self, channel): | ||
| 47 | return int(channel * 4 + self.PWM_BASE) | ||
| 48 | |||
| 49 | def __pwmRead__(self, channel): | ||
| 50 | addr = self.getChannelAddress(channel) | ||
| 51 | d = self.readRegisters(addr, 4) | ||
| 52 | start = d[1] << 8 | d[0] | ||
| 53 | end = d[3] << 8 | d[2] | ||
| 54 | return end-start | ||
| 55 | |||
| 56 | def __pwmWrite__(self, channel, value): | ||
| 57 | addr = self.getChannelAddress(channel) | ||
| 58 | d = bytearray(4) | ||
| 59 | d[0] = 0 | ||
| 60 | d[1] = 0 | ||
| 61 | d[2] = (value & 0x0FF) | ||
| 62 | d[3] = (value & 0xF00) >> 8 | ||
| 63 | self.writeRegisters(addr, d) | ||
diff --git a/python/webiopi/devices/bus.py b/python/webiopi/devices/bus.py new file mode 100644 index 0000000..cd271e0 --- /dev/null +++ b/python/webiopi/devices/bus.py | |||
| @@ -0,0 +1,117 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import os | ||
| 16 | import time | ||
| 17 | import subprocess | ||
| 18 | |||
| 19 | from webiopi.utils.logger import debug, info | ||
| 20 | |||
| 21 | BUSLIST = { | ||
| 22 | "I2C": {"enabled": False, "gpio": {0:"SDA", 1:"SCL", 2:"SDA", 3:"SCL"}, "modules": ["i2c-bcm2708", "i2c-dev"]}, | ||
| 23 | "SPI": {"enabled": False, "gpio": {7:"CE1", 8:"CE0", 9:"MISO", 10:"MOSI", 11:"SCLK"}, "modules": ["spi-bcm2708", "spidev"]}, | ||
| 24 | "UART": {"enabled": False, "gpio": {14:"TX", 15:"RX"}}, | ||
| 25 | "ONEWIRE": {"enabled": False, "gpio": {4:"DATA"}, "modules": ["w1-gpio"], "wait": 2} | ||
| 26 | } | ||
| 27 | |||
| 28 | def loadModule(module): | ||
| 29 | debug("Loading module : %s" % module) | ||
| 30 | subprocess.call(["modprobe", module]) | ||
| 31 | |||
| 32 | def unloadModule(module): | ||
| 33 | subprocess.call(["modprobe", "-r", module]) | ||
| 34 | |||
| 35 | def loadModules(bus): | ||
| 36 | if BUSLIST[bus]["enabled"] == False and not modulesLoaded(bus): | ||
| 37 | info("Loading %s modules" % bus) | ||
| 38 | for module in BUSLIST[bus]["modules"]: | ||
| 39 | loadModule(module) | ||
| 40 | if "wait" in BUSLIST[bus]: | ||
| 41 | info("Sleeping %ds to let %s modules load" % (BUSLIST[bus]["wait"], bus)) | ||
| 42 | time.sleep(BUSLIST[bus]["wait"]) | ||
| 43 | |||
| 44 | BUSLIST[bus]["enabled"] = True | ||
| 45 | |||
| 46 | def unloadModules(bus): | ||
| 47 | info("Unloading %s modules" % bus) | ||
| 48 | for module in BUSLIST[bus]["modules"]: | ||
| 49 | unloadModule(module) | ||
| 50 | BUSLIST[bus]["enabled"] = False | ||
| 51 | |||
| 52 | def __modulesLoaded__(modules, lines): | ||
| 53 | if len(modules) == 0: | ||
| 54 | return True | ||
| 55 | for line in lines: | ||
| 56 | if modules[0].replace("-", "_") == line.split(" ")[0]: | ||
| 57 | return __modulesLoaded__(modules[1:], lines) | ||
| 58 | return False | ||
| 59 | |||
| 60 | def modulesLoaded(bus): | ||
| 61 | if not bus in BUSLIST or not "modules" in BUSLIST[bus]: | ||
| 62 | return True | ||
| 63 | |||
| 64 | try: | ||
| 65 | with open("/proc/modules") as f: | ||
| 66 | c = f.read() | ||
| 67 | f.close() | ||
| 68 | lines = c.split("\n") | ||
| 69 | return __modulesLoaded__(BUSLIST[bus]["modules"], lines) | ||
| 70 | except: | ||
| 71 | return False | ||
| 72 | |||
| 73 | def checkAllBus(): | ||
| 74 | for bus in BUSLIST: | ||
| 75 | if modulesLoaded(bus): | ||
| 76 | BUSLIST[bus]["enabled"] = True | ||
| 77 | |||
| 78 | class Bus(): | ||
| 79 | def __init__(self, busName, device, flag=os.O_RDWR): | ||
| 80 | loadModules(busName) | ||
| 81 | self.busName = busName | ||
| 82 | self.device = device | ||
| 83 | self.flag = flag | ||
| 84 | self.fd = 0 | ||
| 85 | self.open() | ||
| 86 | |||
| 87 | def open(self): | ||
| 88 | self.fd = os.open(self.device, self.flag) | ||
| 89 | if self.fd < 0: | ||
| 90 | raise Exception("Cannot open %s" % self.device) | ||
| 91 | |||
| 92 | def close(self): | ||
| 93 | if self.fd > 0: | ||
| 94 | os.close(self.fd) | ||
| 95 | |||
| 96 | def read(self, size=1): | ||
| 97 | if self.fd > 0: | ||
| 98 | return os.read(self.fd, size) | ||
| 99 | raise Exception("Device %s not open" % self.device) | ||
| 100 | |||
| 101 | def readBytes(self, size=1): | ||
| 102 | return bytearray(self.read(size)) | ||
| 103 | |||
| 104 | def readByte(self): | ||
| 105 | return self.readBytes()[0] | ||
| 106 | |||
| 107 | def write(self, string): | ||
| 108 | if self.fd > 0: | ||
| 109 | return os.write(self.fd, string) | ||
| 110 | raise Exception("Device %s not open" % self.device) | ||
| 111 | |||
| 112 | def writeBytes(self, data): | ||
| 113 | return self.write(bytearray(data)) | ||
| 114 | |||
| 115 | def writeByte(self, value): | ||
| 116 | self.writeBytes([value]) | ||
| 117 | |||
diff --git a/python/webiopi/devices/digital/__init__.py b/python/webiopi/devices/digital/__init__.py new file mode 100644 index 0000000..2c992b3 --- /dev/null +++ b/python/webiopi/devices/digital/__init__.py | |||
| @@ -0,0 +1,144 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.decorators.rest import request, response | ||
| 16 | from webiopi.utils.types import M_JSON | ||
| 17 | |||
| 18 | class GPIOPort(): | ||
| 19 | IN = 0 | ||
| 20 | OUT = 1 | ||
| 21 | |||
| 22 | LOW = False | ||
| 23 | HIGH = True | ||
| 24 | |||
| 25 | def __init__(self, channelCount): | ||
| 26 | self.digitalChannelCount = channelCount | ||
| 27 | |||
| 28 | def checkDigitalChannel(self, channel): | ||
| 29 | if not 0 <= channel < self.digitalChannelCount: | ||
| 30 | raise ValueError("Channel %d out of range [%d..%d]" % (channel, 0, self.digitalChannelCount-1)) | ||
| 31 | |||
| 32 | def checkDigitalValue(self, value): | ||
| 33 | if not (value == 0 or value == 1): | ||
| 34 | raise ValueError("Value %d not in {0, 1}") | ||
| 35 | |||
| 36 | |||
| 37 | @request("GET", "count") | ||
| 38 | @response("%d") | ||
| 39 | def digitalCount(self): | ||
| 40 | return self.digitalChannelCount | ||
| 41 | |||
| 42 | def __family__(self): | ||
| 43 | return "GPIOPort" | ||
| 44 | |||
| 45 | def __getFunction__(self, channel): | ||
| 46 | raise NotImplementedError | ||
| 47 | |||
| 48 | def __setFunction__(self, channel, func): | ||
| 49 | raise NotImplementedError | ||
| 50 | |||
| 51 | def __digitalRead__(self, chanel): | ||
| 52 | raise NotImplementedError | ||
| 53 | |||
| 54 | def __portRead__(self): | ||
| 55 | raise NotImplementedError | ||
| 56 | |||
| 57 | def __digitalWrite__(self, chanel, value): | ||
| 58 | raise NotImplementedError | ||
| 59 | |||
| 60 | def __portWrite__(self, value): | ||
| 61 | raise NotImplementedError | ||
| 62 | |||
| 63 | def getFunction(self, channel): | ||
| 64 | self.checkDigitalChannel(channel) | ||
| 65 | return self.__getFunction__(channel) | ||
| 66 | |||
| 67 | @request("GET", "%(channel)d/function") | ||
| 68 | def getFunctionString(self, channel): | ||
| 69 | func = self.getFunction(channel) | ||
| 70 | if func == self.IN: | ||
| 71 | return "IN" | ||
| 72 | elif func == self.OUT: | ||
| 73 | return "OUT" | ||
| 74 | # elif func == GPIO.PWM: | ||
| 75 | # return "PWM" | ||
| 76 | else: | ||
| 77 | return "UNKNOWN" | ||
| 78 | |||
| 79 | def setFunction(self, channel, value): | ||
| 80 | self.checkDigitalChannel(channel) | ||
| 81 | self.__setFunction__(channel, value) | ||
| 82 | return self.getFunction(channel) | ||
| 83 | |||
| 84 | @request("POST", "%(channel)d/function/%(value)s") | ||
| 85 | def setFunctionString(self, channel, value): | ||
| 86 | value = value.lower() | ||
| 87 | if value == "in": | ||
| 88 | self.setFunction(channel, self.IN) | ||
| 89 | elif value == "out": | ||
| 90 | self.setFunction(channel, self.OUT) | ||
| 91 | # elif value == "pwm": | ||
| 92 | # self.setFunction(channel, GPIO.PWM) | ||
| 93 | else: | ||
| 94 | raise ValueError("Bad Function") | ||
| 95 | return self.getFunctionString(channel) | ||
| 96 | |||
| 97 | @request("GET", "%(channel)d/value") | ||
| 98 | @response("%d") | ||
| 99 | def digitalRead(self, channel): | ||
| 100 | self.checkDigitalChannel(channel) | ||
| 101 | return self.__digitalRead__(channel) | ||
| 102 | |||
| 103 | @request("GET", "*") | ||
| 104 | @response(contentType=M_JSON) | ||
| 105 | def wildcard(self, compact=False): | ||
| 106 | if compact: | ||
| 107 | f = "f" | ||
| 108 | v = "v" | ||
| 109 | else: | ||
| 110 | f = "function" | ||
| 111 | v = "value" | ||
| 112 | |||
| 113 | values = {} | ||
| 114 | for i in range(self.digitalChannelCount): | ||
| 115 | if compact: | ||
| 116 | func = self.getFunction(i) | ||
| 117 | else: | ||
| 118 | func = self.getFunctionString(i) | ||
| 119 | values[i] = {f: func, v: int(self.digitalRead(i))} | ||
| 120 | return values | ||
| 121 | |||
| 122 | @request("GET", "*/integer") | ||
| 123 | @response("%d") | ||
| 124 | def portRead(self): | ||
| 125 | return self.__portRead__() | ||
| 126 | |||
| 127 | @request("POST", "%(channel)d/value/%(value)d") | ||
| 128 | @response("%d") | ||
| 129 | def digitalWrite(self, channel, value): | ||
| 130 | self.checkDigitalChannel(channel) | ||
| 131 | self.checkDigitalValue(value) | ||
| 132 | self.__digitalWrite__(channel, value) | ||
| 133 | return self.digitalRead(channel) | ||
| 134 | |||
| 135 | @request("POST", "*/integer/%(value)d") | ||
| 136 | @response("%d") | ||
| 137 | def portWrite(self, value): | ||
| 138 | self.__portWrite__(value) | ||
| 139 | return self.portRead() | ||
| 140 | |||
| 141 | DRIVERS = {} | ||
| 142 | DRIVERS["mcp23XXX"] = ["MCP23008", "MCP23009", "MCP23017", "MCP23018", "MCP23S08", "MCP23S09", "MCP23S17", "MCP23S18"] | ||
| 143 | DRIVERS["pcf8574" ] = ["PCF8574", "PCF8574A"] | ||
| 144 | DRIVERS["ds2408" ] = ["DS2408"] | ||
diff --git a/python/webiopi/devices/digital/ds2408.py b/python/webiopi/devices/digital/ds2408.py new file mode 100644 index 0000000..4784593 --- /dev/null +++ b/python/webiopi/devices/digital/ds2408.py | |||
| @@ -0,0 +1,84 @@ | |||
| 1 | # Copyright 2013 Stuart Marsden | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.devices.onewire import OneWire | ||
| 16 | from webiopi.devices.digital import GPIOPort | ||
| 17 | |||
| 18 | class DS2408(OneWire, GPIOPort): | ||
| 19 | FUNCTIONS = [GPIOPort.IN for i in range(8)] | ||
| 20 | |||
| 21 | def __init__(self, slave=None): | ||
| 22 | OneWire.__init__(self, slave, 0x29, "2408") | ||
| 23 | GPIOPort.__init__(self, 8) | ||
| 24 | self.portWrite(0x00) | ||
| 25 | |||
| 26 | def __str__(self): | ||
| 27 | return "DS2408(slave=%s)" % self.slave | ||
| 28 | |||
| 29 | def __getFunction__(self, channel): | ||
| 30 | return self.FUNCTIONS[channel] | ||
| 31 | |||
| 32 | def __setFunction__(self, channel, value): | ||
| 33 | if not value in [self.IN, self.OUT]: | ||
| 34 | raise ValueError("Requested function not supported") | ||
| 35 | self.FUNCTIONS[channel] = value | ||
| 36 | if value == self.IN: | ||
| 37 | self.__output__(channel, 0) | ||
| 38 | |||
| 39 | def __digitalRead__(self, channel): | ||
| 40 | mask = 1 << channel | ||
| 41 | d = self.readState() | ||
| 42 | if d != None: | ||
| 43 | return (d & mask) == mask | ||
| 44 | |||
| 45 | |||
| 46 | def __digitalWrite__(self, channel, value): | ||
| 47 | mask = 1 << channel | ||
| 48 | b = self.readByte() | ||
| 49 | if value: | ||
| 50 | b |= mask | ||
| 51 | else: | ||
| 52 | b &= ~mask | ||
| 53 | self.writeByte(b) | ||
| 54 | |||
| 55 | def __portWrite__(self, value): | ||
| 56 | self.writeByte(value) | ||
| 57 | |||
| 58 | def __portRead__(self): | ||
| 59 | return self.readByte() | ||
| 60 | |||
| 61 | def readState(self): | ||
| 62 | try: | ||
| 63 | with open("/sys/bus/w1/devices/%s/state" % self.slave, "rb") as f: | ||
| 64 | data = f.read(1) | ||
| 65 | return ord(data) | ||
| 66 | except IOError: | ||
| 67 | return -1 | ||
| 68 | |||
| 69 | def readByte(self): | ||
| 70 | try: | ||
| 71 | with open("/sys/bus/w1/devices/%s/output" % self.slave, "rb") as f: | ||
| 72 | data = f.read(1) | ||
| 73 | return bytearray(data)[0] | ||
| 74 | except IOError: | ||
| 75 | return -1 | ||
| 76 | |||
| 77 | def writeByte(self, value): | ||
| 78 | try: | ||
| 79 | with open("/sys/bus/w1/devices/%s/output" % self.slave, "wb") as f: | ||
| 80 | f.write(bytearray([value])) | ||
| 81 | except IOError: | ||
| 82 | pass | ||
| 83 | |||
| 84 | |||
diff --git a/python/webiopi/devices/digital/gpio.py b/python/webiopi/devices/digital/gpio.py new file mode 100644 index 0000000..5da6cc5 --- /dev/null +++ b/python/webiopi/devices/digital/gpio.py | |||
| @@ -0,0 +1,189 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import M_JSON | ||
| 16 | from webiopi.utils.logger import debug | ||
| 17 | from webiopi.devices.digital import GPIOPort | ||
| 18 | from webiopi.decorators.rest import request, response | ||
| 19 | try: | ||
| 20 | import _webiopi.GPIO as GPIO | ||
| 21 | except: | ||
| 22 | pass | ||
| 23 | |||
| 24 | EXPORT = [] | ||
| 25 | |||
| 26 | class NativeGPIO(GPIOPort): | ||
| 27 | def __init__(self): | ||
| 28 | GPIOPort.__init__(self, 54) | ||
| 29 | self.export = range(54) | ||
| 30 | self.post_value = True | ||
| 31 | self.post_function = True | ||
| 32 | self.gpio_setup = [] | ||
| 33 | self.gpio_reset = [] | ||
| 34 | |||
| 35 | def __str__(self): | ||
| 36 | return "GPIO" | ||
| 37 | |||
| 38 | def addGPIO(self, lst, gpio, params): | ||
| 39 | gpio = int(gpio) | ||
| 40 | params = params.split(" ") | ||
| 41 | func = params[0].lower() | ||
| 42 | if func == "in": | ||
| 43 | func = GPIO.IN | ||
| 44 | elif func == "out": | ||
| 45 | func = GPIO.OUT | ||
| 46 | else: | ||
| 47 | raise Exception("Unknown function") | ||
| 48 | |||
| 49 | value = -1 | ||
| 50 | if len(params) > 1: | ||
| 51 | value = int(params[1]) | ||
| 52 | lst.append({"gpio": gpio, "func": func, "value": value}) | ||
| 53 | |||
| 54 | def addGPIOSetup(self, gpio, params): | ||
| 55 | self.addGPIO(self.gpio_setup, gpio, params) | ||
| 56 | |||
| 57 | def addGPIOReset(self, gpio, params): | ||
| 58 | self.addGPIO(self.gpio_reset, gpio, params) | ||
| 59 | |||
| 60 | def addSetups(self, gpios): | ||
| 61 | for (gpio, params) in gpios: | ||
| 62 | self.addGPIOSetup(gpio, params) | ||
| 63 | |||
| 64 | def addResets(self, gpios): | ||
| 65 | for (gpio, params) in gpios: | ||
| 66 | self.addGPIOReset(gpio, params) | ||
| 67 | |||
| 68 | def setup(self): | ||
| 69 | for g in self.gpio_setup: | ||
| 70 | gpio = g["gpio"] | ||
| 71 | debug("Setup GPIO %d" % gpio) | ||
| 72 | GPIO.setFunction(gpio, g["func"]) | ||
| 73 | if g["value"] >= 0 and GPIO.getFunction(gpio) == GPIO.OUT: | ||
| 74 | GPIO.digitalWrite(gpio, g["value"]) | ||
| 75 | |||
| 76 | def close(self): | ||
| 77 | for g in self.gpio_reset: | ||
| 78 | gpio = g["gpio"] | ||
| 79 | debug("Reset GPIO %d" % gpio) | ||
| 80 | GPIO.setFunction(gpio, g["func"]) | ||
| 81 | if g["value"] >= 0 and GPIO.getFunction(gpio) == GPIO.OUT: | ||
| 82 | GPIO.digitalWrite(gpio, g["value"]) | ||
| 83 | |||
| 84 | def checkDigitalChannelExported(self, channel): | ||
| 85 | if not channel in self.export: | ||
| 86 | raise GPIO.InvalidChannelException("Channel %d is not allowed" % channel) | ||
| 87 | |||
| 88 | def checkPostingFunctionAllowed(self): | ||
| 89 | if not self.post_function: | ||
| 90 | raise ValueError("POSTing function to native GPIO not allowed") | ||
| 91 | |||
| 92 | def checkPostingValueAllowed(self): | ||
| 93 | if not self.post_value: | ||
| 94 | raise ValueError("POSTing value to native GPIO not allowed") | ||
| 95 | |||
| 96 | def __digitalRead__(self, channel): | ||
| 97 | self.checkDigitalChannelExported(channel) | ||
| 98 | return GPIO.digitalRead(channel) | ||
| 99 | |||
| 100 | def __digitalWrite__(self, channel, value): | ||
| 101 | self.checkDigitalChannelExported(channel) | ||
| 102 | self.checkPostingValueAllowed() | ||
| 103 | GPIO.digitalWrite(channel, value) | ||
| 104 | |||
| 105 | def __getFunction__(self, channel): | ||
| 106 | self.checkDigitalChannelExported(channel) | ||
| 107 | return GPIO.getFunction(channel) | ||
| 108 | |||
| 109 | def __setFunction__(self, channel, value): | ||
| 110 | self.checkDigitalChannelExported(channel) | ||
| 111 | self.checkPostingFunctionAllowed() | ||
| 112 | GPIO.setFunction(channel, value) | ||
| 113 | |||
| 114 | def __portRead__(self): | ||
| 115 | value = 0 | ||
| 116 | for i in self.export: | ||
| 117 | value |= GPIO.digitalRead(i) << i | ||
| 118 | return value | ||
| 119 | |||
| 120 | def __portWrite__(self, value): | ||
| 121 | if len(self.export) < 54: | ||
| 122 | for i in self.export: | ||
| 123 | if GPIO.getFunction(i) == GPIO.OUT: | ||
| 124 | GPIO.digitalWrite(i, (value >> i) & 1) | ||
| 125 | else: | ||
| 126 | raise Exception("Please limit exported GPIO to write integers") | ||
| 127 | |||
| 128 | @request("GET", "*") | ||
| 129 | @response(contentType=M_JSON) | ||
| 130 | def wildcard(self, compact=False): | ||
| 131 | if compact: | ||
| 132 | f = "f" | ||
| 133 | v = "v" | ||
| 134 | else: | ||
| 135 | f = "function" | ||
| 136 | v = "value" | ||
| 137 | |||
| 138 | values = {} | ||
| 139 | print(self.export) | ||
| 140 | for i in self.export: | ||
| 141 | if compact: | ||
| 142 | func = GPIO.getFunction(i) | ||
| 143 | else: | ||
| 144 | func = GPIO.getFunctionString(i) | ||
| 145 | values[i] = {f: func, v: int(GPIO.digitalRead(i))} | ||
| 146 | return values | ||
| 147 | |||
| 148 | |||
| 149 | @request("GET", "%(channel)d/pulse", "%s") | ||
| 150 | def getPulse(self, channel): | ||
| 151 | self.checkDigitalChannelExported(channel) | ||
| 152 | self.checkDigitalChannel(channel) | ||
| 153 | return GPIO.getPulse(channel) | ||
| 154 | |||
| 155 | @request("POST", "%(channel)d/sequence/%(args)s") | ||
| 156 | @response("%d") | ||
| 157 | def outputSequence(self, channel, args): | ||
| 158 | self.checkDigitalChannelExported(channel) | ||
| 159 | self.checkPostingValueAllowed() | ||
| 160 | self.checkDigitalChannel(channel) | ||
| 161 | (period, sequence) = args.split(",") | ||
| 162 | period = int(period) | ||
| 163 | GPIO.outputSequence(channel, period, sequence) | ||
| 164 | return int(sequence[-1]) | ||
| 165 | |||
| 166 | @request("POST", "%(channel)d/pulse/") | ||
| 167 | def pulse(self, channel): | ||
| 168 | self.checkDigitalChannelExported(channel) | ||
| 169 | self.checkPostingValueAllowed() | ||
| 170 | self.checkDigitalChannel(channel) | ||
| 171 | GPIO.pulse(channel) | ||
| 172 | return "OK" | ||
| 173 | |||
| 174 | @request("POST", "%(channel)d/pulseRatio/%(value)f") | ||
| 175 | def pulseRatio(self, channel, value): | ||
| 176 | self.checkDigitalChannelExported(channel) | ||
| 177 | self.checkPostingValueAllowed() | ||
| 178 | self.checkDigitalChannel(channel) | ||
| 179 | GPIO.pulseRatio(channel, value) | ||
| 180 | return GPIO.getPulse(channel) | ||
| 181 | |||
| 182 | @request("POST", "%(channel)d/pulseAngle/%(value)f") | ||
| 183 | def pulseAngle(self, channel, value): | ||
| 184 | self.checkDigitalChannelExported(channel) | ||
| 185 | self.checkPostingValueAllowed() | ||
| 186 | self.checkDigitalChannel(channel) | ||
| 187 | GPIO.pulseAngle(channel, value) | ||
| 188 | return GPIO.getPulse(channel) | ||
| 189 | |||
diff --git a/python/webiopi/devices/digital/mcp23XXX.py b/python/webiopi/devices/digital/mcp23XXX.py new file mode 100644 index 0000000..99edd61 --- /dev/null +++ b/python/webiopi/devices/digital/mcp23XXX.py | |||
| @@ -0,0 +1,153 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint | ||
| 16 | from webiopi.devices.i2c import I2C | ||
| 17 | from webiopi.devices.spi import SPI | ||
| 18 | from webiopi.devices.digital import GPIOPort | ||
| 19 | |||
| 20 | class MCP23XXX(GPIOPort): | ||
| 21 | IODIR = 0x00 | ||
| 22 | IPOL = 0x01 | ||
| 23 | GPINTEN = 0x02 | ||
| 24 | DEFVAL = 0x03 | ||
| 25 | INTCON = 0x04 | ||
| 26 | IOCON = 0x05 | ||
| 27 | GPPU = 0x06 | ||
| 28 | INTF = 0x07 | ||
| 29 | INTCAP = 0x08 | ||
| 30 | GPIO = 0x09 | ||
| 31 | OLAT = 0x0A | ||
| 32 | |||
| 33 | def __init__(self, channelCount): | ||
| 34 | GPIOPort.__init__(self, channelCount) | ||
| 35 | self.banks = int(channelCount / 8) | ||
| 36 | |||
| 37 | def getAddress(self, register, channel=0): | ||
| 38 | return register * self.banks + int(channel / 8) | ||
| 39 | |||
| 40 | def getChannel(self, register, channel): | ||
| 41 | self.checkDigitalChannel(channel) | ||
| 42 | addr = self.getAddress(register, channel) | ||
| 43 | mask = 1 << (channel % 8) | ||
| 44 | return (addr, mask) | ||
| 45 | |||
| 46 | def __digitalRead__(self, channel): | ||
| 47 | (addr, mask) = self.getChannel(self.GPIO, channel) | ||
| 48 | d = self.readRegister(addr) | ||
| 49 | return (d & mask) == mask | ||
| 50 | |||
| 51 | def __digitalWrite__(self, channel, value): | ||
| 52 | (addr, mask) = self.getChannel(self.GPIO, channel) | ||
| 53 | d = self.readRegister(addr) | ||
| 54 | if value: | ||
| 55 | d |= mask | ||
| 56 | else: | ||
| 57 | d &= ~mask | ||
| 58 | self.writeRegister(addr, d) | ||
| 59 | |||
| 60 | def __getFunction__(self, channel): | ||
| 61 | (addr, mask) = self.getChannel(self.IODIR, channel) | ||
| 62 | d = self.readRegister(addr) | ||
| 63 | return self.IN if (d & mask) == mask else self.OUT | ||
| 64 | |||
| 65 | def __setFunction__(self, channel, value): | ||
| 66 | if not value in [self.IN, self.OUT]: | ||
| 67 | raise ValueError("Requested function not supported") | ||
| 68 | |||
| 69 | (addr, mask) = self.getChannel(self.IODIR, channel) | ||
| 70 | d = self.readRegister(addr) | ||
| 71 | if value == self.IN: | ||
| 72 | d |= mask | ||
| 73 | else: | ||
| 74 | d &= ~mask | ||
| 75 | self.writeRegister(addr, d) | ||
| 76 | |||
| 77 | def __portRead__(self): | ||
| 78 | value = 0 | ||
| 79 | for i in range(self.banks): | ||
| 80 | value |= self.readRegister(self.banks*self.GPIO+i) << 8*i | ||
| 81 | return value | ||
| 82 | |||
| 83 | def __portWrite__(self, value): | ||
| 84 | for i in range(self.banks): | ||
| 85 | self.writeRegister(self.banks*self.GPIO+i, (value >> 8*i) & 0xFF) | ||
| 86 | |||
| 87 | class MCP230XX(MCP23XXX, I2C): | ||
| 88 | def __init__(self, slave, channelCount, name): | ||
| 89 | I2C.__init__(self, toint(slave)) | ||
| 90 | MCP23XXX.__init__(self, channelCount) | ||
| 91 | self.name = name | ||
| 92 | |||
| 93 | def __str__(self): | ||
| 94 | return "%s(slave=0x%02X)" % (self.name, self.slave) | ||
| 95 | |||
| 96 | class MCP23008(MCP230XX): | ||
| 97 | def __init__(self, slave=0x20): | ||
| 98 | MCP230XX.__init__(self, slave, 8, "MCP23008") | ||
| 99 | |||
| 100 | class MCP23009(MCP230XX): | ||
| 101 | def __init__(self, slave=0x20): | ||
| 102 | MCP230XX.__init__(self, slave, 8, "MCP23009") | ||
| 103 | |||
| 104 | class MCP23017(MCP230XX): | ||
| 105 | def __init__(self, slave=0x20): | ||
| 106 | MCP230XX.__init__(self, slave, 16, "MCP23017") | ||
| 107 | |||
| 108 | class MCP23018(MCP230XX): | ||
| 109 | def __init__(self, slave=0x20): | ||
| 110 | MCP230XX.__init__(self, slave, 16, "MCP23018") | ||
| 111 | |||
| 112 | class MCP23SXX(MCP23XXX, SPI): | ||
| 113 | SLAVE = 0x20 | ||
| 114 | |||
| 115 | WRITE = 0x00 | ||
| 116 | READ = 0x01 | ||
| 117 | |||
| 118 | def __init__(self, chip, slave, channelCount, name): | ||
| 119 | SPI.__init__(self, toint(chip), 0, 8, 10000000) | ||
| 120 | MCP23XXX.__init__(self, channelCount) | ||
| 121 | self.slave = self.SLAVE | ||
| 122 | iocon_value = 0x08 # Hardware Address Enable | ||
| 123 | iocon_addr = self.getAddress(self.IOCON) | ||
| 124 | self.writeRegister(iocon_addr, iocon_value) | ||
| 125 | self.slave = toint(slave) | ||
| 126 | self.name = name | ||
| 127 | |||
| 128 | def __str__(self): | ||
| 129 | return "%s(chip=%d, slave=0x%02X)" % (self.name, self.chip, self.slave) | ||
| 130 | |||
| 131 | def readRegister(self, addr): | ||
| 132 | d = self.xfer([(self.slave << 1) | self.READ, addr, 0x00]) | ||
| 133 | return d[2] | ||
| 134 | |||
| 135 | def writeRegister(self, addr, value): | ||
| 136 | self.writeBytes([(self.slave << 1) | self.WRITE, addr, value]) | ||
| 137 | |||
| 138 | class MCP23S08(MCP23SXX): | ||
| 139 | def __init__(self, chip=0, slave=0x20): | ||
| 140 | MCP23SXX.__init__(self, chip, slave, 8, "MCP23S08") | ||
| 141 | |||
| 142 | class MCP23S09(MCP23SXX): | ||
| 143 | def __init__(self, chip=0, slave=0x20): | ||
| 144 | MCP23SXX.__init__(self, chip, slave, 8, "MCP23S09") | ||
| 145 | |||
| 146 | class MCP23S17(MCP23SXX): | ||
| 147 | def __init__(self, chip=0, slave=0x20): | ||
| 148 | MCP23SXX.__init__(self, chip, slave, 16, "MCP23S17") | ||
| 149 | |||
| 150 | class MCP23S18(MCP23SXX): | ||
| 151 | def __init__(self, chip=0, slave=0x20): | ||
| 152 | MCP23SXX.__init__(self, chip, slave, 16, "MCP23S18") | ||
| 153 | |||
diff --git a/python/webiopi/devices/digital/pcf8574.py b/python/webiopi/devices/digital/pcf8574.py new file mode 100644 index 0000000..146e8f4 --- /dev/null +++ b/python/webiopi/devices/digital/pcf8574.py | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint | ||
| 16 | from webiopi.devices.i2c import I2C | ||
| 17 | from webiopi.devices.digital import GPIOPort | ||
| 18 | |||
| 19 | class PCF8574(I2C, GPIOPort): | ||
| 20 | FUNCTIONS = [GPIOPort.IN for i in range(8)] | ||
| 21 | |||
| 22 | def __init__(self, slave=0x20): | ||
| 23 | slave = toint(slave) | ||
| 24 | if slave in range(0x20, 0x28): | ||
| 25 | self.name = "PCF8574" | ||
| 26 | elif slave in range(0x38, 0x40): | ||
| 27 | self.name = "PCF8574A" | ||
| 28 | else: | ||
| 29 | raise ValueError("Bad slave address for PCF8574(A) : 0x%02X not in range [0x20..0x27, 0x38..0x3F]" % slave) | ||
| 30 | |||
| 31 | I2C.__init__(self, slave) | ||
| 32 | GPIOPort.__init__(self, 8) | ||
| 33 | self.portWrite(0xFF) | ||
| 34 | self.portRead() | ||
| 35 | |||
| 36 | def __str__(self): | ||
| 37 | return "%s(slave=0x%02X)" % (self.name, self.slave) | ||
| 38 | |||
| 39 | def __getFunction__(self, channel): | ||
| 40 | return self.FUNCTIONS[channel] | ||
| 41 | |||
| 42 | def __setFunction__(self, channel, value): | ||
| 43 | if not value in [self.IN, self.OUT]: | ||
| 44 | raise ValueError("Requested function not supported") | ||
| 45 | self.FUNCTIONS[channel] = value | ||
| 46 | |||
| 47 | def __digitalRead__(self, channel): | ||
| 48 | mask = 1 << channel | ||
| 49 | d = self.readByte() | ||
| 50 | return (d & mask) == mask | ||
| 51 | |||
| 52 | def __portRead__(self): | ||
| 53 | return self.readByte() | ||
| 54 | |||
| 55 | def __digitalWrite__(self, channel, value): | ||
| 56 | mask = 1 << channel | ||
| 57 | b = self.readByte() | ||
| 58 | if value: | ||
| 59 | b |= mask | ||
| 60 | else: | ||
| 61 | b &= ~mask | ||
| 62 | self.writeByte(b) | ||
| 63 | |||
| 64 | def __portWrite__(self, value): | ||
| 65 | self.writeByte(value) | ||
| 66 | |||
| 67 | class PCF8574A(PCF8574): | ||
| 68 | def __init__(self, slave=0x38): | ||
| 69 | PCF8574.__init__(self, slave) | ||
| 70 | |||
diff --git a/python/webiopi/devices/i2c.py b/python/webiopi/devices/i2c.py new file mode 100644 index 0000000..1b3196a --- /dev/null +++ b/python/webiopi/devices/i2c.py | |||
| @@ -0,0 +1,75 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import fcntl | ||
| 16 | |||
| 17 | from webiopi.utils.version import BOARD_REVISION | ||
| 18 | from webiopi.devices.bus import Bus | ||
| 19 | |||
| 20 | # /dev/i2c-X ioctl commands. The ioctl's parameter is always an | ||
| 21 | # unsigned long, except for: | ||
| 22 | # - I2C_FUNCS, takes pointer to an unsigned long | ||
| 23 | # - I2C_RDWR, takes pointer to struct i2c_rdwr_ioctl_data | ||
| 24 | # - I2C_SMBUS, takes pointer to struct i2c_smbus_ioctl_data | ||
| 25 | |||
| 26 | I2C_RETRIES = 0x0701 # number of times a device address should | ||
| 27 | # be polled when not acknowledging | ||
| 28 | I2C_TIMEOUT = 0x0702 # set timeout in units of 10 ms | ||
| 29 | |||
| 30 | # NOTE: Slave address is 7 or 10 bits, but 10-bit addresses | ||
| 31 | # are NOT supported! (due to code brokenness) | ||
| 32 | |||
| 33 | I2C_SLAVE = 0x0703 # Use this slave address | ||
| 34 | I2C_SLAVE_FORCE = 0x0706 # Use this slave address, even if it | ||
| 35 | # is already in use by a driver! | ||
| 36 | I2C_TENBIT = 0x0704 # 0 for 7 bit addrs, != 0 for 10 bit | ||
| 37 | |||
| 38 | I2C_FUNCS = 0x0705 # Get the adapter functionality mask | ||
| 39 | |||
| 40 | I2C_RDWR = 0x0707 # Combined R/W transfer (one STOP only) | ||
| 41 | |||
| 42 | I2C_PEC = 0x0708 # != 0 to use PEC with SMBus | ||
| 43 | I2C_SMBUS = 0x0720 # SMBus transfer */ | ||
| 44 | |||
| 45 | |||
| 46 | class I2C(Bus): | ||
| 47 | def __init__(self, slave): | ||
| 48 | self.channel = 0 | ||
| 49 | if BOARD_REVISION > 1: | ||
| 50 | self.channel = 1 | ||
| 51 | |||
| 52 | Bus.__init__(self, "I2C", "/dev/i2c-%d" % self.channel) | ||
| 53 | self.slave = slave | ||
| 54 | if fcntl.ioctl(self.fd, I2C_SLAVE, self.slave): | ||
| 55 | raise Exception("Error binding I2C slave 0x%02X" % self.slave) | ||
| 56 | |||
| 57 | def __str__(self): | ||
| 58 | return "I2C(slave=0x%02X)" % self.slave | ||
| 59 | |||
| 60 | def readRegister(self, addr): | ||
| 61 | self.writeByte(addr) | ||
| 62 | return self.readByte() | ||
| 63 | |||
| 64 | def readRegisters(self, addr, count): | ||
| 65 | self.writeByte(addr) | ||
| 66 | return self.readBytes(count) | ||
| 67 | |||
| 68 | def writeRegister(self, addr, byte): | ||
| 69 | self.writeBytes([addr, byte]) | ||
| 70 | |||
| 71 | def writeRegisters(self, addr, buff): | ||
| 72 | d = bytearray(len(buff)+1) | ||
| 73 | d[0] = addr | ||
| 74 | d[1:] = buff | ||
| 75 | self.writeBytes(d) | ||
diff --git a/python/webiopi/devices/instance.py b/python/webiopi/devices/instance.py new file mode 100644 index 0000000..633a8c3 --- /dev/null +++ b/python/webiopi/devices/instance.py | |||
| @@ -0,0 +1,6 @@ | |||
| 1 | DEVICES = {} | ||
| 2 | def deviceInstance(name): | ||
| 3 | if name in DEVICES: | ||
| 4 | return DEVICES[name]["device"] | ||
| 5 | else: | ||
| 6 | return None | ||
diff --git a/python/webiopi/devices/manager.py b/python/webiopi/devices/manager.py new file mode 100644 index 0000000..0f1e521 --- /dev/null +++ b/python/webiopi/devices/manager.py | |||
| @@ -0,0 +1,77 @@ | |||
| 1 | import imp | ||
| 2 | from webiopi.utils import logger | ||
| 3 | from webiopi.utils import types | ||
| 4 | from webiopi.devices.instance import DEVICES | ||
| 5 | |||
| 6 | from webiopi.devices import serial, digital, analog, sensor, shield | ||
| 7 | |||
| 8 | PACKAGES = [serial, digital, analog, sensor, shield] | ||
| 9 | def findDeviceClass(name): | ||
| 10 | for package in PACKAGES: | ||
| 11 | if hasattr(package, name): | ||
| 12 | return getattr(package, name) | ||
| 13 | if hasattr(package, "DRIVERS"): | ||
| 14 | for driver in package.DRIVERS: | ||
| 15 | if name in package.DRIVERS[driver]: | ||
| 16 | (fp, pathname, stuff) = imp.find_module(package.__name__.replace(".", "/") + "/" + driver) | ||
| 17 | module = imp.load_module(driver, fp, pathname, stuff) | ||
| 18 | return getattr(module, name) | ||
| 19 | return None | ||
| 20 | |||
| 21 | def addDevice(name, device, args): | ||
| 22 | devClass = findDeviceClass(device) | ||
| 23 | if devClass == None: | ||
| 24 | raise Exception("Device driver not found for %s" % device) | ||
| 25 | if len(args) > 0: | ||
| 26 | dev = devClass(**args) | ||
| 27 | else: | ||
| 28 | dev = devClass() | ||
| 29 | addDeviceInstance(name, dev, args) | ||
| 30 | |||
| 31 | def addDeviceInstance(name, dev, args): | ||
| 32 | funcs = {"GET": {}, "POST": {}} | ||
| 33 | for att in dir(dev): | ||
| 34 | func = getattr(dev, att) | ||
| 35 | if callable(func) and hasattr(func, "routed"): | ||
| 36 | if name == "GPIO": | ||
| 37 | logger.debug("Mapping %s.%s to REST %s /GPIO/%s" % (dev, att, func.method, func.path)) | ||
| 38 | else: | ||
| 39 | logger.debug("Mapping %s.%s to REST %s /devices/%s/%s" % (dev, att, func.method, name, func.path)) | ||
| 40 | funcs[func.method][func.path] = func | ||
| 41 | |||
| 42 | DEVICES[name] = {'device': dev, 'functions': funcs} | ||
| 43 | if name == "GPIO": | ||
| 44 | logger.info("GPIO - Native mapped to REST API /GPIO") | ||
| 45 | else: | ||
| 46 | logger.info("%s - %s mapped to REST API /devices/%s" % (dev.__family__(), dev, name)) | ||
| 47 | |||
| 48 | def closeDevices(): | ||
| 49 | devices = [k for k in DEVICES.keys()] | ||
| 50 | for name in devices: | ||
| 51 | device = DEVICES[name]["device"] | ||
| 52 | logger.debug("Closing device %s - %s" % (name, device)) | ||
| 53 | del DEVICES[name] | ||
| 54 | device.close() | ||
| 55 | |||
| 56 | def getDevicesJSON(compact=False): | ||
| 57 | devname = "name" | ||
| 58 | devtype = "type" | ||
| 59 | |||
| 60 | devices = [] | ||
| 61 | for devName in DEVICES: | ||
| 62 | if devName == "GPIO": | ||
| 63 | continue | ||
| 64 | instance = DEVICES[devName]["device"] | ||
| 65 | if hasattr(instance, "__family__"): | ||
| 66 | family = instance.__family__() | ||
| 67 | if isinstance(family, str): | ||
| 68 | devices.append({devname: devName, devtype:family}) | ||
| 69 | else: | ||
| 70 | for fam in family: | ||
| 71 | devices.append({devname: devName, devtype:fam}) | ||
| 72 | |||
| 73 | else: | ||
| 74 | devices.append({devname: devName, type:instance.__str__()}) | ||
| 75 | |||
| 76 | return types.jsonDumps(sorted(devices, key=lambda dev: dev[devname])) | ||
| 77 | |||
diff --git a/python/webiopi/devices/onewire.py b/python/webiopi/devices/onewire.py new file mode 100644 index 0000000..b012c90 --- /dev/null +++ b/python/webiopi/devices/onewire.py | |||
| @@ -0,0 +1,74 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import os | ||
| 16 | from webiopi.devices.bus import Bus, loadModule | ||
| 17 | |||
| 18 | EXTRAS = { | ||
| 19 | "TEMP": {"loaded": False, "module": "w1-therm"}, | ||
| 20 | "2408": {"loaded": False, "module": "w1_ds2408"} | ||
| 21 | |||
| 22 | } | ||
| 23 | |||
| 24 | def loadExtraModule(name): | ||
| 25 | if EXTRAS[name]["loaded"] == False: | ||
| 26 | loadModule(EXTRAS[name]["module"]) | ||
| 27 | EXTRAS[name]["loaded"] = True | ||
| 28 | |||
| 29 | class OneWire(Bus): | ||
| 30 | def __init__(self, slave=None, family=0, extra=None): | ||
| 31 | Bus.__init__(self, "ONEWIRE", "/sys/bus/w1/devices/w1_bus_master1/w1_master_slaves", os.O_RDONLY) | ||
| 32 | if self.fd > 0: | ||
| 33 | os.close(self.fd) | ||
| 34 | self.fd = 0 | ||
| 35 | |||
| 36 | self.family = family | ||
| 37 | if slave != None: | ||
| 38 | addr = slave.split("-") | ||
| 39 | if len(addr) == 1: | ||
| 40 | self.slave = "%02x-%s" % (family, slave) | ||
| 41 | elif len(addr) == 2: | ||
| 42 | prefix = int(addr[0], 16) | ||
| 43 | if family > 0 and family != prefix: | ||
| 44 | raise Exception("1-Wire slave address %s does not match family %02x" % (slave, family)) | ||
| 45 | self.slave = slave | ||
| 46 | else: | ||
| 47 | devices = self.deviceList() | ||
| 48 | if len(devices) == 0: | ||
| 49 | raise Exception("No device match family %02x" % family) | ||
| 50 | self.slave = devices[0] | ||
| 51 | |||
| 52 | loadExtraModule(extra) | ||
| 53 | |||
| 54 | def __str__(self): | ||
| 55 | return "1-Wire(slave=%s)" % self.slave | ||
| 56 | |||
| 57 | def deviceList(self): | ||
| 58 | devices = [] | ||
| 59 | with open(self.device) as f: | ||
| 60 | lines = f.read().split("\n") | ||
| 61 | if self.family > 0: | ||
| 62 | prefix = "%02x-" % self.family | ||
| 63 | for line in lines: | ||
| 64 | if line.startswith(prefix): | ||
| 65 | devices.append(line) | ||
| 66 | else: | ||
| 67 | devices = lines | ||
| 68 | return devices; | ||
| 69 | |||
| 70 | def read(self): | ||
| 71 | with open("/sys/bus/w1/devices/%s/w1_slave" % self.slave) as f: | ||
| 72 | data = f.read() | ||
| 73 | return data | ||
| 74 | |||
diff --git a/python/webiopi/devices/sensor/__init__.py b/python/webiopi/devices/sensor/__init__.py new file mode 100644 index 0000000..598a82f --- /dev/null +++ b/python/webiopi/devices/sensor/__init__.py | |||
| @@ -0,0 +1,177 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint | ||
| 16 | from webiopi.utils.types import M_JSON | ||
| 17 | from webiopi.devices.instance import deviceInstance | ||
| 18 | from webiopi.decorators.rest import request, response | ||
| 19 | |||
| 20 | class Pressure(): | ||
| 21 | def __init__(self, altitude=0, external=None): | ||
| 22 | self.altitude = toint(altitude) | ||
| 23 | if isinstance(external, str): | ||
| 24 | self.external = deviceInstance(external) | ||
| 25 | else: | ||
| 26 | self.external = external | ||
| 27 | |||
| 28 | if self.external != None and not isinstance(self.external, Temperature): | ||
| 29 | raise Exception("external must be a Temperature sensor") | ||
| 30 | |||
| 31 | def __family__(self): | ||
| 32 | return "Pressure" | ||
| 33 | |||
| 34 | def __getPascal__(self): | ||
| 35 | raise NotImplementedError | ||
| 36 | |||
| 37 | def __getPascalAtSea__(self): | ||
| 38 | raise NotImplementedError | ||
| 39 | |||
| 40 | @request("GET", "sensor/pressure/pa") | ||
| 41 | @response("%d") | ||
| 42 | def getPascal(self): | ||
| 43 | return self.__getPascal__() | ||
| 44 | |||
| 45 | @request("GET", "sensor/pressure/hpa") | ||
| 46 | @response("%.2f") | ||
| 47 | def getHectoPascal(self): | ||
| 48 | return float(self.__getPascal__()) / 100.0 | ||
| 49 | |||
| 50 | @request("GET", "sensor/pressure/sea/pa") | ||
| 51 | @response("%d") | ||
| 52 | def getPascalAtSea(self): | ||
| 53 | pressure = self.__getPascal__() | ||
| 54 | if self.external != None: | ||
| 55 | k = self.external.getKelvin() | ||
| 56 | if k != 0: | ||
| 57 | return float(pressure) / (1.0 / (1.0 + 0.0065 / k * self.altitude)**5.255) | ||
| 58 | return float(pressure) / (1.0 - self.altitude / 44330.0)**5.255 | ||
| 59 | |||
| 60 | @request("GET", "sensor/pressure/sea/hpa") | ||
| 61 | @response("%.2f") | ||
| 62 | def getHectoPascalAtSea(self): | ||
| 63 | return self.getPascalAtSea() / 100.0 | ||
| 64 | |||
| 65 | class Temperature(): | ||
| 66 | def __family__(self): | ||
| 67 | return "Temperature" | ||
| 68 | |||
| 69 | def __getKelvin__(self): | ||
| 70 | raise NotImplementedError | ||
| 71 | |||
| 72 | def __getCelsius__(self): | ||
| 73 | raise NotImplementedError | ||
| 74 | |||
| 75 | def __getFahrenheit__(self): | ||
| 76 | raise NotImplementedError | ||
| 77 | |||
| 78 | def Kelvin2Celsius(self, value=None): | ||
| 79 | if value == None: | ||
| 80 | value = self.getKelvin() | ||
| 81 | return value - 273.15 | ||
| 82 | |||
| 83 | def Kelvin2Fahrenheit(self, value=None): | ||
| 84 | if value == None: | ||
| 85 | value = self.getKelvin() | ||
| 86 | return value * 1.8 - 459.67 | ||
| 87 | |||
| 88 | def Celsius2Kelvin(self, value=None): | ||
| 89 | if value == None: | ||
| 90 | value = self.getCelsius() | ||
| 91 | return value + 273.15 | ||
| 92 | |||
| 93 | def Celsius2Fahrenheit(self, value=None): | ||
| 94 | if value == None: | ||
| 95 | value = self.getCelsius() | ||
| 96 | return value * 1.8 + 32 | ||
| 97 | |||
| 98 | def Fahrenheit2Kelvin(self, value=None): | ||
| 99 | if value == None: | ||
| 100 | value = self.getFahrenheit() | ||
| 101 | return (value - 459.67) / 1.8 | ||
| 102 | |||
| 103 | def Fahrenheit2Celsius(self, value=None): | ||
| 104 | if value == None: | ||
| 105 | value = self.getFahrenheit() | ||
| 106 | return (value - 32) / 1.8 | ||
| 107 | |||
| 108 | @request("GET", "sensor/temperature/k") | ||
| 109 | @response("%.02f") | ||
| 110 | def getKelvin(self): | ||
| 111 | return self.__getKelvin__() | ||
| 112 | |||
| 113 | @request("GET", "sensor/temperature/c") | ||
| 114 | @response("%.02f") | ||
| 115 | def getCelsius(self): | ||
| 116 | return self.__getCelsius__() | ||
| 117 | |||
| 118 | @request("GET", "sensor/temperature/f") | ||
| 119 | @response("%.02f") | ||
| 120 | def getFahrenheit(self): | ||
| 121 | return self.__getFahrenheit__() | ||
| 122 | |||
| 123 | class Luminosity(): | ||
| 124 | def __family__(self): | ||
| 125 | return "Luminosity" | ||
| 126 | |||
| 127 | def __getLux__(self): | ||
| 128 | raise NotImplementedError | ||
| 129 | |||
| 130 | @request("GET", "sensor/luminosity/lx") | ||
| 131 | @response("%.02f") | ||
| 132 | def getLux(self): | ||
| 133 | return self.__getLux__() | ||
| 134 | |||
| 135 | class Distance(): | ||
| 136 | def __family__(self): | ||
| 137 | return "Distance" | ||
| 138 | |||
| 139 | def __getMillimeter__(self): | ||
| 140 | raise NotImplementedError | ||
| 141 | |||
| 142 | @request("GET", "sensor/distance/mm") | ||
| 143 | @response("%.02f") | ||
| 144 | def getMillimeter(self): | ||
| 145 | return self.__getMillimeter__() | ||
| 146 | |||
| 147 | @request("GET", "sensor/distance/cm") | ||
| 148 | @response("%.02f") | ||
| 149 | def getCentimeter(self): | ||
| 150 | return self.getMillimeter() / 10 | ||
| 151 | |||
| 152 | @request("GET", "sensor/distance/m") | ||
| 153 | @response("%.02f") | ||
| 154 | def getMeter(self): | ||
| 155 | return self.getMillimeter() / 1000 | ||
| 156 | |||
| 157 | @request("GET", "sensor/distance/in") | ||
| 158 | @response("%.02f") | ||
| 159 | def getInch(self): | ||
| 160 | return self.getMillimeter() / 0.254 | ||
| 161 | |||
| 162 | @request("GET", "sensor/distance/ft") | ||
| 163 | @response("%.02f") | ||
| 164 | def getFoot(self): | ||
| 165 | return self.getInch() / 12 | ||
| 166 | |||
| 167 | @request("GET", "sensor/distance/yd") | ||
| 168 | @response("%.02f") | ||
| 169 | def getYard(self): | ||
| 170 | return self.getInch() / 36 | ||
| 171 | |||
| 172 | DRIVERS = {} | ||
| 173 | DRIVERS["bmp085"] = ["BMP085"] | ||
| 174 | DRIVERS["onewiretemp"] = ["DS1822", "DS1825", "DS18B20", "DS18S20", "DS28EA00"] | ||
| 175 | DRIVERS["tmpXXX"] = ["TMP75", "TMP102", "TMP275"] | ||
| 176 | DRIVERS["tslXXXX"] = ["TSL2561", "TSL2561CS", "TSL2561T", "TSL4531", "TSL45311", "TSL45313", "TSL45315", "TSL45317"] | ||
| 177 | DRIVERS["vcnl4000"] = ["VCNL4000"] | ||
diff --git a/python/webiopi/devices/sensor/bmp085.py b/python/webiopi/devices/sensor/bmp085.py new file mode 100644 index 0000000..e21ab9a --- /dev/null +++ b/python/webiopi/devices/sensor/bmp085.py | |||
| @@ -0,0 +1,100 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import time | ||
| 16 | from webiopi.utils.types import signInteger | ||
| 17 | from webiopi.devices.i2c import I2C | ||
| 18 | from webiopi.devices.sensor import Temperature, Pressure | ||
| 19 | |||
| 20 | class BMP085(I2C, Temperature, Pressure): | ||
| 21 | def __init__(self, altitude=0, external=None): | ||
| 22 | I2C.__init__(self, 0x77) | ||
| 23 | Pressure.__init__(self, altitude, external) | ||
| 24 | |||
| 25 | self.ac1 = self.readSignedInteger(0xAA) | ||
| 26 | self.ac2 = self.readSignedInteger(0xAC) | ||
| 27 | self.ac3 = self.readSignedInteger(0xAE) | ||
| 28 | self.ac4 = self.readUnsignedInteger(0xB0) | ||
| 29 | self.ac5 = self.readUnsignedInteger(0xB2) | ||
| 30 | self.ac6 = self.readUnsignedInteger(0xB4) | ||
| 31 | self.b1 = self.readSignedInteger(0xB6) | ||
| 32 | self.b2 = self.readSignedInteger(0xB8) | ||
| 33 | self.mb = self.readSignedInteger(0xBA) | ||
| 34 | self.mc = self.readSignedInteger(0xBC) | ||
| 35 | self.md = self.readSignedInteger(0xBE) | ||
| 36 | |||
| 37 | def __str__(self): | ||
| 38 | return "BMP085" | ||
| 39 | |||
| 40 | def __family__(self): | ||
| 41 | return [Temperature.__family__(self), Pressure.__family__(self)] | ||
| 42 | |||
| 43 | def readUnsignedInteger(self, address): | ||
| 44 | d = self.readRegisters(address, 2) | ||
| 45 | return d[0] << 8 | d[1] | ||
| 46 | |||
| 47 | def readSignedInteger(self, address): | ||
| 48 | d = self.readUnsignedInteger(address) | ||
| 49 | return signInteger(d, 16) | ||
| 50 | |||
| 51 | def readUT(self): | ||
| 52 | self.writeRegister(0xF4, 0x2E) | ||
| 53 | time.sleep(0.01) | ||
| 54 | return self.readUnsignedInteger(0xF6) | ||
| 55 | |||
| 56 | def readUP(self): | ||
| 57 | self.writeRegister(0xF4, 0x34) | ||
| 58 | time.sleep(0.01) | ||
| 59 | return self.readUnsignedInteger(0xF6) | ||
| 60 | |||
| 61 | def getB5(self): | ||
| 62 | ut = self.readUT() | ||
| 63 | x1 = ((ut - self.ac6) * self.ac5) / 2**15 | ||
| 64 | x2 = (self.mc * 2**11) / (x1 + self.md) | ||
| 65 | return x1 + x2 | ||
| 66 | |||
| 67 | def __getKelvin__(self): | ||
| 68 | return self.Celsius2Kelvin() | ||
| 69 | |||
| 70 | def __getCelsius__(self): | ||
| 71 | t = (self.getB5() + 8) / 2**4 | ||
| 72 | return float(t) / 10.0 | ||
| 73 | |||
| 74 | def __getFahrenheit__(self): | ||
| 75 | return self.Celsius2Fahrenheit() | ||
| 76 | |||
| 77 | def __getPascal__(self): | ||
| 78 | b5 = self.getB5() | ||
| 79 | up = self.readUP() | ||
| 80 | b6 = b5 - 4000 | ||
| 81 | x1 = (self.b2 * (b6 * b6 / 2**12)) / 2**11 | ||
| 82 | x2 = self.ac2 * b6 / 2**11 | ||
| 83 | x3 = x1 + x2 | ||
| 84 | b3 = (self.ac1*4 + x3 + 2) / 4 | ||
| 85 | |||
| 86 | x1 = self.ac3 * b6 / 2**13 | ||
| 87 | x2 = (self.b1 * (b6 * b6 / 2**12)) / 2**16 | ||
| 88 | x3 = (x1 + x2 + 2) / 2**2 | ||
| 89 | b4 = self.ac4 * (x3 + 32768) / 2**15 | ||
| 90 | b7 = (up-b3) * 50000 | ||
| 91 | if b7 < 0x80000000: | ||
| 92 | p = (b7 * 2) / b4 | ||
| 93 | else: | ||
| 94 | p = (b7 / b4) * 2 | ||
| 95 | |||
| 96 | x1 = (p / 2**8) * (p / 2**8) | ||
| 97 | x1 = (x1 * 3038) / 2**16 | ||
| 98 | x2 = (-7357*p) / 2**16 | ||
| 99 | p = p + (x1 + x2 + 3791) / 2**4 | ||
| 100 | return int(p) | ||
diff --git a/python/webiopi/devices/sensor/onewiretemp.py b/python/webiopi/devices/sensor/onewiretemp.py new file mode 100644 index 0000000..703c32d --- /dev/null +++ b/python/webiopi/devices/sensor/onewiretemp.py | |||
| @@ -0,0 +1,58 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.devices.onewire import OneWire | ||
| 16 | from webiopi.devices.sensor import Temperature | ||
| 17 | |||
| 18 | class OneWireTemp(OneWire, Temperature): | ||
| 19 | def __init__(self, slave=None, family=0, name="1-Wire"): | ||
| 20 | OneWire.__init__(self, slave, family, "TEMP") | ||
| 21 | self.name = name | ||
| 22 | |||
| 23 | def __str__(self): | ||
| 24 | return "%s(slave=%s)" % (self.name, self.slave) | ||
| 25 | |||
| 26 | def __getKelvin__(self): | ||
| 27 | return self.Celsius2Kelvin() | ||
| 28 | |||
| 29 | def __getCelsius__(self): | ||
| 30 | data = self.read() | ||
| 31 | lines = data.split("\n") | ||
| 32 | if lines[0].endswith("YES"): | ||
| 33 | i = lines[1].find("=") | ||
| 34 | temp = lines[1][i+1:] | ||
| 35 | return int(temp) / 1000.0 | ||
| 36 | |||
| 37 | def __getFahrenheit__(self): | ||
| 38 | return self.Celsius2Fahrenheit() | ||
| 39 | |||
| 40 | class DS18S20(OneWireTemp): | ||
| 41 | def __init__(self, slave=None): | ||
| 42 | OneWireTemp.__init__(self, slave, 0x10, "DS18S20") | ||
| 43 | |||
| 44 | class DS1822(OneWireTemp): | ||
| 45 | def __init__(self, slave=None): | ||
| 46 | OneWireTemp.__init__(self, slave, 0x22, "DS1822") | ||
| 47 | |||
| 48 | class DS18B20(OneWireTemp): | ||
| 49 | def __init__(self, slave=None): | ||
| 50 | OneWireTemp.__init__(self, slave, 0x28, "DS18B20") | ||
| 51 | |||
| 52 | class DS1825(OneWireTemp): | ||
| 53 | def __init__(self, slave=None): | ||
| 54 | OneWireTemp.__init__(self, slave, 0x3B, "DS1825") | ||
| 55 | |||
| 56 | class DS28EA00(OneWireTemp): | ||
| 57 | def __init__(self, slave=None): | ||
| 58 | OneWireTemp.__init__(self, slave, 0x42, "DS28EA00") | ||
diff --git a/python/webiopi/devices/sensor/tmpXXX.py b/python/webiopi/devices/sensor/tmpXXX.py new file mode 100644 index 0000000..ba41153 --- /dev/null +++ b/python/webiopi/devices/sensor/tmpXXX.py | |||
| @@ -0,0 +1,60 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import toint, signInteger | ||
| 16 | from webiopi.devices.i2c import I2C | ||
| 17 | from webiopi.devices.sensor import Temperature | ||
| 18 | |||
| 19 | class TMP102(I2C, Temperature): | ||
| 20 | def __init__(self, slave=0x48): | ||
| 21 | I2C.__init__(self, toint(slave)) | ||
| 22 | |||
| 23 | def __str__(self): | ||
| 24 | return "TMP102(slave=0x%02X)" % self.slave | ||
| 25 | |||
| 26 | def __getKelvin__(self): | ||
| 27 | return self.Celsius2Kelvin() | ||
| 28 | |||
| 29 | def __getCelsius__(self): | ||
| 30 | d = self.readBytes(2) | ||
| 31 | count = ((d[0] << 4) | (d[1] >> 4)) & 0xFFF | ||
| 32 | return signInteger(count, 12)*0.0625 | ||
| 33 | |||
| 34 | def __getFahrenheit__(self): | ||
| 35 | return self.Celsius2Fahrenheit() | ||
| 36 | |||
| 37 | class TMP75(TMP102): | ||
| 38 | def __init__(self, slave=0x48, resolution=12): | ||
| 39 | TMP102.__init__(self, slave) | ||
| 40 | resolution = toint(resolution) | ||
| 41 | if not resolution in range(9,13): | ||
| 42 | raise ValueError("%dbits resolution out of range [%d..%d]bits" % (resolution, 9, 12)) | ||
| 43 | self.resolution = resolution | ||
| 44 | |||
| 45 | config = self.readRegister(0x01) | ||
| 46 | config &= ~0x60 | ||
| 47 | config |= (self.resolution - 9) << 5 | ||
| 48 | self.writeRegister(0x01, config) | ||
| 49 | self.readRegisters(0x00, 2) | ||
| 50 | |||
| 51 | def __str__(self): | ||
| 52 | return "TMP75(slave=0x%02X, resolution=%d-bits)" % (self.slave, self.resolution) | ||
| 53 | |||
| 54 | class TMP275(TMP75): | ||
| 55 | def __init__(self, slave=0x48, resolution=12): | ||
| 56 | TMP75.__init__(self, slave, resolution) | ||
| 57 | |||
| 58 | def __str__(self): | ||
| 59 | return "TMP275(slave=0x%02X, resolution=%d-bits)" % (self.slave, self.resolution) | ||
| 60 | |||
diff --git a/python/webiopi/devices/sensor/tslXXXX.py b/python/webiopi/devices/sensor/tslXXXX.py new file mode 100644 index 0000000..1189d6f --- /dev/null +++ b/python/webiopi/devices/sensor/tslXXXX.py | |||
| @@ -0,0 +1,247 @@ | |||
| 1 | # Copyright 2013 Andreas Riegg | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | # | ||
| 15 | # | ||
| 16 | # Changelog | ||
| 17 | # | ||
| 18 | # 1.0 2013/02/28 Initial release | ||
| 19 | # | ||
| 20 | |||
| 21 | from webiopi.utils.types import toint | ||
| 22 | from webiopi.devices.i2c import I2C | ||
| 23 | from webiopi.devices.sensor import Luminosity | ||
| 24 | |||
| 25 | class TSL_LIGHT_X(I2C, Luminosity): | ||
| 26 | VAL_COMMAND = 0x80 | ||
| 27 | REG_CONTROL = 0x00 | VAL_COMMAND | ||
| 28 | REG_CONFIG = 0x01 | VAL_COMMAND | ||
| 29 | |||
| 30 | VAL_PWON = 0x03 | ||
| 31 | VAL_PWOFF = 0x00 | ||
| 32 | VAL_INVALID = -1 | ||
| 33 | |||
| 34 | def __init__(self, slave, time, name="TSL_LIGHT_X"): | ||
| 35 | I2C.__init__(self, toint(slave)) | ||
| 36 | self.name = name | ||
| 37 | self.wake() # devices are powered down after power reset, wake them | ||
| 38 | self.setTime(toint(time)) | ||
| 39 | |||
| 40 | def __str__(self): | ||
| 41 | return "%s(slave=0x%02X)" % (self.name, self.slave) | ||
| 42 | |||
| 43 | def wake(self): | ||
| 44 | self.__wake__() | ||
| 45 | |||
| 46 | def __wake__(self): | ||
| 47 | self.writeRegister(self.REG_CONTROL, self.VAL_PWON) | ||
| 48 | |||
| 49 | def sleep(self): | ||
| 50 | self.__sleep__() | ||
| 51 | |||
| 52 | def __sleep__(self): | ||
| 53 | self.writeRegister(self.REG_CONTROL, self.VAL_PWOFF) | ||
| 54 | |||
| 55 | def setTime(self, time): | ||
| 56 | self.__setTime__(time) | ||
| 57 | |||
| 58 | def getTime(self): | ||
| 59 | return self.__getTime__() | ||
| 60 | |||
| 61 | class TSL2561X(TSL_LIGHT_X): | ||
| 62 | VAL_TIME_402_MS = 0x02 | ||
| 63 | VAL_TIME_101_MS = 0x01 | ||
| 64 | VAL_TIME_14_MS = 0x00 | ||
| 65 | |||
| 66 | REG_CHANNEL_0_LOW = 0x0C | TSL_LIGHT_X.VAL_COMMAND | ||
| 67 | REG_CHANNEL_1_LOW = 0x0E | TSL_LIGHT_X.VAL_COMMAND | ||
| 68 | |||
| 69 | MASK_GAIN = 0x10 | ||
| 70 | MASK_TIME = 0x03 | ||
| 71 | |||
| 72 | def __init__(self, slave, time, gain, name="TSL2561X"): | ||
| 73 | TSL_LIGHT_X.__init__(self, slave, time, name) | ||
| 74 | self.setGain(toint(gain)) | ||
| 75 | |||
| 76 | def __getLux__(self): | ||
| 77 | ch0_bytes = self.readRegisters(self.REG_CHANNEL_0_LOW, 2) | ||
| 78 | ch1_bytes = self.readRegisters(self.REG_CHANNEL_1_LOW, 2) | ||
| 79 | ch0_word = ch0_bytes[1] << 8 | ch0_bytes[0] | ||
| 80 | ch1_word = ch1_bytes[1] << 8 | ch1_bytes[0] | ||
| 81 | if ch0_word == 0 | ch1_word == 0: | ||
| 82 | return self.VAL_INVALID # Driver security, avoid crash in lux calculation | ||
| 83 | else: | ||
| 84 | scaling = self.time_multiplier * self.gain_multiplier | ||
| 85 | return self.__calculateLux__(scaling * ch0_word, scaling * ch1_word) | ||
| 86 | |||
| 87 | def setGain(self, gain): | ||
| 88 | if gain == 1: | ||
| 89 | bit_gain = 0 | ||
| 90 | self.gain_multiplier = 16 | ||
| 91 | elif gain == 16: | ||
| 92 | bit_gain = 1 | ||
| 93 | self.gain_multiplier = 1 | ||
| 94 | else: | ||
| 95 | raise ValueError("Gain %d out of range [%d,%d]" % (gain, 1, 16)) | ||
| 96 | new_byte_gain = (bit_gain << 4) & self.MASK_GAIN | ||
| 97 | |||
| 98 | current_byte_config = self.readRegister(self.REG_CONFIG) | ||
| 99 | new_byte_config = (current_byte_config & ~self.MASK_GAIN) | new_byte_gain | ||
| 100 | self.writeRegister(self.REG_CONFIG, new_byte_config) | ||
| 101 | |||
| 102 | def getGain(self): | ||
| 103 | current_byte_config = self.readRegister(self.REG_CONFIG) | ||
| 104 | if (current_byte_config & self.MASK_GAIN): | ||
| 105 | return 16 | ||
| 106 | else: | ||
| 107 | return 1 | ||
| 108 | |||
| 109 | def __setTime__(self, time): | ||
| 110 | if not time in [14, 101, 402]: | ||
| 111 | raise ValueError("Time %d out of range [%d,%d,%d]" % (time, 14, 101, 402)) | ||
| 112 | if time == 402: | ||
| 113 | bits_time = self.VAL_TIME_402_MS | ||
| 114 | self.time_multiplier = 1 | ||
| 115 | elif time == 101: | ||
| 116 | bits_time = self.VAL_TIME_101_MS | ||
| 117 | self.time_multiplier = 322 / 81 | ||
| 118 | elif time == 14: | ||
| 119 | bits_time = self.VAL_TIME_14_MS | ||
| 120 | self.time_multiplier = 322 / 11 | ||
| 121 | new_byte_time = bits_time & self.MASK_TIME | ||
| 122 | |||
| 123 | current_byte_config = self.readRegister(self.REG_CONFIG) | ||
| 124 | new_byte_config = (current_byte_config & ~self.MASK_TIME) | new_byte_time | ||
| 125 | self.writeRegister(self.REG_CONFIG, new_byte_config) | ||
| 126 | |||
| 127 | def __getTime__(self): | ||
| 128 | current_byte_config = self.readRegister(self.REG_CONFIG) | ||
| 129 | bits_time = (current_byte_config & self.MASK_TIME) | ||
| 130 | if bits_time == self.VAL_TIME_402_MS: | ||
| 131 | t = 402 | ||
| 132 | elif bits_time == self.VAL_TIME_101_MS: | ||
| 133 | t = 101 | ||
| 134 | elif bits_time == self.VAL_TIME_14_MS: | ||
| 135 | t = 14 | ||
| 136 | else: | ||
| 137 | t = TSL_LIGHT_X.VAL_INVALID # indicates undefined | ||
| 138 | return t | ||
| 139 | |||
| 140 | class TSL2561CS(TSL2561X): | ||
| 141 | # Package CS (Chipscale) chip version | ||
| 142 | def __init__(self, slave=0x39, time=402, gain=1): | ||
| 143 | TSL2561X.__init__(self, slave, time, gain, "TSL2561CS") | ||
| 144 | |||
| 145 | def __calculateLux__(self, channel0_value, channel1value): | ||
| 146 | channelRatio = channel1value / channel0_value | ||
| 147 | if 0 < channelRatio <= 0.52: | ||
| 148 | lux = 0.0315 * channel0_value - 0.0593 * channel0_value *(channelRatio**1.4) | ||
| 149 | elif 0.52 < channelRatio <= 0.65: | ||
| 150 | lux = 0.0229 * channel0_value - 0.0291 * channel1value | ||
| 151 | elif 0.65 < channelRatio <= 0.80: | ||
| 152 | lux = 0.0157 * channel0_value - 0.0180 * channel1value | ||
| 153 | elif 0.80 < channelRatio <= 1.30: | ||
| 154 | lux = 0.00338 * channel0_value - 0.00260 * channel1value | ||
| 155 | else: # if channelRatio > 1.30 | ||
| 156 | lux = 0 | ||
| 157 | return lux | ||
| 158 | |||
| 159 | class TSL2561T(TSL2561X): | ||
| 160 | # Package T (TMB-6) chip version | ||
| 161 | def __init__(self, slave=0x39, time=402, gain=1): | ||
| 162 | TSL2561X.__init__(self, slave, time, gain, "TSL2561T") | ||
| 163 | |||
| 164 | def __calculateLux__(self, channel0_value, channel1_value): | ||
| 165 | channel_ratio = channel1_value / channel0_value | ||
| 166 | if 0 < channel_ratio <= 0.50: | ||
| 167 | lux = 0.0304 * channel0_value - 0.062 * channel0_value * (channel_ratio**1.4) | ||
| 168 | elif 0.50 < channel_ratio <= 0.61: | ||
| 169 | lux = 0.0224 * channel0_value - 0.031 * channel1_value | ||
| 170 | elif 0.61 < channel_ratio <= 0.80: | ||
| 171 | lux = 0.0128 * channel0_value - 0.0153 * channel1_value | ||
| 172 | elif 0.80 < channel_ratio <= 1.30: | ||
| 173 | lux = 0.00146 * channel0_value - 0.00112 * channel1_value | ||
| 174 | else: # if channel_ratio > 1.30 | ||
| 175 | lux = 0 | ||
| 176 | return lux | ||
| 177 | |||
| 178 | class TSL2561(TSL2561T): | ||
| 179 | # Default version for unknown packages, uses T Package class lux calculation | ||
| 180 | def __init__(self, slave=0x39, time=402, gain=1): | ||
| 181 | TSL2561X.__init__(self, slave, time, gain, "TSL2561") | ||
| 182 | |||
| 183 | |||
| 184 | class TSL4531(TSL_LIGHT_X): | ||
| 185 | # Default version for unknown subtypes, uses 0x29 as slave address | ||
| 186 | VAL_TIME_400_MS = 0x00 | ||
| 187 | VAL_TIME_200_MS = 0x01 | ||
| 188 | VAL_TIME_100_MS = 0x02 | ||
| 189 | |||
| 190 | REG_DATA_LOW = 0x04 | TSL_LIGHT_X.VAL_COMMAND | ||
| 191 | |||
| 192 | MASK_TCNTRL = 0x03 | ||
| 193 | |||
| 194 | def __init__(self, slave=0x29, time=400, name="TSL4531"): | ||
| 195 | TSL_LIGHT_X.__init__(self, slave, time, name) | ||
| 196 | |||
| 197 | def __setTime__(self, time): | ||
| 198 | if not time in [100, 200, 400]: | ||
| 199 | raise ValueError("Time %d out of range [%d,%d,%d]" % (time, 100, 200, 400)) | ||
| 200 | if time == 400: | ||
| 201 | bits_time = self.VAL_TIME_400_MS | ||
| 202 | self.time_multiplier = 1 | ||
| 203 | elif time == 200: | ||
| 204 | bits_time = self.VAL_TIME_200_MS | ||
| 205 | self.time_multiplier = 2 | ||
| 206 | elif time == 100: | ||
| 207 | bits_time = self.VAL_TIME_100_MS | ||
| 208 | self.time_multiplier = 4 | ||
| 209 | new_byte_time = bits_time & self.MASK_TCNTRL | ||
| 210 | |||
| 211 | current_byte_config = self.readRegister(self.REG_CONFIG) | ||
| 212 | new_byte_config = (current_byte_config & ~self.MASK_TCNTRL) | new_byte_time | ||
| 213 | self.writeRegister(self.REG_CONFIG, new_byte_config) | ||
| 214 | |||
| 215 | def __getTime__(self): | ||
| 216 | current_byte_config = self.readRegister(self.REG_CONFIG) | ||
| 217 | bits_time = (current_byte_config & self.MASK_TCNTRL) | ||
| 218 | if bits_time == self.VAL_TIME_400_MS: | ||
| 219 | t = 400 | ||
| 220 | elif bits_time == self.VAL_TIME_200_MS: | ||
| 221 | t = 200 | ||
| 222 | elif bits_time == self.VAL_TIME_100_MS: | ||
| 223 | t = 100 | ||
| 224 | else: | ||
| 225 | t = TSL_LIGHT_X.VAL_INVALID # indicates undefined | ||
| 226 | return t | ||
| 227 | |||
| 228 | def __getLux__(self): | ||
| 229 | data_bytes = self.readRegisters(self.REG_DATA_LOW, 2) | ||
| 230 | return self.time_multiplier * (data_bytes[1] << 8 | data_bytes[0]) | ||
| 231 | |||
| 232 | class TSL45311(TSL4531): | ||
| 233 | def __init__(self, slave=0x39, time=400): | ||
| 234 | TSL4531.__init__(self, slave, time, "TSL45311") | ||
| 235 | |||
| 236 | class TSL45313(TSL4531): | ||
| 237 | def __init__(self, slave=0x39, time=400): | ||
| 238 | TSL4531.__init__(self, slave, time, "TSL45313") | ||
| 239 | |||
| 240 | class TSL45315(TSL4531): | ||
| 241 | def __init__(self, slave=0x29, time=400): | ||
| 242 | TSL4531.__init__(self, slave, time, "TSL45315") | ||
| 243 | |||
| 244 | class TSL45317(TSL4531): | ||
| 245 | def __init__(self, slave=0x29, time=400): | ||
| 246 | TSL4531.__init__(self, slave, time, "TSL45317") | ||
| 247 | |||
diff --git a/python/webiopi/devices/sensor/vcnl4000.py b/python/webiopi/devices/sensor/vcnl4000.py new file mode 100644 index 0000000..fb8b40b --- /dev/null +++ b/python/webiopi/devices/sensor/vcnl4000.py | |||
| @@ -0,0 +1,211 @@ | |||
| 1 | # Copyright 2013 Andreas Riegg | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | # | ||
| 15 | # | ||
| 16 | # Changelog | ||
| 17 | # | ||
| 18 | # 1.0 2013/02/24 Initial release. Luminosity is final. Proximity is good beta | ||
| 19 | # and a working coarse estimation for distance value. | ||
| 20 | # | ||
| 21 | |||
| 22 | import time | ||
| 23 | from webiopi.devices.i2c import I2C | ||
| 24 | from webiopi.devices.sensor import Luminosity, Distance | ||
| 25 | from webiopi.utils.types import toint | ||
| 26 | from webiopi.utils.logger import debug | ||
| 27 | |||
| 28 | class VCNL4000(I2C, Luminosity, Distance): | ||
| 29 | REG_COMMAND = 0x80 | ||
| 30 | REG_IR_LED_CURRENT = 0x83 | ||
| 31 | REG_AMB_PARAMETERS = 0x84 | ||
| 32 | REG_AMB_RESULT_HIGH = 0x85 | ||
| 33 | REG_PROX_RESULT_HIGH = 0x87 | ||
| 34 | REG_PROX_FREQUENCY = 0x89 | ||
| 35 | REG_PROX_ADJUST = 0x8A | ||
| 36 | |||
| 37 | VAL_MOD_TIMING_DEF = 129 # default from data sheet | ||
| 38 | |||
| 39 | VAL_PR_FREQ_3M125HZ = 0 | ||
| 40 | VAL_PR_FREQ_1M5625HZ = 1 | ||
| 41 | VAL_PR_FREQ_781K25HZ = 2 | ||
| 42 | VAL_PR_FREQ_390K625HZ = 3 | ||
| 43 | |||
| 44 | VAL_START_AMB = 1 << 4 | ||
| 45 | VAL_START_PROX = 1 << 3 | ||
| 46 | |||
| 47 | VAL_INVALID = -1 | ||
| 48 | VAL_NO_PROXIMITY = -1 | ||
| 49 | |||
| 50 | MASK_PROX_FREQUENCY = 0b00111111 | ||
| 51 | MASK_IR_LED_CURRENT = 0b00111111 | ||
| 52 | MASK_PROX_READY = 0b00100000 | ||
| 53 | MASK_AMB_READY = 0b01000000 | ||
| 54 | |||
| 55 | def __init__(self, slave=0b0010011, current=20, frequency=781, prox_threshold=15, prox_cycles=10, cal_cycles= 5): | ||
| 56 | I2C.__init__(self, toint(slave)) | ||
| 57 | self.setCurrent(toint(current)) | ||
| 58 | self.setFrequency(toint(frequency)) | ||
| 59 | self.prox_threshold = toint(prox_threshold) | ||
| 60 | self.prox_cycles = toint(prox_cycles) | ||
| 61 | self.cal_cycles = toint(cal_cycles) | ||
| 62 | self.__setProximityTiming__() | ||
| 63 | self.__setAmbientMeasuringMode__() | ||
| 64 | time.sleep(0.001) | ||
| 65 | self.calibrate() # may have to be repeated from time to time or before every proximity measurement | ||
| 66 | |||
| 67 | def __str__(self): | ||
| 68 | return "VCNL4000(slave=0x%02X)" % self.slave | ||
| 69 | |||
| 70 | def __family__(self): | ||
| 71 | return [Luminosity.__family__(self), Distance.__family__(self)] | ||
| 72 | |||
| 73 | def __setProximityTiming__(self): | ||
| 74 | self.writeRegister(self.REG_PROX_ADJUST, self.VAL_MOD_TIMING_DEF) | ||
| 75 | |||
| 76 | def __setAmbientMeasuringMode__(self): | ||
| 77 | ambient_parameter_bytes = 1 << 7 | 1 << 3 | 5 | ||
| 78 | # Parameter is set to | ||
| 79 | # -continuous conversion mode (bit 7) | ||
| 80 | # -auto offset compensation (bit 3) | ||
| 81 | # -averaging 32 samples (5) | ||
| 82 | self.writeRegister(self.REG_AMB_PARAMETERS, ambient_parameter_bytes) | ||
| 83 | |||
| 84 | def calibrate(self): | ||
| 85 | self.offset = self.__measureOffset__() | ||
| 86 | debug ("VCNL4000: offset = %d" % (self.offset)) | ||
| 87 | return self.offset | ||
| 88 | |||
| 89 | |||
| 90 | def setCurrent(self, current): | ||
| 91 | self.current = current | ||
| 92 | self.__setCurrent__() | ||
| 93 | |||
| 94 | |||
| 95 | def getCurrent(self): | ||
| 96 | return self.__getCurrent__() | ||
| 97 | |||
| 98 | def setFrequency(self, frequency): | ||
| 99 | self.frequency = frequency | ||
| 100 | self.__setFrequency__() | ||
| 101 | |||
| 102 | def getFrequency(self): | ||
| 103 | return self.__getFrequency__() | ||
| 104 | |||
| 105 | def __setFrequency__(self): | ||
| 106 | if not self.frequency in [391, 781, 1563, 3125]: | ||
| 107 | raise ValueError("Frequency %d out of range [%d,%d,%d,,%d]" % (self.frequency, 391, 781, 1563, 3125)) | ||
| 108 | if self.frequency == 391: | ||
| 109 | bits_frequency = self.VAL_PR_FREQ_390K625HZ | ||
| 110 | elif self.frequency == 781: | ||
| 111 | bits_frequency = self.VAL_PR_FREQ_781K25HZ | ||
| 112 | elif self.frequency == 1563: | ||
| 113 | bits_frequency = self.VAL_PR_FREQ_1M5625HZ | ||
| 114 | elif self.frequency == 3125: | ||
| 115 | bits_frequency = self.VAL_PR_FREQ_3M125HZ | ||
| 116 | self.writeRegister(self.REG_PROX_FREQUENCY, bits_frequency) | ||
| 117 | debug ("VCNL4000: new freq = %d" % (self.readRegister(self.REG_PROX_FREQUENCY))) | ||
| 118 | |||
| 119 | def __getFrequency__(self): | ||
| 120 | bits_frequency = self.readRegister(self.REG_PROX_FREQUENCY) & self.MASK_PROX_FREQUENCY | ||
| 121 | if bits_frequency == self.VAL_PR_FREQ_390K625HZ: | ||
| 122 | f = 391 | ||
| 123 | elif bits_frequency == self.VAL_PR_FREQ_781K25HZ: | ||
| 124 | f = 781 | ||
| 125 | elif bits_frequency == self.VAL_PR_FREQ_1M5625HZ: | ||
| 126 | f = 1563 | ||
| 127 | elif bits_frequency == self.VAL_PR_FREQ_3M125HZ: | ||
| 128 | f = 3125 | ||
| 129 | else: | ||
| 130 | f = self.VAL_INVALID # indicates undefined | ||
| 131 | return f | ||
| 132 | |||
| 133 | def __setCurrent__(self): | ||
| 134 | if not self.current in range(0,201): | ||
| 135 | raise ValueError("%d mA LED current out of range [%d..%d] mA" % (self.current, 0, 201)) | ||
| 136 | self.writeRegister(self.REG_IR_LED_CURRENT, int(self.current / 10)) | ||
| 137 | debug ("VCNL4000: new curr = %d" % (self.readRegister(self.REG_IR_LED_CURRENT))) | ||
| 138 | |||
| 139 | def __getCurrent__(self): | ||
| 140 | bits_current = self.readRegister(self.REG_IR_LED_CURRENT) & self.MASK_IR_LED_CURRENT | ||
| 141 | return bits_current * 10 | ||
| 142 | |||
| 143 | def __getLux__(self): | ||
| 144 | self.writeRegister(self.REG_COMMAND, self.VAL_START_AMB) | ||
| 145 | while not (self.readRegister(self.REG_COMMAND) & self.MASK_AMB_READY): | ||
| 146 | time.sleep(0.001) | ||
| 147 | light_bytes = self.readRegisters(self.REG_AMB_RESULT_HIGH, 2) | ||
| 148 | light_word = light_bytes[0] << 8 | light_bytes[1] | ||
| 149 | return self.__calculateLux__(light_word) | ||
| 150 | |||
| 151 | def __calculateLux__(self, light_word): | ||
| 152 | return (light_word + 3) * 0.25 # From VISHAY application note | ||
| 153 | |||
| 154 | def __getMillimeter__(self): | ||
| 155 | success = 0 | ||
| 156 | fail = 0 | ||
| 157 | prox = 0 | ||
| 158 | match_cycles = self.prox_cycles | ||
| 159 | while (fail < match_cycles) & (success < match_cycles): | ||
| 160 | real_counts = self.__readProximityCounts__() - self.offset | ||
| 161 | if real_counts > self.prox_threshold: | ||
| 162 | success += 1 | ||
| 163 | prox += real_counts | ||
| 164 | else: | ||
| 165 | fail += 1 | ||
| 166 | if fail == match_cycles: | ||
| 167 | return self.VAL_NO_PROXIMITY | ||
| 168 | else: | ||
| 169 | return self.__calculateMillimeter__(prox // match_cycles) | ||
| 170 | |||
| 171 | def __calculateMillimeter__(self, raw_proximity_counts): | ||
| 172 | # According to chip spec the proximity counts are strong non-linear with distance and cannot be calculated | ||
| 173 | # with a direct formula. From experience found on web this chip is generally not suited for really exact | ||
| 174 | # distance calculations. This is a rough distance estimation lookup table for now. Maybe someone can | ||
| 175 | # provide a more exact approximation in the future. | ||
| 176 | |||
| 177 | debug ("VCNL4000: prox real raw counts = %d" % (raw_proximity_counts)) | ||
| 178 | if raw_proximity_counts >= 10000: | ||
| 179 | estimated_distance = 0 | ||
| 180 | elif raw_proximity_counts >= 3000: | ||
| 181 | estimated_distance = 5 | ||
| 182 | elif raw_proximity_counts >= 900: | ||
| 183 | estimated_distance = 10 | ||
| 184 | elif raw_proximity_counts >= 300: | ||
| 185 | estimated_distance = 20 | ||
| 186 | elif raw_proximity_counts >= 150: | ||
| 187 | estimated_distance = 30 | ||
| 188 | elif raw_proximity_counts >= 75: | ||
| 189 | estimated_distance = 40 | ||
| 190 | elif raw_proximity_counts >= 50: | ||
| 191 | estimated_distance = 50 | ||
| 192 | elif raw_proximity_counts >= 25: | ||
| 193 | estimated_distance = 70 | ||
| 194 | else: | ||
| 195 | estimated_distance = 100 | ||
| 196 | return estimated_distance | ||
| 197 | |||
| 198 | def __measureOffset__(self): | ||
| 199 | offset = 0 | ||
| 200 | for unused in range(self.cal_cycles): | ||
| 201 | offset += self.__readProximityCounts__() | ||
| 202 | return offset // self.cal_cycles | ||
| 203 | |||
| 204 | def __readProximityCounts__(self): | ||
| 205 | self.writeRegister(self.REG_COMMAND, self.VAL_START_PROX) | ||
| 206 | while not (self.readRegister(self.REG_COMMAND) & self.MASK_PROX_READY): | ||
| 207 | time.sleep(0.001) | ||
| 208 | proximity_bytes = self.readRegisters(self.REG_PROX_RESULT_HIGH, 2) | ||
| 209 | debug ("VCNL4000: prox raw value = %d" % (proximity_bytes[0] << 8 | proximity_bytes[1])) | ||
| 210 | return (proximity_bytes[0] << 8 | proximity_bytes[1]) | ||
| 211 | \ No newline at end of file | ||
diff --git a/python/webiopi/devices/serial.py b/python/webiopi/devices/serial.py new file mode 100644 index 0000000..800f96f --- /dev/null +++ b/python/webiopi/devices/serial.py | |||
| @@ -0,0 +1,86 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import os | ||
| 16 | import fcntl | ||
| 17 | import struct | ||
| 18 | import termios | ||
| 19 | |||
| 20 | from webiopi.devices.bus import Bus | ||
| 21 | from webiopi.decorators.rest import request | ||
| 22 | |||
| 23 | TIOCINQ = hasattr(termios, 'FIONREAD') and termios.FIONREAD or 0x541B | ||
| 24 | TIOCM_zero_str = struct.pack('I', 0) | ||
| 25 | |||
| 26 | class Serial(Bus): | ||
| 27 | def __init__(self, device="/dev/ttyAMA0", baudrate=9600): | ||
| 28 | if not device.startswith("/dev/"): | ||
| 29 | device = "/dev/%s" % device | ||
| 30 | |||
| 31 | if isinstance(baudrate, str): | ||
| 32 | baudrate = int(baudrate) | ||
| 33 | |||
| 34 | aname = "B%d" % baudrate | ||
| 35 | if not hasattr(termios, aname): | ||
| 36 | raise Exception("Unsupported baudrate") | ||
| 37 | self.baudrate = baudrate | ||
| 38 | |||
| 39 | Bus.__init__(self, "UART", device, os.O_RDWR | os.O_NOCTTY) | ||
| 40 | fcntl.fcntl(self.fd, fcntl.F_SETFL, os.O_NDELAY) | ||
| 41 | |||
| 42 | #backup = termios.tcgetattr(self.fd) | ||
| 43 | options = termios.tcgetattr(self.fd) | ||
| 44 | # iflag | ||
| 45 | options[0] = 0 | ||
| 46 | |||
| 47 | # oflag | ||
| 48 | options[1] = 0 | ||
| 49 | |||
| 50 | # cflag | ||
| 51 | options[2] |= (termios.CLOCAL | termios.CREAD) | ||
| 52 | options[2] &= ~termios.PARENB | ||
| 53 | options[2] &= ~termios.CSTOPB | ||
| 54 | options[2] &= ~termios.CSIZE | ||
| 55 | options[2] |= termios.CS8 | ||
| 56 | |||
| 57 | # lflag | ||
| 58 | options[3] = 0 | ||
| 59 | |||
| 60 | speed = getattr(termios, aname) | ||
| 61 | # input speed | ||
| 62 | options[4] = speed | ||
| 63 | # output speed | ||
| 64 | options[5] = speed | ||
| 65 | |||
| 66 | termios.tcsetattr(self.fd, termios.TCSADRAIN, options) | ||
| 67 | |||
| 68 | def __str__(self): | ||
| 69 | return "Serial(%s, %dbps)" % (self.device, self.baudrate) | ||
| 70 | |||
| 71 | def __family__(self): | ||
| 72 | return "Serial" | ||
| 73 | |||
| 74 | def available(self): | ||
| 75 | s = fcntl.ioctl(self.fd, TIOCINQ, TIOCM_zero_str) | ||
| 76 | return struct.unpack('I',s)[0] | ||
| 77 | |||
| 78 | @request("GET", "") | ||
| 79 | def read(self): | ||
| 80 | if self.available() > 0: | ||
| 81 | return Bus.read(self, self.available()).decode() | ||
| 82 | return "" | ||
| 83 | |||
| 84 | @request("POST", "", "data") | ||
| 85 | def write(self, data): | ||
| 86 | Bus.write(self, data) | ||
diff --git a/python/webiopi/devices/shield/__init__.py b/python/webiopi/devices/shield/__init__.py new file mode 100644 index 0000000..ef77597 --- /dev/null +++ b/python/webiopi/devices/shield/__init__.py | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | DRIVERS = {} | ||
| 16 | DRIVERS["piface"] = ["PiFaceDigital"] \ No newline at end of file | ||
diff --git a/python/webiopi/devices/shield/piface.py b/python/webiopi/devices/shield/piface.py new file mode 100644 index 0000000..ffe6215 --- /dev/null +++ b/python/webiopi/devices/shield/piface.py | |||
| @@ -0,0 +1,66 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.types import M_JSON | ||
| 16 | from webiopi.devices.digital.mcp23XXX import MCP23S17 | ||
| 17 | from webiopi.decorators.rest import request, response | ||
| 18 | |||
| 19 | |||
| 20 | class PiFaceDigital(): | ||
| 21 | def __init__(self, board=0): | ||
| 22 | mcp = MCP23S17(0, 0x20+board) | ||
| 23 | mcp.writeRegister(mcp.getAddress(mcp.IODIR, 0), 0x00) # Port A as output | ||
| 24 | mcp.writeRegister(mcp.getAddress(mcp.IODIR, 8), 0xFF) # Port B as input | ||
| 25 | mcp.writeRegister(mcp.getAddress(mcp.GPPU, 0), 0x00) # Port A PU OFF | ||
| 26 | mcp.writeRegister(mcp.getAddress(mcp.GPPU, 8), 0xFF) # Port B PU ON | ||
| 27 | self.mcp = mcp | ||
| 28 | self.board = board | ||
| 29 | |||
| 30 | def __str__(self): | ||
| 31 | return "PiFaceDigital(%d)" % self.board | ||
| 32 | |||
| 33 | def __family__(self): | ||
| 34 | return "PiFaceDigital" | ||
| 35 | |||
| 36 | def checkChannel(self, channel): | ||
| 37 | if not channel in range(8): | ||
| 38 | raise ValueError("Channel %d invalid" % channel) | ||
| 39 | |||
| 40 | @request("GET", "digital/input/%(channel)d") | ||
| 41 | @response("%d") | ||
| 42 | def digitalRead(self, channel): | ||
| 43 | self.checkChannel(channel) | ||
| 44 | return not self.mcp.digitalRead(channel+8) | ||
| 45 | |||
| 46 | @request("POST", "digital/output/%(channel)d/%(value)d") | ||
| 47 | @response("%d") | ||
| 48 | def digitalWrite(self, channel, value): | ||
| 49 | self.checkChannel(channel) | ||
| 50 | return self.mcp.digitalWrite(channel, value) | ||
| 51 | |||
| 52 | @request("GET", "digital/output/%(channel)d") | ||
| 53 | @response("%d") | ||
| 54 | def digitalReadOutput(self, channel): | ||
| 55 | self.checkChannel(channel) | ||
| 56 | return self.mcp.digitalRead(channel) | ||
| 57 | |||
| 58 | @request("GET", "digital/*") | ||
| 59 | @response(contentType=M_JSON) | ||
| 60 | def readAll(self): | ||
| 61 | inputs = {} | ||
| 62 | outputs = {} | ||
| 63 | for i in range(8): | ||
| 64 | inputs[i] = self.digitalRead(i) | ||
| 65 | outputs[i] = self.digitalReadOutput(i) | ||
| 66 | return {"input": inputs, "output": outputs} | ||
diff --git a/python/webiopi/devices/spi.py b/python/webiopi/devices/spi.py new file mode 100644 index 0000000..d19de79 --- /dev/null +++ b/python/webiopi/devices/spi.py | |||
| @@ -0,0 +1,145 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import fcntl | ||
| 16 | import array | ||
| 17 | import ctypes | ||
| 18 | import struct | ||
| 19 | |||
| 20 | from webiopi.utils.version import PYTHON_MAJOR | ||
| 21 | from webiopi.devices.bus import Bus | ||
| 22 | |||
| 23 | # from spi/spidev.h | ||
| 24 | _IOC_NRBITS = 8 | ||
| 25 | _IOC_TYPEBITS = 8 | ||
| 26 | _IOC_SIZEBITS = 14 | ||
| 27 | _IOC_DIRBITS = 2 | ||
| 28 | |||
| 29 | _IOC_NRSHIFT = 0 | ||
| 30 | _IOC_TYPESHIFT = (_IOC_NRSHIFT+_IOC_NRBITS) | ||
| 31 | _IOC_SIZESHIFT = (_IOC_TYPESHIFT+_IOC_TYPEBITS) | ||
| 32 | _IOC_DIRSHIFT = (_IOC_SIZESHIFT+_IOC_SIZEBITS) | ||
| 33 | |||
| 34 | _IOC_NONE = 0 | ||
| 35 | _IOC_WRITE = 1 | ||
| 36 | _IOC_READ = 2 | ||
| 37 | |||
| 38 | def _IOC(direction,t,nr,size): | ||
| 39 | return (((direction) << _IOC_DIRSHIFT) | | ||
| 40 | ((size) << _IOC_SIZESHIFT) | | ||
| 41 | ((t) << _IOC_TYPESHIFT) | | ||
| 42 | ((nr) << _IOC_NRSHIFT)) | ||
| 43 | def _IOR(t, number, size): | ||
| 44 | return _IOC(_IOC_READ, t, number, size) | ||
| 45 | def _IOW(t, number, size): | ||
| 46 | return _IOC(_IOC_WRITE, t, number, size) | ||
| 47 | |||
| 48 | SPI_CPHA = 0x01 | ||
| 49 | SPI_CPOL = 0x02 | ||
| 50 | |||
| 51 | SPI_MODE_0 = (0|0) | ||
| 52 | SPI_MODE_1 = (0|SPI_CPHA) | ||
| 53 | SPI_MODE_2 = (SPI_CPOL|0) | ||
| 54 | SPI_MODE_3 = (SPI_CPOL|SPI_CPHA) | ||
| 55 | |||
| 56 | # does not work | ||
| 57 | # SPI_CS_HIGH = 0x04 | ||
| 58 | # SPI_LSB_FIRST = 0x08 | ||
| 59 | # SPI_3WIRE = 0x10 | ||
| 60 | # SPI_LOOP = 0x20 | ||
| 61 | # SPI_NO_CS = 0x40 | ||
| 62 | # SPI_READY = 0x80 | ||
| 63 | |||
| 64 | SPI_IOC_MAGIC = ord('k') | ||
| 65 | |||
| 66 | def SPI_IOC_MESSAGE(count): | ||
| 67 | return _IOW(SPI_IOC_MAGIC, 0, count) | ||
| 68 | |||
| 69 | # Read / Write of SPI mode (SPI_MODE_0..SPI_MODE_3) | ||
| 70 | SPI_IOC_RD_MODE = _IOR(SPI_IOC_MAGIC, 1, 1) | ||
| 71 | SPI_IOC_WR_MODE = _IOW(SPI_IOC_MAGIC, 1, 1) | ||
| 72 | |||
| 73 | # Read / Write SPI bit justification | ||
| 74 | # does not work | ||
| 75 | # SPI_IOC_RD_LSB_FIRST = _IOR(SPI_IOC_MAGIC, 2, 1) | ||
| 76 | # SPI_IOC_WR_LSB_FIRST = _IOW(SPI_IOC_MAGIC, 2, 1) | ||
| 77 | |||
| 78 | # Read / Write SPI device word length (1..N) | ||
| 79 | SPI_IOC_RD_BITS_PER_WORD = _IOR(SPI_IOC_MAGIC, 3, 1) | ||
| 80 | SPI_IOC_WR_BITS_PER_WORD = _IOW(SPI_IOC_MAGIC, 3, 1) | ||
| 81 | |||
| 82 | # Read / Write SPI device default max speed hz | ||
| 83 | SPI_IOC_RD_MAX_SPEED_HZ = _IOR(SPI_IOC_MAGIC, 4, 4) | ||
| 84 | SPI_IOC_WR_MAX_SPEED_HZ = _IOW(SPI_IOC_MAGIC, 4, 4) | ||
| 85 | |||
| 86 | class SPI(Bus): | ||
| 87 | def __init__(self, chip=0, mode=0, bits=8, speed=0): | ||
| 88 | Bus.__init__(self, "SPI", "/dev/spidev0.%d" % chip) | ||
| 89 | self.chip = chip | ||
| 90 | |||
| 91 | val8 = array.array('B', [0]) | ||
| 92 | val8[0] = mode | ||
| 93 | if fcntl.ioctl(self.fd, SPI_IOC_WR_MODE, val8): | ||
| 94 | raise Exception("Cannot write SPI Mode") | ||
| 95 | if fcntl.ioctl(self.fd, SPI_IOC_RD_MODE, val8): | ||
| 96 | raise Exception("Cannot read SPI Mode") | ||
| 97 | self.mode = struct.unpack('B', val8)[0] | ||
| 98 | assert(self.mode == mode) | ||
| 99 | |||
| 100 | val8[0] = bits | ||
| 101 | if fcntl.ioctl(self.fd, SPI_IOC_WR_BITS_PER_WORD, val8): | ||
| 102 | raise Exception("Cannot write SPI Bits per word") | ||
| 103 | if fcntl.ioctl(self.fd, SPI_IOC_RD_BITS_PER_WORD, val8): | ||
| 104 | raise Exception("Cannot read SPI Bits per word") | ||
| 105 | self.bits = struct.unpack('B', val8)[0] | ||
| 106 | assert(self.bits == bits) | ||
| 107 | |||
| 108 | val32 = array.array('I', [0]) | ||
| 109 | if speed > 0: | ||
| 110 | val32[0] = speed | ||
| 111 | if fcntl.ioctl(self.fd, SPI_IOC_WR_MAX_SPEED_HZ, val32): | ||
| 112 | raise Exception("Cannot write SPI Max speed") | ||
| 113 | if fcntl.ioctl(self.fd, SPI_IOC_RD_MAX_SPEED_HZ, val32): | ||
| 114 | raise Exception("Cannot read SPI Max speed") | ||
| 115 | self.speed = struct.unpack('I', val32)[0] | ||
| 116 | assert((self.speed == speed) or (speed == 0)) | ||
| 117 | |||
| 118 | def __str__(self): | ||
| 119 | return "SPI(chip=%d, mode=%d, speed=%dHz)" % (self.chip, self.mode, self.speed) | ||
| 120 | |||
| 121 | def xfer(self, txbuff=None): | ||
| 122 | length = len(txbuff) | ||
| 123 | if PYTHON_MAJOR >= 3: | ||
| 124 | _txbuff = bytes(txbuff) | ||
| 125 | _txptr = ctypes.create_string_buffer(_txbuff) | ||
| 126 | else: | ||
| 127 | _txbuff = str(bytearray(txbuff)) | ||
| 128 | _txptr = ctypes.create_string_buffer(_txbuff) | ||
| 129 | _rxptr = ctypes.create_string_buffer(length) | ||
| 130 | |||
| 131 | data = struct.pack("QQLLHBBL", #64 64 32 32 16 8 8 32 b = 32B | ||
| 132 | ctypes.addressof(_txptr), | ||
| 133 | ctypes.addressof(_rxptr), | ||
| 134 | length, | ||
| 135 | self.speed, | ||
| 136 | 0, #delay | ||
| 137 | self.bits, | ||
| 138 | 0, # cs_change, | ||
| 139 | 0 # pad | ||
| 140 | ) | ||
| 141 | |||
| 142 | fcntl.ioctl(self.fd, SPI_IOC_MESSAGE(len(data)), data) | ||
| 143 | _rxbuff = ctypes.string_at(_rxptr, length) | ||
| 144 | return bytearray(_rxbuff) | ||
| 145 | \ No newline at end of file | ||
diff --git a/python/webiopi/protocols/__init__.py b/python/webiopi/protocols/__init__.py new file mode 100644 index 0000000..fc4ac9a --- /dev/null +++ b/python/webiopi/protocols/__init__.py | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
diff --git a/python/webiopi/protocols/coap.py b/python/webiopi/protocols/coap.py new file mode 100644 index 0000000..3ed6b3e --- /dev/null +++ b/python/webiopi/protocols/coap.py | |||
| @@ -0,0 +1,537 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils.version import PYTHON_MAJOR | ||
| 16 | from webiopi.utils.logger import info, exception | ||
| 17 | |||
| 18 | import socket | ||
| 19 | import struct | ||
| 20 | import logging | ||
| 21 | import threading | ||
| 22 | |||
| 23 | M_PLAIN = "text/plain" | ||
| 24 | M_JSON = "application/json" | ||
| 25 | |||
| 26 | if PYTHON_MAJOR >= 3: | ||
| 27 | from urllib.parse import urlparse | ||
| 28 | else: | ||
| 29 | from urlparse import urlparse | ||
| 30 | |||
| 31 | try : | ||
| 32 | import _webiopi.GPIO as GPIO | ||
| 33 | except: | ||
| 34 | pass | ||
| 35 | |||
| 36 | def HTTPCode2CoAPCode(code): | ||
| 37 | return int(code/100) * 32 + (code%100) | ||
| 38 | |||
| 39 | |||
| 40 | class COAPContentFormat(): | ||
| 41 | FORMATS = {0: "text/plain", | ||
| 42 | 40: "application/link-format", | ||
| 43 | 41: "application/xml", | ||
| 44 | 42: "application/octet-stream", | ||
| 45 | 47: "application/exi", | ||
| 46 | 50: "application/json" | ||
| 47 | } | ||
| 48 | |||
| 49 | @staticmethod | ||
| 50 | def getCode(fmt): | ||
| 51 | if fmt == None: | ||
| 52 | return None | ||
| 53 | for code in COAPContentFormat.FORMATS: | ||
| 54 | if COAPContentFormat.FORMATS[code] == fmt: | ||
| 55 | return code | ||
| 56 | return None | ||
| 57 | |||
| 58 | @staticmethod | ||
| 59 | def toString(code): | ||
| 60 | if code == None: | ||
| 61 | return None | ||
| 62 | |||
| 63 | if code in COAPContentFormat.FORMATS: | ||
| 64 | return COAPContentFormat.FORMATS[code] | ||
| 65 | |||
| 66 | raise Exception("Unknown content format %d" % code) | ||
| 67 | |||
| 68 | |||
| 69 | class COAPOption(): | ||
| 70 | OPTIONS = {1: "If-Match", | ||
| 71 | 3: "Uri-Host", | ||
| 72 | 4: "ETag", | ||
| 73 | 5: "If-None-Match", | ||
| 74 | 7: "Uri-Port", | ||
| 75 | 8: "Location-Path", | ||
| 76 | 11: "Uri-Path", | ||
| 77 | 12: "Content-Format", | ||
| 78 | 14: "Max-Age", | ||
| 79 | 15: "Uri-Query", | ||
| 80 | 16: "Accept", | ||
| 81 | 20: "Location-Query", | ||
| 82 | 35: "Proxy-Uri", | ||
| 83 | 39: "Proxy-Scheme" | ||
| 84 | } | ||
| 85 | |||
| 86 | IF_MATCH = 1 | ||
| 87 | URI_HOST = 3 | ||
| 88 | ETAG = 4 | ||
| 89 | IF_NONE_MATCH = 5 | ||
| 90 | URI_PORT = 7 | ||
| 91 | LOCATION_PATH = 8 | ||
| 92 | URI_PATH = 11 | ||
| 93 | CONTENT_FORMAT = 12 | ||
| 94 | MAX_AGE = 14 | ||
| 95 | URI_QUERY = 15 | ||
| 96 | ACCEPT = 16 | ||
| 97 | LOCATION_QUERY = 20 | ||
| 98 | PROXY_URI = 35 | ||
| 99 | PROXY_SCHEME = 39 | ||
| 100 | |||
| 101 | |||
| 102 | class COAPMessage(): | ||
| 103 | TYPES = ["CON", "NON", "ACK", "RST"] | ||
| 104 | CON = 0 | ||
| 105 | NON = 1 | ||
| 106 | ACK = 2 | ||
| 107 | RST = 3 | ||
| 108 | |||
| 109 | def __init__(self, msg_type=0, code=0, uri=None): | ||
| 110 | self.version = 1 | ||
| 111 | self.type = msg_type | ||
| 112 | self.code = code | ||
| 113 | self.id = 0 | ||
| 114 | self.token = None | ||
| 115 | self.options = [] | ||
| 116 | self.host = "" | ||
| 117 | self.port = 5683 | ||
| 118 | self.uri_path = "" | ||
| 119 | self.content_format = None | ||
| 120 | self.payload = None | ||
| 121 | |||
| 122 | if uri != None: | ||
| 123 | p = urlparse(uri) | ||
| 124 | self.host = p.hostname | ||
| 125 | if p.port: | ||
| 126 | self.port = int(p.port) | ||
| 127 | self.uri_path = p.path | ||
| 128 | |||
| 129 | def __getOptionHeader__(self, byte): | ||
| 130 | delta = (byte & 0xF0) >> 4 | ||
| 131 | length = byte & 0x0F | ||
| 132 | return (delta, length) | ||
| 133 | |||
| 134 | def __str__(self): | ||
| 135 | result = [] | ||
| 136 | result.append("Version: %s" % self.version) | ||
| 137 | result.append("Type: %s" % self.TYPES[self.type]) | ||
| 138 | result.append("Code: %s" % self.CODES[self.code]) | ||
| 139 | result.append("Id: %s" % self.id) | ||
| 140 | result.append("Token: %s" % self.token) | ||
| 141 | #result.append("Options: %s" % len(self.options)) | ||
| 142 | #for option in self.options: | ||
| 143 | # result.append("+ %d: %s" % (option["number"], option["value"])) | ||
| 144 | result.append("Uri-Path: %s" % self.uri_path) | ||
| 145 | result.append("Content-Format: %s" % (COAPContentFormat.toString(self.content_format) if self.content_format else M_PLAIN)) | ||
| 146 | result.append("Payload: %s" % self.payload) | ||
| 147 | result.append("") | ||
| 148 | return '\n'.join(result) | ||
| 149 | |||
| 150 | def getOptionHeaderValue(self, value): | ||
| 151 | if value > 268: | ||
| 152 | return 14 | ||
| 153 | if value > 12: | ||
| 154 | return 13 | ||
| 155 | return value | ||
| 156 | |||
| 157 | def getOptionHeaderExtension(self, value): | ||
| 158 | buff = bytearray() | ||
| 159 | v = self.getOptionHeaderValue(value) | ||
| 160 | |||
| 161 | if v == 14: | ||
| 162 | value -= 269 | ||
| 163 | buff.append((value & 0xFF00) >> 8) | ||
| 164 | buff.append(value & 0x00FF) | ||
| 165 | |||
| 166 | elif v == 13: | ||
| 167 | value -= 13 | ||
| 168 | buff.append(value) | ||
| 169 | |||
| 170 | return buff | ||
| 171 | |||
| 172 | def appendOption(self, buff, lastnumber, option, data): | ||
| 173 | delta = option - lastnumber | ||
| 174 | length = len(data) | ||
| 175 | |||
| 176 | d = self.getOptionHeaderValue(delta) | ||
| 177 | l = self.getOptionHeaderValue(length) | ||
| 178 | |||
| 179 | b = 0 | ||
| 180 | b |= (d << 4) & 0xF0 | ||
| 181 | b |= l & 0x0F | ||
| 182 | buff.append(b) | ||
| 183 | |||
| 184 | ext = self.getOptionHeaderExtension(delta); | ||
| 185 | for b in ext: | ||
| 186 | buff.append(b) | ||
| 187 | |||
| 188 | ext = self.getOptionHeaderExtension(length); | ||
| 189 | for b in ext: | ||
| 190 | buff.append(b) | ||
| 191 | |||
| 192 | for b in data: | ||
| 193 | buff.append(b) | ||
| 194 | |||
| 195 | return option | ||
| 196 | |||
| 197 | def getBytes(self): | ||
| 198 | buff = bytearray() | ||
| 199 | byte = (self.version & 0x03) << 6 | ||
| 200 | byte |= (self.type & 0x03) << 4 | ||
| 201 | if self.token: | ||
| 202 | token_len = min(len(self.token), 8); | ||
| 203 | else: | ||
| 204 | token_len = 0 | ||
| 205 | byte |= token_len | ||
| 206 | buff.append(byte) | ||
| 207 | buff.append(self.code) | ||
| 208 | buff.append((self.id & 0xFF00) >> 8) | ||
| 209 | buff.append(self.id & 0x00FF) | ||
| 210 | |||
| 211 | if self.token: | ||
| 212 | for c in self.token: | ||
| 213 | buff.append(c) | ||
| 214 | |||
| 215 | lastnumber = 0 | ||
| 216 | |||
| 217 | if len(self.uri_path) > 0: | ||
| 218 | paths = self.uri_path.split("/") | ||
| 219 | for p in paths: | ||
| 220 | if len(p) > 0: | ||
| 221 | if PYTHON_MAJOR >= 3: | ||
| 222 | data = p.encode() | ||
| 223 | else: | ||
| 224 | data = bytearray(p) | ||
| 225 | lastnumber = self.appendOption(buff, lastnumber, COAPOption.URI_PATH, data) | ||
| 226 | |||
| 227 | if self.content_format != None: | ||
| 228 | data = bytearray() | ||
| 229 | fmt_code = self.content_format | ||
| 230 | if fmt_code > 0xFF: | ||
| 231 | data.append((fmt_code & 0xFF00) >> 8) | ||
| 232 | data.append(fmt_code & 0x00FF) | ||
| 233 | lastnumber = self.appendOption(buff, lastnumber, COAPOption.CONTENT_FORMAT, data) | ||
| 234 | |||
| 235 | buff.append(0xFF) | ||
| 236 | |||
| 237 | if self.payload: | ||
| 238 | if PYTHON_MAJOR >= 3: | ||
| 239 | data = self.payload.encode() | ||
| 240 | else: | ||
| 241 | data = bytearray(self.payload) | ||
| 242 | for c in data: | ||
| 243 | buff.append(c) | ||
| 244 | |||
| 245 | return buff | ||
| 246 | |||
| 247 | def parseByteArray(self, buff): | ||
| 248 | self.version = (buff[0] & 0xC0) >> 6 | ||
| 249 | self.type = (buff[0] & 0x30) >> 4 | ||
| 250 | token_length = buff[0] & 0x0F | ||
| 251 | index = 4 | ||
| 252 | if token_length > 0: | ||
| 253 | self.token = buff[index:index+token_length] | ||
| 254 | |||
| 255 | index += token_length | ||
| 256 | self.code = buff[1] | ||
| 257 | self.id = (buff[2] << 8) | buff[3] | ||
| 258 | |||
| 259 | number = 0 | ||
| 260 | |||
| 261 | # process options | ||
| 262 | while index < len(buff) and buff[index] != 0xFF: | ||
| 263 | (delta, length) = self.__getOptionHeader__(buff[index]) | ||
| 264 | offset = 1 | ||
| 265 | |||
| 266 | # delta extended with 1 byte | ||
| 267 | if delta == 13: | ||
| 268 | delta += buff[index+offset] | ||
| 269 | offset += 1 | ||
| 270 | # delta extended with 2 buff | ||
| 271 | elif delta == 14: | ||
| 272 | delta += 255 + ((buff[index+offset] << 8) | buff[index+offset+1]) | ||
| 273 | offset += 2 | ||
| 274 | |||
| 275 | # length extended with 1 byte | ||
| 276 | if length == 13: | ||
| 277 | length += buff[index+offset] | ||
| 278 | offset += 1 | ||
| 279 | |||
| 280 | # length extended with 2 buff | ||
| 281 | elif length == 14: | ||
| 282 | length += 255 + ((buff[index+offset] << 8) | buff[index+offset+1]) | ||
| 283 | offset += 2 | ||
| 284 | |||
| 285 | number += delta | ||
| 286 | valueBytes = buff[index+offset:index+offset+length] | ||
| 287 | # opaque option value | ||
| 288 | if number in [COAPOption.IF_MATCH, COAPOption.ETAG]: | ||
| 289 | value = valueBytes | ||
| 290 | # integer option value | ||
| 291 | elif number in [COAPOption.URI_PORT, COAPOption.CONTENT_FORMAT, COAPOption.MAX_AGE, COAPOption.ACCEPT]: | ||
| 292 | value = 0 | ||
| 293 | for b in valueBytes: | ||
| 294 | value <<= 8 | ||
| 295 | value |= b | ||
| 296 | # string option value | ||
| 297 | else: | ||
| 298 | if PYTHON_MAJOR >= 3: | ||
| 299 | value = valueBytes.decode() | ||
| 300 | else: | ||
| 301 | value = str(valueBytes) | ||
| 302 | self.options.append({'number': number, 'value': value}) | ||
| 303 | index += offset + length | ||
| 304 | |||
| 305 | index += 1 # skip 0xFF / end-of-options | ||
| 306 | |||
| 307 | if len(buff) > index: | ||
| 308 | self.payload = buff[index:] | ||
| 309 | else: | ||
| 310 | self.payload = "" | ||
| 311 | |||
| 312 | for option in self.options: | ||
| 313 | (number, value) = option.values() | ||
| 314 | if number == COAPOption.URI_PATH: | ||
| 315 | self.uri_path += "/%s" % value | ||
| 316 | |||
| 317 | |||
| 318 | class COAPRequest(COAPMessage): | ||
| 319 | CODES = {0: None, | ||
| 320 | 1: "GET", | ||
| 321 | 2: "POST", | ||
| 322 | 3: "PUT", | ||
| 323 | 4: "DELETE" | ||
| 324 | } | ||
| 325 | |||
| 326 | GET = 1 | ||
| 327 | POST = 2 | ||
| 328 | PUT = 3 | ||
| 329 | DELETE = 4 | ||
| 330 | |||
| 331 | def __init__(self, msg_type=0, code=0, uri=None): | ||
| 332 | COAPMessage.__init__(self, msg_type, code, uri) | ||
| 333 | |||
| 334 | class COAPGet(COAPRequest): | ||
| 335 | def __init__(self, uri): | ||
| 336 | COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.GET, uri) | ||
| 337 | |||
| 338 | class COAPPost(COAPRequest): | ||
| 339 | def __init__(self, uri): | ||
| 340 | COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.POST, uri) | ||
| 341 | |||
| 342 | class COAPPut(COAPRequest): | ||
| 343 | def __init__(self, uri): | ||
| 344 | COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.PUT, uri) | ||
| 345 | |||
| 346 | class COAPDelete(COAPRequest): | ||
| 347 | def __init__(self, uri): | ||
| 348 | COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.DELETE, uri) | ||
| 349 | |||
| 350 | class COAPResponse(COAPMessage): | ||
| 351 | CODES = {0: None, | ||
| 352 | 64: "2.00 OK", | ||
| 353 | 65: "2.01 Created", | ||
| 354 | 66: "2.02 Deleted", | ||
| 355 | 67: "2.03 Valid", | ||
| 356 | 68: "2.04 Changed", | ||
| 357 | 69: "2.05 Content", | ||
| 358 | 128: "4.00 Bad Request", | ||
| 359 | 129: "4.01 Unauthorized", | ||
| 360 | 130: "4.02 Bad Option", | ||
| 361 | 131: "4.03 Forbidden", | ||
| 362 | 132: "4.04 Not Found", | ||
| 363 | 133: "4.05 Method Not Allowed", | ||
| 364 | 134: "4.06 Not Acceptable", | ||
| 365 | 140: "4.12 Precondition Failed", | ||
| 366 | 141: "4.13 Request Entity Too Large", | ||
| 367 | 143: "4.15 Unsupported Content-Format", | ||
| 368 | 160: "5.00 Internal Server Error", | ||
| 369 | 161: "5.01 Not Implemented", | ||
| 370 | 162: "5.02 Bad Gateway", | ||
| 371 | 163: "5.03 Service Unavailable", | ||
| 372 | 164: "5.04 Gateway Timeout", | ||
| 373 | 165: "5.05 Proxying Not Supported" | ||
| 374 | } | ||
| 375 | |||
| 376 | # 2.XX | ||
| 377 | OK = 64 | ||
| 378 | CREATED = 65 | ||
| 379 | DELETED = 66 | ||
| 380 | VALID = 67 | ||
| 381 | CHANGED = 68 | ||
| 382 | CONTENT = 69 | ||
| 383 | |||
| 384 | # 4.XX | ||
| 385 | BAD_REQUEST = 128 | ||
| 386 | UNAUTHORIZED = 129 | ||
| 387 | BAD_OPTION = 130 | ||
| 388 | FORBIDDEN = 131 | ||
| 389 | NOT_FOUND = 132 | ||
| 390 | NOT_ALLOWED = 133 | ||
| 391 | NOT_ACCEPTABLE = 134 | ||
| 392 | PRECONDITION_FAILED = 140 | ||
| 393 | ENTITY_TOO_LARGE = 141 | ||
| 394 | UNSUPPORTED_CONTENT = 143 | ||
| 395 | |||
| 396 | # 5.XX | ||
| 397 | INTERNAL_ERROR = 160 | ||
| 398 | NOT_IMPLEMENTED = 161 | ||
| 399 | BAD_GATEWAY = 162 | ||
| 400 | SERVICE_UNAVAILABLE = 163 | ||
| 401 | GATEWAY_TIMEOUT = 164 | ||
| 402 | PROXYING_NOT_SUPPORTED = 165 | ||
| 403 | |||
| 404 | def __init__(self): | ||
| 405 | COAPMessage.__init__(self) | ||
| 406 | |||
| 407 | class COAPClient(): | ||
| 408 | def __init__(self): | ||
| 409 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
| 410 | self.socket.settimeout(1.0) | ||
| 411 | self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL, 2) | ||
| 412 | |||
| 413 | def sendRequest(self, message): | ||
| 414 | data = message.getBytes(); | ||
| 415 | sent = 0 | ||
| 416 | while sent<4: | ||
| 417 | try: | ||
| 418 | self.socket.sendto(data, (message.host, message.port)) | ||
| 419 | data = self.socket.recv(1500) | ||
| 420 | response = COAPResponse() | ||
| 421 | response.parseByteArray(bytearray(data)) | ||
| 422 | return response | ||
| 423 | except socket.timeout: | ||
| 424 | sent+=1 | ||
| 425 | return None | ||
| 426 | |||
| 427 | class COAPServer(threading.Thread): | ||
| 428 | logger = logging.getLogger("CoAP") | ||
| 429 | |||
| 430 | def __init__(self, host, port, handler): | ||
| 431 | threading.Thread.__init__(self, name="COAPThread") | ||
| 432 | self.handler = COAPHandler(handler) | ||
| 433 | self.host = host | ||
| 434 | self.port = port | ||
| 435 | self.multicast_ip = '224.0.1.123' | ||
| 436 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
| 437 | self.socket.bind(('', port)) | ||
| 438 | self.socket.settimeout(1) | ||
| 439 | self.running = True | ||
| 440 | self.start() | ||
| 441 | |||
| 442 | def run(self): | ||
| 443 | info("CoAP Server binded on coap://%s:%s/" % (self.host, self.port)) | ||
| 444 | while self.running == True: | ||
| 445 | try: | ||
| 446 | (request, client) = self.socket.recvfrom(1500) | ||
| 447 | requestBytes = bytearray(request) | ||
| 448 | coapRequest = COAPRequest() | ||
| 449 | coapRequest.parseByteArray(requestBytes) | ||
| 450 | coapResponse = COAPResponse() | ||
| 451 | #self.logger.debug("Received Request:\n%s" % coapRequest) | ||
| 452 | self.processMessage(coapRequest, coapResponse) | ||
| 453 | #self.logger.debug("Sending Response:\n%s" % coapResponse) | ||
| 454 | responseBytes = coapResponse.getBytes() | ||
| 455 | self.socket.sendto(responseBytes, client) | ||
| 456 | self.logger.debug('"%s %s CoAP/%.1f" %s -' % (coapRequest.CODES[coapRequest.code], coapRequest.uri_path, coapRequest.version, coapResponse.CODES[coapResponse.code])) | ||
| 457 | |||
| 458 | except socket.timeout as e: | ||
| 459 | continue | ||
| 460 | except Exception as e: | ||
| 461 | if self.running == True: | ||
| 462 | exception(e) | ||
| 463 | |||
| 464 | info("CoAP Server stopped") | ||
| 465 | |||
| 466 | def enableMulticast(self): | ||
| 467 | while not self.running: | ||
| 468 | pass | ||
| 469 | mreq = struct.pack("4sl", socket.inet_aton(self.multicast_ip), socket.INADDR_ANY) | ||
| 470 | self.socket.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq) | ||
| 471 | info("CoAP Server binded on coap://%s:%s/ (MULTICAST)" % (self.multicast_ip, self.port)) | ||
| 472 | |||
| 473 | def stop(self): | ||
| 474 | self.running = False | ||
| 475 | self.socket.close() | ||
| 476 | |||
| 477 | def processMessage(self, request, response): | ||
| 478 | if request.type == COAPMessage.CON: | ||
| 479 | response.type = COAPMessage.ACK | ||
| 480 | else: | ||
| 481 | response.type = COAPMessage.NON | ||
| 482 | |||
| 483 | if request.token: | ||
| 484 | response.token = request.token | ||
| 485 | |||
| 486 | response.id = request.id | ||
| 487 | response.uri_path = request.uri_path | ||
| 488 | |||
| 489 | if request.code == COAPRequest.GET: | ||
| 490 | self.handler.do_GET(request, response) | ||
| 491 | elif request.code == COAPRequest.POST: | ||
| 492 | self.handler.do_POST(request, response) | ||
| 493 | elif request.code / 32 == 0: | ||
| 494 | response.code = COAPResponse.NOT_IMPLEMENTED | ||
| 495 | else: | ||
| 496 | exception(Exception("Received CoAP Response : %s" % response)) | ||
| 497 | |||
| 498 | class COAPHandler(): | ||
| 499 | def __init__(self, handler): | ||
| 500 | self.handler = handler | ||
| 501 | |||
| 502 | def do_GET(self, request, response): | ||
| 503 | try: | ||
| 504 | (code, body, contentType) = self.handler.do_GET(request.uri_path[1:], True) | ||
| 505 | if code == 0: | ||
| 506 | response.code = COAPResponse.NOT_FOUND | ||
| 507 | elif code == 200: | ||
| 508 | response.code = COAPResponse.CONTENT | ||
| 509 | else: | ||
| 510 | response.code = HTTPCode2CoAPCode(code) | ||
| 511 | response.payload = body | ||
| 512 | response.content_format = COAPContentFormat.getCode(contentType) | ||
| 513 | except (GPIO.InvalidDirectionException, GPIO.InvalidChannelException, GPIO.SetupException) as e: | ||
| 514 | response.code = COAPResponse.FORBIDDEN | ||
| 515 | response.payload = "%s" % e | ||
| 516 | except Exception as e: | ||
| 517 | response.code = COAPResponse.INTERNAL_ERROR | ||
| 518 | raise e | ||
| 519 | |||
| 520 | def do_POST(self, request, response): | ||
| 521 | try: | ||
| 522 | (code, body, contentType) = self.handler.do_POST(request.uri_path[1:], request.payload, True) | ||
| 523 | if code == 0: | ||
| 524 | response.code = COAPResponse.NOT_FOUND | ||
| 525 | elif code == 200: | ||
| 526 | response.code = COAPResponse.CHANGED | ||
| 527 | else: | ||
| 528 | response.code = HTTPCode2CoAPCode(code) | ||
| 529 | response.payload = body | ||
| 530 | response.content_format = COAPContentFormat.getCode(contentType) | ||
| 531 | except (GPIO.InvalidDirectionException, GPIO.InvalidChannelException, GPIO.SetupException) as e: | ||
| 532 | response.code = COAPResponse.FORBIDDEN | ||
| 533 | response.payload = "%s" % e | ||
| 534 | except Exception as e: | ||
| 535 | response.code = COAPResponse.INTERNAL_ERROR | ||
| 536 | raise e | ||
| 537 | |||
diff --git a/python/webiopi/protocols/http.py b/python/webiopi/protocols/http.py new file mode 100644 index 0000000..aea6d82 --- /dev/null +++ b/python/webiopi/protocols/http.py | |||
| @@ -0,0 +1,249 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import os | ||
| 16 | import threading | ||
| 17 | import codecs | ||
| 18 | import mimetypes as mime | ||
| 19 | import logging | ||
| 20 | |||
| 21 | from webiopi.utils.version import VERSION_STRING, PYTHON_MAJOR | ||
| 22 | from webiopi.utils.logger import info, exception | ||
| 23 | from webiopi.utils.crypto import encrypt | ||
| 24 | from webiopi.utils.types import str2bool | ||
| 25 | |||
| 26 | if PYTHON_MAJOR >= 3: | ||
| 27 | import http.server as BaseHTTPServer | ||
| 28 | else: | ||
| 29 | import BaseHTTPServer | ||
| 30 | |||
| 31 | try : | ||
| 32 | import _webiopi.GPIO as GPIO | ||
| 33 | except: | ||
| 34 | pass | ||
| 35 | |||
| 36 | WEBIOPI_DOCROOT = "/usr/share/webiopi/htdocs" | ||
| 37 | |||
| 38 | class HTTPServer(BaseHTTPServer.HTTPServer, threading.Thread): | ||
| 39 | def __init__(self, host, port, handler, context, docroot, index, auth=None): | ||
| 40 | BaseHTTPServer.HTTPServer.__init__(self, ("", port), HTTPHandler) | ||
| 41 | threading.Thread.__init__(self, name="HTTPThread") | ||
| 42 | self.host = host | ||
| 43 | self.port = port | ||
| 44 | |||
| 45 | if context: | ||
| 46 | self.context = context | ||
| 47 | if not self.context.startswith("/"): | ||
| 48 | self.context = "/" + self.context | ||
| 49 | if not self.context.endswith("/"): | ||
| 50 | self.context += "/" | ||
| 51 | else: | ||
| 52 | self.context = "/" | ||
| 53 | |||
| 54 | self.docroot = docroot | ||
| 55 | |||
| 56 | if index: | ||
| 57 | self.index = index | ||
| 58 | else: | ||
| 59 | self.index = "index.html" | ||
| 60 | |||
| 61 | self.handler = handler | ||
| 62 | self.auth = auth | ||
| 63 | |||
| 64 | self.running = True | ||
| 65 | self.start() | ||
| 66 | |||
| 67 | def get_request(self): | ||
| 68 | sock, addr = self.socket.accept() | ||
| 69 | sock.settimeout(10.0) | ||
| 70 | return (sock, addr) | ||
| 71 | |||
| 72 | def run(self): | ||
| 73 | info("HTTP Server binded on http://%s:%s%s" % (self.host, self.port, self.context)) | ||
| 74 | try: | ||
| 75 | self.serve_forever() | ||
| 76 | except Exception as e: | ||
| 77 | if self.running == True: | ||
| 78 | exception(e) | ||
| 79 | info("HTTP Server stopped") | ||
| 80 | |||
| 81 | def stop(self): | ||
| 82 | self.running = False | ||
| 83 | self.server_close() | ||
| 84 | |||
| 85 | class HTTPHandler(BaseHTTPServer.BaseHTTPRequestHandler): | ||
| 86 | logger = logging.getLogger("HTTP") | ||
| 87 | |||
| 88 | def log_message(self, fmt, *args): | ||
| 89 | self.logger.debug(fmt % args) | ||
| 90 | |||
| 91 | def log_error(self, fmt, *args): | ||
| 92 | pass | ||
| 93 | |||
| 94 | def version_string(self): | ||
| 95 | return VERSION_STRING | ||
| 96 | |||
| 97 | def checkAuthentication(self): | ||
| 98 | if self.server.auth == None or len(self.server.auth) == 0: | ||
| 99 | return True | ||
| 100 | |||
| 101 | authHeader = self.headers.get('Authorization') | ||
| 102 | if authHeader == None: | ||
| 103 | return False | ||
| 104 | |||
| 105 | if not authHeader.startswith("Basic "): | ||
| 106 | return False | ||
| 107 | |||
| 108 | auth = authHeader.replace("Basic ", "") | ||
| 109 | if PYTHON_MAJOR >= 3: | ||
| 110 | auth_hash = encrypt(auth.encode()) | ||
| 111 | else: | ||
| 112 | auth_hash = encrypt(auth) | ||
| 113 | |||
| 114 | if auth_hash == self.server.auth: | ||
| 115 | return True | ||
| 116 | return False | ||
| 117 | |||
| 118 | def requestAuthentication(self): | ||
| 119 | self.send_response(401) | ||
| 120 | self.send_header("WWW-Authenticate", 'Basic realm="webiopi"') | ||
| 121 | self.end_headers(); | ||
| 122 | |||
| 123 | def sendResponse(self, code, body=None, contentType="text/plain"): | ||
| 124 | if code >= 400: | ||
| 125 | if body != None: | ||
| 126 | self.send_error(code, body) | ||
| 127 | else: | ||
| 128 | self.send_error(code) | ||
| 129 | else: | ||
| 130 | self.send_response(code) | ||
| 131 | self.send_header("Cache-Control", "no-cache") | ||
| 132 | if body != None: | ||
| 133 | self.send_header("Content-Type", contentType); | ||
| 134 | self.end_headers(); | ||
| 135 | self.wfile.write(body.encode()) | ||
| 136 | |||
| 137 | def findFile(self, filepath): | ||
| 138 | if os.path.exists(filepath): | ||
| 139 | if os.path.isdir(filepath): | ||
| 140 | filepath += "/" + self.server.index | ||
| 141 | if os.path.exists(filepath): | ||
| 142 | return filepath | ||
| 143 | else: | ||
| 144 | return filepath | ||
| 145 | return None | ||
| 146 | |||
| 147 | |||
| 148 | def serveFile(self, relativePath): | ||
| 149 | if self.server.docroot != None: | ||
| 150 | path = self.findFile(self.server.docroot + "/" + relativePath) | ||
| 151 | if path == None: | ||
| 152 | path = self.findFile("./" + relativePath) | ||
| 153 | |||
| 154 | else: | ||
| 155 | path = self.findFile("./" + relativePath) | ||
| 156 | if path == None: | ||
| 157 | path = self.findFile(WEBIOPI_DOCROOT + "/" + relativePath) | ||
| 158 | |||
| 159 | if path == None and (relativePath.startswith("webiopi.") or relativePath.startswith("jquery")): | ||
| 160 | path = self.findFile(WEBIOPI_DOCROOT + "/" + relativePath) | ||
| 161 | |||
| 162 | if path == None: | ||
| 163 | return self.sendResponse(404, "Not Found") | ||
| 164 | |||
| 165 | realPath = os.path.realpath(path) | ||
| 166 | |||
| 167 | if realPath.endswith(".py"): | ||
| 168 | return self.sendResponse(403, "Not Authorized") | ||
| 169 | |||
| 170 | if not (realPath.startswith(os.getcwd()) | ||
| 171 | or (self.server.docroot and realPath.startswith(self.server.docroot)) | ||
| 172 | or realPath.startswith(WEBIOPI_DOCROOT)): | ||
| 173 | return self.sendResponse(403, "Not Authorized") | ||
| 174 | |||
| 175 | (contentType, encoding) = mime.guess_type(path) | ||
| 176 | f = codecs.open(path, encoding=encoding) | ||
| 177 | data = f.read() | ||
| 178 | f.close() | ||
| 179 | self.send_response(200) | ||
| 180 | self.send_header("Content-Type", contentType); | ||
| 181 | self.send_header("Content-Length", os.path.getsize(realPath)) | ||
| 182 | self.end_headers() | ||
| 183 | self.wfile.write(data) | ||
| 184 | |||
| 185 | def processRequest(self): | ||
| 186 | self.request.settimeout(None) | ||
| 187 | if not self.checkAuthentication(): | ||
| 188 | return self.requestAuthentication() | ||
| 189 | |||
| 190 | request = self.path.replace(self.server.context, "/").split('?') | ||
| 191 | relativePath = request[0] | ||
| 192 | if relativePath[0] == "/": | ||
| 193 | relativePath = relativePath[1:] | ||
| 194 | |||
| 195 | if relativePath == "webiopi" or relativePath == "webiopi/": | ||
| 196 | self.send_response(301) | ||
| 197 | self.send_header("Location", "/") | ||
| 198 | self.end_headers() | ||
| 199 | return | ||
| 200 | |||
| 201 | params = {} | ||
| 202 | if len(request) > 1: | ||
| 203 | for s in request[1].split('&'): | ||
| 204 | if s.find('=') > 0: | ||
| 205 | (name, value) = s.split('=') | ||
| 206 | params[name] = value | ||
| 207 | else: | ||
| 208 | params[s] = None | ||
| 209 | |||
| 210 | compact = False | ||
| 211 | if 'compact' in params: | ||
| 212 | compact = str2bool(params['compact']) | ||
| 213 | |||
| 214 | try: | ||
| 215 | result = (None, None, None) | ||
| 216 | if self.command == "GET": | ||
| 217 | result = self.server.handler.do_GET(relativePath, compact) | ||
| 218 | elif self.command == "POST": | ||
| 219 | length = 0 | ||
| 220 | length_header = 'content-length' | ||
| 221 | if length_header in self.headers: | ||
| 222 | length = int(self.headers[length_header]) | ||
| 223 | result = self.server.handler.do_POST(relativePath, self.rfile.read(length), compact) | ||
| 224 | else: | ||
| 225 | result = (405, None, None) | ||
| 226 | |||
| 227 | (code, body, contentType) = result | ||
| 228 | |||
| 229 | if code > 0: | ||
| 230 | self.sendResponse(code, body, contentType) | ||
| 231 | else: | ||
| 232 | if self.command == "GET": | ||
| 233 | self.serveFile(relativePath) | ||
| 234 | else: | ||
| 235 | self.sendResponse(404) | ||
| 236 | |||
| 237 | except (GPIO.InvalidDirectionException, GPIO.InvalidChannelException, GPIO.SetupException) as e: | ||
| 238 | self.sendResponse(403, "%s" % e) | ||
| 239 | except ValueError as e: | ||
| 240 | self.sendResponse(403, "%s" % e) | ||
| 241 | except Exception as e: | ||
| 242 | self.sendResponse(500) | ||
| 243 | raise e | ||
| 244 | |||
| 245 | def do_GET(self): | ||
| 246 | self.processRequest() | ||
| 247 | |||
| 248 | def do_POST(self): | ||
| 249 | self.processRequest() | ||
diff --git a/python/webiopi/protocols/rest.py b/python/webiopi/protocols/rest.py new file mode 100644 index 0000000..6010604 --- /dev/null +++ b/python/webiopi/protocols/rest.py | |||
| @@ -0,0 +1,254 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | from webiopi.utils import types | ||
| 16 | from webiopi.utils import logger | ||
| 17 | from webiopi.utils.types import M_JSON, M_PLAIN | ||
| 18 | from webiopi.utils.version import BOARD_REVISION, VERSION_STRING, MAPPING | ||
| 19 | from webiopi.devices import manager | ||
| 20 | from webiopi.devices import instance | ||
| 21 | from webiopi.devices.bus import BUSLIST | ||
| 22 | |||
| 23 | try: | ||
| 24 | import _webiopi.GPIO as GPIO | ||
| 25 | except: | ||
| 26 | pass | ||
| 27 | |||
| 28 | MACROS = {} | ||
| 29 | |||
| 30 | class RESTHandler(): | ||
| 31 | def __init__(self): | ||
| 32 | self.device_mapping = True | ||
| 33 | self.export = [] | ||
| 34 | self.routes = {} | ||
| 35 | self.macros = {} | ||
| 36 | |||
| 37 | |||
| 38 | def addMacro(self, macro): | ||
| 39 | self.macros[macro.__name__] = macro | ||
| 40 | |||
| 41 | def addRoute(self, source, destination): | ||
| 42 | if source[0] == "/": | ||
| 43 | source = source[1:] | ||
| 44 | if destination[0] == "/": | ||
| 45 | destination = destination[1:] | ||
| 46 | self.routes[source] = destination | ||
| 47 | logger.info("Added Route /%s => /%s" % (source, destination)) | ||
| 48 | |||
| 49 | def findRoute(self, path): | ||
| 50 | for source in self.routes: | ||
| 51 | if path.startswith(source): | ||
| 52 | route = path.replace(source, self.routes[source]) | ||
| 53 | logger.info("Routing /%s => /%s" % (path, route)) | ||
| 54 | return route | ||
| 55 | return path | ||
| 56 | |||
| 57 | def extract(self, fmtArray, pathArray, args): | ||
| 58 | if len(fmtArray) != len(pathArray): | ||
| 59 | return False | ||
| 60 | if len(fmtArray) == 0: | ||
| 61 | return True | ||
| 62 | fmt = fmtArray[0] | ||
| 63 | path = pathArray[0] | ||
| 64 | if fmt == path: | ||
| 65 | return self.extract(fmtArray[1:], pathArray[1:], args) | ||
| 66 | if fmt.startswith("%"): | ||
| 67 | |||
| 68 | fmt = fmt[1:] | ||
| 69 | t = 's' | ||
| 70 | if fmt[0] == '(': | ||
| 71 | if fmt[-1] == ')': | ||
| 72 | name = fmt[1:-1] | ||
| 73 | elif fmt[-2] == ')': | ||
| 74 | name = fmt[1:-2] | ||
| 75 | t = fmt[-1] | ||
| 76 | else: | ||
| 77 | raise Exception("Missing closing brace") | ||
| 78 | else: | ||
| 79 | name = fmt | ||
| 80 | |||
| 81 | if t == 's': | ||
| 82 | args[name] = path | ||
| 83 | elif t == 'b': | ||
| 84 | args[name] = types.str2bool(path) | ||
| 85 | elif t == 'd': | ||
| 86 | args[name] = types.toint(path) | ||
| 87 | elif t == 'x': | ||
| 88 | args[name] = int(path, 16) | ||
| 89 | elif t == 'f': | ||
| 90 | args[name] = float(path) | ||
| 91 | else: | ||
| 92 | raise Exception("Unknown format type : %s" % t) | ||
| 93 | |||
| 94 | return self.extract(fmtArray[1:], pathArray[1:], args) | ||
| 95 | |||
| 96 | return False | ||
| 97 | |||
| 98 | def getDeviceRoute(self, method, path): | ||
| 99 | pathArray = path.split("/") | ||
| 100 | deviceName = pathArray[0] | ||
| 101 | device = instance.DEVICES[deviceName] | ||
| 102 | if device == None: | ||
| 103 | return (None, deviceName + " Not Found") | ||
| 104 | pathArray = pathArray[1:] | ||
| 105 | funcs = device["functions"][method] | ||
| 106 | functionName = "/".join(pathArray) | ||
| 107 | if functionName in funcs: | ||
| 108 | return (funcs[functionName], {}) | ||
| 109 | |||
| 110 | for fname in funcs: | ||
| 111 | func = funcs[fname] | ||
| 112 | funcPathArray = func.path.split("/") | ||
| 113 | args = {} | ||
| 114 | if self.extract(funcPathArray, pathArray, args): | ||
| 115 | return (func, args) | ||
| 116 | |||
| 117 | return (None, functionName + " Not Found") | ||
| 118 | |||
| 119 | def callDeviceFunction(self, method, path, data=None): | ||
| 120 | (func, args) = self.getDeviceRoute(method, path) | ||
| 121 | if func == None: | ||
| 122 | return (404, args, M_PLAIN) | ||
| 123 | |||
| 124 | if func.data != None: | ||
| 125 | args[func.data] = data | ||
| 126 | |||
| 127 | result = func(**args) | ||
| 128 | response = None | ||
| 129 | contentType = None | ||
| 130 | if result != None: | ||
| 131 | if hasattr(func, "contentType"): | ||
| 132 | contentType = func.contentType | ||
| 133 | if contentType == M_JSON: | ||
| 134 | response = types.jsonDumps(result) | ||
| 135 | else: | ||
| 136 | response = func.format % result | ||
| 137 | else: | ||
| 138 | response = result | ||
| 139 | |||
| 140 | return (200, response, contentType) | ||
| 141 | |||
| 142 | def do_GET(self, relativePath, compact=False): | ||
| 143 | relativePath = self.findRoute(relativePath) | ||
| 144 | |||
| 145 | # JSON full state | ||
| 146 | if relativePath == "*": | ||
| 147 | return (200, self.getJSON(compact), M_JSON) | ||
| 148 | |||
| 149 | # RPi header map | ||
| 150 | elif relativePath == "map": | ||
| 151 | json = "%s" % MAPPING | ||
| 152 | json = json.replace("'", '"') | ||
| 153 | return (200, json, M_JSON) | ||
| 154 | |||
| 155 | # server version | ||
| 156 | elif relativePath == "version": | ||
| 157 | return (200, VERSION_STRING, M_PLAIN) | ||
| 158 | |||
| 159 | # board revision | ||
| 160 | elif relativePath == "revision": | ||
| 161 | revision = "%s" % BOARD_REVISION | ||
| 162 | return (200, revision, M_PLAIN) | ||
| 163 | |||
| 164 | # Single GPIO getter | ||
| 165 | elif relativePath.startswith("GPIO/"): | ||
| 166 | return self.callDeviceFunction("GET", relativePath) | ||
| 167 | |||
| 168 | elif relativePath == "devices/*": | ||
| 169 | return (200, manager.getDevicesJSON(compact), M_JSON) | ||
| 170 | |||
| 171 | elif relativePath.startswith("devices/"): | ||
| 172 | if not self.device_mapping: | ||
| 173 | return (404, None, None) | ||
| 174 | path = relativePath.replace("devices/", "") | ||
| 175 | return self.callDeviceFunction("GET", path) | ||
| 176 | |||
| 177 | else: | ||
| 178 | return (0, None, None) | ||
| 179 | |||
| 180 | def do_POST(self, relativePath, data, compact=False): | ||
| 181 | relativePath = self.findRoute(relativePath) | ||
| 182 | |||
| 183 | if relativePath.startswith("GPIO/"): | ||
| 184 | return self.callDeviceFunction("POST", relativePath) | ||
| 185 | |||
| 186 | elif relativePath.startswith("macros/"): | ||
| 187 | paths = relativePath.split("/") | ||
| 188 | mname = paths[1] | ||
| 189 | if len(paths) > 2: | ||
| 190 | value = paths[2] | ||
| 191 | else: | ||
| 192 | value = "" | ||
| 193 | |||
| 194 | if mname in self.macros: | ||
| 195 | macro = self.macros[mname] | ||
| 196 | |||
| 197 | if ',' in value: | ||
| 198 | args = value.split(',') | ||
| 199 | result = macro(*args) | ||
| 200 | elif len(value) > 0: | ||
| 201 | result = macro(value) | ||
| 202 | else: | ||
| 203 | result = macro() | ||
| 204 | |||
| 205 | response = "" | ||
| 206 | if result: | ||
| 207 | response = "%s" % result | ||
| 208 | return (200, response, M_PLAIN) | ||
| 209 | |||
| 210 | else: | ||
| 211 | return (404, mname + " Not Found", M_PLAIN) | ||
| 212 | |||
| 213 | elif relativePath.startswith("devices/"): | ||
| 214 | if not self.device_mapping: | ||
| 215 | return (404, None, None) | ||
| 216 | path = relativePath.replace("devices/", "") | ||
| 217 | return self.callDeviceFunction("POST", path, data) | ||
| 218 | |||
| 219 | else: # path unknowns | ||
| 220 | return (0, None, None) | ||
| 221 | |||
| 222 | def getJSON(self, compact=False): | ||
| 223 | if compact: | ||
| 224 | f = 'f' | ||
| 225 | v = 'v' | ||
| 226 | else: | ||
| 227 | f = 'function' | ||
| 228 | v = 'value' | ||
| 229 | |||
| 230 | json = {} | ||
| 231 | for (bus, value) in BUSLIST.items(): | ||
| 232 | json[bus] = int(value["enabled"]) | ||
| 233 | |||
| 234 | gpios = {} | ||
| 235 | if len(self.export) > 0: | ||
| 236 | export = self.export | ||
| 237 | else: | ||
| 238 | export = range(GPIO.GPIO_COUNT) | ||
| 239 | |||
| 240 | for gpio in export: | ||
| 241 | gpios[gpio] = {} | ||
| 242 | if compact: | ||
| 243 | gpios[gpio][f] = GPIO.getFunction(gpio) | ||
| 244 | else: | ||
| 245 | gpios[gpio][f] = GPIO.getFunctionString(gpio) | ||
| 246 | gpios[gpio][v] = int(GPIO.input(gpio)) | ||
| 247 | |||
| 248 | if GPIO.getFunction(gpio) == GPIO.PWM: | ||
| 249 | (pwmType, value) = GPIO.getPulse(gpio).split(':') | ||
| 250 | gpios[gpio][pwmType] = value | ||
| 251 | |||
| 252 | json['GPIO'] = gpios | ||
| 253 | return types.jsonDumps(json) | ||
| 254 | |||
diff --git a/python/webiopi/server/__init__.py b/python/webiopi/server/__init__.py new file mode 100644 index 0000000..68fdbe6 --- /dev/null +++ b/python/webiopi/server/__init__.py | |||
| @@ -0,0 +1,139 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | import os | ||
| 16 | import time | ||
| 17 | import socket | ||
| 18 | |||
| 19 | from webiopi.utils.config import Config | ||
| 20 | from webiopi.utils import loader | ||
| 21 | from webiopi.utils import logger | ||
| 22 | from webiopi.utils import crypto | ||
| 23 | from webiopi.devices import manager | ||
| 24 | from webiopi.protocols import rest | ||
| 25 | from webiopi.protocols import http | ||
| 26 | from webiopi.protocols import coap | ||
| 27 | from webiopi.devices.digital.gpio import NativeGPIO | ||
| 28 | |||
| 29 | def getLocalIP(): | ||
| 30 | s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | ||
| 31 | try: | ||
| 32 | s.connect(('8.8.8.8', 53)) | ||
| 33 | host = s.getsockname()[0] | ||
| 34 | s.close() | ||
| 35 | return host | ||
| 36 | except: | ||
| 37 | return "localhost" | ||
| 38 | |||
| 39 | class Server(): | ||
| 40 | def __init__(self, port=8000, coap_port=5683, login=None, password=None, passwdfile=None, configfile=None): | ||
| 41 | self.host = getLocalIP() | ||
| 42 | self.gpio = NativeGPIO() | ||
| 43 | self.restHandler = rest.RESTHandler() | ||
| 44 | manager.addDeviceInstance("GPIO", self.gpio, []) | ||
| 45 | |||
| 46 | if configfile != None: | ||
| 47 | logger.info("Loading configuration from %s" % configfile) | ||
| 48 | config = Config(configfile) | ||
| 49 | else: | ||
| 50 | config = Config() | ||
| 51 | |||
| 52 | self.gpio.addSetups(config.items("GPIO")) | ||
| 53 | self.gpio.addResets(config.items("~GPIO")) | ||
| 54 | self.gpio.setup() | ||
| 55 | |||
| 56 | devices = config.items("DEVICES") | ||
| 57 | for (name, params) in devices: | ||
| 58 | values = params.split(" ") | ||
| 59 | driver = values[0]; | ||
| 60 | args = {} | ||
| 61 | i = 1 | ||
| 62 | while i < len(values): | ||
| 63 | (arg, val) = values[i].split(":") | ||
| 64 | args[arg] = val | ||
| 65 | i+=1 | ||
| 66 | manager.addDevice(name, driver, args) | ||
| 67 | |||
| 68 | scripts = config.items("SCRIPTS") | ||
| 69 | for (name, source) in scripts: | ||
| 70 | loader.loadScript(name, source, self.restHandler) | ||
| 71 | |||
| 72 | self.restHandler.device_mapping = config.getboolean("REST", "device-mapping", True) | ||
| 73 | self.gpio.post_value = config.getboolean("REST", "gpio-post-value", True) | ||
| 74 | self.gpio.post_function = config.getboolean("REST", "gpio-post-function", True) | ||
| 75 | exports = config.get("REST", "gpio-export", None) | ||
| 76 | if exports != None: | ||
| 77 | self.gpio.export = [int(s) for s in exports.split(",")] | ||
| 78 | self.restHandler.export = self.gpio.export | ||
| 79 | |||
| 80 | http_port = config.getint("HTTP", "port", port) | ||
| 81 | http_enabled = config.getboolean("HTTP", "enabled", http_port > 0) | ||
| 82 | http_passwdfile = config.get("HTTP", "passwd-file", passwdfile) | ||
| 83 | context = config.get("HTTP", "context", None) | ||
| 84 | docroot = config.get("HTTP", "doc-root", None) | ||
| 85 | index = config.get("HTTP", "welcome-file", None) | ||
| 86 | |||
| 87 | coap_port = config.getint("COAP", "port", coap_port) | ||
| 88 | coap_enabled = config.getboolean("COAP", "enabled", coap_port > 0) | ||
| 89 | coap_multicast = config.getboolean("COAP", "multicast", coap_enabled) | ||
| 90 | |||
| 91 | routes = config.items("ROUTES") | ||
| 92 | for (source, destination) in routes: | ||
| 93 | self.restHandler.addRoute(source, destination) | ||
| 94 | |||
| 95 | auth = None | ||
| 96 | if http_passwdfile != None: | ||
| 97 | if os.path.exists(http_passwdfile): | ||
| 98 | f = open(http_passwdfile) | ||
| 99 | auth = f.read().strip(" \r\n") | ||
| 100 | f.close() | ||
| 101 | if len(auth) > 0: | ||
| 102 | logger.info("Access protected using %s" % http_passwdfile) | ||
| 103 | else: | ||
| 104 | logger.info("Passwd file %s is empty" % http_passwdfile) | ||
| 105 | else: | ||
| 106 | logger.error("Passwd file %s not found" % http_passwdfile) | ||
| 107 | |||
| 108 | elif login != None or password != None: | ||
| 109 | auth = crypto.encryptCredentials(login, password) | ||
| 110 | logger.info("Access protected using login/password") | ||
| 111 | |||
| 112 | if auth == None or len(auth) == 0: | ||
| 113 | logger.warn("Access unprotected") | ||
| 114 | |||
| 115 | if http_enabled: | ||
| 116 | self.http_server = http.HTTPServer(self.host, http_port, self.restHandler, context, docroot, index, auth) | ||
| 117 | else: | ||
| 118 | self.http_server = None | ||
| 119 | |||
| 120 | if coap_enabled: | ||
| 121 | self.coap_server = coap.COAPServer(self.host, coap_port, self.restHandler) | ||
| 122 | if coap_multicast: | ||
| 123 | self.coap_server.enableMulticast() | ||
| 124 | else: | ||
| 125 | self.coap_server = None | ||
| 126 | |||
| 127 | def addMacro(self, macro): | ||
| 128 | self.restHandler.addMacro(macro) | ||
| 129 | |||
| 130 | def stop(self): | ||
| 131 | if self.http_server: | ||
| 132 | self.http_server.stop() | ||
| 133 | if self.coap_server: | ||
| 134 | self.coap_server.stop() | ||
| 135 | loader.unloadScripts() | ||
| 136 | manager.closeDevices() | ||
| 137 | |||
| 138 | |||
| 139 | |||
diff --git a/python/webiopi/utils/__init__.py b/python/webiopi/utils/__init__.py new file mode 100644 index 0000000..ad86c93 --- /dev/null +++ b/python/webiopi/utils/__init__.py | |||
| @@ -0,0 +1,16 @@ | |||
| 1 | # Copyright 2012-2013 Eric Ptak - trouch.com | ||
| 2 | # | ||
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | # you may not use this file except in compliance with the License. | ||
| 5 | # You may obtain a copy of the License at | ||
| 6 | # | ||
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | # | ||
| 9 | # Unless required by applicable law or agreed to in writing, software | ||
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | # See the License for the specific language governing permissions and | ||
| 13 | # limitations under the License. | ||
| 14 | |||
| 15 | |||
| 16 | |||
diff --git a/python/webiopi/utils/config.py b/python/webiopi/utils/config.py new file mode 100644 index 0000000..72302e5 --- /dev/null +++ b/python/webiopi/utils/config.py | |||
| @@ -0,0 +1,35 @@ | |||
| 1 | from webiopi.utils.version import PYTHON_MAJOR | ||
| 2 | |||
| 3 | if PYTHON_MAJOR >= 3: | ||
| 4 | import configparser as parser | ||
| 5 | else: | ||
| 6 | import ConfigParser as parser | ||
| 7 | |||
| 8 | class Config(): | ||
| 9 | |||
| 10 | def __init__(self, configfile=None): | ||
| 11 | config = parser.ConfigParser() | ||
| 12 | config.optionxform = str | ||
| 13 | if configfile != None: | ||
| 14 | config.read(configfile) | ||
| 15 | self.config = config | ||
| 16 | |||
| 17 | def get(self, section, key, default): | ||
| 18 | if self.config.has_option(section, key): | ||
| 19 | return self.config.get(section, key) | ||
| 20 | return default | ||
| 21 | |||
| 22 | def getboolean(self, section, key, default): | ||
| 23 | if self.config.has_option(section, key): | ||
| 24 | return self.config.getboolean(section, key) | ||
| 25 | return default | ||
| 26 | |||
| 27 | def getint(self, section, key, default): | ||
| 28 | if self.config.has_option(section, key): | ||
| 29 | return self.config.getint(section, key) | ||
| 30 | return default | ||
| 31 | |||
| 32 | def items(self, section): | ||
| 33 | if self.config.has_section(section): | ||
| 34 | return self.config.items(section) | ||
| 35 | return {} | ||
diff --git a/python/webiopi/utils/crypto.py b/python/webiopi/utils/crypto.py new file mode 100644 index 0000000..aa48a82 --- /dev/null +++ b/python/webiopi/utils/crypto.py | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | import base64 | ||
| 2 | import hashlib | ||
| 3 | from webiopi.utils.version import PYTHON_MAJOR | ||
| 4 | |||
| 5 | def encodeCredentials(login, password): | ||
| 6 | abcd = "%s:%s" % (login, password) | ||
| 7 | if PYTHON_MAJOR >= 3: | ||
| 8 | b = base64.b64encode(abcd.encode()) | ||
| 9 | else: | ||
| 10 | b = base64.b64encode(abcd) | ||
| 11 | return b | ||
| 12 | |||
| 13 | def encrypt(value): | ||
| 14 | return hashlib.sha256(value).hexdigest() | ||
| 15 | |||
| 16 | def encryptCredentials(login, password): | ||
| 17 | return encrypt(encodeCredentials(login, password)) | ||
diff --git a/python/webiopi/utils/loader.py b/python/webiopi/utils/loader.py new file mode 100644 index 0000000..bf7c693 --- /dev/null +++ b/python/webiopi/utils/loader.py | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | import imp | ||
| 2 | import webiopi.utils.logger as logger | ||
| 3 | import webiopi.utils.thread as thread | ||
| 4 | SCRIPTS = {} | ||
| 5 | |||
| 6 | def loadScript(name, source, handler = None): | ||
| 7 | logger.info("Loading %s from %s" % (name, source)) | ||
| 8 | script = imp.load_source(name, source) | ||
| 9 | SCRIPTS[name] = script | ||
| 10 | |||
| 11 | if hasattr(script, "setup"): | ||
| 12 | script.setup() | ||
| 13 | if handler: | ||
| 14 | for aname in dir(script): | ||
| 15 | attr = getattr(script, aname) | ||
| 16 | if callable(attr) and hasattr(attr, "macro"): | ||
| 17 | handler.addMacro(attr) | ||
| 18 | if hasattr(script, "loop"): | ||
| 19 | thread.runLoop(script.loop, True) | ||
| 20 | |||
| 21 | def unloadScripts(): | ||
| 22 | for name in SCRIPTS: | ||
| 23 | script = SCRIPTS[name] | ||
| 24 | if hasattr(script, "destroy"): | ||
| 25 | script.destroy() | ||
| 26 | \ No newline at end of file | ||
diff --git a/python/webiopi/utils/logger.py b/python/webiopi/utils/logger.py new file mode 100644 index 0000000..fb92ec5 --- /dev/null +++ b/python/webiopi/utils/logger.py | |||
| @@ -0,0 +1,45 @@ | |||
| 1 | import logging | ||
| 2 | |||
| 3 | LOG_FORMATTER = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt="%Y-%m-%d %H:%M:%S") | ||
| 4 | ROOT_LOGGER = logging.getLogger() | ||
| 5 | ROOT_LOGGER.setLevel(logging.WARN) | ||
| 6 | |||
| 7 | CONSOLE_HANDLER = logging.StreamHandler() | ||
| 8 | CONSOLE_HANDLER.setFormatter(LOG_FORMATTER) | ||
| 9 | ROOT_LOGGER.addHandler(CONSOLE_HANDLER) | ||
| 10 | |||
| 11 | LOGGER = logging.getLogger("WebIOPi") | ||
| 12 | |||
| 13 | def setInfo(): | ||
| 14 | ROOT_LOGGER.setLevel(logging.INFO) | ||
| 15 | |||
| 16 | def setDebug(): | ||
| 17 | ROOT_LOGGER.setLevel(logging.DEBUG) | ||
| 18 | |||
| 19 | def debugEnabled(): | ||
| 20 | return ROOT_LOGGER.level == logging.DEBUG | ||
| 21 | |||
| 22 | def logToFile(filename): | ||
| 23 | FILE_HANDLER = logging.FileHandler(filename) | ||
| 24 | FILE_HANDLER.setFormatter(LOG_FORMATTER) | ||
| 25 | ROOT_LOGGER.addHandler(FILE_HANDLER) | ||
| 26 | |||
| 27 | def debug(message): | ||
| 28 | LOGGER.debug(message) | ||
| 29 | |||
| 30 | def info(message): | ||
| 31 | LOGGER.info(message) | ||
| 32 | |||
| 33 | def warn(message): | ||
| 34 | LOGGER.warn(message) | ||
| 35 | |||
| 36 | def error(message): | ||
| 37 | LOGGER.error(message) | ||
| 38 | |||
| 39 | def exception(message): | ||
| 40 | LOGGER.exception(message) | ||
| 41 | |||
| 42 | def printBytes(buff): | ||
| 43 | for i in range(0, len(buff)): | ||
| 44 | print("%03d: 0x%02X %03d %c" % (i, buff[i], buff[i], buff[i])) | ||
| 45 | |||
diff --git a/python/webiopi/utils/thread.py b/python/webiopi/utils/thread.py new file mode 100644 index 0000000..98ed752 --- /dev/null +++ b/python/webiopi/utils/thread.py | |||
| @@ -0,0 +1,50 @@ | |||
| 1 | import time | ||
| 2 | import signal | ||
| 3 | import threading | ||
| 4 | from webiopi.utils import logger | ||
| 5 | |||
| 6 | RUNNING = False | ||
| 7 | TASKS = [] | ||
| 8 | |||
| 9 | class Task(threading.Thread): | ||
| 10 | def __init__(self, func, loop=False): | ||
| 11 | threading.Thread.__init__(self) | ||
| 12 | self.func = func | ||
| 13 | self.loop = loop | ||
| 14 | self.running = True | ||
| 15 | self.start() | ||
| 16 | |||
| 17 | def stop(self): | ||
| 18 | self.running = False | ||
| 19 | |||
| 20 | def run(self): | ||
| 21 | if self.loop: | ||
| 22 | while self.running == True: | ||
| 23 | self.func() | ||
| 24 | else: | ||
| 25 | self.func() | ||
| 26 | |||
| 27 | def stop(signum=0, frame=None): | ||
| 28 | global RUNNING | ||
| 29 | if RUNNING: | ||
| 30 | logger.info("Stopping...") | ||
| 31 | RUNNING = False | ||
| 32 | for task in TASKS: | ||
| 33 | task.stop() | ||
| 34 | |||
| 35 | |||
| 36 | def runLoop(func=None, async=False): | ||
| 37 | global RUNNING | ||
| 38 | RUNNING = True | ||
| 39 | signal.signal(signal.SIGINT, stop) | ||
| 40 | signal.signal(signal.SIGTERM, stop) | ||
| 41 | |||
| 42 | if func != None: | ||
| 43 | if async: | ||
| 44 | TASKS.append(Task(func, True)) | ||
| 45 | else: | ||
| 46 | while RUNNING: | ||
| 47 | func() | ||
| 48 | else: | ||
| 49 | while RUNNING: | ||
| 50 | time.sleep(1) | ||
diff --git a/python/webiopi/utils/types.py b/python/webiopi/utils/types.py new file mode 100644 index 0000000..0afaa20 --- /dev/null +++ b/python/webiopi/utils/types.py | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | import json | ||
| 2 | from webiopi.utils import logger | ||
| 3 | |||
| 4 | M_PLAIN = "text/plain" | ||
| 5 | M_JSON = "application/json" | ||
| 6 | |||
| 7 | def jsonDumps(obj): | ||
| 8 | if logger.debugEnabled(): | ||
| 9 | return json.dumps(obj, sort_keys=True, indent=4, separators=(',', ': ')) | ||
| 10 | else: | ||
| 11 | return json.dumps(obj) | ||
| 12 | |||
| 13 | def str2bool(value): | ||
| 14 | return (value == "1") or (value == "true") or (value == "True") or (value == "yes") or (value == "Yes") | ||
| 15 | |||
| 16 | def toint(value): | ||
| 17 | if isinstance(value, str): | ||
| 18 | if value.startswith("0b"): | ||
| 19 | return int(value, 2) | ||
| 20 | elif value.startswith("0x"): | ||
| 21 | return int(value, 16) | ||
| 22 | else: | ||
| 23 | return int(value) | ||
| 24 | return value | ||
| 25 | |||
| 26 | |||
| 27 | def signInteger(value, bitcount): | ||
| 28 | if value & (1<<(bitcount-1)): | ||
| 29 | return value - (1<<bitcount) | ||
| 30 | return value | ||
diff --git a/python/webiopi/utils/version.py b/python/webiopi/utils/version.py new file mode 100644 index 0000000..b5e32de --- /dev/null +++ b/python/webiopi/utils/version.py | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | import re | ||
| 2 | import sys | ||
| 3 | |||
| 4 | VERSION = '0.6.2' | ||
| 5 | VERSION_STRING = "WebIOPi/%s/Python%d.%d" % (VERSION, sys.version_info.major, sys.version_info.minor) | ||
| 6 | PYTHON_MAJOR = sys.version_info.major | ||
| 7 | BOARD_REVISION = 0 | ||
| 8 | |||
| 9 | _MAPPING = [[], [], []] | ||
| 10 | _MAPPING[1] = ["V33", "V50", 0, "V50", 1, "GND", 4, 14, "GND", 15, 17, 18, 21, "GND", 22, 23, "V33", 24, 10, "GND", 9, 25, 11, 8, "GND", 7] | ||
| 11 | _MAPPING[2] = ["V33", "V50", 2, "V50", 3, "GND", 4, 14, "GND", 15, 17, 18, 27, "GND", 22, 23, "V33", 24, 10, "GND", 9, 25, 11, 8, "GND", 7] | ||
| 12 | |||
| 13 | |||
| 14 | try: | ||
| 15 | with open("/proc/cpuinfo") as f: | ||
| 16 | rc = re.compile("Revision\s*:\s(.*)\n") | ||
| 17 | info = f.read() | ||
| 18 | result = rc.search(info) | ||
| 19 | if result != None: | ||
| 20 | hex_cpurev = result.group(1) | ||
| 21 | if hex_cpurev.startswith("1000"): | ||
| 22 | hex_cpurev = hex_cpurev[-4:] | ||
| 23 | cpurev = int(hex_cpurev, 16) | ||
| 24 | BOARD_REVISION = 1 if (cpurev < 4) else 2 | ||
| 25 | |||
| 26 | except: | ||
| 27 | pass | ||
| 28 | |||
| 29 | MAPPING = _MAPPING[BOARD_REVISION] | ||
diff --git a/setup.sh b/setup.sh new file mode 100755 index 0000000..865c6fe --- /dev/null +++ b/setup.sh | |||
| @@ -0,0 +1,126 @@ | |||
| 1 | #!/bin/sh | ||
| 2 | # WebIOPi setup script | ||
| 3 | |||
| 4 | SEARCH="python python3" | ||
| 5 | FOUND="" | ||
| 6 | INSTALLED="" | ||
| 7 | |||
| 8 | if [ "$#" = "1" ]; then | ||
| 9 | command="$1" | ||
| 10 | else | ||
| 11 | command="none" | ||
| 12 | fi | ||
| 13 | |||
| 14 | echo | ||
| 15 | echo "Installing WebIOPi..." | ||
| 16 | echo | ||
| 17 | |||
| 18 | if [ "$command" != "skip-apt" ]; then | ||
| 19 | echo "Updating apt package list..." | ||
| 20 | apt-get update | ||
| 21 | echo | ||
| 22 | fi | ||
| 23 | |||
| 24 | # Install Python library | ||
| 25 | cd python | ||
| 26 | |||
| 27 | # Look up for installed python | ||
| 28 | for python in $SEARCH; do | ||
| 29 | program="/usr/bin/$python" | ||
| 30 | if [ -x $program ]; then | ||
| 31 | FOUND="$FOUND $python" | ||
| 32 | version=`$python -V 2>&1` | ||
| 33 | include=`$python -c "import distutils.sysconfig; print(distutils.sysconfig.get_python_inc())"` | ||
| 34 | echo "Found $version... " | ||
| 35 | |||
| 36 | if [ "$command" != "skip-apt" ]; then | ||
| 37 | # Install required dev header and setuptools | ||
| 38 | echo "Trying to install $python-dev using apt-get" | ||
| 39 | apt-get install -y $python-dev $python-setuptools | ||
| 40 | fi | ||
| 41 | |||
| 42 | # Try to compile and install for the current python | ||
| 43 | if [ -f "$include/Python.h" ]; then | ||
| 44 | echo "Trying to install WebIOPi for $version" | ||
| 45 | $python setup.py install | ||
| 46 | if [ "$?" -ne "0" ]; then | ||
| 47 | # Sub setup error, continue with next python | ||
| 48 | echo "Build for $version failed\n" | ||
| 49 | continue | ||
| 50 | fi | ||
| 51 | echo "WebIOPi installed for $version\n" | ||
| 52 | INSTALLED="$INSTALLED $python" | ||
| 53 | else | ||
| 54 | echo "Cannot install for $version : missing development headers\n" | ||
| 55 | fi | ||
| 56 | fi | ||
| 57 | done | ||
| 58 | |||
| 59 | # Go back to the root folder | ||
| 60 | cd .. | ||
| 61 | |||
| 62 | # Ensure WebIOPi is installed to continue | ||
| 63 | if [ -z "$INSTALLED" ]; then | ||
| 64 | if [ -z "$FOUND" ]; then | ||
| 65 | echo "ERROR: WebIOPi cannot be installed - neither python or python3 found" | ||
| 66 | exit 1 | ||
| 67 | else | ||
| 68 | echo "ERROR: WebIOPi cannot be installed - please check errors above" | ||
| 69 | exit 2 | ||
| 70 | fi | ||
| 71 | fi | ||
| 72 | |||
| 73 | # Select greater python version | ||
| 74 | for python in $INSTALLED; do | ||
| 75 | echo $python > /dev/null | ||
| 76 | done | ||
| 77 | |||
| 78 | # Update HTML resources | ||
| 79 | echo "Copying HTML resources..." | ||
| 80 | mkdir /usr/share/webiopi 2>/dev/null 1>/dev/null | ||
| 81 | cp -rfv htdocs /usr/share/webiopi | ||
| 82 | echo | ||
| 83 | |||
| 84 | # Add config file if it does not exist | ||
| 85 | if [ ! -f "/etc/webiopi/config" ]; then | ||
| 86 | echo "Copying default config file..." | ||
| 87 | mkdir /etc/webiopi 2>/dev/null 1>/dev/null | ||
| 88 | cp -v python/config /etc/webiopi/config | ||
| 89 | fi | ||
| 90 | |||
| 91 | # Add passwd file if it does not exist | ||
| 92 | if [ ! -f "/etc/webiopi/passwd" ]; then | ||
| 93 | echo "Copying default passwd file..." | ||
| 94 | mkdir /etc/webiopi 2>/dev/null 1>/dev/null | ||
| 95 | cp -v python/passwd /etc/webiopi/passwd | ||
| 96 | fi | ||
| 97 | |||
| 98 | # Add service/daemon script | ||
| 99 | #if [ ! -f "/etc/init.d/webiopi" ]; then | ||
| 100 | echo "Installing startup script..." | ||
| 101 | cp -rf python/webiopi.init.sh /etc/init.d/webiopi | ||
| 102 | sed -i "s/python/$python/g" /etc/init.d/webiopi | ||
| 103 | chmod 0755 /etc/init.d/webiopi | ||
| 104 | |||
| 105 | # Add webiopi command | ||
| 106 | echo "Installing webiopi command..." | ||
| 107 | cp -rf python/webiopi.sh /usr/bin/webiopi | ||
| 108 | sed -i "s/python/$python/g" /usr/bin/webiopi | ||
| 109 | chmod 0755 /usr/bin/webiopi | ||
| 110 | |||
| 111 | # Add webiopi-passwd command | ||
| 112 | echo "Installing webiopi-passwd command..." | ||
| 113 | cp -rf python/webiopi-passwd.py /usr/bin/webiopi-passwd | ||
| 114 | sed -i "s/python/$python/g" /usr/bin/webiopi-passwd | ||
| 115 | chmod 0755 /usr/bin/webiopi-passwd | ||
| 116 | |||
| 117 | # Display WebIOPi usages | ||
| 118 | echo | ||
| 119 | echo "WebIOPi successfully installed" | ||
| 120 | echo "* To start WebIOPi foreground\t: sudo webiopi [-h] [-c config] [-l log] [-s script] [-d] [port]" | ||
| 121 | echo | ||
| 122 | echo "* To start WebIOPi background\t: sudo /etc/init.d/webiopi start" | ||
| 123 | echo "* To start WebIOPi at boot\t: sudo update-rc.d webiopi defaults" | ||
| 124 | echo | ||
| 125 | echo "* Look in `pwd`/examples for Python library usage examples" | ||
| 126 | echo | ||
