Version 2.0.0

pull/173/head
Connor Rigby 2016-10-04 09:01:23 -07:00
parent b0f40cc909
commit 0d384c7019
154 changed files with 3195 additions and 3051 deletions

60
.gitignore vendored
View File

@ -1,49 +1,11 @@
secrets.txt
*.pstore
*.gem
*.rbc
*.swp
.bundle/*
.DS_Store
.idea/*
.loadpath
.project
.ruby-gemset
.ruby-version
/.bundle
/.bundle/
/.yardoc/
/_yardoc/
/coverage/
/coverage/
*.sqlite3
*.sqlite3-journal
/doc/
/InstalledFiles
/lib/.build
/lib/bundler/man/
/log/*.log
*.log
/pkg/
/rdoc/
/spec/reports/
/test/tmp/
/test/version_tmp/
/tmp
/tmp/
config.yml
coverage/**
credentials.yml
db/settings.yml
doc/*
doc/**/*
log/*
notes.rb
/lib/.build
write_db_settings.rb
snippet.coffee
snippets.rb
tmp/**/*
*.rb~
serial_port.txt
*credentials*
# App artifacts
/_build
/db
/deps
/*.ez
# Nerves images
/_images
# Generate on crash by the VM
erl_crash.dump

View File

@ -1 +0,0 @@
To get started, <a href="https://www.clahub.com/agreements/FarmBot/farmbot-raspberry-pi-controller">sign the Contributor License Agreement</a>.

19
Gemfile
View File

@ -1,19 +0,0 @@
source 'https://rubygems.org'
gem 'eventmachine', '1.2.0.1'
gem 'active_record_migrations'
gem 'farmbot-resource', '0.6.0' # REST API client for farmbot.
gem 'farmbot-serial', '1.1.2' # Arduino communication.
gem 'god' # For autorestarting on crashes and stuff.
gem 'ice_cube' # For scheduling of pre-programmed schedule
gem 'liquid' # For custom programming of schedules.
gem 'mutations', '0.7.2' # for e-v-e-r-y-t-h-i-n-g
gem 'settingslogic' # For settings.yml file
gem 'highline' # For setup.rb.
gem 'sqlite3' # For storing schedule data locally.
gem 'em-mqtt', '~> 0.0.5' # For browser <--> device communication.
group :test, :development do
gem 'rspec'
gem 'simplecov'
end

View File

@ -1,139 +0,0 @@
GEM
remote: https://rubygems.org/
specs:
actionpack (4.2.3)
actionview (= 4.2.3)
activesupport (= 4.2.3)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
actionview (4.2.3)
activesupport (= 4.2.3)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
active_record_migrations (4.2.3.1)
activerecord (= 4.2.3)
railties (= 4.2.3)
rake
activemodel (4.2.3)
activesupport (= 4.2.3)
builder (~> 3.1)
activerecord (4.2.3)
activemodel (= 4.2.3)
activesupport (= 4.2.3)
arel (~> 6.0)
activesupport (4.2.3)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
arel (6.0.3)
builder (3.2.2)
diff-lcs (1.2.5)
docile (1.1.5)
domain_name (0.5.20160310)
unf (>= 0.0.5, < 1.0.0)
em-mqtt (0.0.5)
eventmachine
mqtt (>= 0.3.0)
erubis (2.7.0)
eventmachine (1.2.0.1)
farmbot-resource (0.6.0)
rest-client (~> 1.8)
farmbot-serial (1.1.2)
eventmachine
serialport
god (0.13.6)
highline (1.7.8)
http-cookie (1.0.2)
domain_name (~> 0.5)
i18n (0.7.0)
ice_cube (0.13.0)
json (1.8.3)
liquid (3.0.6)
loofah (2.0.2)
nokogiri (>= 1.5.9)
mime-types (2.99.1)
mini_portile (0.6.2)
minitest (5.8.0)
mqtt (0.3.1)
mutations (0.7.2)
activesupport
netrc (0.11.0)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
rack (1.6.4)
rack-test (0.6.3)
rack (>= 1.0)
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
rails-dom-testing (1.0.6)
activesupport (>= 4.2.0.beta, < 5.0)
nokogiri (~> 1.6.0)
rails-deprecated_sanitizer (>= 1.0.1)
rails-html-sanitizer (1.0.2)
loofah (~> 2.0)
railties (4.2.3)
actionpack (= 4.2.3)
activesupport (= 4.2.3)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rake (10.4.2)
rest-client (1.8.0)
http-cookie (>= 1.0.2, < 2.0)
mime-types (>= 1.16, < 3.0)
netrc (~> 0.7)
rspec (3.3.0)
rspec-core (~> 3.3.0)
rspec-expectations (~> 3.3.0)
rspec-mocks (~> 3.3.0)
rspec-core (3.3.2)
rspec-support (~> 3.3.0)
rspec-expectations (3.3.1)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.3.0)
rspec-mocks (3.3.2)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.3.0)
rspec-support (3.3.0)
serialport (1.3.1)
settingslogic (2.0.9)
simplecov (0.10.0)
docile (~> 1.1.0)
json (~> 1.8)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.0)
sqlite3 (1.3.10)
thor (0.19.1)
thread_safe (0.3.5)
tzinfo (1.2.2)
thread_safe (~> 0.1)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.2)
PLATFORMS
ruby
DEPENDENCIES
active_record_migrations
em-mqtt (~> 0.0.5)
eventmachine (= 1.2.0.1)
farmbot-resource (= 0.6.0)
farmbot-serial (= 1.1.2)
god
highline
ice_cube
liquid
mutations (= 0.7.2)
rspec
settingslogic
simplecov
sqlite3
BUNDLED WITH
1.12.5

9
LICENSE 100644
View File

@ -0,0 +1,9 @@
The MIT License
Copyright (c) 2016 Farmbot Project
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

181
README.md
View File

@ -1,137 +1,68 @@
# FarmBot Software for the Raspberry Pi
# FarmBot Software for the RaspBerry Pi 3
The "brains" of Farmbot. Responsible for receiving the commands from users or the farmbot-web-app. It executes them and reports back the results to any subscribed user(s).
The "brains" of Farmbot. Responsible for receiving the commands from users or the farmbot-web-app. It executes them and report back the results to any subscribed user(s).
## Technical Stuff
* Written in Elixir with the Nerves Framework
* Operation scheduling is not working yet
* Device status infor, such as X,Y,Z and calibration data is stored on the Data partition Nerves allows.
* Backups to the cloud provided by ["Farmbot Web API"]("https://github.com/farmbot/farmbot-web-api")
* Messaging happens vie ["MQTT"]("https://github.com/farmbot/mqtt-gateway")
Technical Stuff
---------------
# Running in production
You can download the latest release from ["Here"]("https://github.com/FarmBot/farmbot-raspberry-pi-controller/releases")
Make sure you download the `.img` file.
## Windows users
* You can use ["Win32 Disk Imager"]("https://sourceforge.net/projects/win32diskimager/")
* Select the `.img` file you downloaded
* Select your sdcard's drive letter
* Click `write`
* Written in Ruby.
* Operation scheduling data is stored in SQLite 3.
* Device status info, such as X, Y, Z and calibration data is stored via [PStore](http://ruby-doc.org/stdlib-1.9.2/libdoc/pstore/rdoc/PStore.html)
* Backups to the cloud provided by [Farmbot Web API](https://github.com/farmbot/farmbot-web-api).
* Messaging happens via [MQTT](http://mqtt.org/).
* Communicates with Arduino hardware using the [farmbot-serial gem](https://github.com/FarmBot/farmbot-serial)
## Linux / OSX / UNIX
* `dd if=img_file of=/dev/sdX`
* where img_file is the path to you `.img` file, and X is your device's drive letter.
Running in production
---------------------
## Running
* Plug your SD Card into your RPi3
* Plug your Arduino into your RPi3
* Plug your power into your RPi3
* From a WiFi enabled device, search for the SSID `FarmbotConfigurator`
* Connect to that and open a web browser to ["http://192.168.24.1/"]("http://192.168.24.1")
* Follow the on screen instruction
* Profit???
```
bundle install
ruby farmbot.rb
# Building
Ok its actually really easy once your environment is set up. Let me prefix this with this simple phrase. ["YOU NEED LINUX"]("http://www.whylinuxisbetter.net/") I am sorry. It is just the bottom line. A VM works fine, a dual boot environment works. even OpenSuse works. But `bash for windows` does not work. `Cygwin` does not work. and for the love of all things development ready, `osx` does not work. You need Linux to build Linux. So with that rant out of the way, and ready for revision here are the steps to build:
* Install Elixir and Erlang. (Shameless plug for ASDF) ([HEY TRY ME?]("https://gist.github.com/ConnorRigby/8a8bffff935d1a43cd74c4b8cf28a845"))
* install [`Nerves`]("https://hexdocs.pm/nerves/installation.html") (and all the things it tells you to install there)
* clone and cd into this directory.
* plug an sdcard into your machine.
``` bash
MIX_ENV=prod mix deps.get
MIX_ENV=prod mix firmware
MIX_ENV=prod mix firmware.burn
```
Running on Local
----------------
If you're running your own local [farmbot web app](https://github.com/farmbot/farmbot-web-app)
`FBENV=development ruby farmbot.rb`
Installation on Raspberry Pi
----------------------------
Installation on a Raspberry Pi 3 will take about 30 minutes.
### Update the RPi
```
sudo apt-get update
You can also run locally (not on an RPI3) (This works on windows)
``` bash
export MIX_ENV=dev
rm -rf _deps build _images
mix deps.get
iex -S mix
```
You should only need to do the first two commands once.
### Install Ruby 2.2
## Debugging on hardware
The Rpi will boot up with an Iex console on the hardware UART. If you need to debug something this is the easiest to get too.
If you do not happen to have a 3.3v FTDI cable, you can build the firmware with the console on HDMI. In the `erlinit.config` file you can change `-c ttyS0` to `-c ttyS1`. This requires you to plug a usb mouse, keyboard and monitor into your pi.
This gem requires Ruby 2.2 minimum. Later versions will work fine as well. As of this writing, a Pi is loaded with 1.9.3 by default.
## Other stuff
You can connect IEx to the running pi by doing
`iex -name console@localhost --remsh farmbot@<FARMBOT IP ADDRESS> --cookie democookie`
Debug message still only will print to UART or HDMI (whichever you have configured)
To remove the old version of Ruby that comes with most RPi distributions:
If you are frequently building firmware, removing the sdcard and writing the build every time gets pretty old. You can upload firmware to an already running farmbot one of two ways after you run a successful `mix firmware`
0. You can upload the image to the pi using CURL or similar. `curl -T _images/rpi3/fw.fw "http://$RPI_IP_ADDRESS:8988/firmware" -H "Content-Type: application/x-firmware" -H "X-Reboot: true"`
0. Or you can host the .fw file on your pc using a webserver ie `npm install serve` and download it from the pi.This should be ran ON THE PI ITSELF `Downloader.download_and_install_update("http://<DEV_PC_IP_ADDRESS>/WHEREVER YOUR FILE IS")`
```
sudo apt-get remove ruby* --purge
```
To upgrade your ruby version, try this:
```
gpg --keyserver hkp://keys.gnupg.net --recv-keys 409B6B1796C275462A1703113804BB82D39DC0E3
curl -L https://get.rvm.io | bash -s stable --ruby
```
This will take about 2 hours a standard Raspberry Pi 2, or about 20 minutes on a Raspberry Pi 3.
### Clone, install and run
**NOTE:** We tag releases when they are stable. The latest version (on master) is not guaranteed to be stable.
See [releases](https://github.com/FarmBot/farmbot-raspberry-pi-controller/releases) to find a stable release.
**OPTION A:** For less stable "edge" version:
```
git clone https://github.com/FarmBot/farmbot-raspberry-pi-controller
```
**OPTION B:** For stable release 1.1:
```
git clone -b 'alpha-1.1' --single-branch https://github.com/FarmBot/farmbot-raspberry-pi-controller
```
**REQUIRED FOR EITHER:** (takes about 10 minutes on an RPi 3)
```
cd farmbot-raspberry-pi-controller
gem install bundler
bundle install
rake db:setup
```
#### Set up the device:
Go to the [My Farmbot Website](http://my.farmbot.io) (or your private server) and sign up for a Farmbot account.
Then from within the `farmbot-raspberry-pi-controller` project directory run:
```bash
ruby setup.rb
```
#### Report problems:
We can't fix issues we don't know about. If you are having issues with setup, please [raise an issue with us](https://github.com/FarmBot/farmbot-raspberry-pi-controller/issues/new). This helps us identify confusing steps, common setup issues and other problems.
Arduino
-------
You will need to flash your Arduino with custom firmware. For instructions on how to do this, see [the FarmBot-Arduino github page](https://github.com/FarmBot/farmbot-arduino-firmware)
Contributors
-------
See full list [here](https://github.com/FarmBot/farmbot-raspberry-pi-controller/graphs/contributors).
License
-------
The MIT License
Copyright (c) 2016 Farmbot Project
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
'Software'), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# Tests
Test coverage is not very good right now but you should be able to run `mix test --no-start` and see tests run.

View File

@ -1,10 +0,0 @@
require 'active_record_migrations'
ActiveRecordMigrations.configure do |c|
c.database_configuration = {
'development' => {'adapter' => 'sqlite3',
'database' => 'db/db.sqlite3'},
}
end
ActiveRecordMigrations.load_tasks

1
VERSION 100644
View File

@ -0,0 +1 @@
2.0.0

View File

@ -0,0 +1,2 @@
use Mix.Config
import_config "#{Mix.env}.exs"

9
config/dev.exs 100644
View File

@ -0,0 +1,9 @@
use Mix.Config
config :uart,
baud: 115200
config :fb,
ro_path: "/tmp"
config :json_rpc,
transport: MqttHandler

14
config/prod.exs 100644
View File

@ -0,0 +1,14 @@
use Mix.Config
config :nerves, :firmware,
rootfs_additions: "config/rootfs-additions-#{Mix.Project.config[:target]}",
hardware: "config/rootfs-additions-#{Mix.Project.config[:target]}"
config :uart,
baud: 115200
config :fb,
ro_path: "/root",
update_server: "http://192.168.29.154:4040"
config :json_rpc,
transport: MqttHandler

View File

@ -0,0 +1,41 @@
# Additional configuration for erlinit
# Turn on the debug prints
#-v
# Specify the UART port that the shell should use.
-c ttyS0
# If more than one tty are available, always warn if the user is looking at
# the wrong one.
--warn-unused-tty
# Use dtach to capture the iex session so that it can be redirected
# to the app's GUI
#-s "/usr/bin/dtach -N /tmp/iex_prompt"
# Specify the user and group IDs for the Erlang VM
#--uid 100
#--gid 200
# Uncomment to hang the board rather than rebooting when Erlang exits
--hang-on-exit
# Optionally run a program if the Erlang VM exits
--run-on-exit /bin/sh
# Enable UTF-8 filename handling in Erlang and custom inet configuration
-e LANG=en_US.UTF-8;LANGUAGE=en;ERL_INETRC=/etc/erl_inetrc
# Mount the application partition
# See http://www.linuxfromscratch.org/lfs/view/6.3/chapter08/fstab.html about
# ignoring warning the Linux kernel warning about using UTF8 with vfat.
-m /dev/mmcblk0p3:/root:vfat::
# Erlang release search path
-r /srv/erlang
# Assign a unique hostname based on the board id
-d "/usr/bin/boardid -b rpi -n 4"
-n nerves-%.4s

View File

@ -0,0 +1,6 @@
interface=wlan0
ssid=FarmbotConfigurator
hw_mode=g
channel=6
auth_algs=1
wmm_enabled=0

View File

@ -0,0 +1,2 @@
127.0.0.1 localhost
192.168.29.154 mqtt

View File

@ -0,0 +1,318 @@
/*
AngularJS v1.5.8
(c) 2010-2016 Google, Inc. http://angularjs.org
License: MIT
*/
(function(C){'use strict';function N(a){return function(){var b=arguments[0],d;d="["+(a?a+":":"")+b+"] http://errors.angularjs.org/1.5.8/"+(a?a+"/":"")+b;for(b=1;b<arguments.length;b++){d=d+(1==b?"?":"&")+"p"+(b-1)+"=";var c=encodeURIComponent,e;e=arguments[b];e="function"==typeof e?e.toString().replace(/ \{[\s\S]*$/,""):"undefined"==typeof e?"undefined":"string"!=typeof e?JSON.stringify(e):e;d+=c(e)}return Error(d)}}function ta(a){if(null==a||Va(a))return!1;if(L(a)||G(a)||F&&a instanceof F)return!0;
var b="length"in Object(a)&&a.length;return T(b)&&(0<=b&&(b-1 in a||a instanceof Array)||"function"==typeof a.item)}function q(a,b,d){var c,e;if(a)if(z(a))for(c in a)"prototype"==c||"length"==c||"name"==c||a.hasOwnProperty&&!a.hasOwnProperty(c)||b.call(d,a[c],c,a);else if(L(a)||ta(a)){var f="object"!==typeof a;c=0;for(e=a.length;c<e;c++)(f||c in a)&&b.call(d,a[c],c,a)}else if(a.forEach&&a.forEach!==q)a.forEach(b,d,a);else if(sc(a))for(c in a)b.call(d,a[c],c,a);else if("function"===typeof a.hasOwnProperty)for(c in a)a.hasOwnProperty(c)&&
b.call(d,a[c],c,a);else for(c in a)ua.call(a,c)&&b.call(d,a[c],c,a);return a}function tc(a,b,d){for(var c=Object.keys(a).sort(),e=0;e<c.length;e++)b.call(d,a[c[e]],c[e]);return c}function uc(a){return function(b,d){a(d,b)}}function Yd(){return++pb}function Pb(a,b,d){for(var c=a.$$hashKey,e=0,f=b.length;e<f;++e){var g=b[e];if(D(g)||z(g))for(var h=Object.keys(g),k=0,l=h.length;k<l;k++){var m=h[k],n=g[m];d&&D(n)?da(n)?a[m]=new Date(n.valueOf()):Wa(n)?a[m]=new RegExp(n):n.nodeName?a[m]=n.cloneNode(!0):
Qb(n)?a[m]=n.clone():(D(a[m])||(a[m]=L(n)?[]:{}),Pb(a[m],[n],!0)):a[m]=n}}c?a.$$hashKey=c:delete a.$$hashKey;return a}function S(a){return Pb(a,va.call(arguments,1),!1)}function Zd(a){return Pb(a,va.call(arguments,1),!0)}function Z(a){return parseInt(a,10)}function Rb(a,b){return S(Object.create(a),b)}function A(){}function Xa(a){return a}function ha(a){return function(){return a}}function vc(a){return z(a.toString)&&a.toString!==ma}function y(a){return"undefined"===typeof a}function w(a){return"undefined"!==
typeof a}function D(a){return null!==a&&"object"===typeof a}function sc(a){return null!==a&&"object"===typeof a&&!wc(a)}function G(a){return"string"===typeof a}function T(a){return"number"===typeof a}function da(a){return"[object Date]"===ma.call(a)}function z(a){return"function"===typeof a}function Wa(a){return"[object RegExp]"===ma.call(a)}function Va(a){return a&&a.window===a}function Ya(a){return a&&a.$evalAsync&&a.$watch}function Ga(a){return"boolean"===typeof a}function $d(a){return a&&T(a.length)&&
ae.test(ma.call(a))}function Qb(a){return!(!a||!(a.nodeName||a.prop&&a.attr&&a.find))}function be(a){var b={};a=a.split(",");var d;for(d=0;d<a.length;d++)b[a[d]]=!0;return b}function wa(a){return Q(a.nodeName||a[0]&&a[0].nodeName)}function Za(a,b){var d=a.indexOf(b);0<=d&&a.splice(d,1);return d}function pa(a,b){function d(a,b){var d=b.$$hashKey,e;if(L(a)){e=0;for(var f=a.length;e<f;e++)b.push(c(a[e]))}else if(sc(a))for(e in a)b[e]=c(a[e]);else if(a&&"function"===typeof a.hasOwnProperty)for(e in a)a.hasOwnProperty(e)&&
(b[e]=c(a[e]));else for(e in a)ua.call(a,e)&&(b[e]=c(a[e]));d?b.$$hashKey=d:delete b.$$hashKey;return b}function c(a){if(!D(a))return a;var b=f.indexOf(a);if(-1!==b)return g[b];if(Va(a)||Ya(a))throw xa("cpws");var b=!1,c=e(a);void 0===c&&(c=L(a)?[]:Object.create(wc(a)),b=!0);f.push(a);g.push(c);return b?d(a,c):c}function e(a){switch(ma.call(a)){case "[object Int8Array]":case "[object Int16Array]":case "[object Int32Array]":case "[object Float32Array]":case "[object Float64Array]":case "[object Uint8Array]":case "[object Uint8ClampedArray]":case "[object Uint16Array]":case "[object Uint32Array]":return new a.constructor(c(a.buffer),
a.byteOffset,a.length);case "[object ArrayBuffer]":if(!a.slice){var b=new ArrayBuffer(a.byteLength);(new Uint8Array(b)).set(new Uint8Array(a));return b}return a.slice(0);case "[object Boolean]":case "[object Number]":case "[object String]":case "[object Date]":return new a.constructor(a.valueOf());case "[object RegExp]":return b=new RegExp(a.source,a.toString().match(/[^\/]*$/)[0]),b.lastIndex=a.lastIndex,b;case "[object Blob]":return new a.constructor([a],{type:a.type})}if(z(a.cloneNode))return a.cloneNode(!0)}
var f=[],g=[];if(b){if($d(b)||"[object ArrayBuffer]"===ma.call(b))throw xa("cpta");if(a===b)throw xa("cpi");L(b)?b.length=0:q(b,function(a,d){"$$hashKey"!==d&&delete b[d]});f.push(a);g.push(b);return d(a,b)}return c(a)}function na(a,b){if(a===b)return!0;if(null===a||null===b)return!1;if(a!==a&&b!==b)return!0;var d=typeof a,c;if(d==typeof b&&"object"==d)if(L(a)){if(!L(b))return!1;if((d=a.length)==b.length){for(c=0;c<d;c++)if(!na(a[c],b[c]))return!1;return!0}}else{if(da(a))return da(b)?na(a.getTime(),
b.getTime()):!1;if(Wa(a))return Wa(b)?a.toString()==b.toString():!1;if(Ya(a)||Ya(b)||Va(a)||Va(b)||L(b)||da(b)||Wa(b))return!1;d=U();for(c in a)if("$"!==c.charAt(0)&&!z(a[c])){if(!na(a[c],b[c]))return!1;d[c]=!0}for(c in b)if(!(c in d)&&"$"!==c.charAt(0)&&w(b[c])&&!z(b[c]))return!1;return!0}return!1}function $a(a,b,d){return a.concat(va.call(b,d))}function ab(a,b){var d=2<arguments.length?va.call(arguments,2):[];return!z(b)||b instanceof RegExp?b:d.length?function(){return arguments.length?b.apply(a,
$a(d,arguments,0)):b.apply(a,d)}:function(){return arguments.length?b.apply(a,arguments):b.call(a)}}function ce(a,b){var d=b;"string"===typeof a&&"$"===a.charAt(0)&&"$"===a.charAt(1)?d=void 0:Va(b)?d="$WINDOW":b&&C.document===b?d="$DOCUMENT":Ya(b)&&(d="$SCOPE");return d}function bb(a,b){if(!y(a))return T(b)||(b=b?2:null),JSON.stringify(a,ce,b)}function xc(a){return G(a)?JSON.parse(a):a}function yc(a,b){a=a.replace(de,"");var d=Date.parse("Jan 01, 1970 00:00:00 "+a)/6E4;return isNaN(d)?b:d}function Sb(a,
b,d){d=d?-1:1;var c=a.getTimezoneOffset();b=yc(b,c);d*=b-c;a=new Date(a.getTime());a.setMinutes(a.getMinutes()+d);return a}function ya(a){a=F(a).clone();try{a.empty()}catch(b){}var d=F("<div>").append(a).html();try{return a[0].nodeType===Ma?Q(d):d.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+Q(b)})}catch(c){return Q(d)}}function zc(a){try{return decodeURIComponent(a)}catch(b){}}function Ac(a){var b={};q((a||"").split("&"),function(a){var c,e,f;a&&(e=a=a.replace(/\+/g,"%20"),
c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=zc(e),w(e)&&(f=w(f)?zc(f):!0,ua.call(b,e)?L(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function Tb(a){var b=[];q(a,function(a,c){L(a)?q(a,function(a){b.push(ea(c,!0)+(!0===a?"":"="+ea(a,!0)))}):b.push(ea(c,!0)+(!0===a?"":"="+ea(a,!0)))});return b.length?b.join("&"):""}function qb(a){return ea(a,!0).replace(/%26/gi,"&").replace(/%3D/gi,"=").replace(/%2B/gi,"+")}function ea(a,b){return encodeURIComponent(a).replace(/%40/gi,
"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function ee(a,b){var d,c,e=Na.length;for(c=0;c<e;++c)if(d=Na[c]+b,G(d=a.getAttribute(d)))return d;return null}function fe(a,b){var d,c,e={};q(Na,function(b){b+="app";!d&&a.hasAttribute&&a.hasAttribute(b)&&(d=a,c=a.getAttribute(b))});q(Na,function(b){b+="app";var e;!d&&(e=a.querySelector("["+b.replace(":","\\:")+"]"))&&(d=e,c=e.getAttribute(b))});d&&(e.strictDi=null!==ee(d,"strict-di"),
b(d,c?[c]:[],e))}function Bc(a,b,d){D(d)||(d={});d=S({strictDi:!1},d);var c=function(){a=F(a);if(a.injector()){var c=a[0]===C.document?"document":ya(a);throw xa("btstrpd",c.replace(/</,"&lt;").replace(/>/,"&gt;"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);b.unshift("ng");c=cb(b,d.strictDi);c.invoke(["$rootScope","$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",
d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;C&&e.test(C.name)&&(d.debugInfoEnabled=!0,C.name=C.name.replace(e,""));if(C&&!f.test(C.name))return c();C.name=C.name.replace(f,"");ca.resumeBootstrap=function(a){q(a,function(a){b.push(a)});return c()};z(ca.resumeDeferredBootstrap)&&ca.resumeDeferredBootstrap()}function ge(){C.name="NG_ENABLE_DEBUG_INFO!"+C.name;C.location.reload()}function he(a){a=ca.element(a).injector();if(!a)throw xa("test");return a.get("$$testability")}
function Cc(a,b){b=b||"_";return a.replace(ie,function(a,c){return(c?b:"")+a.toLowerCase()})}function je(){var a;if(!Dc){var b=rb();(qa=y(b)?C.jQuery:b?C[b]:void 0)&&qa.fn.on?(F=qa,S(qa.fn,{scope:Oa.scope,isolateScope:Oa.isolateScope,controller:Oa.controller,injector:Oa.injector,inheritedData:Oa.inheritedData}),a=qa.cleanData,qa.cleanData=function(b){for(var c,e=0,f;null!=(f=b[e]);e++)(c=qa._data(f,"events"))&&c.$destroy&&qa(f).triggerHandler("$destroy");a(b)}):F=O;ca.element=F;Dc=!0}}function sb(a,
b,d){if(!a)throw xa("areq",b||"?",d||"required");return a}function Pa(a,b,d){d&&L(a)&&(a=a[a.length-1]);sb(z(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Qa(a,b){if("hasOwnProperty"===a)throw xa("badname",b);}function Ec(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g<f;g++)c=b[g],a&&(a=(e=a)[c]);return!d&&z(a)?ab(e,a):a}function tb(a){for(var b=a[0],d=a[a.length-1],c,e=1;b!==d&&(b=b.nextSibling);e++)if(c||a[e]!==
b)c||(c=F(va.call(a,0,e))),c.push(b);return c||a}function U(){return Object.create(null)}function ke(a){function b(a,b,c){return a[b]||(a[b]=c())}var d=N("$injector"),c=N("ng");a=b(a,"angular",Object);a.$$minErr=a.$$minErr||N;return b(a,"module",function(){var a={};return function(f,g,h){if("hasOwnProperty"===f)throw c("badname","module");g&&a.hasOwnProperty(f)&&(a[f]=null);return b(a,f,function(){function a(b,d,e,f){f||(f=c);return function(){f[e||"push"]([b,d,arguments]);return R}}function b(a,
d){return function(b,e){e&&z(e)&&(e.$$moduleName=f);c.push([a,d,arguments]);return R}}if(!g)throw d("nomod",f);var c=[],e=[],p=[],u=a("$injector","invoke","push",e),R={_invokeQueue:c,_configBlocks:e,_runBlocks:p,requires:g,name:f,provider:b("$provide","provider"),factory:b("$provide","factory"),service:b("$provide","service"),value:a("$provide","value"),constant:a("$provide","constant","unshift"),decorator:b("$provide","decorator"),animation:b("$animateProvider","register"),filter:b("$filterProvider",
"register"),controller:b("$controllerProvider","register"),directive:b("$compileProvider","directive"),component:b("$compileProvider","component"),config:u,run:function(a){p.push(a);return this}};h&&u(h);return R})}})}function ia(a,b){if(L(a)){b=b||[];for(var d=0,c=a.length;d<c;d++)b[d]=a[d]}else if(D(a))for(d in b=b||{},a)if("$"!==d.charAt(0)||"$"!==d.charAt(1))b[d]=a[d];return b||a}function le(a){S(a,{bootstrap:Bc,copy:pa,extend:S,merge:Zd,equals:na,element:F,forEach:q,injector:cb,noop:A,bind:ab,
toJson:bb,fromJson:xc,identity:Xa,isUndefined:y,isDefined:w,isString:G,isFunction:z,isObject:D,isNumber:T,isElement:Qb,isArray:L,version:me,isDate:da,lowercase:Q,uppercase:ub,callbacks:{$$counter:0},getTestability:he,$$minErr:N,$$csp:Ba,reloadWithDebugInfo:ge});Ub=ke(C);Ub("ng",["ngLocale"],["$provide",function(a){a.provider({$$sanitizeUri:ne});a.provider("$compile",Fc).directive({a:oe,input:Gc,textarea:Gc,form:pe,script:qe,select:re,style:se,option:te,ngBind:ue,ngBindHtml:ve,ngBindTemplate:we,ngClass:xe,
ngClassEven:ye,ngClassOdd:ze,ngCloak:Ae,ngController:Be,ngForm:Ce,ngHide:De,ngIf:Ee,ngInclude:Fe,ngInit:Ge,ngNonBindable:He,ngPluralize:Ie,ngRepeat:Je,ngShow:Ke,ngStyle:Le,ngSwitch:Me,ngSwitchWhen:Ne,ngSwitchDefault:Oe,ngOptions:Pe,ngTransclude:Qe,ngModel:Re,ngList:Se,ngChange:Te,pattern:Hc,ngPattern:Hc,required:Ic,ngRequired:Ic,minlength:Jc,ngMinlength:Jc,maxlength:Kc,ngMaxlength:Kc,ngValue:Ue,ngModelOptions:Ve}).directive({ngInclude:We}).directive(vb).directive(Lc);a.provider({$anchorScroll:Xe,
$animate:Ye,$animateCss:Ze,$$animateJs:$e,$$animateQueue:af,$$AnimateRunner:bf,$$animateAsyncRun:cf,$browser:df,$cacheFactory:ef,$controller:ff,$document:gf,$exceptionHandler:hf,$filter:Mc,$$forceReflow:jf,$interpolate:kf,$interval:lf,$http:mf,$httpParamSerializer:nf,$httpParamSerializerJQLike:of,$httpBackend:pf,$xhrFactory:qf,$jsonpCallbacks:rf,$location:sf,$log:tf,$parse:uf,$rootScope:vf,$q:wf,$$q:xf,$sce:yf,$sceDelegate:zf,$sniffer:Af,$templateCache:Bf,$templateRequest:Cf,$$testability:Df,$timeout:Ef,
$window:Ff,$$rAF:Gf,$$jqLite:Hf,$$HashMap:If,$$cookieReader:Jf})}])}function db(a){return a.replace(Kf,function(a,d,c,e){return e?c.toUpperCase():c}).replace(Lf,"Moz$1")}function Nc(a){a=a.nodeType;return 1===a||!a||9===a}function Oc(a,b){var d,c,e=b.createDocumentFragment(),f=[];if(Vb.test(a)){d=e.appendChild(b.createElement("div"));c=(Mf.exec(a)||["",""])[1].toLowerCase();c=ja[c]||ja._default;d.innerHTML=c[1]+a.replace(Nf,"<$1></$2>")+c[2];for(c=c[0];c--;)d=d.lastChild;f=$a(f,d.childNodes);d=e.firstChild;
d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";q(f,function(a){e.appendChild(a)});return e}function Pc(a,b){var d=a.parentNode;d&&d.replaceChild(b,a);b.appendChild(a)}function O(a){if(a instanceof O)return a;var b;G(a)&&(a=W(a),b=!0);if(!(this instanceof O)){if(b&&"<"!=a.charAt(0))throw Wb("nosel");return new O(a)}if(b){b=C.document;var d;a=(d=Of.exec(a))?[b.createElement(d[1])]:(d=Oc(a,b))?d.childNodes:[]}Qc(this,a)}function Xb(a){return a.cloneNode(!0)}function wb(a,
b){b||eb(a);if(a.querySelectorAll)for(var d=a.querySelectorAll("*"),c=0,e=d.length;c<e;c++)eb(d[c])}function Rc(a,b,d,c){if(w(c))throw Wb("offargs");var e=(c=xb(a))&&c.events,f=c&&c.handle;if(f)if(b){var g=function(b){var c=e[b];w(d)&&Za(c||[],d);w(d)&&c&&0<c.length||(a.removeEventListener(b,f,!1),delete e[b])};q(b.split(" "),function(a){g(a);yb[a]&&g(yb[a])})}else for(b in e)"$destroy"!==b&&a.removeEventListener(b,f,!1),delete e[b]}function eb(a,b){var d=a.ng339,c=d&&fb[d];c&&(b?delete c.data[b]:
(c.handle&&(c.events.$destroy&&c.handle({},"$destroy"),Rc(a)),delete fb[d],a.ng339=void 0))}function xb(a,b){var d=a.ng339,d=d&&fb[d];b&&!d&&(a.ng339=d=++Pf,d=fb[d]={events:{},data:{},handle:void 0});return d}function Yb(a,b,d){if(Nc(a)){var c=w(d),e=!c&&b&&!D(b),f=!b;a=(a=xb(a,!e))&&a.data;if(c)a[b]=d;else{if(f)return a;if(e)return a&&a[b];S(a,b)}}}function zb(a,b){return a.getAttribute?-1<(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").indexOf(" "+b+" "):!1}function Ab(a,b){b&&a.setAttribute&&
q(b.split(" "),function(b){a.setAttribute("class",W((" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ").replace(" "+W(b)+" "," ")))})}function Bb(a,b){if(b&&a.setAttribute){var d=(" "+(a.getAttribute("class")||"")+" ").replace(/[\n\t]/g," ");q(b.split(" "),function(a){a=W(a);-1===d.indexOf(" "+a+" ")&&(d+=a+" ")});a.setAttribute("class",W(d))}}function Qc(a,b){if(b)if(b.nodeType)a[a.length++]=b;else{var d=b.length;if("number"===typeof d&&b.window!==b){if(d)for(var c=0;c<d;c++)a[a.length++]=
b[c]}else a[a.length++]=b}}function Sc(a,b){return Cb(a,"$"+(b||"ngController")+"Controller")}function Cb(a,b,d){9==a.nodeType&&(a=a.documentElement);for(b=L(b)?b:[b];a;){for(var c=0,e=b.length;c<e;c++)if(w(d=F.data(a,b[c])))return d;a=a.parentNode||11===a.nodeType&&a.host}}function Tc(a){for(wb(a,!0);a.firstChild;)a.removeChild(a.firstChild)}function Db(a,b){b||wb(a);var d=a.parentNode;d&&d.removeChild(a)}function Qf(a,b){b=b||C;if("complete"===b.document.readyState)b.setTimeout(a);else F(b).on("load",
a)}function Uc(a,b){var d=Eb[b.toLowerCase()];return d&&Vc[wa(a)]&&d}function Rf(a,b){var d=function(c,d){c.isDefaultPrevented=function(){return c.defaultPrevented};var f=b[d||c.type],g=f?f.length:0;if(g){if(y(c.immediatePropagationStopped)){var h=c.stopImmediatePropagation;c.stopImmediatePropagation=function(){c.immediatePropagationStopped=!0;c.stopPropagation&&c.stopPropagation();h&&h.call(c)}}c.isImmediatePropagationStopped=function(){return!0===c.immediatePropagationStopped};var k=f.specialHandlerWrapper||
Sf;1<g&&(f=ia(f));for(var l=0;l<g;l++)c.isImmediatePropagationStopped()||k(a,c,f[l])}};d.elem=a;return d}function Sf(a,b,d){d.call(a,b)}function Tf(a,b,d){var c=b.relatedTarget;c&&(c===a||Uf.call(a,c))||d.call(a,b)}function Hf(){this.$get=function(){return S(O,{hasClass:function(a,b){a.attr&&(a=a[0]);return zb(a,b)},addClass:function(a,b){a.attr&&(a=a[0]);return Bb(a,b)},removeClass:function(a,b){a.attr&&(a=a[0]);return Ab(a,b)}})}}function Ca(a,b){var d=a&&a.$$hashKey;if(d)return"function"===typeof d&&
(d=a.$$hashKey()),d;d=typeof a;return d="function"==d||"object"==d&&null!==a?a.$$hashKey=d+":"+(b||Yd)():d+":"+a}function Ra(a,b){if(b){var d=0;this.nextUid=function(){return++d}}q(a,this.put,this)}function Wc(a){a=(Function.prototype.toString.call(a)+" ").replace(Vf,"");return a.match(Wf)||a.match(Xf)}function Yf(a){return(a=Wc(a))?"function("+(a[1]||"").replace(/[\s\r\n]+/," ")+")":"fn"}function cb(a,b){function d(a){return function(b,c){if(D(b))q(b,uc(a));else return a(b,c)}}function c(a,b){Qa(a,
"service");if(z(b)||L(b))b=p.instantiate(b);if(!b.$get)throw Ha("pget",a);return n[a+"Provider"]=b}function e(a,b){return function(){var c=B.invoke(b,this);if(y(c))throw Ha("undef",a);return c}}function f(a,b,d){return c(a,{$get:!1!==d?e(a,b):b})}function g(a){sb(y(a)||L(a),"modulesToLoad","not an array");var b=[],c;q(a,function(a){function d(a){var b,c;b=0;for(c=a.length;b<c;b++){var e=a[b],f=p.get(e[0]);f[e[1]].apply(f,e[2])}}if(!m.get(a)){m.put(a,!0);try{G(a)?(c=Ub(a),b=b.concat(g(c.requires)).concat(c._runBlocks),
d(c._invokeQueue),d(c._configBlocks)):z(a)?b.push(p.invoke(a)):L(a)?b.push(p.invoke(a)):Pa(a,"module")}catch(e){throw L(a)&&(a=a[a.length-1]),e.message&&e.stack&&-1==e.stack.indexOf(e.message)&&(e=e.message+"\n"+e.stack),Ha("modulerr",a,e.stack||e.message||e);}}});return b}function h(a,c){function d(b,e){if(a.hasOwnProperty(b)){if(a[b]===k)throw Ha("cdep",b+" <- "+l.join(" <- "));return a[b]}try{return l.unshift(b),a[b]=k,a[b]=c(b,e)}catch(f){throw a[b]===k&&delete a[b],f;}finally{l.shift()}}function e(a,
c,f){var g=[];a=cb.$$annotate(a,b,f);for(var h=0,k=a.length;h<k;h++){var l=a[h];if("string"!==typeof l)throw Ha("itkn",l);g.push(c&&c.hasOwnProperty(l)?c[l]:d(l,f))}return g}return{invoke:function(a,b,c,d){"string"===typeof c&&(d=c,c=null);c=e(a,c,d);L(a)&&(a=a[a.length-1]);d=11>=Ea?!1:"function"===typeof a&&/^(?:class\b|constructor\()/.test(Function.prototype.toString.call(a)+" ");return d?(c.unshift(null),new (Function.prototype.bind.apply(a,c))):a.apply(b,c)},instantiate:function(a,b,c){var d=
L(a)?a[a.length-1]:a;a=e(a,b,c);a.unshift(null);return new (Function.prototype.bind.apply(d,a))},get:d,annotate:cb.$$annotate,has:function(b){return n.hasOwnProperty(b+"Provider")||a.hasOwnProperty(b)}}}b=!0===b;var k={},l=[],m=new Ra([],!0),n={$provide:{provider:d(c),factory:d(f),service:d(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:d(function(a,b){return f(a,ha(b),!1)}),constant:d(function(a,b){Qa(a,"constant");n[a]=b;u[a]=b}),decorator:function(a,b){var c=
p.get(a+"Provider"),d=c.$get;c.$get=function(){var a=B.invoke(d,c);return B.invoke(b,null,{$delegate:a})}}}},p=n.$injector=h(n,function(a,b){ca.isString(b)&&l.push(b);throw Ha("unpr",l.join(" <- "));}),u={},R=h(u,function(a,b){var c=p.get(a+"Provider",b);return B.invoke(c.$get,c,void 0,a)}),B=R;n.$injectorProvider={$get:ha(R)};var r=g(a),B=R.get("$injector");B.strictDi=b;q(r,function(a){a&&B.invoke(a)});return B}function Xe(){var a=!0;this.disableAutoScrolling=function(){a=!1};this.$get=["$window",
"$location","$rootScope",function(b,d,c){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"===wa(a))return b=a,!0});return b}function f(a){if(a){a.scrollIntoView();var c;c=g.yOffset;z(c)?c=c():Qb(c)?(c=c[0],c="fixed"!==b.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):T(c)||(c=0);c&&(a=a.getBoundingClientRect().top,b.scrollBy(0,a-c))}else b.scrollTo(0,0)}function g(a){a=G(a)?a:d.hash();var b;a?(b=h.getElementById(a))?f(b):(b=e(h.getElementsByName(a)))?f(b):"top"===
a&&f(null):f(null)}var h=b.document;a&&c.$watch(function(){return d.hash()},function(a,b){a===b&&""===a||Qf(function(){c.$evalAsync(g)})});return g}]}function gb(a,b){if(!a&&!b)return"";if(!a)return b;if(!b)return a;L(a)&&(a=a.join(" "));L(b)&&(b=b.join(" "));return a+" "+b}function Zf(a){G(a)&&(a=a.split(" "));var b=U();q(a,function(a){a.length&&(b[a]=!0)});return b}function Ia(a){return D(a)?a:{}}function $f(a,b,d,c){function e(a){try{a.apply(null,va.call(arguments,1))}finally{if(R--,0===R)for(;B.length;)try{B.pop()()}catch(b){d.error(b)}}}
function f(){t=null;g();h()}function g(){r=K();r=y(r)?null:r;na(r,E)&&(r=E);E=r}function h(){if(v!==k.url()||J!==r)v=k.url(),J=r,q(M,function(a){a(k.url(),r)})}var k=this,l=a.location,m=a.history,n=a.setTimeout,p=a.clearTimeout,u={};k.isMock=!1;var R=0,B=[];k.$$completeOutstandingRequest=e;k.$$incOutstandingRequestCount=function(){R++};k.notifyWhenNoOutstandingRequests=function(a){0===R?a():B.push(a)};var r,J,v=l.href,fa=b.find("base"),t=null,K=c.history?function(){try{return m.state}catch(a){}}:
A;g();J=r;k.url=function(b,d,e){y(e)&&(e=null);l!==a.location&&(l=a.location);m!==a.history&&(m=a.history);if(b){var f=J===e;if(v===b&&(!c.history||f))return k;var h=v&&Ja(v)===Ja(b);v=b;J=e;!c.history||h&&f?(h||(t=b),d?l.replace(b):h?(d=l,e=b.indexOf("#"),e=-1===e?"":b.substr(e),d.hash=e):l.href=b,l.href!==b&&(t=b)):(m[d?"replaceState":"pushState"](e,"",b),g(),J=r);t&&(t=b);return k}return t||l.href.replace(/%27/g,"'")};k.state=function(){return r};var M=[],H=!1,E=null;k.onUrlChange=function(b){if(!H){if(c.history)F(a).on("popstate",
f);F(a).on("hashchange",f);H=!0}M.push(b);return b};k.$$applicationDestroyed=function(){F(a).off("hashchange popstate",f)};k.$$checkUrlChange=h;k.baseHref=function(){var a=fa.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};k.defer=function(a,b){var c;R++;c=n(function(){delete u[c];e(a)},b||0);u[c]=!0;return c};k.defer.cancel=function(a){return u[a]?(delete u[a],p(a),e(A),!0):!1}}function df(){this.$get=["$window","$log","$sniffer","$document",function(a,b,d,c){return new $f(a,c,b,
d)}]}function ef(){this.$get=function(){function a(a,c){function e(a){a!=n&&(p?p==a&&(p=a.n):p=a,f(a.n,a.p),f(a,n),n=a,n.n=null)}function f(a,b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(a in b)throw N("$cacheFactory")("iid",a);var g=0,h=S({},c,{id:a}),k=U(),l=c&&c.capacity||Number.MAX_VALUE,m=U(),n=null,p=null;return b[a]={put:function(a,b){if(!y(b)){if(l<Number.MAX_VALUE){var c=m[a]||(m[a]={key:a});e(c)}a in k||g++;k[a]=b;g>l&&this.remove(p.key);return b}},get:function(a){if(l<Number.MAX_VALUE){var b=m[a];
if(!b)return;e(b)}return k[a]},remove:function(a){if(l<Number.MAX_VALUE){var b=m[a];if(!b)return;b==n&&(n=b.p);b==p&&(p=b.n);f(b.n,b.p);delete m[a]}a in k&&(delete k[a],g--)},removeAll:function(){k=U();g=0;m=U();n=p=null},destroy:function(){m=h=k=null;delete b[a]},info:function(){return S({},h,{size:g})}}}var b={};a.info=function(){var a={};q(b,function(b,e){a[e]=b.info()});return a};a.get=function(a){return b[a]};return a}}function Bf(){this.$get=["$cacheFactory",function(a){return a("templates")}]}
function Fc(a,b){function d(a,b,c){var d=/^\s*([@&<]|=(\*?))(\??)\s*(\w*)\s*$/,e=U();q(a,function(a,f){if(a in n)e[f]=n[a];else{var g=a.match(d);if(!g)throw ga("iscp",b,f,a,c?"controller bindings definition":"isolate scope definition");e[f]={mode:g[1][0],collection:"*"===g[2],optional:"?"===g[3],attrName:g[4]||f};g[4]&&(n[a]=e[f])}});return e}function c(a){var b=a.charAt(0);if(!b||b!==Q(b))throw ga("baddir",a);if(a!==a.trim())throw ga("baddir",a);}function e(a){var b=a.require||a.controller&&a.name;
!L(b)&&D(b)&&q(b,function(a,c){var d=a.match(l);a.substring(d[0].length)||(b[c]=d[0]+c)});return b}var f={},g=/^\s*directive\:\s*([\w\-]+)\s+(.*)$/,h=/(([\w\-]+)(?:\:([^;]+))?;?)/,k=be("ngSrc,ngSrcset,src,srcset"),l=/^(?:(\^\^?)?(\?)?(\^\^?)?)?/,m=/^(on[a-z]+|formaction)$/,n=U();this.directive=function B(b,d){Qa(b,"directive");G(b)?(c(b),sb(d,"directiveFactory"),f.hasOwnProperty(b)||(f[b]=[],a.factory(b+"Directive",["$injector","$exceptionHandler",function(a,c){var d=[];q(f[b],function(f,g){try{var h=
a.invoke(f);z(h)?h={compile:ha(h)}:!h.compile&&h.link&&(h.compile=ha(h.link));h.priority=h.priority||0;h.index=g;h.name=h.name||b;h.require=e(h);h.restrict=h.restrict||"EA";h.$$moduleName=f.$$moduleName;d.push(h)}catch(k){c(k)}});return d}])),f[b].push(d)):q(b,uc(B));return this};this.component=function(a,b){function c(a){function e(b){return z(b)||L(b)?function(c,d){return a.invoke(b,this,{$element:c,$attrs:d})}:b}var f=b.template||b.templateUrl?b.template:"",g={controller:d,controllerAs:Xc(b.controller)||
b.controllerAs||"$ctrl",template:e(f),templateUrl:e(b.templateUrl),transclude:b.transclude,scope:{},bindToController:b.bindings||{},restrict:"E",require:b.require};q(b,function(a,b){"$"===b.charAt(0)&&(g[b]=a)});return g}var d=b.controller||function(){};q(b,function(a,b){"$"===b.charAt(0)&&(c[b]=a,z(d)&&(d[b]=a))});c.$inject=["$injector"];return this.directive(a,c)};this.aHrefSanitizationWhitelist=function(a){return w(a)?(b.aHrefSanitizationWhitelist(a),this):b.aHrefSanitizationWhitelist()};this.imgSrcSanitizationWhitelist=
function(a){return w(a)?(b.imgSrcSanitizationWhitelist(a),this):b.imgSrcSanitizationWhitelist()};var p=!0;this.debugInfoEnabled=function(a){return w(a)?(p=a,this):p};var u=10;this.onChangesTtl=function(a){return arguments.length?(u=a,this):u};this.$get=["$injector","$interpolate","$exceptionHandler","$templateRequest","$parse","$controller","$rootScope","$sce","$animate","$$sanitizeUri",function(a,b,c,e,n,t,K,M,H,E){function I(){try{if(!--qa)throw Y=void 0,ga("infchng",u);K.$apply(function(){for(var a=
[],b=0,c=Y.length;b<c;++b)try{Y[b]()}catch(d){a.push(d)}Y=void 0;if(a.length)throw a;})}finally{qa++}}function Da(a,b){if(b){var c=Object.keys(b),d,e,f;d=0;for(e=c.length;d<e;d++)f=c[d],this[f]=b[f]}else this.$attr={};this.$$element=a}function P(a,b,c){pa.innerHTML="<span "+b+">";b=pa.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name);d.value=c;a.attributes.setNamedItem(d)}function x(a,b){try{a.addClass(b)}catch(c){}}function aa(a,b,c,d,e){a instanceof F||(a=F(a));for(var f=/\S+/,g=0,h=a.length;g<
h;g++){var k=a[g];k.nodeType===Ma&&k.nodeValue.match(f)&&Pc(k,a[g]=C.document.createElement("span"))}var l=s(a,b,a,c,d,e);aa.$$addScopeClass(a);var m=null;return function(b,c,d){sb(b,"scope");e&&e.needsNewScope&&(b=b.$parent.$new());d=d||{};var f=d.parentBoundTranscludeFn,g=d.transcludeControllers;d=d.futureParentElement;f&&f.$$boundTransclude&&(f=f.$$boundTransclude);m||(m=(d=d&&d[0])?"foreignobject"!==wa(d)&&ma.call(d).match(/SVG/)?"svg":"html":"html");d="html"!==m?F(da(m,F("<div>").append(a).html())):
c?Oa.clone.call(a):a;if(g)for(var h in g)d.data("$"+h+"Controller",g[h].instance);aa.$$addScopeInfo(d,b);c&&c(d,b);l&&l(b,d,d,f);return d}}function s(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,p,r,v;if(n)for(v=Array(c.length),m=0;m<h.length;m+=3)f=h[m],v[f]=c[f];else v=c;m=0;for(p=h.length;m<p;)k=v[h[m++]],c=h[m++],f=h[m++],c?(c.scope?(l=a.$new(),aa.$$addScopeInfo(F(k),l)):l=a,r=c.transcludeOnThisElement?za(a,c.transclude,e):!c.templateOnThisElement&&e?e:!e&&b?za(a,b):null,c(f,l,k,d,r)):f&&f(a,
k.childNodes,void 0,e)}for(var h=[],k,l,m,p,n,r=0;r<a.length;r++){k=new Da;l=$b(a[r],[],k,0===r?d:void 0,e);(f=l.length?oa(l,a[r],k,b,c,null,[],[],f):null)&&f.scope&&aa.$$addScopeClass(k.$$element);k=f&&f.terminal||!(m=a[r].childNodes)||!m.length?null:s(m,f?(f.transcludeOnThisElement||!f.templateOnThisElement)&&f.transclude:b);if(f||k)h.push(r,f,k),p=!0,n=n||f;f=null}return p?g:null}function za(a,b,c){function d(e,f,g,h,k){e||(e=a.$new(!1,k),e.$$transcluded=!0);return b(e,f,{parentBoundTranscludeFn:c,
transcludeControllers:g,futureParentElement:h})}var e=d.$$slots=U(),f;for(f in b.$$slots)e[f]=b.$$slots[f]?za(a,b.$$slots[f],c):null;return d}function $b(a,b,c,d,e){var f=c.$attr;switch(a.nodeType){case 1:O(b,Aa(wa(a)),"E",d,e);for(var g,k,l,m,p=a.attributes,n=0,r=p&&p.length;n<r;n++){var v=!1,u=!1;g=p[n];k=g.name;l=W(g.value);g=Aa(k);if(m=Ba.test(g))k=k.replace(Yc,"").substr(8).replace(/_(.)/g,function(a,b){return b.toUpperCase()});(g=g.match(Ca))&&V(g[1])&&(v=k,u=k.substr(0,k.length-5)+"end",k=
k.substr(0,k.length-6));g=Aa(k.toLowerCase());f[g]=k;if(m||!c.hasOwnProperty(g))c[g]=l,Uc(a,g)&&(c[g]=!0);ia(a,b,l,g,m);O(b,g,"A",d,e,v,u)}f=a.className;D(f)&&(f=f.animVal);if(G(f)&&""!==f)for(;a=h.exec(f);)g=Aa(a[2]),O(b,g,"C",d,e)&&(c[g]=W(a[3])),f=f.substr(a.index+a[0].length);break;case Ma:if(11===Ea)for(;a.parentNode&&a.nextSibling&&a.nextSibling.nodeType===Ma;)a.nodeValue+=a.nextSibling.nodeValue,a.parentNode.removeChild(a.nextSibling);ca(b,a.nodeValue);break;case 8:hb(a,b,c,d,e)}b.sort(Z);
return b}function hb(a,b,c,d,e){try{var f=g.exec(a.nodeValue);if(f){var h=Aa(f[1]);O(b,h,"M",d,e)&&(c[h]=W(f[2]))}}catch(k){}}function N(a,b,c){var d=[],e=0;if(b&&a.hasAttribute&&a.hasAttribute(b)){do{if(!a)throw ga("uterdir",b,c);1==a.nodeType&&(a.hasAttribute(b)&&e++,a.hasAttribute(c)&&e--);d.push(a);a=a.nextSibling}while(0<e)}else d.push(a);return F(d)}function Zc(a,b,c){return function(d,e,f,g,h){e=N(e[0],b,c);return a(d,e,f,g,h)}}function ac(a,b,c,d,e,f){var g;return a?aa(b,c,d,e,f):function(){g||
(g=aa(b,c,d,e,f),b=c=f=null);return g.apply(this,arguments)}}function oa(a,b,d,e,f,g,h,k,l){function m(a,b,c,d){if(a){c&&(a=Zc(a,c,d));a.require=x.require;a.directiveName=I;if(u===x||x.$$isolateScope)a=ja(a,{isolateScope:!0});h.push(a)}if(b){c&&(b=Zc(b,c,d));b.require=x.require;b.directiveName=I;if(u===x||x.$$isolateScope)b=ja(b,{isolateScope:!0});k.push(b)}}function p(a,e,f,g,l){function m(a,b,c,d){var e;Ya(a)||(d=c,c=b,b=a,a=void 0);fa&&(e=t);c||(c=fa?I.parent():I);if(d){var f=l.$$slots[d];if(f)return f(a,
b,e,c,s);if(y(f))throw ga("noslot",d,ya(I));}else return l(a,b,e,c,s)}var n,E,x,M,B,t,P,I;b===f?(g=d,I=d.$$element):(I=F(f),g=new Da(I,d));B=e;u?M=e.$new(!0):r&&(B=e.$parent);l&&(P=m,P.$$boundTransclude=l,P.isSlotFilled=function(a){return!!l.$$slots[a]});v&&(t=ag(I,g,P,v,M,e,u));u&&(aa.$$addScopeInfo(I,M,!0,!(H&&(H===u||H===u.$$originalDirective))),aa.$$addScopeClass(I,!0),M.$$isolateBindings=u.$$isolateBindings,E=ka(e,g,M,M.$$isolateBindings,u),E.removeWatches&&M.$on("$destroy",E.removeWatches));
for(n in t){E=v[n];x=t[n];var Zb=E.$$bindings.bindToController;x.bindingInfo=x.identifier&&Zb?ka(B,g,x.instance,Zb,E):{};var K=x();K!==x.instance&&(x.instance=K,I.data("$"+E.name+"Controller",K),x.bindingInfo.removeWatches&&x.bindingInfo.removeWatches(),x.bindingInfo=ka(B,g,x.instance,Zb,E))}q(v,function(a,b){var c=a.require;a.bindToController&&!L(c)&&D(c)&&S(t[b].instance,ib(b,c,I,t))});q(t,function(a){var b=a.instance;if(z(b.$onChanges))try{b.$onChanges(a.bindingInfo.initialChanges)}catch(d){c(d)}if(z(b.$onInit))try{b.$onInit()}catch(e){c(e)}z(b.$doCheck)&&
(B.$watch(function(){b.$doCheck()}),b.$doCheck());z(b.$onDestroy)&&B.$on("$destroy",function(){b.$onDestroy()})});n=0;for(E=h.length;n<E;n++)x=h[n],la(x,x.isolateScope?M:e,I,g,x.require&&ib(x.directiveName,x.require,I,t),P);var s=e;u&&(u.template||null===u.templateUrl)&&(s=M);a&&a(s,f.childNodes,void 0,l);for(n=k.length-1;0<=n;n--)x=k[n],la(x,x.isolateScope?M:e,I,g,x.require&&ib(x.directiveName,x.require,I,t),P);q(t,function(a){a=a.instance;z(a.$postLink)&&a.$postLink()})}l=l||{};for(var n=-Number.MAX_VALUE,
r=l.newScopeDirective,v=l.controllerDirectives,u=l.newIsolateScopeDirective,H=l.templateDirective,E=l.nonTlbTranscludeDirective,M=!1,B=!1,fa=l.hasElementTranscludeDirective,t=d.$$element=F(b),x,I,P,K=e,s,Fa=!1,za=!1,w,A=0,C=a.length;A<C;A++){x=a[A];var G=x.$$start,hb=x.$$end;G&&(t=N(b,G,hb));P=void 0;if(n>x.priority)break;if(w=x.scope)x.templateUrl||(D(w)?(X("new/isolated scope",u||r,x,t),u=x):X("new/isolated scope",u,x,t)),r=r||x;I=x.name;if(!Fa&&(x.replace&&(x.templateUrl||x.template)||x.transclude&&
!x.$$tlb)){for(w=A+1;Fa=a[w++];)if(Fa.transclude&&!Fa.$$tlb||Fa.replace&&(Fa.templateUrl||Fa.template)){za=!0;break}Fa=!0}!x.templateUrl&&x.controller&&(w=x.controller,v=v||U(),X("'"+I+"' controller",v[I],x,t),v[I]=x);if(w=x.transclude)if(M=!0,x.$$tlb||(X("transclusion",E,x,t),E=x),"element"==w)fa=!0,n=x.priority,P=t,t=d.$$element=F(aa.$$createComment(I,d[I])),b=t[0],ea(f,va.call(P,0),b),P[0].$$parentNode=P[0].parentNode,K=ac(za,P,e,n,g&&g.name,{nonTlbTranscludeDirective:E});else{var oa=U();P=F(Xb(b)).contents();
if(D(w)){P=[];var Q=U(),O=U();q(w,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;Q[a]=b;oa[b]=null;O[b]=c});q(t.contents(),function(a){var b=Q[Aa(wa(a))];b?(O[b]=!0,oa[b]=oa[b]||[],oa[b].push(a)):P.push(a)});q(O,function(a,b){if(!a)throw ga("reqslot",b);});for(var V in oa)oa[V]&&(oa[V]=ac(za,oa[V],e))}t.empty();K=ac(za,P,e,void 0,void 0,{needsNewScope:x.$$isolateScope||x.$$newScope});K.$$slots=oa}if(x.template)if(B=!0,X("template",H,x,t),H=x,w=z(x.template)?x.template(t,d):x.template,
w=xa(w),x.replace){g=x;P=Vb.test(w)?$c(da(x.templateNamespace,W(w))):[];b=P[0];if(1!=P.length||1!==b.nodeType)throw ga("tplrt",I,"");ea(f,t,b);C={$attr:{}};w=$b(b,[],C);var Z=a.splice(A+1,a.length-(A+1));(u||r)&&T(w,u,r);a=a.concat(w).concat(Z);$(d,C);C=a.length}else t.html(w);if(x.templateUrl)B=!0,X("template",H,x,t),H=x,x.replace&&(g=x),p=ba(a.splice(A,a.length-A),t,d,f,M&&K,h,k,{controllerDirectives:v,newScopeDirective:r!==x&&r,newIsolateScopeDirective:u,templateDirective:H,nonTlbTranscludeDirective:E}),
C=a.length;else if(x.compile)try{s=x.compile(t,d,K);var Y=x.$$originalDirective||x;z(s)?m(null,ab(Y,s),G,hb):s&&m(ab(Y,s.pre),ab(Y,s.post),G,hb)}catch(ca){c(ca,ya(t))}x.terminal&&(p.terminal=!0,n=Math.max(n,x.priority))}p.scope=r&&!0===r.scope;p.transcludeOnThisElement=M;p.templateOnThisElement=B;p.transclude=K;l.hasElementTranscludeDirective=fa;return p}function ib(a,b,c,d){var e;if(G(b)){var f=b.match(l);b=b.substring(f[0].length);var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&
e.instance;if(!e){var h="$"+b+"Controller";e=g?c.inheritedData(h):c.data(h)}if(!e&&!f)throw ga("ctreq",b,a);}else if(L(b))for(e=[],g=0,f=b.length;g<f;g++)e[g]=ib(a,b[g],c,d);else D(b)&&(e={},q(b,function(b,f){e[f]=ib(a,b,c,d)}));return e||null}function ag(a,b,c,d,e,f,g){var h=U(),k;for(k in d){var l=d[k],m={$scope:l===g||l.$$isolateScope?e:f,$element:a,$attrs:b,$transclude:c},p=l.controller;"@"==p&&(p=b[l.name]);m=t(p,m,!0,l.controllerAs);h[l.name]=m;a.data("$"+l.name+"Controller",m.instance)}return h}
function T(a,b,c){for(var d=0,e=a.length;d<e;d++)a[d]=Rb(a[d],{$$isolateScope:b,$$newScope:c})}function O(b,e,g,h,k,l,m){if(e===k)return null;k=null;if(f.hasOwnProperty(e)){var p;e=a.get(e+"Directive");for(var n=0,r=e.length;n<r;n++)try{if(p=e[n],(y(h)||h>p.priority)&&-1!=p.restrict.indexOf(g)){l&&(p=Rb(p,{$$start:l,$$end:m}));if(!p.$$bindings){var u=p,v=p,x=p.name,H={isolateScope:null,bindToController:null};D(v.scope)&&(!0===v.bindToController?(H.bindToController=d(v.scope,x,!0),H.isolateScope={}):
H.isolateScope=d(v.scope,x,!1));D(v.bindToController)&&(H.bindToController=d(v.bindToController,x,!0));if(D(H.bindToController)){var E=v.controller,M=v.controllerAs;if(!E)throw ga("noctrl",x);if(!Xc(E,M))throw ga("noident",x);}var t=u.$$bindings=H;D(t.isolateScope)&&(p.$$isolateBindings=t.isolateScope)}b.push(p);k=p}}catch(I){c(I)}}return k}function V(b){if(f.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,e=c.length;d<e;d++)if(b=c[d],b.multiElement)return!0;return!1}function $(a,b){var c=b.$attr,
d=a.$attr;q(a,function(d,e){"$"!=e.charAt(0)&&(b[e]&&b[e]!==d&&(d+=("style"===e?";":" ")+b[e]),a.$set(e,d,!0,c[e]))});q(b,function(b,e){a.hasOwnProperty(e)||"$"===e.charAt(0)||(a[e]=b,"class"!==e&&"style"!==e&&(d[e]=c[e]))})}function ba(a,b,c,d,f,g,h,k){var l=[],m,p,n=b[0],r=a.shift(),u=Rb(r,{templateUrl:null,transclude:null,replace:null,$$originalDirective:r}),H=z(r.templateUrl)?r.templateUrl(b,c):r.templateUrl,E=r.templateNamespace;b.empty();e(H).then(function(e){var v,M;e=xa(e);if(r.replace){e=
Vb.test(e)?$c(da(E,W(e))):[];v=e[0];if(1!=e.length||1!==v.nodeType)throw ga("tplrt",r.name,H);e={$attr:{}};ea(d,b,v);var B=$b(v,[],e);D(r.scope)&&T(B,!0);a=B.concat(a);$(c,e)}else v=n,b.html(e);a.unshift(u);m=oa(a,v,c,f,b,r,g,h,k);q(d,function(a,c){a==v&&(d[c]=b[0])});for(p=s(b[0].childNodes,f);l.length;){e=l.shift();M=l.shift();var t=l.shift(),I=l.shift(),B=b[0];if(!e.$$destroyed){if(M!==n){var P=M.className;k.hasElementTranscludeDirective&&r.replace||(B=Xb(v));ea(t,F(M),B);x(F(B),P)}M=m.transcludeOnThisElement?
za(e,m.transclude,I):I;m(p,e,B,d,M)}}l=null});return function(a,b,c,d,e){a=e;b.$$destroyed||(l?l.push(b,c,d,a):(m.transcludeOnThisElement&&(a=za(b,m.transclude,e)),m(p,b,c,d,a)))}}function Z(a,b){var c=b.priority-a.priority;return 0!==c?c:a.name!==b.name?a.name<b.name?-1:1:a.index-b.index}function X(a,b,c,d){function e(a){return a?" (module: "+a+")":""}if(b)throw ga("multidir",b.name,e(b.$$moduleName),c.name,e(c.$$moduleName),a,ya(d));}function ca(a,c){var d=b(c,!0);d&&a.push({priority:0,compile:function(a){a=
a.parent();var b=!!a.length;b&&aa.$$addBindingClass(a);return function(a,c){var e=c.parent();b||aa.$$addBindingClass(e);aa.$$addBindingInfo(e,d.expressions);a.$watch(d,function(a){c[0].nodeValue=a})}}})}function da(a,b){a=Q(a||"html");switch(a){case "svg":case "math":var c=C.document.createElement("div");c.innerHTML="<"+a+">"+b+"</"+a+">";return c.childNodes[0].childNodes;default:return b}}function ha(a,b){if("srcdoc"==b)return M.HTML;var c=wa(a);if("xlinkHref"==b||"form"==c&&"action"==b||"img"!=
c&&("src"==b||"ngSrc"==b))return M.RESOURCE_URL}function ia(a,c,d,e,f){var g=ha(a,e);f=k[e]||f;var h=b(d,!0,g,f);if(h){if("multiple"===e&&"select"===wa(a))throw ga("selmulti",ya(a));c.push({priority:100,compile:function(){return{pre:function(a,c,k){c=k.$$observers||(k.$$observers=U());if(m.test(e))throw ga("nodomevents");var l=k[e];l!==d&&(h=l&&b(l,!0,g,f),d=l);h&&(k[e]=h(a),(c[e]||(c[e]=[])).$$inter=!0,(k.$$observers&&k.$$observers[e].$$scope||a).$watch(h,function(a,b){"class"===e&&a!=b?k.$updateClass(a,
b):k.$set(e,a)}))}}}})}}function ea(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g<h;g++)if(a[g]==d){a[g++]=c;h=g+e-1;for(var k=a.length;g<k;g++,h++)h<k?a[g]=a[h]:delete a[g];a.length-=e-1;a.context===d&&(a.context=c);break}f&&f.replaceChild(c,d);a=C.document.createDocumentFragment();for(g=0;g<e;g++)a.appendChild(b[g]);F.hasData(d)&&(F.data(c,F.data(d)),F(d).off("$destroy"));F.cleanData(a.querySelectorAll("*"));for(g=1;g<e;g++)delete b[g];b[0]=c;b.length=1}function ja(a,
b){return S(function(){return a.apply(null,arguments)},a,b)}function la(a,b,d,e,f,g){try{a(b,d,e,f,g)}catch(h){c(h,ya(d))}}function ka(a,c,d,e,f){function g(b,c,e){z(d.$onChanges)&&c!==e&&(Y||(a.$$postDigest(I),Y=[]),m||(m={},Y.push(h)),m[b]&&(e=m[b].previousValue),m[b]=new Fb(e,c))}function h(){d.$onChanges(m);m=void 0}var k=[],l={},m;q(e,function(e,h){var m=e.attrName,p=e.optional,v,u,x,H;switch(e.mode){case "@":p||ua.call(c,m)||(d[h]=c[m]=void 0);c.$observe(m,function(a){if(G(a)||Ga(a))g(h,a,d[h]),
d[h]=a});c.$$observers[m].$$scope=a;v=c[m];G(v)?d[h]=b(v)(a):Ga(v)&&(d[h]=v);l[h]=new Fb(bc,d[h]);break;case "=":if(!ua.call(c,m)){if(p)break;c[m]=void 0}if(p&&!c[m])break;u=n(c[m]);H=u.literal?na:function(a,b){return a===b||a!==a&&b!==b};x=u.assign||function(){v=d[h]=u(a);throw ga("nonassign",c[m],m,f.name);};v=d[h]=u(a);p=function(b){H(b,d[h])||(H(b,v)?x(a,b=d[h]):d[h]=b);return v=b};p.$stateful=!0;p=e.collection?a.$watchCollection(c[m],p):a.$watch(n(c[m],p),null,u.literal);k.push(p);break;case "<":if(!ua.call(c,
m)){if(p)break;c[m]=void 0}if(p&&!c[m])break;u=n(c[m]);var E=d[h]=u(a);l[h]=new Fb(bc,d[h]);p=a.$watch(u,function(a,b){if(b===a){if(b===E)return;b=E}g(h,a,b);d[h]=a},u.literal);k.push(p);break;case "&":u=c.hasOwnProperty(m)?n(c[m]):A;if(u===A&&p)break;d[h]=function(b){return u(a,b)}}});return{initialChanges:l,removeWatches:k.length&&function(){for(var a=0,b=k.length;a<b;++a)k[a]()}}}var ta=/^\w/,pa=C.document.createElement("div"),qa=u,Y;Da.prototype={$normalize:Aa,$addClass:function(a){a&&0<a.length&&
H.addClass(this.$$element,a)},$removeClass:function(a){a&&0<a.length&&H.removeClass(this.$$element,a)},$updateClass:function(a,b){var c=ad(a,b);c&&c.length&&H.addClass(this.$$element,c);(c=ad(b,a))&&c.length&&H.removeClass(this.$$element,c)},$set:function(a,b,d,e){var f=Uc(this.$$element[0],a),g=bd[a],h=a;f?(this.$$element.prop(a,b),e=f):g&&(this[g]=b,h=g);this[a]=b;e?this.$attr[a]=e:(e=this.$attr[a])||(this.$attr[a]=e=Cc(a,"-"));f=wa(this.$$element);if("a"===f&&("href"===a||"xlinkHref"===a)||"img"===
f&&"src"===a)this[a]=b=E(b,"src"===a);else if("img"===f&&"srcset"===a&&w(b)){for(var f="",g=W(b),k=/(\s+\d+x\s*,|\s+\d+w\s*,|\s+,|,\s+)/,k=/\s/.test(g)?k:/(,)/,g=g.split(k),k=Math.floor(g.length/2),l=0;l<k;l++)var m=2*l,f=f+E(W(g[m]),!0),f=f+(" "+W(g[m+1]));g=W(g[2*l]).split(/\s/);f+=E(W(g[0]),!0);2===g.length&&(f+=" "+W(g[1]));this[a]=b=f}!1!==d&&(null===b||y(b)?this.$$element.removeAttr(e):ta.test(e)?this.$$element.attr(e,b):P(this.$$element[0],e,b));(a=this.$$observers)&&q(a[h],function(a){try{a(b)}catch(d){c(d)}})},
$observe:function(a,b){var c=this,d=c.$$observers||(c.$$observers=U()),e=d[a]||(d[a]=[]);e.push(b);K.$evalAsync(function(){e.$$inter||!c.hasOwnProperty(a)||y(c[a])||b(c[a])});return function(){Za(e,b)}}};var ra=b.startSymbol(),sa=b.endSymbol(),xa="{{"==ra&&"}}"==sa?Xa:function(a){return a.replace(/\{\{/g,ra).replace(/}}/g,sa)},Ba=/^ngAttr[A-Z]/,Ca=/^(.+)Start$/;aa.$$addBindingInfo=p?function(a,b){var c=a.data("$binding")||[];L(b)?c=c.concat(b):c.push(b);a.data("$binding",c)}:A;aa.$$addBindingClass=
p?function(a){x(a,"ng-binding")}:A;aa.$$addScopeInfo=p?function(a,b,c,d){a.data(c?d?"$isolateScopeNoTemplate":"$isolateScope":"$scope",b)}:A;aa.$$addScopeClass=p?function(a,b){x(a,b?"ng-isolate-scope":"ng-scope")}:A;aa.$$createComment=function(a,b){var c="";p&&(c=" "+(a||"")+": ",b&&(c+=b+" "));return C.document.createComment(c)};return aa}]}function Fb(a,b){this.previousValue=a;this.currentValue=b}function Aa(a){return db(a.replace(Yc,""))}function ad(a,b){var d="",c=a.split(/\s+/),e=b.split(/\s+/),
f=0;a:for(;f<c.length;f++){for(var g=c[f],h=0;h<e.length;h++)if(g==e[h])continue a;d+=(0<d.length?" ":"")+g}return d}function $c(a){a=F(a);var b=a.length;if(1>=b)return a;for(;b--;)8===a[b].nodeType&&bg.call(a,b,1);return a}function Xc(a,b){if(b&&G(b))return b;if(G(a)){var d=cd.exec(a);if(d)return d[3]}}function ff(){var a={},b=!1;this.has=function(b){return a.hasOwnProperty(b)};this.register=function(b,c){Qa(b,"controller");D(b)?S(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector",
"$window",function(d,c){function e(a,b,c,d){if(!a||!D(a.$scope))throw N("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,g,h,k){var l,m,n;h=!0===h;k&&G(k)&&(n=k);if(G(f)){k=f.match(cd);if(!k)throw cg("ctrlfmt",f);m=k[1];n=n||k[3];f=a.hasOwnProperty(m)?a[m]:Ec(g.$scope,m,!0)||(b?Ec(c,m,!0):void 0);Pa(f,m,!0)}if(h)return h=(L(f)?f[f.length-1]:f).prototype,l=Object.create(h||null),n&&e(g,n,l,m||f.name),S(function(){var a=d.invoke(f,l,g,m);a!==l&&(D(a)||z(a))&&(l=a,n&&e(g,n,l,m||f.name));return l},
{instance:l,identifier:n});l=d.instantiate(f,g,m);n&&e(g,n,l,m||f.name);return l}}]}function gf(){this.$get=["$window",function(a){return F(a.document)}]}function hf(){this.$get=["$log",function(a){return function(b,d){a.error.apply(a,arguments)}}]}function cc(a){return D(a)?da(a)?a.toISOString():bb(a):a}function nf(){this.$get=function(){return function(a){if(!a)return"";var b=[];tc(a,function(a,c){null===a||y(a)||(L(a)?q(a,function(a){b.push(ea(c)+"="+ea(cc(a)))}):b.push(ea(c)+"="+ea(cc(a))))});
return b.join("&")}}}function of(){this.$get=function(){return function(a){function b(a,e,f){null===a||y(a)||(L(a)?q(a,function(a,c){b(a,e+"["+(D(a)?c:"")+"]")}):D(a)&&!da(a)?tc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):d.push(ea(e)+"="+ea(cc(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function dc(a,b){if(G(a)){var d=a.replace(dg,"").trim();if(d){var c=b("Content-Type");(c=c&&0===c.indexOf(dd))||(c=(c=d.match(eg))&&fg[c[0]].test(d));c&&(a=xc(d))}}return a}function ed(a){var b=
U(),d;G(a)?q(a.split("\n"),function(a){d=a.indexOf(":");var e=Q(W(a.substr(0,d)));a=W(a.substr(d+1));e&&(b[e]=b[e]?b[e]+", "+a:a)}):D(a)&&q(a,function(a,d){var f=Q(d),g=W(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function fd(a){var b;return function(d){b||(b=ed(a));return d?(d=b[Q(d)],void 0===d&&(d=null),d):b}}function gd(a,b,d,c){if(z(c))return c(a,b,d);q(c,function(c){a=c(a,b,d)});return a}function mf(){var a=this.defaults={transformResponse:[dc],transformRequest:[function(a){return D(a)&&"[object File]"!==
ma.call(a)&&"[object Blob]"!==ma.call(a)&&"[object FormData]"!==ma.call(a)?bb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ia(ec),put:ia(ec),patch:ia(ec)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer"},b=!1;this.useApplyAsync=function(a){return w(a)?(b=!!a,this):b};var d=!0;this.useLegacyPromiseExtensions=function(a){return w(a)?(d=!!a,this):d};var c=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory",
"$rootScope","$q","$injector",function(e,f,g,h,k,l){function m(b){function c(a,b){for(var d=0,e=b.length;d<e;){var f=b[d++],g=b[d++];a=a.then(f,g)}b.length=0;return a}function e(a,b){var c,d={};q(a,function(a,e){z(a)?(c=a(b),null!=c&&(d[e]=c)):d[e]=a});return d}function f(a){var b=S({},a);b.data=gd(a.data,a.headers,a.status,g.transformResponse);a=a.status;return 200<=a&&300>a?b:k.reject(b)}if(!D(b))throw N("$http")("badreq",b);if(!G(b.url))throw N("$http")("badreq",b.url);var g=S({method:"get",transformRequest:a.transformRequest,
transformResponse:a.transformResponse,paramSerializer:a.paramSerializer},b);g.headers=function(b){var c=a.headers,d=S({},b.headers),f,g,h,c=S({},c.common,c[Q(b.method)]);a:for(f in c){g=Q(f);for(h in d)if(Q(h)===g)continue a;d[f]=c[f]}return e(d,ia(b))}(b);g.method=ub(g.method);g.paramSerializer=G(g.paramSerializer)?l.get(g.paramSerializer):g.paramSerializer;var h=[],m=[],p=k.when(g);q(R,function(a){(a.request||a.requestError)&&h.unshift(a.request,a.requestError);(a.response||a.responseError)&&m.push(a.response,
a.responseError)});p=c(p,h);p=p.then(function(b){var c=b.headers,d=gd(b.data,fd(c),void 0,b.transformRequest);y(d)&&q(c,function(a,b){"content-type"===Q(b)&&delete c[b]});y(b.withCredentials)&&!y(a.withCredentials)&&(b.withCredentials=a.withCredentials);return n(b,d).then(f,f)});p=c(p,m);d?(p.success=function(a){Pa(a,"fn");p.then(function(b){a(b.data,b.status,b.headers,g)});return p},p.error=function(a){Pa(a,"fn");p.then(null,function(b){a(b.data,b.status,b.headers,g)});return p}):(p.success=hd("success"),
p.error=hd("error"));return p}function n(c,d){function g(a){if(a){var c={};q(a,function(a,d){c[d]=function(c){function d(){a(c)}b?h.$applyAsync(d):h.$$phase?d():h.$apply(d)}});return c}}function l(a,c,d,e){function f(){n(c,a,d,e)}E&&(200<=a&&300>a?E.put(P,[a,c,ed(d),e]):E.remove(P));b?h.$applyAsync(f):(f(),h.$$phase||h.$apply())}function n(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?M.resolve:M.reject)({data:a,status:b,headers:fd(d),config:c,statusText:e})}function t(a){n(a.data,a.status,ia(a.headers()),
a.statusText)}function R(){var a=m.pendingRequests.indexOf(c);-1!==a&&m.pendingRequests.splice(a,1)}var M=k.defer(),H=M.promise,E,I,Da=c.headers,P=p(c.url,c.paramSerializer(c.params));m.pendingRequests.push(c);H.then(R,R);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(E=D(c.cache)?c.cache:D(a.cache)?a.cache:u);E&&(I=E.get(P),w(I)?I&&z(I.then)?I.then(t,t):L(I)?n(I[1],I[0],ia(I[2]),I[3]):n(I,200,{},"OK"):E.put(P,H));y(I)&&((I=id(c.url)?f()[c.xsrfCookieName||a.xsrfCookieName]:
void 0)&&(Da[c.xsrfHeaderName||a.xsrfHeaderName]=I),e(c.method,P,d,l,Da,c.timeout,c.withCredentials,c.responseType,g(c.eventHandlers),g(c.uploadEventHandlers)));return H}function p(a,b){0<b.length&&(a+=(-1==a.indexOf("?")?"?":"&")+b);return a}var u=g("$http");a.paramSerializer=G(a.paramSerializer)?l.get(a.paramSerializer):a.paramSerializer;var R=[];q(c,function(a){R.unshift(G(a)?l.get(a):l.invoke(a))});m.pendingRequests=[];(function(a){q(arguments,function(a){m[a]=function(b,c){return m(S({},c||{},
{method:a,url:b}))}})})("get","delete","head","jsonp");(function(a){q(arguments,function(a){m[a]=function(b,c,d){return m(S({},d||{},{method:a,url:b,data:c}))}})})("post","put","patch");m.defaults=a;return m}]}function qf(){this.$get=function(){return function(){return new C.XMLHttpRequest}}}function pf(){this.$get=["$browser","$jsonpCallbacks","$document","$xhrFactory",function(a,b,d,c){return gg(a,c,a.defer,b,d[0])}]}function gg(a,b,d,c,e){function f(a,b,d){a=a.replace("JSON_CALLBACK",b);var f=
e.createElement("script"),m=null;f.type="text/javascript";f.src=a;f.async=!0;m=function(a){f.removeEventListener("load",m,!1);f.removeEventListener("error",m,!1);e.body.removeChild(f);f=null;var g=-1,u="unknown";a&&("load"!==a.type||c.wasCalled(b)||(a={type:"error"}),u=a.type,g="error"===a.type?404:200);d&&d(g,u)};f.addEventListener("load",m,!1);f.addEventListener("error",m,!1);e.body.appendChild(f);return m}return function(e,h,k,l,m,n,p,u,R,B){function r(){fa&&fa();t&&t.abort()}function J(b,c,e,
f,g){w(M)&&d.cancel(M);fa=t=null;b(c,e,f,g);a.$$completeOutstandingRequest(A)}a.$$incOutstandingRequestCount();h=h||a.url();if("jsonp"===Q(e))var v=c.createCallback(h),fa=f(h,v,function(a,b){var d=200===a&&c.getResponse(v);J(l,a,d,"",b);c.removeCallback(v)});else{var t=b(e,h);t.open(e,h,!0);q(m,function(a,b){w(a)&&t.setRequestHeader(b,a)});t.onload=function(){var a=t.statusText||"",b="response"in t?t.response:t.responseText,c=1223===t.status?204:t.status;0===c&&(c=b?200:"file"==Y(h).protocol?404:
0);J(l,c,b,t.getAllResponseHeaders(),a)};e=function(){J(l,-1,null,null,"")};t.onerror=e;t.onabort=e;q(R,function(a,b){t.addEventListener(b,a)});q(B,function(a,b){t.upload.addEventListener(b,a)});p&&(t.withCredentials=!0);if(u)try{t.responseType=u}catch(K){if("json"!==u)throw K;}t.send(y(k)?null:k)}if(0<n)var M=d(r,n);else n&&z(n.then)&&n.then(r)}}function kf(){var a="{{",b="}}";this.startSymbol=function(b){return b?(a=b,this):a};this.endSymbol=function(a){return a?(b=a,this):b};this.$get=["$parse",
"$exceptionHandler","$sce",function(d,c,e){function f(a){return"\\\\\\"+a}function g(c){return c.replace(n,a).replace(p,b)}function h(a,b,c,d){var e;return e=a.$watch(function(a){e();return d(a)},b,c)}function k(f,k,p,n){function J(a){try{var b=a;a=p?e.getTrusted(p,b):e.valueOf(b);var d;if(n&&!w(a))d=a;else if(null==a)d="";else{switch(typeof a){case "string":break;case "number":a=""+a;break;default:a=bb(a)}d=a}return d}catch(g){c(Ka.interr(f,g))}}if(!f.length||-1===f.indexOf(a)){var v;k||(k=g(f),
v=ha(k),v.exp=f,v.expressions=[],v.$$watchDelegate=h);return v}n=!!n;var q,t,K=0,M=[],H=[];v=f.length;for(var E=[],I=[];K<v;)if(-1!=(q=f.indexOf(a,K))&&-1!=(t=f.indexOf(b,q+l)))K!==q&&E.push(g(f.substring(K,q))),K=f.substring(q+l,t),M.push(K),H.push(d(K,J)),K=t+m,I.push(E.length),E.push("");else{K!==v&&E.push(g(f.substring(K)));break}p&&1<E.length&&Ka.throwNoconcat(f);if(!k||M.length){var Da=function(a){for(var b=0,c=M.length;b<c;b++){if(n&&y(a[b]))return;E[I[b]]=a[b]}return E.join("")};return S(function(a){var b=
0,d=M.length,e=Array(d);try{for(;b<d;b++)e[b]=H[b](a);return Da(e)}catch(g){c(Ka.interr(f,g))}},{exp:f,expressions:M,$$watchDelegate:function(a,b){var c;return a.$watchGroup(H,function(d,e){var f=Da(d);z(b)&&b.call(this,f,d!==e?c:f,a);c=f})}})}}var l=a.length,m=b.length,n=new RegExp(a.replace(/./g,f),"g"),p=new RegExp(b.replace(/./g,f),"g");k.startSymbol=function(){return a};k.endSymbol=function(){return b};return k}]}function lf(){this.$get=["$rootScope","$window","$q","$$q","$browser",function(a,
b,d,c,e){function f(f,k,l,m){function n(){p?f.apply(null,u):f(r)}var p=4<arguments.length,u=p?va.call(arguments,4):[],R=b.setInterval,q=b.clearInterval,r=0,J=w(m)&&!m,v=(J?c:d).defer(),fa=v.promise;l=w(l)?l:0;fa.$$intervalId=R(function(){J?e.defer(n):a.$evalAsync(n);v.notify(r++);0<l&&r>=l&&(v.resolve(r),q(fa.$$intervalId),delete g[fa.$$intervalId]);J||a.$apply()},k);g[fa.$$intervalId]=v;return fa}var g={};f.cancel=function(a){return a&&a.$$intervalId in g?(g[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId),
delete g[a.$$intervalId],!0):!1};return f}]}function fc(a){a=a.split("/");for(var b=a.length;b--;)a[b]=qb(a[b]);return a.join("/")}function jd(a,b){var d=Y(a);b.$$protocol=d.protocol;b.$$host=d.hostname;b.$$port=Z(d.port)||hg[d.protocol]||null}function kd(a,b){var d="/"!==a.charAt(0);d&&(a="/"+a);var c=Y(a);b.$$path=decodeURIComponent(d&&"/"===c.pathname.charAt(0)?c.pathname.substring(1):c.pathname);b.$$search=Ac(c.search);b.$$hash=decodeURIComponent(c.hash);b.$$path&&"/"!=b.$$path.charAt(0)&&(b.$$path=
"/"+b.$$path)}function ka(a,b){if(0===b.lastIndexOf(a,0))return b.substr(a.length)}function Ja(a){var b=a.indexOf("#");return-1==b?a:a.substr(0,b)}function jb(a){return a.replace(/(#.+)|#$/,"$1")}function gc(a,b,d){this.$$html5=!0;d=d||"";jd(a,this);this.$$parse=function(a){var d=ka(b,a);if(!G(d))throw Gb("ipthprfx",a,b);kd(d,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Tb(this.$$search),d=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=fc(this.$$path)+(a?"?"+
a:"")+d;this.$$absUrl=b+this.$$url.substr(1)};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;w(f=ka(a,c))?(g=f,g=w(f=ka(d,f))?b+(ka("/",f)||f):a+g):w(f=ka(b,c))?g=b+f:b==c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function hc(a,b,d){jd(a,this);this.$$parse=function(c){var e=ka(a,c)||ka(b,c),f;y(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",y(e)&&(a=c,this.replace())):(f=ka(d,e),y(f)&&(f=e));kd(f,this);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;0===f.lastIndexOf(e,
0)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=Tb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=fc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+(this.$$url?d+this.$$url:"")};this.$$parseLinkUrl=function(b,d){return Ja(a)==Ja(b)?(this.$$parse(b),!0):!1}}function ld(a,b,d){this.$$html5=!0;hc.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a==Ja(c)?
f=c:(g=ka(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$compose=function(){var b=Tb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=fc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl=a+d+this.$$url}}function Hb(a){return function(){return this[a]}}function md(a,b){return function(d){if(y(d))return this[a];this[a]=b(d);this.$$compose();return this}}function sf(){var a="",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return w(b)?(a=b,this):
a};this.html5Mode=function(a){return Ga(a)?(b.enabled=a,this):D(a)?(Ga(a.enabled)&&(b.enabled=a.enabled),Ga(a.requireBase)&&(b.requireBase=a.requireBase),Ga(a.rewriteLinks)&&(b.rewriteLinks=a.rewriteLinks),this):b};this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d,c,e,f,g){function h(a,b,d){var e=l.url(),f=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(g){throw l.url(e),l.$$state=f,g;}}function k(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state,
b)}var l,m;m=c.baseHref();var n=c.url(),p;if(b.enabled){if(!m&&b.requireBase)throw Gb("nobase");p=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(m||"/");m=e.history?gc:ld}else p=Ja(n),m=hc;var u=p.substr(0,Ja(p).lastIndexOf("/")+1);l=new m(p,u,"#"+a);l.$$parseLinkUrl(n,n);l.$$state=c.state();var R=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(b.rewriteLinks&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!=a.which&&2!=a.button){for(var e=F(a.target);"a"!==wa(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;
var h=e.prop("href"),k=e.attr("href")||e.attr("xlink:href");D(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=Y(h.animVal).href);R.test(h)||!h||e.attr("target")||a.isDefaultPrevented()||!l.$$parseLinkUrl(h,k)||(a.preventDefault(),l.absUrl()!=c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});jb(l.absUrl())!=jb(n)&&c.url(l.absUrl(),!0);var q=!0;c.onUrlChange(function(a,b){y(ka(u,a))?g.location.href=a:(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,f;a=jb(a);l.$$parse(a);l.$$state=
b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;l.absUrl()===a&&(f?(l.$$parse(c),l.$$state=e,h(c,!1,e)):(q=!1,k(c,e)))}),d.$$phase||d.$digest())});d.$watch(function(){var a=jb(c.url()),b=jb(l.absUrl()),f=c.state(),g=l.$$replace,m=a!==b||l.$$html5&&e.history&&f!==l.$$state;if(q||m)q=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,f).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=f):(m&&h(b,g,f===l.$$state?null:l.$$state),
k(a,f)))});l.$$replace=!1});return l}]}function tf(){var a=!0,b=this;this.debugEnabled=function(b){return w(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)?"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||A;a=!1;try{a=!!e.apply}catch(k){}return a?function(){var a=[];q(arguments,function(b){a.push(c(b))});
return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b,arguments)}}()}}]}function Sa(a,b){if("__defineGetter__"===a||"__defineSetter__"===a||"__lookupGetter__"===a||"__lookupSetter__"===a||"__proto__"===a)throw X("isecfld",b);return a}function ig(a){return a+""}function ra(a,b){if(a){if(a.constructor===a)throw X("isecfn",b);if(a.window===a)throw X("isecwindow",b);if(a.children&&
(a.nodeName||a.prop&&a.attr&&a.find))throw X("isecdom",b);if(a===Object)throw X("isecobj",b);}return a}function nd(a,b){if(a){if(a.constructor===a)throw X("isecfn",b);if(a===jg||a===kg||a===lg)throw X("isecff",b);}}function Ib(a,b){if(a&&(a===(0).constructor||a===(!1).constructor||a==="".constructor||a==={}.constructor||a===[].constructor||a===Function.constructor))throw X("isecaf",b);}function mg(a,b){return"undefined"!==typeof a?a:b}function od(a,b){return"undefined"===typeof a?b:"undefined"===
typeof b?a:a+b}function V(a,b){var d,c;switch(a.type){case s.Program:d=!0;q(a.body,function(a){V(a.expression,b);d=d&&a.expression.constant});a.constant=d;break;case s.Literal:a.constant=!0;a.toWatch=[];break;case s.UnaryExpression:V(a.argument,b);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case s.BinaryExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case s.LogicalExpression:V(a.left,b);V(a.right,
b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case s.ConditionalExpression:V(a.test,b);V(a.alternate,b);V(a.consequent,b);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch=a.constant?[]:[a];break;case s.Identifier:a.constant=!1;a.toWatch=[a];break;case s.MemberExpression:V(a.object,b);a.computed&&V(a.property,b);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=[a];break;case s.CallExpression:d=a.filter?!b(a.callee.name).$stateful:
!1;c=[];q(a.arguments,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=a.filter&&!b(a.callee.name).$stateful?c:[a];break;case s.AssignmentExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case s.ArrayExpression:d=!0;c=[];q(a.elements,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=c;break;case s.ObjectExpression:d=!0;c=[];q(a.properties,function(a){V(a.value,
b);d=d&&a.value.constant&&!a.computed;a.value.constant||c.push.apply(c,a.value.toWatch)});a.constant=d;a.toWatch=c;break;case s.ThisExpression:a.constant=!1;a.toWatch=[];break;case s.LocalsExpression:a.constant=!1,a.toWatch=[]}}function pd(a){if(1==a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:void 0}}function qd(a){return a.type===s.Identifier||a.type===s.MemberExpression}function rd(a){if(1===a.body.length&&qd(a.body[0].expression))return{type:s.AssignmentExpression,
left:a.body[0].expression,right:{type:s.NGValueParameter},operator:"="}}function sd(a){return 0===a.body.length||1===a.body.length&&(a.body[0].expression.type===s.Literal||a.body[0].expression.type===s.ArrayExpression||a.body[0].expression.type===s.ObjectExpression)}function td(a,b){this.astBuilder=a;this.$filter=b}function ud(a,b){this.astBuilder=a;this.$filter=b}function Jb(a){return"constructor"==a}function ic(a){return z(a.valueOf)?a.valueOf():ng.call(a)}function uf(){var a=U(),b=U(),d={"true":!0,
"false":!1,"null":null,undefined:void 0},c,e;this.addLiteral=function(a,b){d[a]=b};this.setIdentifierFns=function(a,b){c=a;e=b;return this};this.$get=["$filter",function(f){function g(c,d,e){var g,k,H;e=e||J;switch(typeof c){case "string":H=c=c.trim();var E=e?b:a;g=E[H];if(!g){":"===c.charAt(0)&&":"===c.charAt(1)&&(k=!0,c=c.substring(2));g=e?r:B;var q=new jc(g);g=(new kc(q,f,g)).parse(c);g.constant?g.$$watchDelegate=p:k?g.$$watchDelegate=g.literal?n:m:g.inputs&&(g.$$watchDelegate=l);e&&(g=h(g));E[H]=
g}return u(g,d);case "function":return u(c,d);default:return u(A,d)}}function h(a){function b(c,d,e,f){var g=J;J=!0;try{return a(c,d,e,f)}finally{J=g}}if(!a)return a;b.$$watchDelegate=a.$$watchDelegate;b.assign=h(a.assign);b.constant=a.constant;b.literal=a.literal;for(var c=0;a.inputs&&c<a.inputs.length;++c)a.inputs[c]=h(a.inputs[c]);b.inputs=a.inputs;return b}function k(a,b){return null==a||null==b?a===b:"object"===typeof a&&(a=ic(a),"object"===typeof a)?!1:a===b||a!==a&&b!==b}function l(a,b,c,d,
e){var f=d.inputs,g;if(1===f.length){var h=k,f=f[0];return a.$watch(function(a){var b=f(a);k(b,h)||(g=d(a,void 0,void 0,[b]),h=b&&ic(b));return g},b,c,e)}for(var l=[],m=[],p=0,n=f.length;p<n;p++)l[p]=k,m[p]=null;return a.$watch(function(a){for(var b=!1,c=0,e=f.length;c<e;c++){var h=f[c](a);if(b||(b=!k(h,l[c])))m[c]=h,l[c]=h&&ic(h)}b&&(g=d(a,void 0,void 0,m));return g},b,c,e)}function m(a,b,c,d){var e,f;return e=a.$watch(function(a){return d(a)},function(a,c,d){f=a;z(b)&&b.apply(this,arguments);w(a)&&
d.$$postDigest(function(){w(f)&&e()})},c)}function n(a,b,c,d){function e(a){var b=!0;q(a,function(a){w(a)||(b=!1)});return b}var f,g;return f=a.$watch(function(a){return d(a)},function(a,c,d){g=a;z(b)&&b.call(this,a,c,d);e(a)&&d.$$postDigest(function(){e(g)&&f()})},c)}function p(a,b,c,d){var e;return e=a.$watch(function(a){e();return d(a)},b,c)}function u(a,b){if(!b)return a;var c=a.$$watchDelegate,d=!1,c=c!==n&&c!==m?function(c,e,f,g){f=d&&g?g[0]:a(c,e,f,g);return b(f,c,e)}:function(c,d,e,f){e=a(c,
d,e,f);c=b(e,c,d);return w(e)?c:e};a.$$watchDelegate&&a.$$watchDelegate!==l?c.$$watchDelegate=a.$$watchDelegate:b.$stateful||(c.$$watchDelegate=l,d=!a.inputs,c.inputs=a.inputs?a.inputs:[a]);return c}var R=Ba().noUnsafeEval,B={csp:R,expensiveChecks:!1,literals:pa(d),isIdentifierStart:z(c)&&c,isIdentifierContinue:z(e)&&e},r={csp:R,expensiveChecks:!0,literals:pa(d),isIdentifierStart:z(c)&&c,isIdentifierContinue:z(e)&&e},J=!1;g.$$runningExpensiveChecks=function(){return J};return g}]}function wf(){this.$get=
["$rootScope","$exceptionHandler",function(a,b){return vd(function(b){a.$evalAsync(b)},b)}]}function xf(){this.$get=["$browser","$exceptionHandler",function(a,b){return vd(function(b){a.defer(b)},b)}]}function vd(a,b){function d(){this.$$state={status:0}}function c(a,b){return function(c){b.call(a,c)}}function e(c){!c.processScheduled&&c.pending&&(c.processScheduled=!0,a(function(){var a,d,e;e=c.pending;c.processScheduled=!1;c.pending=void 0;for(var f=0,g=e.length;f<g;++f){d=e[f][0];a=e[f][c.status];
try{z(a)?d.resolve(a(c.value)):1===c.status?d.resolve(c.value):d.reject(c.value)}catch(h){d.reject(h),b(h)}}}))}function f(){this.promise=new d}var g=N("$q",TypeError),h=function(){var a=new f;a.resolve=c(a,a.resolve);a.reject=c(a,a.reject);a.notify=c(a,a.notify);return a};S(d.prototype,{then:function(a,b,c){if(y(a)&&y(b)&&y(c))return this;var d=new f;this.$$state.pending=this.$$state.pending||[];this.$$state.pending.push([d,a,b,c]);0<this.$$state.status&&e(this.$$state);return d.promise},"catch":function(a){return this.then(null,
a)},"finally":function(a,b){return this.then(function(b){return l(b,!0,a)},function(b){return l(b,!1,a)},b)}});S(f.prototype,{resolve:function(a){this.promise.$$state.status||(a===this.promise?this.$$reject(g("qcycle",a)):this.$$resolve(a))},$$resolve:function(a){function d(a){k||(k=!0,h.$$resolve(a))}function f(a){k||(k=!0,h.$$reject(a))}var g,h=this,k=!1;try{if(D(a)||z(a))g=a&&a.then;z(g)?(this.promise.$$state.status=-1,g.call(a,d,f,c(this,this.notify))):(this.promise.$$state.value=a,this.promise.$$state.status=
1,e(this.promise.$$state))}catch(l){f(l),b(l)}},reject:function(a){this.promise.$$state.status||this.$$reject(a)},$$reject:function(a){this.promise.$$state.value=a;this.promise.$$state.status=2;e(this.promise.$$state)},notify:function(c){var d=this.promise.$$state.pending;0>=this.promise.$$state.status&&d&&d.length&&a(function(){for(var a,e,f=0,g=d.length;f<g;f++){e=d[f][0];a=d[f][3];try{e.notify(z(a)?a(c):c)}catch(h){b(h)}}})}});var k=function(a,b){var c=new f;b?c.resolve(a):c.reject(a);return c.promise},
l=function(a,b,c){var d=null;try{z(c)&&(d=c())}catch(e){return k(e,!1)}return d&&z(d.then)?d.then(function(){return k(a,b)},function(a){return k(a,!1)}):k(a,b)},m=function(a,b,c,d){var e=new f;e.resolve(a);return e.promise.then(b,c,d)},n=function(a){if(!z(a))throw g("norslvr",a);var b=new f;a(function(a){b.resolve(a)},function(a){b.reject(a)});return b.promise};n.prototype=d.prototype;n.defer=h;n.reject=function(a){var b=new f;b.reject(a);return b.promise};n.when=m;n.resolve=m;n.all=function(a){var b=
new f,c=0,d=L(a)?[]:{};q(a,function(a,e){c++;m(a).then(function(a){d.hasOwnProperty(e)||(d[e]=a,--c||b.resolve(d))},function(a){d.hasOwnProperty(e)||b.reject(a)})});0===c&&b.resolve(d);return b.promise};n.race=function(a){var b=h();q(a,function(a){m(a).then(b.resolve,b.reject)});return b.promise};return n}function Gf(){this.$get=["$window","$timeout",function(a,b){var d=a.requestAnimationFrame||a.webkitRequestAnimationFrame,c=a.cancelAnimationFrame||a.webkitCancelAnimationFrame||a.webkitCancelRequestAnimationFrame,
e=!!d,f=e?function(a){var b=d(a);return function(){c(b)}}:function(a){var c=b(a,16.66,!1);return function(){b.cancel(c)}};f.supported=e;return f}]}function vf(){function a(a){function b(){this.$$watchers=this.$$nextSibling=this.$$childHead=this.$$childTail=null;this.$$listeners={};this.$$listenerCount={};this.$$watchersCount=0;this.$id=++pb;this.$$ChildScope=null}b.prototype=a;return b}var b=10,d=N("$rootScope"),c=null,e=null;this.digestTtl=function(a){arguments.length&&(b=a);return b};this.$get=
["$exceptionHandler","$parse","$browser",function(f,g,h){function k(a){a.currentScope.$$destroyed=!0}function l(a){9===Ea&&(a.$$childHead&&l(a.$$childHead),a.$$nextSibling&&l(a.$$nextSibling));a.$parent=a.$$nextSibling=a.$$prevSibling=a.$$childHead=a.$$childTail=a.$root=a.$$watchers=null}function m(){this.$id=++pb;this.$$phase=this.$parent=this.$$watchers=this.$$nextSibling=this.$$prevSibling=this.$$childHead=this.$$childTail=null;this.$root=this;this.$$destroyed=!1;this.$$listeners={};this.$$listenerCount=
{};this.$$watchersCount=0;this.$$isolateBindings=null}function n(a){if(J.$$phase)throw d("inprog",J.$$phase);J.$$phase=a}function p(a,b){do a.$$watchersCount+=b;while(a=a.$parent)}function u(a,b,c){do a.$$listenerCount[c]-=b,0===a.$$listenerCount[c]&&delete a.$$listenerCount[c];while(a=a.$parent)}function s(){}function B(){for(;t.length;)try{t.shift()()}catch(a){f(a)}e=null}function r(){null===e&&(e=h.defer(function(){J.$apply(B)}))}m.prototype={constructor:m,$new:function(b,c){var d;c=c||this;b?
(d=new m,d.$root=this.$root):(this.$$ChildScope||(this.$$ChildScope=a(this)),d=new this.$$ChildScope);d.$parent=c;d.$$prevSibling=c.$$childTail;c.$$childHead?(c.$$childTail.$$nextSibling=d,c.$$childTail=d):c.$$childHead=c.$$childTail=d;(b||c!=this)&&d.$on("$destroy",k);return d},$watch:function(a,b,d,e){var f=g(a);if(f.$$watchDelegate)return f.$$watchDelegate(this,b,d,f,a);var h=this,k=h.$$watchers,l={fn:b,last:s,get:f,exp:e||a,eq:!!d};c=null;z(b)||(l.fn=A);k||(k=h.$$watchers=[]);k.unshift(l);p(this,
1);return function(){0<=Za(k,l)&&p(h,-1);c=null}},$watchGroup:function(a,b){function c(){h=!1;k?(k=!1,b(e,e,g)):b(e,d,g)}var d=Array(a.length),e=Array(a.length),f=[],g=this,h=!1,k=!0;if(!a.length){var l=!0;g.$evalAsync(function(){l&&b(e,e,g)});return function(){l=!1}}if(1===a.length)return this.$watch(a[0],function(a,c,f){e[0]=a;d[0]=c;b(e,a===c?e:d,f)});q(a,function(a,b){var k=g.$watch(a,function(a,f){e[b]=a;d[b]=f;h||(h=!0,g.$evalAsync(c))});f.push(k)});return function(){for(;f.length;)f.shift()()}},
$watchCollection:function(a,b){function c(a){e=a;var b,d,g,h;if(!y(e)){if(D(e))if(ta(e))for(f!==n&&(f=n,u=f.length=0,l++),a=e.length,u!==a&&(l++,f.length=u=a),b=0;b<a;b++)h=f[b],g=e[b],d=h!==h&&g!==g,d||h===g||(l++,f[b]=g);else{f!==p&&(f=p={},u=0,l++);a=0;for(b in e)ua.call(e,b)&&(a++,g=e[b],h=f[b],b in f?(d=h!==h&&g!==g,d||h===g||(l++,f[b]=g)):(u++,f[b]=g,l++));if(u>a)for(b in l++,f)ua.call(e,b)||(u--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1<b.length,l=0,m=
g(a,c),n=[],p={},r=!0,u=0;return this.$watch(m,function(){r?(r=!1,b(e,e,d)):b(e,h,d);if(k)if(D(e))if(ta(e)){h=Array(e.length);for(var a=0;a<e.length;a++)h[a]=e[a]}else for(a in h={},e)ua.call(e,a)&&(h[a]=e[a]);else h=e})},$digest:function(){var a,g,k,l,m,p,u,r,q=b,t,y=[],A,C;n("$digest");h.$$checkUrlChange();this===J&&null!==e&&(h.defer.cancel(e),B());c=null;do{r=!1;t=this;for(p=0;p<v.length;p++){try{C=v[p],C.scope.$eval(C.expression,C.locals)}catch(F){f(F)}c=null}v.length=0;a:do{if(p=t.$$watchers)for(u=
p.length;u--;)try{if(a=p[u])if(m=a.get,(g=m(t))!==(k=a.last)&&!(a.eq?na(g,k):"number"===typeof g&&"number"===typeof k&&isNaN(g)&&isNaN(k)))r=!0,c=a,a.last=a.eq?pa(g,null):g,l=a.fn,l(g,k===s?g:k,t),5>q&&(A=4-q,y[A]||(y[A]=[]),y[A].push({msg:z(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:k}));else if(a===c){r=!1;break a}}catch(G){f(G)}if(!(p=t.$$watchersCount&&t.$$childHead||t!==this&&t.$$nextSibling))for(;t!==this&&!(p=t.$$nextSibling);)t=t.$parent}while(t=p);if((r||v.length)&&
!q--)throw J.$$phase=null,d("infdig",b,y);}while(r||v.length);for(J.$$phase=null;K<w.length;)try{w[K++]()}catch(D){f(D)}w.length=K=0},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===J&&h.$$applicationDestroyed();p(this,-this.$$watchersCount);for(var b in this.$$listenerCount)u(this,this.$$listenerCount[b],b);a&&a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&
(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&&(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=A;this.$on=this.$watch=this.$watchGroup=function(){return A};this.$$listeners={};this.$$nextSibling=null;l(this)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){J.$$phase||v.length||h.defer(function(){v.length&&J.$digest()});v.push({scope:this,expression:g(a),locals:b})},$$postDigest:function(a){w.push(a)},
$apply:function(a){try{n("$apply");try{return this.$eval(a)}finally{J.$$phase=null}}catch(b){f(b)}finally{try{J.$digest()}catch(c){throw f(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&t.push(b);a=g(a);r()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,u(e,1,a))}},$emit:function(a,
b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e,stopPropagation:function(){g=!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=$a([h],arguments,1),l,m;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(m=d.length;l<m;l++)if(d[l])try{d[l].apply(null,k)}catch(n){f(n)}else d.splice(l,1),l--,m--;if(g)return h.currentScope=null,h;e=e.$parent}while(e);h.currentScope=null;return h},$broadcast:function(a,b){var c=this,d=this,e={name:a,targetScope:this,preventDefault:function(){e.defaultPrevented=
!0},defaultPrevented:!1};if(!this.$$listenerCount[a])return e;for(var g=$a([e],arguments,1),h,k;c=d;){e.currentScope=c;d=c.$$listeners[a]||[];h=0;for(k=d.length;h<k;h++)if(d[h])try{d[h].apply(null,g)}catch(l){f(l)}else d.splice(h,1),h--,k--;if(!(d=c.$$listenerCount[a]&&c.$$childHead||c!==this&&c.$$nextSibling))for(;c!==this&&!(d=c.$$nextSibling);)c=c.$parent}e.currentScope=null;return e}};var J=new m,v=J.$$asyncQueue=[],w=J.$$postDigestQueue=[],t=J.$$applyAsyncQueue=[],K=0;return J}]}function ne(){var a=
/^\s*(https?|ftp|mailto|tel|file):/,b=/^\s*((https?|ftp|file|blob):|data:image\/)/;this.aHrefSanitizationWhitelist=function(b){return w(b)?(a=b,this):a};this.imgSrcSanitizationWhitelist=function(a){return w(a)?(b=a,this):b};this.$get=function(){return function(d,c){var e=c?b:a,f;f=Y(d).href;return""===f||f.match(e)?d:"unsafe:"+f}}}function og(a){if("self"===a)return a;if(G(a)){if(-1<a.indexOf("***"))throw sa("iwcard",a);a=wd(a).replace("\\*\\*",".*").replace("\\*","[^:/.?&;]*");return new RegExp("^"+
a+"$")}if(Wa(a))return new RegExp("^"+a.source+"$");throw sa("imatcher");}function xd(a){var b=[];w(a)&&q(a,function(a){b.push(og(a))});return b}function zf(){this.SCE_CONTEXTS=la;var a=["self"],b=[];this.resourceUrlWhitelist=function(b){arguments.length&&(a=xd(b));return a};this.resourceUrlBlacklist=function(a){arguments.length&&(b=xd(a));return b};this.$get=["$injector",function(d){function c(a,b){return"self"===a?id(b):!!a.exec(b.href)}function e(a){var b=function(a){this.$$unwrapTrustedValue=
function(){return a}};a&&(b.prototype=new a);b.prototype.valueOf=function(){return this.$$unwrapTrustedValue()};b.prototype.toString=function(){return this.$$unwrapTrustedValue().toString()};return b}var f=function(a){throw sa("unsafe");};d.has("$sanitize")&&(f=d.get("$sanitize"));var g=e(),h={};h[la.HTML]=e(g);h[la.CSS]=e(g);h[la.URL]=e(g);h[la.JS]=e(g);h[la.RESOURCE_URL]=e(h[la.URL]);return{trustAs:function(a,b){var c=h.hasOwnProperty(a)?h[a]:null;if(!c)throw sa("icontext",a,b);if(null===b||y(b)||
""===b)return b;if("string"!==typeof b)throw sa("itype",a);return new c(b)},getTrusted:function(d,e){if(null===e||y(e)||""===e)return e;var g=h.hasOwnProperty(d)?h[d]:null;if(g&&e instanceof g)return e.$$unwrapTrustedValue();if(d===la.RESOURCE_URL){var g=Y(e.toString()),n,p,u=!1;n=0;for(p=a.length;n<p;n++)if(c(a[n],g)){u=!0;break}if(u)for(n=0,p=b.length;n<p;n++)if(c(b[n],g)){u=!1;break}if(u)return e;throw sa("insecurl",e.toString());}if(d===la.HTML)return f(e);throw sa("unsafe");},valueOf:function(a){return a instanceof
g?a.$$unwrapTrustedValue():a}}}]}function yf(){var a=!0;this.enabled=function(b){arguments.length&&(a=!!b);return a};this.$get=["$parse","$sceDelegate",function(b,d){if(a&&8>Ea)throw sa("iequirks");var c=ia(la);c.isEnabled=function(){return a};c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b},c.valueOf=Xa);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs,
f=c.getTrusted,g=c.trustAs;q(la,function(a,b){var d=Q(b);c[db("parse_as_"+d)]=function(b){return e(a,b)};c[db("get_trusted_"+d)]=function(b){return f(a,b)};c[db("trust_as_"+d)]=function(b){return g(a,b)}});return c}]}function Af(){this.$get=["$window","$document",function(a,b){var d={},c=!(a.chrome&&a.chrome.app&&a.chrome.app.runtime)&&a.history&&a.history.pushState,e=Z((/android (\d+)/.exec(Q((a.navigator||{}).userAgent))||[])[1]),f=/Boxee/i.test((a.navigator||{}).userAgent),g=b[0]||{},h,k=/^(Moz|webkit|ms)(?=[A-Z])/,
l=g.body&&g.body.style,m=!1,n=!1;if(l){for(var p in l)if(m=k.exec(p)){h=m[0];h=h[0].toUpperCase()+h.substr(1);break}h||(h="WebkitOpacity"in l&&"webkit");m=!!("transition"in l||h+"Transition"in l);n=!!("animation"in l||h+"Animation"in l);!e||m&&n||(m=G(l.webkitTransition),n=G(l.webkitAnimation))}return{history:!(!c||4>e||f),hasEvent:function(a){if("input"===a&&11>=Ea)return!1;if(y(d[a])){var b=g.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ba(),vendorPrefix:h,transitions:m,animations:n,android:e}}]}
function Cf(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$templateCache","$http","$q","$sce",function(b,d,c,e){function f(g,h){f.totalPendingRequests++;if(!G(g)||y(b.get(g)))g=e.getTrustedResourceUrl(g);var k=d.defaults&&d.defaults.transformResponse;L(k)?k=k.filter(function(a){return a!==dc}):k===dc&&(k=null);return d.get(g,S({cache:b,transformResponse:k},a))["finally"](function(){f.totalPendingRequests--}).then(function(a){b.put(g,a.data);return a.data},function(a){if(!h)throw pg("tpload",
g,a.status,a.statusText);return c.reject(a)})}f.totalPendingRequests=0;return f}]}function Df(){this.$get=["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];q(a,function(a){var c=ca.element(a).data("$binding");c&&q(c,function(c){d?(new RegExp("(^|\\s)"+wd(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!=c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-","data-ng-","ng\\:"],h=0;h<g.length;++h){var k=
a.querySelectorAll("["+g[h]+"model"+(d?"=":"*=")+'"'+b+'"]');if(k.length)return k}},getLocation:function(){return d.url()},setLocation:function(b){b!==d.url()&&(d.url(b),a.$digest())},whenStable:function(a){b.notifyWhenNoOutstandingRequests(a)}}}]}function Ef(){this.$get=["$rootScope","$browser","$q","$$q","$exceptionHandler",function(a,b,d,c,e){function f(f,k,l){z(f)||(l=k,k=f,f=A);var m=va.call(arguments,3),n=w(l)&&!l,p=(n?c:d).defer(),u=p.promise,q;q=b.defer(function(){try{p.resolve(f.apply(null,
m))}catch(b){p.reject(b),e(b)}finally{delete g[u.$$timeoutId]}n||a.$apply()},k);u.$$timeoutId=q;g[q]=p;return u}var g={};f.cancel=function(a){return a&&a.$$timeoutId in g?(g[a.$$timeoutId].reject("canceled"),delete g[a.$$timeoutId],b.defer.cancel(a.$$timeoutId)):!1};return f}]}function Y(a){Ea&&($.setAttribute("href",a),a=$.href);$.setAttribute("href",a);return{href:$.href,protocol:$.protocol?$.protocol.replace(/:$/,""):"",host:$.host,search:$.search?$.search.replace(/^\?/,""):"",hash:$.hash?$.hash.replace(/^#/,
""):"",hostname:$.hostname,port:$.port,pathname:"/"===$.pathname.charAt(0)?$.pathname:"/"+$.pathname}}function id(a){a=G(a)?Y(a):a;return a.protocol===yd.protocol&&a.host===yd.host}function Ff(){this.$get=ha(C)}function zd(a){function b(a){try{return decodeURIComponent(a)}catch(b){return a}}var d=a[0]||{},c={},e="";return function(){var a,g,h,k,l;a=d.cookie||"";if(a!==e)for(e=a,a=e.split("; "),c={},h=0;h<a.length;h++)g=a[h],k=g.indexOf("="),0<k&&(l=b(g.substring(0,k)),y(c[l])&&(c[l]=b(g.substring(k+
1))));return c}}function Jf(){this.$get=zd}function Mc(a){function b(d,c){if(D(d)){var e={};q(d,function(a,c){e[c]=b(c,a)});return e}return a.factory(d+"Filter",c)}this.register=b;this.$get=["$injector",function(a){return function(b){return a.get(b+"Filter")}}];b("currency",Ad);b("date",Bd);b("filter",qg);b("json",rg);b("limitTo",sg);b("lowercase",tg);b("number",Cd);b("orderBy",Dd);b("uppercase",ug)}function qg(){return function(a,b,d,c){if(!ta(a)){if(null==a)return a;throw N("filter")("notarray",
a);}c=c||"$";var e;switch(lc(b)){case "function":break;case "boolean":case "null":case "number":case "string":e=!0;case "object":b=vg(b,d,c,e);break;default:return a}return Array.prototype.filter.call(a,b)}}function vg(a,b,d,c){var e=D(a)&&d in a;!0===b?b=na:z(b)||(b=function(a,b){if(y(a))return!1;if(null===a||null===b)return a===b;if(D(b)||D(a)&&!vc(a))return!1;a=Q(""+a);b=Q(""+b);return-1!==a.indexOf(b)});return function(f){return e&&!D(f)?La(f,a[d],b,d,!1):La(f,a,b,d,c)}}function La(a,b,d,c,e,
f){var g=lc(a),h=lc(b);if("string"===h&&"!"===b.charAt(0))return!La(a,b.substring(1),d,c,e);if(L(a))return a.some(function(a){return La(a,b,d,c,e)});switch(g){case "object":var k;if(e){for(k in a)if("$"!==k.charAt(0)&&La(a[k],b,d,c,!0))return!0;return f?!1:La(a,b,d,c,!1)}if("object"===h){for(k in b)if(f=b[k],!z(f)&&!y(f)&&(g=k===c,!La(g?a:a[k],f,d,c,g,g)))return!1;return!0}return d(a,b);case "function":return!1;default:return d(a,b)}}function lc(a){return null===a?"null":typeof a}function Ad(a){var b=
a.NUMBER_FORMATS;return function(a,c,e){y(c)&&(c=b.CURRENCY_SYM);y(e)&&(e=b.PATTERNS[1].maxFrac);return null==a?a:Ed(a,b.PATTERNS[1],b.GROUP_SEP,b.DECIMAL_SEP,e).replace(/\u00A4/g,c)}}function Cd(a){var b=a.NUMBER_FORMATS;return function(a,c){return null==a?a:Ed(a,b.PATTERNS[0],b.GROUP_SEP,b.DECIMAL_SEP,c)}}function wg(a){var b=0,d,c,e,f,g;-1<(c=a.indexOf(Fd))&&(a=a.replace(Fd,""));0<(e=a.search(/e/i))?(0>c&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)==mc;e++);
if(e==(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)==mc;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Gd&&(d=d.splice(0,Gd-1),b=c-1,c=1);return{d:d,e:b,i:c}}function xg(a,b,d,c){var e=a.d,f=e.length-a.i;b=y(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0<d){e.splice(Math.max(a.i,d));for(var g=d;g<e.length;g++)e[g]=0}else for(f=Math.max(0,f),a.i=1,e.length=Math.max(1,d=b+1),e[0]=0,g=1;g<d;g++)e[g]=0;if(5<=c)if(0>d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;
for(;f<Math.max(0,b);f++)e.push(0);if(b=e.reduceRight(function(a,b,c,d){b+=a;d[c]=b%10;return Math.floor(b/10)},0))e.unshift(b),a.i++}function Ed(a,b,d,c,e){if(!G(a)&&!T(a)||isNaN(a))return"";var f=!isFinite(a),g=!1,h=Math.abs(a)+"",k="";if(f)k="\u221e";else{g=wg(h);xg(g,e,b.minFrac,b.maxFrac);k=g.d;h=g.i;e=g.e;f=[];for(g=k.reduce(function(a,b){return a&&!b},!0);0>h;)k.unshift(0),h++;0<h?f=k.splice(h,k.length):(f=k,k=[0]);h=[];for(k.length>=b.lgSize&&h.unshift(k.splice(-b.lgSize,k.length).join(""));k.length>
b.gSize;)h.unshift(k.splice(-b.gSize,k.length).join(""));k.length&&h.unshift(k.join(""));k=h.join(d);f.length&&(k+=c+f.join(""));e&&(k+="e+"+e)}return 0>a&&!g?b.negPre+k+b.negSuf:b.posPre+k+b.posSuf}function Kb(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length<b;)a=mc+a;d&&(a=a.substr(a.length-b));return e+a}function ba(a,b,d,c,e){d=d||0;return function(f){f=f["get"+a]();if(0<d||f>-d)f+=d;0===f&&-12==d&&(f=12);return Kb(f,b,c,e)}}function kb(a,b,d){return function(c,e){var f=
c["get"+a](),g=ub((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Hd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Id(a){return function(b){var d=Hd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Kb(b,a)}}function nc(a,b){return 0>=a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function Bd(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,
k=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=Z(b[9]+b[10]),g=Z(b[9]+b[11]));h.call(a,Z(b[1]),Z(b[2])-1,Z(b[3]));f=Z(b[4]||0)-f;g=Z(b[5]||0)-g;h=Z(b[6]||0);b=Math.round(1E3*parseFloat("0."+(b[7]||0)));k.call(a,f,g,h,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,d,f){var g="",h=[],k,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;G(c)&&(c=yg.test(c)?Z(c):b(c));T(c)&&(c=new Date(c));if(!da(c)||!isFinite(c.getTime()))return c;
for(;d;)(l=zg.exec(d))?(h=$a(h,l,1),d=h.pop()):(h.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=yc(f,m),c=Sb(c,f,!0));q(h,function(b){k=Ag[b];g+=k?k(c,a.DATETIME_FORMATS,m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function rg(){return function(a,b){y(b)&&(b=2);return bb(a,b)}}function sg(){return function(a,b,d){b=Infinity===Math.abs(Number(b))?Number(b):Z(b);if(isNaN(b))return a;T(a)&&(a=a.toString());if(!ta(a))return a;d=!d||isNaN(d)?0:Z(d);d=0>d?Math.max(0,a.length+
d):d;return 0<=b?oc(a,d,d+b):0===d?oc(a,b,a.length):oc(a,Math.max(0,d+b),d)}}function oc(a,b,d){return G(a)?a.slice(b,d):va.call(a,b,d)}function Dd(a){function b(b){return b.map(function(b){var c=1,d=Xa;if(z(b))d=b;else if(G(b)){if("+"==b.charAt(0)||"-"==b.charAt(0))c="-"==b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(d=a(b),d.constant))var e=d(),d=function(a){return a[e]}}return{get:d,descending:c}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}
function c(a,b){var c=0,d=a.type,k=b.type;if(d===k){var k=a.value,l=b.value;"string"===d?(k=k.toLowerCase(),l=l.toLowerCase()):"object"===d&&(D(k)&&(k=a.index),D(l)&&(l=b.index));k!==l&&(c=k<l?-1:1)}else c=d<k?-1:1;return c}return function(a,f,g,h){if(null==a)return a;if(!ta(a))throw N("orderBy")("notarray",a);L(f)||(f=[f]);0===f.length&&(f=["+"]);var k=b(f),l=g?-1:1,m=z(h)?h:c;a=Array.prototype.map.call(a,function(a,b){return{value:a,tieBreaker:{value:b,type:"number",index:b},predicateValues:k.map(function(c){var e=
c.get(a);c=typeof e;if(null===e)c="string",e="null";else if("object"===c)a:{if(z(e.valueOf)&&(e=e.valueOf(),d(e)))break a;vc(e)&&(e=e.toString(),d(e))}return{value:e,type:c,index:b}})}});a.sort(function(a,b){for(var c=0,d=k.length;c<d;c++){var e=m(a.predicateValues[c],b.predicateValues[c]);if(e)return e*k[c].descending*l}return m(a.tieBreaker,b.tieBreaker)*l});return a=a.map(function(a){return a.value})}}function Ta(a){z(a)&&(a={link:a});a.restrict=a.restrict||"AC";return ha(a)}function Jd(a,b,d,
c,e){var f=this,g=[];f.$error={};f.$$success={};f.$pending=void 0;f.$name=e(b.name||b.ngForm||"")(d);f.$dirty=!1;f.$pristine=!0;f.$valid=!0;f.$invalid=!1;f.$submitted=!1;f.$$parentForm=Lb;f.$rollbackViewValue=function(){q(g,function(a){a.$rollbackViewValue()})};f.$commitViewValue=function(){q(g,function(a){a.$commitViewValue()})};f.$addControl=function(a){Qa(a.$name,"input");g.push(a);a.$name&&(f[a.$name]=a);a.$$parentForm=f};f.$$renameControl=function(a,b){var c=a.$name;f[c]===a&&delete f[c];f[b]=
a;a.$name=b};f.$removeControl=function(a){a.$name&&f[a.$name]===a&&delete f[a.$name];q(f.$pending,function(b,c){f.$setValidity(c,null,a)});q(f.$error,function(b,c){f.$setValidity(c,null,a)});q(f.$$success,function(b,c){f.$setValidity(c,null,a)});Za(g,a);a.$$parentForm=Lb};Kd({ctrl:this,$element:a,set:function(a,b,c){var d=a[b];d?-1===d.indexOf(c)&&d.push(c):a[b]=[c]},unset:function(a,b,c){var d=a[b];d&&(Za(d,c),0===d.length&&delete a[b])},$animate:c});f.$setDirty=function(){c.removeClass(a,Ua);c.addClass(a,
Mb);f.$dirty=!0;f.$pristine=!1;f.$$parentForm.$setDirty()};f.$setPristine=function(){c.setClass(a,Ua,Mb+" ng-submitted");f.$dirty=!1;f.$pristine=!0;f.$submitted=!1;q(g,function(a){a.$setPristine()})};f.$setUntouched=function(){q(g,function(a){a.$setUntouched()})};f.$setSubmitted=function(){c.addClass(a,"ng-submitted");f.$submitted=!0;f.$$parentForm.$setSubmitted()}}function pc(a){a.$formatters.push(function(b){return a.$isEmpty(b)?b:b.toString()})}function lb(a,b,d,c,e,f){var g=Q(b[0].type);if(!e.android){var h=
!1;b.on("compositionstart",function(){h=!0});b.on("compositionend",function(){h=!1;l()})}var k,l=function(a){k&&(f.defer.cancel(k),k=null);if(!h){var e=b.val();a=a&&a.type;"password"===g||d.ngTrim&&"false"===d.ngTrim||(e=W(e));(c.$viewValue!==e||""===e&&c.$$hasNativeValidators)&&c.$setViewValue(e,a)}};if(e.hasEvent("input"))b.on("input",l);else{var m=function(a,b,c){k||(k=f.defer(function(){k=null;b&&b.value===c||l(a)}))};b.on("keydown",function(a){var b=a.keyCode;91===b||15<b&&19>b||37<=b&&40>=b||
m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut",m)}b.on("change",l);if(Ld[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!k){var b=this.validity,c=b.badInput,d=b.typeMismatch;k=f.defer(function(){k=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)?"":c.$viewValue;b.val()!==a&&b.val(a)}}function Nb(a,b){return function(d,c){var e,f;if(da(d))return d;if(G(d)){'"'==d.charAt(0)&&'"'==d.charAt(d.length-
1)&&(d=d.substring(1,d.length-1));if(Bg.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},q(e,function(a,c){c<b.length&&(f[b[c]]=+a)}),new Date(f.yyyy,f.MM-1,f.dd,f.HH,f.mm,f.ss||0,1E3*f.sss||0)}return NaN}}function mb(a,b,d,c){return function(e,f,g,h,k,l,m){function n(a){return a&&!(a.getTime&&
a.getTime()!==a.getTime())}function p(a){return w(a)&&!da(a)?d(a)||void 0:a}Md(e,f,g,h);lb(e,f,g,h,k,l);var u=h&&h.$options&&h.$options.timezone,q;h.$$parserName=a;h.$parsers.push(function(a){if(h.$isEmpty(a))return null;if(b.test(a))return a=d(a,q),u&&(a=Sb(a,u)),a});h.$formatters.push(function(a){if(a&&!da(a))throw nb("datefmt",a);if(n(a))return(q=a)&&u&&(q=Sb(q,u,!0)),m("date")(a,c,u);q=null;return""});if(w(g.min)||g.ngMin){var s;h.$validators.min=function(a){return!n(a)||y(s)||d(a)>=s};g.$observe("min",
function(a){s=p(a);h.$validate()})}if(w(g.max)||g.ngMax){var r;h.$validators.max=function(a){return!n(a)||y(r)||d(a)<=r};g.$observe("max",function(a){r=p(a);h.$validate()})}}}function Md(a,b,d,c){(c.$$hasNativeValidators=D(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{};return c.badInput||c.typeMismatch?void 0:a})}function Nd(a,b,d,c,e){if(w(c)){a=a(c);if(!a.constant)throw nb("constexpr",d,c);return a(b)}return e}function qc(a,b){a="ngClass"+a;return["$animate",function(d){function c(a,
b){var c=[],d=0;a:for(;d<a.length;d++){for(var e=a[d],m=0;m<b.length;m++)if(e==b[m])continue a;c.push(e)}return c}function e(a){var b=[];return L(a)?(q(a,function(a){b=b.concat(e(a))}),b):G(a)?a.split(" "):D(a)?(q(a,function(a,c){a&&(b=b.concat(c.split(" ")))}),b):a}return{restrict:"AC",link:function(f,g,h){function k(a){a=l(a,1);h.$addClass(a)}function l(a,b){var c=g.data("$classCounts")||U(),d=[];q(a,function(a){if(0<b||c[a])c[a]=(c[a]||0)+b,c[a]===+(0<b)&&d.push(a)});g.data("$classCounts",c);return d.join(" ")}
function m(a,b){var e=c(b,a),f=c(a,b),e=l(e,1),f=l(f,-1);e&&e.length&&d.addClass(g,e);f&&f.length&&d.removeClass(g,f)}function n(a){if(!0===b||(f.$index&1)===b){var c=e(a||[]);if(!p)k(c);else if(!na(a,p)){var d=e(p);m(d,c)}}p=L(a)?a.map(function(a){return ia(a)}):ia(a)}var p;f.$watch(h[a],n,!0);h.$observe("class",function(b){n(f.$eval(h[a]))});"ngClass"!==a&&f.$watch("$index",function(c,d){var g=c&1;if(g!==(d&1)){var m=e(f.$eval(h[a]));g===b?k(m):(g=l(m,-1),h.$removeClass(g))}})}}}]}function Kd(a){function b(a,
b){b&&!f[a]?(k.addClass(e,a),f[a]=!0):!b&&f[a]&&(k.removeClass(e,a),f[a]=!1)}function d(a,c){a=a?"-"+Cc(a,"-"):"";b(ob+a,!0===c);b(Od+a,!1===c)}var c=a.ctrl,e=a.$element,f={},g=a.set,h=a.unset,k=a.$animate;f[Od]=!(f[ob]=e.hasClass(ob));c.$setValidity=function(a,e,f){y(e)?(c.$pending||(c.$pending={}),g(c.$pending,a,f)):(c.$pending&&h(c.$pending,a,f),Pd(c.$pending)&&(c.$pending=void 0));Ga(e)?e?(h(c.$error,a,f),g(c.$$success,a,f)):(g(c.$error,a,f),h(c.$$success,a,f)):(h(c.$error,a,f),h(c.$$success,
a,f));c.$pending?(b(Qd,!0),c.$valid=c.$invalid=void 0,d("",null)):(b(Qd,!1),c.$valid=Pd(c.$error),c.$invalid=!c.$valid,d("",c.$valid));e=c.$pending&&c.$pending[a]?void 0:c.$error[a]?!1:c.$$success[a]?!0:null;d(a,e);c.$$parentForm.$setValidity(a,e,c)}}function Pd(a){if(a)for(var b in a)if(a.hasOwnProperty(b))return!1;return!0}var Cg=/^\/(.+)\/([a-z]*)$/,ua=Object.prototype.hasOwnProperty,Q=function(a){return G(a)?a.toLowerCase():a},ub=function(a){return G(a)?a.toUpperCase():a},Ea,F,qa,va=[].slice,
bg=[].splice,Dg=[].push,ma=Object.prototype.toString,wc=Object.getPrototypeOf,xa=N("ng"),ca=C.angular||(C.angular={}),Ub,pb=0;Ea=C.document.documentMode;A.$inject=[];Xa.$inject=[];var L=Array.isArray,ae=/^\[object (?:Uint8|Uint8Clamped|Uint16|Uint32|Int8|Int16|Int32|Float32|Float64)Array\]$/,W=function(a){return G(a)?a.trim():a},wd=function(a){return a.replace(/([-()\[\]{}+?*.$\^|,:#<!\\])/g,"\\$1").replace(/\x08/g,"\\x08")},Ba=function(){if(!w(Ba.rules)){var a=C.document.querySelector("[ng-csp]")||
C.document.querySelector("[data-ng-csp]");if(a){var b=a.getAttribute("ng-csp")||a.getAttribute("data-ng-csp");Ba.rules={noUnsafeEval:!b||-1!==b.indexOf("no-unsafe-eval"),noInlineStyle:!b||-1!==b.indexOf("no-inline-style")}}else{a=Ba;try{new Function(""),b=!1}catch(d){b=!0}a.rules={noUnsafeEval:b,noInlineStyle:!1}}}return Ba.rules},rb=function(){if(w(rb.name_))return rb.name_;var a,b,d=Na.length,c,e;for(b=0;b<d;++b)if(c=Na[b],a=C.document.querySelector("["+c.replace(":","\\:")+"jq]")){e=a.getAttribute(c+
"jq");break}return rb.name_=e},de=/:/g,Na=["ng-","data-ng-","ng:","x-ng-"],ie=/[A-Z]/g,Dc=!1,Ma=3,me={full:"1.5.8",major:1,minor:5,dot:8,codeName:"arbitrary-fallbacks"};O.expando="ng339";var fb=O.cache={},Pf=1;O._data=function(a){return this.cache[a[this.expando]]||{}};var Kf=/([\:\-\_]+(.))/g,Lf=/^moz([A-Z])/,yb={mouseleave:"mouseout",mouseenter:"mouseover"},Wb=N("jqLite"),Of=/^<([\w-]+)\s*\/?>(?:<\/\1>|)$/,Vb=/<|&#?\w+;/,Mf=/<([\w:-]+)/,Nf=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi,
ja={option:[1,'<select multiple="multiple">',"</select>"],thead:[1,"<table>","</table>"],col:[2,"<table><colgroup>","</colgroup></table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],_default:[0,"",""]};ja.optgroup=ja.option;ja.tbody=ja.tfoot=ja.colgroup=ja.caption=ja.thead;ja.th=ja.td;var Uf=C.Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Oa=O.prototype={ready:function(a){function b(){d||(d=!0,a())}var d=!1;"complete"===
C.document.readyState?C.setTimeout(b):(this.on("DOMContentLoaded",b),O(C).on("load",b))},toString:function(){var a=[];q(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?F(this[a]):F(this[this.length+a])},length:0,push:Dg,sort:[].sort,splice:[].splice},Eb={};q("multiple selected checked disabled readOnly required open".split(" "),function(a){Eb[Q(a)]=a});var Vc={};q("input select option textarea button form details".split(" "),function(a){Vc[a]=!0});var bd={ngMinlength:"minlength",
ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};q({data:Yb,removeData:eb,hasData:function(a){for(var b in fb[a.ng339])return!0;return!1},cleanData:function(a){for(var b=0,d=a.length;b<d;b++)eb(a[b])}},function(a,b){O[b]=a});q({data:Yb,inheritedData:Cb,scope:function(a){return F.data(a,"$scope")||Cb(a.parentNode||a,["$isolateScope","$scope"])},isolateScope:function(a){return F.data(a,"$isolateScope")||F.data(a,"$isolateScopeNoTemplate")},controller:Sc,injector:function(a){return Cb(a,
"$injector")},removeAttr:function(a,b){a.removeAttribute(b)},hasClass:zb,css:function(a,b,d){b=db(b);if(w(d))a.style[b]=d;else return a.style[b]},attr:function(a,b,d){var c=a.nodeType;if(c!==Ma&&2!==c&&8!==c)if(c=Q(b),Eb[c])if(w(d))d?(a[b]=!0,a.setAttribute(b,c)):(a[b]=!1,a.removeAttribute(c));else return a[b]||(a.attributes.getNamedItem(b)||A).specified?c:void 0;else if(w(d))a.setAttribute(b,d);else if(a.getAttribute)return a=a.getAttribute(b,2),null===a?void 0:a},prop:function(a,b,d){if(w(d))a[b]=
d;else return a[b]},text:function(){function a(a,d){if(y(d)){var c=a.nodeType;return 1===c||c===Ma?a.textContent:""}a.textContent=d}a.$dv="";return a}(),val:function(a,b){if(y(b)){if(a.multiple&&"select"===wa(a)){var d=[];q(a.options,function(a){a.selected&&d.push(a.value||a.text)});return 0===d.length?null:d}return a.value}a.value=b},html:function(a,b){if(y(b))return a.innerHTML;wb(a,!0);a.innerHTML=b},empty:Tc},function(a,b){O.prototype[b]=function(b,c){var e,f,g=this.length;if(a!==Tc&&y(2==a.length&&
a!==zb&&a!==Sc?b:c)){if(D(b)){for(e=0;e<g;e++)if(a===Yb)a(this[e],b);else for(f in b)a(this[e],f,b[f]);return this}e=a.$dv;g=y(e)?Math.min(g,1):g;for(f=0;f<g;f++){var h=a(this[f],b,c);e=e?e+h:h}return e}for(e=0;e<g;e++)a(this[e],b,c);return this}});q({removeData:eb,on:function(a,b,d,c){if(w(c))throw Wb("onargs");if(Nc(a)){c=xb(a,!0);var e=c.events,f=c.handle;f||(f=c.handle=Rf(a,e));c=0<=b.indexOf(" ")?b.split(" "):[b];for(var g=c.length,h=function(b,c,g){var h=e[b];h||(h=e[b]=[],h.specialHandlerWrapper=
c,"$destroy"===b||g||a.addEventListener(b,f,!1));h.push(d)};g--;)b=c[g],yb[b]?(h(yb[b],Tf),h(b,void 0,!0)):h(b)}},off:Rc,one:function(a,b,d){a=F(a);a.on(b,function e(){a.off(b,d);a.off(b,e)});a.on(b,d)},replaceWith:function(a,b){var d,c=a.parentNode;wb(a);q(new O(b),function(b){d?c.insertBefore(b,d.nextSibling):c.replaceChild(b,a);d=b})},children:function(a){var b=[];q(a.childNodes,function(a){1===a.nodeType&&b.push(a)});return b},contents:function(a){return a.contentDocument||a.childNodes||[]},append:function(a,
b){var d=a.nodeType;if(1===d||11===d){b=new O(b);for(var d=0,c=b.length;d<c;d++)a.appendChild(b[d])}},prepend:function(a,b){if(1===a.nodeType){var d=a.firstChild;q(new O(b),function(b){a.insertBefore(b,d)})}},wrap:function(a,b){Pc(a,F(b).eq(0).clone()[0])},remove:Db,detach:function(a){Db(a,!0)},after:function(a,b){var d=a,c=a.parentNode;b=new O(b);for(var e=0,f=b.length;e<f;e++){var g=b[e];c.insertBefore(g,d.nextSibling);d=g}},addClass:Bb,removeClass:Ab,toggleClass:function(a,b,d){b&&q(b.split(" "),
function(b){var e=d;y(e)&&(e=!zb(a,b));(e?Bb:Ab)(a,b)})},parent:function(a){return(a=a.parentNode)&&11!==a.nodeType?a:null},next:function(a){return a.nextElementSibling},find:function(a,b){return a.getElementsByTagName?a.getElementsByTagName(b):[]},clone:Xb,triggerHandler:function(a,b,d){var c,e,f=b.type||b,g=xb(a);if(g=(g=g&&g.events)&&g[f])c={preventDefault:function(){this.defaultPrevented=!0},isDefaultPrevented:function(){return!0===this.defaultPrevented},stopImmediatePropagation:function(){this.immediatePropagationStopped=
!0},isImmediatePropagationStopped:function(){return!0===this.immediatePropagationStopped},stopPropagation:A,type:f,target:a},b.type&&(c=S(c,b)),b=ia(g),e=d?[c].concat(d):[c],q(b,function(b){c.isImmediatePropagationStopped()||b.apply(a,e)})}},function(a,b){O.prototype[b]=function(b,c,e){for(var f,g=0,h=this.length;g<h;g++)y(f)?(f=a(this[g],b,c,e),w(f)&&(f=F(f))):Qc(f,a(this[g],b,c,e));return w(f)?f:this};O.prototype.bind=O.prototype.on;O.prototype.unbind=O.prototype.off});Ra.prototype={put:function(a,
b){this[Ca(a,this.nextUid)]=b},get:function(a){return this[Ca(a,this.nextUid)]},remove:function(a){var b=this[a=Ca(a,this.nextUid)];delete this[a];return b}};var If=[function(){this.$get=[function(){return Ra}]}],Wf=/^([^\(]+?)=>/,Xf=/^[^\(]*\(\s*([^\)]*)\)/m,Eg=/,/,Fg=/^\s*(_?)(\S+?)\1\s*$/,Vf=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ha=N("$injector");cb.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw G(d)&&d||(d=a.name||Yf(a)),Ha("strictdi",d);
b=Wc(a);q(b[1].split(Eg),function(a){a.replace(Fg,function(a,b,d){c.push(d)})})}a.$inject=c}}else L(a)?(b=a.length-1,Pa(a[b],"fn"),c=a.slice(0,b)):Pa(a,"fn",!0);return c};var Rd=N("$animate"),$e=function(){this.$get=A},af=function(){var a=new Ra,b=[];this.$get=["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=G(b)?b.split(" "):L(b)?b:[],q(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){q(b,function(b){var c=a.get(b);if(c){var d=Zf(b.attr("class")),e="",f="";q(c,
function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});q(b,function(a){e&&Bb(a,e);f&&Ab(a,f)});a.remove(b)}});b.length=0}return{enabled:A,on:A,off:A,pin:A,push:function(g,h,k,l){l&&l();k=k||{};k.from&&g.css(k.from);k.to&&g.css(k.to);if(k.addClass||k.removeClass)if(h=k.addClass,l=k.removeClass,k=a.get(g)||{},h=e(k,h,!0),l=e(k,l,!1),h||l)a.put(g,k),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},Ye=["$provide",function(a){var b=this;this.$$registeredAnimations=
Object.create(null);this.register=function(d,c){if(d&&"."!==d.charAt(0))throw Rd("notcsel",d);var e=d+"-animation";b.$$registeredAnimations[d.substr(1)]=e;a.factory(e,c)};this.classNameFilter=function(a){if(1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null)&&/(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString()))throw Rd("nongcls","ng-animate");return this.$$classNameFilter};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var h;a:{for(h=0;h<d.length;h++){var k=
d[h];if(1===k.nodeType){h=k;break a}}h=void 0}!h||h.parentNode||h.previousElementSibling||(d=null)}d?d.after(a):c.prepend(a)}return{on:a.on,off:a.off,pin:a.pin,enabled:a.enabled,cancel:function(a){a.end&&a.end()},enter:function(e,f,g,h){f=f&&F(f);g=g&&F(g);f=f||g.parent();b(e,f,g);return a.push(e,"enter",Ia(h))},move:function(e,f,g,h){f=f&&F(f);g=g&&F(g);f=f||g.parent();b(e,f,g);return a.push(e,"move",Ia(h))},leave:function(b,c){return a.push(b,"leave",Ia(c),function(){b.remove()})},addClass:function(b,
c,g){g=Ia(g);g.addClass=gb(g.addclass,c);return a.push(b,"addClass",g)},removeClass:function(b,c,g){g=Ia(g);g.removeClass=gb(g.removeClass,c);return a.push(b,"removeClass",g)},setClass:function(b,c,g,h){h=Ia(h);h.addClass=gb(h.addClass,c);h.removeClass=gb(h.removeClass,g);return a.push(b,"setClass",h)},animate:function(b,c,g,h,k){k=Ia(k);k.from=k.from?S(k.from,c):c;k.to=k.to?S(k.to,g):g;k.tempClasses=gb(k.tempClasses,h||"ng-inline-animate");return a.push(b,"animate",k)}}}]}],cf=function(){this.$get=
["$$rAF",function(a){function b(b){d.push(b);1<d.length||a(function(){for(var a=0;a<d.length;a++)d[a]();d=[]})}var d=[];return function(){var a=!1;b(function(){a=!0});return function(d){a?d():b(d)}}}]},bf=function(){this.$get=["$q","$sniffer","$$animateAsyncRun","$document","$timeout",function(a,b,d,c,e){function f(a){this.setHost(a);var b=d();this._doneCallbacks=[];this._tick=function(a){var d=c[0];d&&d.hidden?e(a,0,!1):b(a)};this._state=0}f.chain=function(a,b){function c(){if(d===a.length)b(!0);
else a[d](function(a){!1===a?b(!1):(d++,c())})}var d=0;c()};f.all=function(a,b){function c(f){e=e&&f;++d===a.length&&b(e)}var d=0,e=!0;q(a,function(a){a.done(c)})};f.prototype={setHost:function(a){this.host=a||{}},done:function(a){2===this._state?a():this._doneCallbacks.push(a)},progress:A,getPromise:function(){if(!this.promise){var b=this;this.promise=a(function(a,c){b.done(function(b){!1===b?c():a()})})}return this.promise},then:function(a,b){return this.getPromise().then(a,b)},"catch":function(a){return this.getPromise()["catch"](a)},
"finally":function(a){return this.getPromise()["finally"](a)},pause:function(){this.host.pause&&this.host.pause()},resume:function(){this.host.resume&&this.host.resume()},end:function(){this.host.end&&this.host.end();this._resolve(!0)},cancel:function(){this.host.cancel&&this.host.cancel();this._resolve(!1)},complete:function(a){var b=this;0===b._state&&(b._state=1,b._tick(function(){b._resolve(a)}))},_resolve:function(a){2!==this._state&&(q(this._doneCallbacks,function(b){b(a)}),this._doneCallbacks.length=
0,this._state=2)}};return f}]},Ze=function(){this.$get=["$$rAF","$q","$$AnimateRunner",function(a,b,d){return function(b,e){function f(){a(function(){g.addClass&&(b.addClass(g.addClass),g.addClass=null);g.removeClass&&(b.removeClass(g.removeClass),g.removeClass=null);g.to&&(b.css(g.to),g.to=null);h||k.complete();h=!0});return k}var g=e||{};g.$$prepared||(g=pa(g));g.cleanupStyles&&(g.from=g.to=null);g.from&&(b.css(g.from),g.from=null);var h,k=new d;return{start:f,end:f}}}]},ga=N("$compile"),bc=new function(){};
Fc.$inject=["$provide","$$sanitizeUriProvider"];Fb.prototype.isFirstChange=function(){return this.previousValue===bc};var Yc=/^((?:x|data)[\:\-_])/i,cg=N("$controller"),cd=/^(\S+)(\s+as\s+([\w$]+))?$/,jf=function(){this.$get=["$document",function(a){return function(b){b?!b.nodeType&&b instanceof F&&(b=b[0]):b=a[0].body;return b.offsetWidth+1}}]},dd="application/json",ec={"Content-Type":dd+";charset=utf-8"},eg=/^\[|^\{(?!\{)/,fg={"[":/]$/,"{":/}$/},dg=/^\)\]\}',?\n/,Gg=N("$http"),hd=function(a){return function(){throw Gg("legacy",
a);}},Ka=ca.$interpolateMinErr=N("$interpolate");Ka.throwNoconcat=function(a){throw Ka("noconcat",a);};Ka.interr=function(a,b){return Ka("interr",a,b.toString())};var rf=function(){this.$get=["$window",function(a){function b(a){var b=function(a){b.data=a;b.called=!0};b.id=a;return b}var d=a.angular.callbacks,c={};return{createCallback:function(a){a="_"+(d.$$counter++).toString(36);var f="angular.callbacks."+a,g=b(a);c[f]=d[a]=g;return f},wasCalled:function(a){return c[a].called},getResponse:function(a){return c[a].data},
removeCallback:function(a){delete d[c[a].id];delete c[a]}}}]},Hg=/^([^\?#]*)(\?([^#]*))?(#(.*))?$/,hg={http:80,https:443,ftp:21},Gb=N("$location"),Ig={$$absUrl:"",$$html5:!1,$$replace:!1,absUrl:Hb("$$absUrl"),url:function(a){if(y(a))return this.$$url;var b=Hg.exec(a);(b[1]||""===a)&&this.path(decodeURIComponent(b[1]));(b[2]||b[1]||""===a)&&this.search(b[3]||"");this.hash(b[5]||"");return this},protocol:Hb("$$protocol"),host:Hb("$$host"),port:Hb("$$port"),path:md("$$path",function(a){a=null!==a?a.toString():
"";return"/"==a.charAt(0)?a:"/"+a}),search:function(a,b){switch(arguments.length){case 0:return this.$$search;case 1:if(G(a)||T(a))a=a.toString(),this.$$search=Ac(a);else if(D(a))a=pa(a,{}),q(a,function(b,c){null==b&&delete a[c]}),this.$$search=a;else throw Gb("isrcharg");break;default:y(b)||null===b?delete this.$$search[a]:this.$$search[a]=b}this.$$compose();return this},hash:md("$$hash",function(a){return null!==a?a.toString():""}),replace:function(){this.$$replace=!0;return this}};q([ld,hc,gc],
function(a){a.prototype=Object.create(Ig);a.prototype.state=function(b){if(!arguments.length)return this.$$state;if(a!==gc||!this.$$html5)throw Gb("nostate");this.$$state=y(b)?null:b;return this}});var X=N("$parse"),jg=Function.prototype.call,kg=Function.prototype.apply,lg=Function.prototype.bind,Ob=U();q("+ - * / % === !== == != < > <= >= && || ! = |".split(" "),function(a){Ob[a]=!0});var Jg={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},jc=function(a){this.options=a};jc.prototype={constructor:jc,
lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index<this.text.length;)if(a=this.text.charAt(this.index),'"'===a||"'"===a)this.readString(a);else if(this.isNumber(a)||"."===a&&this.isNumber(this.peek()))this.readNumber();else if(this.isIdentifierStart(this.peekMultichar()))this.readIdent();else if(this.is(a,"(){}[].,;:?"))this.tokens.push({index:this.index,text:a}),this.index++;else if(this.isWhitespace(a))this.index++;else{var b=a+this.peek(),d=b+this.peek(2),c=Ob[b],e=Ob[d];Ob[a]||
c||e?(a=e?d:c?b:a,this.tokens.push({index:this.index,text:a,operator:!0}),this.index+=a.length):this.throwError("Unexpected next character ",this.index,this.index+1)}return this.tokens},is:function(a,b){return-1!==b.indexOf(a)},peek:function(a){a=a||1;return this.index+a<this.text.length?this.text.charAt(this.index+a):!1},isNumber:function(a){return"0"<=a&&"9">=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdentifierStart:function(a){return this.options.isIdentifierStart?
this.options.isIdentifierStart(a,this.codePointAt(a)):this.isValidIdentifierStart(a)},isValidIdentifierStart:function(a){return"a"<=a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isIdentifierContinue:function(a){return this.options.isIdentifierContinue?this.options.isIdentifierContinue(a,this.codePointAt(a)):this.isValidIdentifierContinue(a)},isValidIdentifierContinue:function(a,b){return this.isValidIdentifierStart(a,b)||this.isNumber(a)},codePointAt:function(a){return 1===a.length?a.charCodeAt(0):
(a.charCodeAt(0)<<10)+a.charCodeAt(1)-56613888},peekMultichar:function(){var a=this.text.charAt(this.index),b=this.peek();if(!b)return a;var d=a.charCodeAt(0),c=b.charCodeAt(0);return 55296<=d&&56319>=d&&56320<=c&&57343>=c?a+b:a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=w(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw X("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index<
this.text.length;){var d=Q(this.text.charAt(this.index));if("."==d||this.isNumber(d))a+=d;else{var c=this.peek();if("e"==d&&this.isExpOperator(c))a+=d;else if(this.isExpOperator(d)&&c&&this.isNumber(c)&&"e"==a.charAt(a.length-1))a+=d;else if(!this.isExpOperator(d)||c&&this.isNumber(c)||"e"!=a.charAt(a.length-1))break;else this.throwError("Invalid exponent")}this.index++}this.tokens.push({index:b,text:a,constant:!0,value:Number(a)})},readIdent:function(){var a=this.index;for(this.index+=this.peekMultichar().length;this.index<
this.text.length;){var b=this.peekMultichar();if(!this.isIdentifierContinue(b))break;this.index+=b.length}this.tokens.push({index:a,text:this.text.slice(a,this.index),identifier:!0})},readString:function(a){var b=this.index;this.index++;for(var d="",c=a,e=!1;this.index<this.text.length;){var f=this.text.charAt(this.index),c=c+f;if(e)"u"===f?(e=this.text.substring(this.index+1,this.index+5),e.match(/[\da-f]{4}/i)||this.throwError("Invalid unicode escape [\\u"+e+"]"),this.index+=4,d+=String.fromCharCode(parseInt(e,
16))):d+=Jg[f]||f,e=!1;else if("\\"===f)e=!0;else{if(f===a){this.index++;this.tokens.push({index:b,text:c,constant:!0,value:d});return}d+=f}this.index++}this.throwError("Unterminated quote",b)}};var s=function(a,b){this.lexer=a;this.options=b};s.Program="Program";s.ExpressionStatement="ExpressionStatement";s.AssignmentExpression="AssignmentExpression";s.ConditionalExpression="ConditionalExpression";s.LogicalExpression="LogicalExpression";s.BinaryExpression="BinaryExpression";s.UnaryExpression="UnaryExpression";
s.CallExpression="CallExpression";s.MemberExpression="MemberExpression";s.Identifier="Identifier";s.Literal="Literal";s.ArrayExpression="ArrayExpression";s.Property="Property";s.ObjectExpression="ObjectExpression";s.ThisExpression="ThisExpression";s.LocalsExpression="LocalsExpression";s.NGValueParameter="NGValueParameter";s.prototype={ast:function(a){this.text=a;this.tokens=this.lexer.lex(a);a=this.program();0!==this.tokens.length&&this.throwError("is an unexpected token",this.tokens[0]);return a},
program:function(){for(var a=[];;)if(0<this.tokens.length&&!this.peek("}",")",";","]")&&a.push(this.expressionStatement()),!this.expect(";"))return{type:s.Program,body:a}},expressionStatement:function(){return{type:s.ExpressionStatement,expression:this.filterChain()}},filterChain:function(){for(var a=this.expression();this.expect("|");)a=this.filter(a);return a},expression:function(){return this.assignment()},assignment:function(){var a=this.ternary();this.expect("=")&&(a={type:s.AssignmentExpression,
left:a,right:this.assignment(),operator:"="});return a},ternary:function(){var a=this.logicalOR(),b,d;return this.expect("?")&&(b=this.expression(),this.consume(":"))?(d=this.expression(),{type:s.ConditionalExpression,test:a,alternate:b,consequent:d}):a},logicalOR:function(){for(var a=this.logicalAND();this.expect("||");)a={type:s.LogicalExpression,operator:"||",left:a,right:this.logicalAND()};return a},logicalAND:function(){for(var a=this.equality();this.expect("&&");)a={type:s.LogicalExpression,
operator:"&&",left:a,right:this.equality()};return a},equality:function(){for(var a=this.relational(),b;b=this.expect("==","!=","===","!==");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.relational()};return a},relational:function(){for(var a=this.additive(),b;b=this.expect("<",">","<=",">=");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:s.BinaryExpression,operator:b.text,
left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:s.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+","-","!"))?{type:s.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():
this.selfReferential.hasOwnProperty(this.peek().text)?a=pa(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:s.Literal,value:this.options.literals[this.consume().text]}:this.peek().identifier?a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:s.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):
"["===b.text?(a={type:s.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:s.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a=[a];for(var b={type:s.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.filterChain());while(this.expect(","))
}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:s.Identifier,name:a.text}},constant:function(){return{type:s.Literal,value:this.consume().value}},arrayDeclaration:function(){var a=[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:s.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;
b={type:s.Property,kind:"init"};this.peek().constant?(b.key=this.constant(),b.computed=!1,this.consume(":"),b.value=this.expression()):this.peek().identifier?(b.key=this.identifier(),b.computed=!1,this.peek(":")?(this.consume(":"),b.value=this.expression()):b.value=b.key):this.peek("[")?(this.consume("["),b.key=this.expression(),this.consume("]"),b.computed=!0,this.consume(":"),b.value=this.expression()):this.throwError("invalid key",this.peek());a.push(b)}while(this.expect(","))}this.consume("}");
return{type:s.ObjectExpression,properties:a}},throwError:function(a,b){throw X("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw X("ueoe",this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw X("ueoe",this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a,b,d,c,e){if(this.tokens.length>
a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1},expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:s.ThisExpression},$locals:{type:s.LocalsExpression}}};td.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:b,fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};V(c,d.$filter);var e="",f;this.stage="assign";
if(f=rd(c))this.state.computing="assign",e=this.nextId(),this.recurse(f,e),this.return_(e),e="fn.assign="+this.generateFunction("assign","s,v,l");f=pd(c.body);d.stage="inputs";q(f,function(a,b){var c="fn"+b;d.state[c]={vars:[],body:[],own:{}};d.state.computing=c;var e=d.nextId();d.recurse(a,e);d.return_(e);d.state.inputs.push(c);a.watchId=b});this.state.computing="fn";this.stage="main";this.recurse(c);e='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+this.generateFunction("fn","s,l,a,i")+
e+this.watchFns()+"return fn;";e=(new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","getStringValue","ensureSafeAssignContext","ifDefined","plus","text",e))(this.$filter,Sa,ra,nd,ig,Ib,mg,od,a);this.state=this.stage=void 0;e.literal=sd(c);e.constant=c.constant;return e},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;q(b,function(b){a.push("var "+b+"="+d.generateFunction(b,"s"))});b.length&&a.push("fn.inputs=["+b.join(",")+"];");
return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;q(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,b,d,c,e,f){var g,h,k=this,l,m,n;c=c||A;if(!f&&w(a.watchId))b=
b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d,c,e,!0));else switch(a.type){case s.Program:q(a.body,function(b,c){k.recurse(b.expression,void 0,void 0,function(a){h=a});c!==a.body.length-1?k.current().body.push(h,";"):k.return_(h)});break;case s.Literal:m=this.escape(a.value);this.assign(b,m);c(m);break;case s.UnaryExpression:this.recurse(a.argument,void 0,void 0,function(a){h=a});m=a.operator+"("+this.ifDefined(h,0)+")";this.assign(b,m);
c(m);break;case s.BinaryExpression:this.recurse(a.left,void 0,void 0,function(a){g=a});this.recurse(a.right,void 0,void 0,function(a){h=a});m="+"===a.operator?this.plus(g,h):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(h,0):"("+g+")"+a.operator+"("+h+")";this.assign(b,m);c(m);break;case s.LogicalExpression:b=b||this.nextId();k.recurse(a.left,b);k.if_("&&"===a.operator?b:k.not(b),k.lazyRecurse(a.right,b));c(b);break;case s.ConditionalExpression:b=b||this.nextId();k.recurse(a.test,
b);k.if_(b,k.lazyRecurse(a.alternate,b),k.lazyRecurse(a.consequent,b));c(b);break;case s.Identifier:b=b||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);Sa(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){e&&1!==e&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(b,k.nonComputedMember("s",
a.name))})},b&&k.lazyAssign(b,k.nonComputedMember("l",a.name)));(k.state.expensiveChecks||Jb(a.name))&&k.addEnsureSafeObject(b);c(b);break;case s.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();k.recurse(a.object,g,void 0,function(){k.if_(k.notNull(g),function(){e&&1!==e&&k.addEnsureSafeAssignContext(g);if(a.computed)h=k.nextId(),k.recurse(a.property,h),k.getStringValue(h),k.addEnsureSafeMemberName(h),e&&1!==e&&k.if_(k.not(k.computedMember(g,h)),k.lazyAssign(k.computedMember(g,
h),"{}")),m=k.ensureSafeObject(k.computedMember(g,h)),k.assign(b,m),d&&(d.computed=!0,d.name=h);else{Sa(a.property.name);e&&1!==e&&k.if_(k.not(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name),"{}"));m=k.nonComputedMember(g,a.property.name);if(k.state.expensiveChecks||Jb(a.property.name))m=k.ensureSafeObject(m);k.assign(b,m);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(b,"undefined")});c(b)},!!e);break;case s.CallExpression:b=b||this.nextId();
a.filter?(h=k.filter(a.callee.name),l=[],q(a.arguments,function(a){var b=k.nextId();k.recurse(a,b);l.push(b)}),m=h+"("+l.join(",")+")",k.assign(b,m),c(b)):(h=k.nextId(),g={},l=[],k.recurse(a.callee,h,g,function(){k.if_(k.notNull(h),function(){k.addEnsureSafeFunction(h);q(a.arguments,function(a){k.recurse(a,k.nextId(),void 0,function(a){l.push(k.ensureSafeObject(a))})});g.name?(k.state.expensiveChecks||k.addEnsureSafeObject(g.context),m=k.member(g.context,g.name,g.computed)+"("+l.join(",")+")"):m=
h+"("+l.join(",")+")";m=k.ensureSafeObject(m);k.assign(b,m)},function(){k.assign(b,"undefined")});c(b)}));break;case s.AssignmentExpression:h=this.nextId();g={};if(!qd(a.left))throw X("lval");this.recurse(a.left,void 0,g,function(){k.if_(k.notNull(g.context),function(){k.recurse(a.right,h);k.addEnsureSafeObject(k.member(g.context,g.name,g.computed));k.addEnsureSafeAssignContext(g.context);m=k.member(g.context,g.name,g.computed)+a.operator+h;k.assign(b,m);c(b||m)})},1);break;case s.ArrayExpression:l=
[];q(a.elements,function(a){k.recurse(a,k.nextId(),void 0,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(m);break;case s.ObjectExpression:l=[];n=!1;q(a.properties,function(a){a.computed&&(n=!0)});n?(b=b||this.nextId(),this.assign(b,"{}"),q(a.properties,function(a){a.computed?(g=k.nextId(),k.recurse(a.key,g)):g=a.key.type===s.Identifier?a.key.name:""+a.key.value;h=k.nextId();k.recurse(a.value,h);k.assign(k.member(b,g,a.computed),h)})):(q(a.properties,function(b){k.recurse(b.value,
a.constant?void 0:k.nextId(),void 0,function(a){l.push(k.escape(b.key.type===s.Identifier?b.key.name:""+b.key.value)+":"+a)})}),m="{"+l.join(",")+"}",this.assign(b,m));c(b||m);break;case s.ThisExpression:this.assign(b,"s");c("s");break;case s.LocalsExpression:this.assign(b,"l");c("l");break;case s.NGValueParameter:this.assign(b,"v"),c("v")}},getHasOwnProperty:function(a,b){var d=a+"."+b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+a+")"));return c[d]},
assign:function(a,b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a,b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ",a,";")},if_:function(a,b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}");d&&(c.push("else{"),
d(),c.push("}"))}},not:function(a){return"!("+a+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){var d=/[^$_a-zA-Z0-9]/g;return/[$_a-zA-Z][$_a-zA-Z0-9]*/.test(b)?a+"."+b:a+'["'+b.replace(d,this.stringEscapeFn)+'"]'},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,b)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a),
";")},addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a),";")},addEnsureSafeAssignContext:function(a){this.current().body.push(this.ensureSafeAssignContext(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},ensureSafeAssignContext:function(a){return"ensureSafeAssignContext("+
a+",text)"},lazyRecurse:function(a,b,d,c,e,f){var g=this;return function(){g.recurse(a,b,d,c,e,f)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(G(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(T(a))return a.toString();if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"===
typeof a)return"undefined";throw X("esc");},nextId:function(a,b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};ud.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=b;V(c,d.$filter);var e,f;if(e=rd(c))f=this.recurse(e);e=pd(c.body);var g;e&&(g=[],q(e,function(a,b){var c=d.recurse(a);a.input=c;g.push(c);a.watchId=b}));var h=[];q(c.body,function(a){h.push(d.recurse(a.expression))});
e=0===c.body.length?A:1===c.body.length?h[0]:function(a,b){var c;q(h,function(d){c=d(a,b)});return c};f&&(e.assign=function(a,b,c){return f(a,c,b)});g&&(e.inputs=g);e.literal=sd(c);e.constant=c.constant;return e},recurse:function(a,b,d){var c,e,f=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case s.Literal:return this.value(a.value,b);case s.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case s.BinaryExpression:return c=this.recurse(a.left),
e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case s.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case s.Identifier:return Sa(a.name,f.expression),f.identifier(a.name,f.expensiveChecks||Jb(a.name),b,d,f.expression);case s.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(Sa(a.property.name,
f.expression),e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,e,b,d,f.expression):this.nonComputedMember(c,e,f.expensiveChecks,b,d,f.expression);case s.CallExpression:return g=[],q(a.arguments,function(a){g.push(f.recurse(a))}),a.filter&&(e=this.$filter(a.callee.name)),a.filter||(e=this.recurse(a.callee,!0)),a.filter?function(a,c,d,f){for(var n=[],p=0;p<g.length;++p)n.push(g[p](a,c,d,f));a=e.apply(void 0,n,f);return b?{context:void 0,name:void 0,value:a}:
a}:function(a,c,d,m){var n=e(a,c,d,m),p;if(null!=n.value){ra(n.context,f.expression);nd(n.value,f.expression);p=[];for(var q=0;q<g.length;++q)p.push(ra(g[q](a,c,d,m),f.expression));p=ra(n.value.apply(n.context,p),f.expression)}return b?{value:p}:p};case s.AssignmentExpression:return c=this.recurse(a.left,!0,1),e=this.recurse(a.right),function(a,d,g,m){var n=c(a,d,g,m);a=e(a,d,g,m);ra(n.value,f.expression);Ib(n.context);n.context[n.name]=a;return b?{value:a}:a};case s.ArrayExpression:return g=[],q(a.elements,
function(a){g.push(f.recurse(a))}),function(a,c,d,e){for(var f=[],p=0;p<g.length;++p)f.push(g[p](a,c,d,e));return b?{value:f}:f};case s.ObjectExpression:return g=[],q(a.properties,function(a){a.computed?g.push({key:f.recurse(a.key),computed:!0,value:f.recurse(a.value)}):g.push({key:a.key.type===s.Identifier?a.key.name:""+a.key.value,computed:!1,value:f.recurse(a.value)})}),function(a,c,d,e){for(var f={},p=0;p<g.length;++p)g[p].computed?f[g[p].key(a,c,d,e)]=g[p].value(a,c,d,e):f[g[p].key]=g[p].value(a,
c,d,e);return b?{value:f}:f};case s.ThisExpression:return function(a){return b?{value:a}:a};case s.LocalsExpression:return function(a,c){return b?{value:c}:c};case s.NGValueParameter:return function(a,c,d){return b?{value:d}:d}}},"unary+":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=w(d)?+d:0;return b?{value:d}:d}},"unary-":function(a,b){return function(d,c,e,f){d=a(d,c,e,f);d=w(d)?-d:0;return b?{value:d}:d}},"unary!":function(a,b){return function(d,c,e,f){d=!a(d,c,e,f);return b?{value:d}:
d}},"binary+":function(a,b,d){return function(c,e,f,g){var h=a(c,e,f,g);c=b(c,e,f,g);h=od(h,c);return d?{value:h}:h}},"binary-":function(a,b,d){return function(c,e,f,g){var h=a(c,e,f,g);c=b(c,e,f,g);h=(w(h)?h:0)-(w(c)?c:0);return d?{value:h}:h}},"binary*":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)*b(c,e,f,g);return d?{value:c}:c}},"binary/":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)/b(c,e,f,g);return d?{value:c}:c}},"binary%":function(a,b,d){return function(c,e,f,g){c=a(c,e,
f,g)%b(c,e,f,g);return d?{value:c}:c}},"binary===":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)===b(c,e,f,g);return d?{value:c}:c}},"binary!==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!==b(c,e,f,g);return d?{value:c}:c}},"binary==":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)==b(c,e,f,g);return d?{value:c}:c}},"binary!=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)!=b(c,e,f,g);return d?{value:c}:c}},"binary<":function(a,b,d){return function(c,e,f,g){c=a(c,e,
f,g)<b(c,e,f,g);return d?{value:c}:c}},"binary>":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>b(c,e,f,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||
b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,h){e=a(e,f,g,h)?b(e,f,g,h):d(e,f,g,h);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:void 0,name:void 0,value:a}:a}},identifier:function(a,b,d,c,e){return function(f,g,h,k){f=g&&a in g?g:f;c&&1!==c&&f&&!f[a]&&(f[a]={});g=f?f[a]:void 0;b&&ra(g,e);return d?{context:f,name:a,value:g}:g}},computedMember:function(a,b,d,c,e){return function(f,g,h,k){var l=a(f,g,h,k),m,n;null!=l&&(m=b(f,
g,h,k),m+="",Sa(m,e),c&&1!==c&&(Ib(l),l&&!l[m]&&(l[m]={})),n=l[m],ra(n,e));return d?{context:l,name:m,value:n}:n}},nonComputedMember:function(a,b,d,c,e,f){return function(g,h,k,l){g=a(g,h,k,l);e&&1!==e&&(Ib(g),g&&!g[b]&&(g[b]={}));h=null!=g?g[b]:void 0;(d||Jb(b))&&ra(h,f);return c?{context:g,name:b,value:h}:h}},inputs:function(a,b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};var kc=function(a,b,d){this.lexer=a;this.$filter=b;this.options=d;this.ast=new s(a,d);this.astCompiler=d.csp?new ud(this.ast,
b):new td(this.ast,b)};kc.prototype={constructor:kc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};var ng=Object.prototype.valueOf,sa=N("$sce"),la={HTML:"html",CSS:"css",URL:"url",RESOURCE_URL:"resourceUrl",JS:"js"},pg=N("$compile"),$=C.document.createElement("a"),yd=Y(C.location.href);zd.$inject=["$document"];Mc.$inject=["$provide"];var Gd=22,Fd=".",mc="0";Ad.$inject=["$locale"];Cd.$inject=["$locale"];var Ag={yyyy:ba("FullYear",4,0,!1,!0),yy:ba("FullYear",2,0,
!0,!0),y:ba("FullYear",1,0,!1,!0),MMMM:kb("Month"),MMM:kb("Month",!0),MM:ba("Month",2,1),M:ba("Month",1,1),LLLL:kb("Month",!1,!0),dd:ba("Date",2),d:ba("Date",1),HH:ba("Hours",2),H:ba("Hours",1),hh:ba("Hours",2,-12),h:ba("Hours",1,-12),mm:ba("Minutes",2),m:ba("Minutes",1),ss:ba("Seconds",2),s:ba("Seconds",1),sss:ba("Milliseconds",3),EEEE:kb("Day"),EEE:kb("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Kb(Math[0<a?"floor":"ceil"](a/
60),2)+Kb(Math.abs(a%60),2))},ww:Id(2),w:Id(1),G:nc,GG:nc,GGG:nc,GGGG:function(a,b){return 0>=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},zg=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/,yg=/^\-?\d+$/;Bd.$inject=["$locale"];var tg=ha(Q),ug=ha(ub);Dd.$inject=["$parse"];var oe=ha({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===ma.call(b.prop("href"))?
"xlink:href":"href";b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),vb={};q(Eb,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!=a){var c=Aa("ng-"+b),e=d;"checked"===a&&(e=function(a,b,e){e.ngModel!==e[c]&&d(a,b,e)});vb[c]=function(){return{restrict:"A",priority:100,link:e}}}});q(bd,function(a,b){vb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"==e.ngPattern.charAt(0)&&(c=e.ngPattern.match(Cg))){e.$set("ngPattern",
new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});q(["src","srcset","href"],function(a){var b=Aa("ng-"+a);vb[b]=function(){return{priority:99,link:function(d,c,e){var f=a,g=a;"href"===a&&"[object SVGAnimatedString]"===ma.call(c.prop("href"))&&(g="xlinkHref",e.$attr[g]="xlink:href",f=null);e.$observe(b,function(b){b?(e.$set(g,b),Ea&&f&&c.prop(f,e[g])):"href"===a&&e.$set(g,null)})}}}});var Lb={$addControl:A,$$renameControl:function(a,b){a.$name=b},$removeControl:A,$setValidity:A,
$setDirty:A,$setPristine:A,$setSubmitted:A};Jd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Sd=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||A}return{name:"form",restrict:a?"EAC":"E",require:["form","^^?form"],controller:Jd,compile:function(d,f){d.addClass(Ua).addClass(ob);var g=f.name?"name":a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var n=f[0];if(!("action"in e)){var p=function(b){a.$apply(function(){n.$commitViewValue();
n.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",p,!1);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",p,!1)},0,!1)})}(f[1]||n.$$parentForm).$addControl(n);var q=g?c(n.$name):A;g&&(q(a,n),e.$observe(g,function(b){n.$name!==b&&(q(a,void 0),n.$$parentForm.$$renameControl(n,b),q=c(n.$name),q(a,n))}));d.on("$destroy",function(){n.$$parentForm.$removeControl(n);q(a,void 0);S(n,Lb)})}}}}}]},pe=Sd(),Ce=Sd(!0),Bg=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,
Kg=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,Lg=/^(?=.{1,254}$)(?=.{1,64}@)[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+(\.[-!#$%&'*+\/0-9=?A-Z^_`a-z{|}~]+)*@[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?(\.[A-Za-z0-9]([A-Za-z0-9-]{0,61}[A-Za-z0-9])?)*$/,Mg=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Td=/^(\d{4,})-(\d{2})-(\d{2})$/,Ud=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,rc=/^(\d{4,})-W(\d\d)$/,Vd=/^(\d{4,})-(\d\d)$/,
Wd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Ld=U();q(["date","datetime-local","month","time","week"],function(a){Ld[a]=!0});var Xd={text:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);pc(c)},date:mb("date",Td,Nb(Td,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":mb("datetimelocal",Ud,Nb(Ud,"yyyy MM dd HH mm ss sss".split(" ")),"yyyy-MM-ddTHH:mm:ss.sss"),time:mb("time",Wd,Nb(Wd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:mb("week",rc,function(a,b){if(da(a))return a;if(G(a)){rc.lastIndex=0;var d=rc.exec(a);
if(d){var c=+d[1],e=+d[2],f=d=0,g=0,h=0,k=Hd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),h=b.getMilliseconds());return new Date(c,0,k.getDate()+e,d,f,g,h)}}return NaN},"yyyy-Www"),month:mb("month",Vd,Nb(Vd,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f){Md(a,b,d,c);lb(a,b,d,c,e,f);c.$$parserName="number";c.$parsers.push(function(a){if(c.$isEmpty(a))return null;if(Mg.test(a))return parseFloat(a)});c.$formatters.push(function(a){if(!c.$isEmpty(a)){if(!T(a))throw nb("numfmt",
a);a=a.toString()}return a});if(w(d.min)||d.ngMin){var g;c.$validators.min=function(a){return c.$isEmpty(a)||y(g)||a>=g};d.$observe("min",function(a){w(a)&&!T(a)&&(a=parseFloat(a));g=T(a)&&!isNaN(a)?a:void 0;c.$validate()})}if(w(d.max)||d.ngMax){var h;c.$validators.max=function(a){return c.$isEmpty(a)||y(h)||a<=h};d.$observe("max",function(a){w(a)&&!T(a)&&(a=parseFloat(a));h=T(a)&&!isNaN(a)?a:void 0;c.$validate()})}},url:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);pc(c);c.$$parserName="url";c.$validators.url=
function(a,b){var d=a||b;return c.$isEmpty(d)||Kg.test(d)}},email:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);pc(c);c.$$parserName="email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||Lg.test(d)}},radio:function(a,b,d,c){y(d.name)&&b.attr("name",++pb);b.on("click",function(a){b[0].checked&&c.$setViewValue(d.value,a&&a.type)});c.$render=function(){b[0].checked=d.value==c.$viewValue};d.$observe("value",c.$render)},checkbox:function(a,b,d,c,e,f,g,h){var k=Nd(h,a,"ngTrueValue",d.ngTrueValue,
!0),l=Nd(h,a,"ngFalseValue",d.ngFalseValue,!1);b.on("click",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=function(a){return!1===a};c.$formatters.push(function(a){return na(a,k)});c.$parsers.push(function(a){return a?k:l})},hidden:A,button:A,submit:A,reset:A,file:A},Gc=["$browser","$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,h){h[0]&&(Xd[Q(g.type)]||Xd.text)(e,f,
g,h[0],b,a,d,c)}}}}],Ng=/^(true|false|\d+)$/,Ue=function(){return{restrict:"A",priority:100,compile:function(a,b){return Ng.test(b.ngValue)?function(a,b,e){e.$set("value",a.$eval(e.ngValue))}:function(a,b,e){a.$watch(e.ngValue,function(a){e.$set("value",a)})}}}},ue=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b);return function(b,c,e){a.$$addBindingInfo(c,e.ngBind);c=c[0];b.$watch(e.ngBind,function(a){c.textContent=y(a)?"":a})}}}}],we=["$interpolate","$compile",
function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=y(a)?"":a})}}}}],ve=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g=b(e.ngBindHtml,function(b){return a.valueOf(b)});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){var d=
f(b);c.html(a.getTrustedHtml(d)||"")})}}}}],Te=ha({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),xe=qc("",!0),ze=qc("Odd",0),ye=qc("Even",1),Ae=Ta({compile:function(a,b){b.$set("ngCloak",void 0);a.removeClass("ng-cloak")}}),Be=[function(){return{restrict:"A",scope:!0,controller:"@",priority:500}}],Lc={},Og={blur:!0,focus:!0};q("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),
function(a){var b=Aa("ng-"+a);Lc[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(e,f){var g=d(f[b],null,!0);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};Og[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var Ee=["$animate","$compile",function(a,b){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var h,k,l;d.$watch(e.ngIf,function(d){d?k||g(function(d,f){k=f;d[d.length++]=
b.$$createComment("end ngIf",e.ngIf);h={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),k&&(k.$destroy(),k=null),h&&(l=tb(h.clone),a.leave(l).then(function(){l=null}),h=null))})}}}],Fe=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0,transclude:"element",controller:ca.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",h=e.autoscroll;return function(c,e,m,n,p){var q=0,s,B,r,y=function(){B&&(B.remove(),B=null);s&&
(s.$destroy(),s=null);r&&(d.leave(r).then(function(){B=null}),B=r,r=null)};c.$watch(f,function(f){var m=function(){!w(h)||h&&!c.$eval(h)||b()},t=++q;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&t===q){var b=c.$new();n.template=a;a=p(b,function(a){y();d.enter(a,null,e).then(m)});s=b;r=a;s.$emit("$includeContentLoaded",f);c.$eval(g)}},function(){c.$$destroyed||t!==q||(y(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(y(),n.template=null)})}}}}],We=["$compile",function(a){return{restrict:"ECA",
priority:-400,require:"ngInclude",link:function(b,d,c,e){ma.call(d[0]).match(/SVG/)?(d.empty(),a(Oc(e.template,C.document).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],Ge=Ta({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}),Se=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=b.attr(d.$attr.ngList)||", ",f="false"!==d.ngTrim,g=f?W(e):e;c.$parsers.push(function(a){if(!y(a)){var b=
[];a&&q(a.split(g),function(a){a&&b.push(f?W(a):a)});return b}});c.$formatters.push(function(a){if(L(a))return a.join(e)});c.$isEmpty=function(a){return!a||!a.length}}}},ob="ng-valid",Od="ng-invalid",Ua="ng-pristine",Mb="ng-dirty",Qd="ng-pending",nb=N("ngModel"),Pg=["$scope","$exceptionHandler","$attrs","$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,b,d,c,e,f,g,h,k,l){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=void 0;this.$validators={};
this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=void 0;this.$name=l(d.name||"",!1)(a);this.$$parentForm=Lb;var m=e(d.ngModel),n=m.assign,p=m,u=n,s=null,B,r=this;this.$$setOptions=function(a){if((r.$options=a)&&a.getterSetter){var b=e(d.ngModel+"()"),f=e(d.ngModel+"($$$p)");p=function(a){var c=m(a);z(c)&&(c=b(a));
return c};u=function(a,b){z(m(a))?f(a,{$$$p:b}):n(a,b)}}else if(!m.assign)throw nb("nonassign",d.ngModel,ya(c));};this.$render=A;this.$isEmpty=function(a){return y(a)||""===a||null===a||a!==a};this.$$updateEmptyClasses=function(a){r.$isEmpty(a)?(f.removeClass(c,"ng-not-empty"),f.addClass(c,"ng-empty")):(f.removeClass(c,"ng-empty"),f.addClass(c,"ng-not-empty"))};var J=0;Kd({ctrl:this,$element:c,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]},$animate:f});this.$setPristine=function(){r.$dirty=
!1;r.$pristine=!0;f.removeClass(c,Mb);f.addClass(c,Ua)};this.$setDirty=function(){r.$dirty=!0;r.$pristine=!1;f.removeClass(c,Ua);f.addClass(c,Mb);r.$$parentForm.$setDirty()};this.$setUntouched=function(){r.$touched=!1;r.$untouched=!0;f.setClass(c,"ng-untouched","ng-touched")};this.$setTouched=function(){r.$touched=!0;r.$untouched=!1;f.setClass(c,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){g.cancel(s);r.$viewValue=r.$$lastCommittedViewValue;r.$render()};this.$validate=function(){if(!T(r.$modelValue)||
!isNaN(r.$modelValue)){var a=r.$$rawModelValue,b=r.$valid,c=r.$modelValue,d=r.$options&&r.$options.allowInvalid;r.$$runValidators(a,r.$$lastCommittedViewValue,function(e){d||b===e||(r.$modelValue=e?a:void 0,r.$modelValue!==c&&r.$$writeModelToScope())})}};this.$$runValidators=function(a,b,c){function d(){var c=!0;q(r.$validators,function(d,e){var g=d(a,b);c=c&&g;f(e,g)});return c?!0:(q(r.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;q(r.$asyncValidators,function(e,g){var h=
e(a,b);if(!h||!z(h.then))throw nb("nopromise",h);f(g,void 0);c.push(h.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?k.all(c).then(function(){g(d)},A):g(!0)}function f(a,b){h===J&&r.$setValidity(a,b)}function g(a){h===J&&c(a)}J++;var h=J;(function(){var a=r.$$parserName||"parse";if(y(B))f(a,null);else return B||(q(r.$validators,function(a,b){f(b,null)}),q(r.$asyncValidators,function(a,b){f(b,null)})),f(a,B),B;return!0})()?d()?e():g(!1):g(!1)};this.$commitViewValue=function(){var a=
r.$viewValue;g.cancel(s);if(r.$$lastCommittedViewValue!==a||""===a&&r.$$hasNativeValidators)r.$$updateEmptyClasses(a),r.$$lastCommittedViewValue=a,r.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var b=r.$$lastCommittedViewValue;if(B=y(b)?void 0:!0)for(var c=0;c<r.$parsers.length;c++)if(b=r.$parsers[c](b),y(b)){B=!1;break}T(r.$modelValue)&&isNaN(r.$modelValue)&&(r.$modelValue=p(a));var d=r.$modelValue,e=r.$options&&r.$options.allowInvalid;r.$$rawModelValue=
b;e&&(r.$modelValue=b,r.$modelValue!==d&&r.$$writeModelToScope());r.$$runValidators(b,r.$$lastCommittedViewValue,function(a){e||(r.$modelValue=a?b:void 0,r.$modelValue!==d&&r.$$writeModelToScope())})};this.$$writeModelToScope=function(){u(a,r.$modelValue);q(r.$viewChangeListeners,function(a){try{a()}catch(c){b(c)}})};this.$setViewValue=function(a,b){r.$viewValue=a;r.$options&&!r.$options.updateOnDefault||r.$$debounceViewValueCommit(b)};this.$$debounceViewValueCommit=function(b){var c=0,d=r.$options;
d&&w(d.debounce)&&(d=d.debounce,T(d)?c=d:T(d[b])?c=d[b]:T(d["default"])&&(c=d["default"]));g.cancel(s);c?s=g(function(){r.$commitViewValue()},c):h.$$phase?r.$commitViewValue():a.$apply(function(){r.$commitViewValue()})};a.$watch(function(){var b=p(a);if(b!==r.$modelValue&&(r.$modelValue===r.$modelValue||b===b)){r.$modelValue=r.$$rawModelValue=b;B=void 0;for(var c=r.$formatters,d=c.length,e=b;d--;)e=c[d](e);r.$viewValue!==e&&(r.$$updateEmptyClasses(e),r.$viewValue=r.$$lastCommittedViewValue=e,r.$render(),
r.$$runValidators(b,e,A))}return b})}],Re=["$rootScope",function(a){return{restrict:"A",require:["ngModel","^?form","^?ngModelOptions"],controller:Pg,priority:1,compile:function(b){b.addClass(Ua).addClass("ng-untouched").addClass(ob);return{pre:function(a,b,e,f){var g=f[0];b=f[1]||g.$$parentForm;g.$$setOptions(f[2]&&f[2].$options);b.$addControl(g);e.$observe("name",function(a){g.$name!==a&&g.$$parentForm.$$renameControl(g,a)});a.$on("$destroy",function(){g.$$parentForm.$removeControl(g)})},post:function(b,
c,e,f){var g=f[0];if(g.$options&&g.$options.updateOn)c.on(g.$options.updateOn,function(a){g.$$debounceViewValueCommit(a&&a.type)});c.on("blur",function(){g.$touched||(a.$$phase?b.$evalAsync(g.$setTouched):b.$apply(g.$setTouched))})}}}}}],Qg=/(\s+|^)default(\s+|$)/,Ve=function(){return{restrict:"A",controller:["$scope","$attrs",function(a,b){var d=this;this.$options=pa(a.$eval(b.ngModelOptions));w(this.$options.updateOn)?(this.$options.updateOnDefault=!1,this.$options.updateOn=W(this.$options.updateOn.replace(Qg,
function(){d.$options.updateOnDefault=!0;return" "}))):this.$options.updateOnDefault=!0}]}},He=Ta({terminal:!0,priority:1E3}),Rg=N("ngOptions"),Sg=/^\s*([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+group\s+by\s+([\s\S]+?))?(?:\s+disable\s+when\s+([\s\S]+?))?\s+for\s+(?:([\$\w][\$\w]*)|(?:\(\s*([\$\w][\$\w]*)\s*,\s*([\$\w][\$\w]*)\s*\)))\s+in\s+([\s\S]+?)(?:\s+track\s+by\s+([\s\S]+?))?$/,Pe=["$compile","$document","$parse",function(a,b,d){function c(a,b,c){function e(a,b,c,d,f){this.selectValue=a;this.viewValue=
b;this.label=c;this.group=d;this.disabled=f}function f(a){var b;if(!q&&ta(a))b=a;else{b=[];for(var c in a)a.hasOwnProperty(c)&&"$"!==c.charAt(0)&&b.push(c)}return b}var n=a.match(Sg);if(!n)throw Rg("iexp",a,ya(b));var p=n[5]||n[7],q=n[6];a=/ as /.test(n[0])&&n[1];var s=n[9];b=d(n[2]?n[1]:p);var w=a&&d(a)||b,r=s&&d(s),y=s?function(a,b){return r(c,b)}:function(a){return Ca(a)},v=function(a,b){return y(a,E(a,b))},A=d(n[2]||n[1]),t=d(n[3]||""),K=d(n[4]||""),z=d(n[8]),H={},E=q?function(a,b){H[q]=b;H[p]=
a;return H}:function(a){H[p]=a;return H};return{trackBy:s,getTrackByValue:v,getWatchables:d(z,function(a){var b=[];a=a||[];for(var d=f(a),e=d.length,g=0;g<e;g++){var h=a===d?g:d[g],l=a[h],h=E(l,h),l=y(l,h);b.push(l);if(n[2]||n[1])l=A(c,h),b.push(l);n[4]&&(h=K(c,h),b.push(h))}return b}),getOptions:function(){for(var a=[],b={},d=z(c)||[],g=f(d),h=g.length,n=0;n<h;n++){var p=d===g?n:g[n],q=E(d[p],p),r=w(c,q),p=y(r,q),u=A(c,q),H=t(c,q),q=K(c,q),r=new e(p,r,u,H,q);a.push(r);b[p]=r}return{items:a,selectValueMap:b,
getOptionFromViewValue:function(a){return b[v(a)]},getViewValueFromOption:function(a){return s?ca.copy(a.viewValue):a.viewValue}}}}}var e=C.document.createElement("option"),f=C.document.createElement("optgroup");return{restrict:"A",terminal:!0,require:["select","ngModel"],link:{pre:function(a,b,c,d){d[0].registerOption=A},post:function(d,h,k,l){function m(a,b){a.element=b;b.disabled=a.disabled;a.label!==b.label&&(b.label=a.label,b.textContent=a.label);a.value!==b.value&&(b.value=a.selectValue)}function n(){var a=
t&&p.readValue();if(t)for(var b=t.items.length-1;0<=b;b--){var c=t.items[b];w(c.group)?Db(c.element.parentNode):Db(c.element)}t=K.getOptions();var d={};v&&h.prepend(B);t.items.forEach(function(a){var b;if(w(a.group)){b=d[a.group];b||(b=f.cloneNode(!1),C.appendChild(b),b.label=null===a.group?"null":a.group,d[a.group]=b);var c=e.cloneNode(!1)}else b=C,c=e.cloneNode(!1);b.appendChild(c);m(a,c)});h[0].appendChild(C);s.$render();s.$isEmpty(a)||(b=p.readValue(),(K.trackBy||y?na(a,b):a===b)||(s.$setViewValue(b),
s.$render()))}var p=l[0],s=l[1],y=k.multiple,B;l=0;for(var r=h.children(),A=r.length;l<A;l++)if(""===r[l].value){B=r.eq(l);break}var v=!!B,z=F(e.cloneNode(!1));z.val("?");var t,K=c(k.ngOptions,h,d),C=b[0].createDocumentFragment();y?(s.$isEmpty=function(a){return!a||0===a.length},p.writeValue=function(a){t.items.forEach(function(a){a.element.selected=!1});a&&a.forEach(function(a){if(a=t.getOptionFromViewValue(a))a.element.selected=!0})},p.readValue=function(){var a=h.val()||[],b=[];q(a,function(a){(a=
t.selectValueMap[a])&&!a.disabled&&b.push(t.getViewValueFromOption(a))});return b},K.trackBy&&d.$watchCollection(function(){if(L(s.$viewValue))return s.$viewValue.map(function(a){return K.getTrackByValue(a)})},function(){s.$render()})):(p.writeValue=function(a){var b=t.getOptionFromViewValue(a);b?(h[0].value!==b.selectValue&&(z.remove(),v||B.remove(),h[0].value=b.selectValue,b.element.selected=!0),b.element.setAttribute("selected","selected")):null===a||v?(z.remove(),v||h.prepend(B),h.val(""),B.prop("selected",
!0),B.attr("selected",!0)):(v||B.remove(),h.prepend(z),h.val("?"),z.prop("selected",!0),z.attr("selected",!0))},p.readValue=function(){var a=t.selectValueMap[h.val()];return a&&!a.disabled?(v||B.remove(),z.remove(),t.getViewValueFromOption(a)):null},K.trackBy&&d.$watch(function(){return K.getTrackByValue(s.$viewValue)},function(){s.$render()}));v?(B.remove(),a(B)(d),B.removeClass("ng-scope")):B=F(e.cloneNode(!1));h.empty();n();d.$watchCollection(K.getWatchables,n)}}}}],Ie=["$locale","$interpolate",
"$log",function(a,b,d){var c=/{}/g,e=/^when(Minus)?(.+)$/;return{link:function(f,g,h){function k(a){g.text(a||"")}var l=h.count,m=h.$attr.when&&g.attr(h.$attr.when),n=h.offset||0,p=f.$eval(m)||{},s={},w=b.startSymbol(),B=b.endSymbol(),r=w+l+"-"+n+B,z=ca.noop,v;q(h,function(a,b){var c=e.exec(b);c&&(c=(c[1]?"-":"")+Q(c[2]),p[c]=g.attr(h.$attr[b]))});q(p,function(a,d){s[d]=b(a.replace(c,r))});f.$watch(l,function(b){var c=parseFloat(b),e=isNaN(c);e||c in p||(c=a.pluralCat(c-n));c===v||e&&T(v)&&isNaN(v)||
(z(),e=s[c],y(e)?(null!=b&&d.debug("ngPluralize: no rule defined for '"+c+"' in "+m),z=A,k()):z=f.$watch(e,k),v=c)})}}}],Je=["$parse","$animate","$compile",function(a,b,d){var c=N("ngRepeat"),e=function(a,b,c,d,e,m,n){a[c]=d;e&&(a[e]=m);a.$index=b;a.$first=0===b;a.$last=b===n-1;a.$middle=!(a.$first||a.$last);a.$odd=!(a.$even=0===(b&1))};return{restrict:"A",multiElement:!0,transclude:"element",priority:1E3,terminal:!0,$$tlb:!0,compile:function(f,g){var h=g.ngRepeat,k=d.$$createComment("end ngRepeat",
h),l=h.match(/^\s*([\s\S]+?)\s+in\s+([\s\S]+?)(?:\s+as\s+([\s\S]+?))?(?:\s+track\s+by\s+([\s\S]+?))?\s*$/);if(!l)throw c("iexp",h);var m=l[1],n=l[2],p=l[3],s=l[4],l=m.match(/^(?:(\s*[\$\w]+)|\(\s*([\$\w]+)\s*,\s*([\$\w]+)\s*\))$/);if(!l)throw c("iidexp",m);var w=l[3]||l[1],y=l[2];if(p&&(!/^[$a-zA-Z_][$a-zA-Z0-9_]*$/.test(p)||/^(null|undefined|this|\$index|\$first|\$middle|\$last|\$even|\$odd|\$parent|\$root|\$id)$/.test(p)))throw c("badident",p);var r,z,v,A,t={$id:Ca};s?r=a(s):(v=function(a,b){return Ca(b)},
A=function(a){return a});return function(a,d,f,g,l){r&&(z=function(b,c,d){y&&(t[y]=b);t[w]=c;t.$index=d;return r(a,t)});var m=U();a.$watchCollection(n,function(f){var g,n,r=d[0],s,u=U(),t,C,F,E,G,D,H;p&&(a[p]=f);if(ta(f))G=f,n=z||v;else for(H in n=z||A,G=[],f)ua.call(f,H)&&"$"!==H.charAt(0)&&G.push(H);t=G.length;H=Array(t);for(g=0;g<t;g++)if(C=f===G?g:G[g],F=f[C],E=n(C,F,g),m[E])D=m[E],delete m[E],u[E]=D,H[g]=D;else{if(u[E])throw q(H,function(a){a&&a.scope&&(m[a.id]=a)}),c("dupes",h,E,F);H[g]={id:E,
scope:void 0,clone:void 0};u[E]=!0}for(s in m){D=m[s];E=tb(D.clone);b.leave(E);if(E[0].parentNode)for(g=0,n=E.length;g<n;g++)E[g].$$NG_REMOVED=!0;D.scope.$destroy()}for(g=0;g<t;g++)if(C=f===G?g:G[g],F=f[C],D=H[g],D.scope){s=r;do s=s.nextSibling;while(s&&s.$$NG_REMOVED);D.clone[0]!=s&&b.move(tb(D.clone),null,r);r=D.clone[D.clone.length-1];e(D.scope,g,w,F,y,C,t)}else l(function(a,c){D.scope=c;var d=k.cloneNode(!1);a[a.length++]=d;b.enter(a,null,r);r=d;D.clone=a;u[D.id]=D;e(D.scope,g,w,F,y,C,t)});m=
u})}}}}],Ke=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngShow,function(b){a[b?"removeClass":"addClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],De=["$animate",function(a){return{restrict:"A",multiElement:!0,link:function(b,d,c){b.$watch(c.ngHide,function(b){a[b?"addClass":"removeClass"](d,"ng-hide",{tempClasses:"ng-hide-animate"})})}}}],Le=Ta(function(a,b,d){a.$watch(d.ngStyle,function(a,d){d&&a!==d&&q(d,function(a,c){b.css(c,"")});a&&b.css(a)},
!0)}),Me=["$animate","$compile",function(a,b){return{require:"ngSwitch",controller:["$scope",function(){this.cases={}}],link:function(d,c,e,f){var g=[],h=[],k=[],l=[],m=function(a,b){return function(){a.splice(b,1)}};d.$watch(e.ngSwitch||e.on,function(c){var d,e;d=0;for(e=k.length;d<e;++d)a.cancel(k[d]);d=k.length=0;for(e=l.length;d<e;++d){var s=tb(h[d].clone);l[d].$destroy();(k[d]=a.leave(s)).then(m(k,d))}h.length=0;l.length=0;(g=f.cases["!"+c]||f.cases["?"])&&q(g,function(c){c.transclude(function(d,
e){l.push(e);var f=c.element;d[d.length++]=b.$$createComment("end ngSwitchWhen");h.push({clone:d});a.enter(d,f.parent(),f)})})})}}}],Ne=Ta({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){c.cases["!"+d.ngSwitchWhen]=c.cases["!"+d.ngSwitchWhen]||[];c.cases["!"+d.ngSwitchWhen].push({transclude:e,element:b})}}),Oe=Ta({transclude:"element",priority:1200,require:"^ngSwitch",multiElement:!0,link:function(a,b,d,c,e){c.cases["?"]=c.cases["?"]||[];c.cases["?"].push({transclude:e,
element:b})}}),Tg=N("ngTransclude"),Qe=["$compile",function(a){return{restrict:"EAC",terminal:!0,compile:function(b){var d=a(b.contents());b.empty();return function(a,b,f,g,h){function k(){d(a,function(a){b.append(a)})}if(!h)throw Tg("orphan",ya(b));f.ngTransclude===f.$attr.ngTransclude&&(f.ngTransclude="");f=f.ngTransclude||f.ngTranscludeSlot;h(function(a,c){a.length?b.append(a):(k(),c.$destroy())},null,f);f&&!h.isSlotFilled(f)&&k()}}}}],qe=["$templateCache",function(a){return{restrict:"E",terminal:!0,
compile:function(b,d){"text/ng-template"==d.type&&a.put(d.id,b[0].text)}}}],Ug={$setViewValue:A,$render:A},Vg=["$element","$scope",function(a,b){var d=this,c=new Ra;d.ngModelCtrl=Ug;d.unknownOption=F(C.document.createElement("option"));d.renderUnknownOption=function(b){b="? "+Ca(b)+" ?";d.unknownOption.val(b);a.prepend(d.unknownOption);a.val(b)};b.$on("$destroy",function(){d.renderUnknownOption=A});d.removeUnknownOption=function(){d.unknownOption.parent()&&d.unknownOption.remove()};d.readValue=function(){d.removeUnknownOption();
return a.val()};d.writeValue=function(b){d.hasOption(b)?(d.removeUnknownOption(),a.val(b),""===b&&d.emptyOption.prop("selected",!0)):null==b&&d.emptyOption?(d.removeUnknownOption(),a.val("")):d.renderUnknownOption(b)};d.addOption=function(a,b){if(8!==b[0].nodeType){Qa(a,'"option value"');""===a&&(d.emptyOption=b);var g=c.get(a)||0;c.put(a,g+1);d.ngModelCtrl.$render();b[0].hasAttribute("selected")&&(b[0].selected=!0)}};d.removeOption=function(a){var b=c.get(a);b&&(1===b?(c.remove(a),""===a&&(d.emptyOption=
void 0)):c.put(a,b-1))};d.hasOption=function(a){return!!c.get(a)};d.registerOption=function(a,b,c,h,k){if(h){var l;c.$observe("value",function(a){w(l)&&d.removeOption(l);l=a;d.addOption(a,b)})}else k?a.$watch(k,function(a,e){c.$set("value",a);e!==a&&d.removeOption(e);d.addOption(a,b)}):d.addOption(c.value,b);b.on("$destroy",function(){d.removeOption(c.value);d.ngModelCtrl.$render()})}}],re=function(){return{restrict:"E",require:["select","?ngModel"],controller:Vg,priority:1,link:{pre:function(a,b,
d,c){var e=c[1];if(e){var f=c[0];f.ngModelCtrl=e;b.on("change",function(){a.$apply(function(){e.$setViewValue(f.readValue())})});if(d.multiple){f.readValue=function(){var a=[];q(b.find("option"),function(b){b.selected&&a.push(b.value)});return a};f.writeValue=function(a){var c=new Ra(a);q(b.find("option"),function(a){a.selected=w(c.get(a.value))})};var g,h=NaN;a.$watch(function(){h!==e.$viewValue||na(g,e.$viewValue)||(g=ia(e.$viewValue),e.$render());h=e.$viewValue});e.$isEmpty=function(a){return!a||
0===a.length}}}},post:function(a,b,d,c){var e=c[1];if(e){var f=c[0];e.$render=function(){f.writeValue(e.$viewValue)}}}}}},te=["$interpolate",function(a){return{restrict:"E",priority:100,compile:function(b,d){if(w(d.value))var c=a(d.value,!0);else{var e=a(b.text(),!0);e||d.$set("value",b.text())}return function(a,b,d){var k=b.parent();(k=k.data("$selectController")||k.parent().data("$selectController"))&&k.registerOption(a,b,d,c,e)}}}}],se=ha({restrict:"E",terminal:!1}),Ic=function(){return{restrict:"A",
require:"?ngModel",link:function(a,b,d,c){c&&(d.required=!0,c.$validators.required=function(a,b){return!d.required||!c.$isEmpty(b)},d.$observe("required",function(){c.$validate()}))}}},Hc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e,f=d.ngPattern||d.pattern;d.$observe("pattern",function(a){G(a)&&0<a.length&&(a=new RegExp("^"+a+"$"));if(a&&!a.test)throw N("ngPattern")("noregexp",f,a,ya(b));e=a||void 0;c.$validate()});c.$validators.pattern=function(a,b){return c.$isEmpty(b)||
y(e)||e.test(b)}}}}},Kc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=-1;d.$observe("maxlength",function(a){a=Z(a);e=isNaN(a)?-1:a;c.$validate()});c.$validators.maxlength=function(a,b){return 0>e||c.$isEmpty(b)||b.length<=e}}}}},Jc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=0;d.$observe("minlength",function(a){e=Z(a)||0;c.$validate()});c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=e}}}}};C.angular.bootstrap?
C.console&&console.log("WARNING: Tried to load angular more than once."):(je(),le(ca),ca.module("ngLocale",[],["$provide",function(a){function b(a){a+="";var b=a.indexOf(".");return-1==b?0:a.length-b-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM","PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),
SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "),WEEKENDRANGE:[5,6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",
PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a,c){var e=a|0,f=c;void 0===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),F(C.document).ready(function(){fe(C.document,Bc)}))})(window);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend('<style type="text/css">@charset "UTF-8";[ng\\:cloak],[ng-cloak],[data-ng-cloak],[x-ng-cloak],.ng-cloak,.x-ng-cloak,.ng-hide:not(.ng-hide-animate){display:none !important;}ng\\:form{display:block;}.ng-animate-shim{visibility:hidden;}.ng-anchor{position:absolute;}</style>');
//# sourceMappingURL=angular.min.js.map

