Browse Source

First commit

Info @ meCoffee 5 years ago
commit
bf607b6707
100 changed files with 8234 additions and 0 deletions
  1. 17 0
      .editorconfig
  2. 37 0
      .gitignore
  3. 339 0
      LICENSE.txt
  4. 37 0
      README.md
  5. 24 0
      bower_components/platform.js/.bower.json
  6. 21 0
      bower_components/platform.js/LICENSE
  7. 12 0
      bower_components/platform.js/bower.json
  8. 1217 0
      bower_components/platform.js/platform.js
  9. 41 0
      bower_components/platform.js/yarn.lock
  10. 72 0
      config.xml
  11. 6 0
      ionic.config.json
  12. 71 0
      meBarista-Info.plist
  13. 54 0
      node_modules/svg-gauge/.npmignore
  14. 47 0
      node_modules/svg-gauge/Gulpfile.js
  15. 21 0
      node_modules/svg-gauge/LICENSE
  16. 84 0
      node_modules/svg-gauge/README.md
  17. 280 0
      node_modules/svg-gauge/dist/gauge.js
  18. 1 0
      node_modules/svg-gauge/dist/gauge.min.js
  19. 104 0
      node_modules/svg-gauge/example/dark.css
  20. 1 0
      node_modules/svg-gauge/example/highlight.pack.js
  21. 120 0
      node_modules/svg-gauge/example/index.css
  22. 343 0
      node_modules/svg-gauge/example/index.html
  23. 295 0
      node_modules/svg-gauge/index.js
  24. 1 0
      node_modules/svg-gauge/index.js.bkp
  25. 89 0
      node_modules/svg-gauge/package.json
  26. 280 0
      node_modules/svg-gauge/src/gauge.js
  27. 58 0
      package.json
  28. BIN
      resources/android/icon/drawable-hdpi-icon.png
  29. BIN
      resources/android/icon/drawable-ldpi-icon.png
  30. BIN
      resources/android/icon/drawable-mdpi-icon.png
  31. BIN
      resources/android/icon/drawable-xhdpi-icon.png
  32. BIN
      resources/android/icon/drawable-xxhdpi-icon.png
  33. BIN
      resources/android/icon/drawable-xxxhdpi-icon.png
  34. BIN
      resources/android/splash/drawable-land-hdpi-screen.png
  35. BIN
      resources/android/splash/drawable-land-ldpi-screen.png
  36. BIN
      resources/android/splash/drawable-land-mdpi-screen.png
  37. BIN
      resources/android/splash/drawable-land-xhdpi-screen.png
  38. BIN
      resources/android/splash/drawable-land-xxhdpi-screen.png
  39. BIN
      resources/android/splash/drawable-land-xxxhdpi-screen.png
  40. BIN
      resources/android/splash/drawable-port-hdpi-screen.png
  41. BIN
      resources/android/splash/drawable-port-ldpi-screen.png
  42. BIN
      resources/android/splash/drawable-port-mdpi-screen.png
  43. BIN
      resources/android/splash/drawable-port-xhdpi-screen.png
  44. BIN
      resources/android/splash/drawable-port-xxhdpi-screen.png
  45. BIN
      resources/android/splash/drawable-port-xxxhdpi-screen.png
  46. BIN
      resources/icon.png
  47. BIN
      resources/ios/icon/icon-40.png
  48. BIN
      resources/ios/icon/icon-40@2x.png
  49. BIN
      resources/ios/icon/icon-40@3x.png
  50. BIN
      resources/ios/icon/icon-50.png
  51. BIN
      resources/ios/icon/icon-50@2x.png
  52. BIN
      resources/ios/icon/icon-60.png
  53. BIN
      resources/ios/icon/icon-60@2x.png
  54. BIN
      resources/ios/icon/icon-60@3x.png
  55. BIN
      resources/ios/icon/icon-72.png
  56. BIN
      resources/ios/icon/icon-72@2x.png
  57. BIN
      resources/ios/icon/icon-76.png
  58. BIN
      resources/ios/icon/icon-76@2x.png
  59. BIN
      resources/ios/icon/icon-83.5@2x.png
  60. BIN
      resources/ios/icon/icon-small.png
  61. BIN
      resources/ios/icon/icon-small@2x.png
  62. BIN
      resources/ios/icon/icon-small@3x.png
  63. BIN
      resources/ios/icon/icon.png
  64. BIN
      resources/ios/icon/icon@2x.png
  65. BIN
      resources/ios/splash/Default-568h@2x~iphone.png
  66. BIN
      resources/ios/splash/Default-667h.png
  67. BIN
      resources/ios/splash/Default-736h.png
  68. BIN
      resources/ios/splash/Default-Landscape-736h.png
  69. BIN
      resources/ios/splash/Default-Landscape@2x~ipad.png
  70. BIN
      resources/ios/splash/Default-Landscape~ipad.png
  71. BIN
      resources/ios/splash/Default-Portrait@2x~ipad.png
  72. BIN
      resources/ios/splash/Default-Portrait~ipad.png
  73. BIN
      resources/ios/splash/Default@2x~iphone.png
  74. BIN
      resources/ios/splash/Default~iphone.png
  75. BIN
      resources/splash.png
  76. 71 0
      src/app/app.component.ts
  77. 19 0
      src/app/app.html
  78. 72 0
      src/app/app.module.ts
  79. 16 0
      src/app/app.scss
  80. 5 0
      src/app/main.ts
  81. 3891 0
      src/assets/demos/56273d393cab0
  82. BIN
      src/assets/icon/favicon.ico
  83. 3 0
      src/components/mc-range/mc-range.scss
  84. 114 0
      src/components/mc-range/mc-range.ts
  85. 14 0
      src/declarations.d.ts
  86. 41 0
      src/index.html
  87. 13 0
      src/manifest.json
  88. 28 0
      src/pages/about/about.html
  89. 3 0
      src/pages/about/about.scss
  90. 27 0
      src/pages/about/about.ts
  91. 28 0
      src/pages/demos/demos.html
  92. 3 0
      src/pages/demos/demos.scss
  93. 33 0
      src/pages/demos/demos.ts
  94. 90 0
      src/pages/hardware/hardware.html
  95. 15 0
      src/pages/hardware/hardware.scss
  96. 27 0
      src/pages/hardware/hardware.ts
  97. 15 0
      src/pages/installation/installation.html
  98. 3 0
      src/pages/installation/installation.scss
  99. 31 0
      src/pages/installation/installation.ts
  100. 0 0
      src/pages/page1/page1.html

+ 17 - 0
.editorconfig

@@ -0,0 +1,17 @@
+# EditorConfig helps developers define and maintain consistent coding styles between different editors and IDEs
+# editorconfig.org
+
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+
+# We recommend you to keep these unchanged
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false

+ 37 - 0
.gitignore

@@ -0,0 +1,37 @@
+# Specifies intentionally untracked files to ignore when using Git
+# http://git-scm.com/docs/gitignore
+
+*~
+*.sw[mnpcod]
+*.log
+*.tmp
+*.tmp.*
+log.txt
+*.sublime-project
+*.sublime-workspace
+.vscode/
+npm-debug.log*
+
+.idea/
+.sass-cache/
+.tmp/
+.versions/
+coverage/
+dist/
+!node_modules/svg-gauge
+node_modules/
+tmp/
+temp/
+hooks/
+platforms/
+plugins/
+plugins/android.json
+plugins/ios.json
+www/*
+!www/.gitkeep
+$RECYCLE.BIN/
+
+.DS_Store
+Thumbs.db
+UserInterfaceState.xcuserstate
+

+ 339 - 0
LICENSE.txt

@@ -0,0 +1,339 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.

+ 37 - 0
README.md

@@ -0,0 +1,37 @@
+meBarista in Ionic2.
+
+This is the codebase used to make the iOS meBarista App. It also builds for Android, but support is limited to Bluetooth Classic devices for now ( no BLE ). I think it is feasible to support BLE on Android by adding a BLE plugin.
+
+On Android ( with Classic devices ) there is no auto-pairing : you need to pair your meCoffee through the system Bluetooth settings.
+
+I hope to get it working on OSX and Windows to replace the Google Chrome App.
+
+Build:
+
+- Install NPM
+- Install Ionic2 and Cordova
+- Clone the repository
+- npm install
+
+- ionic platform add android
+- ionic run android
+
+- ionic platform add ios
+- ionic run ios --device
+
+- For Android, use Google Chrome "chrome://inspect" to connect to the app
+- For iOS, use XCode to attach the debugger
+
+Release iOS:
+- bump version in config.xml
+- copy of plist ( permissions )
+- Open in XCode
+- Enter developer keys
+- Build, archive
+- Go to https://appstoreconnect.apple.com/
+
+To get provision profile
+ - Get Apple Developer Account : https://developer.apple.com/account/
+ - Go into "Certificates, Identifiers & Profiles"
+ - Create keys and provision profiles
+

+ 24 - 0
bower_components/platform.js/.bower.json

@@ -0,0 +1,24 @@
+{
+  "name": "platform.js",
+  "main": "platform.js",
+  "ignore": [
+    ".*",
+    "*.log",
+    "*.md",
+    "package.json",
+    "doc",
+    "test"
+  ],
+  "homepage": "https://github.com/bestiejs/platform.js",
+  "version": "1.3.4",
+  "_release": "1.3.4",
+  "_resolution": {
+    "type": "version",
+    "tag": "1.3.4",
+    "commit": "dbe9db964ca25a26712ccb51482111f3773d35ef"
+  },
+  "_source": "https://github.com/bestiejs/platform.js.git",
+  "_target": "^1.3.4",
+  "_originalSource": "platform",
+  "_direct": true
+}

+ 21 - 0
bower_components/platform.js/LICENSE

@@ -0,0 +1,21 @@
+Copyright 2014-2016 Benjamin Tan <https://demoneaux.github.io/>
+Copyright 2011-2013 John-David Dalton <http://allyoucanleet.com/>
+
+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.

+ 12 - 0
bower_components/platform.js/bower.json

@@ -0,0 +1,12 @@
+{
+  "name": "platform.js",
+  "main": "platform.js",
+  "ignore": [
+    ".*",
+    "*.log",
+    "*.md",
+    "package.json",
+    "doc",
+    "test"
+  ]
+}

File diff suppressed because it is too large
+ 1217 - 0
bower_components/platform.js/platform.js


+ 41 - 0
bower_components/platform.js/yarn.lock

@@ -0,0 +1,41 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+docdown@~0.7.2:
+  version "0.7.2"
+  resolved "https://registry.yarnpkg.com/docdown/-/docdown-0.7.2.tgz#f8c68f5836974860e5bb1802bcac52ba8a3ff677"
+  dependencies:
+    doctrine "^2.0.0"
+    lodash "^4.17.2"
+
+doctrine@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63"
+  dependencies:
+    esutils "^2.0.2"
+    isarray "^1.0.0"
+
+esutils@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
+
+isarray@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+
+lodash@^4.17.2:
+  version "4.17.4"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae"
+
+qunit-extras@^1.5.0:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/qunit-extras/-/qunit-extras-1.5.0.tgz#a64d1c5088ab20c01c0e1b04c72132c397b3964c"
+
+qunitjs@^1.23.1:
+  version "1.23.1"
+  resolved "https://registry.yarnpkg.com/qunitjs/-/qunitjs-1.23.1.tgz#1971cf97ac9be01a64d2315508d2e48e6fd4e719"
+
+requirejs@^2.3.3:
+  version "2.3.3"
+  resolved "https://registry.yarnpkg.com/requirejs/-/requirejs-2.3.3.tgz#aa59fd3a0287eaf407959a138228044b5dd6a6a3"

+ 72 - 0
config.xml

@@ -0,0 +1,72 @@
+<?xml version='1.0' encoding='utf-8'?>
+<widget id="nl.digitalthings.mebarista.v2" version="0.1.0" android-versionCode="21" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0">
+    <name>meBarista</name>
+    <description>meBarista accompanies your meCoffee espresso PID</description>
+    <author email="info@mecoffee.nl" href="https://mecoffee.nl/">meCoffee.nl / Digital Things</author>
+    <content src="index.html" />
+    <access origin="*" />
+    <allow-navigation href="http://ionic.local/*" />
+    <allow-navigation href="https://mecoffee.nl/*" />
+    <allow-navigation href="*youtube*" />
+    <allow-navigation href="*ytimg*" />
+    <allow-intent href="http://*/*" />
+    <allow-intent href="https://*/*" />
+    <allow-intent href="tel:*" />
+    <allow-intent href="sms:*" />
+    <allow-intent href="mailto:*" />
+    <allow-intent href="geo:*" />
+    <platform name="android">
+        <allow-intent href="market:*" />
+    </platform>
+    <platform name="ios">
+        <allow-intent href="itms:*" />
+        <allow-intent href="itms-apps:*" />
+        <preference name="Orientation" value="all" />
+        <icon height="57" src="resources/ios/icon/icon.png" width="57" />
+        <icon height="114" src="resources/ios/icon/icon@2x.png" width="114" />
+        <icon height="40" src="resources/ios/icon/icon-40.png" width="40" />
+        <icon height="80" src="resources/ios/icon/icon-40@2x.png" width="80" />
+        <icon height="120" src="resources/ios/icon/icon-40@3x.png" width="120" />
+        <icon height="50" src="resources/ios/icon/icon-50.png" width="50" />
+        <icon height="100" src="resources/ios/icon/icon-50@2x.png" width="100" />
+        <icon height="60" src="resources/ios/icon/icon-60.png" width="60" />
+        <icon height="120" src="resources/ios/icon/icon-60@2x.png" width="120" />
+        <icon height="180" src="resources/ios/icon/icon-60@3x.png" width="180" />
+        <icon height="72" src="resources/ios/icon/icon-72.png" width="72" />
+        <icon height="144" src="resources/ios/icon/icon-72@2x.png" width="144" />
+        <icon height="76" src="resources/ios/icon/icon-76.png" width="76" />
+        <icon height="152" src="resources/ios/icon/icon-76@2x.png" width="152" />
+        <icon height="167" src="resources/ios/icon/icon-83.5@2x.png" width="167" />
+        <icon height="29" src="resources/ios/icon/icon-small.png" width="29" />
+        <icon height="58" src="resources/ios/icon/icon-small@2x.png" width="58" />
+        <icon height="87" src="resources/ios/icon/icon-small@3x.png" width="87" />
+        <splash height="1136" src="resources/ios/splash/Default-568h@2x~iphone.png" width="640" />
+        <splash height="1334" src="resources/ios/splash/Default-667h.png" width="750" />
+        <splash height="1536" src="resources/ios/splash/Default-Landscape@2x~ipad.png" width="2048" />
+        <splash height="768" src="resources/ios/splash/Default-Landscape~ipad.png" width="1024" />
+        <splash height="2048" src="resources/ios/splash/Default-Portrait@2x~ipad.png" width="1536" />
+        <splash height="1024" src="resources/ios/splash/Default-Portrait~ipad.png" width="768" />
+        <splash height="960" src="resources/ios/splash/Default@2x~iphone.png" width="640" />
+        <splash height="480" src="resources/ios/splash/Default~iphone.png" width="320" />
+    </platform>
+    <preference name="webviewbounce" value="false" />
+    <preference name="UIWebViewBounce" value="false" />
+    <preference name="DisallowOverscroll" value="true" />
+    <preference name="android-minSdkVersion" value="16" />
+    <preference name="BackupWebStorage" value="none" />
+    <preference name="SplashMaintainAspectRatio" value="true" />
+    <preference name="FadeSplashScreenDuration" value="300" />
+    <preference name="SplashShowOnlyFirstTime" value="false" />
+    <feature name="StatusBar">
+        <param name="ios-package" onload="true" value="CDVStatusBar" />
+    </feature>
+    <plugin name="ionic-plugin-keyboard" spec="~2.2.1" />
+    <plugin name="cordova-plugin-whitelist" spec="1.3.1" />
+    <plugin name="cordova-plugin-console" spec="1.0.5" />
+    <plugin name="cordova-plugin-statusbar" spec="2.2.1" />
+    <plugin name="cordova-plugin-splashscreen" spec="~4.0.1" />
+    <plugin name="cordova-plugin-file" spec="~4.3.2" />
+    <plugin name="cordova-plugin-bluetooth-serial" />
+    <icon src="resources/ios/icon/icon-small@3x.png" />
+    <plugin name="cordova-plugin-device" spec="~1.1.6" />
+</widget>

