summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authormanuel <manuel@mausz.at>2013-12-25 13:25:16 +0100
committermanuel <manuel@mausz.at>2013-12-25 13:25:16 +0100
commit0c8c9ad976879f7c90f9915a60845ccb0cdb337d (patch)
tree162951b4713f3836f4114958a423e2c90ecf9c6b
downloadwebiopi-0c8c9ad976879f7c90f9915a60845ccb0cdb337d.tar.gz
webiopi-0c8c9ad976879f7c90f9915a60845ccb0cdb337d.tar.bz2
webiopi-0c8c9ad976879f7c90f9915a60845ccb0cdb337d.zip
initial commit
-rw-r--r--doc/CHANGELOG84
-rw-r--r--doc/INSTALL46
-rw-r--r--doc/LICENSE204
-rw-r--r--doc/README60
-rwxr-xr-xdoc/updatedoc23
-rw-r--r--examples/clients/coap-client.py15
-rw-r--r--examples/clients/webiopi-client.py46
-rw-r--r--examples/magpi-9-cambot/cambot.py139
-rw-r--r--examples/magpi-9-cambot/index.html70
-rwxr-xr-xexamples/magpi-9-cambot/stream.sh11
-rw-r--r--examples/magpi-9-cambot2/cambot.py117
-rw-r--r--examples/magpi-9-cambot2/index.html70
-rwxr-xr-xexamples/magpi-9-cambot2/stream.sh11
-rw-r--r--examples/scripts/basic/script.py34
-rw-r--r--examples/scripts/blink/script.py41
-rw-r--r--examples/scripts/macros/index.html120
-rw-r--r--examples/scripts/macros/script.py60
-rw-r--r--examples/scripts/simple/index.html68
-rw-r--r--htdocs/app/devices-monitor/index.html36
-rw-r--r--htdocs/app/gpio-header/index.html18
-rw-r--r--htdocs/app/gpio-list/index.html28
-rw-r--r--htdocs/app/serial-monitor/index.html64
-rw-r--r--htdocs/index.html25
-rw-r--r--htdocs/jquery-mobile.css2
-rw-r--r--htdocs/jquery-mobile.js2
-rw-r--r--htdocs/jquery.js2
-rw-r--r--htdocs/webiopi.css93
-rw-r--r--htdocs/webiopi.js1448
-rw-r--r--java/client/src/Test.java52
-rw-r--r--java/client/src/com/trouch/webiopi/client/PiClient.java38
-rw-r--r--java/client/src/com/trouch/webiopi/client/PiCoapClient.java57
-rw-r--r--java/client/src/com/trouch/webiopi/client/PiHttpClient.java68
-rw-r--r--java/client/src/com/trouch/webiopi/client/PiMixedClient.java56
-rw-r--r--java/client/src/com/trouch/webiopi/client/PiMulticastClient.java27
-rw-r--r--java/client/src/com/trouch/webiopi/client/devices/Device.java44
-rw-r--r--java/client/src/com/trouch/webiopi/client/devices/analog/ADC.java34
-rw-r--r--java/client/src/com/trouch/webiopi/client/devices/analog/DAC.java37
-rw-r--r--java/client/src/com/trouch/webiopi/client/devices/analog/PWM.java33
-rw-r--r--java/client/src/com/trouch/webiopi/client/devices/digital/GPIO.java53
-rw-r--r--java/client/src/com/trouch/webiopi/client/devices/digital/NativeGPIO.java26
-rw-r--r--java/client/src/com/trouch/webiopi/client/devices/sensor/Distance.java30
-rw-r--r--java/client/src/com/trouch/webiopi/client/devices/sensor/Luminosity.java30
-rw-r--r--java/client/src/com/trouch/webiopi/client/devices/sensor/Pressure.java34
-rw-r--r--java/client/src/com/trouch/webiopi/client/devices/sensor/Temperature.java34
-rw-r--r--java/client/src/org/apache/commons/codec/BinaryDecoder.java38
-rw-r--r--java/client/src/org/apache/commons/codec/BinaryEncoder.java38
-rw-r--r--java/client/src/org/apache/commons/codec/CharEncoding.java113
-rw-r--r--java/client/src/org/apache/commons/codec/Charsets.java144
-rw-r--r--java/client/src/org/apache/commons/codec/Decoder.java47
-rw-r--r--java/client/src/org/apache/commons/codec/DecoderException.java86
-rw-r--r--java/client/src/org/apache/commons/codec/Encoder.java44
-rw-r--r--java/client/src/org/apache/commons/codec/EncoderException.java89
-rw-r--r--java/client/src/org/apache/commons/codec/binary/Base64.java775
-rw-r--r--java/client/src/org/apache/commons/codec/binary/BaseNCodec.java500
-rw-r--r--java/client/src/org/apache/commons/codec/binary/StringUtils.java343
-rw-r--r--midori/config9
-rwxr-xr-xplay.sh19
-rw-r--r--python/config136
-rw-r--r--python/native/bridge.c720
-rw-r--r--python/native/cpuinfo.c65
-rw-r--r--python/native/cpuinfo.h23
-rw-r--r--python/native/gpio.c329
-rw-r--r--python/native/gpio.h73
-rw-r--r--python/passwd1
-rw-r--r--python/setup.py37
-rwxr-xr-xpython/webiopi-passwd.py57
-rwxr-xr-xpython/webiopi.init.sh155
-rwxr-xr-xpython/webiopi.sh2
-rw-r--r--python/webiopi/__init__.py34
-rw-r--r--python/webiopi/__main__.py79
-rw-r--r--python/webiopi/clients/__init__.py209
-rw-r--r--python/webiopi/decorators/__init__.py0
-rw-r--r--python/webiopi/decorators/rest.py19
-rw-r--r--python/webiopi/devices/__init__.py13
-rw-r--r--python/webiopi/devices/analog/__init__.py267
-rw-r--r--python/webiopi/devices/analog/ads1x1x.py82
-rw-r--r--python/webiopi/devices/analog/mcp3x0x.py75
-rw-r--r--python/webiopi/devices/analog/mcp4725.py38
-rw-r--r--python/webiopi/devices/analog/mcp492X.py53
-rw-r--r--python/webiopi/devices/analog/pca9685.py63
-rw-r--r--python/webiopi/devices/bus.py117
-rw-r--r--python/webiopi/devices/digital/__init__.py144
-rw-r--r--python/webiopi/devices/digital/ds2408.py84
-rw-r--r--python/webiopi/devices/digital/gpio.py189
-rw-r--r--python/webiopi/devices/digital/mcp23XXX.py153
-rw-r--r--python/webiopi/devices/digital/pcf8574.py70
-rw-r--r--python/webiopi/devices/i2c.py75
-rw-r--r--python/webiopi/devices/instance.py6
-rw-r--r--python/webiopi/devices/manager.py77
-rw-r--r--python/webiopi/devices/onewire.py74
-rw-r--r--python/webiopi/devices/sensor/__init__.py177
-rw-r--r--python/webiopi/devices/sensor/bmp085.py100
-rw-r--r--python/webiopi/devices/sensor/onewiretemp.py58
-rw-r--r--python/webiopi/devices/sensor/tmpXXX.py60
-rw-r--r--python/webiopi/devices/sensor/tslXXXX.py247
-rw-r--r--python/webiopi/devices/sensor/vcnl4000.py211
-rw-r--r--python/webiopi/devices/serial.py86
-rw-r--r--python/webiopi/devices/shield/__init__.py16
-rw-r--r--python/webiopi/devices/shield/piface.py66
-rw-r--r--python/webiopi/devices/spi.py145
-rw-r--r--python/webiopi/protocols/__init__.py14
-rw-r--r--python/webiopi/protocols/coap.py537
-rw-r--r--python/webiopi/protocols/http.py249
-rw-r--r--python/webiopi/protocols/rest.py254
-rw-r--r--python/webiopi/server/__init__.py139
-rw-r--r--python/webiopi/utils/__init__.py16
-rw-r--r--python/webiopi/utils/config.py35
-rw-r--r--python/webiopi/utils/crypto.py17
-rw-r--r--python/webiopi/utils/loader.py26
-rw-r--r--python/webiopi/utils/logger.py45
-rw-r--r--python/webiopi/utils/thread.py50
-rw-r--r--python/webiopi/utils/types.py30
-rw-r--r--python/webiopi/utils/version.py29
-rwxr-xr-xsetup.sh126
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 @@
1WebIOPi is developed and tested on Raspbian.
2
3You 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
10Finally, 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
23You'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
25You can also start/stop the background service :
26$ sudo /etc/init.d/webiopi start
27and
28$ sudo /etc/init.d/webiopi stop
29
30You can even setup your system to start webiopi at startup :
31$ sudo update-rc.d webiopi defaults
32
33
34=Usage=
35If your are directly using your Raspberry Pi with keyboard/mouse/display plugged, open a browser to http://localhost:8000/
36
37If 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
39You can even add a port redirection on your router (and/or use IPv6) to control your GPIOs over Internet !
40
41Default user is "webiopi" and password is "raspberry"
42
43By 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.
46http://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
50http://trouch.com/wp-content/uploads/2012/11/webiopi-architecture.png
51
52http://trouch.com/wp-content/uploads/2012/08/webiopi-chrome.png
53
54http://trouch.com/wp-content/uploads/2013/03/webiopi-0.6-serialmon.png
55
56http://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
3if [ -z "$*" ]; then
4 files=`ls`
5else
6 files="$*"
7fi
8
9for 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"
20done
21
22echo "Uploading changes on SVN..."
23svn 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 @@
1from webiopi.protocols.coap import *
2from time import sleep
3
4client = COAPClient()
5client.sendRequest(COAPPost("coap://224.0.1.123/GPIO/25/function/out"))
6state = True
7
8while 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 @@
1from webiopi.clients import *
2from time import sleep
3
4# Create a WebIOPi client
5client = PiHttpClient("192.168.1.234")
6#client = PiMixedClient("192.168.1.234")
7#client = PiCoapClient("192.168.1.234")
8#client = PiMulticastClient()
9
10client.setCredentials("webiopi", "raspberry")
11
12# RPi native GPIO
13gpio = NativeGPIO(client)
14gpio.setFunction(25, "out")
15state = True
16
17# DAC named "dac1"
18dac = DAC(client, "dac1")
19
20# ADC named "adc1"
21adc = ADC(client, "adc1")
22value = 0.0
23
24# Temperature sensor named "temp0"
25temp = Temperature(client, "temp0")
26
27while 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
2import webiopi
3
4# Retrieve GPIO lib
5GPIO = webiopi.GPIO
6
7# -------------------------------------------------- #
8# Constants definition #
9# -------------------------------------------------- #
10
11# Left motor GPIOs
12L1=9 # H-Bridge 1
13L2=10 # H-Bridge 2
14LS=11 # H-Bridge 1,2EN
15
16# Right motor GPIOs
17R1=23 # H-Bridge 3
18R2=24 # H-Bridge 4
19RS=25 # H-Bridge 3,4EN
20
21# -------------------------------------------------- #
22# Convenient PWM Function #
23# -------------------------------------------------- #
24
25# Set the speed of two motors
26def set_speed(speed):
27 GPIO.pulseRatio(LS, speed)
28 GPIO.pulseRatio(RS, speed)
29
30# -------------------------------------------------- #
31# Left Motor Functions #
32# -------------------------------------------------- #
33
34def left_stop():
35 GPIO.output(L1, GPIO.LOW)
36 GPIO.output(L2, GPIO.LOW)
37
38def left_forward():
39 GPIO.output(L1, GPIO.HIGH)
40 GPIO.output(L2, GPIO.LOW)
41
42def left_backward():
43 GPIO.output(L1, GPIO.LOW)
44 GPIO.output(L2, GPIO.HIGH)
45
46# -------------------------------------------------- #
47# Right Motor Functions #
48# -------------------------------------------------- #
49def right_stop():
50 GPIO.output(R1, GPIO.LOW)
51 GPIO.output(R2, GPIO.LOW)
52
53def right_forward():
54 GPIO.output(R1, GPIO.HIGH)
55 GPIO.output(R2, GPIO.LOW)
56
57def right_backward():
58 GPIO.output(R1, GPIO.LOW)
59 GPIO.output(R2, GPIO.HIGH)
60
61# -------------------------------------------------- #
62# Macro definition part #
63# -------------------------------------------------- #
64
65def go_forward():
66 left_forward()
67 right_forward()
68
69def go_backward():
70 left_backward()
71 right_backward()
72
73def turn_left():
74 left_backward()
75 right_forward()
76
77def turn_right():
78 left_forward()
79 right_backward()
80
81def stop():
82 left_stop()
83 right_stop()
84
85# -------------------------------------------------- #
86# Initialization part #
87# -------------------------------------------------- #
88
89# Setup GPIOs
90GPIO.setFunction(LS, GPIO.PWM)
91GPIO.setFunction(L1, GPIO.OUT)
92GPIO.setFunction(L2, GPIO.OUT)
93
94GPIO.setFunction(RS, GPIO.PWM)
95GPIO.setFunction(R1, GPIO.OUT)
96GPIO.setFunction(R2, GPIO.OUT)
97
98set_speed(0.5)
99stop()
100
101# -------------------------------------------------- #
102# Main server part #
103# -------------------------------------------------- #
104
105
106# Instantiate the server on the port 8000, it starts immediately in its own thread
107server = 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
111server.addMacro(go_forward)
112server.addMacro(go_backward)
113server.addMacro(turn_left)
114server.addMacro(turn_right)
115server.addMacro(stop)
116
117# -------------------------------------------------- #
118# Loop execution part #
119# -------------------------------------------------- #
120
121# Run our loop until CTRL-C is pressed or SIGTERM received
122webiopi.runLoop()
123
124# -------------------------------------------------- #
125# Termination part #
126# -------------------------------------------------- #
127
128# Stop the server
129server.stop()
130
131# Reset GPIO functions
132GPIO.setFunction(LS, GPIO.IN)
133GPIO.setFunction(L1, GPIO.IN)
134GPIO.setFunction(L2, GPIO.IN)
135
136GPIO.setFunction(RS, GPIO.IN)
137GPIO.setFunction(R1, GPIO.IN)
138GPIO.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
3STREAMER=mjpg_streamer
4DEVICE=/dev/video0
5RESOLUTION=320x240
6FRAMERATE=25
7HTTP_PORT=8001
8
9PLUGINPATH=/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
5import webiopi
6
7# Retrieve GPIO lib
8GPIO = webiopi.GPIO
9
10# -------------------------------------------------- #
11# Constants definition #
12# -------------------------------------------------- #
13
14# Left motor GPIOs
15L1=17 # H-Bridge 1
16L2=18 # H-Bridge 2
17LS=21 # H-Bridge 1,2EN
18
19# Right motor GPIOs
20R1=23 # H-Bridge 3
21R2=24 # H-Bridge 4
22RS=25 # H-Bridge 3,4EN
23
24# -------------------------------------------------- #
25# Convenient PWM Function #
26# -------------------------------------------------- #
27
28# Set the speed of two motors
29def set_speed(speed):
30 GPIO.pulseRatio(LS, speed)
31 GPIO.pulseRatio(RS, speed)
32
33# -------------------------------------------------- #
34# Left Motor Functions #
35# -------------------------------------------------- #
36
37def left_stop():
38 GPIO.output(L1, GPIO.LOW)
39 GPIO.output(L2, GPIO.LOW)
40
41def left_forward():
42 GPIO.output(L1, GPIO.HIGH)
43 GPIO.output(L2, GPIO.LOW)
44
45def left_backward():
46 GPIO.output(L1, GPIO.LOW)
47 GPIO.output(L2, GPIO.HIGH)
48
49# -------------------------------------------------- #
50# Right Motor Functions #
51# -------------------------------------------------- #
52def right_stop():
53 GPIO.output(R1, GPIO.LOW)
54 GPIO.output(R2, GPIO.LOW)
55
56def right_forward():
57 GPIO.output(R1, GPIO.HIGH)
58 GPIO.output(R2, GPIO.LOW)
59
60def right_backward():
61 GPIO.output(R1, GPIO.LOW)
62 GPIO.output(R2, GPIO.HIGH)
63
64# -------------------------------------------------- #
65# Macro definition part #
66# -------------------------------------------------- #
67@webiopi.macro
68def go_forward():
69 left_forward()
70 right_forward()
71
72@webiopi.macro
73def go_backward():
74 left_backward()
75 right_backward()
76
77@webiopi.macro
78def turn_left():
79 left_backward()
80 right_forward()
81
82@webiopi.macro
83def turn_right():
84 left_forward()
85 right_backward()
86
87@webiopi.macro
88def stop():
89 left_stop()
90 right_stop()
91
92# Called by WebIOPi at script loading
93def 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
108def 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
3STREAMER=mjpg_streamer
4DEVICE=/dev/video0
5RESOLUTION=320x240
6FRAMERATE=25
7HTTP_PORT=8001
8
9PLUGINPATH=/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
2import webiopi
3
4# Enable debug output
5webiopi.setDebug()
6
7# Retrieve GPIO lib
8GPIO = webiopi.GPIO
9SWITCH = 21
10SERVO = 23
11LED0 = 24
12LED1 = 25
13
14# Called by WebIOPi at script loading
15def 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
28def 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
2import webiopi
3
4# Enable debug output
5webiopi.setDebug()
6
7# Retrieve GPIO lib
8GPIO = webiopi.GPIO
9SWITCH = 21
10SERVO = 23
11LED0 = 24
12LED1 = 25
13
14# Called by WebIOPi at script loading
15def 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
28def 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
35def 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
2import webiopi
3import time
4
5# Enable debug output
6webiopi.setDebug()
7
8# Retrieve GPIO lib
9GPIO = webiopi.GPIO
10
11SWITCH = 21
12SERVO = 23
13LED0 = 24
14LED1 = 25
15
16# Called by WebIOPi at script loading
17def 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
33def 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
40def 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
52def 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
58def 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="&#160;",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
3button {
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
14input[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
17var _gaq = _gaq || [];
18var _webiopi;
19
20function w() {
21 if (_webiopi == undefined) {
22 _webiopi = new WebIOPi();
23 }
24
25 return _webiopi;
26}
27
28function webiopi() {
29 return w();
30}
31
32function 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
37var _isMobile = undefined;
38function 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
47function 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
154WebIOPi.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
188WebIOPi.prototype.initMobile = function() {
189 webiopi().init();
190}
191
192WebIOPi.prototype.ready = function (cb) {
193 w().readyCallback = cb;
194}
195
196WebIOPi.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
206WebIOPi.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
213WebIOPi.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
219WebIOPi.prototype.updateFunction = function (gpio, func) {
220 w().GPIO[gpio].func = func;
221 $("#function"+gpio).val(func);
222 $("#function"+gpio).text(func);
223}
224
225WebIOPi.prototype.updateSlider = function (gpio, slider, value) {
226 $("#"+slider+gpio).val(value);
227}
228
229WebIOPi.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
247WebIOPi.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
273WebIOPi.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
298WebIOPi.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
308WebIOPi.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
322WebIOPi.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}
331WebIOPi.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
340WebIOPi.prototype.toggleValue = function (gpio) {
341 var value = (w().GPIO[gpio].value == 1) ? 0 : 1;
342 w().digitalWrite(gpio, value);
343}
344
345WebIOPi.prototype.toggleFunction = function (gpio) {
346 var value = (w().GPIO[gpio].func == "IN") ? "OUT" : "IN";
347 w().setFunction(gpio, value)
348}
349
350WebIOPi.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
359WebIOPi.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
370WebIOPi.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
378WebIOPi.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
386WebIOPi.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
394WebIOPi.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
402WebIOPi.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
410WebIOPi.prototype.setLabel = function (id, label) {
411 $("#" + id).val(label);
412 $("#" + id).text(label);
413}
414
415WebIOPi.prototype.setClass = function (id, cssClass) {
416 $("#" + id).attr("class", cssClass);
417}
418
419WebIOPi.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
439WebIOPi.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
447WebIOPi.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
456WebIOPi.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
464WebIOPi.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
472WebIOPi.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
480WebIOPi.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
489WebIOPi.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
498WebIOPi.prototype.RPiHeader = function () {
499 if (w()._header == undefined) {
500 w()._header = new RPiHeader();
501 }
502 return w()._header;
503}
504
505function RPiHeader() {
506
507}
508
509RPiHeader.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
525RPiHeader.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
544RPiHeader.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
553RPiHeader.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
577WebIOPi.prototype.Expert = function () {
578 if (w()._expert == undefined) {
579 w()._expert = new Expert();
580 }
581 return w()._expert;
582}
583
584function Expert() {
585
586}
587
588Expert.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
602Expert.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
619WebIOPi.prototype.Serial = function(device) {
620 return new Serial(device);
621}
622
623function Serial(device) {
624 this.device = device;
625 this.url = "/devices/" + device
626}
627
628Serial.prototype.write = function(data) {
629 $.post(this.url, data);
630}
631
632Serial.prototype.read = function(callback) {
633 $.get(this.url, callback);
634}
635
636WebIOPi.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
676function 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
690GPIOPort.prototype.isReady = function() {
691 return (this.channelCount > 0);
692}
693
694GPIOPort.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
700GPIOPort.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
707GPIOPort.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
714GPIOPort.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
721GPIOPort.prototype.readAll = function(callback) {
722 var name = this.name;
723 $.get(this.url+ "/*", function(data) {
724 callback(name, data);
725 });
726}
727
728GPIOPort.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
808function 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
830ADC.prototype.isReady = function() {
831 return (this.channelCount > 0 && this.maxInteger > 0 && this.resolution > 0 );
832}
833
834ADC.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
840ADC.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
847ADC.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
854ADC.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
861ADC.prototype.readAllInteger = function(callback) {
862 var name = this.name;
863 $.get(this.url + "/*/integer", function(data) {
864 callback(name, data);
865 });
866}
867
868ADC.prototype.readAllFloat = function(callback) {
869 var name = this.name;
870 $.get(this.url + "/*/float", function(data) {
871 callback(name, data);
872 });
873}
874
875ADC.prototype.readAllVolt = function(callback) {
876 var name = this.name;
877 $.get(this.url + "/*/volt", function(data) {
878 callback(name, data);
879 });
880}
881
882ADC.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
914function 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
935DAC.prototype.isReady = function() {
936 return (this.channelCount > 0 && this.maxInteger > 0 && this.resolution > 0 );
937}
938
939DAC.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
945DAC.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
952DAC.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
959DAC.prototype.readAllInteger = function(callback) {
960 var name = this.name;
961 $.get(this.url + "/*/integer", function(data) {
962 callback(name, data);
963 });
964}
965
966DAC.prototype.readAllFloat = function(callback) {
967 var name = this.name;
968 $.get(this.url + "/*/float", function(data) {
969 callback(name, data);
970 });
971}
972
973DAC.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
1032function 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
1054PWM.prototype.isReady = function() {
1055 return (this.channelCount > 0 && this.maxInteger > 0 && this.resolution > 0 );
1056}
1057
1058PWM.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
1064PWM.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
1071PWM.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
1078PWM.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
1085PWM.prototype.readAllInteger = function(callback) {
1086 var name = this.name;
1087 $.get(this.url + "/*/integer", function(data) {
1088 callback(name, data);
1089 });
1090}
1091
1092PWM.prototype.readAllFloat = function(callback) {
1093 var name = this.name;
1094 $.get(this.url + "/*/float", function(data) {
1095 callback(name, data);
1096 });
1097}
1098
1099PWM.prototype.readAll = function(callback) {
1100 var name = this.name;
1101 $.get(this.url + "/*", function(data) {
1102 callback(name, data);
1103 });
1104}
1105
1106PWM.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
1201function Temperature(name) {
1202 this.name = name;
1203 this.url = "/devices/" + name + "/sensor";
1204 this.refreshTime = 5000;
1205}
1206
1207Temperature.prototype.toString = function() {
1208 return this.name + ": Temperature";
1209}
1210
1211Temperature.prototype.getKelvin = function(callback) {
1212 $.get(this.url + "/temperature/k", function(data) {
1213 callback(this.name, data);
1214 });
1215}
1216
1217Temperature.prototype.getCelsius = function(callback) {
1218 $.get(this.url + "/temperature/c", function(data) {
1219 callback(this.name, data);
1220 });
1221}
1222
1223Temperature.prototype.getFahrenheit = function(callback) {
1224 $.get(this.url + "/temperature/f", function(data) {
1225 callback(this.name, data);
1226 });
1227}
1228
1229Temperature.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
1245function Pressure(name) {
1246 this.name = name;
1247 this.url = "/devices/" + name + "/sensor";
1248 this.refreshTime = 5000;
1249}
1250
1251Pressure.prototype.toString = function() {
1252 return this.name + ": Pressure";
1253}
1254
1255Pressure.prototype.getPascal = function(callback) {
1256 $.get(this.url + "/pressure/pa", function(data) {
1257 callback(this.name, data);
1258 });
1259}
1260
1261Pressure.prototype.getHectoPascal = function(callback) {
1262 $.get(this.url + "/pressure/hpa", function(data) {
1263 callback(this.name, data);
1264 });
1265}
1266
1267Pressure.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
1284function Luminosity(name) {
1285 this.name = name;
1286 this.url = "/devices/" + name + "/sensor";
1287 this.refreshTime = 1000;
1288}
1289
1290Luminosity.prototype.toString = function() {
1291 return this.name + ": Luminosity";
1292}
1293
1294Luminosity.prototype.getLux = function(callback) {
1295 $.get(this.url + "/luminosity/lx", function(data) {
1296 callback(this.name, data);
1297 });
1298}
1299
1300Luminosity.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
1317function Distance(name) {
1318 this.name = name;
1319 this.url = "/devices/" + name + "/sensor";
1320 this.refreshTime = 1000;
1321}
1322
1323Distance.prototype.toString = function() {
1324 return this.name + ": Distance";
1325}
1326
1327Distance.prototype.getMillimeter = function(callback) {
1328 $.get(this.url + "/distance/mm", function(data) {
1329 callback(this.name, data);
1330 });
1331}
1332
1333Distance.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
1350function PiFaceDigital(name) {
1351 this.name = name;
1352 this.url = "/devices/" + name + "/digital";
1353 this.onready = null;
1354 this.refreshTime = 1000;
1355}
1356
1357PiFaceDigital.prototype.toString = function() {
1358 return "PiFaceDigital";
1359}
1360
1361PiFaceDigital.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
1368PiFaceDigital.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
1375PiFaceDigital.prototype.readAll = function(callback) {
1376 var name = this.name;
1377 $.get(this.url+ "/*", function(data) {
1378 callback(name, data);
1379 });
1380}
1381
1382PiFaceDigital.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 @@
1import com.trouch.webiopi.client.PiClient;
2import com.trouch.webiopi.client.PiCoapClient;
3import com.trouch.webiopi.client.PiHttpClient;
4import com.trouch.webiopi.client.PiMixedClient;
5import com.trouch.webiopi.client.PiMulticastClient;
6import com.trouch.webiopi.client.devices.analog.ADC;
7import com.trouch.webiopi.client.devices.analog.DAC;
8import com.trouch.webiopi.client.devices.analog.PWM;
9import com.trouch.webiopi.client.devices.digital.GPIO;
10import com.trouch.webiopi.client.devices.digital.NativeGPIO;
11import com.trouch.webiopi.client.devices.sensor.Temperature;
12
13public 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
15package com.trouch.webiopi.client;
16
17import org.apache.commons.codec.binary.Base64;
18
19public 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
15package com.trouch.webiopi.client;
16
17import com.trouch.coap.client.CoapClient;
18import com.trouch.coap.messages.CoapRequest;
19import com.trouch.coap.messages.CoapResponse;
20import com.trouch.coap.methods.CoapGet;
21import com.trouch.coap.methods.CoapPost;
22
23
24public 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
15package com.trouch.webiopi.client;
16
17import java.io.BufferedReader;
18import java.io.IOException;
19import java.io.InputStreamReader;
20import java.net.HttpURLConnection;
21import java.net.URL;
22
23public 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
15package com.trouch.webiopi.client;
16
17public 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
15package com.trouch.webiopi.client;
16
17public 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
15package com.trouch.webiopi.client.devices;
16
17import com.trouch.webiopi.client.PiClient;
18
19public 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
15package com.trouch.webiopi.client.devices.analog;
16
17import com.trouch.webiopi.client.PiClient;
18import com.trouch.webiopi.client.devices.Device;
19
20public 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
15package com.trouch.webiopi.client.devices.analog;
16
17import com.trouch.webiopi.client.PiClient;
18
19public 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
15package com.trouch.webiopi.client.devices.analog;
16
17import com.trouch.webiopi.client.PiClient;
18
19public 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
15package com.trouch.webiopi.client.devices.digital;
16
17import com.trouch.webiopi.client.PiClient;
18import com.trouch.webiopi.client.devices.Device;
19
20public 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
15package com.trouch.webiopi.client.devices.digital;
16
17import com.trouch.webiopi.client.PiClient;
18
19public 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
15package com.trouch.webiopi.client.devices.sensor;
16
17import com.trouch.webiopi.client.PiClient;
18import com.trouch.webiopi.client.devices.Device;
19
20public 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
15package com.trouch.webiopi.client.devices.sensor;
16
17import com.trouch.webiopi.client.PiClient;
18import com.trouch.webiopi.client.devices.Device;
19
20public 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
15package com.trouch.webiopi.client.devices.sensor;
16
17import com.trouch.webiopi.client.PiClient;
18import com.trouch.webiopi.client.devices.Device;
19
20public 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
15package com.trouch.webiopi.client.devices.sensor;
16
17import com.trouch.webiopi.client.PiClient;
18import com.trouch.webiopi.client.devices.Device;
19
20public 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
18package org.apache.commons.codec;
19
20/**
21 * Defines common decoding methods for byte array decoders.
22 *
23 * @version $Id$
24 */
25public 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
18package org.apache.commons.codec;
19
20/**
21 * Defines common encoding methods for byte array encoders.
22 *
23 * @version $Id$
24 */
25public 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
18package 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 */
58public 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 */
17package org.apache.commons.codec;
18
19import 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 */
59public 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
18package 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 */
30public 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
18package 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 */
26public 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
18package 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 */
29public 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
18package 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 */
27public 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
18package org.apache.commons.codec.binary;
19
20import 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 */
51public 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
18package org.apache.commons.codec.binary;
19
20import org.apache.commons.codec.BinaryDecoder;
21import org.apache.commons.codec.BinaryEncoder;
22import org.apache.commons.codec.DecoderException;
23import 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 */
34public 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 &gt; 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
18package org.apache.commons.codec.binary;
19
20import java.io.UnsupportedEncodingException;
21import java.nio.charset.Charset;
22
23import org.apache.commons.codec.CharEncoding;
24import 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 */
38public 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]
2default-encoding=ISO-8859-1
3enable-site-specific-quirks=true
4last-window-width=800
5last-window-height=600
6show-menubar=false
7show-navigationbar=false
8enable-html5-database=true
9user-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
diff --git a/play.sh b/play.sh
new file mode 100755
index 0000000..5ec99ac
--- /dev/null
+++ b/play.sh
@@ -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
13sudo /etc/init.d/webiopi start
14
15# Launch the browser
16midori -c `pwd`/midori -a http://localhost:8000/
17
18# Stop WebIOPi service
19sudo /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
31enabled = true
32port = 8000
33
34# File containing sha256(base64("user:password"))
35# Use webiopi-passwd command to generate it
36passwd-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
48enabled = true
49port = 5683
50# Enable CoAP multicast
51multicast = 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/*
2Copyright (c) 2012 Ben Croston / 2012-2013 Eric PTAK
3
4Permission is hereby granted, free of charge, to any person obtaining a copy of
5this software and associated documentation files (the "Software"), to deal in
6the Software without restriction, including without limitation the rights to
7use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8of the Software, and to permit persons to whom the Software is furnished to do
9so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
21*/
22
23#include "Python.h"
24#include "gpio.h"
25#include "cpuinfo.h"
26
27static PyObject *_SetupException;
28static PyObject *_InvalidDirectionException;
29static PyObject *_InvalidChannelException;
30static PyObject *_InvalidPullException;
31
32static PyObject *_gpioCount;
33
34
35static PyObject *_low;
36static PyObject *_high;
37
38static PyObject *_in;
39static PyObject *_out;
40static PyObject *_alt0;
41static PyObject *_alt1;
42static PyObject *_alt2;
43static PyObject *_alt3;
44static PyObject *_alt4;
45static PyObject *_alt5;
46static PyObject *_pwm;
47
48static PyObject *_pud_off;
49static PyObject *_pud_up;
50static PyObject *_pud_down;
51
52static PyObject *_board_revision;
53
54static char* FUNCTIONS[] = {"IN", "OUT", "ALT5", "ALT4", "ALT0", "ALT1", "ALT2", "ALT3", "PWM"};
55static char* PWM_MODES[] = {"none", "ratio", "angle"};
56
57static int module_state = -1;
58
59// setup function run on import of the RPi.GPIO module
60static 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)
80static 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)
102static 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)
126static 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)
167static 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)
191static 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)
222static 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
254static 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
286static 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
319static 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
350static 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
382static 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
414static 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
446static 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
467static 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
496static 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
517static 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
540static 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
563PyMethodDef 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
605static 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
616PyMODINIT_FUNC PyInit_GPIO(void)
617#else
618PyMODINIT_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
714exit:
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/*
2Copyright (c) 2012 Ben Croston
3
4Permission is hereby granted, free of charge, to any person obtaining a copy of
5this software and associated documentation files (the "Software"), to deal in
6the Software without restriction, including without limitation the rights to
7use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8of the Software, and to permit persons to whom the Software is furnished to do
9so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
21*/
22
23#include <stdio.h>
24#include <string.h>
25#include "cpuinfo.h"
26
27char *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
51int 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/*
2Copyright (c) 2012 Ben Croston
3
4Permission is hereby granted, free of charge, to any person obtaining a copy of
5this software and associated documentation files (the "Software"), to deal in
6the Software without restriction, including without limitation the rights to
7use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8of the Software, and to permit persons to whom the Software is furnished to do
9so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
21*/
22
23int 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/*
2Copyright (c) 2012 Ben Croston / 2012-2013 Eric PTAK
3
4Permission is hereby granted, free of charge, to any person obtaining a copy of
5this software and associated documentation files (the "Software"), to deal in
6the Software without restriction, including without limitation the rights to
7use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8of the Software, and to permit persons to whom the Software is furnished to do
9so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
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
49static volatile uint32_t *gpio_map;
50
51struct tspair {
52 struct timespec up;
53 struct timespec down;
54};
55
56static struct pulse gpio_pulses[GPIO_COUNT];
57static struct tspair gpio_tspairs[GPIO_COUNT];
58static pthread_t *gpio_threads[GPIO_COUNT];
59
60void 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
70int 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
94void 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
114void 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
132int 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
146int 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
156void 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
171void 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
189void 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
200void 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
213void 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
224void 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
236void 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
243void 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
255void 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
262void 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
271void 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
279struct pulse* getPulse(int gpio) {
280 return &gpio_pulses[gpio];
281}
282
283//added Eric PTAK - trouch.com
284void* 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
293void 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
307void 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
320int isPWMEnabled(int gpio) {
321 return gpio_threads[gpio] != NULL;
322}
323
324
325void 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/*
2Copyright (c) 2012 Ben Croston / 2012-2013 Eric PTAK
3
4Permission is hereby granted, free of charge, to any person obtaining a copy of
5this software and associated documentation files (the "Software"), to deal in
6the Software without restriction, including without limitation the rights to
7use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
8of the Software, and to permit persons to whom the Software is furnished to do
9so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall be included in all
12copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
20SOFTWARE.
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
50struct pulse {
51 int type;
52 float value;
53};
54
55int setup(void);
56int get_function(int gpio);
57void set_function(int gpio, int function, int pud);
58int input(int gpio);
59void output(int gpio, int value);
60void outputSequence(int gpio, int period, char* sequence);
61struct pulse* getPulse(int gpio);
62void pulseMilli(int gpio, int up, int down);
63void pulseMilliRatio(int gpio, int width, float ratio);
64void pulseMicro(int gpio, int up, int down);
65void pulseMicroRatio(int gpio, int width, float ratio);
66void pulseAngle(int gpio, float angle);
67void pulseRatio(int gpio, float ratio);
68void enablePWM(int gpio);
69void disablePWM(int gpio);
70int isPWMEnabled(int gpio);
71
72void 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 @@
1from setuptools import setup, Extension
2
3classifiers = ['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
14setup(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
16import sys
17file = None
18
19print("WebIOPi passwd file generator")
20if 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()
30else:
31 file = "/etc/webiopi/passwd"
32
33f = open(file, "w")
34_LOGIN = "Enter Login: "
35_PASSWORD = "Enter Password: "
36_CONFIRM = "Confirm password: "
37_DONTMATCH = "Passwords don't match !"
38
39import getpass
40try:
41 login = raw_input(_LOGIN)
42except NameError:
43 login = input(_LOGIN)
44password = getpass.getpass(_PASSWORD)
45password2 = getpass.getpass(_CONFIRM)
46while password != password2:
47 print(_DONTMATCH)
48 password = getpass.getpass(_PASSWORD)
49 password2 = getpass.getpass(_CONFIRM)
50
51from webiopi.utils.crypto import encryptCredentials
52auth = encryptCredentials(login, password)
53print("\nHash: %s" % auth)
54if 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>
13LOG_FILE=/var/log/webiopi
14CONFIG_FILE=/etc/webiopi/config
15
16PATH=/sbin:/usr/sbin:/bin:/usr/bin
17DESC="WebIOPi"
18NAME=webiopi
19HOME=/usr/share/webiopi/htdocs
20DAEMON=/usr/bin/python
21DAEMON_ARGS="-m webiopi -l $LOG_FILE -c $CONFIG_FILE"
22PIDFILE=/var/run/$NAME.pid
23SCRIPTNAME=/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#
42do_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#
61do_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#
87do_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
97case "$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 ;;
153esac
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
2python -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
16from time import sleep
17
18from webiopi.utils.version import BOARD_REVISION, VERSION
19from webiopi.utils.logger import setInfo, setDebug, info, debug, warn, error, exception
20from webiopi.utils.thread import runLoop
21from webiopi.server import Server
22from webiopi.devices.instance import deviceInstance
23from webiopi.decorators.rest import macro
24
25from webiopi.devices import bus as _bus
26
27try:
28 import _webiopi.GPIO as GPIO
29except:
30 pass
31
32
33setInfo()
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
15import sys
16from webiopi.server import Server
17from webiopi.utils.loader import loadScript
18from webiopi.utils.logger import exception, setDebug, info, logToFile
19from webiopi.utils.version import VERSION_STRING
20from webiopi.utils.thread import runLoop, stop
21
22def 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
37def 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
74if __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
15from webiopi.utils.logger import LOGGER
16from webiopi.utils.version import PYTHON_MAJOR
17from webiopi.utils.crypto import encodeCredentials
18from webiopi.protocols.coap import COAPClient, COAPGet, COAPPost, COAPPut, COAPDelete
19
20if PYTHON_MAJOR >= 3:
21 import http.client as httplib
22else:
23 import httplib
24
25class 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
79class PiHttpClient(PiMixedClient):
80 def __init__(self, host, port=8000):
81 PiMixedClient.__init__(self, host, port, -1)
82
83class PiCoapClient(PiMixedClient):
84 def __init__(self, host, port=5683):
85 PiMixedClient.__init__(self, host, -1, port)
86
87class PiMulticastClient(PiMixedClient):
88 def __init__(self, port=5683):
89 PiMixedClient.__init__(self, "224.0.1.123", -1, port)
90
91class 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
99class 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
109class Device(RESTAPI):
110 def __init__(self, client, name, category):
111 RESTAPI.__init__(self, client, "/devices/" + name + "/" + category)
112
113class 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
135class NativeGPIO(GPIO):
136 def __init__(self, client):
137 RESTAPI.__init__(self, client, "/GPIO")
138
139class 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
152class 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
165class 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
175class Sensor(Device):
176 def __init__(self, client, name):
177 Device.__init__(self, client, name, "sensor")
178
179class 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
189class 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
196class Luminosity(Sensor):
197 def getLux(self):
198 return float(self.sendRequest("GET", "/luminosity/lux"))
199
200class 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 @@
1def 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
10def 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
17def 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
15from webiopi.decorators.rest import request, response
16from webiopi.utils.types import M_JSON
17
18class 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
101class 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
132class 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
262DRIVERS = {}
263DRIVERS["ads1x1x"] = ["ADS1014", "ADS1015", "ADS1114", "ADS1115"]
264DRIVERS["mcp3x0x"] = ["MCP3004", "MCP3008", "MCP3204", "MCP3208"]
265DRIVERS["mcp4725"] = ["MCP4725"]
266DRIVERS["mcp492X"] = ["MCP4921", "MCP4922"]
267DRIVERS["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
15from time import sleep
16from webiopi.utils.types import toint, signInteger
17from webiopi.devices.i2c import I2C
18from webiopi.devices.analog import ADC
19
20
21class 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
67class ADS1014(ADS1X1X):
68 def __init__(self, slave=0x48):
69 ADS1X1X.__init__(self, slave, 1, 12, "ADS1014")
70
71class ADS1015(ADS1X1X):
72 def __init__(self, slave=0x48):
73 ADS1X1X.__init__(self, slave, 4, 12, "ADS1015")
74
75class ADS1114(ADS1X1X):
76 def __init__(self, slave=0x48):
77 ADS1X1X.__init__(self, slave, 1, 16, "ADS1114")
78
79class 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
15from webiopi.utils.types import toint
16from webiopi.devices.spi import SPI
17from webiopi.devices.analog import ADC
18
19class 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
34class 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
47class MCP3004(MCP300X):
48 def __init__(self, chip=0):
49 MCP300X.__init__(self, chip, 4, "MCP3004")
50
51class MCP3008(MCP300X):
52 def __init__(self, chip=0):
53 MCP300X.__init__(self, chip, 8, "MCP3008")
54
55class 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
68class MCP3204(MCP320X):
69 def __init__(self, chip=0):
70 MCP320X.__init__(self, chip, 4, "MCP3204")
71
72class 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
15from webiopi.utils.types import toint
16from webiopi.devices.i2c import I2C
17from webiopi.devices.analog import DAC
18
19
20class 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
15from webiopi.utils.types import toint
16from webiopi.devices.spi import SPI
17from webiopi.devices.analog import DAC
18
19class 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
46class MCP4921(MCP492X):
47 def __init__(self, chip=0):
48 MCP492X.__init__(self, chip, 1)
49
50class 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
15import time
16from webiopi.utils.types import toint
17from webiopi.devices.i2c import I2C
18from webiopi.devices.analog import PWM
19
20class 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
15import os
16import time
17import subprocess
18
19from webiopi.utils.logger import debug, info
20
21BUSLIST = {
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
28def loadModule(module):
29 debug("Loading module : %s" % module)
30 subprocess.call(["modprobe", module])
31
32def unloadModule(module):
33 subprocess.call(["modprobe", "-r", module])
34
35def 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
46def unloadModules(bus):
47 info("Unloading %s modules" % bus)
48 for module in BUSLIST[bus]["modules"]:
49 unloadModule(module)
50 BUSLIST[bus]["enabled"] = False
51
52def __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
60def 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
73def checkAllBus():
74 for bus in BUSLIST:
75 if modulesLoaded(bus):
76 BUSLIST[bus]["enabled"] = True
77
78class 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
15from webiopi.decorators.rest import request, response
16from webiopi.utils.types import M_JSON
17
18class 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
141DRIVERS = {}
142DRIVERS["mcp23XXX"] = ["MCP23008", "MCP23009", "MCP23017", "MCP23018", "MCP23S08", "MCP23S09", "MCP23S17", "MCP23S18"]
143DRIVERS["pcf8574" ] = ["PCF8574", "PCF8574A"]
144DRIVERS["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
15from webiopi.devices.onewire import OneWire
16from webiopi.devices.digital import GPIOPort
17
18class 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
15from webiopi.utils.types import M_JSON
16from webiopi.utils.logger import debug
17from webiopi.devices.digital import GPIOPort
18from webiopi.decorators.rest import request, response
19try:
20 import _webiopi.GPIO as GPIO
21except:
22 pass
23
24EXPORT = []
25
26class 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
15from webiopi.utils.types import toint
16from webiopi.devices.i2c import I2C
17from webiopi.devices.spi import SPI
18from webiopi.devices.digital import GPIOPort
19
20class 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
87class 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
96class MCP23008(MCP230XX):
97 def __init__(self, slave=0x20):
98 MCP230XX.__init__(self, slave, 8, "MCP23008")
99
100class MCP23009(MCP230XX):
101 def __init__(self, slave=0x20):
102 MCP230XX.__init__(self, slave, 8, "MCP23009")
103
104class MCP23017(MCP230XX):
105 def __init__(self, slave=0x20):
106 MCP230XX.__init__(self, slave, 16, "MCP23017")
107
108class MCP23018(MCP230XX):
109 def __init__(self, slave=0x20):
110 MCP230XX.__init__(self, slave, 16, "MCP23018")
111
112class 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
138class MCP23S08(MCP23SXX):
139 def __init__(self, chip=0, slave=0x20):
140 MCP23SXX.__init__(self, chip, slave, 8, "MCP23S08")
141
142class MCP23S09(MCP23SXX):
143 def __init__(self, chip=0, slave=0x20):
144 MCP23SXX.__init__(self, chip, slave, 8, "MCP23S09")
145
146class MCP23S17(MCP23SXX):
147 def __init__(self, chip=0, slave=0x20):
148 MCP23SXX.__init__(self, chip, slave, 16, "MCP23S17")
149
150class 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
15from webiopi.utils.types import toint
16from webiopi.devices.i2c import I2C
17from webiopi.devices.digital import GPIOPort
18
19class 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
67class 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
15import fcntl
16
17from webiopi.utils.version import BOARD_REVISION
18from 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
26I2C_RETRIES = 0x0701 # number of times a device address should
27 # be polled when not acknowledging
28I2C_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
33I2C_SLAVE = 0x0703 # Use this slave address
34I2C_SLAVE_FORCE = 0x0706 # Use this slave address, even if it
35 # is already in use by a driver!
36I2C_TENBIT = 0x0704 # 0 for 7 bit addrs, != 0 for 10 bit
37
38I2C_FUNCS = 0x0705 # Get the adapter functionality mask
39
40I2C_RDWR = 0x0707 # Combined R/W transfer (one STOP only)
41
42I2C_PEC = 0x0708 # != 0 to use PEC with SMBus
43I2C_SMBUS = 0x0720 # SMBus transfer */
44
45
46class 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 @@
1DEVICES = {}
2def 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 @@
1import imp
2from webiopi.utils import logger
3from webiopi.utils import types
4from webiopi.devices.instance import DEVICES
5
6from webiopi.devices import serial, digital, analog, sensor, shield
7
8PACKAGES = [serial, digital, analog, sensor, shield]
9def 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
21def 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
31def 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
48def 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
56def 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
15import os
16from webiopi.devices.bus import Bus, loadModule
17
18EXTRAS = {
19 "TEMP": {"loaded": False, "module": "w1-therm"},
20 "2408": {"loaded": False, "module": "w1_ds2408"}
21
22}
23
24def loadExtraModule(name):
25 if EXTRAS[name]["loaded"] == False:
26 loadModule(EXTRAS[name]["module"])
27 EXTRAS[name]["loaded"] = True
28
29class 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
15from webiopi.utils.types import toint
16from webiopi.utils.types import M_JSON
17from webiopi.devices.instance import deviceInstance
18from webiopi.decorators.rest import request, response
19
20class 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
65class 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
123class 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
135class 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
172DRIVERS = {}
173DRIVERS["bmp085"] = ["BMP085"]
174DRIVERS["onewiretemp"] = ["DS1822", "DS1825", "DS18B20", "DS18S20", "DS28EA00"]
175DRIVERS["tmpXXX"] = ["TMP75", "TMP102", "TMP275"]
176DRIVERS["tslXXXX"] = ["TSL2561", "TSL2561CS", "TSL2561T", "TSL4531", "TSL45311", "TSL45313", "TSL45315", "TSL45317"]
177DRIVERS["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
15import time
16from webiopi.utils.types import signInteger
17from webiopi.devices.i2c import I2C
18from webiopi.devices.sensor import Temperature, Pressure
19
20class 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
15from webiopi.devices.onewire import OneWire
16from webiopi.devices.sensor import Temperature
17
18class 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
40class DS18S20(OneWireTemp):
41 def __init__(self, slave=None):
42 OneWireTemp.__init__(self, slave, 0x10, "DS18S20")
43
44class DS1822(OneWireTemp):
45 def __init__(self, slave=None):
46 OneWireTemp.__init__(self, slave, 0x22, "DS1822")
47
48class DS18B20(OneWireTemp):
49 def __init__(self, slave=None):
50 OneWireTemp.__init__(self, slave, 0x28, "DS18B20")
51
52class DS1825(OneWireTemp):
53 def __init__(self, slave=None):
54 OneWireTemp.__init__(self, slave, 0x3B, "DS1825")
55
56class 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
15from webiopi.utils.types import toint, signInteger
16from webiopi.devices.i2c import I2C
17from webiopi.devices.sensor import Temperature
18
19class 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
37class 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
54class 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
21from webiopi.utils.types import toint
22from webiopi.devices.i2c import I2C
23from webiopi.devices.sensor import Luminosity
24
25class 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
61class 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
140class 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
159class 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
178class 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
184class 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
232class TSL45311(TSL4531):
233 def __init__(self, slave=0x39, time=400):
234 TSL4531.__init__(self, slave, time, "TSL45311")
235
236class TSL45313(TSL4531):
237 def __init__(self, slave=0x39, time=400):
238 TSL4531.__init__(self, slave, time, "TSL45313")
239
240class TSL45315(TSL4531):
241 def __init__(self, slave=0x29, time=400):
242 TSL4531.__init__(self, slave, time, "TSL45315")
243
244class 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
22import time
23from webiopi.devices.i2c import I2C
24from webiopi.devices.sensor import Luminosity, Distance
25from webiopi.utils.types import toint
26from webiopi.utils.logger import debug
27
28class 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
15import os
16import fcntl
17import struct
18import termios
19
20from webiopi.devices.bus import Bus
21from webiopi.decorators.rest import request
22
23TIOCINQ = hasattr(termios, 'FIONREAD') and termios.FIONREAD or 0x541B
24TIOCM_zero_str = struct.pack('I', 0)
25
26class 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
15DRIVERS = {}
16DRIVERS["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
15from webiopi.utils.types import M_JSON
16from webiopi.devices.digital.mcp23XXX import MCP23S17
17from webiopi.decorators.rest import request, response
18
19
20class 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
15import fcntl
16import array
17import ctypes
18import struct
19
20from webiopi.utils.version import PYTHON_MAJOR
21from 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
38def _IOC(direction,t,nr,size):
39 return (((direction) << _IOC_DIRSHIFT) |
40 ((size) << _IOC_SIZESHIFT) |
41 ((t) << _IOC_TYPESHIFT) |
42 ((nr) << _IOC_NRSHIFT))
43def _IOR(t, number, size):
44 return _IOC(_IOC_READ, t, number, size)
45def _IOW(t, number, size):
46 return _IOC(_IOC_WRITE, t, number, size)
47
48SPI_CPHA = 0x01
49SPI_CPOL = 0x02
50
51SPI_MODE_0 = (0|0)
52SPI_MODE_1 = (0|SPI_CPHA)
53SPI_MODE_2 = (SPI_CPOL|0)
54SPI_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
64SPI_IOC_MAGIC = ord('k')
65
66def 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)
70SPI_IOC_RD_MODE = _IOR(SPI_IOC_MAGIC, 1, 1)
71SPI_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)
79SPI_IOC_RD_BITS_PER_WORD = _IOR(SPI_IOC_MAGIC, 3, 1)
80SPI_IOC_WR_BITS_PER_WORD = _IOW(SPI_IOC_MAGIC, 3, 1)
81
82# Read / Write SPI device default max speed hz
83SPI_IOC_RD_MAX_SPEED_HZ = _IOR(SPI_IOC_MAGIC, 4, 4)
84SPI_IOC_WR_MAX_SPEED_HZ = _IOW(SPI_IOC_MAGIC, 4, 4)
85
86class 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
15from webiopi.utils.version import PYTHON_MAJOR
16from webiopi.utils.logger import info, exception
17
18import socket
19import struct
20import logging
21import threading
22
23M_PLAIN = "text/plain"
24M_JSON = "application/json"
25
26if PYTHON_MAJOR >= 3:
27 from urllib.parse import urlparse
28else:
29 from urlparse import urlparse
30
31try :
32 import _webiopi.GPIO as GPIO
33except:
34 pass
35
36def HTTPCode2CoAPCode(code):
37 return int(code/100) * 32 + (code%100)
38
39
40class 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
69class 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
102class 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
318class 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
334class COAPGet(COAPRequest):
335 def __init__(self, uri):
336 COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.GET, uri)
337
338class COAPPost(COAPRequest):
339 def __init__(self, uri):
340 COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.POST, uri)
341
342class COAPPut(COAPRequest):
343 def __init__(self, uri):
344 COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.PUT, uri)
345
346class COAPDelete(COAPRequest):
347 def __init__(self, uri):
348 COAPRequest.__init__(self, COAPMessage.CON, COAPRequest.DELETE, uri)
349
350class 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
407class 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
427class 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
498class 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
15import os
16import threading
17import codecs
18import mimetypes as mime
19import logging
20
21from webiopi.utils.version import VERSION_STRING, PYTHON_MAJOR
22from webiopi.utils.logger import info, exception
23from webiopi.utils.crypto import encrypt
24from webiopi.utils.types import str2bool
25
26if PYTHON_MAJOR >= 3:
27 import http.server as BaseHTTPServer
28else:
29 import BaseHTTPServer
30
31try :
32 import _webiopi.GPIO as GPIO
33except:
34 pass
35
36WEBIOPI_DOCROOT = "/usr/share/webiopi/htdocs"
37
38class 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
85class 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
15from webiopi.utils import types
16from webiopi.utils import logger
17from webiopi.utils.types import M_JSON, M_PLAIN
18from webiopi.utils.version import BOARD_REVISION, VERSION_STRING, MAPPING
19from webiopi.devices import manager
20from webiopi.devices import instance
21from webiopi.devices.bus import BUSLIST
22
23try:
24 import _webiopi.GPIO as GPIO
25except:
26 pass
27
28MACROS = {}
29
30class 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
15import os
16import time
17import socket
18
19from webiopi.utils.config import Config
20from webiopi.utils import loader
21from webiopi.utils import logger
22from webiopi.utils import crypto
23from webiopi.devices import manager
24from webiopi.protocols import rest
25from webiopi.protocols import http
26from webiopi.protocols import coap
27from webiopi.devices.digital.gpio import NativeGPIO
28
29def 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
39class 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 @@
1from webiopi.utils.version import PYTHON_MAJOR
2
3if PYTHON_MAJOR >= 3:
4 import configparser as parser
5else:
6 import ConfigParser as parser
7
8class 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 @@
1import base64
2import hashlib
3from webiopi.utils.version import PYTHON_MAJOR
4
5def 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
13def encrypt(value):
14 return hashlib.sha256(value).hexdigest()
15
16def 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 @@
1import imp
2import webiopi.utils.logger as logger
3import webiopi.utils.thread as thread
4SCRIPTS = {}
5
6def 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
21def 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 @@
1import logging
2
3LOG_FORMATTER = logging.Formatter(fmt='%(asctime)s - %(name)s - %(levelname)s - %(message)s', datefmt="%Y-%m-%d %H:%M:%S")
4ROOT_LOGGER = logging.getLogger()
5ROOT_LOGGER.setLevel(logging.WARN)
6
7CONSOLE_HANDLER = logging.StreamHandler()
8CONSOLE_HANDLER.setFormatter(LOG_FORMATTER)
9ROOT_LOGGER.addHandler(CONSOLE_HANDLER)
10
11LOGGER = logging.getLogger("WebIOPi")
12
13def setInfo():
14 ROOT_LOGGER.setLevel(logging.INFO)
15
16def setDebug():
17 ROOT_LOGGER.setLevel(logging.DEBUG)
18
19def debugEnabled():
20 return ROOT_LOGGER.level == logging.DEBUG
21
22def logToFile(filename):
23 FILE_HANDLER = logging.FileHandler(filename)
24 FILE_HANDLER.setFormatter(LOG_FORMATTER)
25 ROOT_LOGGER.addHandler(FILE_HANDLER)
26
27def debug(message):
28 LOGGER.debug(message)
29
30def info(message):
31 LOGGER.info(message)
32
33def warn(message):
34 LOGGER.warn(message)
35
36def error(message):
37 LOGGER.error(message)
38
39def exception(message):
40 LOGGER.exception(message)
41
42def 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 @@
1import time
2import signal
3import threading
4from webiopi.utils import logger
5
6RUNNING = False
7TASKS = []
8
9class 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
27def 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
36def 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 @@
1import json
2from webiopi.utils import logger
3
4M_PLAIN = "text/plain"
5M_JSON = "application/json"
6
7def 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
13def str2bool(value):
14 return (value == "1") or (value == "true") or (value == "True") or (value == "yes") or (value == "Yes")
15
16def 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
27def 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 @@
1import re
2import sys
3
4VERSION = '0.6.2'
5VERSION_STRING = "WebIOPi/%s/Python%d.%d" % (VERSION, sys.version_info.major, sys.version_info.minor)
6PYTHON_MAJOR = sys.version_info.major
7BOARD_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
14try:
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
26except:
27 pass
28
29MAPPING = _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
4SEARCH="python python3"
5FOUND=""
6INSTALLED=""
7
8if [ "$#" = "1" ]; then
9 command="$1"
10else
11 command="none"
12fi
13
14echo
15echo "Installing WebIOPi..."
16echo
17
18if [ "$command" != "skip-apt" ]; then
19 echo "Updating apt package list..."
20 apt-get update
21 echo
22fi
23
24# Install Python library
25cd python
26
27# Look up for installed python
28for 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
57done
58
59# Go back to the root folder
60cd ..
61
62# Ensure WebIOPi is installed to continue
63if [ -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
71fi
72
73# Select greater python version
74for python in $INSTALLED; do
75 echo $python > /dev/null
76done
77
78# Update HTML resources
79echo "Copying HTML resources..."
80mkdir /usr/share/webiopi 2>/dev/null 1>/dev/null
81cp -rfv htdocs /usr/share/webiopi
82echo
83
84# Add config file if it does not exist
85if [ ! -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
89fi
90
91# Add passwd file if it does not exist
92if [ ! -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
96fi
97
98# Add service/daemon script
99#if [ ! -f "/etc/init.d/webiopi" ]; then
100echo "Installing startup script..."
101cp -rf python/webiopi.init.sh /etc/init.d/webiopi
102sed -i "s/python/$python/g" /etc/init.d/webiopi
103chmod 0755 /etc/init.d/webiopi
104
105# Add webiopi command
106echo "Installing webiopi command..."
107cp -rf python/webiopi.sh /usr/bin/webiopi
108sed -i "s/python/$python/g" /usr/bin/webiopi
109chmod 0755 /usr/bin/webiopi
110
111# Add webiopi-passwd command
112echo "Installing webiopi-passwd command..."
113cp -rf python/webiopi-passwd.py /usr/bin/webiopi-passwd
114sed -i "s/python/$python/g" /usr/bin/webiopi-passwd
115chmod 0755 /usr/bin/webiopi-passwd
116
117# Display WebIOPi usages
118echo
119echo "WebIOPi successfully installed"
120echo "* To start WebIOPi foreground\t: sudo webiopi [-h] [-c config] [-l log] [-s script] [-d] [port]"
121echo
122echo "* To start WebIOPi background\t: sudo /etc/init.d/webiopi start"
123echo "* To start WebIOPi at boot\t: sudo update-rc.d webiopi defaults"
124echo
125echo "* Look in `pwd`/examples for Python library usage examples"
126echo