View File

@ -0,0 +1,52 @@
var phonecatApp = angular.module('phonecatApp', []);
// Define the `PhoneListController` controller on the `phonecatApp` module
phonecatApp.controller('PhoneListController', function PhoneListController($scope, $http) {
$scope.ssids = [];
// $scope.url = "http://" + location.host.split(":")[0] + ":4000";
$scope.url = "http://192.168.24.1:4000";
$http.get($scope.url + "/scan").then(function(resp){
console.log(resp.data);
$scope.ssids = resp.data;
}).catch(function(error){
console.log("not running on device?");
$scope.ssids = ["test_ssid", "meep", "lawl"];
})
$scope.select_ssid = function(ssid){
document.getElementById("wifissid").value = ssid;
};
$scope.submit = function(){
ssid = document.getElementById("wifissid").value;
psk = document.getElementById("wifipsk").value;
email = document.getElementById("fbemail").value;
password = document.getElementById("fbpwd").value;
server = document.getElementById("fbserver").value;
port = document.getElementById("fbport").value;
realSrv = "http://" + server + ":" + port;
if(ssid != ""){
json = {
"email": email,
"password": password,
"server": realSrv,
"wifi":{
"ssid": ssid,
"psk": psk
}
};
console.log(JSON.stringify(json));
$http.post($scope.url + "/login", json).then(function(resp){
console.log("Should never see this...");
}).catch(function(error){
console.log("will probably see this a lot...");
console.log(JSON.stringify(error));
});
} else{
console.log("write some better error handling n00b.")
}
};
});