+ 6 - 0
ionic.config.json

@@ -0,0 +1,6 @@
+{
+  "name": "meBarista",
+  "app_id": "",
+  "v2": true,
+  "typescript": true
+}

+ 71 - 0
meBarista-Info.plist

@@ -0,0 +1,71 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+	<key>CFBundleDevelopmentRegion</key>
+	<string>English</string>
+	<key>CFBundleDisplayName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundleExecutable</key>
+	<string>${EXECUTABLE_NAME}</string>
+	<key>CFBundleIcons</key>
+	<dict/>
+	<key>CFBundleIcons~ipad</key>
+	<dict/>
+	<key>CFBundleIdentifier</key>
+	<string>nl.digitalthings.mebarista.v2</string>
+	<key>CFBundleInfoDictionaryVersion</key>
+	<string>6.0</string>
+	<key>CFBundleName</key>
+	<string>${PRODUCT_NAME}</string>
+	<key>CFBundlePackageType</key>
+	<string>APPL</string>
+	<key>CFBundleShortVersionString</key>
+	<string>0.0.5</string>
+	<key>CFBundleSignature</key>
+	<string>????</string>
+	<key>CFBundleVersion</key>
+	<string>0.0.5</string>
+	<key>ITSAppUsesNonExemptEncryption</key>
+	<false/>
+	<key>LSRequiresIPhoneOS</key>
+	<true/>
+	<key>NSAppTransportSecurity</key>
+	<dict>
+		<key>NSAllowsArbitraryLoads</key>
+		<true/>
+		<key>NSExceptionDomains</key>
+		<dict>
+			<key>ionic.local</key>
+			<dict>
+				<key>NSExceptionAllowsInsecureHTTPLoads</key>
+				<true/>
+			</dict>
+		</dict>
+	</dict>
+	<key>NSBluetoothPeripheralUsageDescription</key>
+	<string>meBarista uses your Bluetooth to connect your meCoffee espresso PID controller</string>
+	<key>NSMainNibFile~ipad</key>
+	<string></string>
+	<key>UIInterfaceOrientation</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+	</array>
+	<key>UIRequiresFullScreen</key>
+	<true/>
+	<key>UISupportedInterfaceOrientations</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+	<key>UISupportedInterfaceOrientations~ipad</key>
+	<array>
+		<string>UIInterfaceOrientationPortrait</string>
+		<string>UIInterfaceOrientationPortraitUpsideDown</string>
+		<string>UIInterfaceOrientationLandscapeLeft</string>
+		<string>UIInterfaceOrientationLandscapeRight</string>
+	</array>
+</dict>
+</plist>

+ 54 - 0
node_modules/svg-gauge/.npmignore

@@ -0,0 +1,54 @@
+/nbproject/
+/node_modules/
+
+### JetBrains IDEs ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff:
+.idea/workspace.xml
+.idea/tasks.xml
+
+# Sensitive or high-churn files:
+.idea/dataSources.ids
+.idea/dataSources.xml
+.idea/dataSources.local.xml
+.idea/sqlDataSources.xml
+.idea/dynamic.xml
+.idea/uiDesigner.xml
+
+# Gradle:
+.idea/gradle.xml
+.idea/libraries
+
+# Mongo Explorer plugin:
+.idea/mongoSettings.xml
+
+## File-based project format:
+*.iws
+
+## Plugin-specific files:
+
+# IntelliJ
+/out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+### WebStorm Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+.idea/

+ 47 - 0
node_modules/svg-gauge/Gulpfile.js

@@ -0,0 +1,47 @@
+var gulp = require("gulp"),
+    del = require("del"),
+    concat = require("gulp-concat"),
+    uglify = require("gulp-uglify"),
+    eventStream = require("event-stream");
+
+
+var config = {
+  src_dir: "src",
+  dist: {
+    dir: "dist",
+    css_dir: "dist/css"
+  }
+};
+
+gulp.task("default", function() {
+  console.log("Available tasks:");
+  console.log([
+    "------------------------------------------------------------------------",
+    "build           Build stage in the dist directory",
+    "clean           Clean the dest directory",
+    "-------------------------------------------------------------------------"
+  ].join("\n"));
+});
+
+
+gulp.task("clean", function(cb) {
+  del([
+    config.dist.dir
+  ], cb);
+});
+
+
+gulp.task("build", [], function() {
+   // do other build things
+   // gulp.start("jshint");
+
+   return eventStream.merge(
+      gulp.src(["src/gauge.js"]/*, {debug: true}*/)
+            .pipe(concat("gauge.js"))
+            .pipe(gulp.dest(config.dist.dir))
+            .pipe(concat("gauge.min.js"))
+            .pipe(gulp.dest(config.dist.dir))
+            .pipe(uglify({comments: /^\/\*\!*/}))
+            .pipe(gulp.dest(config.dist.dir))
+   );
+});

+ 21 - 0
node_modules/svg-gauge/LICENSE

@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Aniket Naik
+
+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.

+ 84 - 0
node_modules/svg-gauge/README.md

@@ -0,0 +1,84 @@
+# SVG Gauge
+Minmalistic, configurable, animated SVG gauge. Zero dependencies
+
+
+### Usage
+
+HTML
+```html
+<div id="cpuSpeed" class="gauge-container"></div>
+```
+CSS
+```css
+.gauge-container {
+  width: 150px;
+  height: 150px;
+  display: block;
+  padding: 10px;
+}
+.gauge-container > .gauge > .dial {
+  stroke: #eee;
+  stroke-width: 20;
+  fill: rgba(0,0,0,0);
+}
+.gauge-container > .gauge > .value {
+  stroke: rgb(47, 227, 255);
+  stroke-width: 20;
+  fill: rgba(0,0,0,0);
+}
+.gauge-container > .gauge > .value-text {
+  fill: rgb(47, 227, 255);
+  font-family: sans-serif;
+  font-weight: bold;
+  font-size: 10em;
+}
+```
+Javascript
+```js
+// npm install
+npm install svg-gauge
+
+// Require JS
+var Gauge = require("svg-guage");
+
+// Standalone
+var Gauge = window.Gauge;
+
+// Create a new Gauge
+var cpuGauge = Gauge(document.getElementById("cpuSpeed"), {
+    max: 100,
+    // custom label renderer
+    label: function(value) {
+      return Math.round(value) + "/" + this.max;
+    },
+    value: 50,
+});
+
+// Set gauge value
+cpuGauge.setValue(75);
+
+// Set value and animate (value, animation duration in seconds)
+cpuGauge.setValueAnimated(90, 1);
+
+```
+
+#### Options
+
+|      Name            |                  Description                       |
+| -------------------- | ------------------------------------------------------------------------------------- |
+| ```dialStartAngle``` | The angle in degrees to start the dial (```135```)       |
+| ```dialEndAngle```   | The angle in degrees to end the dial. This MUST be less than dialStartAngle (```45```)  |
+| ```radius```         | The radius of the gauge (```400```) |
+| ```max```            | The maximum value for the gauge (```100```)  |
+| ```label```          | Optional function that returns a string label that will be rendered in the center. This function will be passed the current value |
+| ```showValue```      | Whether to show the value at the center of the gauge (```true```) |
+| ```gaugeClass```     | The CSS class of the gauge (```gauge```) |
+| ```dialClass```      | The CSS class of the gauge's dial (```dial```) |
+| ```valueDialClass``` | The CSS class of the gauge's fill (value dial) (```value```) |
+| ```valueTextClass``` | The CSS class of the gauge's text (```value-text```) |
+
+
+
+
+
+#### [Live Demo](http://codepen.io/naikus/pen/BzkoLL)

+ 280 - 0
node_modules/svg-gauge/dist/gauge.js