View File

@ -0,0 +1,93 @@
html {
height: 100%;
}
body {
background: linear-gradient(-135deg, #089460, #159ACC);
margin: 0;
}
.container {
padding: 50px 15px;
max-width: 425px;
margin-left: auto;
margin-right: auto;
}
h1 {
font-family: Arial;
text-align: center;
margin: 0;
font-size: 38px;
font-weight: 300;
color: #f4f4f4;
}
h2 {
text-align: center;
font-family: Arial;
font-size: 18px;
color: #f4f4f4;
font-weight: 300;
}
h3 {
font-family: Arial;
color: #f4f4f4;
margin: 10px 0;
}
.configuration-group {
margin-top: 50px;
}
input {
padding: 7px 10px;
font-size: 16px;
border: none;
border-radius: 2px;
margin-bottom: 10px;
width: 100%;
}
.btn {
color: #159ACC;
font-size: 16px;
font-weight: 600;
border-radius: 2px;
border: none;
padding: 7px 10px;
background: #f4f4f4;
margin-bottom: 10px;
font-family: Arial;
text-decoration: none;
}
.btn:hover {
background: white;
}
ul {
color: #f4f4f4;
padding-left: 20px;
margin: 0 0 10px 0;
}
li a:hover {
text-decoration: underline;
}
li a {
color: #f4f4f4;
text-decoration: none;
font-family: Arial;
}
.save-button {
display: block;
text-align: center;
margin-top: 50px;
font-size: 30px;
padding: 15px;
border-radius: 4px;
}

View File

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en" ng-app="phonecatApp">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<link rel="stylesheet" type="text/css" href="configurator-styles.css">
<title>Welcome to Configurator</title>
</head>
<body ng-controller="PhoneListController">
<div class="container">
<h1>Configure Your Farmbot</h1>
<h2>Enter your Wi-Fi and FarmBot Web App credentials</h2>
<div class="jumbotron">
<!-- wifi -->
<div class="configuration-group">
<h3 class="input-group-addon" id="sizing-addon1">Wi-Fi</h3>
<div class="input-group input-group-lg">
<!-- /btn-group -->
<input id="wifissid" type="text" class="form-control" placeholder="Wireless SSID" aria-describedby="sizing-addon1" required="required">
<div class="input-group-btn input-group-btn-lg">
<!-- Scan button -->
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
Scan for Networks
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right">
<li ng-repeat=" ssid in ssids "><a href="#" ng-click="select_ssid(ssid)"> {{ssid}} </a></li>
</ul>
<!-- Scan button -->
</div>
</div>
<!-- /input-group -->
<input id="wifipsk" type="password" class="form-control" placeholder="Wireless PSK" aria-describedby="sizing-addon1" required="required">
</div>
<!-- /wifi -->
<!-- farmbot config-->
<div class="configuration-group">
<h3 class="input-group-addon" id="sizing-addon1">Web App</h3>
<input id="fbemail" type="email" class="form-control" placeholder="Username" aria-describedby="sizing-addon1" required="required">
<input id="fbpwd" type="password" class="form-control" placeholder="Password" aria-describedby="sizing-addon1" required="required">
<input id="fbserver" type="url" value="my.farmbot.io" placeholder="my.farmbot.io" class="form-control" aria-describedby="sizing-addon1" required="required">
<input id="fbport" type="integer" value="80" placeholder="80" class="form-control" aria-describedby="sizing-addon1" required="required">
</div>
<!-- /farmbot config-->
<a href="#" ng-click="submit()" class="btn btn-lg form-control save-button"> <strong>Save Configuration</strong> </a>
</div>
</div> <!-- /container -->
<script src="jquery-3.1.0.min.js"></script>
<script src="angular.min.js"></script>
<script src="app/app.js"></script>
</body>
</html>

File diff suppressed because one or more lines are too long

5
config/test.exs 100644
View File

@ -0,0 +1,5 @@
use Mix.Config
import_config "dev.exs"
config :json_rpc,
transport: FakeMqtt

View File

@ -1,31 +0,0 @@
default: &development
adapter: sqlite3
database: db/db.sqlite3
pool: 5
timeout: 5000
# SQLite version 3.x
# gem install sqlite3
#
# Ensure the SQLite 3 gem is defined in your Gemfile
# gem 'sqlite3'
development:
adapter: sqlite3
database: db/db.sqlite3
pool: 5
timeout: 5000
# Warning: The database defined as "test" will be erased and
# re-generated from your development database when you run "rake".
# Do not set this db to the same as development or production.
test:
adapter: sqlite3
database: db/test.sqlite3
pool: 5
timeout: 5000
production:
adapter: sqlite3
database: db/production.sqlite3
pool: 5
timeout: 5000

View File

@ -1,32 +0,0 @@
class AddScheduleSequenceStep < ActiveRecord::Migration
def change
create_table :sequences do |t|
t.string :name
t.timestamps null: false
end
create_table :schedules do |t|
t.references :sequence
t.string :id_on_web_app
t.string :time_unit
t.float :repeat
t.time :start_time
t.time :end_time
t.time :next_time
t.timestamps null: false
end
create_table :steps do |t|
t.string :message_type
t.integer :x
t.integer :y
t.integer :z
t.integer :speed
t.integer :pin
t.integer :value
t.integer :mode
t.references :sequence
t.timestamps null: false
end
end
end

View File

@ -1,7 +0,0 @@
class ChangeAllToDatetime < ActiveRecord::Migration
def change
change_column :schedules, :start_time, :datetime
change_column :schedules, :end_time, :datetime
change_column :schedules, :next_time, :datetime
end
end

View File

@ -1,5 +0,0 @@
class AddPositionToStep < ActiveRecord::Migration
def change
add_column :steps, :position, :integer
end
end

View File

@ -1,5 +0,0 @@
class ChangeStepsValueFromIntegerToString < ActiveRecord::Migration
def change
change_column :steps, :value, :text
end
end

View File

@ -1,8 +0,0 @@
class AddNewIfStatementFieldsToStep < ActiveRecord::Migration
def change
add_column :steps, :operator, :string
add_column :steps, :variable, :string
add_column :steps, :sub_sequence_id, :string
add_column :sequences, :id_on_web_app, :string
end
end

View File

@ -1,9 +0,0 @@
class CreatePlantTable < ActiveRecord::Migration
def change
create_table :plants do |t|
t.string :id_on_web_app
t.integer :x
t.integer :y
end
end
end

View File

@ -1,59 +0,0 @@
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20151009154651) do
create_table "plants", force: :cascade do |t|
t.string "id_on_web_app"
t.integer "x"
t.integer "y"
end
create_table "schedules", force: :cascade do |t|
t.integer "sequence_id"
t.string "time_unit"
t.float "repeat"
t.datetime "start_time"
t.datetime "end_time"
t.datetime "next_time"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "id_on_web_app"
end
create_table "sequences", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "id_on_web_app"
end
create_table "steps", force: :cascade do |t|
t.string "message_type"
t.integer "x"
t.integer "y"
t.integer "z"
t.integer "speed"
t.integer "pin"
t.text "value"
t.integer "mode"
t.integer "sequence_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "position"
t.string "operator"
t.string "variable"
t.string "sub_sequence_id"
end
end