@@ -0,0 +1,280 @@
+/* global window, define, module */
+(function(global, factory) {
+  var Gauge = factory(global);
+  if(typeof define === "function" && define.amd) {
+    // AMD support
+    define(function() {return Gauge;});
+  }else if(typeof module === "object" && module.exports) {
+    // CommonJS support
+    module.exports = Gauge;
+  }else {
+    // We are probably running in the browser
+    global.Gauge = Gauge;
+  }
+})(typeof window === "undefined" ? this : window, function(global, undefined) {
+
+  var document = global.document,
+    requestAnimationFrame = (global.requestAnimationFrame ||
+        global.mozRequestAnimationFrame ||
+        global.webkitRequestAnimationFrame ||
+        global.msRequestAnimationFrame ||
+        function(cb) {
+          return setTimeout(cb, 1000 / 60);
+        });
+
+  // EXPERIMENTAL!!
+  /**
+   * Simplistic animation function for animating the gauge. That's all!
+   * Options are:
+   * {
+   *  duration: 1,    // In seconds
+   *  start: 0,       // The start value
+   *  end: 100,       // The end value
+   *  step: function, // REQUIRED! The step function that will be passed the value and does something
+   *  easing: function // The easing function. Default is easeInOutCubic
+   * }
+   */
+  function Animation(options) {
+    var duration = options.duration,
+        currentIteration = 1,
+        iterations = 60 * duration,
+        start = options.start || 0,
+        end = options.end,
+        change = end - start,
+        step = options.step,
+        easing = options.easing || function easeInOutCubic(pos) {
+          // https://github.com/danro/easing-js/blob/master/easing.js
+          if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,3);
+          return 0.5 * (Math.pow((pos-2),3) + 2);
+        };
+
+    function animate() {
+      var progress = (currentIteration++) / iterations;
+      var value = change * easing(progress) + start;
+      // console.log(progress + ", " + value);
+      step(value);
+      if(progress < 1) {
+        requestAnimationFrame(animate);
+      }
+    }
+    // start!
+    requestAnimationFrame(animate);
+  }
+
+
+
+  var Gauge = (function() {
+
+    var SVG_NS = "http://www.w3.org/2000/svg";
+
+    var GaugeDefaults = {
+      dialStartAngle: 135,
+      dialEndAngle: 45,
+      centerX: 500,
+      centerY: 500,
+      radius: 400
+    };
+
+    /**
+     * A utility function to create SVG dom tree
+     * @param {String} name The SVG element name
+     * @param {Object} attrs The attributes as they appear in DOM e.g. stroke-width and not strokeWidth
+     * @param {Array} children An array of children (can be created by this same function)
+     * @return The SVG element
+     */
+    function svg(name, attrs, children) {
+      var elem = document.createElementNS(SVG_NS, name);
+      for(var attrName in attrs) {
+        elem.setAttribute(attrName, attrs[attrName]);
+      }
+
+      if(children) {
+        children.forEach(function(c) {
+          elem.appendChild(c);
+        });
+      }
+      return elem;
+    }
+
+    /**
+     * Translates percentage value to angle. e.g. If gauge span angle is 180deg, then 50%
+     * will be 90deg
+     */
+    function getAngle(percentage, gaugeSpanAngle) {
+      return percentage * gaugeSpanAngle / 100;
+    }
+
+    function normalize(value, limit) {
+      var val = Number(value);
+      if(val > limit) return limit;
+      if(val < 0) return 0;
+      return val;
+    }
+
+    function getValueInPercentage(value, limit) {
+      return 100 * value / limit;
+    }
+
+    /**
+     * Gets cartesian co-ordinates for a specified radius and angle (in degrees)
+     * @param cx {Number} The center x co-oriinate
+     * @param cy {Number} The center y co-ordinate
+     * @param radius {Number} The radius of the circle
+     * @param angle {Number} The angle in degrees
+     * @return An object with x,y co-ordinates
+     */
+    function getCartesian(cx, cy, radius, angle) {
+      var rad = angle * Math.PI / 180;
+      return {
+        x: Math.round((cx + radius * Math.cos(rad)) * 1000) / 1000,
+        y: Math.round((cy + radius * Math.sin(rad)) * 1000) / 1000
+      };
+    }
+
+    // Returns start and end points for dial
+    // i.e. starts at 135deg ends at 45deg with large arc flag
+    // REMEMBER!! angle=0 starts on X axis and then increases clockwise
+    function getDialCoords(radius, startAngle, endAngle) {
+      var cx = GaugeDefaults.centerX,
+          cy = GaugeDefaults.centerY;
+      return {
+        end: getCartesian(cx, cy, radius, endAngle),
+      	start: getCartesian(cx, cy, radius, startAngle)
+      };
+    }
+
+    function defaultLabelRenderer(theValue) {
+      return Math.round(theValue);
+    }
+
+    /**
+     * Creates a Gauge object. This should be called without the 'new' operator. Various options
+     * can be passed for the gauge:
+     * {
+     *    dialStartAngle: The angle to start the dial. MUST be greater than dialEndAngle. Default 135deg
+     *    dialEndAngle: The angle to end the dial. Default 45deg
+     *    radius: The gauge's radius. Default 400
+     *    max: The maximum value of the gauge. Default 100
+     *    value: The starting value of the gauge. Default 0
+     *    label: The function on how to render the center label (Should return a value)
+     * }
+     * @param {Element} elem The DOM into which to render the gauge
+     * @param {Object} opts The gauge options
+     * @return a Gauge object
+     */
+    return function Gauge(elem, opts) {
+      opts = opts || {};
+      var gaugeContainer = elem,
+          limit = opts.max || 100,
+          value = normalize(opts.value || 0, limit),
+          radius = opts.radius || 400,
+          displayValue = opts.showValue === false ? false : true,
+          valueLabelRender = typeof (opts.label) === "function" ? opts.label : defaultLabelRenderer,
+          startAngle = typeof (opts.dialStartAngle) === "undefined" ? 135 : opts.dialStartAngle,
+          endAngle = typeof (opts.dialEndAngle) === "undefined" ? 45 : opts.dialEndAngle,
+          valueDialClass = typeof (opts.valueDialClass) === "undefined" ? 'value' : opts.valueDialClass,
+          valueTextClass = typeof (opts.valueTextClass) === "undefined" ? 'value-text' : opts.valueTextClass,
+          dialClass = typeof (opts.dialClass) === "undefined" ? 'dial' : opts.dialClass,
+          gaugeClass = typeof (opts.gaugeClass) === "undefined" ? 'gauge' : opts.gaugeClass,
+          gaugeTextElem,
+          gaugeValuePath,
+          instance;
+
+      if(startAngle < endAngle) {
+        console.log("WARNING! Start angle should be greater than end angle. Swapping");
+        var tmp = startAngle;
+        startAngle = endAngle;
+        endAngle = tmp;
+      }
+
+      function pathString(radius, startAngle, endAngle, largeArc) {
+        var coords = getDialCoords(radius, startAngle, endAngle),
+            start = coords.start,
+            end = coords.end,
+            largeArcFlag = typeof(largeArc) === "undefined" ? 1 : largeArc;
+
+        return ["M", start.x, start.y, "A", radius, radius, "0", largeArcFlag, "1", end.x, end.y].join(" ");
+      }
+
+      function initializeGauge(elem) {
+        gaugeTextElem = svg("text", {
+          "class": valueTextClass,
+          "x": 500,
+          "y": 550,
+          "font-size": "700%",
+          "font-family": "sans-serif",
+          "font-weight": "bold",
+          "text-anchor": "middle"
+        });
+        gaugeValuePath = svg("path", {
+          "class": valueDialClass,
+          "fill": "transparent",
+          "stroke": "#666",
+          "stroke-width": 25,
+          "d": pathString(radius, startAngle, startAngle) // value of 0
+        });
+
+        var angle = getAngle(100, 360 - Math.abs(startAngle - endAngle));
+        var flag = angle <= 180 ? 0 : 1;
+        var gaugeElement = svg("svg", {"viewBox": "0 0 1000 1000", "class": gaugeClass},
+          [
+            svg("path", {
+              "class": dialClass,
+              "fill": "transparent",
+              "stroke": "#eee",
+              "stroke-width": 20,
+              "d": pathString(radius, startAngle, endAngle, flag)
+            }),
+            gaugeTextElem,
+            gaugeValuePath
+          ]
+        );
+        elem.appendChild(gaugeElement);
+      }
+
+      function updateGauge(theValue) {
+        var val = getValueInPercentage(theValue, limit),
+            // angle = getAngle(val, 360 - Math.abs(endAngle - startAngle)),
+            angle = getAngle(val, 360 - Math.abs(startAngle - endAngle)),
+            // this is because we are using arc greater than 180deg
+            flag = angle <= 180 ? 0 : 1;
+        (displayValue && (gaugeTextElem.textContent = valueLabelRender.call(opts, theValue)));
+        gaugeValuePath.setAttribute("d", pathString(radius, startAngle, angle + startAngle, flag));
+      }
+
+      instance = {
+        setMaxValue: function(max) {
+          limit = max;
+        },
+        setValue: function(val) {
+          value = normalize(val, limit);
+          updateGauge(value);
+        },
+        setValueAnimated: function(val, duration) {
+        	var oldVal = value;
+          value = normalize(val, limit);
+          if(oldVal === value) {
+            return;
+          }
+          Animation({
+            start: oldVal || 0,
+            end: value,
+            duration: duration || 1,
+            step: function(val) {
+              updateGauge(Math.round(val * 100) / 100);
+            }
+          });
+        },
+        getValue: function() {
+          return value;
+        }
+      };
+
+      initializeGauge(gaugeContainer);
+      updateGauge(value);
+      return instance;
+    };
+  })();
+
+  return Gauge;
+});

File diff suppressed because it is too large
+ 1 - 0
node_modules/svg-gauge/dist/gauge.min.js


+ 104 - 0
node_modules/svg-gauge/example/dark.css

@@ -0,0 +1,104 @@
+/*
+
+Dark style from softwaremaniacs.org (c) Ivan Sagalaev <Maniac@SoftwareManiacs.Org>
+
+*/
+
+.hljs {
+  display: block;
+  overflow-x: auto;
+  padding: 0.5em;
+  background: #444;
+  -webkit-text-size-adjust: none;
+}
+
+.hljs-keyword,
+.hljs-literal,
+.hljs-change,
+.hljs-winutils,
+.hljs-flow,
+.nginx .hljs-title,
+.tex .hljs-special {
+  color: white;
+}
+
+.hljs,
+.hljs-subst {
+  color: #ddd;
+}
+
+.hljs-string,
+.hljs-title,
+.hljs-type,
+.ini .hljs-title,
+.hljs-tag .hljs-value,
+.css .hljs-rules .hljs-value,
+.hljs-preprocessor,
+.hljs-pragma,
+.ruby .hljs-symbol,
+.ruby .hljs-symbol .hljs-string,
+.ruby .hljs-class .hljs-parent,
+.hljs-built_in,
+.django .hljs-template_tag,
+.django .hljs-variable,
+.smalltalk .hljs-class,
+.hljs-javadoc,
+.ruby .hljs-string,
+.django .hljs-filter .hljs-argument,
+.smalltalk .hljs-localvars,
+.smalltalk .hljs-array,
+.hljs-attr_selector,
+.hljs-pseudo,
+.hljs-addition,
+.hljs-stream,
+.hljs-envvar,
+.apache .hljs-tag,
+.apache .hljs-cbracket,
+.tex .hljs-command,
+.hljs-prompt,
+.coffeescript .hljs-attribute {
+  color: #d88;
+}
+
+.hljs-comment,
+.hljs-annotation,
+.hljs-decorator,
+.hljs-pi,
+.hljs-doctype,
+.hljs-deletion,
+.hljs-shebang,
+.apache .hljs-sqbracket,
+.tex .hljs-formula {
+  color: #777;
+}
+
+.hljs-keyword,
+.hljs-literal,
+.hljs-title,
+.css .hljs-id,
+.hljs-phpdoc,
+.hljs-dartdoc,
+.hljs-type,
+.vbscript .hljs-built_in,
+.rsl .hljs-built_in,
+.smalltalk .hljs-class,
+.diff .hljs-header,
+.hljs-chunk,
+.hljs-winutils,
+.bash .hljs-variable,
+.apache .hljs-tag,
+.tex .hljs-special,
+.hljs-request,
+.hljs-status {
+  font-weight: bold;
+}
+
+.coffeescript .javascript,
+.javascript .xml,
+.tex .hljs-formula,
+.xml .javascript,
+.xml .vbscript,
+.xml .css,
+.xml .hljs-cdata {
+  opacity: 0.5;
+}

File diff suppressed because it is too large
+ 1 - 0
node_modules/svg-gauge/example/highlight.pack.js


+ 120 - 0
node_modules/svg-gauge/example/index.css

@@ -0,0 +1,120 @@
+  body {
+    background-color: rgba(0,0,0,0.8);
+    color: #999;
+    font-family: Hevletica, 'sans-serif';
+  }
+
+  /* ------------------- Default Style ------------------------- */
+
+  .gauge-container {
+    width: 200px;
+    height: 200px;
+    display: block;
+    float: left;
+    padding: 10px;
+    overflow: hidden;
+  }
+  .gauge-container.one > .gauge > .dial {
+    stroke: #334455;
+    stroke-width: 20;
+    fill: rgba(0,0,0,0);
+  }
+  .gauge-container.one > .gauge > .value {
+    stroke: rgb(47, 227, 255);
+    stroke-width: 20;
+    fill: rgba(0,0,0,0);
+  }
+  .gauge-container.one > .gauge > .value-text {
+    fill: rgb(47, 227, 255);
+    font-family: sans, 'sans-serif';
+    font-weight: bold;
+    font-size: 10em;
+  }
+
+  /* ------------------- Alternate Style ------------------------- */
+
+  .gauge-container.two {
+  }
+  .gauge-container.two > .gauge > .dial {
+    stroke: #334455;
+    stroke-width: 100;
+  }
+  .gauge-container.two > .gauge > .value {
+    stroke: orange;
+    stroke-dasharray: none;
+    stroke-width: 130;
+  }
+  .gauge-container.two > .gauge > .value-text {
+    fill: orange;
+  }
+
+
+  /* ------------------- Alternate Style 2 ------------------------- */
+
+  .gauge-container.three {
+  }
+  .gauge-container.three > .gauge > .dial {
+    stroke: #334455;
+    stroke-width: 20;
+    /*stroke-dasharray: 125, 20;*/
+  }
+  .gauge-container.three > .gauge > .value {
+    stroke: #C9DE3C;
+    stroke-width: 50;
+    /*stroke-dasharray: 125, 20;*/
+  }
+  .gauge-container.three > .gauge > .value-text {
+    fill: #C9DE3C;
+  }
+
+
+  /* ------------------- Alternate Style 2 ------------------------- */
+
+  .gauge-container.four > .gauge > .dial {
+    stroke: #334455;
+    fill: "#334455";
+    stroke-width: 50;
+  }
+  .gauge-container.four > .gauge > .value {
+    stroke: #BE80FF;
+    stroke-dasharray: none;
+    stroke-width: 50;
+  }
+  .gauge-container.four > .gauge > .value-text {
+    fill: #BE80FF;
+  }
+
+
+
+  .gauge-container.five > .gauge > .dial {
+    stroke: #334455;
+    fill: "#334455";
+    stroke-width: 50;
+  }
+  .gauge-container.five > .gauge > .value {
+    stroke: #F8774B;
+    stroke-dasharray: none;
+    stroke-width: 50;
+  }
+  .gauge-container.five > .gauge > .value-text {
+    fill: #F8774B;
+    font-size: 7em;
+  }
+
+
+
+  .gauge-container.six > .gauge > .dial {
+    stroke: #334455;
+    fill: "#334455";
+    stroke-dasharray: 110 10;
+    stroke-width: 50;
+  }
+  .gauge-container.six > .gauge > .value {
+    stroke: #FF6DAF;
+    stroke-dasharray: 110 10;
+    stroke-width: 52;
+  }
+  .gauge-container.six > .gauge > .value-text {
+    fill: #FF6DAF;
+    font-size: 7em;
+  }