View File

@ -1,7 +0,0 @@
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
#
# Examples:
#
# cities = City.create([{ name: 'Chicago' }, { name: 'Copenhagen' }])
# Mayor.create(name: 'Emanuel', city: cities.first)

View File

@ -1,16 +0,0 @@
# config/application.yml
defaults: &defaults
secrets_file: 'secrets.txt'
mqtt_port: 1883
serial_ports:
- "/dev/ttyUSB0"
- "/dev/ttyACM0"
development:
<<: *defaults
test:
<<: *defaults
production:
<<: *defaults

View File

@ -1,20 +0,0 @@
# god -c farmbot.god -D
God.watch do |w|
w.name = "farmbot"
w.dir = File.expand_path(File.dirname(__FILE__))
w.start = "ruby farmbot.rb"
w.restart_if do |restart|
restart.condition(:memory_usage) do |c|
c.interval = 5.seconds
c.above = 150.megabytes
c.times = [4, 5] # 4 out of 5 intervals
end
restart.condition(:cpu_usage) do |c|
c.interval = 5.seconds
c.above = 50.percent
c.times = [4, 5] # 4 out of 5 intervals
end
end
end

View File

@ -1,10 +0,0 @@
require_relative 'lib/farmbot-pi'
begin
puts "Connecting..."
FarmBotPi.new.start
rescue MQTT::NotConnectedException => e
puts "OH NOES! Farmbot was disconnected from MQTT. Retrying..."
sleep 1
retry
end