+ 343 - 0
node_modules/svg-gauge/example/index.html

@@ -0,0 +1,343 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Gauges</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
+    <meta name="HandheldFriendly" content="True" />
+    <meta name="MobileOptimized" content="480" />
+    <meta name="viewport"
+        content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
+
+    <meta name="apple-mobile-web-app-capable" content="yes" />
+    <meta name="theme-color" content="#2f434e" />
+    <meta http-equiv="cleartype" content="on" />
+    <link rel="stylesheet" href="index.css" />
+    <link rel="stylesheet" href="dark.css" />
+    <style>
+      .example {
+        overflow: auto;
+        padding: 10px;
+        box-sizing: border-box;
+        clear: both;
+      }
+      .example > .code, .example > .display {
+        box-sizing: border-box;
+        padding: 10px;
+      }
+      .example > .code {
+      }
+      .example > .display {
+        text-align: center;
+        overflow: auto;
+        padding: 20px 0;
+        overflow: hidden;
+      }
+      .example > .display {
+      }
+
+      pre {
+        padding: 0;
+        margin: 0;
+      }
+
+      #gauge1 .value-text {
+        font-size: 6em;
+      }
+
+      @media only screen and (min-device-width: 768px) {
+        .example > .code {
+          width: 65%;
+        }
+        .example > .display {
+          width: 35%;
+        }
+        .example > .code {
+          float: left;
+        }
+        .example > .display {
+          float: right;
+          padding: 10px;
+        }
+      }
+    </style>
+
+    <script type="text/javascript" src="highlight.pack.js"> </script>
+    <script>hljs.initHighlightingOnLoad();</script>
+  </head>
+
+  <body>
+    <h2>SVG Gauge</h2>
+    <h4>Minimalistic, zero dependency animated gauge widget</h4>
+    <p>
+      All the guages are styled via CSS. See gauge-test.css for styling for each of these gauges
+      below.
+    </p>
+
+
+    <div class="example">
+      <div class="code">
+        <pre>
+<code class="html">
+  &lt;!-- Unstyled gauge (All defaults) --&gt;
+  &lt;div id="gauge0" class="gauge-container"&gt;&lt;/div&gt;
+</code>
+<code class="javascript">
+  var gauge0 = Gauge(document.getElementById("gauge0"));
+</code>
+        </pre>
+      </div>
+      <div class="display">
+        <div id="gauge0" class="gauge-container"></div>
+      </div>
+    </div>
+
+
+    <div class="example">
+      <div class="code">
+        <pre>
+<code class="html">  &lt;div id="gauge1" class="gauge-container"&gt;&lt;/div&gt;</code>
+<code class="javascript">
+  var gauge1 = Gauge(
+    document.getElementById("gauge1"),
+    {
+      max: 100,
+      dialStartAngle: -90,
+      dialEndAngle: -90.001,
+      value: 100,
+      label: function(value) {
+        return Math.round(value) + "/" + this.max;
+      }
+    }
+  );
+</code>
+        </pre>
+      </div>
+      <div class="display">
+        <div id="gauge1" class="gauge-container one"></div>
+      </div>
+    </div>
+
+
+
+    <div class="example">
+      <div class="code">
+        <pre>
+<code class="html">
+  &lt;div id="gauge2" class="gauge-container two"&gt;&lt;/div&gt;
+</code>
+<code class="javascript">
+  var gauge2 = Gauge(
+    document.getElementById("gauge2"),
+    {
+      max: 100,
+      dialStartAngle: 180,
+      dialEndAngle: 0,
+      value: 50
+    }
+  );
+</code>
+        </pre>
+      </div>
+      <div class="display">
+        <div id="gauge2" class="gauge-container two"></div>
+      </div>
+    </div>
+
+
+
+
+    <div class="example">
+      <div class="code">
+        <pre>
+<code class="html">
+  &lt;div id="gauge3" class="gauge-container three"&gt;&lt;/div&gt;
+</code>
+<code class="javascript">
+  var gauge3 = Gauge(
+    document.getElementById("gauge3"),
+    {
+      max: 100,
+      value: 50
+    }
+  );
+</code>
+</pre>
+</div>
+<div class="display">
+<div id="gauge3" class="gauge-container three"></div>
+</div>
+</div>
+
+
+
+<div class="example">
+<div class="code">
+<pre>
+<code class="html">
+  &lt;div id="gauge4" class="gauge-container four"&gt;&lt;/div&gt;
+</code>
+<code class="javascript">
+  var gauge4 = Gauge(
+    document.getElementById("gauge4"),
+    {
+      max: 100,
+      dialStartAngle: 180,
+      dialEndAngle: -90,
+      value: 50
+    }
+  );
+</code>
+</pre>
+</div>
+<div class="display">
+<div id="gauge4" class="gauge-container four"></div>
+</div>
+</div>
+
+
+
+
+<div class="example">
+<div class="code">
+<pre>
+<code class="html">
+  &lt;div id="gauge5" class="gauge-container five"&gt;&lt;/div&gt;
+</code>
+<code class="javascript">
+  var gauge5 = Gauge(
+    document.getElementById("gauge5"),
+    {
+      max: 200,
+      dialStartAngle: 0,
+      dialEndAngle: -180,
+      value: 50
+    }
+  );
+</code>
+        </pre>
+      </div>
+      <div class="display">
+        <div id="gauge5" class="gauge-container five"></div>
+      </div>
+    </div>
+
+
+
+
+    <div class="example">
+      <div class="code">
+        <pre>
+<code class="html">
+  &lt;div id="gauge6" class="gauge-container six"&gt;&lt;/div&gt;
+</code>
+<code class="javascript">
+  var gauge6 = Gauge(
+    document.getElementById("gauge6"),
+    {
+      max: 100,
+      dialStartAngle: 90.01,
+      dialEndAngle: 89.99,
+      radius: 150,
+      displayValue: false,
+      value: 50,
+    }
+  );
+</code>
+        </pre>
+      </div>
+      <div class="display">
+        <div id="gauge6" class="gauge-container six"></div>
+      </div>
+    </div>
+
+
+
+
+    <script type="text/javascript" src="../src/gauge.js"> </script>
+    <script>
+
+      var gauge0 = Gauge(document.getElementById("gauge0"));
+
+      var gauge1 = Gauge(
+        document.getElementById("gauge1"),
+		    {
+          max: 100,
+          dialStartAngle: -90,
+          dialEndAngle: -90.001,
+          value: 100,
+          label: function(value) {
+            return Math.round(value) + "/" + this.max;
+          }
+        }
+      );
+
+      var gauge2 = Gauge(
+        document.getElementById("gauge2"),
+		    {
+          max: 100,
+          dialStartAngle: 180,
+          dialEndAngle: 0,
+          value: 50
+        }
+      );
+
+      var gauge3 = Gauge(
+        document.getElementById("gauge3"),
+		    {
+          max: 100,
+          value: 50
+        }
+      );
+
+      var gauge4 = Gauge(
+        document.getElementById("gauge4"),
+		    {
+          max: 100,
+          dialStartAngle: 180,
+          dialEndAngle: -90,
+          value: 50
+        }
+      );
+
+      var gauge5 = Gauge(
+        document.getElementById("gauge5"),
+		    {
+          max: 200,
+          dialStartAngle: 0,
+          dialEndAngle: -180,
+          value: 50
+        }
+      );
+
+      var gauge6 = Gauge(
+        document.getElementById("gauge6"),
+		    {
+          max: 100,
+          dialStartAngle: 90.01,
+          dialEndAngle: 89.99,
+          radius: 150,
+          showValue: false,
+          value: 100
+        }
+      );
+
+      (function loop() {
+        var value1 = Math.round(Math.random() * 100),
+            value2 = Math.round(Math.random() * 100),
+            value3 = Math.round(Math.random() * 100),
+            value4 = Math.round(Math.random() * 100),
+            value5 = Math.round(Math.random() * 200);
+
+        gauge0.setValueAnimated(value1, 1);
+        gauge1.setValueAnimated(value1, 1);
+        gauge2.setValueAnimated(value2, 2);
+        gauge3.setValueAnimated(value3, 1.5);
+        gauge4.setValueAnimated(value4, 2);
+        gauge5.setValueAnimated(value5, 1);
+        gauge6.setValueAnimated(value1, 1);
+        window.setTimeout(loop, 2500);
+      })();
+    </script>
+  </body>
+</html>

+ 295 - 0
node_modules/svg-gauge/index.js

@@ -0,0 +1,295 @@
+/* global window, define, module */
+(function(global, factory) {
+  var Gauge = factory(global);
+  if(typeof define === "function" && define.amd) {
+    // AMD support
+    define(function() {return Gauge;});
+  }else if(typeof module === "object" && module.exports) {
+    // CommonJS support
+    module.exports = Gauge;
+  }else {
+    // We are probably running in the browser
+    global.Gauge = Gauge;
+  }
+})(typeof window === "undefined" ? this : window, function(global, undefined) {
+
+  var document = global.document,
+    requestAnimationFrame = (global.requestAnimationFrame ||
+        global.mozRequestAnimationFrame ||
+        global.webkitRequestAnimationFrame ||
+        global.msRequestAnimationFrame ||
+        function(cb) {
+          return setTimeout(cb, 1000 / 60);
+        });
+
+  // EXPERIMENTAL!!
+  /**
+   * Simplistic animation function for animating the gauge. That's all!
+   * Options are:
+   * {
+   *  duration: 1,    // In seconds
+   *  start: 0,       // The start value
+   *  end: 100,       // The end value
+   *  step: function, // REQUIRED! The step function that will be passed the value and does something
+   *  easing: function // The easing function. Default is easeInOutCubic
+   * }
+   */
+  function Animation(options) {
+    var duration = options.duration,
+        currentIteration = 1,
+        iterations = 60 * duration,
+        start = options.start || 0,
+        end = options.end,
+        change = end - start,
+        step = options.step,
+        easing = options.easing || function easeInOutCubic(pos) {
+          // https://github.com/danro/easing-js/blob/master/easing.js
+          if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,3);
+          return 0.5 * (Math.pow((pos-2),3) + 2);
+        };
+
+    function animate() {
+      var progress = (currentIteration++) / iterations;
+      var value = change * easing(progress) + start;
+      // console.log(progress + ", " + value);
+      step(value);
+      if(progress < 1) {
+        requestAnimationFrame(animate);
+      }
+    }
+    // start!
+    requestAnimationFrame(animate);
+  }
+
+
+
+  var Gauge = (function() {
+
+    var SVG_NS = "http://www.w3.org/2000/svg";
+
+    var GaugeDefaults = {
+      dialStartAngle: 135,
+      dialEndAngle: 45,
+      centerX: 500,
+      centerY: 500,
+      radius: 400
+    };
+
+    /**
+     * A utility function to create SVG dom tree
+     * @param {String} name The SVG element name
+     * @param {Object} attrs The attributes as they appear in DOM e.g. stroke-width and not strokeWidth
+     * @param {Array} children An array of children (can be created by this same function)
+     * @return The SVG element
+     */
+    function svg(name, attrs, children) {
+      var elem = document.createElementNS(SVG_NS, name);
+      for(var attrName in attrs) {
+        elem.setAttribute(attrName, attrs[attrName]);
+      }
+
+      if(children) {
+        children.forEach(function(c) {
+          elem.appendChild(c);
+        });
+      }
+      return elem;
+    }
+
+    /**
+     * Translates percentage value to angle. e.g. If gauge span angle is 180deg, then 50%
+     * will be 90deg
+     */
+    function getAngle(percentage, gaugeSpanAngle) {
+      return percentage * gaugeSpanAngle / 100;
+    }
+
+    function normalize(value, limit) {
+      var val = Number(value);
+      if(val > limit) return limit;
+      if(val < 0) return 0;
+      return val;
+    }
+
+    function getValueInPercentage(value, limit) {
+      return 100 * value / limit;
+    }
+
+    /**
+     * Gets cartesian co-ordinates for a specified radius and angle (in degrees)
+     * @param cx {Number} The center x co-oriinate
+     * @param cy {Number} The center y co-ordinate
+     * @param radius {Number} The radius of the circle
+     * @param angle {Number} The angle in degrees
+     * @return An object with x,y co-ordinates
+     */
+    function getCartesian(cx, cy, radius, angle) {
+      var rad = angle * Math.PI / 180;
+      return {
+        x: Math.round((cx + radius * Math.cos(rad)) * 1000) / 1000,
+        y: Math.round((cy + radius * Math.sin(rad)) * 1000) / 1000
+      };
+    }
+
+    // Returns start and end points for dial
+    // i.e. starts at 135deg ends at 45deg with large arc flag
+    // REMEMBER!! angle=0 starts on X axis and then increases clockwise
+    function getDialCoords(radius, startAngle, endAngle) {
+      var cx = GaugeDefaults.centerX,
+          cy = GaugeDefaults.centerY;
+      return {
+        end: getCartesian(cx, cy, radius, endAngle),
+      	start: getCartesian(cx, cy, radius, startAngle)
+      };
+    }
+
+    function defaultLabelRenderer(theValue) {
+      return Math.round(theValue);
+    }
+
+    /**
+     * Creates a Gauge object. This should be called without the 'new' operator. Various options
+     * can be passed for the gauge:
+     * {
+     *    dialStartAngle: The angle to start the dial. MUST be greater than dialEndAngle. Default 135deg
+     *    dialEndAngle: The angle to end the dial. Default 45deg
+     *    radius: The gauge's radius. Default 400
+     *    max: The maximum value of the gauge. Default 100
+     *    value: The starting value of the gauge. Default 0
+     *    label: The function on how to render the center label (Should return a value)
+     * }
+     * @param {Element} elem The DOM into which to render the gauge
+     * @param {Object} opts The gauge options
+     * @return a Gauge object
+     */
+    return function Gauge(elem, opts) {
+      opts = opts || {};
+      var gaugeContainer = elem,
+          limit = opts.max || 100,
+          value = normalize(opts.value || 0, limit),
+          radius = opts.radius || 400,
+          displayValue = opts.showValue === false ? false : true,
+          valueLabelRender = typeof (opts.label) === "function" ? opts.label : defaultLabelRenderer,
+          startAngle = typeof (opts.dialStartAngle) === "undefined" ? 135 : opts.dialStartAngle,
+          endAngle = typeof (opts.dialEndAngle) === "undefined" ? 45 : opts.dialEndAngle,
+          valueDialClass = typeof (opts.valueDialClass) === "undefined" ? 'value' : opts.valueDialClass,
+          valueTextClass = typeof (opts.valueTextClass) === "undefined" ? 'value-text' : opts.valueTextClass,
+          dialClass = typeof (opts.dialClass) === "undefined" ? 'dial' : opts.dialClass,
+          gaugeClass = typeof (opts.gaugeClass) === "undefined" ? 'gauge' : opts.gaugeClass,
+          title = typeof (opts.title) === "undefined" ? 'title' : opts.title,
+          gaugeTextElem,
+          gaugeTitleElem,
+          gaugeValuePath,
+          instance;
+
+      if(startAngle < endAngle) {
+        console.log("WARNING! Start angle should be greater than end angle. Swapping");
+        var tmp = startAngle;
+        startAngle = endAngle;
+        endAngle = tmp;
+      }
+
+      function pathString(radius, startAngle, endAngle, largeArc) {
+        var coords = getDialCoords(radius, startAngle, endAngle),
+            start = coords.start,
+            end = coords.end,
+            largeArcFlag = typeof(largeArc) === "undefined" ? 1 : largeArc;
+
+        return ["M", start.x, start.y, "A", radius, radius, "0", largeArcFlag, "1", end.x, end.y].join(" ");
+      }
+
+      function initializeGauge(elem) {
+        gaugeTextElem = svg("text", {
+          "class": valueTextClass,
+          "x": 500,
+          "y": 375,
+          "font-size": "700%",
+          "font-family": "sans-serif",
+          "font-weight": "bold",
+          "text-anchor": "middle"
+        });
+        gaugeTitleElem = svg("text", {
+    //      "class": valueTextClass,
+          "class": "title",
+          "x": 500,
+          "y": 500,
+         // "font-size": "80%",
+          "font-family": "sans-serif",
+         // "font-weight": "bold",
+          "text-anchor": "middle"
+        });
+        
+        gaugeValuePath = svg("path", {
+          "class": valueDialClass,
+          "fill": "transparent",
+          "stroke": "#666",
+          "stroke-width": 25,
+          "d": pathString(radius, startAngle, startAngle) // value of 0
+        });
+        gaugeTitleElem.textContent = title;
+
+        var angle = getAngle(100, 360 - Math.abs(startAngle - endAngle));
+        var flag = angle <= 180 ? 0 : 1;
+        var gaugeElement = svg("svg", {"viewBox": "0 0 1000 525", "class": gaugeClass},
+          [
+            svg("path", {
+              "class": dialClass,
+              "fill": "transparent",
+              "stroke": "#eee",
+              "stroke-width": 20,
+              "d": pathString(radius, startAngle, endAngle, flag)
+            }),
+            gaugeTextElem,
+            gaugeTitleElem,
+            gaugeValuePath
+          ]
+        );
+        elem.appendChild(gaugeElement);
+      }
+
+      function updateGauge(theValue) {
+        var val = getValueInPercentage(theValue, limit),
+            // angle = getAngle(val, 360 - Math.abs(endAngle - startAngle)),
+            angle = getAngle(val, 360 - Math.abs(startAngle - endAngle)),
+            // this is because we are using arc greater than 180deg
+            flag = angle <= 180 ? 0 : 1;
+        (displayValue && (gaugeTextElem.textContent = valueLabelRender.call(opts, theValue)));
+        gaugeValuePath.setAttribute("d", pathString(radius, startAngle, angle + startAngle, flag));
+      }
+
+      instance = {
+        setMaxValue: function(max) {
+          limit = max;
+        },
+        setValue: function(val) {
+          value = normalize(val, limit);
+          updateGauge(value);
+        },
+        setValueAnimated: function(val, duration) {
+        	var oldVal = value;
+          value = normalize(val, limit);
+          if(oldVal === value) {
+            return;
+          }
+          Animation({
+            start: oldVal || 0,
+            end: value,
+            duration: duration || 1,
+            step: function(val) {
+              updateGauge(Math.round(val * 100) / 100);
+            }
+          });
+        },
+        getValue: function() {
+          return value;
+        }
+      };
+
+      initializeGauge(gaugeContainer);
+      updateGauge(value);
+      return instance;
+    };
+  })();
+
+  return Gauge;
+});

+ 1 - 0
node_modules/svg-gauge/index.js.bkp

@@ -0,0 +1 @@
+module.exports = require("./src/gauge");

+ 89 - 0
node_modules/svg-gauge/package.json

@@ -0,0 +1,89 @@
+{
+  "_args": [
+    [
+      "svg-gauge",
+      "/Users/janbranbergen/tmp/ionic/myApp"
+    ]
+  ],
+  "_from": "svg-gauge@latest",
+  "_id": "svg-gauge@1.0.2",
+  "_inCache": true,
+  "_installable": true,
+  "_location": "/svg-gauge",
+  "_nodeVersion": "5.1.0",
+  "_npmOperationalInternal": {
+    "host": "packages-18-east.internal.npmjs.com",
+    "tmp": "tmp/svg-gauge-1.0.2.tgz_1478079554462_0.9864934992510825"
+  },
+  "_npmUser": {
+    "email": "aniketn3@gmail.com",
+    "name": "naikus"
+  },
+  "_npmVersion": "3.3.12",
+  "_phantomChildren": {},
+  "_requested": {
+    "name": "svg-gauge",
+    "raw": "svg-gauge",
+    "rawSpec": "",
+    "scope": null,
+    "spec": "latest",
+    "type": "tag"
+  },
+  "_requiredBy": [
+    "/"
+  ],
+  "_resolved": "https://registry.npmjs.org/svg-gauge/-/svg-gauge-1.0.2.tgz",
+  "_shasum": "824c09766faf6d27986015e29d3867ce32a51b85",
+  "_shrinkwrap": null,
+  "_spec": "svg-gauge",
+  "_where": "/Users/janbranbergen/tmp/ionic/myApp",
+  "author": {
+    "name": "aniketn3@gmail.com"
+  },
+  "bugs": {
+    "url": "https://github.com/naikus/svg-gauge/issues"
+  },
+  "dependencies": {},
+  "description": "Minimal SVG animated gauge with zero dependencies",
+  "devDependencies": {
+    "del": "^2.2.1",
+    "event-stream": "^3.3.3",
+    "gulp": "^3.9.1",
+    "gulp-concat": "^2.6.0",
+    "gulp-jshint": "^2.0.1",
+    "gulp-uglify": "^1.5.4"
+  },
+  "directories": {
+    "test": "test"
+  },
+  "dist": {
+    "shasum": "824c09766faf6d27986015e29d3867ce32a51b85",
+    "tarball": "https://registry.npmjs.org/svg-gauge/-/svg-gauge-1.0.2.tgz"
+  },
+  "gitHead": "91c508af87fc40ac7126dd0ca47cc06634a90012",
+  "homepage": "https://github.com/naikus/svg-gauge#readme",
+  "keywords": [
+    "Animation",
+    "Gauge",
+    "SVG"
+  ],
+  "license": "MIT",
+  "main": "index.js",
+  "maintainers": [
+    {
+      "name": "naikus",
+      "email": "aniketn3@gmail.com"
+    }
+  ],
+  "name": "svg-gauge",
+  "optionalDependencies": {},
+  "readme": "ERROR: No README data found!",
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/naikus/svg-gauge.git"
+  },
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1"
+  },
+  "version": "1.0.2"
+}

+ 280 - 0
node_modules/svg-gauge/src/gauge.js