102
lib/auth.ex 100644
View File

@ -0,0 +1,102 @@
defmodule Auth do
@path Application.get_env(:fb, :ro_path)
require GenServer
require Logger
@moduledoc """
This is a small disaster ever growing into a bigger disaster.
"""
def get_public_key(server) do
resp = HTTPotion.get("#{server}/api/public_key")
case resp do
%HTTPotion.ErrorResponse{message: "enetunreach"} -> get_public_key(server)
%HTTPotion.ErrorResponse{message: "econnrefused"} -> {:error, "econnrefused"}
_ -> RSA.decode_key(resp.body)
end
end
def encrypt(email, pass, server) do
# Json to encrypt.
json = Poison.encode!(%{"email": email,"password": pass,
"id": Nerves.Lib.UUID.generate,"version": 1})
secret = String.Chars.to_string(RSA.encrypt(json, {:public, get_public_key(server)}))
save_encrypted(secret, server)
secret
end
@doc """
Saves the secret and the server to disk.
"""
def save_encrypted(secret, server) do
File.write("#{@path}/secretes.txt", :erlang.term_to_binary(%{secret: secret, server: server}))
end
@doc """
Loads the secret and server from disk
if there is no file returns nil
"""
def load_encrypted do
file = File.read("#{@path}/secretes.txt")
case file do
{:ok, contents} -> t = :erlang.binary_to_term(contents)
get_token(Map.get(t, :secret), Map.get(t, :server))
_ -> nil
end
end
# Gets a token from the API with given secret and server
def get_token(secret, server) do
payload = Poison.encode!(%{user: %{credentials: :base64.encode_to_string(secret) |> String.Chars.to_string }} )
resp = HTTPotion.post "#{server}/api/tokens", [body: payload, headers: ["Content-Type": "application/json"]]
case resp do
%HTTPotion.ErrorResponse{message: "enetunreach"} -> get_token(secret, server)
%HTTPotion.ErrorResponse{message: reason} -> {:error, reason}
_ -> Map.get(Poison.decode!(resp.body), "token")
end
end
def get_token do
GenServer.call(__MODULE__, {:get_token})
end
# Infinite recursion until we have a token.
# Not concerned about performance yet because the bot can't do anything yet.
def fetch_token do
case Auth.get_token do
nil -> fetch_token
token -> token
end
end
# Genserver stuff
def init(_args) do
{:ok, load_encrypted}
end
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__ )
end
# Am i even using this?
def login(email,pass,server) when is_bitstring(email)
and is_bitstring(pass)
and is_bitstring(server) do
case Wifi.connected? do
true ->
GenServer.call(__MODULE__, {:login, email,pass,server})
_ ->
login(email,pass,server) # Probably process heavy here but im lazy
end
end
def handle_call({:login, email,pass,server}, _from, _old_token) do
secret = encrypt(email,pass,server)
token = get_token(secret, server)
{:reply,token,token}
end
def handle_call({:get_token}, _from, token) do
{:reply, token, token}
end
end