@@ -0,0 +1,280 @@
+/* global window, define, module */
+(function(global, factory) {
+  var Gauge = factory(global);
+  if(typeof define === "function" && define.amd) {
+    // AMD support
+    define(function() {return Gauge;});
+  }else if(typeof module === "object" && module.exports) {
+    // CommonJS support
+    module.exports = Gauge;
+  }else {
+    // We are probably running in the browser
+    global.Gauge = Gauge;
+  }
+})(typeof window === "undefined" ? this : window, function(global, undefined) {
+
+  var document = global.document,
+    requestAnimationFrame = (global.requestAnimationFrame ||
+        global.mozRequestAnimationFrame ||
+        global.webkitRequestAnimationFrame ||
+        global.msRequestAnimationFrame ||
+        function(cb) {
+          return setTimeout(cb, 1000 / 60);
+        });
+
+  // EXPERIMENTAL!!
+  /**
+   * Simplistic animation function for animating the gauge. That's all!
+   * Options are:
+   * {
+   *  duration: 1,    // In seconds
+   *  start: 0,       // The start value
+   *  end: 100,       // The end value
+   *  step: function, // REQUIRED! The step function that will be passed the value and does something
+   *  easing: function // The easing function. Default is easeInOutCubic
+   * }
+   */
+  function Animation(options) {
+    var duration = options.duration,
+        currentIteration = 1,
+        iterations = 60 * duration,
+        start = options.start || 0,
+        end = options.end,
+        change = end - start,
+        step = options.step,
+        easing = options.easing || function easeInOutCubic(pos) {
+          // https://github.com/danro/easing-js/blob/master/easing.js
+          if ((pos/=0.5) < 1) return 0.5*Math.pow(pos,3);
+          return 0.5 * (Math.pow((pos-2),3) + 2);
+        };
+
+    function animate() {
+      var progress = (currentIteration++) / iterations;
+      var value = change * easing(progress) + start;
+      // console.log(progress + ", " + value);
+      step(value);
+      if(progress < 1) {
+        requestAnimationFrame(animate);
+      }
+    }
+    // start!
+    requestAnimationFrame(animate);
+  }
+
+
+
+  var Gauge = (function() {
+
+    var SVG_NS = "http://www.w3.org/2000/svg";
+
+    var GaugeDefaults = {
+      dialStartAngle: 135,
+      dialEndAngle: 45,
+      centerX: 500,
+      centerY: 500,
+      radius: 400
+    };
+
+    /**
+     * A utility function to create SVG dom tree
+     * @param {String} name The SVG element name
+     * @param {Object} attrs The attributes as they appear in DOM e.g. stroke-width and not strokeWidth
+     * @param {Array} children An array of children (can be created by this same function)
+     * @return The SVG element
+     */
+    function svg(name, attrs, children) {
+      var elem = document.createElementNS(SVG_NS, name);
+      for(var attrName in attrs) {
+        elem.setAttribute(attrName, attrs[attrName]);
+      }
+
+      if(children) {
+        children.forEach(function(c) {
+          elem.appendChild(c);
+        });
+      }
+      return elem;
+    }
+
+    /**
+     * Translates percentage value to angle. e.g. If gauge span angle is 180deg, then 50%
+     * will be 90deg
+     */
+    function getAngle(percentage, gaugeSpanAngle) {
+      return percentage * gaugeSpanAngle / 100;
+    }
+
+    function normalize(value, limit) {
+      var val = Number(value);
+      if(val > limit) return limit;
+      if(val < 0) return 0;
+      return val;
+    }
+
+    function getValueInPercentage(value, limit) {
+      return 100 * value / limit;
+    }
+
+    /**
+     * Gets cartesian co-ordinates for a specified radius and angle (in degrees)
+     * @param cx {Number} The center x co-oriinate
+     * @param cy {Number} The center y co-ordinate
+     * @param radius {Number} The radius of the circle
+     * @param angle {Number} The angle in degrees
+     * @return An object with x,y co-ordinates
+     */
+    function getCartesian(cx, cy, radius, angle) {
+      var rad = angle * Math.PI / 180;
+      return {
+        x: Math.round((cx + radius * Math.cos(rad)) * 1000) / 1000,
+        y: Math.round((cy + radius * Math.sin(rad)) * 1000) / 1000
+      };
+    }
+
+    // Returns start and end points for dial
+    // i.e. starts at 135deg ends at 45deg with large arc flag
+    // REMEMBER!! angle=0 starts on X axis and then increases clockwise
+    function getDialCoords(radius, startAngle, endAngle) {
+      var cx = GaugeDefaults.centerX,
+          cy = GaugeDefaults.centerY;
+      return {
+        end: getCartesian(cx, cy, radius, endAngle),
+      	start: getCartesian(cx, cy, radius, startAngle)
+      };
+    }
+
+    function defaultLabelRenderer(theValue) {
+      return Math.round(theValue);
+    }
+
+    /**
+     * Creates a Gauge object. This should be called without the 'new' operator. Various options
+     * can be passed for the gauge:
+     * {
+     *    dialStartAngle: The angle to start the dial. MUST be greater than dialEndAngle. Default 135deg
+     *    dialEndAngle: The angle to end the dial. Default 45deg
+     *    radius: The gauge's radius. Default 400
+     *    max: The maximum value of the gauge. Default 100
+     *    value: The starting value of the gauge. Default 0
+     *    label: The function on how to render the center label (Should return a value)
+     * }
+     * @param {Element} elem The DOM into which to render the gauge
+     * @param {Object} opts The gauge options
+     * @return a Gauge object
+     */
+    return function Gauge(elem, opts) {
+      opts = opts || {};
+      var gaugeContainer = elem,
+          limit = opts.max || 100,
+          value = normalize(opts.value || 0, limit),
+          radius = opts.radius || 400,
+          displayValue = opts.showValue === false ? false : true,
+          valueLabelRender = typeof (opts.label) === "function" ? opts.label : defaultLabelRenderer,
+          startAngle = typeof (opts.dialStartAngle) === "undefined" ? 135 : opts.dialStartAngle,
+          endAngle = typeof (opts.dialEndAngle) === "undefined" ? 45 : opts.dialEndAngle,
+          valueDialClass = typeof (opts.valueDialClass) === "undefined" ? 'value' : opts.valueDialClass,
+          valueTextClass = typeof (opts.valueTextClass) === "undefined" ? 'value-text' : opts.valueTextClass,
+          dialClass = typeof (opts.dialClass) === "undefined" ? 'dial' : opts.dialClass,
+          gaugeClass = typeof (opts.gaugeClass) === "undefined" ? 'gauge' : opts.gaugeClass,
+          gaugeTextElem,
+          gaugeValuePath,
+          instance;
+
+      if(startAngle < endAngle) {
+        console.log("WARNING! Start angle should be greater than end angle. Swapping");
+        var tmp = startAngle;
+        startAngle = endAngle;
+        endAngle = tmp;
+      }
+
+      function pathString(radius, startAngle, endAngle, largeArc) {
+        var coords = getDialCoords(radius, startAngle, endAngle),
+            start = coords.start,
+            end = coords.end,
+            largeArcFlag = typeof(largeArc) === "undefined" ? 1 : largeArc;
+
+        return ["M", start.x, start.y, "A", radius, radius, "0", largeArcFlag, "1", end.x, end.y].join(" ");
+      }
+
+      function initializeGauge(elem) {
+        gaugeTextElem = svg("text", {
+          "class": valueTextClass,
+          "x": 500,
+          "y": 550,
+          "font-size": "700%",
+          "font-family": "sans-serif",
+          "font-weight": "bold",
+          "text-anchor": "middle"
+        });
+        gaugeValuePath = svg("path", {
+          "class": valueDialClass,
+          "fill": "transparent",
+          "stroke": "#666",
+          "stroke-width": 25,
+          "d": pathString(radius, startAngle, startAngle) // value of 0
+        });
+
+        var angle = getAngle(100, 360 - Math.abs(startAngle - endAngle));
+        var flag = angle <= 180 ? 0 : 1;
+        var gaugeElement = svg("svg", {"viewBox": "0 0 1000 1000", "class": gaugeClass},
+          [
+            svg("path", {
+              "class": dialClass,
+              "fill": "transparent",
+              "stroke": "#eee",
+              "stroke-width": 20,
+              "d": pathString(radius, startAngle, endAngle, flag)
+            }),
+            gaugeTextElem,
+            gaugeValuePath
+          ]
+        );
+        elem.appendChild(gaugeElement);
+      }
+
+      function updateGauge(theValue) {
+        var val = getValueInPercentage(theValue, limit),
+            // angle = getAngle(val, 360 - Math.abs(endAngle - startAngle)),
+            angle = getAngle(val, 360 - Math.abs(startAngle - endAngle)),
+            // this is because we are using arc greater than 180deg
+            flag = angle <= 180 ? 0 : 1;
+        (displayValue && (gaugeTextElem.textContent = valueLabelRender.call(opts, theValue)));
+        gaugeValuePath.setAttribute("d", pathString(radius, startAngle, angle + startAngle, flag));
+      }
+
+      instance = {
+        setMaxValue: function(max) {
+          limit = max;
+        },
+        setValue: function(val) {
+          value = normalize(val, limit);
+          updateGauge(value);
+        },
+        setValueAnimated: function(val, duration) {
+        	var oldVal = value;
+          value = normalize(val, limit);
+          if(oldVal === value) {
+            return;
+          }
+          Animation({
+            start: oldVal || 0,
+            end: value,
+            duration: duration || 1,
+            step: function(val) {
+              updateGauge(Math.round(val * 100) / 100);
+            }
+          });
+        },
+        getValue: function() {
+          return value;
+        }
+      };
+
+      initializeGauge(gaugeContainer);
+      updateGauge(value);
+      return instance;
+    };
+  })();
+
+  return Gauge;
+});

+ 58 - 0
package.json

@@ -0,0 +1,58 @@
+{
+  "name": "meBarista",
+  "author": "Digital Things",
+  "homepage": "https://mecoffee.nl/",
+  "private": true,
+  "scripts": {
+    "clean": "ionic-app-scripts clean",
+    "build": "ionic-app-scripts build",
+    "ionic:build": "ionic-app-scripts build",
+    "ionic:serve": "ionic-app-scripts serve"
+  },
+  "dependencies": {
+    "@angular/common": "2.4.8",
+    "@angular/compiler": "2.4.8",
+    "@angular/compiler-cli": "2.4.8",
+    "@angular/core": "2.4.8",
+    "@angular/forms": "2.4.8",
+    "@angular/http": "2.4.8",
+    "@angular/platform-browser": "2.4.8",
+    "@angular/platform-browser-dynamic": "2.4.8",
+    "@angular/platform-server": "2.4.8",
+    "@ionic-native/ble": "^3.4.4",
+    "@ionic-native/bluetooth-serial": "^3.4.4",
+    "@ionic-native/core": "^3.6.1",
+    "@ionic-native/file": "^3.6.1",
+    "@ionic-native/in-app-browser": "^3.14.0",
+    "@ionic-native/splash-screen": "3.1.0",
+    "@ionic-native/status-bar": "3.1.0",
+    "@ionic/storage": "2.0.0",
+    "bindings": "^1.2.1",
+    "buffer": "^5.0.6",
+    "chart.js": "^2.5.0",
+    "ionic-angular": "2.3.0",
+    "ionicons": "3.0.0",
+    "rxjs": "5.0.1",
+    "svg-gauge": "^1.0.2",
+    "sw-toolbox": "3.4.0",
+    "types": "^0.1.1",
+    "typings": "^2.1.1",
+    "zone.js": "0.7.2"
+  },
+  "devDependencies": {
+    "@ionic/app-scripts": "1.1.4",
+    "typescript": "2.0.9"
+  },
+  "cordovaPlugins": [
+    "cordova-plugin-statusbar",
+    "cordova-plugin-console",
+    "cordova-plugin-device",
+    "cordova-plugin-whitelist",
+    "cordova-plugin-splashscreen",
+    "ionic-plugin-keyboard",
+    "cordova-plugin-bluetooth-serial"
+  ],
+  "cordovaPlatforms": [
+  ],
+  "description": "meBarista, the App to control meCoffee"
+}

BIN
resources/android/icon/drawable-hdpi-icon.png


BIN
resources/android/icon/drawable-ldpi-icon.png


BIN
resources/android/icon/drawable-mdpi-icon.png


BIN
resources/android/icon/drawable-xhdpi-icon.png


BIN
resources/android/icon/drawable-xxhdpi-icon.png


BIN
resources/android/icon/drawable-xxxhdpi-icon.png


BIN
resources/android/splash/drawable-land-hdpi-screen.png


BIN
resources/android/splash/drawable-land-ldpi-screen.png


BIN
resources/android/splash/drawable-land-mdpi-screen.png


BIN
resources/android/splash/drawable-land-xhdpi-screen.png


BIN
resources/android/splash/drawable-land-xxhdpi-screen.png


BIN
resources/android/splash/drawable-land-xxxhdpi-screen.png


BIN
resources/android/splash/drawable-port-hdpi-screen.png


BIN
resources/android/splash/drawable-port-ldpi-screen.png


BIN
resources/android/splash/drawable-port-mdpi-screen.png


BIN
resources/android/splash/drawable-port-xhdpi-screen.png


BIN
resources/android/splash/drawable-port-xxhdpi-screen.png


BIN
resources/android/splash/drawable-port-xxxhdpi-screen.png


BIN
resources/icon.png


BIN
resources/ios/icon/icon-40.png


BIN
resources/ios/icon/icon-40@2x.png


BIN
resources/ios/icon/icon-40@3x.png


BIN
resources/ios/icon/icon-50.png


BIN
resources/ios/icon/icon-50@2x.png


BIN
resources/ios/icon/icon-60.png


BIN
resources/ios/icon/icon-60@2x.png


BIN
resources/ios/icon/icon-60@3x.png


BIN
resources/ios/icon/icon-72.png


BIN
resources/ios/icon/icon-72@2x.png


BIN
resources/ios/icon/icon-76.png


BIN
resources/ios/icon/icon-76@2x.png


BIN
resources/ios/icon/icon-83.5@2x.png


BIN
resources/ios/icon/icon-small.png


BIN
resources/ios/icon/icon-small@2x.png


BIN
resources/ios/icon/icon-small@3x.png


BIN
resources/ios/icon/icon.png


BIN
resources/ios/icon/icon@2x.png


BIN
resources/ios/splash/Default-568h@2x~iphone.png


BIN
resources/ios/splash/Default-667h.png


BIN
resources/ios/splash/Default-736h.png


BIN
resources/ios/splash/Default-Landscape-736h.png


BIN
resources/ios/splash/Default-Landscape@2x~ipad.png


BIN
resources/ios/splash/Default-Landscape~ipad.png


BIN
resources/ios/splash/Default-Portrait@2x~ipad.png


BIN
resources/ios/splash/Default-Portrait~ipad.png


BIN
resources/ios/splash/Default@2x~iphone.png


BIN
resources/ios/splash/Default~iphone.png


BIN
resources/splash.png


+ 71 - 0
src/app/app.component.ts

@@ -0,0 +1,71 @@
+import { Component, ViewChild } from '@angular/core';
+import { Nav, Platform } from 'ionic-angular';
+import { StatusBar } from '@ionic-native/status-bar';
+import { SplashScreen } from '@ionic-native/splash-screen';
+
+import { Page1 } from '../pages/page1/page1';
+import { PressurePage } from '../pages/pressure/pressure';
+import { PreinfusionPage } from '../pages/preinfusion/preinfusion';
+import { TemperaturePage } from '../pages/temperature/temperature';
+import { TimersPage } from '../pages/timers/timers';
+import { PIDPage } from '../pages/pid/pid';
+import { HardwarePage } from '../pages/hardware/hardware';
+import { InstallationPage } from '../pages/installation/installation';
+
+import { AboutPage } from '../pages/about/about';
+import { DemosPage } from '../pages/demos/demos';
+
+import { MeBaristaService } from '../providers/me-barista-service';
+
+@Component({
+  templateUrl: 'app.html'
+  //,
+  //providers: [MeBaristaService]
+})
+
+export class MyApp {
+  @ViewChild(Nav) nav: Nav;
+
+  rootPage: any = Page1;
+
+  pages: Array<{title: string, component: any}>;
+
+  constructor(public platform: Platform, public statusBar: StatusBar, public splashScreen: SplashScreen, public abc: MeBaristaService) {
+    this.initializeApp();
+
+    // used for an example of ngFor and navigation
+    this.pages = [
+      
+      { title: 'Home', component: Page1 },
+      { title: 'Temperature', component: TemperaturePage },
+      { title: 'Preinfusion', component: PreinfusionPage },
+      { title: 'Pressure', component: PressurePage },
+      { title: 'Timers', component: TimersPage },
+      { title: 'PID', component: PIDPage },
+      { title: 'Hardware', component: HardwarePage },
+      { title: 'Demos', component: DemosPage },
+      { title: 'Installation', component: InstallationPage },
+      { title: 'About', component: AboutPage }
+
+    ];
+
+  }
+
+  initializeApp() {
+    this.platform.ready().then(() => {
+      // Okay, so the platform is ready and our plugins are available.
+      // Here you can do any higher level native things you might need.
+      this.statusBar.styleDefault();
+      this.splashScreen.hide();
+
+      this.abc.startup();
+
+    });
+  }
+
+  openPage(page) {
+    // Reset the content nav to have just this page
+    // we wouldn't want the back button to show in this scenario
+    this.nav.setRoot(page.component);
+  }
+}

+ 19 - 0
src/app/app.html

@@ -0,0 +1,19 @@
+<ion-menu [content]="content">
+  <ion-header>
+    <ion-toolbar>
+      <ion-title>Menu</ion-title>
+    </ion-toolbar>
+  </ion-header>
+
+  <ion-content>
+    <ion-list>
+      <button menuClose ion-item *ngFor="let p of pages" (click)="openPage(p)">
+        {{p.title}}
+      </button>
+    </ion-list>
+  </ion-content>
+
+</ion-menu>
+
+<!-- Disable swipe-to-go-back because it's poor UX to combine STGB with side menus -->
+<ion-nav [root]="rootPage" #content swipeBackEnabled="false"></ion-nav>

+ 72 - 0
src/app/app.module.ts

@@ -0,0 +1,72 @@
+import { NgModule, ErrorHandler } from '@angular/core';
+import { IonicApp, IonicModule, IonicErrorHandler } from 'ionic-angular';
+import { MyApp } from './app.component';
+import { Page1 } from '../pages/page1/page1';
+import { PressurePage } from '../pages/pressure/pressure';
+import { PreinfusionPage } from '../pages/preinfusion/preinfusion';
+import { TemperaturePage } from '../pages/temperature/temperature';
+import { TimersPage } from '../pages/timers/timers';
+import { PIDPage } from '../pages/pid/pid';
+import { HardwarePage } from '../pages/hardware/hardware';
+import { InstallationPage } from '../pages/installation/installation';
+import { AboutPage } from '../pages/about/about';
+import { DemosPage } from '../pages/demos/demos';
+
+import { SettingBasePage } from '../pages/setting-base/setting-base';
+
+import { StatusBar } from '@ionic-native/status-bar';
+
+import { SplashScreen } from '@ionic-native/splash-screen';
+
+import { InAppBrowser } from '@ionic-native/in-app-browser';
+
+import { McRangeComponent } from '../components/mc-range/mc-range';
+
+import { MeBaristaService } from '../providers/me-barista-service';
+
+import { File } from '@ionic-native/file';
+
+@NgModule({
+  declarations: [
+    MyApp,
+    Page1,
+    PressurePage,
+    PreinfusionPage,
+    TemperaturePage,
+    TimersPage,
+    HardwarePage,
+    InstallationPage,
+    PIDPage,
+    AboutPage,
+    DemosPage,
+    McRangeComponent,
+    SettingBasePage
+  ],
+  imports: [
+    IonicModule.forRoot(MyApp)
+  ],
+  bootstrap: [IonicApp],
+  entryComponents: [
+    MyApp,
+    Page1,
+    PressurePage,
+    PreinfusionPage,
+    TemperaturePage,
+    TimersPage,
+    HardwarePage,
+    InstallationPage,
+    PIDPage,
+    AboutPage,
+    DemosPage,
+  ],
+  providers: [
+    InAppBrowser,
+    StatusBar,
+    SplashScreen,
+    MeBaristaService,
+    File,
+    ,
+    {provide: ErrorHandler, useClass: IonicErrorHandler}
+  ]
+})
+export class AppModule {}

+ 16 - 0
src/app/app.scss

@@ -0,0 +1,16 @@
+// http://ionicframework.com/docs/v2/theming/
+
+
+// App Global Sass
+// --------------------------------------------------
+// Put style rules here that you want to apply globally. These
+// styles are for the entire app and not just one component.
+// Additionally, this file can be also used as an entry point
+// to import other Sass files to be included in the output CSS.
+//
+// Shared Sass variables, which can be used to adjust Ionic's
+// default Sass variables, belong in "theme/variables.scss".
+//
+// To declare rules for a specific mode, create a child rule
+// for the .md, .ios, or .wp mode classes. The mode class is
+// automatically applied to the <body> element in the app.

+ 5 - 0
src/app/main.ts

@@ -0,0 +1,5 @@
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app.module';
+
+platformBrowserDynamic().bootstrapModule(AppModule);

File diff suppressed because it is too large
+ 3891 - 0
src/assets/demos/56273d393cab0


BIN
src/assets/icon/favicon.ico


+ 3 - 0
src/components/mc-range/mc-range.scss

@@ -0,0 +1,3 @@
+mc-range {
+
+}

+ 114 - 0
src/components/mc-range/mc-range.ts

@@ -0,0 +1,114 @@
+import { ChangeDetectorRef, Component, ElementRef, Renderer, Optional, forwardRef, Input  } from '@angular/core';
+
+import { Platform, Config, DomController, Form, Haptic, Item, Range } from 'ionic-angular';
+
+import { NG_VALUE_ACCESSOR } from '@angular/forms';
+
+class TempRange extends Range {
+    static decorators = undefined;
+}
+
+export const MCRANGE_VALUE_ACCESSOR: any = {
+  provide: NG_VALUE_ACCESSOR,
+  useExisting: forwardRef(() => McRangeComponent),
+  multi: true
+};
+
+@Component({
+  selector: 'mc-range',
+  template:
+    '<ng-content select="[range-left]"></ng-content>' +
+    '<div class="range-slider" #slider>' +
+      '<div class="range-tick" *ngFor="let t of _ticks" [style.left]="t.left" [class.range-tick-active]="t.active" role="presentation"></div>' +
+      '<div class="range-bar" role="presentation"></div>' +
+      '<div class="range-bar range-bar-active" [style.left]="_barL" [style.right]="_barR" #bar role="presentation"></div>' +
+      '<div class="range-knob-handle" (ionIncrease)="_keyChg(true, false)" (ionDecrease)="_keyChg(false, false)" [ratio]="_ratioA" [val]="_valA" [pin]="_pin" [pressed]="true" [min]="_min" [max]="_max" [disabled]="_disabled" [labelId]="_lblId"></div>' +
+      '<div class="range-knob-handle" (ionIncrease)="_keyChg(true, true)" (ionDecrease)="_keyChg(false, true)" [ratio]="_ratioB" [val]="_valB" [pin]="_pin" [pressed]="_pressedB" [min]="_min" [max]="_max" [disabled]="_disabled" [labelId]="_lblId" *ngIf="_dual"></div>' +
+    '</div><span style="top: -1em; font-size: 80%; position: relative; float: left;">{{_min}}{{_unit}}</span><span style="top: -1em; font-size: 80%; position: relative;float:right;">{{_max}}{{_unit}}</span>' +
+    '<ng-content select="[range-right]"></ng-content>',
+    host: {
+    '[class.range-disabled]': '_disabled',
+    '[class.range-pressed]': '_pressed',
+    '[class.range-has-pin]': '_pin'
+  },
+  providers: [MCRANGE_VALUE_ACCESSOR]
+})
+export class McRangeComponent extends TempRange {
+
+  //text: string;
+  _description: string;
+  _unit: string;
+  _plt_mf: Platform;
+
+  constructor(
+    _form: Form,
+    _haptic: Haptic,
+    @Optional() _item: Item,
+    config: Config,
+    _plt: Platform,
+    elementRef: ElementRef,
+    renderer: Renderer,
+    _dom: DomController,
+    _cd: ChangeDetectorRef
+
+  ) {
+    super( _form, _haptic, _item, config, _plt, elementRef, renderer, _dom, _cd );
+    
+    this._description = '';
+    this._unit = '';
+    this._plt_mf = _plt;
+
+    // console.log('Hello McRange Component');
+    // this.text = 'Hello World';
+  }
+
+  /**
+   * @input {number} Maximum integer value of the range. Defaults to `100`.
+   */
+  @Input()
+  get description(): string {
+    return this._description;
+  }
+  set description(val: string) {
+    this._description = val;
+  }
+
+  @Input()
+  get step(): number {
+    return this._step;
+  }
+  set step(val: number) {
+    if (!isNaN(val) && val > 0) {
+      this._step = val;
+    }
+  }
+
+  @Input()
+  get unit(): string {
+    return this._unit;
+  }
+  set unit(val: string) {
+      this._unit = ' ' + val;
+  }
+
+  _ratioToValue(ratio: number) {
+    //this._step = 0.1;
+    ratio = (this._max - this._min) * ratio;
+    ratio = Math.round(ratio / this._step) * this._step + this._min;
+    
+    return Math.round( ratio * 100 ) / 100; // strange bug with 10th significant digit
+ //   return clamp(this._min, ratio, this._max) + 12.3;
+  }
+
+  _pointerDown(ev: UIEvent): boolean { 
+
+    // console.log( ev ); 
+
+    if( ev.srcElement.className != "range-slider" || !this._plt_mf.is( 'ios' ) )
+      return super._pointerDown( ev );
+
+    return false;
+  
+  }
+
+}

+ 14 - 0
src/declarations.d.ts

@@ -0,0 +1,14 @@
+/*
+  Declaration files are how the Typescript compiler knows about the type information(or shape) of an object.
+  They're what make intellisense work and make Typescript know all about your code.
+
+  A wildcard module is declared below to allow third party libraries to be used in an app even if they don't
+  provide their own type declarations.
+
+  To learn more about using third party libraries in an Ionic app, check out the docs here:
+  http://ionicframework.com/docs/v2/resources/third-party-libs/
+
+  For more info on type definition files, check out the Typescript docs here:
+  https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html
+*/
+declare module '*';

+ 41 - 0
src/index.html

@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html lang="en" dir="ltr">
+<head>
+  <meta charset="UTF-8">
+  <title>Ionic App</title>
+  <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">
+  <meta name="format-detection" content="telephone=no">
+  <meta name="msapplication-tap-highlight" content="no">
+
+  <link rel="icon" type="image/x-icon" href="assets/icon/favicon.ico">
+  <link rel="manifest" href="manifest.json">
+  <meta name="theme-color" content="#4e8ef7">
+ 
+  <!-- cordova.js required for cordova apps -->
+  <script src="cordova.js"></script>
+
+  <!-- un-comment this code to enable service worker
+  <script>
+    if ('serviceWorker' in navigator) {
+      navigator.serviceWorker.register('service-worker.js')
+        .then(() => console.log('service worker installed'))
+        .catch(err => console.log('Error', err));
+    }
+  </script>-->
+
+  <link href="build/main.css" rel="stylesheet">
+
+</head>
+<body>
+
+  <!-- Ionic's root component and where the app will load -->
+  <ion-app></ion-app>
+
+  <!-- The polyfills js is generated during the build process -->
+  <script src="build/polyfills.js"></script>
+
+  <!-- The bundle js is generated during the build process -->
+  <script src="build/main.js"></script>
+
+</body>
+</html>

+ 13 - 0
src/manifest.json