View File

@ -0,0 +1,129 @@
defmodule BotCommandHandler do
require Logger
use GenServer
@moduledoc """
This is the log mechanism for bot commands.
"""
def init(_args) do
{:ok, pid} = GenEvent.start_link
GenEvent.add_handler(pid, BotCommandManager, [])
spawn fn -> get_events(pid) end
{:ok, pid}
end
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
def handle_cast({:add_event, event}, pid) do
GenEvent.notify(pid, event)
{:noreply, pid}
end
def handle_call(:get_pid, _from, pid) do
{:reply, pid, pid}
end
def handle_call(:e_stop, _from, pid) do
GenEvent.notify(pid, :e_stop)
{:reply, :ok, pid}
end
def get_pid do
GenServer.call(__MODULE__, :get_pid, 5000)
end
def e_stop do
GenServer.call(__MODULE__, :e_stop, 5000)
end
@doc """
Gets events from the GenEvent server (pid)
"""
def get_events(pid) do
events = GenEvent.call(pid, BotCommandManager, :events)
for event <- events do
Process.sleep(150)
check_busy
BotStatus.busy true
do_handle(event)
Process.sleep(50)
RPCMessageHandler.send_status
end
get_events(pid)
end
defp check_busy do
case BotStatus.busy? do
true -> check_busy
false -> :ok
end
end
def notify(event) do
GenServer.cast(__MODULE__, {:add_event, event})
end
###
## This might not be the best implementation.
## PROBLEM: There is a small chance for the bot to be out of sync from the
## Arduino.
##
## EXAMPLE: If Farmbot recieves a command and sends it properly over UART
## but the Arduino decides for any reason to not accept that command,
## Farmbot and the frontend think the bot is at one position
## and the actual bot is at a different position.
###
defp do_handle({:home_x, {speed}}) do
Logger.info("HOME X")
SerialMessageManager.sync_notify( {:send, "F11"} )
end
defp do_handle({:home_y, {speed}}) do
Logger.info("HOME Y")
SerialMessageManager.sync_notify( {:send, "F12"} )
end
defp do_handle({:home_z, {speed}}) do
Logger.info("HOME Z")
SerialMessageManager.sync_notify( {:send, "F13"} )
end
# These need to be "safe" commands. IE they shouldnt crash anythin.
defp do_handle({:write_pin, {pin, value, mode}}) do
Logger.info("WRITE_PIN " <> "F41 P#{pin} V#{value} M#{mode}")
SerialMessageManager.sync_notify( {:send, "F41 P#{pin} V#{value} M#{mode}"} )
end
defp do_handle({:move_absolute, {x,y,z,_s}}) do
Logger.info("MOVE_ABSOLUTE " <> "G00 X#{x} Y#{y} Z#{z}")
SerialMessageManager.sync_notify( {:send, "G00 X#{x} Y#{y} Z#{z}"} )
end
defp do_handle({:read_param, param}) do
Logger.info("READ_PARAM "<> "#{param}")
SerialMessageManager.sync_notify({:send, "F21 P#{param}" })
end
defp do_handle({:read_pin, {pin, mode}}) do
Logger.info("READ PIN "<> "#{pin}")
SerialMessageManager.sync_notify({:send, "F42 P#{pin} M#{mode}" })
end
defp do_handle({:update_param, {param, value}}) do
Logger.info("UPDATE PARAM " <> "#{param} #{value}")
SerialMessageManager.sync_notify({:send, "F22 P#{param} V#{value}"})
end
defp do_handle({method, params}) do
Logger.debug("Unhandled method: #{inspect method} with params: #{inspect params}")
end
# Unhandled event. Probably not implemented if it got this far.
defp do_handle(event) do
Logger.debug("[Command Handler] (Probably not implemented) Unhandled Event: #{inspect event}")
end
end

View File

@ -0,0 +1,24 @@
defmodule BotCommandManager do
use GenEvent
require Logger
def start_link() do
GenEvent.start_link([])
end
# add event to the log
def handle_event({event, params}, events) do
{:ok, [{event, params} | events]}
end
def handle_event(:e_stop, _events) do
# Destroy the event queue
{:ok, []}
end
# dump teh current events to be handled
def handle_call(:events, events) do
{:ok, Enum.reverse(events), []}
end
end

View File

@ -0,0 +1,16 @@
defmodule BotCommandSupervisor do
@moduledoc """
Supervises Bot Commands
"""
def start_link(_) do
import Supervisor.Spec
children = [
worker(BotCommandHandler, [[]])
]
Supervisor.start_link(children, strategy: :one_for_one, name: __MODULE__)
end
def init(_) do
{:ok, %{}}
end
end

View File

@ -0,0 +1,162 @@
defmodule Command do
require Logger
@moduledoc """
BotCommands.
"""
@doc """
EMERGENCY STOP
"""
def e_stop do
UartHandler.send("E")
Process.exit(Process.whereis(BotCommandHandler), :kill)
:ok
end
@doc """
Home All (TODO: this might be broken)
"""
def home_all(speed) do
Logger.info("HOME ALL")
# SerialMessageManager.sync_notify( {:send, "G28"} )
Command.move_absolute(0, 0, 0, speed)
end
@doc """
Home x
I dont think anything uses these.
"""
def home_x(speed) do
BotCommandHandler.notify({:home_x, speed})
end
@doc """
Home y
"""
def home_y(speed) do
BotCommandHandler.notify({:home_y, speed})
end
@doc """
Home z
"""
def home_z(speed) do
BotCommandHandler.notify({:home_z, speed})
end
@doc """
Writes a pin high or low
"""
def write_pin(pin, value, mode \\ "1")
def write_pin(pin, value, mode) do
BotStatus.set_pin(pin, value)
BotCommandHandler.notify({:write_pin, {pin, value, mode}})
end
@doc """
Moves to (x,y,z) point.
Sets the bot status to given coords
replies to the mqtt message that caused it (if one exists)
adds the move to the command queue.
"""
def move_absolute(x \\ 0,y \\ 0,z \\ 0,s \\ 100)
def move_absolute(x, y, z, s) when x >= 0 and y >= 0 do
BotStatus.set_pos(x,y,z)
BotCommandHandler.notify({:move_absolute, {x,y,z,s}})
end
# When both x and y are negative
def move_absolute(x, y, z, s) when x < 0 and y < 0 do
BotStatus.set_pos(0,0,z)
BotCommandHandler.notify({:move_absolute, {0,0,z,s}})
end
# when x is negative
def move_absolute(x, y, z, s) when x < 0 do
BotStatus.set_pos(0,y,z)
BotCommandHandler.notify({:move_absolute, {0,y,z,s}})
end
# when y is negative
def move_absolute(x, y, z, s ) when y < 0 do
BotStatus.set_pos(x,0,z)
BotCommandHandler.notify({:move_absolute, {x,0,z,s}})
end
@doc """
Gets the current position
then pipes into move_absolute
"""
def move_relative(e)
def move_relative({:x, s, move_by}) when is_integer move_by do
[x,y,z] = BotStatus.get_current_pos
move_absolute(x + move_by,y,z,s)
end
def move_relative({:y, s, move_by}) when is_integer move_by do
[x,y,z] = BotStatus.get_current_pos
move_absolute(x,y + move_by,z,s)
end
def move_relative({:z, s, move_by}) when is_integer move_by do
[x,y,z] = BotStatus.get_current_pos
move_absolute(x,y,z + move_by,s)
end
# This is a funky one. Only used in sequences right now.
def move_relative(%{x: x_move_by, y: y_move_by, z: z_move_by, speed: speed})
when is_integer x_move_by and
is_integer y_move_by and
is_integer z_move_by
do
[x,y,z] = BotStatus.get_current_pos
move_absolute(x + x_move_by,y + y_move_by,z + z_move_by, speed)
end
@doc """
Used when bootstrapping the bot.
Reads pins 0-13 in digital mode.
"""
def read_all_pins do
spawn fn -> Enum.each(0..13, fn pin -> Command.read_pin(pin) end) end
end
@doc """
Used when bootstrapping the bot.
Reads all the params.
"""
def read_all_params do
rel_params = [0,11,12,13,21,22,23,
31,32,33,41,42,43,51,
52,53,61,62,63,71,72,73]
spawn fn -> Enum.each(rel_params, fn param -> Command.read_param(param) end ) end
end
@doc """
Reads a pin value.
mode: 1 = digital.
mode: 0 = analog.
"""
def read_pin(pin, mode \\ 0) do
BotCommandHandler.notify({:read_pin, {pin, mode} })
end
@doc """
Reads a param. Needs the integer version of said param.
"""
def read_param(param) when is_integer param do
BotCommandHandler.notify({:read_param, param })
end
@doc """
Update a param. Param needs to be an integer.
"""
def update_param(param, value) when is_integer param do
BotCommandHandler.notify({:update_param, {param, value} })
Command.read_param(param)
end
def update_param(nil, _value) do
{:error, "Unknown param"}
end
end

View File

@ -1,59 +0,0 @@
require_relative 'telemetry_message'
module FBPi
# This class wraps around the FB::Arduino class (farmbot-serial gem) to add
# extra functionality that is application specific / not available in
# farmbot-serial.
class BotDecorator < SimpleDelegator
attr_accessor :status_storage, :mesh, :rest_client
def self.build(status_storage,
mesh,
rest,
arduino_klass = FB::Arduino,
serial_obj = default_serial_object)
bot = self.new(arduino_klass.new(serial_port: serial_obj))
bot.status_storage, bot.mesh, bot.rest_client = status_storage, mesh, rest
bot
end
def self.default_serial_object
begin
return FB::DefaultSerialPort.new(serial_port)
rescue TypeError => e
raise "\n\nSomething went wrong while connecting to the arduino."\
" Usually, this is a sign that the USB cable is broke or unplugged.\n"
end
end
def self.serial_port
FBPi::Settings.serial_ports.detect { |f| File.exists?(f) }
end
def bootstrap
ColdStart.run!(bot: self)
end
# Called when the socket connection is ready. Go nuts!
def ready
log "Online at #{Time.now}"
end
def log(message, priority = 'low')
mesh.emit '*', id: nil, result: TelemetryMessage.build(message.to_s, status: self.status.to_h)
end
def emit_changes
mesh.emit '*', id: nil, result: FBPi::ReportBotStatus.run!(bot: self)
end
# This method violates intergalactic law.
def template_variables
status
.to_h
.merge('time' => Time.now.to_i)
.reduce({}){ |a, (b, c)| a[b.to_s.downcase] = c; a}
.tap { |h| h['pins'].map { |k, v| h["pin#{k}"] = v.to_s } }
end
end
end

View File

@ -0,0 +1,151 @@
defmodule BotStatus do
use GenServer
require Logger
def init(_) do
# MAKE SURE THE STATUS STAYS FLAT. YOU WILL THANK YOURSELF LATER.
initial_status = %{ x: 0,y: 0,z: 0,speed: 10,
version: Fw.version,
busy: true,
last_sync: -1,
movement_axis_nr_steps_x: 222,
movement_axis_nr_steps_y: 222,
movement_axis_nr_steps_z: 222 }
{ :ok, initial_status }
end
def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end
def get_status do
GenServer.call(__MODULE__, {:get_status}, 90000)
end
def handle_call({:get_status}, _from, current_status) do
{:reply, current_status, current_status}
end
def handle_call({:get_busy}, _from, current_status ) do
{:reply, current_status.busy, current_status}
end
def handle_call(:get_controller_version, _from, current_status) do
{:reply, current_status.version, current_status }
end
def handle_call({:get_pin, pin}, _from, current_status) when is_bitstring(pin) do
pin = Map.get(current_status, "pin"<>pin, -1)
{:reply, pin, current_status}
end
def handle_cast({:set_pin, pin, value}, current_status) when is_bitstring(pin) and is_integer(value) do
{:noreply, Map.put(current_status, "pin"<>pin, value)}
end
def handle_cast({:set_param, param, value}, current_status)
when is_bitstring(param) and
is_integer(value)
do
{:noreply, Map.put(current_status, param, value)}
end
def handle_cast({:set_busy, b}, current_status ) when is_boolean b do
{:noreply, Map.put(current_status, :busy, b)}
end
def handle_cast({:set_pos, x,y,z}, current_status)
when is_integer x and
is_integer y and
is_integer z do
new_status = Map.put(current_status, :x, x)
|> Map.put(:y, y)
|> Map.put(:z, z)
{:noreply, new_status}
end
def handle_cast({:set_end_stop, _stop, _value}, current_status) do
#TODO: this?
# Logger.debug("EndStop reporting is TODO")
{:noreply, current_status}
end
# Sets the pin value in the bot's status
def set_pin(pin, value) when is_integer pin and is_integer value do
GenServer.cast(__MODULE__, {:set_pin, Integer.to_string(pin), value})
end
def set_param(param, value) when is_bitstring(param)
and is_integer(value) do
GenServer.cast(__MODULE__, {:set_param, param, value})
end
def set_param(param, value) when is_bitstring(param)
and is_bitstring(value) do
set_param(param, String.to_integer(value))
end
# Gets the pin value from the bot's status
def get_pin(pin) when is_integer pin do
GenServer.call(__MODULE__, {:get_pin, Integer.to_string(pin)})
end
# Sets busy to true or false.
def busy(b) when is_boolean b do
GenServer.cast(__MODULE__, {:set_busy, b})
end
# Gets busy
def busy? do
GenServer.call(__MODULE__, {:get_busy})
end
# All three coords
def set_pos(x,y,z)
when is_integer x and
is_integer y and
is_integer z
do
GenServer.cast(__MODULE__, {:set_pos, x,y,z})
end
# If we only have one coord, get the current pos of the others first.
def set_pos({:x, x}) when is_integer(x) do
[_x,y,z] = get_current_pos
set_pos(x,y,z)
end
def set_pos({:y, y}) when is_integer(y) do
[x,_y,z] = get_current_pos
set_pos(x,y,z)
end
def set_pos({:z, z}) when is_integer(z) do
[x,y,_z] = get_current_pos
set_pos(x,y,z)
end
def set_end_stop({stop, value}) do
#TODO
GenServer.cast(__MODULE__, {:set_end_stop, stop, value})
end
# Get current coords
def get_current_pos do
Enum.map([:x,:y,:z], fn(coord) ->
Map.get(get_status, coord)
end)
end
def get_current_version do
GenServer.call(__MODULE__, :get_controller_version)
end
@doc """
Gets the current value of a param
"""
def get_param(param) when is_integer(param) do
cur_status = BotStatus.get_status
this_param = Gcode.parse_param(param) |> Atom.to_string |> String.Casing.downcase
Map.get(cur_status, this_param)
end
end

View File

@ -0,0 +1,48 @@
defmodule BotSync do
use GenServer
require Logger
def init(_args) do
{:ok, %{token: token, server: server, sequences: [] }}
end
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
def handle_cast(:sync, state) do
resp = HTTPotion.get "#{server}/api/sequences",
[headers: ["Content-Type": "application/json",
"Authorization": "Bearer" <> Map.get(state.token, "encoded")]]
case resp do
%HTTPotion.ErrorResponse{message: reason} ->
Logger.debug("Error Fetching sequences: #{inspect reason}")
{:noreply, Map.update(state, :sequences, [], fn _cur -> [] end)}
_ ->
sequences = Poison.decode!(resp.body)
Logger.debug("Successfully Fetched Sequences")
{:noreply, Map.update(state, :sequences, [], fn _cur -> sequences end)}
end
end
def handle_call(:sequences, _from, state) do
{:reply, Map.get(state, :sequences), state}
end
def sync do
GenServer.cast(__MODULE__, :sync)
end
def sequences do
GenServer.call(__MODULE__, :sequences)
end
defp token do
Auth.fetch_token
end
defp server do
Map.get(token, "unencoded")
|> Map.get("iss")
end
end

View File

@ -1,33 +0,0 @@
require_relative 'schedule_chore'
module FBPi
# A recurring task that happens every ::INTERVAL seconds. Typically used for
# pulling scheduled sequences out of the database and executing them when the
# time is due. Gets fired off by an EventMachine::PeriodicTimer.
class ChoreRunner
INTERVAL = 60 # seconds
attr_accessor :schedules, :bot
def initialize(bot)
@bot = bot
end
def run
schedules.present? ? something : nothing
end
def something
schedules.each { |s| ScheduleChore.run(s, bot) }
end
def nothing
bot.log "Nothing to run this cycle. Waiting an additional #{INTERVAL}"\
" secs."
end
def schedules
# TODO re-write this as an actual SQL query. This does extra SQL queries.
@schedules ||= ( Schedule.where(next_time: nil).to_a + # New
Schedule.where("next_time < ?", Time.now).to_a )# Overdue
end
end
end

View File

@ -1,48 +0,0 @@
require 'ice_cube'
module FBPi
# Command object that uses a schedule, bot and time object to determine if a
# schedule should be:
# 1. performed 2. destroyed (if no occurences left) 3. Re-scheduled (if
# there are still occurences left on the schedule)
class ScheduleChore
attr_accessor :schedule, :bot, :now, :next_time
def initialize(sched, bot, now = Time.now)
@schedule, @bot, @now = sched, bot, now
end
def self.run(sched, bot)
self.new(sched, bot).run
end
def run
if expired?
schedule.destroy!
else
perform_steps
bump_execution_time
end
self
end
def perform_steps
bot.log "Running #{schedule.sequence.name}"
schedule.sequence.steps.each { |s| s.execute(bot) }
end
def bump_execution_time
schedule.update_attributes(next_time: next_time)
end
def expired?
schedule.end_time < now
end
def next_time
@next_time ||= (
rule = IceCube::Rule.send(schedule.time_unit, schedule.repeat)
sched = IceCube::Schedule.new(now) { |o| o.add_recurrence_rule(rule) }
sched.next_occurrence.to_time )
end
end
end

View File

@ -1,45 +0,0 @@
require 'mutations'
require 'securerandom'
module FBPi
# Builds a message suitable for transmission over MeshBlu messaging system.
# Message objects are mostly compatible with the JSONRPC protocol, except that
# they have additional information added to the JSON object related to MeshBlu
# such as "fromUuid". SEE:
# https://github.com/octoblu/meshblu MeshBlu IoT Gateway
# http://json-rpc.org/wiki/specification JSONRPC v 1.0
class BuildMeshMessage < Mutations::Command
required do
duck :message, methods: [:payload]
end
def validate
parse_message
type_check "params", Hash
type_check "method", String
message["id"] ||= SecureRandom.uuid # Just incase...
end
def execute
MeshMessage.new(message.symbolize_keys)
end
private
def parse_message
inputs["message"] = JSON.parse(inputs["message"].payload)
rescue JSON::ParserError => e
add_error :payload, :parse_error, "Message was probably not JSON"
end
def type_check(key, klass)
if (message[key].is_a?(klass))
return true
else
add_error :payload, :key, "Expected #{key} to be a #{klass}"
return false
end
end
end
end