@@ -0,0 +1,13 @@
+{
+  "name": "Ionic",
+  "short_name": "Ionic",
+  "start_url": "index.html",
+  "display": "standalone",
+  "icons": [{
+    "src": "assets/imgs/logo.png",
+    "sizes": "512x512",
+    "type": "image/png"
+  }],
+  "background_color": "#4e8ef7",
+  "theme_color": "#4e8ef7"
+}

+ 28 - 0
src/pages/about/about.html

@@ -0,0 +1,28 @@
+<ion-header>
+  <ion-navbar>   
+    <ion-spinner #spinner [hidden]="!spinner_show" [paused]="!this.abc.scanning" (click)="this.abc.bt_discover()" style="float: right; margin-right: 1em; width: 1.5em; height: 1.5em;"></ion-spinner>
+    <span #tt42 [hidden]="spinner_show" style="float: right; margin-right: 1em;">{{temperature}}</span>
+
+    <button ion-button menuToggle>
+      <ion-icon name="menu"></ion-icon>
+    </button>
+    <ion-title>About</ion-title>
+  </ion-navbar>
+</ion-header>
+
+
+<ion-content padding>
+
+  <div style="margin-bottom: 2em;">
+  <i>meBarista</i> is the accompanying <i>App</i> to your <i>meCoffee</i> espresso controller. Control and monitor temperatures, PID, pressure, timer and preinfusion settings. Visit <i>meCoffee's</i> <a target="_blank" href="https://mecoffee.nl/">website</a> to learn more.
+	
+	</div>
+
+  <div>x{{this.abc.serial_result}}y</div>
+   <div style="position: absolute; bottom: 20%; left: 20%; right: 20%;">
+
+     <img style="width: 30vmin; height: 30vmin; display: block; margin-left: auto; margin-right: auto; " src="assets/icon/icon.png"/>
+
+     <div style="text-align: center;"><i>meBarista</i> 0.1.0</div>
+   </div>
+</ion-content>

+ 3 - 0
src/pages/about/about.scss

@@ -0,0 +1,3 @@
+page-about {
+
+}

+ 27 - 0
src/pages/about/about.ts

@@ -0,0 +1,27 @@
+import { Component, ChangeDetectorRef } from '@angular/core';
+import { NavController, NavParams } from 'ionic-angular';
+
+import { SettingBasePage } from '../setting-base/setting-base';
+import { MeBaristaService } from '../../providers/me-barista-service';
+
+@Component({
+  selector: 'page-about',
+  templateUrl: 'about.html'
+})
+export class AboutPage extends SettingBasePage {
+
+  constructor(public navCtrl: NavController, public navParams: NavParams, public cdr: ChangeDetectorRef, public abc: MeBaristaService)  { 
+
+    super( navCtrl, navParams, cdr, abc );
+
+  }
+
+  ionViewWillEnter() {
+
+    super.ionViewWillEnter( );
+
+    // this.init( { pd1p: 25, pd1i: 3, pd1d: 128, pd1lck: 120, pd1imn: 0, pd1imx: 20 } );
+
+  }
+
+}

+ 28 - 0
src/pages/demos/demos.html

@@ -0,0 +1,28 @@
+<ion-header>
+  <ion-navbar>   
+    <ion-spinner #spinner [hidden]="!spinner_show" [paused]="!this.abc.scanning" (click)="this.abc.bt_discover()" style="float: right; margin-right: 1em; width: 1.5em; height: 1.5em;"></ion-spinner>
+    <span #tt42 [hidden]="spinner_show" style="float: right; margin-right: 1em;">{{temperature}}</span>
+
+    <button ion-button menuToggle>
+      <ion-icon name="menu"></ion-icon>
+    </button>
+    <ion-title>Demos</ion-title>
+  </ion-navbar>
+</ion-header>
+
+<ion-content padding>
+
+<div style="margin-bottom: 2em;">
+  <p>
+  To see <i>meBarista</i> in action, it is possible to replay recorded meCoffee-sessions. This specific demo shows a warmup, a couple of warmup-shots ( ~ 3 seconds ) and an actual shot of about 25 seconds. Meanwhile <i>meBarista</i> acts like it is connected to an actual espresso-machine and it completely usable.
+  </p>
+
+  <p>
+  Demos run 10 times faster than actual time.
+  </p>
+
+</div>
+
+  <button ion-button id="demo"  (click)="doDemo()">Start demo</button>
+
+</ion-content>

+ 3 - 0
src/pages/demos/demos.scss

@@ -0,0 +1,3 @@
+page-demos {
+
+}

+ 33 - 0
src/pages/demos/demos.ts

@@ -0,0 +1,33 @@
+import { Component, ChangeDetectorRef } from '@angular/core';
+import { NavController, NavParams } from 'ionic-angular';
+
+import { SettingBasePage } from '../setting-base/setting-base';
+import { MeBaristaService } from '../../providers/me-barista-service';
+
+import { Page1 } from '../page1/page1';
+
+@Component({
+  selector: 'page-demos',
+  templateUrl: 'demos.html'
+})
+export class DemosPage extends SettingBasePage {
+
+  constructor(public navCtrl: NavController, public navParams: NavParams, public cdr: ChangeDetectorRef, public abc: MeBaristaService) {
+
+  	super( navCtrl, navParams, cdr, abc );
+
+  }
+
+  ionViewDidLoad() {
+    
+  }
+
+  doDemo( ) {
+
+    console.log( 'demos click' );
+  	this.abc.demo_start( );
+
+    this.navCtrl.setRoot( Page1 );
+  }
+
+}

+ 90 - 0
src/pages/hardware/hardware.html

@@ -0,0 +1,90 @@
+<ion-header>
+  <ion-navbar>   
+    <ion-spinner #spinner [hidden]="!spinner_show" [paused]="!this.abc.scanning" (click)="this.abc.bt_discover()" style="float: right; margin-right: 1em; width: 1.5em; height: 1.5em;"></ion-spinner>
+    <span #tt42 [hidden]="spinner_show" style="float: right; margin-right: 1em;">{{temperature}}</span>
+
+    <button ion-button menuToggle>
+      <ion-icon name="menu"></ion-icon>
+    </button>
+    <ion-title>Hardware</ion-title>
+  </ion-navbar>
+</ion-header>
+
+
+<ion-content padding>
+
+  <div style="margin-bottom: 2em;">
+  <i>meCoffee's</i> output ports are dynamically assignable to a particular function. Configure if you have multiple boiler elements or want to drive the pump with a LIVE signal instead of the default NEUTRAL.  
+	</div>
+
+<ion-item class="mecoffee">
+
+	<ion-label>Output 1 - NEUTRAL</ion-label>
+    <ion-select [(ngModel)]="pars.o0" style="float: right;">
+      <ion-option value="98">Boiler</ion-option>
+      <ion-option value="112">Pump</ion-option>
+      <ion-option value="118">Valve</ion-option>
+      <ion-option value="110">None</ion-option>
+    </ion-select>
+   
+</ion-item>
+
+<ion-item class="mecoffee">
+
+	<ion-label>Output 2 - LIVE</ion-label>
+    <ion-select [(ngModel)]="pars.o1" style="float: right;">
+      <ion-option value="98">Boiler</ion-option>
+      <ion-option value="112">Pump</ion-option>
+      <ion-option value="118">Valve</ion-option>
+      <ion-option value="110">None</ion-option>
+    </ion-select>
+   
+</ion-item>
+
+<ion-item class="mecoffee">
+
+	<ion-label>Output 3 - LIVE</ion-label>
+    <ion-select [(ngModel)]="pars.o2" style="float: right;">
+      <ion-option value="98">Boiler</ion-option>
+      <ion-option value="112">Pump</ion-option>
+      <ion-option value="118">Valve</ion-option>
+      <ion-option value="110">None</ion-option>
+    </ion-select>
+   
+</ion-item>
+
+ <div style="margin-bottom: 2em;">
+   When you have completed installing your <i>meCoffee</i> as a timer per step 9, your machine will presume the on-state after a power outage or unplugging and replugging in the wall socket. Enable to prevent warm up at cold boot.
+ </div>
+
+<ion-item class="mecoffee">
+
+	<ion-label>Installed as a timer</ion-label>
+    <ion-toggle [(ngModel)]="pars.tmrpwr" (ionChange)="notify($event)"  style="float:right;"></ion-toggle>
+ 
+ </ion-item>
+
+ <div style="margin-bottom: 2em;">
+   When you have completed installing your <i>meCoffee</i> as a timer per step 9 on a V5E or machine with builtin 30-minute inactivity timer, enable this to support your variant of the power button.
+ </div>
+
+<ion-item class="mecoffee">
+
+	<ion-label>Flipping power button / V5</ion-label>
+    <ion-toggle [(ngModel)]="pars.pwrflp" (ionChange)="notify($event)"  style="float:right;"></ion-toggle>
+ 
+ </ion-item>
+
+ <div class="row" style="display: flex; flex-direction: row; align-items: center; width: 100%;">
+    <div style="flex: 1 0 0;">
+      <button ion-button id="defaults" [disabled]="!this.showDefaults()" (click)="doDefaults()">Defaults</button>
+    </div>
+    <div style="flex: 4 0 0;">
+    <div style="float:right;">
+    <button ion-button id="reset" [disabled]="!this.showUndo()"  (click)="doUndo()">Undo</button>
+    <button ion-button id="update" [disabled]="!this.showUpdate()" (click)="doUpdate()">Update</button>
+    </div>
+    </div>
+ </div>
+
+</ion-content>

+ 15 - 0
src/pages/hardware/hardware.scss

@@ -0,0 +1,15 @@
+page-hardware {
+
+}
+
+.mecoffee .item-inner {
+	border: none !important;
+}
+
+.mecoffee .label-md {
+	color: black !important;
+}
+
+.mecoffee.item {
+	margin-bottom: 2em !important;
+}

+ 27 - 0
src/pages/hardware/hardware.ts

@@ -0,0 +1,27 @@
+import { Component, ChangeDetectorRef } from '@angular/core';
+import { NavController, NavParams } from 'ionic-angular';
+
+import { SettingBasePage } from '../setting-base/setting-base';
+import { MeBaristaService } from '../../providers/me-barista-service';
+
+@Component({
+  selector: 'page-hardware',
+  templateUrl: 'hardware.html'
+})
+export class HardwarePage extends SettingBasePage {
+
+  constructor(public navCtrl: NavController, public navParams: NavParams, public cdr: ChangeDetectorRef, public abc: MeBaristaService)  { 
+
+    super( navCtrl, navParams, cdr, abc );
+
+  }
+
+  ionViewWillEnter() {
+
+    super.ionViewWillEnter( );
+
+    this.init( { o0: 112, o1: 98, o2: 118, tmrpwr: false, pwrflp: false, s1ad: true } );
+
+  }
+
+}

+ 15 - 0
src/pages/installation/installation.html

@@ -0,0 +1,15 @@
+<ion-header>
+  <ion-navbar>   
+    <ion-spinner #spinner [hidden]="!spinner_show" [paused]="!this.abc.scanning" (click)="this.abc.bt_discover()" style="float: right; margin-right: 1em; width: 1.5em; height: 1.5em;"></ion-spinner>
+    <span #tt42 [hidden]="spinner_show" style="float: right; margin-right: 1em;">{{temperature}}</span>
+
+    <button ion-button menuToggle>
+      <ion-icon name="menu"></ion-icon>
+    </button>
+    <ion-title>Installation</ion-title>
+  </ion-navbar>
+</ion-header>
+
+<ion-content>
+<iframe src="https://mecoffee.nl/mecoffee/installation/?embedded=true" frameBorder="0" style="width: 100%; height: 100%;" > iFrame </iframe>
+</ion-content>

+ 3 - 0
src/pages/installation/installation.scss

@@ -0,0 +1,3 @@
+page-installation {
+
+}

+ 31 - 0
src/pages/installation/installation.ts

@@ -0,0 +1,31 @@
+import { Component, ChangeDetectorRef } from '@angular/core';
+import { NavController, NavParams } from 'ionic-angular';
+
+import { SettingBasePage } from '../setting-base/setting-base';
+import { MeBaristaService } from '../../providers/me-barista-service';
+
+import { InAppBrowser } from '@ionic-native/in-app-browser';
+
+@Component({
+  selector: 'page-installation',
+  templateUrl: 'installation.html'
+})
+export class InstallationPage extends SettingBasePage {
+
+  constructor(public navCtrl: NavController, public navParams: NavParams, public cdr: ChangeDetectorRef, public abc: MeBaristaService, private iab: InAppBrowser )  { 
+
+    super( navCtrl, navParams, cdr, abc );
+
+  }
+
+  ionViewWillEnter() {
+
+    super.ionViewWillEnter( );
+
+    this.init( {} );
+
+    // const browser = this.iab.create('https://mecoffee.nl/');
+
+  }
+
+}

+ 0 - 0
src/pages/page1/page1.html


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