View File

@ -1,60 +0,0 @@
require 'mutations'
require_relative 'fetch_bot_status'
module FBPi
# The Arduino does not have persistent storage on board. There are a
# number of settings that must be set every time the device starts (eg:
# calibration settings). This object is responsible for bootstrapping bot
# settings when the device is powered up.
class ColdStart < Mutations::Command
DEFAULT_PARAMS = { # Default settings for hardware.
MOVEMENT_AXIS_NR_STEPS_X: 222,
MOVEMENT_AXIS_NR_STEPS_Y: 222,
MOVEMENT_AXIS_NR_STEPS_Z: 222 }
required do
duck :bot, methods: [:emit_changes, :log, :mesh, :onchange, :onclose,
:onmessage, :ready, :status, :status_storage]
end
def execute
pull_up_stored_parameters_from_disk
FetchBotStatus.run!(bot: bot)
set_event_handlers
bot
end
def set_event_handlers
# bot.onmessage { |msg| /# not needed anymore, except for debugging #/ }
bot.onchange { |msg| diffmessage(msg) }
bot.onclose { |msg| close(msg) }
end
def pull_up_stored_parameters_from_disk
hash = DEFAULT_PARAMS.merge(bot.status_storage.to_h(:bot))
param_names = FB::Gcode::PARAMETER_DICTIONARY.invert
bot.status.transaction do |s|
hash.each { |k,v|
param_number = param_names.fetch(k, k)
bot.commands.write_parameter(param_number, v) }
end
end
def diffmessage(diff)
bot.status_storage.update_attributes(:bot, diff)
if (diff.keys != [:BUSY])
# Broadcasting busy status changes result in too much network 'noise'.
# We could broadcast bot's busy status, but why?
bot.emit_changes
end
end
def close()
# Offload all persistent variables to file on shutdown.
[:bot, :pi].each do |namespace|
bot.status_storage.update_attributes(namespace, bot.status.to_h)
end
bot.log "Bot offline at #{Time.now}", "high"
EM.stop
end
end
end

View File

@ -1,19 +0,0 @@
require 'active_record'
require 'mutations'
require 'ostruct'
require_relative 'build_mesh_message'
require_relative 'create_step'
require_relative 'create_sequence'
require_relative 'create_schedule'
require_relative 'create_step'
require_relative 'create_plant'
require_relative 'report_bot_status'
require_relative 'fetch_bot_status'
require_relative 'if_statement'
require_relative 'resolve_controller'
require_relative 'send_mesh_response'
require_relative 'sync_bot'
require_relative 'send_message'
require_relative 'read_pin'
require_relative 'cold_start'

View File

@ -1,21 +0,0 @@
require_relative '../models/plant'
module FBPi
# Download all Plant objects (JSON) off of Farmbot Web API, convert them to
# Ruby objects, save them in SQLite.
class CreatePlant < Mutations::Command
required do
string :id
integer :x
integer :y
end
def validate
inputs[:id_on_web_app] = inputs.delete(:id)
end
def execute
Plant.create!(inputs)
end
end
end

View File

@ -1,33 +0,0 @@
require_relative '../models/schedule'
require_relative '../models/sequence'
module FBPi
# Acts as a factory for schedule objects. Used mostly when the RPi is fetching
# JSON schedule objects off of the web app's API (format 1) and needs to
# transform the schedules into a format that is usable by the RPi and storable
# in SQLite (via ActiveRecord)
class CreateSchedule < Mutations::Command
required do
string :start_time
string :id
string :end_time
float :repeat
string :time_unit, in: Schedule::UNITS_OF_TIME
string :sequence_id
end
optional do
string :next_time
end
def validate
inputs[:id_on_web_app] = inputs.delete(:id)
inputs[:sequence] = Sequence.find_by!(id_on_web_app:
inputs.delete(:sequence_id))
end
def execute
Schedule.create!(inputs)
end
end
end

View File

@ -1,17 +0,0 @@
require_relative '../models/sequence'
module FBPi
# Builds a validated sequence (a collection of steps that the bot will
# execute, eg: "Plant tomato seeds")
class CreateSequence < Mutations::Command
required do
string :name
string :id
array(:steps) { model :step, builder: CreateStep, new_records: true }
end
def execute
Sequence.create!(inputs.merge!(id_on_web_app: inputs.delete(:id)))
end
end
end

View File

@ -1,30 +0,0 @@
require_relative '../models/step'
module FBPi
# Factory for new step objects. Mostly used when pulling down steps from the
# Web App's API and transforming that JSON data into SQLite/ActiveRecord
# objects
class CreateStep < Mutations::Command
required do
string :message_type, in: Step::COMMANDS
integer :position
hash :command do
optional do
integer :x, default: nil
integer :y, default: nil
integer :z, default: nil
integer :speed, default: nil
integer :pin, default: nil
integer :mode, default: nil
string :variable, default: nil
string :operator, default: nil
string :sub_sequence_id, default: nil
string :value, default: nil
end
end
end
def execute
Step.new(inputs.merge(inputs["command"]))
end
end
end

View File

@ -1,36 +0,0 @@
require 'mutations'
require 'securerandom'
module FBPi
# Occasionally, the bot will receive trash messages that are not formatted in
# a coherent manner. This object will attempt to recover from failure by
# building an error response and avoiding a crash or unsafe behavior. This
# object operates in a very uncertain environment, so approach with caution.
class DisposeTrashMessage < Mutations::Command
# Nothing is certain, so all inputs are optional with safe defaults.
optional do
duck :id
duck :params
duck :method
end
def execute
be_careful(id, String, :to_s, SecureRandom.uuid)
be_careful(method, String, :to_s, 'unknown')
be_careful(params, Hash, :to_h, {})
MeshMessage.new(method: method,
params: params,
id: id)
end
private
# The most cowardly of all methods. Coerces an object to a class, or cowers
# in fear of the consequences.
def be_careful(object, klass, coersion, default)
object.send(coersion) if object.respond_to?(coersion) # Attempt coercion
object.is_a?(klass) ? object : default # Return coerced value or default.
end
end
end

View File

@ -1,76 +0,0 @@
require 'mutations'
module FBPi
# Excutes a single step in "The Real World(tm)". This command will be called
# multiple times by a single schedule object when it is executed at a
# prescribed time.
class ExecStep < Mutations::Command
class UnsafeCommand < Exception; end
required do
duck :step, methods: [:message_type, :x, :y, :z, :pin, :value, :mode]
duck :bot, methods: [:commands, :current_position]
end
def execute
start = Time.now
self.send(message_key)
finish = Time.now
puts "Executed #{message_key} after #{(finish - start) * 1000.0} ms"
rescue UnsafeCommand; unknown
end
def message_key
msg = step.message_type.to_s
raise UnsafeCommand,
"Unknown sequence step '#{msg}'" if !Step::COMMANDS.include?(msg)
raise UnsafeCommand,
"Step '#{msg}' is allowed, but not yet implemented" if !respond_to?(msg)
msg
end
def move_relative
bot.commands.move_relative(x: (step.x || 0),
y: (step.y || 0),
z: (step.z || 0))
end
def move_absolute
bot.commands.move_absolute(x: step.x || bot.current_position.x,
y: step.y || bot.current_position.y,
z: step.z || bot.current_position.z)
end
def pin_write
bot.commands.write_pin(pin: step.pin, value: step.value, mode: step.mode)
end
def send_message
SendMessage.run! message: step.value, bot: bot
end
def if_statement
params = {
lhs: bot.template_variables[step.variable].to_s,
rhs: step.value.to_s,
operator: step.operator.to_s,
bot: bot,
sequence: Sequence.find_by(id_on_web_app: step.sub_sequence_id)
}
IfStatement.run!(params)
end
def unknown
bot.log("Unknown message #{step.message_type}")
end
def read_pin
ReadPin.run!(bot: bot, pin: step.pin)
end
def wait
bot.commands.wait(step.value.to_f.round.to_i)
end
end
end

View File

@ -1,14 +0,0 @@
require 'mutations'
module FBPi
# Grabs a new auth token from the API.
class FetchAuthToken < Mutations::Command
required do
duck :bot, methods: [:rest_client]
end
def execute
raise "TODO : Build this feature for when tokens expire!"
end
end
end

View File

@ -1,32 +0,0 @@
require 'mutations'
module FBPi
# Typically, the pi does not need to poll the Arduino to know its status
# because updates are broadcast over the serial line when statuses change. The
# exception to this rule is device startup, when there is no data to perform a
# diff on. When this is the case, you can run FetchBotStatus, which polls the
# device for all of its status registers. This command sends as many as 22
# requests down the serial line in one action, so hopefully it can be
# refactored later on. SEE ALSO: ReportBotStatus
class FetchBotStatus < Mutations::Command
RELEVANT_PARAMETERS = [0,11,12,13,21,22,23,31,32,33,41,42,43,51,52,53,
61,62,63,71,72,73]
required do
duck :bot, methods: [:status, :commands]
end
def execute
read_pins
read_parameters
FBPi::ReportBotStatus.run!(bot: bot)
end
def read_pins
0.upto(13) { |pin| bot.commands.read_pin(pin) }
end
def read_parameters
RELEVANT_PARAMETERS.each { |code| bot.commands.read_parameter(code) }
end
end
end

View File

@ -1,32 +0,0 @@
module FBPi
# an if_statement is one of the many `message_type`s that a step can have.
# This command uses relevant information in an if statement to execute a
# sequence, but only if the if statement evaluates to true, using the left
# hand side, right hand side and a set of allowable operators (eg: <, >, !=..)
# Used in the execution of steps.
# Conditionally executes a sequence if `operator(lhs, rhs)` evals to true.
class IfStatement < Mutations::Command
required do
string :lhs
string :rhs
string :operator, in: %w(> < != ==)
model :sequence
duck :bot
end
def execute
sequence.exec(bot) if evaluates_to_true
end
private
def evaluates_to_true
# String eval via liquid templates for safety / sandboxing.
expression = "#{lhs} #{operator} #{rhs}"
test = "{% if #{expression} %}true{% else %}false{% endif %}"
outcome = Liquid::Template.parse(test).render({}).strip == "true"
puts outcome ? "WILL EXECUTE THIS SEQUENCE" : "WONT EXECUTE THIS SEQUENCE"
return outcome
end
end
end

View File

@ -1,20 +0,0 @@
require 'mutations'
require_relative 'send_message'
module FBPi
# Typically used in the performance of a single step within a sequence whose
# message_type == "read_pin". This method only reads the value of pins that
# were REPORTED to the Pi over the serial line. If the pin value has not yet
# been reported by the arduino, the value will be :unknown
class ReadPin < Mutations::Command
required do
integer :pin, min: 0, max: 13
duck :bot, methods: [:commands]
end
def execute
SendMessage.run!(message: "Pin #{pin} is #{bot.status.get_pin(pin).to_s}",
bot: bot)
end
end
end

View File

@ -1,46 +0,0 @@
require 'mutations'
module FBPi
# This class reads bot status information that has been cached by the Pi. This
# should not be confused with FetchBotStatus, which actively requests status
# updates from the bot. This command will return a Hash that has all relevant
# information known about the bot. It's like a serializer, sort of.
class ReportBotStatus < Mutations::Command
required do
duck :bot, methods: [:status, :commands]
end
def execute
{}
.merge(bot_info)
.merge(pin_info)
.merge(pi_info)
.deep_symbolize_keys
end
private
def bot_info
bot
.status
.to_h
.except(:PINS)
.delete_if { |k, v| k.to_s.start_with?("UNKNOWN") }
.map { |k, v| [k.to_s.downcase, v] }
.to_h
end
def pi_info
{ last_sync: bot.status_storage.fetch(:pi, :LAST_SYNC) }
end
def pin_info
[*0..13].inject({}) do |hsh, pin|
val = bot.status.get_pin(pin)
hsh["pin#{pin}".to_sym] = val
hsh
end
end
end
end

View File

@ -1,26 +0,0 @@
Dir['lib/controllers/**/*.rb'].each { |file| load(file) }
module FBPi
# Uses a string such as 'single_command.MOVE RELATIVE' and resolves it to a
# particular controller, or returns UnknownController. Be sure to add new
# controllers and RPC commands to the ROUTES constant.
class ResolveController < Mutations::Command
ROUTES = {
'single_command' => SingleCommandController,
'read_status' => ReadStatusController,
'exec_sequence' => ExecSequenceController,
'sync_sequence' => SyncSequenceController,
'update_calibration' => UpdateCalibrationController,
'unknown' => UnknownController,
}
optional do
string :method, default: ''
end
def execute
ROUTES[method.to_s.split('.').first] || UnknownController
end
end
end

View File

@ -1,32 +0,0 @@
require 'mutations'
require 'securerandom'
module FBPi
# This class emits a JSONRPC compliant response object using a provided mesh
# object.
# For more info see: http://json-rpc.org/wiki/specification#a1.2Response
class SendMeshResponse < Mutations::Command
required do
duck :original_message, methods: [:id]
duck :mesh, methods: [:emit]
string :method
end
optional do
hash(:result, default: {}) do
duck :*, methods: [:to_json]
end
end
def execute
mesh.emit "bot/CHANGE_THIS_PLEASE", output
end
def output
hsh = {id: original_message.id, error: nil, result: nil}
key = (method == "error") ? :error : :result
hsh[key] = result.merge(method: method)
hsh.deep_symbolize_keys # :(
end
end
end

View File

@ -1,28 +0,0 @@
require 'mutations'
require 'liquid'
require_relative '../telemetry_message'
module FBPi
# Sends a message to MeshBlu with optional Liquid Templating. Usually this is
# required to report an event, completion of a schedule or system failures.
class SendMessage < Mutations::Command
Liquid::Template.error_mode = :lax
required do
string :message
duck :bot, methods: [:status, :mesh, :template_variables]
end
def execute
TelemetryMessage.build(template).publish(bot.mesh)
end
private
def template
@template ||= Liquid::Template
.parse(message)
.render(bot.template_variables)
end
end
end

View File

@ -1,34 +0,0 @@
module FBPi
# This Object is responsible for contacting the Farmbot Web App via REST calls
# in order to fetch the latest command execution schedules. This is a
# destructive action that will download all Sequences, Schedules and Steps via
# REST. After fetching the resource, it will destroy all of the local Schedule
# , Step and Sequence objects and assume that the Web App always has the
# latests and "most correct" version of Schedule information.
class SyncBot < Mutations::Command
required { duck :bot, methods: [:rest_client] }
def execute
puts "Attempting to sync now..."
api = bot.rest_client
ActiveRecord::Base.transaction do
[Schedule, Sequence, Step].map(&:destroy_all)
{sequences: api.sequences.fetch.map { |s| CreateSequence.run!(s) }.count,
schedules: api.schedules.fetch.map { |s| CreateSchedule.run!(s) }.count,
plants: api.plants.fetch.map { |s| CreatePlant.run!(s) }.count,
steps: Step.count }.tap { |d| after_sync(d) }
end
puts "Done with sync..."
rescue FbResource::FetchError => e
add_error :web_server, :fetch_error, e.message
rescue => e
add_error :sync_issues, :fetch_error, e.message
end
def after_sync(data)
["Sync completed at #{Time.now}", data].map { |d| bot.log(d) }
bot.status_storage.update_attributes(:pi, LAST_SYNC: Time.now)
bot.emit_changes
end
end
end

View File

@ -1,49 +0,0 @@
require 'mutations'
module FBPi
# Responsible for setting calibration settings for a specific axis. Required
# when changing settings like speed, accelerations, steps_per_mm, etc.
class UpdateCalibration < Mutations::Command
PARAMETER_DICTIONARY = FB::Gcode::PARAMETER_DICTIONARY.invert
required do
duck :bot, methods: [:commands]
hash :settings do
optional do
# Possible values:
# param_version, movement_timeout_x, movement_timeout_y, movement_timeout_z,
# movement_invert_endpoints_x, movement_invert_endpoints_y, movement_invert_endpoints_z,
# movement_invert_motor_x, movement_invert_motor_y, movement_invert_motor_z,
# movement_steps_acc_dec_x, movement_steps_acc_dec_y, movement_steps_acc_dec_z,
# movement_home_up_x, movement_home_up_y, movement_home_up_z, movement_min_spd_x,
# movement_min_spd_y, movement_min_spd_z, movement_max_spd_x, movement_max_spd_y, movement_max_spd_z,
# encoder_enabled_x, encoder_enabled_y, encoder_enabled_z, encoder_missed_steps_max_x,
# encoder_missed_steps_max_y, encoder_missed_steps_max_z, encoder_missed_steps_decay_x,
# encoder_missed_steps_decay_y, encoder_missed_steps_decay_z, movement_axis_nr_steps_x,
# movement_axis_nr_steps_y, movement_axis_nr_steps_z, pin_guard_1_pin_nr, pin_guard_1_time_out,
# pin_guard_1_active_state, pin_guard_2_pin_nr, pin_guard_2_time_out, pin_guard_2_active_state,
# pin_guard_3_pin_nr, pin_guard_3_time_out, pin_guard_3_active_state, pin_guard_4_pin_nr,
# pin_guard_4_time_out, pin_guard_4_active_state, pin_guard_5_pin_nr, pin_guard_5_time_out,
# pin_guard_5_active_state
PARAMETER_DICTIONARY.keys.map(&:downcase).each { |p| integer(p) }
end
end
end
def execute
settings.each do |key, param_value|
key = key.upcase
param_number = PARAMETER_DICTIONARY[key.to_sym]
if bot.status.to_h[key.to_sym] != param_value
# TODO: This belongs inside of write_parameter
# TODO: Move this into farmbot-serial and send PR.
bot.status.transaction { |i| i[key] = param_value }
bot.commands.write_parameter(param_number, param_value)
end
end
ReportBotStatus.run!(bot: bot)
end
end
end

23
lib/controller.ex 100644
View File

@ -0,0 +1,23 @@
defmodule Controller do
# use Application
require Logger
use Supervisor
def start_link(_args) do
import Supervisor.Spec, warn: false
Logger.debug("Starting Controller")
children = [
worker(Auth, [[]]),
supervisor(RPCSupervisor, [[]], restart: :permanent ),
supervisor(BotCommandSupervisor, [[]], restart: :permanent),
worker(BotStatus, [[]] , restart: :permanent ),
supervisor(SerialSupervisor, [[]], restart: :permanent ),
supervisor(MqttSupervisor, [[]], restart: :permanent ),
supervisor(SequenceSupervisor, [[]], restart: :permanent )
# worker(BotSync, [[]] , restart: :permanent )
]
opts = [strategy: :one_for_all, name: Controller.Supervisor]
Supervisor.start_link(children, opts)
end
end

View File

@ -1,28 +0,0 @@
require_relative '../command_objects/send_mesh_response'
# TODO: SimpleCov is not registering hits to this controller, despite the fact
# that tests exist. Investigate later. PRs welcome.
# :nocov:
module FBPi
# The base controller from which all other controllers inherit. Takes in the
# current bot instance, an FBPi::MeshMessage and the current MeshBlu
# connection and routes the JSONRPC request to wherever it needs to go in the
# app.
class AbstractController
attr_reader :message, :bot, :mesh
def initialize(message, bot, mesh)
@message, @bot, @mesh = message, bot, mesh
end
def call
raise "A child of AbstractController is expected to implement #call()"
end
def reply(method, reslt = {})
SendMeshResponse.run!(original_message: message,
mesh: mesh,
method: method,
result: reslt)
end
end
end

View File

@ -1,24 +0,0 @@
require_relative 'abstract_controller'
require_relative '../command_objects/commands'
module FBPi
# Controller that will execute a single Schedule of operations immediately (as
# opposed to scheduling it for execution at a later time).
class ExecSequenceController < AbstractController
def call
sequence.try_sync(bot)
sequence.exec(bot)
reply "exec_sequence", params
rescue Mutations::ValidationException => error
reply "error", error: error.message
end
def params
@params ||= (@message.params || {})
end
def sequence
@sequence ||= CreateSequence.run!(params)
end
end
end

View File

@ -1,11 +0,0 @@
require_relative 'abstract_controller'
require_relative '../command_objects/report_bot_status'
module FBPi
# Responsible for reporting the status of the Bot when it is requested by a
# user
class ReadStatusController < AbstractController
def call
reply "read_status", ReportBotStatus.run!(bot: bot)
end
end
end

View File

@ -1,76 +0,0 @@
require_relative 'abstract_controller'
module FBPi
# This controller will safely execute a subset of whitelisted commands when
# requested. This is similar to ExecSequenceController, except that rather
# than execute an entire sequence of steps, it executes one single command,
# such as moving a single axis to a specific coord.
class SingleCommandController < AbstractController
# TODO: Reduce duplication in this controller.
attr_reader :cmd, :key
AVAILABLE_ACTIONS = { "move relative" => :move_relative,
"move absolute" => :move_absolute,
"unknown" => :unknown,
"home x" => :home_x,
"home y" => :home_y,
"home z" => :home_z,
"home all" => :home_all,
"pin write" => :pin_write,
"emergency stop" => :emergency_stop, }
def call
# "single_command.MOVE RELATIVE" ==> "move relative"
@key = message.method.to_s.split('.').last.downcase.gsub("_", " ")
action = AVAILABLE_ACTIONS.fetch(key, :unknown).to_sym
@cmd = message.params
send(action)
reply 'single_command', ReportBotStatus.run!(bot: bot)
end
def move_relative
bot.commands.move_relative x: cmd['x'], y: cmd['y'], z: cmd['z']
end
def move_absolute
bot.commands.move_absolute x: cmd['x'], y: cmd['y'], z: cmd['z']
end
def home_x
bot.commands.home_x
end
def home_y
bot.commands.home_y
end
def home_z
bot.commands.home_z
end
def home_all
bot.commands.home_all
end
def pin_write
# write_pin(pin: 13, value: 1, mode: 0)
bot.commands.write_pin(pin: cmd['pin'],
value: cmd['value1'],
mode:cmd.fetch('mode', 0))
end
def emergency_stop
bot.commands.emergency_stop
end
def unknown
case key
when 'single command', nil then msg = 'NULL'
else; msg = key
end
raise "Unknown message '#{ msg }'. Most likely, the "\
"command has not been implemented or does not exist. Try: "\
"#{AVAILABLE_ACTIONS.keys.join(', ')}"
end
end
end

View File

@ -1,17 +0,0 @@
require_relative 'abstract_controller'
require 'farmbot-resource'
require_relative '../command_objects/commands'
module FBPi
# This controller will destroy all Schedules, Sequences and Steps on the bot
# and replace them with the most recent version that exists remotely on the
# Web App.
class SyncSequenceController < AbstractController
def call
SyncBot.run!(bot: bot)
reply "sync_sequence"
rescue FbResource::FetchError => error
reply "error", error: error.message, hint: "The Webapp is having issues"
end
end
end

View File

@ -1,14 +0,0 @@
require_relative 'abstract_controller'
module FBPi
# Just a controller that gets called when a user tries to access a nonexistant
# controller name.
class UnknownController < AbstractController
def call
really_long_message = "You tried to send a message with a `method` " +
"property of `#{message.method || 'null'}`, but " +
"that's not a valid options."
reply "error", error: really_long_message
end
end
end

View File

@ -1,13 +0,0 @@
require_relative 'abstract_controller'
require_relative '../command_objects/update_calibration'
module FBPi
# Used to remotely set one of the many possible "parameter" variables inside
# of the Arduino. Useful for calibration. SEE: FBPi::UpdateCalibration
class UpdateCalibrationController < AbstractController
def call
reply "calibrate_axis",
UpdateCalibration.run!(bot: bot, settings: message.params)
end
end
end

44
lib/downloader.ex 100644
View File

@ -0,0 +1,44 @@
defmodule Downloader do
def download_and_install_update(url) do
RPCMessageHandler.log("Downloading an Update!")
run(url, "/tmp/update.fw") |> Nerves.Firmware.upgrade_and_finalize
RPCMessageHandler.log("Going down for update. See you soon!")
Nerves.Firmware.reboot
end
def run(url, dl_file) when is_bitstring url do
HTTPotion.get url, stream_to: self, timeout: :infinity
receive_data(total_bytes: :unknown, data: "", dl_path: dl_file)
end
defp receive_data(total_bytes: total_bytes, data: data, dl_path: path) do
receive do
%HTTPotion.AsyncHeaders{headers: h} ->
{total_bytes, _} = h[:"Content-Length"] |> Integer.parse
IO.puts "Let's download #{mb total_bytes}"
receive_data(total_bytes: total_bytes, data: data, dl_path: path)
%HTTPotion.AsyncChunk{chunk: new_data} ->
accumulated_data = data <> new_data
accumulated_bytes = byte_size(accumulated_data)
percent = accumulated_bytes / total_bytes * 100 |> Float.round(2)
IO.puts "#{percent}% (#{mb accumulated_bytes})"
receive_data(total_bytes: total_bytes, data: accumulated_data, dl_path: path)
%HTTPotion.AsyncEnd{} ->
File.write!(path, data)
IO.puts "All downloaded! See: #{path}"
path
end
end
defp mb(bytes) do
number = bytes / 1_048_576 |> Float.round(2)
"#{number} MB"
end
end

View File

@ -1,48 +0,0 @@
require 'farmbot-serial'
require_relative 'messaging/mqtt'
require_relative 'messaging/message_handler'
require_relative 'chores/chore_runner'
require_relative 'models/status_storage.rb'
require_relative 'bot_decorator'
require_relative 'settings'
require_relative 'rest_client'
ActiveRecord::Base.establish_connection(
:adapter => 'sqlite3',
:database => './db/db.sqlite3'
)
class FarmBotPi
attr_accessor :mqtt, :bot, :handler, :runner, :status_storage
WEBAPP_URL = FBPi::Settings.webapp_url
def initialize
@rest_client = FBPi::RPiRestClient.new(WEBAPP_URL)
uuid = @rest_client.device.current["uuid"]
token = @rest_client.config.token
@mqtt = MQTTAdapter.new(uuid, token)
@status_storage = FBPi::StatusStorage.new("db/bot_status.pstore")
@bot = FBPi::BotDecorator.build(@status_storage, @mqtt, @rest_client)
end
def start
EM.run do
bot.bootstrap
mqtt.connect { |msg| mqttmessage(msg) }
FB::ArduinoEventMachine.connect(bot)
start_chore_runner
end
end
def mqttmessage(msg)
puts msg.payload
FBPi::MessageHandler.call(msg, bot, mqtt)
end
def start_chore_runner
EventMachine::PeriodicTimer.new(FBPi::ChoreRunner::INTERVAL) do
# TODO: Add chore to check validity of session token / refresh as needed.
FBPi::ChoreRunner.new(bot).run
end
end
end

33
lib/fw.ex 100644
View File

@ -0,0 +1,33 @@
defmodule Fw do
require Logger
use Supervisor
@target System.get_env("NERVES_TARGET") || "rpi3"
@version Path.join(__DIR__ <> "/..", "VERSION")
|> File.read!
|> String.strip
def init(_args) do
children = [
Plug.Adapters.Cowboy.child_spec(:http, MyRouter, [], [port: 4000]),
supervisor(NetworkSupervisor, [[]], restart: :permanent),
supervisor(Controller, [[]], restart: :permanent)
]
opts = [strategy: :one_for_all, name: Fw]
supervise(children, opts)
end
def start(_type, args) do
Logger.debug("Starting Firmware on Target: #{@target}")
Supervisor.start_link(__MODULE__, args)
end
def version do
@version
end
def factory_reset do
File.rm("/root/secretes.txt")
File.rm("/root/network.config")
Nerves.Firmware.reboot
end
end

View File

@ -0,0 +1,224 @@
alias Experimental.{GenStage}
defmodule RPCMessageHandler do
use GenStage
require Logger
@transport Application.get_env(:json_rpc, :transport)
@update_server Application.get_env(:fb, :update_server)
@doc """
This is where all JSON RPC messages come in.
Currently only from Mqtt, but is technically transport agnostic.
Right now we set @transport to MqttHandler, but it could technically be
In config and set to anything that can emit and recieve JSON RPC messages.
"""
def start_link() do
GenStage.start_link(__MODULE__, :ok)
end
def init(_args) do
{:consumer, :ok, subscribe_to: [RPCMessageManager]}
end
def handle_events(events, _from, state) do
for event <- events do
handle_rpc(event)
end
{:noreply, [], state}
end
# JSON RPC RESPONSE
def ack_msg(id) when is_bitstring(id) do
Poison.encode!(
%{id: id,
error: nil,
result: %{"OK" => "OK"} })
end
# JSON RPC RESPONSE ERROR
def ack_msg(id, {name, message}) when is_bitstring(id) and is_bitstring(name) and is_bitstring(message) do
IO.inspect({name, message})
Poison.encode!(
%{id: id,
error: %{name: name,
message: message },
result: nil})
end
def log_msg(message) do
Poison.encode!(
%{ id: nil,
method: "log_message",
params: [%{status: BotStatus.get_status,
time: :os.system_time(:seconds),
message: message}] })
end
def personality_msg(message) when is_bitstring(message) do
Poison.encode!(
%{ id: nil,
method: "personality_message",
params: [%{message: message}] })
end
def handle_rpc(%{"method" => method, "params" => params, "id" => id})
when is_list(params) and
is_bitstring(method) and
is_bitstring(id)
do
case do_handle(method, params) do
:ok -> @transport.emit(ack_msg(id))
{:error, name, message} -> @transport.emit(ack_msg(id, {name, message}))
unknown_error -> @transport.emit(ack_msg(id, {"unknown_error", "#{inspect unknown_error}"}))
end
end
def handle_rpc(broken_rpc) do
Logger.debug("Got a broken RPC message!!")
IO.inspect broken_rpc
end
# E STOP
def do_handle("emergency_stop", _) do
Command.e_stop
end
# Home All
def do_handle("home_all", [ %{"speed" => s} ]) when is_integer s do
Command.home_all(s)
end
def do_handle("home_all", params) do
Logger.debug("bad params for home_all")
IO.inspect(params)
{:error, "BAD_PARAMS",
Poison.encode!(%{"speed" => "number"})}
end
# WRITE_PIN
def do_handle("write_pin", [ %{"pin_mode" => 1, "pin_number" => p, "pin_value" => v} ])
when is_integer p and
is_integer v
do
Command.write_pin(p,v,1)
end
def do_handle("write_pin", [ %{"pin_mode" => 0, "pin_number" => p, "pin_value" => v} ])
when is_integer p and
is_integer v
do
Command.write_pin(p,v,0)
end
def do_handle("write_pin", _) do
{:error, "BAD_PARAMS",
Poison.encode!(%{"pin_mode" => "1 or 2", "pin_number" => "number", "pin_value" => "number"})}
end
# Move to a specific coord
def do_handle("move_absolute", [%{"speed" => s, "x" => x, "y" => y, "z" => z}])
when is_integer(x) and
is_integer(y) and
is_integer(z) and
is_integer(s)
do
Command.move_absolute(x,y,z,s)
end
def do_handle("move_absolute", params) do
Logger.debug("bad params for Move Absolute")
IO.inspect params
{:error, "BAD_PARAMS",
Poison.encode!(%{"x" => "number", "y" => "number", "z" => "number", "speed" => "number"})}
end
# Move relative to current x position
def do_handle("move_relative", [%{"speed" => speed,
"x" => x_move_by,
"y" => y_move_by,
"z" => z_move_by}])
when is_integer(speed) and
is_integer(x_move_by) and
is_integer(y_move_by) and
is_integer(z_move_by)
do
Command.move_relative(%{x: x_move_by, y: y_move_by, z: z_move_by, speed: speed})
end
def do_handle("move_relative", _) do
{:error, "BAD_PARAMS",
Poison.encode!(%{"x or y or z" => "number to move by", "speed" => "number"})}
end
# Read status
def do_handle("read_status", _) do
Logger.debug("Reporting Current Status")
send_status
end
def do_handle("check_updates", _) do
resp = HTTPotion.get(@update_server<>"/version.json")
current_version = Fw.version
case resp do
%HTTPotion.ErrorResponse{message: error} ->
{:error, "Check Updates failed", error}
_ ->
json = Poison.decode!(resp.body)
new_version = Map.get(json, "latest") |> Map.get("version")
new_version_url = Map.get(json, "latest") |> Map.get("url")
if(new_version != current_version) do
spawn fn -> Downloader.download_and_install_update(new_version_url) end
end
:ok
end
end
def do_handle("reboot", _ ) do
log("Bot Going down for reboot.")
Nerves.Firmware.reboot
log("Something Weird happened...")
end
def do_handle("power_off", _ ) do
log("Bot Going down. Pls remeber me.")
Nerves.Firmware.poweroff
end
# "{\"update_calibration\", [%{\"movement_home_up_y\" => 0}]}"}
def do_handle("update_calibration", [params]) when is_map(params) do
case Enum.all?(params, fn({param, value}) ->
param_int = Gcode.parse_param(param)
Command.update_param(param_int, value)
end)
do
true -> :ok
false -> {:error, "update_calibration", "Something went wrong."}
end
end
# Unhandled event. Probably not implemented if it got this far.
def do_handle(event, params) do
Logger.debug("[RPC_HANDLER] got valid rpc, but event is not implemented.")
{:error, "Unhandled method", "#{inspect {event, params}}"}
end
@doc """
Shortcut for loggin to teh frontend. Pass it a string, watch it display
"""
def log(message) when is_bitstring(message) do
@transport.emit(log_msg(message))
end
@doc """
Shortcut for a personality message
"""
def pm(message) when is_bitstring(message) do
@transport.emit(personality_msg(message))
end
def send_status do
m = %{id: nil,
method: "status_update",
params: [BotStatus.get_status] }
@transport.emit(Poison.encode!(m))
end
end

View File

@ -0,0 +1,34 @@
alias Experimental.{GenStage}
defmodule RPCMessageManager do
use GenStage
def start_link() do
GenStage.start_link(__MODULE__, :ok, name: __MODULE__)
end
def sync_notify(event, timeout\\5000) do
GenStage.call(__MODULE__, {:notify, event}, timeout)
end
def init(:ok) do
{:producer, {:queue.new, 0}, dispatcher: GenStage.BroadcastDispatcher}
end
def handle_call({:notify, event}, from, {queue, demand}) do
dispatch_events(:queue.in({from, event}, queue), demand, [])
end
def handle_demand(incoming_demand, {queue, demand}) do
dispatch_events(queue, incoming_demand + demand, [])
end
# This is some copy paste magic, not touching it.
defp dispatch_events(queue, demand, events) do
with d when d > 0 <- demand,
{{:value, {from, event}}, queue} <- :queue.out(queue) do
GenStage.reply(from, :ok)
dispatch_events(queue, demand - 1, [event | events])
else
_ -> {:noreply, Enum.reverse(events), {queue, demand}}
end
end
end

View File

@ -0,0 +1,14 @@
defmodule RPCSupervisor do
def start_link(_) do
import Supervisor.Spec
children = [
worker(RPCMessageManager, []),
worker(RPCMessageHandler, [], id: 1)
]
Supervisor.start_link(children, strategy: :one_for_one)
end
def init(_) do
{:ok, %{}}
end
end

View File

@ -1,11 +0,0 @@
module FBPi
# Represents a message as it enters the bot from the outside world. Mostly
# compliant with JSONRPC specification. http://json-rpc.org/wiki/specification
class MeshMessage
attr_accessor :method, :params, :id
def initialize(method:, params: {}, id: '')
@method, @params, @id = method, params, id
end
end
end

View File

@ -1,50 +0,0 @@
require 'json'
require 'time'
require_relative 'mesh_message'
require_relative '../command_objects/build_mesh_message'
require_relative '../command_objects/dispose_trash_message'
require_relative '../command_objects/resolve_controller'
module FBPi
# Every time a message comes in from MeshBlu, a new MessageHandler is created.
# Take a bot, message hash and MeshBlu connection and routes it to a specific
# controller.
class MessageHandler
attr_accessor :message, :bot, :mesh
## general handling messages
def initialize(mqtt_message, bot, mesh)
@bot, @mesh = bot, mesh
@message = BuildMeshMessage.run!(message: mqtt_message)
rescue Mutations::ValidationException => e
puts "BOT WAS UNABLE TO PARSE MALFORMED MESSAGE! DANGER IMMINENT!"
@message = DisposeTrashMessage.run!(mqtt_message)
end
def call
controller_klass = ResolveController.run!(method: message.method)
controller_klass.new(message, bot, mesh).call
rescue Exception => e
send_error(e)
end
# Make a new instance and call() it.
def self.call(message, bot, mesh)
self.new(message, bot, mesh).call
end
def send_error(error)
msg = "#{error.message} @ #{error.backtrace.first}"
bot.log msg
reply 'error', message: error.message, backtrace: error.backtrace
end
def reply(method, reslt = {})
SendMeshResponse.run!(original_message: message,
mesh: mesh,
method: method,
result: reslt)
end
end
end

View File

@ -1,37 +0,0 @@
require 'em/mqtt'
class MQTTAdapter
def initialize(username,
password,
host = FBPi::Settings.mqtt_url,
port = FBPi::Settings.mqtt_port)
@username = username
@password = password
@host = host
@port = port
end
def connect(&blk)
puts "Conencting to #{@host}:#{@port}"
@client = EventMachine::MQTT::ClientConnection.connect(host: @host,
port: @port,
username: @username,
password: @password)
@client.subscribe("bot/#{@username}/request")
@client.receive_callback(&blk)
puts "Connected!"
end
def data(payload)
@client.publish("bot/#{username}/notification", payload.to_json)
end
def emit(_channel, payload)
@client.publish("bot/#{username}/response", payload.to_json)
end
private
attr_reader :username, :password
end

View File

@ -1,3 +0,0 @@
# Stores information about a single organism under the FarmBot's care.
class Plant < ActiveRecord::Base
end

View File

@ -1,7 +0,0 @@
# A schedule will store information about when and how often a Sequence should
# execute.
class Schedule < ActiveRecord::Base
UNITS_OF_TIME = %w(minutely hourly daily weekly monthly yearly)
belongs_to :sequence, dependent: :destroy
validates_presence_of :sequence
end

View File

@ -1,24 +0,0 @@
require_relative "../command_objects/sync_bot"
# References a collection of steps that the bot will execute, eg: "Plant tomato
# seeds". Sequences are stored procedures that are executed in the real world at
# a specified time (through the use of Schedule objects) or immediately.
class Sequence < ActiveRecord::Base
has_many :schedules
has_many :steps, dependent: :destroy
def exec(bot)
steps
.sort{ |a, b| a.position <=> b.position}
.map { |step| step.execute(bot) }
end
def try_sync(bot)
begin
FBPi::SyncBot.run!(bot: bot)
rescue => e
puts 'WARN: Could not sync sequences.'
puts e.class
puts e.message
end
end
end

View File

@ -1,67 +0,0 @@
require 'pstore'
# Farmbot needs a way to persist some variables across reboots.
# We have SQLite, but sometimes its overkill for these use cases.
# One example is the bot's current X, Y, Z coords. We need to know the bot's
# position when powering down, and saving it to SQL is a lot of work for simple
# key/value pairs. This is where PStore (and the StatusStorage class) come in.
# StatusStorage is child of Ruby's `PStore` class, which allows you to create
# hash-like objects that are stored to disk (*.pstore). Farmbot uses that to
# remember settings after reboots. For sanity, settings are put into namespaces.
module FBPi
class StatusStorage < PStore
class InvalidNamespace < Exception; end
NAMESPACES = {
bot: "Used for storage of Arduino specific settings, like the status "\
"register object inside the Arduino.",
pi: "Used for storage of Raspberry-Pi specific settings, such as the "\
"time of :LAST_SYNC, etc.",
misc: "Anything else."
}
# Human readable explanation for use in exceptions
NAMESPACE_EXPLANATIONS = NAMESPACES
.inject("\n") { |a, (k, v)| a += "#{k.inspect} => #{v}\n" }
def initialize(*args)
super
transaction { |i| set_namespaces(i) }
end
# Creates namespaces (hashes) in the store if it has not yet been set.
def set_namespaces(info)
NAMESPACES.keys.each { |k| if info[k] then nil else info[k] = {} end }
end
def update_attributes(namespace = :none, hash)
validate_namespace(namespace)
hash.each do |key, value|
transaction { self[namespace].merge!(key => value) }
end
end
def fetch(namespace = :none, key)
validate_namespace(namespace)
transaction { self[namespace][key] }
end
def to_h(namespace = :none)
validate_namespace(namespace)
transaction do
self[namespace]
.keys
.reduce({}) { |hash, root| hash[root] = self[namespace][root]; hash }
end
end
private
def validate_namespace(namespace)
return if NAMESPACES.keys.include?(namespace)
raise InvalidNamespace, """You tried to access a status_storage namespace\
of '#{namespace}' while accessing `StatusStorage`. Try one of these\
instead:#{NAMESPACE_EXPLANATIONS}""".squeeze(" ")
end
end
end

View File

@ -1,17 +0,0 @@
require_relative '../command_objects/exec_step'
# A step represents one piece of a sequence. For example:
# "Sleep for 3 seconds", "perform an emergency stop", "Move X to 100"
class Step < ActiveRecord::Base
attr_accessor :command
COMMANDS = %w(emergency_stop home_all home_x home_y home_z move_absolute
move_relative pin_write read_parameter read_status write_parameter wait
send_message if_statement read_pin)
belongs_to :sequence
def execute(bot)
FBPi::ExecStep.run!(bot: bot, step: self)
rescue SystemStackError; bot.log("Endless loop in '#{sequence.name}'.")
end
end

View File

@ -0,0 +1,51 @@
defmodule Mqtt.Client do
use Hulaaki.Client
def on_connect(message: message, state: state) do
GenServer.call(state.parent, {:connect, message})
end
def on_connect_ack(message: message, state: state) do
GenServer.call(state.parent, {:connect_ack, message})
end
def on_publish(message: message, state: state) do
GenServer.call(state.parent, {:publish, message})
end
def on_subscribed_publish(message: message, state: state) do
GenServer.call(state.parent, {:subscribed_publish, message})
end
def on_subscribed_publish_ack(message: message, state: state) do
GenServer.call(state.parent, {:subscribed_publish_ack, message})
end
def on_publish_receive(message: message, state: state) do
GenServer.call(state.parent, {:publish_receive, message})
end
def on_publish_release(message: message, state: state) do
GenServer.call(state.parent, {:publish_release, message})
end
def on_publish_complete(message: message, state: state) do
GenServer.call(state.parent, {:publish_complete, message})
end
def on_publish_ack(message: message, state: state) do
GenServer.call(state.parent, {:publish_ack, message})
end
def on_subscribe(message: message, state: state) do
GenServer.call(state.parent, {:subscribe, message})
end
def on_subscribe_ack(message: message, state: state) do
GenServer.call(state.parent, {:subscribe_ack, message})
end
def on_unsubscribe(message: message, state: state) do
GenServer.call(state.parent, {:unsubscribe, message})
end
def on_unsubscribe_ack(message: message, state: state) do
GenServer.call(state.parent, {:unsubscribe_ack, message})
end
def on_ping(message: message, state: state) do
GenServer.call(state.parent, {:ping, message})
end
def on_pong(message: message, state: state) do
GenServer.call(state.parent, {:pong, message})
end
def on_disconnect(message: message, state: state) do
GenServer.call(state.parent, {:disconnect, message})
end
end

View File

@ -0,0 +1,174 @@
defmodule MqttHandler do
require GenServer
require Logger
defp build_last_will_message do
RPCMessageHandler.log_msg("Something TERRIBLE Happened. Bot going offline.")
end
@doc """
"tries to log into mqtt."
"""
def log_in(err_wait_time\\ 10000) do
mqtt_host = Map.get(token, "unencoded") |> Map.get("mqtt")
mqtt_user = Map.get(token, "unencoded") |> Map.get("bot")
mqtt_pass = Map.get(token, "encoded")
options = [client_id: mqtt_user,
username: mqtt_user,
password: mqtt_pass,
host: mqtt_host,
port: 1883,
timeout: 5000,
keep_alive: 500,
will_topic: "bot/#{bot}/from_device",
will_message: build_last_will_message,
will_qos: 0,
will_retain: 0]
case GenServer.call(MqttHandler, {:log_in, options}) do
{:error, reason} -> Logger.debug("Error connecting. #{inspect reason}")
Process.sleep(err_wait_time)
log_in(err_wait_time + 10000) # increment the sleep time for teh lawls
_ -> :ok
end
end
def init(_args) do
Mqtt.Client.start_link(%{parent: __MODULE__})
end
def start_link(args) do
handler = GenServer.start_link(__MODULE__, args, name: __MODULE__)
log_in
Logger.debug("MQTT ONLINE")
handler
end
def handle_call({:connect, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:connect_ack, _message}, from, client) do
options = [id: 24756, topics: ["bot/#{bot}/from_clients"], qoses: [0]]
spawn fn ->
NetworkSupervisor.set_time # Should NOT be here
Mqtt.Client.subscribe(client, options)
handle_call({:emit, RPCMessageHandler.log_msg("Bot Bootstrapping")}, from, client)
Command.read_all_pins # I'm truly sorry these are here
Command.read_all_params
BotSync.sync
end
keep_connection_alive
{:reply, :ok, client}
end
def handle_call({:publish, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:subscribed_publish, message}, _from, client) do
Map.get(message, :message) |> Poison.decode! |>
RPCMessageManager.sync_notify
{:reply, :ok, client}
end
def handle_call({:subscribed_publish_ack, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:publish_receive, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:publish_release, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:publish_complete, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:publish_ack, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:subscribe, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:subscribe_ack, _message}, from, client) do
Logger.debug("Subscribed.")
handle_call({:emit, RPCMessageHandler.log_msg("Bot Online")}, from, client)
end
def handle_call({:unsubscribe, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:unsubscribe_ack, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:ping, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:disconnect, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:pong, _message}, _from, client) do
{:reply, :ok, client}
end
def handle_call({:log_in, options}, _from, client) do
case Mqtt.Client.connect(client, options) do
{:error, reason} -> {:reply, {:error, reason}, client}
_ -> {:reply, :ok, client}
end
end
def handle_call({:emit, message}, _from, client) when is_bitstring(message) do
options = [ id: 1234,
topic: "bot/#{bot}/from_device",
message: message,
dup: 1, qos: 1, retain: 0]
spawn fn -> Mqtt.Client.publish(client, options) end
{:reply, :ok, client}
end
def handle_call(thing, _from, client) do
Logger.debug("Unhandled Thing #{inspect thing}")
{:reply, :ok, client}
end
def handle_cast(event, state) do
Logger.debug("#{inspect event}")
{:noreply, state}
end
def handle_info({:keep_alive}, client) do
Mqtt.Client.ping(client)
keep_connection_alive
{:noreply, client}
end
def emit(message) when is_bitstring(message) do
GenServer.call(__MODULE__, {:emit, message})
end
defp bot do
Map.get(token, "unencoded") |> Map.get("bot")
end
defp token do
Auth.fetch_token
end
defp keep_connection_alive do
Process.send_after(__MODULE__, {:keep_alive}, 15000)
end
def terminate(reason, _state) do
Logger.debug("MqttHandler died. #{inspect reason}")
end
end

View File

@ -0,0 +1,14 @@
defmodule MqttSupervisor do
require Logger
use Supervisor
def init(_args) do
children = [worker(MqttHandler, [[]], restart: :permanent)]
supervise(children, strategy: :one_for_one, name: __MODULE__)
end
def start_link(args) do
Logger.debug("MQTT INIT")
Supervisor.start_link(__MODULE__, args)
end
end

View File

@ -0,0 +1,21 @@
defmodule Network.EventManager do
use GenEvent
require Logger
def handle_event({:udhcpc, _, :bound, %{ipv4_address: address}}, state) do
on_ip(address)
{:ok, state}
end
def handle_event(_event, state) do
{:ok, state}
end
def on_ip(address) do
Logger.debug("WE ARE CONNECTED")
Wifi.set_connected(true)
Node.stop
full_node_name = "farmbot@#{address}" |> String.to_atom
{:ok, _pid} = Node.start(full_node_name)
end
end

View File

@ -0,0 +1,34 @@
defmodule NetworkSupervisor do
require Logger
use Supervisor
@env Mix.env
def start_link(_args) do
Logger.debug("Starting Network")
Nerves.Networking.setup(:eth0) # eh
children = [ worker(Wifi, [[]]) ]
opts = [strategy: :one_for_all, name: NetworkSupervisor]
Supervisor.start_link(children, opts)
end
def set_time do
case @env do
:prod ->
Logger.debug("Setting time. If it seems to hang here, reboot. Need a better ntp pool.")
System.cmd("ntpd", ["-q",
"-p", "0.pool.ntp.org",
"-p", "1.pool.ntp.org",
"-p", "2.pool.ntp.org",
"-p", "3.pool.ntp.org"])
check_time_set
Logger.debug("Time set.")
:ok
_ -> :ok
end
end
def check_time_set do
if :os.system_time(:seconds) < 1474929 do
check_time_set # wait until time is set
end
end
end

141
lib/network/wifi.ex 100644
View File

@ -0,0 +1,141 @@
defmodule Wifi do
@path Application.get_env(:fb, :ro_path)
@env Mix.env
use GenServer
require Logger
defp load do
case File.read("#{@path}/network.config") do
{:ok, contents} -> {:wpa_supplicant, :erlang.binary_to_term(contents)}
_ -> :nope
end
end
defp save({ssid, password}) do
File.write("#{@path}/network.config", :erlang.term_to_binary({ssid, password}))
end
def init(_args) do
System.cmd("epmd", ["-daemon"])
GenEvent.add_handler(Nerves.NetworkInterface.event_manager(), Network.EventManager, [])
initial_state = load
case initial_state do
{:wpa_supplicant, {ssid, pass}} -> start_wifi_client(ssid, pass)
{:ok, {:wpa, connected: false}}
_ -> start_hostapd_deps(@env) # These only need to be started onece per boot
start_hostapd(@env)
{:ok, :hostapd}
end
end
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
defp start_wifi_client(ssid, pass) when is_bitstring(ssid) and is_bitstring(pass) do
spawn_link fn -> Nerves.InterimWiFi.setup "wlan0", ssid: ssid, key_mgmt: :"WPA-PSK", psk: pass end
end
# Blatently ripped off from @joelbyler
# https://github.com/joelbyler/elixir_conf_chores/blob/f13298f9185b850fdfaad0448f03a03b3067a85c/apps/firmware/lib/firmware.ex
defp start_hostapd_deps(:prod) do
System.cmd("httpd",["-p", "80", "-h", "/www"]) |> print_cmd_result
System.cmd("ip", ["link", "set", "wlan0", "up"]) |> print_cmd_result
System.cmd("ip", ["addr", "add", "192.168.24.1/24", "dev", "wlan0"]) |> print_cmd_result
System.cmd("dnsmasq", ["--dhcp-lease", "/root/dnsmasq.lease"]) |> print_cmd_result
end
defp start_hostapd_deps(_) do
nil
end
defp start_hostapd(:prod) do
System.cmd("hostapd", ["-B", "-d", "/etc/hostapd/hostapd.conf"]) |> print_cmd_result
end
defp start_hostapd(_) do
nil
end
defp print_cmd_result({_message, 0}) do
# IO.puts message
nil
end
defp print_cmd_result({message, err_no}) do
IO.puts "ERROR (#{err_no}): #{message}"
end
def connect(ssid, pass) do
save({ssid, pass})
GenServer.cast(__MODULE__, {:connect, ssid, pass})
end
def set_connected(con) when is_boolean(con) do
GenServer.cast(__MODULE__, {:connected, con})
end
def scan do
GenServer.call(__MODULE__, :scan, 10000)
end
def connected? do
GenServer.call(__MODULE__, :am_i_connected)
end
def handle_cast({:connect, ssid, pass}, :hostapd) do
Logger.debug("trying to switch from hostapd to wpa_supplicant ")
System.cmd("sh", ["-c", "killall hostapd"]) |> print_cmd_result
System.cmd("ip", ["link", "set", "wlan0", "down"]) |> print_cmd_result
System.cmd("ip", ["addr", "del", "192.168.24.1/24", "dev", "wlan0"]) |> print_cmd_result
System.cmd("ip", ["link", "set", "wlan0", "up"]) |> print_cmd_result
start_wifi_client(ssid, pass)
{:noreply, :hostapd}
end
def handle_cast({:connect, ssid, pass}, {:wpa, connected: _con}) do
start_wifi_client(ssid, pass)
{:noreply,{ssid, pass, connected: false}}
end
def handle_cast({:connected, con}, {:wpa, connected: _old}) do
{:noreply, {:wpa, connected: con} }
end
def handle_cast({:connected, con}, :hostapd) do
{:noreply, {:wpa, connected: con} }
end
def handle_call(:scan, _from, {:wpa, connected: are_connected} ) do
{hc, 0} = System.cmd("iw", ["wlan0", "scan", "ap-force"])
{:reply, hc |> clean_ssid, {:wpa, connected: are_connected}}
end
def handle_call(:scan, _from, :hostapd ) do
{hc, 0} = System.cmd("iw", ["wlan0", "scan", "ap-force"])
{:reply, hc |> clean_ssid, :hostapd}
end
# IF hostapd always no.
def handle_call(:am_i_connected, _from, :hostapd) do
case @env do
:prod -> {:reply, false, :hostapd}
_ -> {:reply, true, :hostapd}
end
end
def handle_call(:am_i_connected, _from, {:wpa, connected: are_connected}) do
{:reply, are_connected, {:wpa, connected: are_connected}}
end
def handle_call(:get_state, _crom, state) do
{:reply, state, state}
end
defp clean_ssid(hc) do
hc
|> String.replace("\t", "")
|> String.split("\n")
|> Enum.filter(fn(s) -> String.contains?(s, "SSID") end)
|> Enum.map(fn(z) -> String.replace(z, "SSID: ", "") end)
end
end

View File

@ -0,0 +1,40 @@
defmodule Farmbot do
use GenServer
def init(args) do
resp = HTTPotion.get(args)
Process.send_after(__MODULE__, :emit_message, 10000)
case resp do
%HTTPotion.ErrorResponse{message: _} -> {:ok, %{}}
_ -> {:ok, Poison.decode!(resp.body)}
end
end
def start_link(args) do
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
def get_state do
GenServer.call(__MODULE__, :get_state)
end
def download_personality(url) do
resp = HTTPotion.get(url)
case resp do
%HTTPotion.ErrorResponse{message: _} -> {:error, :could_not_get_personality}
_ -> GenServer.call(__MODULE__, {:update_personality, Poison.decode!(resp.body) })
end
end
def handle_call(:get_state, _from, state) do
{:reply, state, state}
end
def handle_call({:update_personality, new_personality_map}, _from, _old_personality_map) do
{:reply, :ok, new_personality_map}
end
def handle_info(:emit_message,personality_map) do
{:noreply, personality_map}
end
end

View File

@ -1,36 +0,0 @@
# Wraps around the Farmbot REST client gem to provide RPi specific functions.
require 'farmbot-resource'
require_relative 'secret_file'
module FBPi
class RPiRestClient < FbResource::Client
CREDENTIALS_FILE = 'db/secrets.txt'
TRANSLATIONS = {
"sub"=> "email",
"iat"=> "token_issued_at",
"jti"=> "token_id",
"iss"=> "webapp_url",
"exp"=> "token_expiration",
"mqtt"=>"mqtt_url",
"bot"=> "device_uuid"
}
def initialize(url)
public_key = FBPi::RPiRestClient.public_key(url)
credentials = SecretFile.new(public_key, CREDENTIALS_FILE).cipher_text
token = FbResource::Client.get_token(credentials: credentials, url: url)
unpack_token_settings(token)
super() { |c| config.token = token }
end
# To prevent adding a bunch of setup steps, we pull most config out of the
# API token. That way users don't need to worry about pointing to the right
# servers or updating settings.
def unpack_token_settings(token)
settings = JSON.parse(Base64.decode64(token.split(".")[1]))
TRANSLATIONS.each do |key, val|
FBPi::Settings[val] = settings[key] || "#{key} not set."
end
FBPi::Settings.save
end
end
end

View File

@ -1,41 +0,0 @@
# a "Write only" file dump for sharing secrets. The web server has the private
# key and is the only party able to read the file.
require 'securerandom'
require 'base64'
class SecretFile
attr_reader :public_key, :file_path, :cipher_text
def initialize(public_key, file_path)
@public_key, @file_path = public_key, file_path
end
def self.save_password(public_key, email, password)
self
.new(public_key, "db/secrets.txt")
.write({email: email,
password: password})
end
def write(payload_obj)
# Add a UUID for comparison / debugging purposes
text = payload_obj.merge!(id: SecureRandom.uuid, version: 1).to_json
self.cipher_text = text
cipher_text
end
def cipher_text=(text)
text = Base64.encode64(public_key.public_encrypt(text))
File.open(file_path, 'w') { |f| f.write(text) }
@cipher_text = text
end
def cipher_text
if @cipher_text || File.file?(file_path)
return @cipher_text ||= File.read(file_path)
else
raise "Attempted to access bot credentials, but none were found. " \
"Did you run `ruby setup.rb`?"
end
end
end

View File

@ -0,0 +1,95 @@
defmodule Sequence do
@moduledoc """
LOL
"""
use GenServer
require Logger
require Kernel
def init(_) do
{:ok, %{}}
end
def start_link(_) do
GenServer.start_link(__MODULE__, %{}, name: __MODULE__)
end
def handle_cast({:add_step, {position, function}}, steps) do
{:noreply, Map.update(steps, position, function, fn _x -> function end)}
end
def handle_call({:get_steps}, _from, steps) do
{:reply, steps, steps}
end
def handle_call({:clean_steps}, _from, steps) do
{:reply, steps, %{}}
end
def handle_call({:execute, _id}, _from, steps) do
Logger.debug("#{inspect steps}")
ordered_steps = Enum.sort(steps)
ordered_list = Enum.map(ordered_steps, fn({_pos, step}) -> step end)
Logger.debug("#{inspect ordered_list}")
pid = spawn fn -> Enum.each(ordered_list, fn step -> Kernel.apply(step, []) end) end
{:reply, pid, %{}}
end
def execute(id \\nil) do
GenServer.call(__MODULE__, {:execute, id})
end
# Pattern match available commands
def add_step(step,id \\ nil)
def add_step(%{"command" => command, "message_type" => "move_absolute",
"position" => position}, _id) do
#TODO: i think this would allow negative numbers
xpos = String.to_integer(Map.get(command, "x", nil))
ypos = String.to_integer(Map.get(command, "y", nil))
zpos = String.to_integer(Map.get(command, "z", nil))
speed = String.to_integer(Map.get(command, "speed", nil))
GenServer.cast(__MODULE__, {:add_step, {position, fn -> Command.move_absolute(xpos,ypos,zpos,speed) end}})
end
def add_step(%{"command" => %{"speed" => speed,
"x" => x, "y" => y, "z" => z},
"id" => _istep_d,
"message_type" => "move_relative",
"position" => position, "sequence_id" => _seq_id}, _id) do
GenServer.cast(__MODULE__,
{:add_step, {position,
fn -> Command.move_relative(%{x: String.to_integer(x), y: String.to_integer(y), z: String.to_integer(z), speed: speed}) end}})
end
# Write pin (MODE IS NOT WORKING?)
def add_step(%{"command" => %{"mode" => _mode, "pin" => pin, "value" => value}, "message_type" => "pin_write", "position" => position}, _id) do
GenServer.cast(__MODULE__,
{:add_step, {position, fn -> Command.write_pin(String.to_integer(pin), String.to_integer(value),0) end}})
end
# Process.sleep seems to be off by a couple seconds?
def add_step(%{"command" => %{"value" => milis}, "message_type" => "wait", "position" => position}, _id) do
GenServer.cast(__MODULE__,
{:add_step, {position, fn -> Process.sleep(String.to_integer(milis)) end}})
end
def add_step(%{"command" => command, "message_type" => "read_pin", "position" => position}, _id) do
# I dont know what this is supposed to do ???
Logger.debug("add_step: COMMAND: #{inspect command}, POSITION: #{inspect position}")
end
def add_step(step, _id) do
Logger.debug("Unable to add step: #{inspect step}")
end
def add_steps(steps, id) when is_list(steps) do
Enum.each(steps, fn step -> add_step(step, id) end)
end
def get_steps do
GenServer.call(__MODULE__, {:get_steps})
end
def clean_steps do
GenServer.call(__MODULE__, {:clean_steps})
end
end

View File

@ -0,0 +1,33 @@
alias Experimental.{GenStage}
defmodule SequenceHandler do
require IEx
use GenStage
require Logger
def start_link() do
GenStage.start_link(__MODULE__, :ok)
end
def init(:ok) do
# Bootstarp the bot here.
{:consumer, :ok, subscribe_to: [SequenceManager]}
end
def handle_events(events, _from, state) do
for event <- events do
do_handle(event)
end
{:noreply, [], state}
end
def do_handle({:exec_sequence, steps, id}) when is_list(steps) do
Sequence.clean_steps
Sequence.add_steps(steps, id)
Sequence.execute(id)
end
# Unhandled event. Probably not implemented if it got this far.
def do_handle(event) do
Logger.debug("[SequenceHandler] (Probably not implemented) Unhandled Event: #{inspect event}")
end
end

View File

@ -0,0 +1,34 @@
alias Experimental.{GenStage}
defmodule SequenceManager do
use GenStage
def start_link() do
GenStage.start_link(__MODULE__, :ok, name: __MODULE__)
end
def sync_notify(event, timeout \\ 5000) do
GenStage.call(__MODULE__, {:notify, event}, timeout)
end
def init(:ok) do
{:producer, {:queue.new, 0}, dispatcher: GenStage.BroadcastDispatcher}
end
def handle_call({:notify, event}, from, {queue, demand}) do
dispatch_events(:queue.in({from, event}, queue), demand, [])
end
def handle_demand(incoming_demand, {queue, demand}) do
dispatch_events(queue, incoming_demand + demand, [])
end
# This is some copy paste magic, not touching it.
defp dispatch_events(queue, demand, events) do
with d when d > 0 <- demand,
{{:value, {from, event}}, queue} <- :queue.out(queue) do
GenStage.reply(from, :ok)
dispatch_events(queue, demand - 1, [event | events])
else
_ -> {:noreply, Enum.reverse(events), {queue, demand}}
end
end
end

View File

@ -0,0 +1,15 @@
defmodule SequenceSupervisor do
def start_link(_args) do
import Supervisor.Spec
children = [
worker(SequenceManager, []),
worker(SequenceHandler, [], id: 1),
worker(Sequence, [[]])
]
Supervisor.start_link(children, strategy: :one_for_one)
end
def init(_) do
{:ok, %{}}
end
end

160
lib/serial/gcode.ex 100644
View File

@ -0,0 +1,160 @@
defmodule Gcode do
@moduledoc """
Im lazy and didn't want to parse yaml or write macros
"""
def parse_code("G0") do { :move_to_location_at_given_speed_for_axis } end
def parse_code("G1" )do { :move_to_location_on_a_straight_line } end
def parse_code("G28") do { :move_home_all_axis } end
def parse_code("F1" ) do { :dose_amount_of_water_using_time_in_millisecond } end
def parse_code("F2" ) do { :dose_amount_of_water_using_flow_meter_that_measures_pulses } end
def parse_code("F11") do { :home_x_axis } end
def parse_code("F12") do { :home_y_axis } end
def parse_code("F13") do { :home_z_axis } end
def parse_code("F14") do { :calibrate_x_axis } end
def parse_code("F15") do { :calibrate_y_axis } end
def parse_code("F16") do { :calibrate_z_axis } end
def parse_code("F21") do { :read_parameter } end
def parse_code("F22") do { :write_parameter } end
def parse_code("F23") do { :update_parameter_during_calibration } end
def parse_code("F31") do { :read_status } end
def parse_code("F32") do { :write_status } end
def parse_code("F41") do { :set_a_value_on_an_arduino_pin } end
def parse_code("F42") do { :read_a_value_from_an_arduino_pin } end
def parse_code("F43") do { :set_the_mode_of_a_pin_in_arduino } end
def parse_code("F44") do { :set_the_value_v_on_an_arduino_pin } end
def parse_code("F51") do { :set_a_value_on_the_tool_mount_with_i2c } end
def parse_code("F52") do { :read_value_from_the_tool_mount_with_i2c } end
def parse_code("F61") do { :set_the_servo_on_the_pin_to_the_requested_angle } end
def parse_code("F81") do { :report_end_stop } end
def parse_code("F82") do { :report_current_position } end
def parse_code("F83") do { :report_software_version } end
def parse_code("E" ) do { :emergency_stop } end
def parse_code("R0" ) do { :idle } end
def parse_code("R1" ) do { :received } end
def parse_code("R2" ) do { :done } end
def parse_code("R3" ) do { :error } end
def parse_code("R4" ) do { :busy } end
def parse_code("R00") do { :idle } end
def parse_code("R01") do { :received } end
def parse_code("R02") do { :done } end
def parse_code("R03") do { :error } end
def parse_code("R04") do { :busy } end
def parse_code("R21 " <> params) do { :report_parameter_value, params } end
def parse_code("R31") do { :report_status_value } end
def parse_code("R41 " <> params) do { :report_pin_value, params } end
def parse_code("R81 " <> params ) do { :reporting_end_stops, params } end
def parse_code("R82 " <> position) do { :report_current_position, position } end
def parse_code("R83") do { :report_software_version } end
def parse_code("R99 " <> message) do { :debug_message, message } end
def parse_code(code) do {:unhandled_gcode, code} end
def parse_param("0" ) do :PARAM_VERSION end
def parse_param("11" ) do :MOVEMENT_TIMEOUT_X end
def parse_param("12" ) do :MOVEMENT_TIMEOUT_Y end
def parse_param("13" ) do :MOVEMENT_TIMEOUT_Z end
def parse_param("21" ) do :MOVEMENT_INVERT_ENDPOINTS_X end
def parse_param("22" ) do :MOVEMENT_INVERT_ENDPOINTS_Y end
def parse_param("23" ) do :MOVEMENT_INVERT_ENDPOINTS_Z end
def parse_param("31" ) do :MOVEMENT_INVERT_MOTOR_X end
def parse_param("32" ) do :MOVEMENT_INVERT_MOTOR_Y end
def parse_param("33" ) do :MOVEMENT_INVERT_MOTOR_Z end
def parse_param("41" ) do :MOVEMENT_STEPS_ACC_DEC_X end
def parse_param("42" ) do :MOVEMENT_STEPS_ACC_DEC_Y end
def parse_param("43" ) do :MOVEMENT_STEPS_ACC_DEC_Z end
def parse_param("51" ) do :MOVEMENT_HOME_UP_X end
def parse_param("52" ) do :MOVEMENT_HOME_UP_Y end
def parse_param("53" ) do :MOVEMENT_HOME_UP_Z end
def parse_param("61" ) do :MOVEMENT_MIN_SPD_X end
def parse_param("62" ) do :MOVEMENT_MIN_SPD_Y end
def parse_param("63" ) do :MOVEMENT_MIN_SPD_Z end
def parse_param("71" ) do :MOVEMENT_MAX_SPD_X end
def parse_param("72" ) do :MOVEMENT_MAX_SPD_Y end
def parse_param("73" ) do :MOVEMENT_MAX_SPD_Z end
def parse_param("101") do :ENCODER_ENABLED_X end
def parse_param("102") do :ENCODER_ENABLED_Y end
def parse_param("103") do :ENCODER_ENABLED_Z end
def parse_param("111") do :ENCODER_MISSED_STEPS_MAX_X end
def parse_param("112") do :ENCODER_MISSED_STEPS_MAX_Y end
def parse_param("113") do :ENCODER_MISSED_STEPS_MAX_Z end
def parse_param("121") do :ENCODER_MISSED_STEPS_DECAY_X end
def parse_param("122") do :ENCODER_MISSED_STEPS_DECAY_Y end
def parse_param("123") do :ENCODER_MISSED_STEPS_DECAY_Z end
def parse_param("141") do :MOVEMENT_AXIS_NR_STEPS_X end
def parse_param("142") do :MOVEMENT_AXIS_NR_STEPS_Y end
def parse_param("143") do :MOVEMENT_AXIS_NR_STEPS_Z end
def parse_param("201") do :PIN_GUARD_1_PIN_NR end
def parse_param("202") do :PIN_GUARD_1_TIME_OUT end
def parse_param("203") do :PIN_GUARD_1_ACTIVE_STATE end
def parse_param("205") do :PIN_GUARD_2_PIN_NR end
def parse_param("206") do :PIN_GUARD_2_TIME_OUT end
def parse_param("207") do :PIN_GUARD_2_ACTIVE_STATE end
def parse_param("211") do :PIN_GUARD_3_PIN_NR end
def parse_param("212") do :PIN_GUARD_3_TIME_OUT end
def parse_param("213") do :PIN_GUARD_3_ACTIVE_STATE end
def parse_param("215") do :PIN_GUARD_4_PIN_NR end
def parse_param("216") do :PIN_GUARD_4_TIME_OUT end
def parse_param("217") do :PIN_GUARD_4_ACTIVE_STATE end
def parse_param("221") do :PIN_GUARD_5_PIN_NR end
def parse_param("222") do :PIN_GUARD_5_TIME_OUT end
def parse_param("223") do :PIN_GUARD_5_ACTIVE_STATE end
def parse_param(param) when is_integer(param) do
parse_param("#{param}")
end
def parse_param(:PARAM_VERSION) do 0 end
def parse_param(:MOVEMENT_TIMEOUT_X) do 11 end
def parse_param(:MOVEMENT_TIMEOUT_Y) do 12 end
def parse_param(:MOVEMENT_TIMEOUT_Z) do 13 end
def parse_param(:MOVEMENT_INVERT_ENDPOINTS_X) do 21 end
def parse_param(:MOVEMENT_INVERT_ENDPOINTS_Y) do 22 end
def parse_param(:MOVEMENT_INVERT_ENDPOINTS_Z) do 23 end
def parse_param(:MOVEMENT_INVERT_MOTOR_X) do 31 end
def parse_param(:MOVEMENT_INVERT_MOTOR_Y) do 32 end
def parse_param(:MOVEMENT_INVERT_MOTOR_Z) do 33 end
def parse_param(:MOVEMENT_STEPS_ACC_DEC_X) do 41 end
def parse_param(:MOVEMENT_STEPS_ACC_DEC_Y) do 42 end
def parse_param(:MOVEMENT_STEPS_ACC_DEC_Z) do 43 end
def parse_param(:MOVEMENT_HOME_UP_X) do 51end
def parse_param(:MOVEMENT_HOME_UP_Y) do 52end
def parse_param(:MOVEMENT_HOME_UP_Z) do 53end
def parse_param(:MOVEMENT_MIN_SPD_X) do 61end
def parse_param(:MOVEMENT_MIN_SPD_Y) do 62end
def parse_param(:MOVEMENT_MIN_SPD_Z) do 63end
def parse_param(:MOVEMENT_MAX_SPD_X) do 71end
def parse_param(:MOVEMENT_MAX_SPD_Y) do 72end
def parse_param(:MOVEMENT_MAX_SPD_Z) do 73end
def parse_param(:ENCODER_ENABLED_X) do 101 end
def parse_param(:ENCODER_ENABLED_Y) do 102 end
def parse_param(:ENCODER_ENABLED_Z) do 103 end
def parse_param(:ENCODER_MISSED_STEPS_MAX_X) do 111 end
def parse_param(:ENCODER_MISSED_STEPS_MAX_Y) do 112 end
def parse_param(:ENCODER_MISSED_STEPS_MAX_Z) do 113 end
def parse_param(:ENCODER_MISSED_STEPS_DECAY_X) do 121 end
def parse_param(:ENCODER_MISSED_STEPS_DECAY_Y) do 122 end
def parse_param(:ENCODER_MISSED_STEPS_DECAY_Z) do 123 end
def parse_param(:MOVEMENT_AXIS_NR_STEPS_X) do 141 end
def parse_param(:MOVEMENT_AXIS_NR_STEPS_Y) do 142 end
def parse_param(:MOVEMENT_AXIS_NR_STEPS_Z) do 143 end
def parse_param(:PIN_GUARD_1_PIN_NR) do 201 end
def parse_param(:PIN_GUARD_1_TIME_OUT) do 202 end
def parse_param(:PIN_GUARD_1_ACTIVE_STATE) do 203 end
def parse_param(:PIN_GUARD_2_PIN_NR) do 205 end
def parse_param(:PIN_GUARD_2_TIME_OUT) do 206 end
def parse_param(:PIN_GUARD_2_ACTIVE_STATE) do 207 end
def parse_param(:PIN_GUARD_3_PIN_NR) do 211 end
def parse_param(:PIN_GUARD_3_TIME_OUT) do 212 end
def parse_param(:PIN_GUARD_3_ACTIVE_STATE) do 213 end
def parse_param(:PIN_GUARD_4_PIN_NR) do 215 end
def parse_param(:PIN_GUARD_4_TIME_OUT) do 216 end
def parse_param(:PIN_GUARD_4_ACTIVE_STATE) do 217 end
def parse_param(:PIN_GUARD_5_PIN_NR) do 221 end
def parse_param(:PIN_GUARD_5_TIME_OUT) do 222 end
def parse_param(:PIN_GUARD_5_ACTIVE_STATE) do 223 end
def parse_param(param) when is_bitstring(param) do
String.Casing.upcase(param)
|> String.to_atom
|> parse_param
end
def parse_param(_) do nil end
end

View File

@ -0,0 +1,133 @@
alias Experimental.{GenStage}
defmodule GcodeMessageHandler do
use GenStage
require Logger
def start_link() do
GenStage.start_link(__MODULE__, :ok)
end
def init(:ok) do
{:consumer, :ok, subscribe_to: [SerialMessageManager]}
end
def handle_events(events, _from, state) do
for event <- events do
do_handle(event)
end
{:noreply, [], state}
end
# I think this is supposed to be somewhere else.
def do_handle({:send, str}) do
BotStatus.busy true
GenServer.cast(UartHandler, {:send, str})
end
# This is the heartbeat messge.
def do_handle({:gcode, {:idle} }) do
BotStatus.busy false
end
# The opposite of the below command?
def do_handle({:gcode, {:done } }) do
BotStatus.busy false
end
# I'm not entirely sure what this is.
def do_handle({:gcode, {:received } }) do
BotStatus.busy true
end
def do_handle({:gcode, { :report_pin_value, params }}) do
["P"<>pin, "V"<>value] = String.split(params, " ")
Logger.debug("pin#{pin}: #{value}")
BotStatus.set_pin(String.to_integer(pin), String.to_integer(value))
end
# TODO report end stops
def do_handle({:gcode, {:reporting_end_stops, stop_values }}) do
# Logger.debug("[gcode_handler] {:reporting_end_stops} stub: #{stop_values}")
stop_values
|> parse_stop_values
|> Enum.each(&BotStatus.set_end_stop/1)
end
def do_handle({:gcode, { :report_current_position, position }}) do
[x, y, z] = parse_coords(position)
BotStatus.set_pos(x,y,z)
end
def do_handle({:gcode, {:report_parameter_value, param }}) do
[p, v] = String.split(param, " ")
[_, real_p] = String.split(p, "P")
[_, real_v] = String.split(v, "V")
Logger.debug("Param: #{real_p}, Value: #{real_v}")
real_p
|> Gcode.parse_param
|> Atom.to_string
|> String.downcase
|> BotStatus.set_param(real_v)
end
def do_handle({:gcode, {:busy}}) do
BotStatus.busy true
end
def do_handle({:gcode, {:debug_message, "stopped"}} ) do
BotStatus.busy false
end
# Serial sending a debug message. Print it.
def do_handle({:gcode, {:debug_message, message}} ) do
Logger.debug("Debug message from arduino: #{message}")
end
# Unhandled gcode message
def do_handle({:gcode, {:unhandled_gcode, code}}) do
Logger.debug("[gcode_handler] Broken code? : #{inspect code}")
end
# Catch all for serial messages
def do_handle({:gcode, message}) do
Logger.debug("[gcode_handler] Unhandled Serial Gcode: #{inspect message}")
end
@doc """
Example:
iex> GcodeMessageHandler.parse_coords("X34 Y756 Z23")
[34, 756, 23]
"""
def parse_coords(position) when position |> is_binary do
position
|> String.split(" ")
|> parse_coords
end
def parse_coords(["X" <> x,"Y" <> y, "Z" <> z]) do
[x,y,z]
|> Enum.map(&String.to_integer/1)
end
@doc """
Example:
iex> GcodeMessageHandler.parse_stop_values("XA0 XB0 YA0 YB0 ZA0 ZB0")
[{"XA", "0"}, {"XB", "0"}, {"YA", "0"}, {"YB", "0"}, {"ZA", "0"}, {"ZB", "0"}]
"""
def parse_stop_values(stop_values) when stop_values |> is_binary do
# same thing here as parse_coords
stop_values
|> String.split(" ")
|> parse_stop_values
end
def parse_stop_values(["XA"<>xa, "XB"<>xb,
"YA"<>ya, "YB"<>yb,
"ZA"<>za, "ZB"<>zb]) do
[
{"XA", xa}, {"XB", xb},
{"YA", ya}, {"YB", yb},
{"ZA", za}, {"ZB", zb},
]
end
end

View File

@ -0,0 +1,39 @@
alias Experimental.{GenStage}
defmodule SerialMessageManager do
use GenStage
def start_link() do
GenStage.start_link(__MODULE__, :ok, name: __MODULE__)
end
@doc """
New Serial message
"""
def sync_notify(event, timeout \\ 5000) do
GenStage.call(__MODULE__, {:notify, event}, timeout)
end
def init(:ok) do
{:producer, {:queue.new, 0}, dispatcher: GenStage.BroadcastDispatcher}
end
def handle_call({:notify, event}, from, {queue, demand}) do
dispatch_events(:queue.in({from, event}, queue), demand, [])
end
def handle_demand(incoming_demand, {queue, demand}) do
dispatch_events(queue, incoming_demand + demand, [])
end
# This is some copy paste magic, not touching it.
defp dispatch_events(queue, demand, events) do
with d when d > 0 <- demand,
{{:value, {from, event}}, queue} <- :queue.out(queue),
_ <- GenStage.reply(from, :ok)
do
dispatch_events(queue, demand - 1, [event | events])
else
_ -> {:noreply, Enum.reverse(events), {queue, demand}}
end
end
end

View File

@ -0,0 +1,15 @@
defmodule SerialSupervisor do
def start_link(_args) do
import Supervisor.Spec
children = [
worker(SerialMessageManager, []),
worker(GcodeMessageHandler, [], id: 1), # Consumer
worker(UartHandler, [[]])
]
Supervisor.start_link(children, strategy: :one_for_one)
end
def init(_) do
{:ok, %{}}
end
end

Some files were not shown because too many files have changed in this diff Show More