diff --git a/.gitignore b/.gitignore
index 6bbc3ffa11b5ac3f4e9acca1b3eeeb70d886e60d..f0dad452b63ae5eed9c7d49a771727c7ca20a493 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,4 +42,9 @@ mongod.log*
 data/db/
 data/dbdump/
 
+# Generated Scenario Files
+scenarios/*/startScenario.sh
+scenarios/*/stopScenario.sh
+scenarios/*/ttyACM*.js
+scenarios/*/ttyACM*.log
 
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f178c8c26ec65b626e0b407d4327c125fb54a153..81a3e78efbcc8cb875a00173a48838f9c7d82154 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -3,12 +3,10 @@
 cmake_minimum_required(VERSION 2.8)
 
 find_package("ArmarXCore" REQUIRED
-    PATHS "$ENV{HOME}/armarx/Core/build/cmake"
-        "$ENV{HOME}/armarx-install/share/cmake/ArmarXCore"
-        "/org/share/archive/SFB588_RefDist/armarx/share/cmake/ArmarXCore"
+    PATHS "$ENV{HOME}/armarx/Core/build"
 )
 
-include(${ArmarXCore_CMAKE_DIR}/ArmarXProject.cmake)
+include(${ArmarXCore_USE_FILE})
 
 armarx_project("RobotAPI")
 
@@ -17,3 +15,5 @@ add_subdirectory(interface)
 
 install_project()
 
+
+add_subdirectory(scenarios)
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000000000000000000000000000000000000..f3b3611ca8958eb5cb7e65846a2639fd68855f88
--- /dev/null
+++ b/README.txt
@@ -0,0 +1 @@
+Stub to make the package tool work.
diff --git a/data/.gitkeep b/data/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..65df063943986e0bdf4c4ecf5e822d953a402fd7
--- /dev/null
+++ b/data/.gitkeep
@@ -0,0 +1,4 @@
+Git can only track files and not directory.
+
+Therefore this file is added to all empty directories
+which need to be available after a Git clone.
diff --git a/etc/CMakeLists.txt b/etc/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/etc/cmake/.gitkeep b/etc/cmake/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..65df063943986e0bdf4c4ecf5e822d953a402fd7
--- /dev/null
+++ b/etc/cmake/.gitkeep
@@ -0,0 +1,4 @@
+Git can only track files and not directory.
+
+Therefore this file is added to all empty directories
+which need to be available after a Git clone.
diff --git a/etc/cmake/UseRobotAPI.cmake b/etc/cmake/UseRobotAPI.cmake
new file mode 100644
index 0000000000000000000000000000000000000000..ddbb6ceada4a458087550ecb9ecff5032286c101
--- /dev/null
+++ b/etc/cmake/UseRobotAPI.cmake
@@ -0,0 +1 @@
+# This file contains macros for projects depending on RobotAPI
diff --git a/gpl-2.0.txt b/gpl-2.0.txt
new file mode 100644
index 0000000000000000000000000000000000000000..d159169d1050894d3ea3b98e1c965c4058208fe1
--- /dev/null
+++ b/gpl-2.0.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.
diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt
index 59c35598d7a14e225c75952ce619863348a8b74a..0c47b575be2866337362dffefeb5724d3c3951e5 100644
--- a/interface/CMakeLists.txt
+++ b/interface/CMakeLists.txt
@@ -4,4 +4,4 @@
 
 set(ROBOTAPI_INTERFACE_DEPEND ArmarXCore)
 # generate the interface library
-armarx_interfaces_generate_library(RobotAPI 0.1.0 0 ${ROBOTAPI_INTERFACE_DEPEND})
+armarx_interfaces_generate_library(RobotAPI 0.1.0 0 "${ROBOTAPI_INTERFACE_DEPEND}")
diff --git a/interface/slice/.gitkeep b/interface/slice/.gitkeep
new file mode 100644
index 0000000000000000000000000000000000000000..65df063943986e0bdf4c4ecf5e822d953a402fd7
--- /dev/null
+++ b/interface/slice/.gitkeep
@@ -0,0 +1,4 @@
+Git can only track files and not directory.
+
+Therefore this file is added to all empty directories
+which need to be available after a Git clone.
diff --git a/interface/slice/units/HapticUnit.ice b/interface/slice/units/HapticUnit.ice
new file mode 100644
index 0000000000000000000000000000000000000000..adcba86504049b042850b8b419368cfbe33f03ca
--- /dev/null
+++ b/interface/slice/units/HapticUnit.ice
@@ -0,0 +1,53 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI
+ * @author     Peter Kaiser <peter dot kaiser at kit dot edu>
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl-2.0.txt
+ *             GNU General Public License
+ */
+
+#ifndef _ARMARX_ROBOTAPI_UNITS_HAPTIC_SLICE_
+#define _ARMARX_ROBOTAPI_UNITS_HAPTIC_SLICE_
+
+#include <units/UnitInterface.ice>
+#include <core/UserException.ice>
+#include <core/BasicTypes.ice>
+#include <observers/VariantBase.ice>
+#include <observers/Matrix.ice>
+#include <observers/Timestamp.ice>
+#include <observers/ObserverInterface.ice>
+#include <robotstate/RobotState.ice>
+
+module armarx
+{
+    interface HapticUnitInterface extends armarx::SensorActorUnitInterface
+    {
+    };
+
+    interface HapticUnitListener
+    {
+        void reportSensorValues(string device, string name, MatrixFloatBase values, TimestampBase timestamp);
+    };
+
+    interface HapticUnitObserverInterface extends ObserverInterface, HapticUnitListener
+    {
+    };
+
+};
+
+#endif
diff --git a/scenarios/CMakeLists.txt b/scenarios/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..a2af915ff2dae8e5f67ab8a06f366d36a393987a
--- /dev/null
+++ b/scenarios/CMakeLists.txt
@@ -0,0 +1,2 @@
+
+add_subdirectory(WeissHapticSensorsUnitTest)
\ No newline at end of file
diff --git a/scenarios/MovePlatformTest/configs/MovePlatformExample.xml b/scenarios/MovePlatformTest/configs/MovePlatformExample.xml
index 3cc6e32ffc0257a9b33d627f6afe3a3d128c56fd..a2a7b1dfd1dcfd466f7e3a80cd4b416f9d67411b 100644
--- a/scenarios/MovePlatformTest/configs/MovePlatformExample.xml
+++ b/scenarios/MovePlatformTest/configs/MovePlatformExample.xml
@@ -40,9 +40,9 @@
             <z>0</z>
         </Item0>
     </targetPositions-->
-    <positionalAccuracy>10</positionAccuracy> <!--mm-->
-    <orientationalAccuracy>0.1</orientationAccuracy> <!--rad-->
-    <timeoutMoveTo>30000</timeoutMoveTO>
+    <positionalAccuracy>10</positionalAccuracy> <!--mm-->
+    <orientationalAccuracy>0.1</orientationalAccuracy> <!--rad-->
+    <timeoutMoveTo>30000</timeoutMoveTo>
   </StateParameters>
 </MovePlatformStateChart>
 
diff --git a/scenarios/MovePlatformTest/configs/MovePlatformExampleCloseToTable.xml b/scenarios/MovePlatformTest/configs/MovePlatformExampleCloseToTable.xml
index 791e75adbdeb3eed788b4846e60ee4f6f6ee3704..1d43e276b64b642df1011b90c87a44e4a33d2ada 100644
--- a/scenarios/MovePlatformTest/configs/MovePlatformExampleCloseToTable.xml
+++ b/scenarios/MovePlatformTest/configs/MovePlatformExampleCloseToTable.xml
@@ -20,9 +20,12 @@
             <z>0</z>
         </Item0>
     </targetPositions-->
-    <positionalAccuracy>10</positionAccuracy> <!--mm-->
-    <orientationalAccuracy>0.1</orientationAccuracy> <!--rad-->
-    <timeoutMoveTo>30000</timeoutMoveTO>
+    <positionalAccuracy>10</positionalAccuracy> <!--mm-->
+    <orientationalAccuracy>0.1</orientationalAccuracy> <!--rad-->
+    <timeoutMoveTo>30000</timeoutMoveTo> <!--ms-->
+    <!-- after the last target was reached, wait a certain amount of 
+         time (should be smaller than the timeout!) -->
+    <waitAfterLast>3000</waitAfterLast> <!--ms-->
   </StateParameters>
 </MovePlatformStateChart>
 
diff --git a/scenarios/MovePlatformToLandmarkTest/configs/MovePlatformToLandmarkExampleGraph.xml b/scenarios/MovePlatformToLandmarkTest/configs/MovePlatformToLandmarkExampleGraph.xml
index 068d84b2447bb5ae98e14dd9534c881e6af8f01a..9abf93e7e4ac967f2c82d2a4842921f9f030ab40 100644
--- a/scenarios/MovePlatformToLandmarkTest/configs/MovePlatformToLandmarkExampleGraph.xml
+++ b/scenarios/MovePlatformToLandmarkTest/configs/MovePlatformToLandmarkExampleGraph.xml
@@ -71,7 +71,7 @@
     <!-- each edge is defined as "<nodename>;<nodename>" (";" is a delimiter between names, 
          so it shouldn't appear in any node name), all edges are bidirectional between the provided nodes -->
     <landmarkEdges>
-		<Item0>start;1</Item0>
+		<Item0>Start;1</Item0>
 		<Item1>1;2</Item1>
 		<Item2>2;Handover</Item2>
 		<Item3>2;3</Item3>
diff --git a/scenarios/WeissHapticSensorsUnitTest/CMakeLists.txt b/scenarios/WeissHapticSensorsUnitTest/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..4d3f9b968efe4fc1217eef767d7ed8f436f223c4
--- /dev/null
+++ b/scenarios/WeissHapticSensorsUnitTest/CMakeLists.txt
@@ -0,0 +1,15 @@
+
+set(SCENARIO_COMPONENTS
+        WeissHapticSensorsUnitApp
+        #WeissHapticSensorApp
+        HapticObserverApp)
+
+
+# optional 3rd parameter: "path/to/global/config.cfg"
+armarx_scenario("WeissHapticSensorsUnitTest" "${SCENARIO_COMPONENTS}")
+
+#set(SCENARIO_CONFIGS
+#    config/ComponentName.optionalString.cfg
+#    )
+# optional 3rd parameter: "path/to/global/config.cfg"
+#armarx_scenario_from_configs("WeissHapticSensorsUnitTest" "${SCENARIO_CONFIGS}")
diff --git a/scenarios/WeissHapticSensorsUnitTest/config/HapticObserverApp.cfg b/scenarios/WeissHapticSensorsUnitTest/config/HapticObserverApp.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..b722009b21b50e4731682b82fe2fd4404281ca5e
--- /dev/null
+++ b/scenarios/WeissHapticSensorsUnitTest/config/HapticObserverApp.cfg
@@ -0,0 +1,83 @@
+# ==================================================================
+# ArmarX properties
+# ==================================================================
+
+# ArmarX.CachePath:  Path for cache files
+#  Attributes:
+#  - Default:            ${HOME}/.armarx/mongo/.cache
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.CachePath = ${HOME}/.armarx/mongo/.cache
+
+
+# ArmarX.DataPath:  Semicolon-separated search list for data files
+#  Attributes:
+#  - Default:            ""
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.DataPath = ""
+
+
+# ArmarX.Verbosity:  Global logging level for whole application
+#  Attributes:
+#  - Default:            Verbose
+#  - Case sensitivity:   no
+#  - Required:           no
+#  - Possible values: {Debug, Error, Fatal, Important, Info, Undefined, Verbose, Warning}
+# ArmarX.Verbosity = Verbose
+
+
+# ArmarX.DisableLogging:  Turn logging off in whole application
+#  Attributes:
+#  - Default:            0
+#  - Case sensitivity:   no
+#  - Required:           no
+#  - Possible values: {0, 1, false, no, true, yes}
+# ArmarX.DisableLogging = 0
+
+
+# ArmarX.ApplicationName:  Application name
+#  Attributes:
+#  - Default:            ""
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.ApplicationName = ""
+
+
+# ArmarX.Config:  Comma-separated list of configuration files 
+#  Attributes:
+#  - Default:            ""
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.Config = ""
+
+
+# ==================================================================
+# ArmarX.HapticUnitObserver properties
+# ==================================================================
+
+# ArmarX.HapticUnitObserver.HapticTopicName:  Name of the HapticUnit Topic
+#  Attributes:
+#  - Case sensitivity:   no
+#  - Required:           yes
+ArmarX.HapticUnitObserver.HapticTopicName = "HapticValues"
+
+
+# ArmarX.HapticUnitObserver.MinimumLoggingLevel:  Local logging level only for this component
+#  Attributes:
+#  - Default:            Undefined
+#  - Case sensitivity:   no
+#  - Required:           no
+#  - Possible values: {Error, Fatal, Info, Undefined, Verbose, Warning}
+# ArmarX.HapticUnitObserver.MinimumLoggingLevel = Undefined
+
+
+# ArmarX.HapticUnitObserver.ObjectName:  Name of IceGrid well-known object
+#  Attributes:
+#  - Default:            ""
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.HapticUnitObserver.ObjectName = ""
+
+
+
diff --git a/scenarios/WeissHapticSensorsUnitTest/config/WeissHapticSensorApp.cfg b/scenarios/WeissHapticSensorsUnitTest/config/WeissHapticSensorApp.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..1f85d7c5cbdd1c0cbc11253d177937916a2a7581
--- /dev/null
+++ b/scenarios/WeissHapticSensorsUnitTest/config/WeissHapticSensorApp.cfg
@@ -0,0 +1,104 @@
+# ==================================================================
+# ArmarX properties
+# ==================================================================
+
+# ArmarX.CachePath:  Path for cache files
+#  Attributes:
+#  - Default:            ${HOME}/.armarx/mongo/.cache
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.CachePath = ${HOME}/.armarx/mongo/.cache
+
+
+# ArmarX.DataPath:  Semicolon-separated search list for data files
+#  Attributes:
+#  - Default:            ""
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.DataPath = ""
+
+
+# ArmarX.Verbosity:  Global logging level for whole application
+#  Attributes:
+#  - Default:            Verbose
+#  - Case sensitivity:   no
+#  - Required:           no
+#  - Possible values: {Debug, Error, Fatal, Important, Info, Undefined, Verbose, Warning}
+# ArmarX.Verbosity = Verbose
+
+
+# ArmarX.DisableLogging:  Turn logging off in whole application
+#  Attributes:
+#  - Default:            0
+#  - Case sensitivity:   no
+#  - Required:           no
+#  - Possible values: {0, 1, false, no, true, yes}
+# ArmarX.DisableLogging = 0
+
+
+# ArmarX.ApplicationName:  Application name
+#  Attributes:
+#  - Default:            ""
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.ApplicationName = ""
+
+
+# ArmarX.Config:  Comma-separated list of configuration files 
+#  Attributes:
+#  - Default:            ""
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.Config = ""
+
+
+# ==================================================================
+# ArmarX.HapticUnitObserver properties
+# ==================================================================
+
+# ArmarX.HapticUnitObserver.HapticTopicName:  Name of the HapticUnit Topic
+#  Attributes:
+#  - Case sensitivity:   no
+#  - Required:           yes
+ArmarX.HapticUnitObserver.HapticTopicName = "HapticValues"
+
+
+# ArmarX.HapticUnitObserver.MinimumLoggingLevel:  Local logging level only for this component
+#  Attributes:
+#  - Default:            Undefined
+#  - Case sensitivity:   no
+#  - Required:           no
+#  - Possible values: {Error, Fatal, Info, Undefined, Verbose, Warning}
+# ArmarX.HapticUnitObserver.MinimumLoggingLevel = Undefined
+
+
+# ArmarX.HapticUnitObserver.ObjectName:  Name of IceGrid well-known object
+#  Attributes:
+#  - Default:            ""
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.HapticUnitObserver.ObjectName = ""
+
+
+# ==================================================================
+# ArmarX.WeissHapticSensorListener properties
+# ==================================================================
+
+# ArmarX.WeissHapticSensorListener.MinimumLoggingLevel:  Local logging level only for this component
+#  Attributes:
+#  - Default:            Undefined
+#  - Case sensitivity:   no
+#  - Required:           no
+#  - Possible values: {Error, Fatal, Info, Undefined, Verbose, Warning}
+# ArmarX.WeissHapticSensorListener.MinimumLoggingLevel = Undefined
+
+
+# ArmarX.WeissHapticSensorListener.ObjectName:  Name of IceGrid well-known object
+#  Attributes:
+#  - Default:            ""
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.WeissHapticSensorListener.ObjectName = ""
+
+
+
diff --git a/scenarios/WeissHapticSensorsUnitTest/config/WeissHapticSensorsUnitApp.cfg b/scenarios/WeissHapticSensorsUnitTest/config/WeissHapticSensorsUnitApp.cfg
new file mode 100644
index 0000000000000000000000000000000000000000..1758548b76a87189af145e83a809582e17327a4e
--- /dev/null
+++ b/scenarios/WeissHapticSensorsUnitTest/config/WeissHapticSensorsUnitApp.cfg
@@ -0,0 +1,55 @@
+# ==================================================================
+# ArmarX properties
+# ==================================================================
+
+# ArmarX.CachePath:  Path for cache files
+#  Attributes:
+#  - Default:            ${HOME}/.armarx/mongo/.cache
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.CachePath = ${HOME}/.armarx/mongo/.cache
+
+
+# ArmarX.DataPath:  Semicolon-separated search list for data files
+#  Attributes:
+#  - Default:            ""
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.DataPath = ""
+
+
+# ArmarX.Verbosity:  Global logging level for whole application
+#  Attributes:
+#  - Default:            Verbose
+#  - Case sensitivity:   no
+#  - Required:           no
+#  - Possible values: {Debug, Error, Fatal, Important, Info, Undefined, Verbose, Warning}
+# ArmarX.Verbosity = Verbose
+
+
+# ArmarX.DisableLogging:  Turn logging off in whole application
+#  Attributes:
+#  - Default:            0
+#  - Case sensitivity:   no
+#  - Required:           no
+#  - Possible values: {0, 1, false, no, true, yes}
+# ArmarX.DisableLogging = 0
+
+
+# ArmarX.ApplicationName:  Application name
+#  Attributes:
+#  - Default:            ""
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.ApplicationName = ""
+
+
+# ArmarX.Config:  Comma-separated list of configuration files 
+#  Attributes:
+#  - Default:            ""
+#  - Case sensitivity:   no
+#  - Required:           no
+# ArmarX.Config = ""
+
+
+
diff --git a/scenarios/WeissHapticSensorsUnitTest/jquery.sparkline.js b/scenarios/WeissHapticSensorsUnitTest/jquery.sparkline.js
new file mode 100644
index 0000000000000000000000000000000000000000..721e03b76b9b7bca11c25552f3aeae17abe87773
--- /dev/null
+++ b/scenarios/WeissHapticSensorsUnitTest/jquery.sparkline.js
@@ -0,0 +1,3054 @@
+/**
+*
+* jquery.sparkline.js
+*
+* v2.1.2
+* (c) Splunk, Inc
+* Contact: Gareth Watts (gareth@splunk.com)
+* http://omnipotent.net/jquery.sparkline/
+*
+* Generates inline sparkline charts from data supplied either to the method
+* or inline in HTML
+*
+* Compatible with Internet Explorer 6.0+ and modern browsers equipped with the canvas tag
+* (Firefox 2.0+, Safari, Opera, etc)
+*
+* License: New BSD License
+*
+* Copyright (c) 2012, Splunk Inc.
+* All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without modification,
+* are permitted provided that the following conditions are met:
+*
+*     * Redistributions of source code must retain the above copyright notice,
+*       this list of conditions and the following disclaimer.
+*     * Redistributions in binary form must reproduce the above copyright notice,
+*       this list of conditions and the following disclaimer in the documentation
+*       and/or other materials provided with the distribution.
+*     * Neither the name of Splunk Inc nor the names of its contributors may
+*       be used to endorse or promote products derived from this software without
+*       specific prior written permission.
+*
+* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
+* SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+* OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*
+*
+* Usage:
+*  $(selector).sparkline(values, options)
+*
+* If values is undefined or set to 'html' then the data values are read from the specified tag:
+*   <p>Sparkline: <span class="sparkline">1,4,6,6,8,5,3,5</span></p>
+*   $('.sparkline').sparkline();
+* There must be no spaces in the enclosed data set
+*
+* Otherwise values must be an array of numbers or null values
+*    <p>Sparkline: <span id="sparkline1">This text replaced if the browser is compatible</span></p>
+*    $('#sparkline1').sparkline([1,4,6,6,8,5,3,5])
+*    $('#sparkline2').sparkline([1,4,6,null,null,5,3,5])
+*
+* Values can also be specified in an HTML comment, or as a values attribute:
+*    <p>Sparkline: <span class="sparkline"><!--1,4,6,6,8,5,3,5 --></span></p>
+*    <p>Sparkline: <span class="sparkline" values="1,4,6,6,8,5,3,5"></span></p>
+*    $('.sparkline').sparkline();
+*
+* For line charts, x values can also be specified:
+*   <p>Sparkline: <span class="sparkline">1:1,2.7:4,3.4:6,5:6,6:8,8.7:5,9:3,10:5</span></p>
+*    $('#sparkline1').sparkline([ [1,1], [2.7,4], [3.4,6], [5,6], [6,8], [8.7,5], [9,3], [10,5] ])
+*
+* By default, options should be passed in as teh second argument to the sparkline function:
+*   $('.sparkline').sparkline([1,2,3,4], {type: 'bar'})
+*
+* Options can also be set by passing them on the tag itself.  This feature is disabled by default though
+* as there's a slight performance overhead:
+*   $('.sparkline').sparkline([1,2,3,4], {enableTagOptions: true})
+*   <p>Sparkline: <span class="sparkline" sparkType="bar" sparkBarColor="red">loading</span></p>
+* Prefix all options supplied as tag attribute with "spark" (configurable by setting tagOptionPrefix)
+*
+* Supported options:
+*   lineColor - Color of the line used for the chart
+*   fillColor - Color used to fill in the chart - Set to '' or false for a transparent chart
+*   width - Width of the chart - Defaults to 3 times the number of values in pixels
+*   height - Height of the chart - Defaults to the height of the containing element
+*   chartRangeMin - Specify the minimum value to use for the Y range of the chart - Defaults to the minimum value supplied
+*   chartRangeMax - Specify the maximum value to use for the Y range of the chart - Defaults to the maximum value supplied
+*   chartRangeClip - Clip out of range values to the max/min specified by chartRangeMin and chartRangeMax
+*   chartRangeMinX - Specify the minimum value to use for the X range of the chart - Defaults to the minimum value supplied
+*   chartRangeMaxX - Specify the maximum value to use for the X range of the chart - Defaults to the maximum value supplied
+*   composite - If true then don't erase any existing chart attached to the tag, but draw
+*           another chart over the top - Note that width and height are ignored if an
+*           existing chart is detected.
+*   tagValuesAttribute - Name of tag attribute to check for data values - Defaults to 'values'
+*   enableTagOptions - Whether to check tags for sparkline options
+*   tagOptionPrefix - Prefix used for options supplied as tag attributes - Defaults to 'spark'
+*   disableHiddenCheck - If set to true, then the plugin will assume that charts will never be drawn into a
+*           hidden dom element, avoding a browser reflow
+*   disableInteraction - If set to true then all mouseover/click interaction behaviour will be disabled,
+*       making the plugin perform much like it did in 1.x
+*   disableTooltips - If set to true then tooltips will be disabled - Defaults to false (tooltips enabled)
+*   disableHighlight - If set to true then highlighting of selected chart elements on mouseover will be disabled
+*       defaults to false (highlights enabled)
+*   highlightLighten - Factor to lighten/darken highlighted chart values by - Defaults to 1.4 for a 40% increase
+*   tooltipContainer - Specify which DOM element the tooltip should be rendered into - defaults to document.body
+*   tooltipClassname - Optional CSS classname to apply to tooltips - If not specified then a default style will be applied
+*   tooltipOffsetX - How many pixels away from the mouse pointer to render the tooltip on the X axis
+*   tooltipOffsetY - How many pixels away from the mouse pointer to render the tooltip on the r axis
+*   tooltipFormatter  - Optional callback that allows you to override the HTML displayed in the tooltip
+*       callback is given arguments of (sparkline, options, fields)
+*   tooltipChartTitle - If specified then the tooltip uses the string specified by this setting as a title
+*   tooltipFormat - A format string or SPFormat object  (or an array thereof for multiple entries)
+*       to control the format of the tooltip
+*   tooltipPrefix - A string to prepend to each field displayed in a tooltip
+*   tooltipSuffix - A string to append to each field displayed in a tooltip
+*   tooltipSkipNull - If true then null values will not have a tooltip displayed (defaults to true)
+*   tooltipValueLookups - An object or range map to map field values to tooltip strings
+*       (eg. to map -1 to "Lost", 0 to "Draw", and 1 to "Win")
+*   numberFormatter - Optional callback for formatting numbers in tooltips
+*   numberDigitGroupSep - Character to use for group separator in numbers "1,234" - Defaults to ","
+*   numberDecimalMark - Character to use for the decimal point when formatting numbers - Defaults to "."
+*   numberDigitGroupCount - Number of digits between group separator - Defaults to 3
+*
+* There are 7 types of sparkline, selected by supplying a "type" option of 'line' (default),
+* 'bar', 'tristate', 'bullet', 'discrete', 'pie' or 'box'
+*    line - Line chart.  Options:
+*       spotColor - Set to '' to not end each line in a circular spot
+*       minSpotColor - If set, color of spot at minimum value
+*       maxSpotColor - If set, color of spot at maximum value
+*       spotRadius - Radius in pixels
+*       lineWidth - Width of line in pixels
+*       normalRangeMin
+*       normalRangeMax - If set draws a filled horizontal bar between these two values marking the "normal"
+*                      or expected range of values
+*       normalRangeColor - Color to use for the above bar
+*       drawNormalOnTop - Draw the normal range above the chart fill color if true
+*       defaultPixelsPerValue - Defaults to 3 pixels of width for each value in the chart
+*       highlightSpotColor - The color to use for drawing a highlight spot on mouseover - Set to null to disable
+*       highlightLineColor - The color to use for drawing a highlight line on mouseover - Set to null to disable
+*       valueSpots - Specify which points to draw spots on, and in which color.  Accepts a range map
+*
+*   bar - Bar chart.  Options:
+*       barColor - Color of bars for postive values
+*       negBarColor - Color of bars for negative values
+*       zeroColor - Color of bars with zero values
+*       nullColor - Color of bars with null values - Defaults to omitting the bar entirely
+*       barWidth - Width of bars in pixels
+*       colorMap - Optional mappnig of values to colors to override the *BarColor values above
+*                  can be an Array of values to control the color of individual bars or a range map
+*                  to specify colors for individual ranges of values
+*       barSpacing - Gap between bars in pixels
+*       zeroAxis - Centers the y-axis around zero if true
+*
+*   tristate - Charts values of win (>0), lose (<0) or draw (=0)
+*       posBarColor - Color of win values
+*       negBarColor - Color of lose values
+*       zeroBarColor - Color of draw values
+*       barWidth - Width of bars in pixels
+*       barSpacing - Gap between bars in pixels
+*       colorMap - Optional mappnig of values to colors to override the *BarColor values above
+*                  can be an Array of values to control the color of individual bars or a range map
+*                  to specify colors for individual ranges of values
+*
+*   discrete - Options:
+*       lineHeight - Height of each line in pixels - Defaults to 30% of the graph height
+*       thesholdValue - Values less than this value will be drawn using thresholdColor instead of lineColor
+*       thresholdColor
+*
+*   bullet - Values for bullet graphs msut be in the order: target, performance, range1, range2, range3, ...
+*       options:
+*       targetColor - The color of the vertical target marker
+*       targetWidth - The width of the target marker in pixels
+*       performanceColor - The color of the performance measure horizontal bar
+*       rangeColors - Colors to use for each qualitative range background color
+*
+*   pie - Pie chart. Options:
+*       sliceColors - An array of colors to use for pie slices
+*       offset - Angle in degrees to offset the first slice - Try -90 or +90
+*       borderWidth - Width of border to draw around the pie chart, in pixels - Defaults to 0 (no border)
+*       borderColor - Color to use for the pie chart border - Defaults to #000
+*
+*   box - Box plot. Options:
+*       raw - Set to true to supply pre-computed plot points as values
+*             values should be: low_outlier, low_whisker, q1, median, q3, high_whisker, high_outlier
+*             When set to false you can supply any number of values and the box plot will
+*             be computed for you.  Default is false.
+*       showOutliers - Set to true (default) to display outliers as circles
+*       outlierIQR - Interquartile range used to determine outliers.  Default 1.5
+*       boxLineColor - Outline color of the box
+*       boxFillColor - Fill color for the box
+*       whiskerColor - Line color used for whiskers
+*       outlierLineColor - Outline color of outlier circles
+*       outlierFillColor - Fill color of the outlier circles
+*       spotRadius - Radius of outlier circles
+*       medianColor - Line color of the median line
+*       target - Draw a target cross hair at the supplied value (default undefined)
+*
+*
+*
+*   Examples:
+*   $('#sparkline1').sparkline(myvalues, { lineColor: '#f00', fillColor: false });
+*   $('.barsparks').sparkline('html', { type:'bar', height:'40px', barWidth:5 });
+*   $('#tristate').sparkline([1,1,-1,1,0,0,-1], { type:'tristate' }):
+*   $('#discrete').sparkline([1,3,4,5,5,3,4,5], { type:'discrete' });
+*   $('#bullet').sparkline([10,12,12,9,7], { type:'bullet' });
+*   $('#pie').sparkline([1,1,2], { type:'pie' });
+*/
+
+/*jslint regexp: true, browser: true, jquery: true, white: true, nomen: false, plusplus: false, maxerr: 500, indent: 4 */
+
+(function(document, Math, undefined) { // performance/minified-size optimization
+(function(factory) {
+    if(typeof define === 'function' && define.amd) {
+        define(['jquery'], factory);
+    } else if (jQuery && !jQuery.fn.sparkline) {
+        factory(jQuery);
+    }
+}
+(function($) {
+    'use strict';
+
+    var UNSET_OPTION = {},
+        getDefaults, createClass, SPFormat, clipval, quartile, normalizeValue, normalizeValues,
+        remove, isNumber, all, sum, addCSS, ensureArray, formatNumber, RangeMap,
+        MouseHandler, Tooltip, barHighlightMixin,
+        line, bar, tristate, discrete, bullet, pie, box, defaultStyles, initStyles,
+        VShape, VCanvas_base, VCanvas_canvas, VCanvas_vml, pending, shapeCount = 0;
+
+    /**
+     * Default configuration settings
+     */
+    getDefaults = function () {
+        return {
+            // Settings common to most/all chart types
+            common: {
+                type: 'line',
+                lineColor: '#00f',
+                fillColor: '#cdf',
+                defaultPixelsPerValue: 3,
+                width: 'auto',
+                height: 'auto',
+                composite: false,
+                tagValuesAttribute: 'values',
+                tagOptionsPrefix: 'spark',
+                enableTagOptions: false,
+                enableHighlight: true,
+                highlightLighten: 1.4,
+                tooltipSkipNull: true,
+                tooltipPrefix: '',
+                tooltipSuffix: '',
+                disableHiddenCheck: false,
+                numberFormatter: false,
+                numberDigitGroupCount: 3,
+                numberDigitGroupSep: ',',
+                numberDecimalMark: '.',
+                disableTooltips: false,
+                disableInteraction: false
+            },
+            // Defaults for line charts
+            line: {
+                spotColor: '#f80',
+                highlightSpotColor: '#5f5',
+                highlightLineColor: '#f22',
+                spotRadius: 1.5,
+                minSpotColor: '#f80',
+                maxSpotColor: '#f80',
+                lineWidth: 1,
+                normalRangeMin: undefined,
+                normalRangeMax: undefined,
+                normalRangeColor: '#ccc',
+                drawNormalOnTop: false,
+                chartRangeMin: undefined,
+                chartRangeMax: undefined,
+                chartRangeMinX: undefined,
+                chartRangeMaxX: undefined,
+                tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{prefix}}{{y}}{{suffix}}')
+            },
+            // Defaults for bar charts
+            bar: {
+                barColor: '#3366cc',
+                negBarColor: '#f44',
+                stackedBarColor: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
+                    '#dd4477', '#0099c6', '#990099'],
+                zeroColor: undefined,
+                nullColor: undefined,
+                zeroAxis: true,
+                barWidth: 4,
+                barSpacing: 1,
+                chartRangeMax: undefined,
+                chartRangeMin: undefined,
+                chartRangeClip: false,
+                colorMap: undefined,
+                tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{prefix}}{{value}}{{suffix}}')
+            },
+            // Defaults for tristate charts
+            tristate: {
+                barWidth: 4,
+                barSpacing: 1,
+                posBarColor: '#6f6',
+                negBarColor: '#f44',
+                zeroBarColor: '#999',
+                colorMap: {},
+                tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{value:map}}'),
+                tooltipValueLookups: { map: { '-1': 'Loss', '0': 'Draw', '1': 'Win' } }
+            },
+            // Defaults for discrete charts
+            discrete: {
+                lineHeight: 'auto',
+                thresholdColor: undefined,
+                thresholdValue: 0,
+                chartRangeMax: undefined,
+                chartRangeMin: undefined,
+                chartRangeClip: false,
+                tooltipFormat: new SPFormat('{{prefix}}{{value}}{{suffix}}')
+            },
+            // Defaults for bullet charts
+            bullet: {
+                targetColor: '#f33',
+                targetWidth: 3, // width of the target bar in pixels
+                performanceColor: '#33f',
+                rangeColors: ['#d3dafe', '#a8b6ff', '#7f94ff'],
+                base: undefined, // set this to a number to change the base start number
+                tooltipFormat: new SPFormat('{{fieldkey:fields}} - {{value}}'),
+                tooltipValueLookups: { fields: {r: 'Range', p: 'Performance', t: 'Target'} }
+            },
+            // Defaults for pie charts
+            pie: {
+                offset: 0,
+                sliceColors: ['#3366cc', '#dc3912', '#ff9900', '#109618', '#66aa00',
+                    '#dd4477', '#0099c6', '#990099'],
+                borderWidth: 0,
+                borderColor: '#000',
+                tooltipFormat: new SPFormat('<span style="color: {{color}}">&#9679;</span> {{value}} ({{percent.1}}%)')
+            },
+            // Defaults for box plots
+            box: {
+                raw: false,
+                boxLineColor: '#000',
+                boxFillColor: '#cdf',
+                whiskerColor: '#000',
+                outlierLineColor: '#333',
+                outlierFillColor: '#fff',
+                medianColor: '#f00',
+                showOutliers: true,
+                outlierIQR: 1.5,
+                spotRadius: 1.5,
+                target: undefined,
+                targetColor: '#4a2',
+                chartRangeMax: undefined,
+                chartRangeMin: undefined,
+                tooltipFormat: new SPFormat('{{field:fields}}: {{value}}'),
+                tooltipFormatFieldlistKey: 'field',
+                tooltipValueLookups: { fields: { lq: 'Lower Quartile', med: 'Median',
+                    uq: 'Upper Quartile', lo: 'Left Outlier', ro: 'Right Outlier',
+                    lw: 'Left Whisker', rw: 'Right Whisker'} }
+            }
+        };
+    };
+
+    // You can have tooltips use a css class other than jqstooltip by specifying tooltipClassname
+    defaultStyles = '.jqstooltip { ' +
+            'position: absolute;' +
+            'left: 0px;' +
+            'top: 0px;' +
+            'visibility: hidden;' +
+            'background: rgb(0, 0, 0) transparent;' +
+            'background-color: rgba(0,0,0,0.6);' +
+            'filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000);' +
+            '-ms-filter: "progid:DXImageTransform.Microsoft.gradient(startColorstr=#99000000, endColorstr=#99000000)";' +
+            'color: white;' +
+            'font: 10px arial, san serif;' +
+            'text-align: left;' +
+            'white-space: nowrap;' +
+            'padding: 5px;' +
+            'border: 1px solid white;' +
+            'z-index: 10000;' +
+            '}' +
+            '.jqsfield { ' +
+            'color: white;' +
+            'font: 10px arial, san serif;' +
+            'text-align: left;' +
+            '}';
+
+    /**
+     * Utilities
+     */
+
+    createClass = function (/* [baseclass, [mixin, ...]], definition */) {
+        var Class, args;
+        Class = function () {
+            this.init.apply(this, arguments);
+        };
+        if (arguments.length > 1) {
+            if (arguments[0]) {
+                Class.prototype = $.extend(new arguments[0](), arguments[arguments.length - 1]);
+                Class._super = arguments[0].prototype;
+            } else {
+                Class.prototype = arguments[arguments.length - 1];
+            }
+            if (arguments.length > 2) {
+                args = Array.prototype.slice.call(arguments, 1, -1);
+                args.unshift(Class.prototype);
+                $.extend.apply($, args);
+            }
+        } else {
+            Class.prototype = arguments[0];
+        }
+        Class.prototype.cls = Class;
+        return Class;
+    };
+
+    /**
+     * Wraps a format string for tooltips
+     * {{x}}
+     * {{x.2}
+     * {{x:months}}
+     */
+    $.SPFormatClass = SPFormat = createClass({
+        fre: /\{\{([\w.]+?)(:(.+?))?\}\}/g,
+        precre: /(\w+)\.(\d+)/,
+
+        init: function (format, fclass) {
+            this.format = format;
+            this.fclass = fclass;
+        },
+
+        render: function (fieldset, lookups, options) {
+            var self = this,
+                fields = fieldset,
+                match, token, lookupkey, fieldvalue, prec;
+            return this.format.replace(this.fre, function () {
+                var lookup;
+                token = arguments[1];
+                lookupkey = arguments[3];
+                match = self.precre.exec(token);
+                if (match) {
+                    prec = match[2];
+                    token = match[1];
+                } else {
+                    prec = false;
+                }
+                fieldvalue = fields[token];
+                if (fieldvalue === undefined) {
+                    return '';
+                }
+                if (lookupkey && lookups && lookups[lookupkey]) {
+                    lookup = lookups[lookupkey];
+                    if (lookup.get) { // RangeMap
+                        return lookups[lookupkey].get(fieldvalue) || fieldvalue;
+                    } else {
+                        return lookups[lookupkey][fieldvalue] || fieldvalue;
+                    }
+                }
+                if (isNumber(fieldvalue)) {
+                    if (options.get('numberFormatter')) {
+                        fieldvalue = options.get('numberFormatter')(fieldvalue);
+                    } else {
+                        fieldvalue = formatNumber(fieldvalue, prec,
+                            options.get('numberDigitGroupCount'),
+                            options.get('numberDigitGroupSep'),
+                            options.get('numberDecimalMark'));
+                    }
+                }
+                return fieldvalue;
+            });
+        }
+    });
+
+    // convience method to avoid needing the new operator
+    $.spformat = function(format, fclass) {
+        return new SPFormat(format, fclass);
+    };
+
+    clipval = function (val, min, max) {
+        if (val < min) {
+            return min;
+        }
+        if (val > max) {
+            return max;
+        }
+        return val;
+    };
+
+    quartile = function (values, q) {
+        var vl;
+        if (q === 2) {
+            vl = Math.floor(values.length / 2);
+            return values.length % 2 ? values[vl] : (values[vl-1] + values[vl]) / 2;
+        } else {
+            if (values.length % 2 ) { // odd
+                vl = (values.length * q + q) / 4;
+                return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 : values[vl-1];
+            } else { //even
+                vl = (values.length * q + 2) / 4;
+                return vl % 1 ? (values[Math.floor(vl)] + values[Math.floor(vl) - 1]) / 2 :  values[vl-1];
+
+            }
+        }
+    };
+
+    normalizeValue = function (val) {
+        var nf;
+        switch (val) {
+            case 'undefined':
+                val = undefined;
+                break;
+            case 'null':
+                val = null;
+                break;
+            case 'true':
+                val = true;
+                break;
+            case 'false':
+                val = false;
+                break;
+            default:
+                nf = parseFloat(val);
+                if (val == nf) {
+                    val = nf;
+                }
+        }
+        return val;
+    };
+
+    normalizeValues = function (vals) {
+        var i, result = [];
+        for (i = vals.length; i--;) {
+            result[i] = normalizeValue(vals[i]);
+        }
+        return result;
+    };
+
+    remove = function (vals, filter) {
+        var i, vl, result = [];
+        for (i = 0, vl = vals.length; i < vl; i++) {
+            if (vals[i] !== filter) {
+                result.push(vals[i]);
+            }
+        }
+        return result;
+    };
+
+    isNumber = function (num) {
+        return !isNaN(parseFloat(num)) && isFinite(num);
+    };
+
+    formatNumber = function (num, prec, groupsize, groupsep, decsep) {
+        var p, i;
+        num = (prec === false ? parseFloat(num).toString() : num.toFixed(prec)).split('');
+        p = (p = $.inArray('.', num)) < 0 ? num.length : p;
+        if (p < num.length) {
+            num[p] = decsep;
+        }
+        for (i = p - groupsize; i > 0; i -= groupsize) {
+            num.splice(i, 0, groupsep);
+        }
+        return num.join('');
+    };
+
+    // determine if all values of an array match a value
+    // returns true if the array is empty
+    all = function (val, arr, ignoreNull) {
+        var i;
+        for (i = arr.length; i--; ) {
+            if (ignoreNull && arr[i] === null) continue;
+            if (arr[i] !== val) {
+                return false;
+            }
+        }
+        return true;
+    };
+
+    // sums the numeric values in an array, ignoring other values
+    sum = function (vals) {
+        var total = 0, i;
+        for (i = vals.length; i--;) {
+            total += typeof vals[i] === 'number' ? vals[i] : 0;
+        }
+        return total;
+    };
+
+    ensureArray = function (val) {
+        return $.isArray(val) ? val : [val];
+    };
+
+    // http://paulirish.com/2008/bookmarklet-inject-new-css-rules/
+    addCSS = function(css) {
+        var tag;
+        //if ('\v' == 'v') /* ie only */ {
+        if (document.createStyleSheet) {
+            document.createStyleSheet().cssText = css;
+        } else {
+            tag = document.createElement('style');
+            tag.type = 'text/css';
+            document.getElementsByTagName('head')[0].appendChild(tag);
+            tag[(typeof document.body.style.WebkitAppearance == 'string') /* webkit only */ ? 'innerText' : 'innerHTML'] = css;
+        }
+    };
+
+    // Provide a cross-browser interface to a few simple drawing primitives
+    $.fn.simpledraw = function (width, height, useExisting, interact) {
+        var target, mhandler;
+        if (useExisting && (target = this.data('_jqs_vcanvas'))) {
+            return target;
+        }
+
+        if ($.fn.sparkline.canvas === false) {
+            // We've already determined that neither Canvas nor VML are available
+            return false;
+
+        } else if ($.fn.sparkline.canvas === undefined) {
+            // No function defined yet -- need to see if we support Canvas or VML
+            var el = document.createElement('canvas');
+            if (!!(el.getContext && el.getContext('2d'))) {
+                // Canvas is available
+                $.fn.sparkline.canvas = function(width, height, target, interact) {
+                    return new VCanvas_canvas(width, height, target, interact);
+                };
+            } else if (document.namespaces && !document.namespaces.v) {
+                // VML is available
+                document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');
+                $.fn.sparkline.canvas = function(width, height, target, interact) {
+                    return new VCanvas_vml(width, height, target);
+                };
+            } else {
+                // Neither Canvas nor VML are available
+                $.fn.sparkline.canvas = false;
+                return false;
+            }
+        }
+
+        if (width === undefined) {
+            width = $(this).innerWidth();
+        }
+        if (height === undefined) {
+            height = $(this).innerHeight();
+        }
+
+        target = $.fn.sparkline.canvas(width, height, this, interact);
+
+        mhandler = $(this).data('_jqs_mhandler');
+        if (mhandler) {
+            mhandler.registerCanvas(target);
+        }
+        return target;
+    };
+
+    $.fn.cleardraw = function () {
+        var target = this.data('_jqs_vcanvas');
+        if (target) {
+            target.reset();
+        }
+    };
+
+    $.RangeMapClass = RangeMap = createClass({
+        init: function (map) {
+            var key, range, rangelist = [];
+            for (key in map) {
+                if (map.hasOwnProperty(key) && typeof key === 'string' && key.indexOf(':') > -1) {
+                    range = key.split(':');
+                    range[0] = range[0].length === 0 ? -Infinity : parseFloat(range[0]);
+                    range[1] = range[1].length === 0 ? Infinity : parseFloat(range[1]);
+                    range[2] = map[key];
+                    rangelist.push(range);
+                }
+            }
+            this.map = map;
+            this.rangelist = rangelist || false;
+        },
+
+        get: function (value) {
+            var rangelist = this.rangelist,
+                i, range, result;
+            if ((result = this.map[value]) !== undefined) {
+                return result;
+            }
+            if (rangelist) {
+                for (i = rangelist.length; i--;) {
+                    range = rangelist[i];
+                    if (range[0] <= value && range[1] >= value) {
+                        return range[2];
+                    }
+                }
+            }
+            return undefined;
+        }
+    });
+
+    // Convenience function
+    $.range_map = function(map) {
+        return new RangeMap(map);
+    };
+
+    MouseHandler = createClass({
+        init: function (el, options) {
+            var $el = $(el);
+            this.$el = $el;
+            this.options = options;
+            this.currentPageX = 0;
+            this.currentPageY = 0;
+            this.el = el;
+            this.splist = [];
+            this.tooltip = null;
+            this.over = false;
+            this.displayTooltips = !options.get('disableTooltips');
+            this.highlightEnabled = !options.get('disableHighlight');
+        },
+
+        registerSparkline: function (sp) {
+            this.splist.push(sp);
+            if (this.over) {
+                this.updateDisplay();
+            }
+        },
+
+        registerCanvas: function (canvas) {
+            var $canvas = $(canvas.canvas);
+            this.canvas = canvas;
+            this.$canvas = $canvas;
+            $canvas.mouseenter($.proxy(this.mouseenter, this));
+            $canvas.mouseleave($.proxy(this.mouseleave, this));
+            $canvas.click($.proxy(this.mouseclick, this));
+        },
+
+        reset: function (removeTooltip) {
+            this.splist = [];
+            if (this.tooltip && removeTooltip) {
+                this.tooltip.remove();
+                this.tooltip = undefined;
+            }
+        },
+
+        mouseclick: function (e) {
+            var clickEvent = $.Event('sparklineClick');
+            clickEvent.originalEvent = e;
+            clickEvent.sparklines = this.splist;
+            this.$el.trigger(clickEvent);
+        },
+
+        mouseenter: function (e) {
+            $(document.body).unbind('mousemove.jqs');
+            $(document.body).bind('mousemove.jqs', $.proxy(this.mousemove, this));
+            this.over = true;
+            this.currentPageX = e.pageX;
+            this.currentPageY = e.pageY;
+            this.currentEl = e.target;
+            if (!this.tooltip && this.displayTooltips) {
+                this.tooltip = new Tooltip(this.options);
+                this.tooltip.updatePosition(e.pageX, e.pageY);
+            }
+            this.updateDisplay();
+        },
+
+        mouseleave: function () {
+            $(document.body).unbind('mousemove.jqs');
+            var splist = this.splist,
+                 spcount = splist.length,
+                 needsRefresh = false,
+                 sp, i;
+            this.over = false;
+            this.currentEl = null;
+
+            if (this.tooltip) {
+                this.tooltip.remove();
+                this.tooltip = null;
+            }
+
+            for (i = 0; i < spcount; i++) {
+                sp = splist[i];
+                if (sp.clearRegionHighlight()) {
+                    needsRefresh = true;
+                }
+            }
+
+            if (needsRefresh) {
+                this.canvas.render();
+            }
+        },
+
+        mousemove: function (e) {
+            this.currentPageX = e.pageX;
+            this.currentPageY = e.pageY;
+            this.currentEl = e.target;
+            if (this.tooltip) {
+                this.tooltip.updatePosition(e.pageX, e.pageY);
+            }
+            this.updateDisplay();
+        },
+
+        updateDisplay: function () {
+            var splist = this.splist,
+                 spcount = splist.length,
+                 needsRefresh = false,
+                 offset = this.$canvas.offset(),
+                 localX = this.currentPageX - offset.left,
+                 localY = this.currentPageY - offset.top,
+                 tooltiphtml, sp, i, result, changeEvent;
+            if (!this.over) {
+                return;
+            }
+            for (i = 0; i < spcount; i++) {
+                sp = splist[i];
+                result = sp.setRegionHighlight(this.currentEl, localX, localY);
+                if (result) {
+                    needsRefresh = true;
+                }
+            }
+            if (needsRefresh) {
+                changeEvent = $.Event('sparklineRegionChange');
+                changeEvent.sparklines = this.splist;
+                this.$el.trigger(changeEvent);
+                if (this.tooltip) {
+                    tooltiphtml = '';
+                    for (i = 0; i < spcount; i++) {
+                        sp = splist[i];
+                        tooltiphtml += sp.getCurrentRegionTooltip();
+                    }
+                    this.tooltip.setContent(tooltiphtml);
+                }
+                if (!this.disableHighlight) {
+                    this.canvas.render();
+                }
+            }
+            if (result === null) {
+                this.mouseleave();
+            }
+        }
+    });
+
+
+    Tooltip = createClass({
+        sizeStyle: 'position: static !important;' +
+            'display: block !important;' +
+            'visibility: hidden !important;' +
+            'float: left !important;',
+
+        init: function (options) {
+            var tooltipClassname = options.get('tooltipClassname', 'jqstooltip'),
+                sizetipStyle = this.sizeStyle,
+                offset;
+            this.container = options.get('tooltipContainer') || document.body;
+            this.tooltipOffsetX = options.get('tooltipOffsetX', 10);
+            this.tooltipOffsetY = options.get('tooltipOffsetY', 12);
+            // remove any previous lingering tooltip
+            $('#jqssizetip').remove();
+            $('#jqstooltip').remove();
+            this.sizetip = $('<div/>', {
+                id: 'jqssizetip',
+                style: sizetipStyle,
+                'class': tooltipClassname
+            });
+            this.tooltip = $('<div/>', {
+                id: 'jqstooltip',
+                'class': tooltipClassname
+            }).appendTo(this.container);
+            // account for the container's location
+            offset = this.tooltip.offset();
+            this.offsetLeft = offset.left;
+            this.offsetTop = offset.top;
+            this.hidden = true;
+            $(window).unbind('resize.jqs scroll.jqs');
+            $(window).bind('resize.jqs scroll.jqs', $.proxy(this.updateWindowDims, this));
+            this.updateWindowDims();
+        },
+
+        updateWindowDims: function () {
+            this.scrollTop = $(window).scrollTop();
+            this.scrollLeft = $(window).scrollLeft();
+            this.scrollRight = this.scrollLeft + $(window).width();
+            this.updatePosition();
+        },
+
+        getSize: function (content) {
+            this.sizetip.html(content).appendTo(this.container);
+            this.width = this.sizetip.width() + 1;
+            this.height = this.sizetip.height();
+            this.sizetip.remove();
+        },
+
+        setContent: function (content) {
+            if (!content) {
+                this.tooltip.css('visibility', 'hidden');
+                this.hidden = true;
+                return;
+            }
+            this.getSize(content);
+            this.tooltip.html(content)
+                .css({
+                    'width': this.width,
+                    'height': this.height,
+                    'visibility': 'visible'
+                });
+            if (this.hidden) {
+                this.hidden = false;
+                this.updatePosition();
+            }
+        },
+
+        updatePosition: function (x, y) {
+            if (x === undefined) {
+                if (this.mousex === undefined) {
+                    return;
+                }
+                x = this.mousex - this.offsetLeft;
+                y = this.mousey - this.offsetTop;
+
+            } else {
+                this.mousex = x = x - this.offsetLeft;
+                this.mousey = y = y - this.offsetTop;
+            }
+            if (!this.height || !this.width || this.hidden) {
+                return;
+            }
+
+            y -= this.height + this.tooltipOffsetY;
+            x += this.tooltipOffsetX;
+
+            if (y < this.scrollTop) {
+                y = this.scrollTop;
+            }
+            if (x < this.scrollLeft) {
+                x = this.scrollLeft;
+            } else if (x + this.width > this.scrollRight) {
+                x = this.scrollRight - this.width;
+            }
+
+            this.tooltip.css({
+                'left': x,
+                'top': y
+            });
+        },
+
+        remove: function () {
+            this.tooltip.remove();
+            this.sizetip.remove();
+            this.sizetip = this.tooltip = undefined;
+            $(window).unbind('resize.jqs scroll.jqs');
+        }
+    });
+
+    initStyles = function() {
+        addCSS(defaultStyles);
+    };
+
+    $(initStyles);
+
+    pending = [];
+    $.fn.sparkline = function (userValues, userOptions) {
+        return this.each(function () {
+            var options = new $.fn.sparkline.options(this, userOptions),
+                 $this = $(this),
+                 render, i;
+            render = function () {
+                var values, width, height, tmp, mhandler, sp, vals;
+                if (userValues === 'html' || userValues === undefined) {
+                    vals = this.getAttribute(options.get('tagValuesAttribute'));
+                    if (vals === undefined || vals === null) {
+                        vals = $this.html();
+                    }
+                    values = vals.replace(/(^\s*<!--)|(-->\s*$)|\s+/g, '').split(',');
+                } else {
+                    values = userValues;
+                }
+
+                width = options.get('width') === 'auto' ? values.length * options.get('defaultPixelsPerValue') : options.get('width');
+                if (options.get('height') === 'auto') {
+                    if (!options.get('composite') || !$.data(this, '_jqs_vcanvas')) {
+                        // must be a better way to get the line height
+                        tmp = document.createElement('span');
+                        tmp.innerHTML = 'a';
+                        $this.html(tmp);
+                        height = $(tmp).innerHeight() || $(tmp).height();
+                        $(tmp).remove();
+                        tmp = null;
+                    }
+                } else {
+                    height = options.get('height');
+                }
+
+                if (!options.get('disableInteraction')) {
+                    mhandler = $.data(this, '_jqs_mhandler');
+                    if (!mhandler) {
+                        mhandler = new MouseHandler(this, options);
+                        $.data(this, '_jqs_mhandler', mhandler);
+                    } else if (!options.get('composite')) {
+                        mhandler.reset();
+                    }
+                } else {
+                    mhandler = false;
+                }
+
+                if (options.get('composite') && !$.data(this, '_jqs_vcanvas')) {
+                    if (!$.data(this, '_jqs_errnotify')) {
+                        alert('Attempted to attach a composite sparkline to an element with no existing sparkline');
+                        $.data(this, '_jqs_errnotify', true);
+                    }
+                    return;
+                }
+
+                sp = new $.fn.sparkline[options.get('type')](this, values, options, width, height);
+
+                sp.render();
+
+                if (mhandler) {
+                    mhandler.registerSparkline(sp);
+                }
+            };
+            if (($(this).html() && !options.get('disableHiddenCheck') && $(this).is(':hidden')) || !$(this).parents('body').length) {
+                if (!options.get('composite') && $.data(this, '_jqs_pending')) {
+                    // remove any existing references to the element
+                    for (i = pending.length; i; i--) {
+                        if (pending[i - 1][0] == this) {
+                            pending.splice(i - 1, 1);
+                        }
+                    }
+                }
+                pending.push([this, render]);
+                $.data(this, '_jqs_pending', true);
+            } else {
+                render.call(this);
+            }
+        });
+    };
+
+    $.fn.sparkline.defaults = getDefaults();
+
+
+    $.sparkline_display_visible = function () {
+        var el, i, pl;
+        var done = [];
+        for (i = 0, pl = pending.length; i < pl; i++) {
+            el = pending[i][0];
+            if ($(el).is(':visible') && !$(el).parents().is(':hidden')) {
+                pending[i][1].call(el);
+                $.data(pending[i][0], '_jqs_pending', false);
+                done.push(i);
+            } else if (!$(el).closest('html').length && !$.data(el, '_jqs_pending')) {
+                // element has been inserted and removed from the DOM
+                // If it was not yet inserted into the dom then the .data request
+                // will return true.
+                // removing from the dom causes the data to be removed.
+                $.data(pending[i][0], '_jqs_pending', false);
+                done.push(i);
+            }
+        }
+        for (i = done.length; i; i--) {
+            pending.splice(done[i - 1], 1);
+        }
+    };
+
+
+    /**
+     * User option handler
+     */
+    $.fn.sparkline.options = createClass({
+        init: function (tag, userOptions) {
+            var extendedOptions, defaults, base, tagOptionType;
+            this.userOptions = userOptions = userOptions || {};
+            this.tag = tag;
+            this.tagValCache = {};
+            defaults = $.fn.sparkline.defaults;
+            base = defaults.common;
+            this.tagOptionsPrefix = userOptions.enableTagOptions && (userOptions.tagOptionsPrefix || base.tagOptionsPrefix);
+
+            tagOptionType = this.getTagSetting('type');
+            if (tagOptionType === UNSET_OPTION) {
+                extendedOptions = defaults[userOptions.type || base.type];
+            } else {
+                extendedOptions = defaults[tagOptionType];
+            }
+            this.mergedOptions = $.extend({}, base, extendedOptions, userOptions);
+        },
+
+
+        getTagSetting: function (key) {
+            var prefix = this.tagOptionsPrefix,
+                val, i, pairs, keyval;
+            if (prefix === false || prefix === undefined) {
+                return UNSET_OPTION;
+            }
+            if (this.tagValCache.hasOwnProperty(key)) {
+                val = this.tagValCache.key;
+            } else {
+                val = this.tag.getAttribute(prefix + key);
+                if (val === undefined || val === null) {
+                    val = UNSET_OPTION;
+                } else if (val.substr(0, 1) === '[') {
+                    val = val.substr(1, val.length - 2).split(',');
+                    for (i = val.length; i--;) {
+                        val[i] = normalizeValue(val[i].replace(/(^\s*)|(\s*$)/g, ''));
+                    }
+                } else if (val.substr(0, 1) === '{') {
+                    pairs = val.substr(1, val.length - 2).split(',');
+                    val = {};
+                    for (i = pairs.length; i--;) {
+                        keyval = pairs[i].split(':', 2);
+                        val[keyval[0].replace(/(^\s*)|(\s*$)/g, '')] = normalizeValue(keyval[1].replace(/(^\s*)|(\s*$)/g, ''));
+                    }
+                } else {
+                    val = normalizeValue(val);
+                }
+                this.tagValCache.key = val;
+            }
+            return val;
+        },
+
+        get: function (key, defaultval) {
+            var tagOption = this.getTagSetting(key),
+                result;
+            if (tagOption !== UNSET_OPTION) {
+                return tagOption;
+            }
+            return (result = this.mergedOptions[key]) === undefined ? defaultval : result;
+        }
+    });
+
+
+    $.fn.sparkline._base = createClass({
+        disabled: false,
+
+        init: function (el, values, options, width, height) {
+            this.el = el;
+            this.$el = $(el);
+            this.values = values;
+            this.options = options;
+            this.width = width;
+            this.height = height;
+            this.currentRegion = undefined;
+        },
+
+        /**
+         * Setup the canvas
+         */
+        initTarget: function () {
+            var interactive = !this.options.get('disableInteraction');
+            if (!(this.target = this.$el.simpledraw(this.width, this.height, this.options.get('composite'), interactive))) {
+                this.disabled = true;
+            } else {
+                this.canvasWidth = this.target.pixelWidth;
+                this.canvasHeight = this.target.pixelHeight;
+            }
+        },
+
+        /**
+         * Actually render the chart to the canvas
+         */
+        render: function () {
+            if (this.disabled) {
+                this.el.innerHTML = '';
+                return false;
+            }
+            return true;
+        },
+
+        /**
+         * Return a region id for a given x/y co-ordinate
+         */
+        getRegion: function (x, y) {
+        },
+
+        /**
+         * Highlight an item based on the moused-over x,y co-ordinate
+         */
+        setRegionHighlight: function (el, x, y) {
+            var currentRegion = this.currentRegion,
+                highlightEnabled = !this.options.get('disableHighlight'),
+                newRegion;
+            if (x > this.canvasWidth || y > this.canvasHeight || x < 0 || y < 0) {
+                return null;
+            }
+            newRegion = this.getRegion(el, x, y);
+            if (currentRegion !== newRegion) {
+                if (currentRegion !== undefined && highlightEnabled) {
+                    this.removeHighlight();
+                }
+                this.currentRegion = newRegion;
+                if (newRegion !== undefined && highlightEnabled) {
+                    this.renderHighlight();
+                }
+                return true;
+            }
+            return false;
+        },
+
+        /**
+         * Reset any currently highlighted item
+         */
+        clearRegionHighlight: function () {
+            if (this.currentRegion !== undefined) {
+                this.removeHighlight();
+                this.currentRegion = undefined;
+                return true;
+            }
+            return false;
+        },
+
+        renderHighlight: function () {
+            this.changeHighlight(true);
+        },
+
+        removeHighlight: function () {
+            this.changeHighlight(false);
+        },
+
+        changeHighlight: function (highlight)  {},
+
+        /**
+         * Fetch the HTML to display as a tooltip
+         */
+        getCurrentRegionTooltip: function () {
+            var options = this.options,
+                header = '',
+                entries = [],
+                fields, formats, formatlen, fclass, text, i,
+                showFields, showFieldsKey, newFields, fv,
+                formatter, format, fieldlen, j;
+            if (this.currentRegion === undefined) {
+                return '';
+            }
+            fields = this.getCurrentRegionFields();
+            formatter = options.get('tooltipFormatter');
+            if (formatter) {
+                return formatter(this, options, fields);
+            }
+            if (options.get('tooltipChartTitle')) {
+                header += '<div class="jqs jqstitle">' + options.get('tooltipChartTitle') + '</div>\n';
+            }
+            formats = this.options.get('tooltipFormat');
+            if (!formats) {
+                return '';
+            }
+            if (!$.isArray(formats)) {
+                formats = [formats];
+            }
+            if (!$.isArray(fields)) {
+                fields = [fields];
+            }
+            showFields = this.options.get('tooltipFormatFieldlist');
+            showFieldsKey = this.options.get('tooltipFormatFieldlistKey');
+            if (showFields && showFieldsKey) {
+                // user-selected ordering of fields
+                newFields = [];
+                for (i = fields.length; i--;) {
+                    fv = fields[i][showFieldsKey];
+                    if ((j = $.inArray(fv, showFields)) != -1) {
+                        newFields[j] = fields[i];
+                    }
+                }
+                fields = newFields;
+            }
+            formatlen = formats.length;
+            fieldlen = fields.length;
+            for (i = 0; i < formatlen; i++) {
+                format = formats[i];
+                if (typeof format === 'string') {
+                    format = new SPFormat(format);
+                }
+                fclass = format.fclass || 'jqsfield';
+                for (j = 0; j < fieldlen; j++) {
+                    if (!fields[j].isNull || !options.get('tooltipSkipNull')) {
+                        $.extend(fields[j], {
+                            prefix: options.get('tooltipPrefix'),
+                            suffix: options.get('tooltipSuffix')
+                        });
+                        text = format.render(fields[j], options.get('tooltipValueLookups'), options);
+                        entries.push('<div class="' + fclass + '">' + text + '</div>');
+                    }
+                }
+            }
+            if (entries.length) {
+                return header + entries.join('\n');
+            }
+            return '';
+        },
+
+        getCurrentRegionFields: function () {},
+
+        calcHighlightColor: function (color, options) {
+            var highlightColor = options.get('highlightColor'),
+                lighten = options.get('highlightLighten'),
+                parse, mult, rgbnew, i;
+            if (highlightColor) {
+                return highlightColor;
+            }
+            if (lighten) {
+                // extract RGB values
+                parse = /^#([0-9a-f])([0-9a-f])([0-9a-f])$/i.exec(color) || /^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i.exec(color);
+                if (parse) {
+                    rgbnew = [];
+                    mult = color.length === 4 ? 16 : 1;
+                    for (i = 0; i < 3; i++) {
+                        rgbnew[i] = clipval(Math.round(parseInt(parse[i + 1], 16) * mult * lighten), 0, 255);
+                    }
+                    return 'rgb(' + rgbnew.join(',') + ')';
+                }
+
+            }
+            return color;
+        }
+
+    });
+
+    barHighlightMixin = {
+        changeHighlight: function (highlight) {
+            var currentRegion = this.currentRegion,
+                target = this.target,
+                shapeids = this.regionShapes[currentRegion],
+                newShapes;
+            // will be null if the region value was null
+            if (shapeids) {
+                newShapes = this.renderRegion(currentRegion, highlight);
+                if ($.isArray(newShapes) || $.isArray(shapeids)) {
+                    target.replaceWithShapes(shapeids, newShapes);
+                    this.regionShapes[currentRegion] = $.map(newShapes, function (newShape) {
+                        return newShape.id;
+                    });
+                } else {
+                    target.replaceWithShape(shapeids, newShapes);
+                    this.regionShapes[currentRegion] = newShapes.id;
+                }
+            }
+        },
+
+        render: function () {
+            var values = this.values,
+                target = this.target,
+                regionShapes = this.regionShapes,
+                shapes, ids, i, j;
+
+            if (!this.cls._super.render.call(this)) {
+                return;
+            }
+            for (i = values.length; i--;) {
+                shapes = this.renderRegion(i);
+                if (shapes) {
+                    if ($.isArray(shapes)) {
+                        ids = [];
+                        for (j = shapes.length; j--;) {
+                            shapes[j].append();
+                            ids.push(shapes[j].id);
+                        }
+                        regionShapes[i] = ids;
+                    } else {
+                        shapes.append();
+                        regionShapes[i] = shapes.id; // store just the shapeid
+                    }
+                } else {
+                    // null value
+                    regionShapes[i] = null;
+                }
+            }
+            target.render();
+        }
+    };
+
+    /**
+     * Line charts
+     */
+    $.fn.sparkline.line = line = createClass($.fn.sparkline._base, {
+        type: 'line',
+
+        init: function (el, values, options, width, height) {
+            line._super.init.call(this, el, values, options, width, height);
+            this.vertices = [];
+            this.regionMap = [];
+            this.xvalues = [];
+            this.yvalues = [];
+            this.yminmax = [];
+            this.hightlightSpotId = null;
+            this.lastShapeId = null;
+            this.initTarget();
+        },
+
+        getRegion: function (el, x, y) {
+            var i,
+                regionMap = this.regionMap; // maps regions to value positions
+            for (i = regionMap.length; i--;) {
+                if (regionMap[i] !== null && x >= regionMap[i][0] && x <= regionMap[i][1]) {
+                    return regionMap[i][2];
+                }
+            }
+            return undefined;
+        },
+
+        getCurrentRegionFields: function () {
+            var currentRegion = this.currentRegion;
+            return {
+                isNull: this.yvalues[currentRegion] === null,
+                x: this.xvalues[currentRegion],
+                y: this.yvalues[currentRegion],
+                color: this.options.get('lineColor'),
+                fillColor: this.options.get('fillColor'),
+                offset: currentRegion
+            };
+        },
+
+        renderHighlight: function () {
+            var currentRegion = this.currentRegion,
+                target = this.target,
+                vertex = this.vertices[currentRegion],
+                options = this.options,
+                spotRadius = options.get('spotRadius'),
+                highlightSpotColor = options.get('highlightSpotColor'),
+                highlightLineColor = options.get('highlightLineColor'),
+                highlightSpot, highlightLine;
+
+            if (!vertex) {
+                return;
+            }
+            if (spotRadius && highlightSpotColor) {
+                highlightSpot = target.drawCircle(vertex[0], vertex[1],
+                    spotRadius, undefined, highlightSpotColor);
+                this.highlightSpotId = highlightSpot.id;
+                target.insertAfterShape(this.lastShapeId, highlightSpot);
+            }
+            if (highlightLineColor) {
+                highlightLine = target.drawLine(vertex[0], this.canvasTop, vertex[0],
+                    this.canvasTop + this.canvasHeight, highlightLineColor);
+                this.highlightLineId = highlightLine.id;
+                target.insertAfterShape(this.lastShapeId, highlightLine);
+            }
+        },
+
+        removeHighlight: function () {
+            var target = this.target;
+            if (this.highlightSpotId) {
+                target.removeShapeId(this.highlightSpotId);
+                this.highlightSpotId = null;
+            }
+            if (this.highlightLineId) {
+                target.removeShapeId(this.highlightLineId);
+                this.highlightLineId = null;
+            }
+        },
+
+        scanValues: function () {
+            var values = this.values,
+                valcount = values.length,
+                xvalues = this.xvalues,
+                yvalues = this.yvalues,
+                yminmax = this.yminmax,
+                i, val, isStr, isArray, sp;
+            for (i = 0; i < valcount; i++) {
+                val = values[i];
+                isStr = typeof(values[i]) === 'string';
+                isArray = typeof(values[i]) === 'object' && values[i] instanceof Array;
+                sp = isStr && values[i].split(':');
+                if (isStr && sp.length === 2) { // x:y
+                    xvalues.push(Number(sp[0]));
+                    yvalues.push(Number(sp[1]));
+                    yminmax.push(Number(sp[1]));
+                } else if (isArray) {
+                    xvalues.push(val[0]);
+                    yvalues.push(val[1]);
+                    yminmax.push(val[1]);
+                } else {
+                    xvalues.push(i);
+                    if (values[i] === null || values[i] === 'null') {
+                        yvalues.push(null);
+                    } else {
+                        yvalues.push(Number(val));
+                        yminmax.push(Number(val));
+                    }
+                }
+            }
+            if (this.options.get('xvalues')) {
+                xvalues = this.options.get('xvalues');
+            }
+
+            this.maxy = this.maxyorg = Math.max.apply(Math, yminmax);
+            this.miny = this.minyorg = Math.min.apply(Math, yminmax);
+
+            this.maxx = Math.max.apply(Math, xvalues);
+            this.minx = Math.min.apply(Math, xvalues);
+
+            this.xvalues = xvalues;
+            this.yvalues = yvalues;
+            this.yminmax = yminmax;
+
+        },
+
+        processRangeOptions: function () {
+            var options = this.options,
+                normalRangeMin = options.get('normalRangeMin'),
+                normalRangeMax = options.get('normalRangeMax');
+
+            if (normalRangeMin !== undefined) {
+                if (normalRangeMin < this.miny) {
+                    this.miny = normalRangeMin;
+                }
+                if (normalRangeMax > this.maxy) {
+                    this.maxy = normalRangeMax;
+                }
+            }
+            if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.miny)) {
+                this.miny = options.get('chartRangeMin');
+            }
+            if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.maxy)) {
+                this.maxy = options.get('chartRangeMax');
+            }
+            if (options.get('chartRangeMinX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMinX') < this.minx)) {
+                this.minx = options.get('chartRangeMinX');
+            }
+            if (options.get('chartRangeMaxX') !== undefined && (options.get('chartRangeClipX') || options.get('chartRangeMaxX') > this.maxx)) {
+                this.maxx = options.get('chartRangeMaxX');
+            }
+
+        },
+
+        drawNormalRange: function (canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey) {
+            var normalRangeMin = this.options.get('normalRangeMin'),
+                normalRangeMax = this.options.get('normalRangeMax'),
+                ytop = canvasTop + Math.round(canvasHeight - (canvasHeight * ((normalRangeMax - this.miny) / rangey))),
+                height = Math.round((canvasHeight * (normalRangeMax - normalRangeMin)) / rangey);
+            this.target.drawRect(canvasLeft, ytop, canvasWidth, height, undefined, this.options.get('normalRangeColor')).append();
+        },
+
+        render: function () {
+            var options = this.options,
+                target = this.target,
+                canvasWidth = this.canvasWidth,
+                canvasHeight = this.canvasHeight,
+                vertices = this.vertices,
+                spotRadius = options.get('spotRadius'),
+                regionMap = this.regionMap,
+                rangex, rangey, yvallast,
+                canvasTop, canvasLeft,
+                vertex, path, paths, x, y, xnext, xpos, xposnext,
+                last, next, yvalcount, lineShapes, fillShapes, plen,
+                valueSpots, hlSpotsEnabled, color, xvalues, yvalues, i;
+
+            if (!line._super.render.call(this)) {
+                return;
+            }
+
+            this.scanValues();
+            this.processRangeOptions();
+
+            xvalues = this.xvalues;
+            yvalues = this.yvalues;
+
+            if (!this.yminmax.length || this.yvalues.length < 2) {
+                // empty or all null valuess
+                return;
+            }
+
+            canvasTop = canvasLeft = 0;
+
+            rangex = this.maxx - this.minx === 0 ? 1 : this.maxx - this.minx;
+            rangey = this.maxy - this.miny === 0 ? 1 : this.maxy - this.miny;
+            yvallast = this.yvalues.length - 1;
+
+            if (spotRadius && (canvasWidth < (spotRadius * 4) || canvasHeight < (spotRadius * 4))) {
+                spotRadius = 0;
+            }
+            if (spotRadius) {
+                // adjust the canvas size as required so that spots will fit
+                hlSpotsEnabled = options.get('highlightSpotColor') &&  !options.get('disableInteraction');
+                if (hlSpotsEnabled || options.get('minSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.miny)) {
+                    canvasHeight -= Math.ceil(spotRadius);
+                }
+                if (hlSpotsEnabled || options.get('maxSpotColor') || (options.get('spotColor') && yvalues[yvallast] === this.maxy)) {
+                    canvasHeight -= Math.ceil(spotRadius);
+                    canvasTop += Math.ceil(spotRadius);
+                }
+                if (hlSpotsEnabled ||
+                     ((options.get('minSpotColor') || options.get('maxSpotColor')) && (yvalues[0] === this.miny || yvalues[0] === this.maxy))) {
+                    canvasLeft += Math.ceil(spotRadius);
+                    canvasWidth -= Math.ceil(spotRadius);
+                }
+                if (hlSpotsEnabled || options.get('spotColor') ||
+                    (options.get('minSpotColor') || options.get('maxSpotColor') &&
+                        (yvalues[yvallast] === this.miny || yvalues[yvallast] === this.maxy))) {
+                    canvasWidth -= Math.ceil(spotRadius);
+                }
+            }
+
+
+            canvasHeight--;
+
+            if (options.get('normalRangeMin') !== undefined && !options.get('drawNormalOnTop')) {
+                this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);
+            }
+
+            path = [];
+            paths = [path];
+            last = next = null;
+            yvalcount = yvalues.length;
+            for (i = 0; i < yvalcount; i++) {
+                x = xvalues[i];
+                xnext = xvalues[i + 1];
+                y = yvalues[i];
+                xpos = canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex));
+                xposnext = i < yvalcount - 1 ? canvasLeft + Math.round((xnext - this.minx) * (canvasWidth / rangex)) : canvasWidth;
+                next = xpos + ((xposnext - xpos) / 2);
+                regionMap[i] = [last || 0, next, i];
+                last = next;
+                if (y === null) {
+                    if (i) {
+                        if (yvalues[i - 1] !== null) {
+                            path = [];
+                            paths.push(path);
+                        }
+                        vertices.push(null);
+                    }
+                } else {
+                    if (y < this.miny) {
+                        y = this.miny;
+                    }
+                    if (y > this.maxy) {
+                        y = this.maxy;
+                    }
+                    if (!path.length) {
+                        // previous value was null
+                        path.push([xpos, canvasTop + canvasHeight]);
+                    }
+                    vertex = [xpos, canvasTop + Math.round(canvasHeight - (canvasHeight * ((y - this.miny) / rangey)))];
+                    path.push(vertex);
+                    vertices.push(vertex);
+                }
+            }
+
+            lineShapes = [];
+            fillShapes = [];
+            plen = paths.length;
+            for (i = 0; i < plen; i++) {
+                path = paths[i];
+                if (path.length) {
+                    if (options.get('fillColor')) {
+                        path.push([path[path.length - 1][0], (canvasTop + canvasHeight)]);
+                        fillShapes.push(path.slice(0));
+                        path.pop();
+                    }
+                    // if there's only a single point in this path, then we want to display it
+                    // as a vertical line which means we keep path[0]  as is
+                    if (path.length > 2) {
+                        // else we want the first value
+                        path[0] = [path[0][0], path[1][1]];
+                    }
+                    lineShapes.push(path);
+                }
+            }
+
+            // draw the fill first, then optionally the normal range, then the line on top of that
+            plen = fillShapes.length;
+            for (i = 0; i < plen; i++) {
+                target.drawShape(fillShapes[i],
+                    options.get('fillColor'), options.get('fillColor')).append();
+            }
+
+            if (options.get('normalRangeMin') !== undefined && options.get('drawNormalOnTop')) {
+                this.drawNormalRange(canvasLeft, canvasTop, canvasHeight, canvasWidth, rangey);
+            }
+
+            plen = lineShapes.length;
+            for (i = 0; i < plen; i++) {
+                target.drawShape(lineShapes[i], options.get('lineColor'), undefined,
+                    options.get('lineWidth')).append();
+            }
+
+            if (spotRadius && options.get('valueSpots')) {
+                valueSpots = options.get('valueSpots');
+                if (valueSpots.get === undefined) {
+                    valueSpots = new RangeMap(valueSpots);
+                }
+                for (i = 0; i < yvalcount; i++) {
+                    color = valueSpots.get(yvalues[i]);
+                    if (color) {
+                        target.drawCircle(canvasLeft + Math.round((xvalues[i] - this.minx) * (canvasWidth / rangex)),
+                            canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[i] - this.miny) / rangey))),
+                            spotRadius, undefined,
+                            color).append();
+                    }
+                }
+
+            }
+            if (spotRadius && options.get('spotColor') && yvalues[yvallast] !== null) {
+                target.drawCircle(canvasLeft + Math.round((xvalues[xvalues.length - 1] - this.minx) * (canvasWidth / rangex)),
+                    canvasTop + Math.round(canvasHeight - (canvasHeight * ((yvalues[yvallast] - this.miny) / rangey))),
+                    spotRadius, undefined,
+                    options.get('spotColor')).append();
+            }
+            if (this.maxy !== this.minyorg) {
+                if (spotRadius && options.get('minSpotColor')) {
+                    x = xvalues[$.inArray(this.minyorg, yvalues)];
+                    target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),
+                        canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.minyorg - this.miny) / rangey))),
+                        spotRadius, undefined,
+                        options.get('minSpotColor')).append();
+                }
+                if (spotRadius && options.get('maxSpotColor')) {
+                    x = xvalues[$.inArray(this.maxyorg, yvalues)];
+                    target.drawCircle(canvasLeft + Math.round((x - this.minx) * (canvasWidth / rangex)),
+                        canvasTop + Math.round(canvasHeight - (canvasHeight * ((this.maxyorg - this.miny) / rangey))),
+                        spotRadius, undefined,
+                        options.get('maxSpotColor')).append();
+                }
+            }
+
+            this.lastShapeId = target.getLastShapeId();
+            this.canvasTop = canvasTop;
+            target.render();
+        }
+    });
+
+    /**
+     * Bar charts
+     */
+    $.fn.sparkline.bar = bar = createClass($.fn.sparkline._base, barHighlightMixin, {
+        type: 'bar',
+
+        init: function (el, values, options, width, height) {
+            var barWidth = parseInt(options.get('barWidth'), 10),
+                barSpacing = parseInt(options.get('barSpacing'), 10),
+                chartRangeMin = options.get('chartRangeMin'),
+                chartRangeMax = options.get('chartRangeMax'),
+                chartRangeClip = options.get('chartRangeClip'),
+                stackMin = Infinity,
+                stackMax = -Infinity,
+                isStackString, groupMin, groupMax, stackRanges,
+                numValues, i, vlen, range, zeroAxis, xaxisOffset, min, max, clipMin, clipMax,
+                stacked, vlist, j, slen, svals, val, yoffset, yMaxCalc, canvasHeightEf;
+            bar._super.init.call(this, el, values, options, width, height);
+
+            // scan values to determine whether to stack bars
+            for (i = 0, vlen = values.length; i < vlen; i++) {
+                val = values[i];
+                isStackString = typeof(val) === 'string' && val.indexOf(':') > -1;
+                if (isStackString || $.isArray(val)) {
+                    stacked = true;
+                    if (isStackString) {
+                        val = values[i] = normalizeValues(val.split(':'));
+                    }
+                    val = remove(val, null); // min/max will treat null as zero
+                    groupMin = Math.min.apply(Math, val);
+                    groupMax = Math.max.apply(Math, val);
+                    if (groupMin < stackMin) {
+                        stackMin = groupMin;
+                    }
+                    if (groupMax > stackMax) {
+                        stackMax = groupMax;
+                    }
+                }
+            }
+
+            this.stacked = stacked;
+            this.regionShapes = {};
+            this.barWidth = barWidth;
+            this.barSpacing = barSpacing;
+            this.totalBarWidth = barWidth + barSpacing;
+            this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
+
+            this.initTarget();
+
+            if (chartRangeClip) {
+                clipMin = chartRangeMin === undefined ? -Infinity : chartRangeMin;
+                clipMax = chartRangeMax === undefined ? Infinity : chartRangeMax;
+            }
+
+            numValues = [];
+            stackRanges = stacked ? [] : numValues;
+            var stackTotals = [];
+            var stackRangesNeg = [];
+            for (i = 0, vlen = values.length; i < vlen; i++) {
+                if (stacked) {
+                    vlist = values[i];
+                    values[i] = svals = [];
+                    stackTotals[i] = 0;
+                    stackRanges[i] = stackRangesNeg[i] = 0;
+                    for (j = 0, slen = vlist.length; j < slen; j++) {
+                        val = svals[j] = chartRangeClip ? clipval(vlist[j], clipMin, clipMax) : vlist[j];
+                        if (val !== null) {
+                            if (val > 0) {
+                                stackTotals[i] += val;
+                            }
+                            if (stackMin < 0 && stackMax > 0) {
+                                if (val < 0) {
+                                    stackRangesNeg[i] += Math.abs(val);
+                                } else {
+                                    stackRanges[i] += val;
+                                }
+                            } else {
+                                stackRanges[i] += Math.abs(val - (val < 0 ? stackMax : stackMin));
+                            }
+                            numValues.push(val);
+                        }
+                    }
+                } else {
+                    val = chartRangeClip ? clipval(values[i], clipMin, clipMax) : values[i];
+                    val = values[i] = normalizeValue(val);
+                    if (val !== null) {
+                        numValues.push(val);
+                    }
+                }
+            }
+            this.max = max = Math.max.apply(Math, numValues);
+            this.min = min = Math.min.apply(Math, numValues);
+            this.stackMax = stackMax = stacked ? Math.max.apply(Math, stackTotals) : max;
+            this.stackMin = stackMin = stacked ? Math.min.apply(Math, numValues) : min;
+
+            if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < min)) {
+                min = options.get('chartRangeMin');
+            }
+            if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > max)) {
+                max = options.get('chartRangeMax');
+            }
+
+            this.zeroAxis = zeroAxis = options.get('zeroAxis', true);
+            if (min <= 0 && max >= 0 && zeroAxis) {
+                xaxisOffset = 0;
+            } else if (zeroAxis == false) {
+                xaxisOffset = min;
+            } else if (min > 0) {
+                xaxisOffset = min;
+            } else {
+                xaxisOffset = max;
+            }
+            this.xaxisOffset = xaxisOffset;
+
+            range = stacked ? (Math.max.apply(Math, stackRanges) + Math.max.apply(Math, stackRangesNeg)) : max - min;
+
+            // as we plot zero/min values a single pixel line, we add a pixel to all other
+            // values - Reduce the effective canvas size to suit
+            this.canvasHeightEf = (zeroAxis && min < 0) ? this.canvasHeight - 2 : this.canvasHeight - 1;
+
+            if (min < xaxisOffset) {
+                yMaxCalc = (stacked && max >= 0) ? stackMax : max;
+                yoffset = (yMaxCalc - xaxisOffset) / range * this.canvasHeight;
+                if (yoffset !== Math.ceil(yoffset)) {
+                    this.canvasHeightEf -= 2;
+                    yoffset = Math.ceil(yoffset);
+                }
+            } else {
+                yoffset = this.canvasHeight;
+            }
+            this.yoffset = yoffset;
+
+            if ($.isArray(options.get('colorMap'))) {
+                this.colorMapByIndex = options.get('colorMap');
+                this.colorMapByValue = null;
+            } else {
+                this.colorMapByIndex = null;
+                this.colorMapByValue = options.get('colorMap');
+                if (this.colorMapByValue && this.colorMapByValue.get === undefined) {
+                    this.colorMapByValue = new RangeMap(this.colorMapByValue);
+                }
+            }
+
+            this.range = range;
+        },
+
+        getRegion: function (el, x, y) {
+            var result = Math.floor(x / this.totalBarWidth);
+            return (result < 0 || result >= this.values.length) ? undefined : result;
+        },
+
+        getCurrentRegionFields: function () {
+            var currentRegion = this.currentRegion,
+                values = ensureArray(this.values[currentRegion]),
+                result = [],
+                value, i;
+            for (i = values.length; i--;) {
+                value = values[i];
+                result.push({
+                    isNull: value === null,
+                    value: value,
+                    color: this.calcColor(i, value, currentRegion),
+                    offset: currentRegion
+                });
+            }
+            return result;
+        },
+
+        calcColor: function (stacknum, value, valuenum) {
+            var colorMapByIndex = this.colorMapByIndex,
+                colorMapByValue = this.colorMapByValue,
+                options = this.options,
+                color, newColor;
+            if (this.stacked) {
+                color = options.get('stackedBarColor');
+            } else {
+                color = (value < 0) ? options.get('negBarColor') : options.get('barColor');
+            }
+            if (value === 0 && options.get('zeroColor') !== undefined) {
+                color = options.get('zeroColor');
+            }
+            if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
+                color = newColor;
+            } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
+                color = colorMapByIndex[valuenum];
+            }
+            return $.isArray(color) ? color[stacknum % color.length] : color;
+        },
+
+        /**
+         * Render bar(s) for a region
+         */
+        renderRegion: function (valuenum, highlight) {
+            var vals = this.values[valuenum],
+                options = this.options,
+                xaxisOffset = this.xaxisOffset,
+                result = [],
+                range = this.range,
+                stacked = this.stacked,
+                target = this.target,
+                x = valuenum * this.totalBarWidth,
+                canvasHeightEf = this.canvasHeightEf,
+                yoffset = this.yoffset,
+                y, height, color, isNull, yoffsetNeg, i, valcount, val, minPlotted, allMin;
+
+            vals = $.isArray(vals) ? vals : [vals];
+            valcount = vals.length;
+            val = vals[0];
+            isNull = all(null, vals);
+            allMin = all(xaxisOffset, vals, true);
+
+            if (isNull) {
+                if (options.get('nullColor')) {
+                    color = highlight ? options.get('nullColor') : this.calcHighlightColor(options.get('nullColor'), options);
+                    y = (yoffset > 0) ? yoffset - 1 : yoffset;
+                    return target.drawRect(x, y, this.barWidth - 1, 0, color, color);
+                } else {
+                    return undefined;
+                }
+            }
+            yoffsetNeg = yoffset;
+            for (i = 0; i < valcount; i++) {
+                val = vals[i];
+
+                if (stacked && val === xaxisOffset) {
+                    if (!allMin || minPlotted) {
+                        continue;
+                    }
+                    minPlotted = true;
+                }
+
+                if (range > 0) {
+                    height = Math.floor(canvasHeightEf * ((Math.abs(val - xaxisOffset) / range))) + 1;
+                } else {
+                    height = 1;
+                }
+                if (val < xaxisOffset || (val === xaxisOffset && yoffset === 0)) {
+                    y = yoffsetNeg;
+                    yoffsetNeg += height;
+                } else {
+                    y = yoffset - height;
+                    yoffset -= height;
+                }
+                color = this.calcColor(i, val, valuenum);
+                if (highlight) {
+                    color = this.calcHighlightColor(color, options);
+                }
+                result.push(target.drawRect(x, y, this.barWidth - 1, height - 1, color, color));
+            }
+            if (result.length === 1) {
+                return result[0];
+            }
+            return result;
+        }
+    });
+
+    /**
+     * Tristate charts
+     */
+    $.fn.sparkline.tristate = tristate = createClass($.fn.sparkline._base, barHighlightMixin, {
+        type: 'tristate',
+
+        init: function (el, values, options, width, height) {
+            var barWidth = parseInt(options.get('barWidth'), 10),
+                barSpacing = parseInt(options.get('barSpacing'), 10);
+            tristate._super.init.call(this, el, values, options, width, height);
+
+            this.regionShapes = {};
+            this.barWidth = barWidth;
+            this.barSpacing = barSpacing;
+            this.totalBarWidth = barWidth + barSpacing;
+            this.values = $.map(values, Number);
+            this.width = width = (values.length * barWidth) + ((values.length - 1) * barSpacing);
+
+            if ($.isArray(options.get('colorMap'))) {
+                this.colorMapByIndex = options.get('colorMap');
+                this.colorMapByValue = null;
+            } else {
+                this.colorMapByIndex = null;
+                this.colorMapByValue = options.get('colorMap');
+                if (this.colorMapByValue && this.colorMapByValue.get === undefined) {
+                    this.colorMapByValue = new RangeMap(this.colorMapByValue);
+                }
+            }
+            this.initTarget();
+        },
+
+        getRegion: function (el, x, y) {
+            return Math.floor(x / this.totalBarWidth);
+        },
+
+        getCurrentRegionFields: function () {
+            var currentRegion = this.currentRegion;
+            return {
+                isNull: this.values[currentRegion] === undefined,
+                value: this.values[currentRegion],
+                color: this.calcColor(this.values[currentRegion], currentRegion),
+                offset: currentRegion
+            };
+        },
+
+        calcColor: function (value, valuenum) {
+            var values = this.values,
+                options = this.options,
+                colorMapByIndex = this.colorMapByIndex,
+                colorMapByValue = this.colorMapByValue,
+                color, newColor;
+
+            if (colorMapByValue && (newColor = colorMapByValue.get(value))) {
+                color = newColor;
+            } else if (colorMapByIndex && colorMapByIndex.length > valuenum) {
+                color = colorMapByIndex[valuenum];
+            } else if (values[valuenum] < 0) {
+                color = options.get('negBarColor');
+            } else if (values[valuenum] > 0) {
+                color = options.get('posBarColor');
+            } else {
+                color = options.get('zeroBarColor');
+            }
+            return color;
+        },
+
+        renderRegion: function (valuenum, highlight) {
+            var values = this.values,
+                options = this.options,
+                target = this.target,
+                canvasHeight, height, halfHeight,
+                x, y, color;
+
+            canvasHeight = target.pixelHeight;
+            halfHeight = Math.round(canvasHeight / 2);
+
+            x = valuenum * this.totalBarWidth;
+            if (values[valuenum] < 0) {
+                y = halfHeight;
+                height = halfHeight - 1;
+            } else if (values[valuenum] > 0) {
+                y = 0;
+                height = halfHeight - 1;
+            } else {
+                y = halfHeight - 1;
+                height = 2;
+            }
+            color = this.calcColor(values[valuenum], valuenum);
+            if (color === null) {
+                return;
+            }
+            if (highlight) {
+                color = this.calcHighlightColor(color, options);
+            }
+            return target.drawRect(x, y, this.barWidth - 1, height - 1, color, color);
+        }
+    });
+
+    /**
+     * Discrete charts
+     */
+    $.fn.sparkline.discrete = discrete = createClass($.fn.sparkline._base, barHighlightMixin, {
+        type: 'discrete',
+
+        init: function (el, values, options, width, height) {
+            discrete._super.init.call(this, el, values, options, width, height);
+
+            this.regionShapes = {};
+            this.values = values = $.map(values, Number);
+            this.min = Math.min.apply(Math, values);
+            this.max = Math.max.apply(Math, values);
+            this.range = this.max - this.min;
+            this.width = width = options.get('width') === 'auto' ? values.length * 2 : this.width;
+            this.interval = Math.floor(width / values.length);
+            this.itemWidth = width / values.length;
+            if (options.get('chartRangeMin') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMin') < this.min)) {
+                this.min = options.get('chartRangeMin');
+            }
+            if (options.get('chartRangeMax') !== undefined && (options.get('chartRangeClip') || options.get('chartRangeMax') > this.max)) {
+                this.max = options.get('chartRangeMax');
+            }
+            this.initTarget();
+            if (this.target) {
+                this.lineHeight = options.get('lineHeight') === 'auto' ? Math.round(this.canvasHeight * 0.3) : options.get('lineHeight');
+            }
+        },
+
+        getRegion: function (el, x, y) {
+            return Math.floor(x / this.itemWidth);
+        },
+
+        getCurrentRegionFields: function () {
+            var currentRegion = this.currentRegion;
+            return {
+                isNull: this.values[currentRegion] === undefined,
+                value: this.values[currentRegion],
+                offset: currentRegion
+            };
+        },
+
+        renderRegion: function (valuenum, highlight) {
+            var values = this.values,
+                options = this.options,
+                min = this.min,
+                max = this.max,
+                range = this.range,
+                interval = this.interval,
+                target = this.target,
+                canvasHeight = this.canvasHeight,
+                lineHeight = this.lineHeight,
+                pheight = canvasHeight - lineHeight,
+                ytop, val, color, x;
+
+            val = clipval(values[valuenum], min, max);
+            x = valuenum * interval;
+            ytop = Math.round(pheight - pheight * ((val - min) / range));
+            color = (options.get('thresholdColor') && val < options.get('thresholdValue')) ? options.get('thresholdColor') : options.get('lineColor');
+            if (highlight) {
+                color = this.calcHighlightColor(color, options);
+            }
+            return target.drawLine(x, ytop, x, ytop + lineHeight, color);
+        }
+    });
+
+    /**
+     * Bullet charts
+     */
+    $.fn.sparkline.bullet = bullet = createClass($.fn.sparkline._base, {
+        type: 'bullet',
+
+        init: function (el, values, options, width, height) {
+            var min, max, vals;
+            bullet._super.init.call(this, el, values, options, width, height);
+
+            // values: target, performance, range1, range2, range3
+            this.values = values = normalizeValues(values);
+            // target or performance could be null
+            vals = values.slice();
+            vals[0] = vals[0] === null ? vals[2] : vals[0];
+            vals[1] = values[1] === null ? vals[2] : vals[1];
+            min = Math.min.apply(Math, values);
+            max = Math.max.apply(Math, values);
+            if (options.get('base') === undefined) {
+                min = min < 0 ? min : 0;
+            } else {
+                min = options.get('base');
+            }
+            this.min = min;
+            this.max = max;
+            this.range = max - min;
+            this.shapes = {};
+            this.valueShapes = {};
+            this.regiondata = {};
+            this.width = width = options.get('width') === 'auto' ? '4.0em' : width;
+            this.target = this.$el.simpledraw(width, height, options.get('composite'));
+            if (!values.length) {
+                this.disabled = true;
+            }
+            this.initTarget();
+        },
+
+        getRegion: function (el, x, y) {
+            var shapeid = this.target.getShapeAt(el, x, y);
+            return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;
+        },
+
+        getCurrentRegionFields: function () {
+            var currentRegion = this.currentRegion;
+            return {
+                fieldkey: currentRegion.substr(0, 1),
+                value: this.values[currentRegion.substr(1)],
+                region: currentRegion
+            };
+        },
+
+        changeHighlight: function (highlight) {
+            var currentRegion = this.currentRegion,
+                shapeid = this.valueShapes[currentRegion],
+                shape;
+            delete this.shapes[shapeid];
+            switch (currentRegion.substr(0, 1)) {
+                case 'r':
+                    shape = this.renderRange(currentRegion.substr(1), highlight);
+                    break;
+                case 'p':
+                    shape = this.renderPerformance(highlight);
+                    break;
+                case 't':
+                    shape = this.renderTarget(highlight);
+                    break;
+            }
+            this.valueShapes[currentRegion] = shape.id;
+            this.shapes[shape.id] = currentRegion;
+            this.target.replaceWithShape(shapeid, shape);
+        },
+
+        renderRange: function (rn, highlight) {
+            var rangeval = this.values[rn],
+                rangewidth = Math.round(this.canvasWidth * ((rangeval - this.min) / this.range)),
+                color = this.options.get('rangeColors')[rn - 2];
+            if (highlight) {
+                color = this.calcHighlightColor(color, this.options);
+            }
+            return this.target.drawRect(0, 0, rangewidth - 1, this.canvasHeight - 1, color, color);
+        },
+
+        renderPerformance: function (highlight) {
+            var perfval = this.values[1],
+                perfwidth = Math.round(this.canvasWidth * ((perfval - this.min) / this.range)),
+                color = this.options.get('performanceColor');
+            if (highlight) {
+                color = this.calcHighlightColor(color, this.options);
+            }
+            return this.target.drawRect(0, Math.round(this.canvasHeight * 0.3), perfwidth - 1,
+                Math.round(this.canvasHeight * 0.4) - 1, color, color);
+        },
+
+        renderTarget: function (highlight) {
+            var targetval = this.values[0],
+                x = Math.round(this.canvasWidth * ((targetval - this.min) / this.range) - (this.options.get('targetWidth') / 2)),
+                targettop = Math.round(this.canvasHeight * 0.10),
+                targetheight = this.canvasHeight - (targettop * 2),
+                color = this.options.get('targetColor');
+            if (highlight) {
+                color = this.calcHighlightColor(color, this.options);
+            }
+            return this.target.drawRect(x, targettop, this.options.get('targetWidth') - 1, targetheight - 1, color, color);
+        },
+
+        render: function () {
+            var vlen = this.values.length,
+                target = this.target,
+                i, shape;
+            if (!bullet._super.render.call(this)) {
+                return;
+            }
+            for (i = 2; i < vlen; i++) {
+                shape = this.renderRange(i).append();
+                this.shapes[shape.id] = 'r' + i;
+                this.valueShapes['r' + i] = shape.id;
+            }
+            if (this.values[1] !== null) {
+                shape = this.renderPerformance().append();
+                this.shapes[shape.id] = 'p1';
+                this.valueShapes.p1 = shape.id;
+            }
+            if (this.values[0] !== null) {
+                shape = this.renderTarget().append();
+                this.shapes[shape.id] = 't0';
+                this.valueShapes.t0 = shape.id;
+            }
+            target.render();
+        }
+    });
+
+    /**
+     * Pie charts
+     */
+    $.fn.sparkline.pie = pie = createClass($.fn.sparkline._base, {
+        type: 'pie',
+
+        init: function (el, values, options, width, height) {
+            var total = 0, i;
+
+            pie._super.init.call(this, el, values, options, width, height);
+
+            this.shapes = {}; // map shape ids to value offsets
+            this.valueShapes = {}; // maps value offsets to shape ids
+            this.values = values = $.map(values, Number);
+
+            if (options.get('width') === 'auto') {
+                this.width = this.height;
+            }
+
+            if (values.length > 0) {
+                for (i = values.length; i--;) {
+                    total += values[i];
+                }
+            }
+            this.total = total;
+            this.initTarget();
+            this.radius = Math.floor(Math.min(this.canvasWidth, this.canvasHeight) / 2);
+        },
+
+        getRegion: function (el, x, y) {
+            var shapeid = this.target.getShapeAt(el, x, y);
+            return (shapeid !== undefined && this.shapes[shapeid] !== undefined) ? this.shapes[shapeid] : undefined;
+        },
+
+        getCurrentRegionFields: function () {
+            var currentRegion = this.currentRegion;
+            return {
+                isNull: this.values[currentRegion] === undefined,
+                value: this.values[currentRegion],
+                percent: this.values[currentRegion] / this.total * 100,
+                color: this.options.get('sliceColors')[currentRegion % this.options.get('sliceColors').length],
+                offset: currentRegion
+            };
+        },
+
+        changeHighlight: function (highlight) {
+            var currentRegion = this.currentRegion,
+                 newslice = this.renderSlice(currentRegion, highlight),
+                 shapeid = this.valueShapes[currentRegion];
+            delete this.shapes[shapeid];
+            this.target.replaceWithShape(shapeid, newslice);
+            this.valueShapes[currentRegion] = newslice.id;
+            this.shapes[newslice.id] = currentRegion;
+        },
+
+        renderSlice: function (valuenum, highlight) {
+            var target = this.target,
+                options = this.options,
+                radius = this.radius,
+                borderWidth = options.get('borderWidth'),
+                offset = options.get('offset'),
+                circle = 2 * Math.PI,
+                values = this.values,
+                total = this.total,
+                next = offset ? (2*Math.PI)*(offset/360) : 0,
+                start, end, i, vlen, color;
+
+            vlen = values.length;
+            for (i = 0; i < vlen; i++) {
+                start = next;
+                end = next;
+                if (total > 0) {  // avoid divide by zero
+                    end = next + (circle * (values[i] / total));
+                }
+                if (valuenum === i) {
+                    color = options.get('sliceColors')[i % options.get('sliceColors').length];
+                    if (highlight) {
+                        color = this.calcHighlightColor(color, options);
+                    }
+
+                    return target.drawPieSlice(radius, radius, radius - borderWidth, start, end, undefined, color);
+                }
+                next = end;
+            }
+        },
+
+        render: function () {
+            var target = this.target,
+                values = this.values,
+                options = this.options,
+                radius = this.radius,
+                borderWidth = options.get('borderWidth'),
+                shape, i;
+
+            if (!pie._super.render.call(this)) {
+                return;
+            }
+            if (borderWidth) {
+                target.drawCircle(radius, radius, Math.floor(radius - (borderWidth / 2)),
+                    options.get('borderColor'), undefined, borderWidth).append();
+            }
+            for (i = values.length; i--;) {
+                if (values[i]) { // don't render zero values
+                    shape = this.renderSlice(i).append();
+                    this.valueShapes[i] = shape.id; // store just the shapeid
+                    this.shapes[shape.id] = i;
+                }
+            }
+            target.render();
+        }
+    });
+
+    /**
+     * Box plots
+     */
+    $.fn.sparkline.box = box = createClass($.fn.sparkline._base, {
+        type: 'box',
+
+        init: function (el, values, options, width, height) {
+            box._super.init.call(this, el, values, options, width, height);
+            this.values = $.map(values, Number);
+            this.width = options.get('width') === 'auto' ? '4.0em' : width;
+            this.initTarget();
+            if (!this.values.length) {
+                this.disabled = 1;
+            }
+        },
+
+        /**
+         * Simulate a single region
+         */
+        getRegion: function () {
+            return 1;
+        },
+
+        getCurrentRegionFields: function () {
+            var result = [
+                { field: 'lq', value: this.quartiles[0] },
+                { field: 'med', value: this.quartiles[1] },
+                { field: 'uq', value: this.quartiles[2] }
+            ];
+            if (this.loutlier !== undefined) {
+                result.push({ field: 'lo', value: this.loutlier});
+            }
+            if (this.routlier !== undefined) {
+                result.push({ field: 'ro', value: this.routlier});
+            }
+            if (this.lwhisker !== undefined) {
+                result.push({ field: 'lw', value: this.lwhisker});
+            }
+            if (this.rwhisker !== undefined) {
+                result.push({ field: 'rw', value: this.rwhisker});
+            }
+            return result;
+        },
+
+        render: function () {
+            var target = this.target,
+                values = this.values,
+                vlen = values.length,
+                options = this.options,
+                canvasWidth = this.canvasWidth,
+                canvasHeight = this.canvasHeight,
+                minValue = options.get('chartRangeMin') === undefined ? Math.min.apply(Math, values) : options.get('chartRangeMin'),
+                maxValue = options.get('chartRangeMax') === undefined ? Math.max.apply(Math, values) : options.get('chartRangeMax'),
+                canvasLeft = 0,
+                lwhisker, loutlier, iqr, q1, q2, q3, rwhisker, routlier, i,
+                size, unitSize;
+
+            if (!box._super.render.call(this)) {
+                return;
+            }
+
+            if (options.get('raw')) {
+                if (options.get('showOutliers') && values.length > 5) {
+                    loutlier = values[0];
+                    lwhisker = values[1];
+                    q1 = values[2];
+                    q2 = values[3];
+                    q3 = values[4];
+                    rwhisker = values[5];
+                    routlier = values[6];
+                } else {
+                    lwhisker = values[0];
+                    q1 = values[1];
+                    q2 = values[2];
+                    q3 = values[3];
+                    rwhisker = values[4];
+                }
+            } else {
+                values.sort(function (a, b) { return a - b; });
+                q1 = quartile(values, 1);
+                q2 = quartile(values, 2);
+                q3 = quartile(values, 3);
+                iqr = q3 - q1;
+                if (options.get('showOutliers')) {
+                    lwhisker = rwhisker = undefined;
+                    for (i = 0; i < vlen; i++) {
+                        if (lwhisker === undefined && values[i] > q1 - (iqr * options.get('outlierIQR'))) {
+                            lwhisker = values[i];
+                        }
+                        if (values[i] < q3 + (iqr * options.get('outlierIQR'))) {
+                            rwhisker = values[i];
+                        }
+                    }
+                    loutlier = values[0];
+                    routlier = values[vlen - 1];
+                } else {
+                    lwhisker = values[0];
+                    rwhisker = values[vlen - 1];
+                }
+            }
+            this.quartiles = [q1, q2, q3];
+            this.lwhisker = lwhisker;
+            this.rwhisker = rwhisker;
+            this.loutlier = loutlier;
+            this.routlier = routlier;
+
+            unitSize = canvasWidth / (maxValue - minValue + 1);
+            if (options.get('showOutliers')) {
+                canvasLeft = Math.ceil(options.get('spotRadius'));
+                canvasWidth -= 2 * Math.ceil(options.get('spotRadius'));
+                unitSize = canvasWidth / (maxValue - minValue + 1);
+                if (loutlier < lwhisker) {
+                    target.drawCircle((loutlier - minValue) * unitSize + canvasLeft,
+                        canvasHeight / 2,
+                        options.get('spotRadius'),
+                        options.get('outlierLineColor'),
+                        options.get('outlierFillColor')).append();
+                }
+                if (routlier > rwhisker) {
+                    target.drawCircle((routlier - minValue) * unitSize + canvasLeft,
+                        canvasHeight / 2,
+                        options.get('spotRadius'),
+                        options.get('outlierLineColor'),
+                        options.get('outlierFillColor')).append();
+                }
+            }
+
+            // box
+            target.drawRect(
+                Math.round((q1 - minValue) * unitSize + canvasLeft),
+                Math.round(canvasHeight * 0.1),
+                Math.round((q3 - q1) * unitSize),
+                Math.round(canvasHeight * 0.8),
+                options.get('boxLineColor'),
+                options.get('boxFillColor')).append();
+            // left whisker
+            target.drawLine(
+                Math.round((lwhisker - minValue) * unitSize + canvasLeft),
+                Math.round(canvasHeight / 2),
+                Math.round((q1 - minValue) * unitSize + canvasLeft),
+                Math.round(canvasHeight / 2),
+                options.get('lineColor')).append();
+            target.drawLine(
+                Math.round((lwhisker - minValue) * unitSize + canvasLeft),
+                Math.round(canvasHeight / 4),
+                Math.round((lwhisker - minValue) * unitSize + canvasLeft),
+                Math.round(canvasHeight - canvasHeight / 4),
+                options.get('whiskerColor')).append();
+            // right whisker
+            target.drawLine(Math.round((rwhisker - minValue) * unitSize + canvasLeft),
+                Math.round(canvasHeight / 2),
+                Math.round((q3 - minValue) * unitSize + canvasLeft),
+                Math.round(canvasHeight / 2),
+                options.get('lineColor')).append();
+            target.drawLine(
+                Math.round((rwhisker - minValue) * unitSize + canvasLeft),
+                Math.round(canvasHeight / 4),
+                Math.round((rwhisker - minValue) * unitSize + canvasLeft),
+                Math.round(canvasHeight - canvasHeight / 4),
+                options.get('whiskerColor')).append();
+            // median line
+            target.drawLine(
+                Math.round((q2 - minValue) * unitSize + canvasLeft),
+                Math.round(canvasHeight * 0.1),
+                Math.round((q2 - minValue) * unitSize + canvasLeft),
+                Math.round(canvasHeight * 0.9),
+                options.get('medianColor')).append();
+            if (options.get('target')) {
+                size = Math.ceil(options.get('spotRadius'));
+                target.drawLine(
+                    Math.round((options.get('target') - minValue) * unitSize + canvasLeft),
+                    Math.round((canvasHeight / 2) - size),
+                    Math.round((options.get('target') - minValue) * unitSize + canvasLeft),
+                    Math.round((canvasHeight / 2) + size),
+                    options.get('targetColor')).append();
+                target.drawLine(
+                    Math.round((options.get('target') - minValue) * unitSize + canvasLeft - size),
+                    Math.round(canvasHeight / 2),
+                    Math.round((options.get('target') - minValue) * unitSize + canvasLeft + size),
+                    Math.round(canvasHeight / 2),
+                    options.get('targetColor')).append();
+            }
+            target.render();
+        }
+    });
+
+    // Setup a very simple "virtual canvas" to make drawing the few shapes we need easier
+    // This is accessible as $(foo).simpledraw()
+
+    VShape = createClass({
+        init: function (target, id, type, args) {
+            this.target = target;
+            this.id = id;
+            this.type = type;
+            this.args = args;
+        },
+        append: function () {
+            this.target.appendShape(this);
+            return this;
+        }
+    });
+
+    VCanvas_base = createClass({
+        _pxregex: /(\d+)(px)?\s*$/i,
+
+        init: function (width, height, target) {
+            if (!width) {
+                return;
+            }
+            this.width = width;
+            this.height = height;
+            this.target = target;
+            this.lastShapeId = null;
+            if (target[0]) {
+                target = target[0];
+            }
+            $.data(target, '_jqs_vcanvas', this);
+        },
+
+        drawLine: function (x1, y1, x2, y2, lineColor, lineWidth) {
+            return this.drawShape([[x1, y1], [x2, y2]], lineColor, lineWidth);
+        },
+
+        drawShape: function (path, lineColor, fillColor, lineWidth) {
+            return this._genShape('Shape', [path, lineColor, fillColor, lineWidth]);
+        },
+
+        drawCircle: function (x, y, radius, lineColor, fillColor, lineWidth) {
+            return this._genShape('Circle', [x, y, radius, lineColor, fillColor, lineWidth]);
+        },
+
+        drawPieSlice: function (x, y, radius, startAngle, endAngle, lineColor, fillColor) {
+            return this._genShape('PieSlice', [x, y, radius, startAngle, endAngle, lineColor, fillColor]);
+        },
+
+        drawRect: function (x, y, width, height, lineColor, fillColor) {
+            return this._genShape('Rect', [x, y, width, height, lineColor, fillColor]);
+        },
+
+        getElement: function () {
+            return this.canvas;
+        },
+
+        /**
+         * Return the most recently inserted shape id
+         */
+        getLastShapeId: function () {
+            return this.lastShapeId;
+        },
+
+        /**
+         * Clear and reset the canvas
+         */
+        reset: function () {
+            alert('reset not implemented');
+        },
+
+        _insert: function (el, target) {
+            $(target).html(el);
+        },
+
+        /**
+         * Calculate the pixel dimensions of the canvas
+         */
+        _calculatePixelDims: function (width, height, canvas) {
+            // XXX This should probably be a configurable option
+            var match;
+            match = this._pxregex.exec(height);
+            if (match) {
+                this.pixelHeight = match[1];
+            } else {
+                this.pixelHeight = $(canvas).height();
+            }
+            match = this._pxregex.exec(width);
+            if (match) {
+                this.pixelWidth = match[1];
+            } else {
+                this.pixelWidth = $(canvas).width();
+            }
+        },
+
+        /**
+         * Generate a shape object and id for later rendering
+         */
+        _genShape: function (shapetype, shapeargs) {
+            var id = shapeCount++;
+            shapeargs.unshift(id);
+            return new VShape(this, id, shapetype, shapeargs);
+        },
+
+        /**
+         * Add a shape to the end of the render queue
+         */
+        appendShape: function (shape) {
+            alert('appendShape not implemented');
+        },
+
+        /**
+         * Replace one shape with another
+         */
+        replaceWithShape: function (shapeid, shape) {
+            alert('replaceWithShape not implemented');
+        },
+
+        /**
+         * Insert one shape after another in the render queue
+         */
+        insertAfterShape: function (shapeid, shape) {
+            alert('insertAfterShape not implemented');
+        },
+
+        /**
+         * Remove a shape from the queue
+         */
+        removeShapeId: function (shapeid) {
+            alert('removeShapeId not implemented');
+        },
+
+        /**
+         * Find a shape at the specified x/y co-ordinates
+         */
+        getShapeAt: function (el, x, y) {
+            alert('getShapeAt not implemented');
+        },
+
+        /**
+         * Render all queued shapes onto the canvas
+         */
+        render: function () {
+            alert('render not implemented');
+        }
+    });
+
+    VCanvas_canvas = createClass(VCanvas_base, {
+        init: function (width, height, target, interact) {
+            VCanvas_canvas._super.init.call(this, width, height, target);
+            this.canvas = document.createElement('canvas');
+            if (target[0]) {
+                target = target[0];
+            }
+            $.data(target, '_jqs_vcanvas', this);
+            $(this.canvas).css({ display: 'inline-block', width: width, height: height, verticalAlign: 'top' });
+            this._insert(this.canvas, target);
+            this._calculatePixelDims(width, height, this.canvas);
+            this.canvas.width = this.pixelWidth;
+            this.canvas.height = this.pixelHeight;
+            this.interact = interact;
+            this.shapes = {};
+            this.shapeseq = [];
+            this.currentTargetShapeId = undefined;
+            $(this.canvas).css({width: this.pixelWidth, height: this.pixelHeight});
+        },
+
+        _getContext: function (lineColor, fillColor, lineWidth) {
+            var context = this.canvas.getContext('2d');
+            if (lineColor !== undefined) {
+                context.strokeStyle = lineColor;
+            }
+            context.lineWidth = lineWidth === undefined ? 1 : lineWidth;
+            if (fillColor !== undefined) {
+                context.fillStyle = fillColor;
+            }
+            return context;
+        },
+
+        reset: function () {
+            var context = this._getContext();
+            context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
+            this.shapes = {};
+            this.shapeseq = [];
+            this.currentTargetShapeId = undefined;
+        },
+
+        _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
+            var context = this._getContext(lineColor, fillColor, lineWidth),
+                i, plen;
+            context.beginPath();
+            context.moveTo(path[0][0] + 0.5, path[0][1] + 0.5);
+            for (i = 1, plen = path.length; i < plen; i++) {
+                context.lineTo(path[i][0] + 0.5, path[i][1] + 0.5); // the 0.5 offset gives us crisp pixel-width lines
+            }
+            if (lineColor !== undefined) {
+                context.stroke();
+            }
+            if (fillColor !== undefined) {
+                context.fill();
+            }
+            if (this.targetX !== undefined && this.targetY !== undefined &&
+                context.isPointInPath(this.targetX, this.targetY)) {
+                this.currentTargetShapeId = shapeid;
+            }
+        },
+
+        _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
+            var context = this._getContext(lineColor, fillColor, lineWidth);
+            context.beginPath();
+            context.arc(x, y, radius, 0, 2 * Math.PI, false);
+            if (this.targetX !== undefined && this.targetY !== undefined &&
+                context.isPointInPath(this.targetX, this.targetY)) {
+                this.currentTargetShapeId = shapeid;
+            }
+            if (lineColor !== undefined) {
+                context.stroke();
+            }
+            if (fillColor !== undefined) {
+                context.fill();
+            }
+        },
+
+        _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
+            var context = this._getContext(lineColor, fillColor);
+            context.beginPath();
+            context.moveTo(x, y);
+            context.arc(x, y, radius, startAngle, endAngle, false);
+            context.lineTo(x, y);
+            context.closePath();
+            if (lineColor !== undefined) {
+                context.stroke();
+            }
+            if (fillColor) {
+                context.fill();
+            }
+            if (this.targetX !== undefined && this.targetY !== undefined &&
+                context.isPointInPath(this.targetX, this.targetY)) {
+                this.currentTargetShapeId = shapeid;
+            }
+        },
+
+        _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
+            return this._drawShape(shapeid, [[x, y], [x + width, y], [x + width, y + height], [x, y + height], [x, y]], lineColor, fillColor);
+        },
+
+        appendShape: function (shape) {
+            this.shapes[shape.id] = shape;
+            this.shapeseq.push(shape.id);
+            this.lastShapeId = shape.id;
+            return shape.id;
+        },
+
+        replaceWithShape: function (shapeid, shape) {
+            var shapeseq = this.shapeseq,
+                i;
+            this.shapes[shape.id] = shape;
+            for (i = shapeseq.length; i--;) {
+                if (shapeseq[i] == shapeid) {
+                    shapeseq[i] = shape.id;
+                }
+            }
+            delete this.shapes[shapeid];
+        },
+
+        replaceWithShapes: function (shapeids, shapes) {
+            var shapeseq = this.shapeseq,
+                shapemap = {},
+                sid, i, first;
+
+            for (i = shapeids.length; i--;) {
+                shapemap[shapeids[i]] = true;
+            }
+            for (i = shapeseq.length; i--;) {
+                sid = shapeseq[i];
+                if (shapemap[sid]) {
+                    shapeseq.splice(i, 1);
+                    delete this.shapes[sid];
+                    first = i;
+                }
+            }
+            for (i = shapes.length; i--;) {
+                shapeseq.splice(first, 0, shapes[i].id);
+                this.shapes[shapes[i].id] = shapes[i];
+            }
+
+        },
+
+        insertAfterShape: function (shapeid, shape) {
+            var shapeseq = this.shapeseq,
+                i;
+            for (i = shapeseq.length; i--;) {
+                if (shapeseq[i] === shapeid) {
+                    shapeseq.splice(i + 1, 0, shape.id);
+                    this.shapes[shape.id] = shape;
+                    return;
+                }
+            }
+        },
+
+        removeShapeId: function (shapeid) {
+            var shapeseq = this.shapeseq,
+                i;
+            for (i = shapeseq.length; i--;) {
+                if (shapeseq[i] === shapeid) {
+                    shapeseq.splice(i, 1);
+                    break;
+                }
+            }
+            delete this.shapes[shapeid];
+        },
+
+        getShapeAt: function (el, x, y) {
+            this.targetX = x;
+            this.targetY = y;
+            this.render();
+            return this.currentTargetShapeId;
+        },
+
+        render: function () {
+            var shapeseq = this.shapeseq,
+                shapes = this.shapes,
+                shapeCount = shapeseq.length,
+                context = this._getContext(),
+                shapeid, shape, i;
+            context.clearRect(0, 0, this.pixelWidth, this.pixelHeight);
+            for (i = 0; i < shapeCount; i++) {
+                shapeid = shapeseq[i];
+                shape = shapes[shapeid];
+                this['_draw' + shape.type].apply(this, shape.args);
+            }
+            if (!this.interact) {
+                // not interactive so no need to keep the shapes array
+                this.shapes = {};
+                this.shapeseq = [];
+            }
+        }
+
+    });
+
+    VCanvas_vml = createClass(VCanvas_base, {
+        init: function (width, height, target) {
+            var groupel;
+            VCanvas_vml._super.init.call(this, width, height, target);
+            if (target[0]) {
+                target = target[0];
+            }
+            $.data(target, '_jqs_vcanvas', this);
+            this.canvas = document.createElement('span');
+            $(this.canvas).css({ display: 'inline-block', position: 'relative', overflow: 'hidden', width: width, height: height, margin: '0px', padding: '0px', verticalAlign: 'top'});
+            this._insert(this.canvas, target);
+            this._calculatePixelDims(width, height, this.canvas);
+            this.canvas.width = this.pixelWidth;
+            this.canvas.height = this.pixelHeight;
+            groupel = '<v:group coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '"' +
+                    ' style="position:absolute;top:0;left:0;width:' + this.pixelWidth + 'px;height=' + this.pixelHeight + 'px;"></v:group>';
+            this.canvas.insertAdjacentHTML('beforeEnd', groupel);
+            this.group = $(this.canvas).children()[0];
+            this.rendered = false;
+            this.prerender = '';
+        },
+
+        _drawShape: function (shapeid, path, lineColor, fillColor, lineWidth) {
+            var vpath = [],
+                initial, stroke, fill, closed, vel, plen, i;
+            for (i = 0, plen = path.length; i < plen; i++) {
+                vpath[i] = '' + (path[i][0]) + ',' + (path[i][1]);
+            }
+            initial = vpath.splice(0, 1);
+            lineWidth = lineWidth === undefined ? 1 : lineWidth;
+            stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';
+            fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
+            closed = vpath[0] === vpath[vpath.length - 1] ? 'x ' : '';
+            vel = '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '" ' +
+                 ' id="jqsshape' + shapeid + '" ' +
+                 stroke +
+                 fill +
+                ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' +
+                ' path="m ' + initial + ' l ' + vpath.join(', ') + ' ' + closed + 'e">' +
+                ' </v:shape>';
+            return vel;
+        },
+
+        _drawCircle: function (shapeid, x, y, radius, lineColor, fillColor, lineWidth) {
+            var stroke, fill, vel;
+            x -= radius;
+            y -= radius;
+            stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="' + lineWidth + 'px" strokeColor="' + lineColor + '" ';
+            fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
+            vel = '<v:oval ' +
+                 ' id="jqsshape' + shapeid + '" ' +
+                stroke +
+                fill +
+                ' style="position:absolute;top:' + y + 'px; left:' + x + 'px; width:' + (radius * 2) + 'px; height:' + (radius * 2) + 'px"></v:oval>';
+            return vel;
+
+        },
+
+        _drawPieSlice: function (shapeid, x, y, radius, startAngle, endAngle, lineColor, fillColor) {
+            var vpath, startx, starty, endx, endy, stroke, fill, vel;
+            if (startAngle === endAngle) {
+                return '';  // VML seems to have problem when start angle equals end angle.
+            }
+            if ((endAngle - startAngle) === (2 * Math.PI)) {
+                startAngle = 0.0;  // VML seems to have a problem when drawing a full circle that doesn't start 0
+                endAngle = (2 * Math.PI);
+            }
+
+            startx = x + Math.round(Math.cos(startAngle) * radius);
+            starty = y + Math.round(Math.sin(startAngle) * radius);
+            endx = x + Math.round(Math.cos(endAngle) * radius);
+            endy = y + Math.round(Math.sin(endAngle) * radius);
+
+            if (startx === endx && starty === endy) {
+                if ((endAngle - startAngle) < Math.PI) {
+                    // Prevent very small slices from being mistaken as a whole pie
+                    return '';
+                }
+                // essentially going to be the entire circle, so ignore startAngle
+                startx = endx = x + radius;
+                starty = endy = y;
+            }
+
+            if (startx === endx && starty === endy && (endAngle - startAngle) < Math.PI) {
+                return '';
+            }
+
+            vpath = [x - radius, y - radius, x + radius, y + radius, startx, starty, endx, endy];
+            stroke = lineColor === undefined ? ' stroked="false" ' : ' strokeWeight="1px" strokeColor="' + lineColor + '" ';
+            fill = fillColor === undefined ? ' filled="false"' : ' fillColor="' + fillColor + '" filled="true" ';
+            vel = '<v:shape coordorigin="0 0" coordsize="' + this.pixelWidth + ' ' + this.pixelHeight + '" ' +
+                 ' id="jqsshape' + shapeid + '" ' +
+                 stroke +
+                 fill +
+                ' style="position:absolute;left:0px;top:0px;height:' + this.pixelHeight + 'px;width:' + this.pixelWidth + 'px;padding:0px;margin:0px;" ' +
+                ' path="m ' + x + ',' + y + ' wa ' + vpath.join(', ') + ' x e">' +
+                ' </v:shape>';
+            return vel;
+        },
+
+        _drawRect: function (shapeid, x, y, width, height, lineColor, fillColor) {
+            return this._drawShape(shapeid, [[x, y], [x, y + height], [x + width, y + height], [x + width, y], [x, y]], lineColor, fillColor);
+        },
+
+        reset: function () {
+            this.group.innerHTML = '';
+        },
+
+        appendShape: function (shape) {
+            var vel = this['_draw' + shape.type].apply(this, shape.args);
+            if (this.rendered) {
+                this.group.insertAdjacentHTML('beforeEnd', vel);
+            } else {
+                this.prerender += vel;
+            }
+            this.lastShapeId = shape.id;
+            return shape.id;
+        },
+
+        replaceWithShape: function (shapeid, shape) {
+            var existing = $('#jqsshape' + shapeid),
+                vel = this['_draw' + shape.type].apply(this, shape.args);
+            existing[0].outerHTML = vel;
+        },
+
+        replaceWithShapes: function (shapeids, shapes) {
+            // replace the first shapeid with all the new shapes then toast the remaining old shapes
+            var existing = $('#jqsshape' + shapeids[0]),
+                replace = '',
+                slen = shapes.length,
+                i;
+            for (i = 0; i < slen; i++) {
+                replace += this['_draw' + shapes[i].type].apply(this, shapes[i].args);
+            }
+            existing[0].outerHTML = replace;
+            for (i = 1; i < shapeids.length; i++) {
+                $('#jqsshape' + shapeids[i]).remove();
+            }
+        },
+
+        insertAfterShape: function (shapeid, shape) {
+            var existing = $('#jqsshape' + shapeid),
+                 vel = this['_draw' + shape.type].apply(this, shape.args);
+            existing[0].insertAdjacentHTML('afterEnd', vel);
+        },
+
+        removeShapeId: function (shapeid) {
+            var existing = $('#jqsshape' + shapeid);
+            this.group.removeChild(existing[0]);
+        },
+
+        getShapeAt: function (el, x, y) {
+            var shapeid = el.id.substr(8);
+            return shapeid;
+        },
+
+        render: function () {
+            if (!this.rendered) {
+                // batch the intial render into a single repaint
+                this.group.innerHTML = this.prerender;
+                this.rendered = true;
+            }
+        }
+    });
+
+}))}(document, Math));
diff --git a/scenarios/WeissHapticSensorsUnitTest/startGui.sh b/scenarios/WeissHapticSensorsUnitTest/startGui.sh
new file mode 100755
index 0000000000000000000000000000000000000000..e2f4b07c6fc3db3a101c1ccfee1d6576e35b8b69
--- /dev/null
+++ b/scenarios/WeissHapticSensorsUnitTest/startGui.sh
@@ -0,0 +1,8 @@
+export CORE_PATH=../../../Core
+export GUI_PATH=../../../Gui
+
+export SCRIPT_PATH=$CORE_PATH/build/bin
+export GUI_BIN_PATH=$GUI_PATH/build/bin
+
+# Gui
+$SCRIPT_PATH/startApplication.sh $GUI_BIN_PATH/ArmarXGuiRun &
diff --git a/scenarios/WeissHapticSensorsUnitTest/tactile.html b/scenarios/WeissHapticSensorsUnitTest/tactile.html
new file mode 100755
index 0000000000000000000000000000000000000000..a0a5d1a8775ddc377e8c0f07f52f3aa7f0dcf9cb
--- /dev/null
+++ b/scenarios/WeissHapticSensorsUnitTest/tactile.html
@@ -0,0 +1,189 @@
+<!DOCTYPE html>
+<html xmlns="http://www.w3.org/1999/xhtml" dir="ltr" lang="de-DE">
+<head>
+<meta charset="utf-8">    
+<title>Tactile Sensor Data Explorer</title>
+<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
+<script type="text/javascript" src="jquery.sparkline.js"></script>
+<script>
+sensors = [];
+</script>
+<script src="ttyACM0.js"></script>
+<script src="ttyACM1.js"></script>
+<style type="text/css">
+#out {
+	font-size: 10px;
+	font-family: Verdana, sans;
+}
+div.sensor {
+	float: left;
+	padding: 10px;
+}
+body {
+	font-family: Verdana, sans;
+}
+</style>
+</head>
+<body>
+<div id="test"></div>
+<div id="buttons"></div>
+<div id="out"></div>
+<div id="graphs" style="clear:both; position:relative;"></div>
+<script>
+
+var getColor = function(x) {
+	var colors = ['000000', '0000ff', '00ffff', '00ff00', 'ffff00', 'ff0000'];
+	var f = x * (colors.length - 1);
+	if(f < 0) return '#' + colors[0];
+	if(f > colors.length - 1) return '#' + colors[colors.length - 1];
+	var i = Math.floor(f);
+	f = f - i;
+	var r1 = parseInt(colors[i].substr(0, 2), 16);
+	var g1 = parseInt(colors[i].substr(2, 2), 16);
+	var b1 = parseInt(colors[i].substr(4, 2), 16);
+	var r2 = parseInt(colors[i + 1].substr(0, 2), 16);
+	var g2 = parseInt(colors[i + 1].substr(2, 2), 16);
+	var b2 = parseInt(colors[i + 1].substr(4, 2), 16);
+	var r = Math.round(r1 * (1 - f) + r2 * f);
+	var g = Math.round(g1 * (1 - f) + g2 * f);
+	var b = Math.round(b1 * (1 - f) + b2 * f);
+	return '#' + ("000000" + (r * 256 * 256 + g * 256 + b).toString(16)).slice(-6);
+};
+/*for(var n = 0; n <= 1; n+=0.01) {
+	$("#test").append(n.toFixed(2) + " " + getColor(n)+"<br />");
+}*/
+
+var binarySearch = function(sensorData, timestamp)
+{
+	var low = 0, high = sensorData.length - 1, i, comparison;
+	while (low <= high) {
+		i = Math.floor((low + high) / 2);
+		if (sensorData[i][0] < timestamp) { low = i + 1; continue; };
+		if (sensorData[i][0] > timestamp) { high = i - 1; continue; };
+		return i;
+	}
+	return ~i;
+};
+
+var displayData = function(data) {
+	var s = 40;
+	var str = '<div style="position: relative; width:' + (data[0].length * s) + 'px; height:' + (data.length * s) + 'px;">';
+	for(var y = 0; y < data.length; y++) {
+		var row = data[y];
+		
+		for(var x = 0; x < row.length; x++) {
+			str += '<div style="top:' + (y * s) + 'px; left:' + (x * s) + 'px; width: '+s+'px; height: '+s+'px; position: absolute; text-align:center; line-height:'+s+'px; background:' + getColor(row[x] / 4096) + '; color: #777;">' + row[x] + '</div>';
+		}
+	}
+	str += '</div>';
+	return str;
+};
+
+var formatDate = function(date) {
+	return (date.getYear() + 1900) + "-" + ("00" + (date.getMonth() + 1)).slice(-2) + "-" + ("00" + date.getDate()).slice(-2) + " " + ("00" + date.getHours()).slice(-2) + ":" + ("00" + date.getMinutes()).slice(-2) + ":" + ("00" + date.getSeconds()).slice(-2) + "." + ("000" + date.getMilliseconds()).slice(-3);
+};
+var update = function(timestamp) {
+	for(var n = 0; n < sensors.length; n++) {
+		var html = "";
+		var sensor = sensors[n];
+		var pos = binarySearch(sensor.data, timestamp);
+		if (pos < 0) pos = ~pos;
+		sensor.pos = pos;
+		//html += '<div class="sensor">' + sensor.device + ", tag=" + sensor.tag;
+		html += sensor.device + ", tag=" + sensor.tag;
+		if (sensor == primarySensor) {
+			html += " (Primary)";
+		}
+		html += "<br />Frame " + pos + "/" + (sensor.data.length - 1) + " t=" + formatDate(new Date(sensor.data[pos][0]/1000)) + displayData(sensor.data[pos][1]);// + '</div>';
+		//html += '<div class="sensor">' + sensor.device + ", tag=" + sensor.tag + "<br />Frame " + pos + "/" + (sensor.data.length - 1) + " t= " + (new Date(sensor.data[pos][0]/1000)) + displayData(sensor.data[pos][1]) + '</div>';
+		sensor.div.html(html);
+	}
+	updatePositionDiv();
+}
+for (var n = 0; n < sensors.length; n++) {
+	var sensor = sensors[n];
+	sensor.pos = 0;
+	sensor.div = $('<div class="sensor"></div>');
+	$("#out").append(sensor.div);
+	sensor.div.data('sensor', sensor);
+	sensor.div.click(function() { primarySensor = $(this).data('sensor'); update(primarySensor.data[primarySensor.pos][0]); });
+}
+var primarySensor = sensors[0];
+
+var getPosByTimestamp = function(timestamp) { var pos = binarySearch(primarySensor.data, timestamp); if (pos < 0) pos = ~pos; return pos; };
+var selectFrameByTimestamp = function(timestamp) { primarySensor.pos = getPosByTimestamp(timestamp); changeFrame(0); };
+var changeFrame = function(delta) { primarySensor.pos = Math.max(0, Math.min(primarySensor.data.length - 1, primarySensor.pos + delta)); update(primarySensor.data[primarySensor.pos][0]); };
+var changeFrameGen = function(delta) { return function() { changeFrame(delta) }; };
+var buttonGen = function(text, delta) { return $("<button>"+text+"</button>").click(changeFrameGen(delta)); };
+$("#buttons").append("navigate: ").append(buttonGen("-100", -100)).append(buttonGen("-10", -10)).append(buttonGen("-1", -1)).append(buttonGen("+1", +1)).append(buttonGen("+10", +10)).append(buttonGen("+100", +100));
+
+
+$(document).keydown(function(e) {
+    switch(e.which) {
+        case 37: // left
+			changeFrame(e.ctrlKey ? -10 : -1);
+			break;
+
+        case 38: // up
+			break;
+
+        case 39: // right
+			changeFrame(e.ctrlKey ? 10 : 1);
+			break;
+
+        case 40: // down
+			break;
+
+        default: return; // exit this handler for other keys
+    }
+    e.preventDefault(); // prevent the default action (scroll / move caret)
+});
+
+var matrixMax = function(data) {
+	var max = data[0][0];
+	for(var y = 0; y < data.length; y++) {
+		var row = data[y];
+		for(var x = 0; x < row.length; x++) {
+			max = Math.max(max, row[x]);
+		}
+	}
+	return max;
+}
+
+//var $positionDiv = $('<div style="width:500px; height:20px;"></div>');
+var $positionDivInner = $('<div style="border-right: 1px solid #00A000; height: ' + (sensors.length * 50) + 'px; position: absolute; left: 0px; top: 0px; z-index:-10;"></div>');
+var updatePositionDiv = function() {
+	$positionDivInner.css('width', (primarySensor.pos / primarySensor.data.length * 500) + "px");
+};
+$("#graphs").append($positionDivInner);
+
+for(var n = 0; n < sensors.length; n++) {
+	var sensor = sensors[n];
+	var $div = $('<div style="line-height: 50px;"></div>');
+	$("#graphs").append($div);
+	var xvalues = [];
+	var yvalues = [];
+	for(var i = 0; i < sensor.data.length; i++) {
+		xvalues.push(sensor.data[i][0]);
+		yvalues.push(matrixMax(sensor.data[i][1]));
+	}
+	
+	$div.sparkline(yvalues, {"width":"500px", "height":"50px", "xvalues":xvalues, "spotColor":false, "minSpotColor":false, "maxSpotColor":false, 'fillColor': 'rgba(0,0,255,0.2)'});
+	$div.data('sensor', sensor);
+	$div.bind('sparklineClick', function(ev) {
+		var sparkline = ev.sparklines[0];
+		var sensor = $(this).data('sensor');
+        var region = sparkline.getCurrentRegionFields();
+		//alert("Clicked on x="+region.x+" y="+region.y);
+		primarySensor = sensor;
+		selectFrameByTimestamp(region.x);
+	});
+	$div.append(sensor.tag);
+}
+
+
+update(primarySensor.data[primarySensor.pos][0]);
+
+</script>
+</body>
+</html>
\ No newline at end of file
diff --git a/source/RobotAPI/CMakeLists.txt b/source/RobotAPI/CMakeLists.txt
index 59796017c05137bcf7667a83e6cd36006671660c..602631618f7001892a3d75cae0bf1cf744fd6373 100644
--- a/source/RobotAPI/CMakeLists.txt
+++ b/source/RobotAPI/CMakeLists.txt
@@ -2,6 +2,7 @@ add_subdirectory(core)
 add_subdirectory(motioncontrol)
 add_subdirectory(applications)
 add_subdirectory(units)
-#add_subdirectory(armarx-objects)
+add_subdirectory(armarx-objects)
 add_subdirectory(statecharts)
+add_subdirectory(drivers)
 
diff --git a/source/RobotAPI/applications/CMakeLists.txt b/source/RobotAPI/applications/CMakeLists.txt
index 828e1afa50b2f9505dfdb83a9f97ad1c5d06503d..712ae22258ea34af1d8e82845c7d763fb61de97e 100644
--- a/source/RobotAPI/applications/CMakeLists.txt
+++ b/source/RobotAPI/applications/CMakeLists.txt
@@ -5,3 +5,7 @@ add_subdirectory(MotionControlTest)
 add_subdirectory(TCPControlUnit)
 add_subdirectory(MovePlatform)
 add_subdirectory(MovePlatformToLandmark)
+
+add_subdirectory(WeissHapticSensorsUnit)
+add_subdirectory(WeissHapticSensor)
+add_subdirectory(HapticObserver)
diff --git a/source/RobotAPI/applications/ForceTorqueObserver/ForceTorqueObserverApp.h b/source/RobotAPI/applications/ForceTorqueObserver/ForceTorqueObserverApp.h
index cab40777f7e5607e02dd0df0e5974c4f44d8af5f..6822374bf4916a29f5c3e304731053c680c22ad0 100644
--- a/source/RobotAPI/applications/ForceTorqueObserver/ForceTorqueObserverApp.h
+++ b/source/RobotAPI/applications/ForceTorqueObserver/ForceTorqueObserverApp.h
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/HapticObserver/CMakeLists.txt b/source/RobotAPI/applications/HapticObserver/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..9a596db9e72df4d6193ead5c8ca2934320ccc1b4
--- /dev/null
+++ b/source/RobotAPI/applications/HapticObserver/CMakeLists.txt
@@ -0,0 +1,15 @@
+armarx_component_set_name("HapticObserverApp")
+
+find_package(Eigen3 QUIET)
+armarx_build_if(Eigen3_FOUND "Eigen3 not available")
+
+if (Eigen3_FOUND)
+    include_directories(
+        ${Eigen3_INCLUDE_DIR})
+endif()
+
+set(COMPONENT_LIBS ArmarXInterfaces ArmarXCore ArmarXCoreObservers ArmarXCoreEigen3Variants WeissHapticSensorListener RobotAPIUnits)
+
+set(EXE_SOURCE main.cpp HapticObserverApp.h)
+
+armarx_add_component_executable("${EXE_SOURCE}")
diff --git a/source/RobotAPI/applications/HapticObserver/HapticObserverApp.h b/source/RobotAPI/applications/HapticObserver/HapticObserverApp.h
new file mode 100644
index 0000000000000000000000000000000000000000..672914e186d68059de11a0b7621902b4d3a0ef5c
--- /dev/null
+++ b/source/RobotAPI/applications/HapticObserver/HapticObserverApp.h
@@ -0,0 +1,52 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI::application::HapticObserver
+ * @author     Simon Ottenhaus ( simon dot ottenhaus at kit dot edu )
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#ifndef _ARMARX_APPLICATION_RobotAPI_HapticObserver_H
+#define _ARMARX_APPLICATION_RobotAPI_HapticObserver_H
+
+#include <Core/core/application/Application.h>
+#include <RobotAPI/units/HapticObserver.h>
+
+namespace armarx
+{
+    /**
+     * @class HapticObserverApp
+     * @brief A brief description
+     *
+     * Detailed Description
+     */
+    class HapticObserverApp :
+        virtual public armarx::Application
+    {
+        /**
+         * @see armarx::Application::setup()
+         */
+        void setup(const ManagedIceObjectRegistryInterfacePtr& registry,
+                   Ice::PropertiesPtr properties)
+        {
+            registry->addObject( Component::create<HapticObserver>(properties) );
+        }
+    };
+}
+
+#endif
diff --git a/source/RobotAPI/applications/HapticObserver/main.cpp b/source/RobotAPI/applications/HapticObserver/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..e0b4270b484237e71355600dde6e595589435535
--- /dev/null
+++ b/source/RobotAPI/applications/HapticObserver/main.cpp
@@ -0,0 +1,33 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI::application::HapticObserver
+ * @author     Simon Ottenhaus ( simon dot ottenhaus at kit dot edu )
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#include "HapticObserverApp.h"
+#include <Core/core/logging/Logging.h>
+
+int main(int argc, char* argv[])
+{
+    armarx::ApplicationPtr app = armarx::Application::createInstance < armarx::HapticObserverApp > ();
+    app->setName("HapticObserver");
+
+    return app->main(argc, argv);
+}
diff --git a/source/RobotAPI/applications/HeadIKUnit/HeadIKUnitApp.h b/source/RobotAPI/applications/HeadIKUnit/HeadIKUnitApp.h
index e48a5ac0ee71e66ee2aaf882ec64bee5640342d5..697a651cb2ef46630ecaca754241fbcd86a75806 100644
--- a/source/RobotAPI/applications/HeadIKUnit/HeadIKUnitApp.h
+++ b/source/RobotAPI/applications/HeadIKUnit/HeadIKUnitApp.h
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/HeadIKUnit/main.cpp b/source/RobotAPI/applications/HeadIKUnit/main.cpp
index c6ae3869fa082742259527466c6c4f18c16ab4ec..f88f74f1241453d86785372e181101ebe2567e6e 100644
--- a/source/RobotAPI/applications/HeadIKUnit/main.cpp
+++ b/source/RobotAPI/applications/HeadIKUnit/main.cpp
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/MotionControl/MotionControlApp.h b/source/RobotAPI/applications/MotionControl/MotionControlApp.h
index 62045985b2efc3df6a18e8216bfa0e2a2139d9eb..72f1483b622bbf0d99f6975e3bba804cc073f8a5 100644
--- a/source/RobotAPI/applications/MotionControl/MotionControlApp.h
+++ b/source/RobotAPI/applications/MotionControl/MotionControlApp.h
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/MotionControl/main.cpp b/source/RobotAPI/applications/MotionControl/main.cpp
index 4e68781696f2a7efbe8d1c3780281625df93d2af..b12d363adb53ba4b5c4e4eb835e7e93f49d35e13 100644
--- a/source/RobotAPI/applications/MotionControl/main.cpp
+++ b/source/RobotAPI/applications/MotionControl/main.cpp
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/MotionControlTest/MotionControlTestApp.h b/source/RobotAPI/applications/MotionControlTest/MotionControlTestApp.h
index b6f7a128c0e1f5b6e423905c2cbd55dad7b46756..f53f50ca6e02e19899f193b0ce7728cbd63acd7f 100644
--- a/source/RobotAPI/applications/MotionControlTest/MotionControlTestApp.h
+++ b/source/RobotAPI/applications/MotionControlTest/MotionControlTestApp.h
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/MotionControlTest/main.cpp b/source/RobotAPI/applications/MotionControlTest/main.cpp
index 630ae96d899591c77ae2eb9326e2cbb21f7e5e1a..dd6c501553c97e8f7c8e41d6655dc79fcaed61cf 100644
--- a/source/RobotAPI/applications/MotionControlTest/main.cpp
+++ b/source/RobotAPI/applications/MotionControlTest/main.cpp
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/MovePlatform/MovePlatformApp.h b/source/RobotAPI/applications/MovePlatform/MovePlatformApp.h
index a2ab3498da2695b08b25d70501b7f48308bafbd4..ac02948a56f3242ee93e338753a47ee852b2eaea 100644
--- a/source/RobotAPI/applications/MovePlatform/MovePlatformApp.h
+++ b/source/RobotAPI/applications/MovePlatform/MovePlatformApp.h
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/MovePlatform/main.cpp b/source/RobotAPI/applications/MovePlatform/main.cpp
index 435aeb57b48acaf36c6d8aa8362348f7aaa36fe8..cb829dda7f8d3c05931738959dc5c6c4f960db13 100644
--- a/source/RobotAPI/applications/MovePlatform/main.cpp
+++ b/source/RobotAPI/applications/MovePlatform/main.cpp
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/MovePlatformToLandmark/MovePlatformToLandmarkApp.h b/source/RobotAPI/applications/MovePlatformToLandmark/MovePlatformToLandmarkApp.h
index c1b3bb43de2d22fa9e3ef35a3365e3e191e2e730..67a3bbaf475768b7a14d6d67b030f2cd28c0e6f8 100644
--- a/source/RobotAPI/applications/MovePlatformToLandmark/MovePlatformToLandmarkApp.h
+++ b/source/RobotAPI/applications/MovePlatformToLandmark/MovePlatformToLandmarkApp.h
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/MovePlatformToLandmark/main.cpp b/source/RobotAPI/applications/MovePlatformToLandmark/main.cpp
index a73e3d2957973db8f59d620089a7b14cec03f6c6..bdab85115fa1faf64133542a921b418bb938fcf5 100644
--- a/source/RobotAPI/applications/MovePlatformToLandmark/main.cpp
+++ b/source/RobotAPI/applications/MovePlatformToLandmark/main.cpp
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/TCPControlUnit/TCPControlUnitApp.h b/source/RobotAPI/applications/TCPControlUnit/TCPControlUnitApp.h
index 4ab7d2dee5cd1843c729a4ea0f3ab2571b6cf801..b0efe903c989a118b0975c718627861341a4d0b6 100644
--- a/source/RobotAPI/applications/TCPControlUnit/TCPControlUnitApp.h
+++ b/source/RobotAPI/applications/TCPControlUnit/TCPControlUnitApp.h
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/TCPControlUnit/main.cpp b/source/RobotAPI/applications/TCPControlUnit/main.cpp
index 07873e9ab1c32dad9e3057c79d25ef2a5a59da3c..ca23f217c9b253ded6218ca10954c78e8f2c0edb 100644
--- a/source/RobotAPI/applications/TCPControlUnit/main.cpp
+++ b/source/RobotAPI/applications/TCPControlUnit/main.cpp
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/applications/WeissHapticSensor/CMakeLists.txt b/source/RobotAPI/applications/WeissHapticSensor/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..378475a1c7b43d082822a9fc398709dfdaff1482
--- /dev/null
+++ b/source/RobotAPI/applications/WeissHapticSensor/CMakeLists.txt
@@ -0,0 +1,15 @@
+armarx_component_set_name("WeissHapticSensorApp")
+
+find_package(Eigen3 QUIET)
+armarx_build_if(Eigen3_FOUND "Eigen3 not available")
+
+if (Eigen3_FOUND)
+    include_directories(
+        ${Eigen3_INCLUDE_DIR})
+endif()
+
+set(COMPONENT_LIBS ArmarXInterfaces ArmarXCore ArmarXCoreObservers ArmarXCoreEigen3Variants WeissHapticSensorListener RobotAPIUnits RobotAPIInterfaces)
+
+set(EXE_SOURCE main.cpp WeissHapticSensorApp.h)
+
+armarx_add_component_executable("${EXE_SOURCE}")
diff --git a/source/RobotAPI/applications/WeissHapticSensor/WeissHapticSensorApp.h b/source/RobotAPI/applications/WeissHapticSensor/WeissHapticSensorApp.h
new file mode 100644
index 0000000000000000000000000000000000000000..c5812ade6b568e8aed6571b785ea4f4d58e303f5
--- /dev/null
+++ b/source/RobotAPI/applications/WeissHapticSensor/WeissHapticSensorApp.h
@@ -0,0 +1,56 @@
+/**
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI::application::WeissHapticSensor
+ * @author     Simon Ottenhaus ( simon dot ottenhaus at kit dot edu )
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#ifndef _ARMARX_APPLICATION_RobotAPI_WeissHapticSensor_H
+#define _ARMARX_APPLICATION_RobotAPI_WeissHapticSensor_H
+
+
+#include <RobotAPI/armarx-objects/WeissHapticSensorListener/WeissHapticSensorListener.h>
+
+#include <Core/core/application/Application.h>
+#include <RobotAPI/units/HapticObserver.h>
+
+namespace armarx
+{
+    /**
+     * @class WeissHapticSensorApp
+     * @brief A brief description
+     *
+     * Detailed Description
+     */
+    class WeissHapticSensorApp :
+        virtual public armarx::Application
+    {
+        /**
+         * @see armarx::Application::setup()
+         */
+        void setup(const ManagedIceObjectRegistryInterfacePtr& registry,
+                   Ice::PropertiesPtr properties)
+        {
+            registry->addObject( Component::create<WeissHapticSensorListener>(properties) );
+            registry->addObject( Component::create<HapticObserver>(properties) );
+        }
+    };
+}
+
+#endif
diff --git a/source/RobotAPI/applications/WeissHapticSensor/main.cpp b/source/RobotAPI/applications/WeissHapticSensor/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..edff60ba31440bbdcb1e8908eacfd3ebd57ac4fb
--- /dev/null
+++ b/source/RobotAPI/applications/WeissHapticSensor/main.cpp
@@ -0,0 +1,33 @@
+/**
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI::application::WeissHapticSensor
+ * @author     Simon Ottenhaus ( simon dot ottenhaus at kit dot edu )
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#include "WeissHapticSensorApp.h"
+#include <Core/core/logging/Logging.h>
+
+int main(int argc, char* argv[])
+{
+    armarx::ApplicationPtr app = armarx::Application::createInstance < armarx::WeissHapticSensorApp > ();
+    app->setName("WeissHapticSensor");
+
+    return app->main(argc, argv);
+}
diff --git a/source/RobotAPI/applications/WeissHapticSensorsUnit/CMakeLists.txt b/source/RobotAPI/applications/WeissHapticSensorsUnit/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..3f7af3cd59fbdac4735e98129332367b788ef8d1
--- /dev/null
+++ b/source/RobotAPI/applications/WeissHapticSensorsUnit/CMakeLists.txt
@@ -0,0 +1,15 @@
+armarx_component_set_name("WeissHapticSensorsUnitApp")
+
+find_package(Eigen3 QUIET)
+armarx_build_if(Eigen3_FOUND "Eigen3 not available")
+
+if (Eigen3_FOUND)
+    include_directories(
+        ${Eigen3_INCLUDE_DIR})
+endif()
+
+set(COMPONENT_LIBS ArmarXInterfaces ArmarXCore ArmarXCoreObservers ArmarXCoreEigen3Variants WeissHapticSensor RobotAPIUnits RobotAPIInterfaces)
+
+set(EXE_SOURCE main.cpp WeissHapticSensorsUnitApp.h)
+
+armarx_add_component_executable("${EXE_SOURCE}")
diff --git a/source/RobotAPI/applications/WeissHapticSensorsUnit/WeissHapticSensorsUnitApp.h b/source/RobotAPI/applications/WeissHapticSensorsUnit/WeissHapticSensorsUnitApp.h
new file mode 100644
index 0000000000000000000000000000000000000000..fba3b1a793e7963b95932e033863113ba0630ec8
--- /dev/null
+++ b/source/RobotAPI/applications/WeissHapticSensorsUnit/WeissHapticSensorsUnitApp.h
@@ -0,0 +1,55 @@
+/**
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI::application::WeissHapticSensorsUnit
+ * @author     Simon Ottenhaus ( simon dot ottenhaus at kit dot edu )
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#ifndef _ARMARX_APPLICATION_RobotAPI_WeissHapticSensorsUnit_H
+#define _ARMARX_APPLICATION_RobotAPI_WeissHapticSensorsUnit_H
+
+
+// #include <RobotAPI/armarx-objects/@MyComponent@.h>
+
+#include <Core/core/application/Application.h>
+#include <RobotAPI/drivers/WeissHapticSensor/WeissHapticSensorsUnit.h>
+
+namespace armarx
+{
+    /**
+     * @class WeissHapticSensorsUnitApp
+     * @brief A brief description
+     *
+     * Detailed Description
+     */
+    class WeissHapticSensorsUnitApp :
+        virtual public armarx::Application
+    {
+        /**
+         * @see armarx::Application::setup()
+         */
+        void setup(const ManagedIceObjectRegistryInterfacePtr& registry,
+                   Ice::PropertiesPtr properties)
+        {
+            registry->addObject( Component::create<WeissHapticSensorsUnit>(properties) );
+        }
+    };
+}
+
+#endif
diff --git a/source/RobotAPI/applications/WeissHapticSensorsUnit/main.cpp b/source/RobotAPI/applications/WeissHapticSensorsUnit/main.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..383d05876e7eadfd48ab4faeb807d8443f9fba7f
--- /dev/null
+++ b/source/RobotAPI/applications/WeissHapticSensorsUnit/main.cpp
@@ -0,0 +1,33 @@
+/**
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI::application::WeissHapticSensorsUnit
+ * @author     Simon Ottenhaus ( simon dot ottenhaus at kit dot edu )
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#include "WeissHapticSensorsUnitApp.h"
+#include <Core/core/logging/Logging.h>
+
+int main(int argc, char* argv[])
+{
+    armarx::ApplicationPtr app = armarx::Application::createInstance < armarx::WeissHapticSensorsUnitApp > ();
+    app->setName("WeissHapticSensorsUnit");
+
+    return app->main(argc, argv);
+}
diff --git a/source/RobotAPI/armarx-objects/CMakeLists.txt b/source/RobotAPI/armarx-objects/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..dc89e3a90eb6b211fb0b93856eee897ff374afbb
--- /dev/null
+++ b/source/RobotAPI/armarx-objects/CMakeLists.txt
@@ -0,0 +1,2 @@
+
+add_subdirectory(WeissHapticSensorListener)
\ No newline at end of file
diff --git a/source/RobotAPI/armarx-objects/WeissHapticSensorListener/CMakeLists.txt b/source/RobotAPI/armarx-objects/WeissHapticSensorListener/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..613112f8e1fae0115c71e6fd745abc90bf2bf20a
--- /dev/null
+++ b/source/RobotAPI/armarx-objects/WeissHapticSensorListener/CMakeLists.txt
@@ -0,0 +1,19 @@
+armarx_component_set_name("WeissHapticSensorListener")
+
+find_package(Eigen3 QUIET)
+armarx_build_if(Eigen3_FOUND "Eigen3 not available")
+
+if (Eigen3_FOUND)
+    include_directories(
+        ${Eigen3_INCLUDE_DIR})
+endif()
+
+set(COMPONENT_LIBS ArmarXInterfaces ArmarXCore ArmarXCoreObservers ArmarXCoreEigen3Variants RobotAPIInterfaces)
+
+set(SOURCES WeissHapticSensorListener.cpp)
+set(HEADERS WeissHapticSensorListener.h)
+
+armarx_add_component("${SOURCES}" "${HEADERS}")
+
+# add unit tests
+#add_subdirectory(test)
diff --git a/source/RobotAPI/armarx-objects/WeissHapticSensorListener/WeissHapticSensorListener.cpp b/source/RobotAPI/armarx-objects/WeissHapticSensorListener/WeissHapticSensorListener.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..5e2ddaeb4920ebc0175c3a451f28f11b7f5ee5e2
--- /dev/null
+++ b/source/RobotAPI/armarx-objects/WeissHapticSensorListener/WeissHapticSensorListener.cpp
@@ -0,0 +1,65 @@
+/**
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI::ArmarXObjects::WeissHapticSensorListener
+ * @author     Simon Ottenhaus ( simon dot ottenhaus at kit dot edu )
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#include "WeissHapticSensorListener.h"
+
+
+using namespace armarx;
+
+
+void WeissHapticSensorListener::onInitComponent()
+{
+    this->usingTopic("HapticValues");
+    ARMARX_LOG << "WeissHapticSensorListener::onInitComponent()";
+}
+
+
+void WeissHapticSensorListener::onConnectComponent()
+{
+
+}
+
+
+void WeissHapticSensorListener::onDisconnectComponent()
+{
+
+}
+
+
+void WeissHapticSensorListener::onExitComponent()
+{
+
+}
+
+PropertyDefinitionsPtr WeissHapticSensorListener::createPropertyDefinitions()
+{
+    return PropertyDefinitionsPtr(new WeissHapticSensorListenerPropertyDefinitions(
+                                      getConfigIdentifier()));
+}
+
+void armarx::WeissHapticSensorListener::reportSensorValues(const std::string& device, const std::string& name, const armarx::MatrixFloatBasePtr& values, const armarx::TimestampBasePtr& timestamp, const Ice::Current&)
+{
+    ARMARX_LOG << deactivateSpam(1) << "WeissHapticSensorListener::reportSensorValues" << MatrixFloatPtr::dynamicCast(values)->toEigen();
+
+}
+
diff --git a/source/RobotAPI/armarx-objects/WeissHapticSensorListener/WeissHapticSensorListener.h b/source/RobotAPI/armarx-objects/WeissHapticSensorListener/WeissHapticSensorListener.h
new file mode 100644
index 0000000000000000000000000000000000000000..11b773cb28684d48825035af867ec7feeca92b53
--- /dev/null
+++ b/source/RobotAPI/armarx-objects/WeissHapticSensorListener/WeissHapticSensorListener.h
@@ -0,0 +1,102 @@
+/**
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI::ArmarXObjects::WeissHapticSensorListener
+ * @author     Simon Ottenhaus ( simon dot ottenhaus at kit dot edu )
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#ifndef _ARMARX_COMPONENT_RobotAPI_WeissHapticSensorListener_H
+#define _ARMARX_COMPONENT_RobotAPI_WeissHapticSensorListener_H
+
+
+#include <Core/core/Component.h>
+#include <RobotAPI/interface/units/HapticUnit.h>
+#include <Core/util/variants/eigen3/MatrixVariant.h>
+#include <Core/util/variants/eigen3/VariantObjectFactories.h>
+
+
+namespace armarx
+{
+    /**
+     * @class WeissHapticSensorListenerPropertyDefinitions
+     * @brief
+     * @ingroup Components
+     */
+    class WeissHapticSensorListenerPropertyDefinitions:
+        public ComponentPropertyDefinitions
+    {
+    public:
+        WeissHapticSensorListenerPropertyDefinitions(std::string prefix):
+            ComponentPropertyDefinitions(prefix)
+        {
+            //defineRequiredProperty<std::string>("PropertyName", "Description");
+            //defineOptionalProperty<std::string>("PropertyName", "DefaultValue", "Description");
+        }
+    };
+
+    /**
+     * @class WeissHapticSensorListener
+     * @brief A brief description
+     *
+     * Detailed Description
+     */
+    class WeissHapticSensorListener :
+        virtual public armarx::Component,
+        virtual public armarx::HapticUnitListener
+    {
+    public:
+        /**
+         * @see armarx::ManagedIceObject::getDefaultName()
+         */
+        virtual std::string getDefaultName() const
+        {
+            return "WeissHapticSensorListener";
+        }
+
+        virtual void reportSensorValues(const ::std::string& device, const ::std::string& name, const ::armarx::MatrixFloatBasePtr& values, const ::armarx::TimestampBasePtr& timestamp, const ::Ice::Current& = ::Ice::Current());
+
+    protected:
+        /**
+         * @see armarx::ManagedIceObject::onInitComponent()
+         */
+        virtual void onInitComponent();
+
+        /**
+         * @see armarx::ManagedIceObject::onConnectComponent()
+         */
+        virtual void onConnectComponent();
+
+        /**
+         * @see armarx::ManagedIceObject::onDisconnectComponent()
+         */
+        virtual void onDisconnectComponent();
+
+        /**
+         * @see armarx::ManagedIceObject::onExitComponent()
+         */
+        virtual void onExitComponent();
+
+        /**
+         * @see PropertyUser::createPropertyDefinitions()
+         */
+        virtual PropertyDefinitionsPtr createPropertyDefinitions();
+    };
+}
+
+#endif
diff --git a/source/RobotAPI/armarx-objects/WeissHapticSensorListener/test/CMakeLists.txt b/source/RobotAPI/armarx-objects/WeissHapticSensorListener/test/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..2c89a2d67c402d97784d7a903af9de0c6191150f
--- /dev/null
+++ b/source/RobotAPI/armarx-objects/WeissHapticSensorListener/test/CMakeLists.txt
@@ -0,0 +1,5 @@
+
+# Libs required for the tests
+SET(LIBS ${LIBS} ArmarXCore WeissHapticSensorListener)
+ 
+armarx_add_test(WeissHapticSensorListenerTest WeissHapticSensorListenerTest.cpp "${LIBS}")
\ No newline at end of file
diff --git a/source/RobotAPI/armarx-objects/WeissHapticSensorListener/test/WeissHapticSensorListenerTest.cpp b/source/RobotAPI/armarx-objects/WeissHapticSensorListener/test/WeissHapticSensorListenerTest.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..089961daf9fcd32354f8b52cb28ad509c22503cb
--- /dev/null
+++ b/source/RobotAPI/armarx-objects/WeissHapticSensorListener/test/WeissHapticSensorListenerTest.cpp
@@ -0,0 +1,38 @@
+/**
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    RobotAPI::ArmarXObjects::WeissHapticSensorListener
+ * @author     Simon Ottenhaus ( simon dot ottenhaus at kit dot edu )
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#define BOOST_TEST_MODULE RobotAPI::ArmarXObjects::WeissHapticSensorListener
+
+#define ARMARX_BOOST_TEST
+
+#include <RobotAPI/Test.h>
+#include <RobotAPI/armarx-objects/WeissHapticSensorListener/WeissHapticSensorListener.h>
+
+#include <iostream>
+
+BOOST_AUTO_TEST_CASE(testExample)
+{
+    armarx::WeissHapticSensorListener instance;
+
+    BOOST_CHECK_EQUAL(true, true);
+}
diff --git a/source/RobotAPI/core/RobotStatechartContext.cpp b/source/RobotAPI/core/RobotStatechartContext.cpp
index 5b85b23d6dcd2f4c5e6c0e0fc1ae59c408d28f18..5570d7d0663c8b5f6d13d35cb3539683a187d79c 100644
--- a/source/RobotAPI/core/RobotStatechartContext.cpp
+++ b/source/RobotAPI/core/RobotStatechartContext.cpp
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
@@ -47,6 +47,18 @@ namespace armarx
         usingProxy(getProperty<std::string>("KinematicUnitName").getValue());
         usingProxy("RobotStateComponent");
         usingProxy(kinematicUnitObserverName);
+
+        if( getProperty<std::string>("HandUnits").isSet())
+        {
+            std::string handUnitsProp = getProperty<std::string>("HandUnits").getValue();
+            std::vector<std::string> handUnitList;
+            boost::split(handUnitList, handUnitsProp, boost::is_any_of(","));
+            for(size_t i = 0; i < handUnitList.size(); i++)
+            {
+                boost::algorithm::trim(handUnitList.at(i));
+                usingProxy(handUnitList.at(i));
+            }
+        }
     }
 
 
@@ -61,6 +73,17 @@ namespace armarx
         kinematicUnitObserverPrx = getProxy<KinematicUnitObserverInterfacePrx>(kinematicUnitObserverName);
         ARMARX_LOG << eINFO << "Fetched proxies" <<  kinematicUnitPrx << " " << robotStateComponent << flush;
 
+        if( getProperty<std::string>("HandUnits").isSet())
+        {
+            std::string handUnitsProp = getProperty<std::string>("HandUnits").getValue();
+            std::vector<std::string> handUnitList;
+            boost::split(handUnitList, handUnitsProp, boost::is_any_of(","));
+            for(size_t i = 0; i < handUnitList.size(); i++)
+            {
+                boost::algorithm::trim(handUnitList.at(i));
+                handUnits[handUnitList.at(i)] = getProxy<HandUnitInterfacePrx>(handUnitList.at(i));
+            }
+        }
 		// initialize remote robot
         remoteRobot.reset(new RemoteRobot(robotStateComponent->getSynchronizedRobot()));
         ARMARX_LOG << eINFO << "Created remote robot" << flush;
diff --git a/source/RobotAPI/core/RobotStatechartContext.h b/source/RobotAPI/core/RobotStatechartContext.h
index 47ba13f6e7f68509cf185176b1600ae491649023..1596eb373724ebc2e62e2fcc981b900edfa2d450 100644
--- a/source/RobotAPI/core/RobotStatechartContext.h
+++ b/source/RobotAPI/core/RobotStatechartContext.h
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
@@ -30,6 +30,7 @@
 #include <Core/statechart/StatechartContext.h>
 #include <Core/robotstate/remote/RemoteRobot.h>
 #include <Core/interface/units/KinematicUnitInterface.h>
+#include <Core/interface/units/HandUnitInterface.h>
 
 #include <RobotAPI/interface/units/TCPControlUnit.h>
 
@@ -51,6 +52,7 @@ namespace armarx
         {
             defineRequiredProperty<std::string>("KinematicUnitName", "Name of the kinematic unit that should be used");
             defineRequiredProperty<std::string>("KinematicUnitObserverName", "Name of the kinematic unit observer that should be used");
+            defineOptionalProperty<std::string>("HandUnits", "", "Name of the comma-seperated hand units that should be used. Unitname for left hand should be LeftHandUnit, and for right hand RightHandUnit");
 
         }
     };
@@ -86,6 +88,7 @@ namespace armarx
         KinematicUnitObserverInterfacePrx kinematicUnitObserverPrx;
         TCPControlUnitInterfacePrx tcpControlPrx;
         VirtualRobot::RobotPtr remoteRobot;
+        std::map<std::string, HandUnitInterfacePrx> handUnits;
     private:
         std::string kinematicUnitObserverName;
     };
diff --git a/source/RobotAPI/drivers/CMakeLists.txt b/source/RobotAPI/drivers/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..802a6b533e8945574afb9e92d9d86a1d0e41fe19
--- /dev/null
+++ b/source/RobotAPI/drivers/CMakeLists.txt
@@ -0,0 +1 @@
+add_subdirectory(WeissHapticSensor)
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/AbstractInterface.cpp b/source/RobotAPI/drivers/WeissHapticSensor/AbstractInterface.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b450fb668f81c4bc9bc73986211bb46d92ef2733
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/AbstractInterface.cpp
@@ -0,0 +1,293 @@
+#include "AbstractInterface.h"
+#include "Response.h"
+#include "Checksum.h"
+#include "Types.h"
+#include "TransmissionException.h"
+#include <string.h>
+#include <iostream>
+#include <stdio.h>
+#include <stdexcept>
+
+
+AbstractInterface::AbstractInterface()
+    : connected(false)
+{
+
+}
+
+AbstractInterface::~AbstractInterface()
+{
+}
+
+int AbstractInterface::read(unsigned char *buf, unsigned int len)
+{
+    int res = readInternal(buf, len);
+
+    if(log != NULL)
+    {
+        log->logRead(buf, res);
+    }
+
+    return res;
+}
+
+int AbstractInterface::write(unsigned char *buf, unsigned int len)
+{
+    if(log != NULL)
+    {
+        log->logWrite(buf, len);
+    }
+
+    return writeInternal(buf, len);
+}
+
+
+Response AbstractInterface::submitCmd( unsigned char id, unsigned char *payload, unsigned int len, bool pending )
+{
+    fireAndForgetCmd(id, payload, len, pending);
+    return receive(pending, id);
+}
+
+void AbstractInterface::fireAndForgetCmd( unsigned char id, unsigned char *payload, unsigned int len, bool pending )
+{
+
+    int res;
+
+    // Check if we're connected
+    if ( !connected )
+    {
+        throw TransmissionException("Interface not connected");
+    }
+
+    // Send command
+    res = send( id, len, payload );
+    if ( res < 0 )
+    {
+        throw TransmissionException("Message send failed");
+    }
+}
+
+void AbstractInterface::startLogging(std::string file)
+{
+    log.reset(new BinaryLogger(file));
+}
+
+void AbstractInterface::logText(std::string message)
+{
+    if(log != NULL)
+    {
+        log->logText(message);
+    }
+}
+
+Response AbstractInterface::receiveWithoutChecks()
+{
+    int res;
+    status_t status;
+    msg_t msg;
+
+    // Receive response data
+    res = receive( &msg );
+    if ( res < 0 )
+    {
+        throw TransmissionException("Message receive failed");
+    }
+
+    status = (status_t) make_short( msg.data[0], msg.data[1] );
+
+    return Response(res, msg.id, status, msg.data, msg.len);
+}
+
+Response AbstractInterface::receive(bool pending, unsigned char expectedId)
+{
+    int res;
+    status_t status;
+    msg_t msg;
+
+    // Receive response. Repeat if pending.
+    do
+    {
+        // Receive response data
+        res = receive( &msg );
+        if ( res < 0 )
+        {
+            throw TransmissionException("Message receive failed");
+        }
+
+        // Check response ID
+        if ( msg.id != expectedId )
+        {
+            //std::strstream strStream;
+            //strStream << "Response ID ()" << msg.id << ") does not match submitted command ID (" << id << ")";
+            //throw std::runtime_error(strStream.str());
+            throw TransmissionException(str(boost::format("Response ID (%02X) does not match submitted command ID (%02X)") % (int)msg.id % (int)expectedId));
+        }
+
+        if ( pending )
+        {
+            if ( msg.len < 2 )
+            {
+                throw TransmissionException("No status code received");
+            }
+
+            status = (status_t) make_short( msg.data[0], msg.data[1] );
+        }
+    }
+    while( pending && status == E_CMD_PENDING );
+
+    status = (status_t) make_short( msg.data[0], msg.data[1] );
+
+    return Response(res, msg.id, status, msg.data, msg.len);
+}
+
+int AbstractInterface::receive( msg_t *msg )
+{
+    int res;
+    unsigned char header[3];			// 1 byte command, 2 bytes payload length
+    unsigned short checksum = 0x50f5;	// Checksum over preamble (0xaa 0xaa 0xaa)
+    unsigned int sync;
+
+    logText("read preamble");
+    // Syncing - necessary for compatibility with serial interface
+    sync = 0;
+    while( sync != MSG_PREAMBLE_LEN )
+    {
+        res = read( header, 1 );
+        if ( res > 0 && header[0] == MSG_PREAMBLE_BYTE )
+        {
+            sync++;
+        }
+        else
+        {
+            sync = 0;
+        }
+    }
+
+    // Read header
+    res = read( header, 3 );
+    if ( res < 3 )
+    {
+        throw TransmissionException(str(boost::format("Failed to receive header data (%d bytes read)") % res));
+    }
+
+    // Calculate checksum over header
+    checksum = Checksum::Update_crc16( header, 3, checksum );
+
+    // Get message id of received
+    msg->id = header[0];
+
+    // Get payload size of received message
+    msg->len = make_short( header[1], header[2] );
+
+    // Allocate space for payload and checksum
+    //msg->data.resize( msg->len + 2u );
+    //if ( !msg->data ) return -1;
+    //boost::shared_ptr<unsigned char[]> data(new unsigned char[ msg->len + 2u ]);
+
+    unsigned char* data = new unsigned char[ msg->len + 2u ];
+
+
+    // Read payload and checksum
+    /*int maxReads = 10;
+    int remaining = msg->len + 2;
+    int dataOffset = 0;
+    while ( maxReads > 0 && remaining > 0)
+    {
+        int read = this->read( data + dataOffset, remaining );
+        dataOffset += read;
+        remaining -= read;
+        maxReads--;
+        if (remaining > 0) {
+            //fprintf( stderr, "Partial message received, waiting 100µs\n" );
+            usleep(100);
+        }
+    }
+    if (remaining > 0)
+    {
+        throw TransmissionException(str(boost::format("Not enough data (%d, expected %d), Command = %02X") % res % (msg->len + 2) % msg->id));
+    }*/
+
+    // Read payload and checksum:
+    // payload: msg->len
+    // checksum: 2 byte
+    int read = this->read( data, msg->len + 2 );
+    if (read != (int)msg->len + 2)
+    {
+        delete[] data;
+        throw TransmissionException(str(boost::format("Not enough data (%d, expected %d), Command = %02X") % res % (msg->len + 2) % msg->id));
+    }
+
+    /*
+    res = interface->read( msg->data, msg->len + 2 );
+    if ( res < (int) (msg->len + 2) )
+    {
+        fprintf( stderr, "Not enough data (%d, expected %d)\n", res, msg->len + 2 );
+        fprintf( stderr, "Command = %X\n", msg->id );
+        return -1;
+    }*/
+
+    // Check checksum
+    checksum = Checksum::Update_crc16( data, msg->len + 2, checksum );
+    if ( checksum != 0 )
+    {
+        delete[] data;
+        logText("CHECKSUM ERROR");
+        throw ChecksumErrorException("Checksum error");
+    }
+
+    msg->data = std::vector<unsigned char>(data, data + msg->len + 2);
+    delete[] data;
+    logText("receive done.");
+    return msg->len + 8;
+}
+
+int AbstractInterface::send(unsigned char id, unsigned int len, unsigned char *data)
+{
+    unsigned char header[MSG_PREAMBLE_LEN + 3];
+    unsigned short crc;
+    int i, res;
+
+    logText("write preamble");
+
+    // Preamble
+    for ( i = 0; i < MSG_PREAMBLE_LEN; i++ ) header[i] = MSG_PREAMBLE_BYTE;
+
+    // Command ID
+    header[MSG_PREAMBLE_LEN] = id;
+
+    // Length
+    header[MSG_PREAMBLE_LEN + 1] = lo( len );
+    header[MSG_PREAMBLE_LEN + 2] = hi( len );
+
+    // Checksum
+    crc = Checksum::Crc16( header, 6 );
+    crc = Checksum::Update_crc16( data, len, crc );
+
+
+    unsigned char *buf = new unsigned char[ 6 + len + 2 ];
+    memcpy( buf, header, 6 );
+    memcpy( buf + 6, data, len );
+    memcpy( buf + 6 + len, (unsigned char *) &crc, 2 );
+
+    res = write( buf, 6 + len + 2 );
+    if ( res < 6 + (int)len + 2 )
+    {
+		delete[] buf;
+        close();
+        throw TransmissionException("Failed to submit message checksum");
+        //cerr << "Failed to submit message checksum" << endl;
+    }
+
+    delete[] buf;
+
+    logText("send done.");
+
+    return len + 8;
+}
+
+
+
+
+std::ostream& operator<<(std::ostream &strm, const AbstractInterface &a) {
+  return strm << a.toString();
+}
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/AbstractInterface.h b/source/RobotAPI/drivers/WeissHapticSensor/AbstractInterface.h
new file mode 100644
index 0000000000000000000000000000000000000000..19e45a9cc1821573cd222cd4f1c53b2894c640af
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/AbstractInterface.h
@@ -0,0 +1,65 @@
+#ifndef ABSTRACTINTERFACE_H
+#define ABSTRACTINTERFACE_H
+
+#include <string>
+#include "Types.h"
+#include "BinaryLogger.h"
+#include <boost/shared_ptr.hpp>
+
+
+#define MSG_PREAMBLE_BYTE		0xaa
+#define MSG_PREAMBLE_LEN		3
+
+// Combine bytes to different types
+#define make_short( lowbyte, highbyte )				( (unsigned short)lowbyte | ( (unsigned short)highbyte << 8 ) )
+#define make_signed_short( lowbyte, highbyte )		( (signed short) ( (unsigned short) lowbyte | ( (unsigned short) highbyte << 8 ) ) )
+#define make_int( lowbyte, mid1, mid2, highbyte )	( (unsigned int) lowbyte | ( (unsigned int) mid1 << 8 ) | ( (unsigned int) mid2 << 16 ) | ( (unsigned int) highbyte << 24 ) )
+#define make_float( result, byteptr )				memcpy( &result, byteptr, sizeof( float ) )
+
+// Byte access
+#define hi( x )    	(unsigned char) ( ((x) >> 8) & 0xff )	// Returns the upper byte of the passed short
+#define lo( x )    	(unsigned char) ( (x) & 0xff )       	// Returns the lower byte of the passed short
+
+
+
+struct Response;
+
+class AbstractInterface
+{
+public:
+    AbstractInterface();
+    virtual ~AbstractInterface();
+    virtual int open() = 0;
+    virtual void close() = 0;
+    int read( unsigned char *buf, unsigned int len);
+    int write( unsigned char *buf, unsigned int len);
+
+    bool IsConnected() const { return connected; }
+
+    virtual std::string toString() const = 0;
+
+    int send(unsigned char id, unsigned int len, unsigned char *data);
+    int receive( msg_t *msg );
+    Response submitCmd( unsigned char id, unsigned char *payload, unsigned int len, bool pending );
+    Response receive(bool pending, unsigned char expectedId);
+    Response receiveWithoutChecks();
+    void fireAndForgetCmd( unsigned char id, unsigned char *payload, unsigned int len, bool pending );
+
+    void startLogging(std::string file);
+    void logText(std::string message);
+
+protected:
+    bool connected;
+
+    virtual int readInternal( unsigned char *buf, unsigned int len) = 0;
+    virtual int writeInternal( unsigned char *buf, unsigned int len) = 0;
+
+private:
+    friend std::ostream& operator<<(std::ostream&, const AbstractInterface&);
+    boost::shared_ptr<BinaryLogger> log;
+};
+
+std::ostream& operator<<(std::ostream &strm, const AbstractInterface &a);
+
+
+#endif // ABSTRACTINTERFACE_H
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/BinaryLogger.cpp b/source/RobotAPI/drivers/WeissHapticSensor/BinaryLogger.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..40c39409735c5b9fefd71983e83294bc19d6c9ca
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/BinaryLogger.cpp
@@ -0,0 +1,40 @@
+#include "BinaryLogger.h"
+#include <boost/format.hpp>
+
+BinaryLogger::BinaryLogger(std::string filename)
+{
+    this->log.open(filename.c_str());
+}
+
+BinaryLogger::~BinaryLogger()
+{
+    log.close();
+}
+
+void BinaryLogger::logRead(unsigned char *buf, unsigned int len)
+{
+    log << "READ";
+    for(unsigned int i = 0; i < len; i++)
+    {
+        log << boost::format(" %02X") % (int)buf[i];
+    }
+    log << std::endl;
+    log.flush();
+}
+
+void BinaryLogger::logWrite(unsigned char *buf, unsigned int len)
+{
+    log << "WRITE";
+    for(unsigned int i = 0; i < len; i++)
+    {
+        log << boost::format(" %02X") % (int)buf[i];
+    }
+    log << std::endl;
+    log.flush();
+}
+
+void BinaryLogger::logText(std::string message)
+{
+    log << message << std::endl;
+    log.flush();
+}
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/BinaryLogger.h b/source/RobotAPI/drivers/WeissHapticSensor/BinaryLogger.h
new file mode 100644
index 0000000000000000000000000000000000000000..535786e85c1b1f02e3fd12dc3fda398cdd239654
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/BinaryLogger.h
@@ -0,0 +1,20 @@
+#ifndef BINARYLOGGER_H
+#define BINARYLOGGER_H
+
+#include <fstream>
+
+class BinaryLogger
+{
+public:
+    BinaryLogger(std::string filename);
+    ~BinaryLogger();
+
+    void logRead(unsigned char *buf, unsigned int len);
+    void logWrite(unsigned char *buf, unsigned int len);
+    void logText(std::string message);
+
+private:
+    std::ofstream log;
+};
+
+#endif // BINARYLOGGER_H
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/CMakeLists.txt b/source/RobotAPI/drivers/WeissHapticSensor/CMakeLists.txt
new file mode 100644
index 0000000000000000000000000000000000000000..950b6e9c3e81eb53b6e3faaf965281b954c7a5ac
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/CMakeLists.txt
@@ -0,0 +1,47 @@
+armarx_set_target("WeissHapticSensor Library: WeissHapticSensor")
+
+
+find_package(Eigen3 QUIET)
+
+armarx_build_if(Eigen3_FOUND "Eigen3 not available")
+
+if (Eigen3_FOUND)
+    include_directories(
+        ${Eigen3_INCLUDE_DIR})
+endif()
+
+set(LIB_NAME       WeissHapticSensor)
+set(LIB_VERSION    0.1.0)
+set(LIB_SOVERSION  0)
+
+set(LIBS RobotAPIUnits RobotAPIInterfaces ArmarXInterfaces ArmarXCore ArmarXCoreObservers ArmarXCoreEigen3Variants)
+
+set(LIB_FILES
+    WeissHapticSensorsUnit.cpp
+    WeissHapticSensor.cpp
+    AbstractInterface.cpp
+    BinaryLogger.cpp
+    TextWriter.cpp
+    Checksum.cpp
+    SerialInterface.cpp
+    TactileSensor.cpp
+    CalibrationInfo.cpp
+    CalibrationHelper.cpp
+)
+set(LIB_HEADERS
+    WeissHapticSensorsUnit.h
+    WeissHapticSensor.h
+    AbstractInterface.h
+    BinaryLogger.h
+    TextWriter.h
+    Checksum.h
+    Response.h
+    SerialInterface.h
+    TactileSensor.h
+    TransmissionException.h
+    Types.h
+    CalibrationInfo.h
+    CalibrationHelper.h
+)
+
+armarx_add_library("${LIB_NAME}" "${LIB_VERSION}" "${LIB_SOVERSION}" "${LIB_FILES}" "${LIB_HEADERS}" "${LIBS}")
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/CalibrationHelper.cpp b/source/RobotAPI/drivers/WeissHapticSensor/CalibrationHelper.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..244edebedae87da6a36d37eff64a6bd955d0c682
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/CalibrationHelper.cpp
@@ -0,0 +1,64 @@
+#include "CalibrationHelper.h"
+
+#include <stdexcept>
+
+using namespace armarx;
+
+CalibrationHelper::CalibrationHelper(int rows, int cols, float noiseThreshold)
+{
+    this->maximumValues = Eigen::MatrixXf::Zero(rows, cols);
+    this->noiseThreshold = noiseThreshold;
+}
+
+void CalibrationHelper::addNoiseSample(Eigen::MatrixXf data)
+{
+    this->noiseSamples.push_back(data);
+}
+
+bool CalibrationHelper::addMaxValueSample(Eigen::MatrixXf data)
+{
+    if(data.maxCoeff() <= noiseThreshold)
+    {
+        this->maximumValues = this->maximumValues.cwiseMax(data);
+        return true;
+    }
+    else
+    {
+        return false;
+    }
+}
+
+CalibrationInfo CalibrationHelper::getCalibrationInfo(float calibratedMinimum, float calibratedMaximum)
+{
+    return CalibrationInfo(getMatrixAverage(noiseSamples), maximumValues, calibratedMinimum, calibratedMaximum);
+}
+
+bool CalibrationHelper::checkMaximumValueThreshold(float threshold)
+{
+    return this->maximumValues.minCoeff() >= threshold;
+}
+
+Eigen::MatrixXf CalibrationHelper::getMaximumValues()
+{
+    return this->maximumValues;
+}
+
+int CalibrationHelper::getNoiseSampleCount()
+{
+    return this->noiseSamples.size();
+}
+
+Eigen::MatrixXf CalibrationHelper::getMatrixAverage(std::vector<Eigen::MatrixXf> samples)
+{
+    if(samples.size() == 0)
+    {
+        throw std::runtime_error("Average of zero samples not possible");
+    }
+
+    Eigen::MatrixXf sum = samples.at(0);
+    for(std::vector<Eigen::MatrixXf>::iterator it = samples.begin() + 1; it != samples.end(); ++it)
+    {
+        sum += *it;
+    }
+    return sum / (float)samples.size();
+}
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/CalibrationHelper.h b/source/RobotAPI/drivers/WeissHapticSensor/CalibrationHelper.h
new file mode 100644
index 0000000000000000000000000000000000000000..243d79ff5b068191f1134c1a4301f4bd94e1083a
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/CalibrationHelper.h
@@ -0,0 +1,36 @@
+#ifndef CALIBRATIONHELPER_H
+#define CALIBRATIONHELPER_H
+
+#include <vector>
+#include <Eigen/Core>
+
+#include "CalibrationInfo.h"
+
+namespace armarx
+{
+    class CalibrationHelper
+    {
+    public:
+        CalibrationHelper(int rows, int cols, float noiseThreshold);
+
+        void addNoiseSample(Eigen::MatrixXf data);
+        bool addMaxValueSample(Eigen::MatrixXf data);
+
+        CalibrationInfo getCalibrationInfo(float calibratedMinimum, float calibratedMaximum);
+
+        bool checkMaximumValueThreshold(float threshold);
+
+        Eigen::MatrixXf getMaximumValues();
+
+        int getNoiseSampleCount();
+
+    private:
+        std::vector<Eigen::MatrixXf> noiseSamples;
+        Eigen::MatrixXf maximumValues;
+        float noiseThreshold;
+
+        Eigen::MatrixXf getMatrixAverage(std::vector<Eigen::MatrixXf> samples);
+    };
+}
+
+#endif // CALIBRATIONHELPER_H
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/CalibrationInfo.cpp b/source/RobotAPI/drivers/WeissHapticSensor/CalibrationInfo.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..caad13edcd6b065b499876a0cf81c51d9976eedf
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/CalibrationInfo.cpp
@@ -0,0 +1,16 @@
+#include "CalibrationInfo.h"
+
+using namespace armarx;
+
+CalibrationInfo::CalibrationInfo(Eigen::MatrixXf averageNoiseValues, Eigen::MatrixXf maximumValues, float minValue, float maxValue)
+{
+    this->calibratedMinimum = minValue;
+    this->calibratedMaximum = maxValue;
+    this->averageNoiseValues = averageNoiseValues;
+    this->maximumValues = maximumValues;
+}
+
+Eigen::MatrixXf CalibrationInfo::applyCalibration(Eigen::MatrixXf rawData)
+{
+    return ((rawData - averageNoiseValues).cwiseQuotient(maximumValues - averageNoiseValues).array() * (calibratedMaximum - calibratedMinimum) + calibratedMinimum).matrix();
+}
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/CalibrationInfo.h b/source/RobotAPI/drivers/WeissHapticSensor/CalibrationInfo.h
new file mode 100644
index 0000000000000000000000000000000000000000..d1ca58a0ca742c2ca317f16470824f9b506c458b
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/CalibrationInfo.h
@@ -0,0 +1,23 @@
+#ifndef CALIBRATIONINFO_H
+#define CALIBRATIONINFO_H
+
+#include <Eigen/Core>
+namespace armarx
+{
+    class CalibrationInfo
+    {
+    public:
+        CalibrationInfo(Eigen::MatrixXf averageNoiseValues, Eigen::MatrixXf maximumValues, float calibratedMinimum, float calibratedMaximum);
+
+        Eigen::MatrixXf applyCalibration(Eigen::MatrixXf rawData);
+
+    private:
+        Eigen::MatrixXf averageNoiseValues;
+        Eigen::MatrixXf maximumValues;
+
+        float calibratedMaximum;
+        float calibratedMinimum;
+    };
+}
+
+#endif // CALIBRATIONINFO_H
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/Checksum.cpp b/source/RobotAPI/drivers/WeissHapticSensor/Checksum.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..15b3cecfff3a5073d476ce5c544f6ad617696859
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/Checksum.cpp
@@ -0,0 +1,94 @@
+
+#include "Checksum.h"
+
+/*
+ * CRC16 lookup table
+ *
+ * CCITT-16 compatible crc table using polynomial 0x1021,
+ * corresponding to x^16 + x^12 + x^5 + 1
+ */
+
+const unsigned short Checksum::CRC_TABLE_CCITT16[256] = {
+    0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
+    0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
+    0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
+    0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
+    0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
+    0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
+    0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
+    0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
+    0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
+    0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
+    0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
+    0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
+    0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
+    0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
+    0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
+    0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
+    0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
+    0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
+    0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
+    0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
+    0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
+    0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
+    0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
+    0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
+    0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
+    0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
+    0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
+    0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
+    0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
+    0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
+    0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
+    0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
+};
+
+/**
+ * Calculates the CRC16 checksum of an array by using a table.
+ * The crc16 polynomial is 0x1021 ( x^16 + x^12 + x^5 + 1 ).
+ *
+ * Note: The checksum generated by this function is NOT according
+ * to CCITT standard!
+ *
+ * @param *data       Points to the byte array from which checksum should
+ *                    be calculated
+ * @param size        Size of the byte array
+ * @param crc         Value calculated over another array and start value
+ *                    of the crc16 calculation
+ *
+ * @return CRC16 checksum
+ */
+
+unsigned short Checksum::Update_crc16( unsigned char *data, unsigned int size, unsigned short crc )
+{
+    unsigned long c;
+
+    /* process each byte prior to checksum field */
+    for ( c=0; c < size; c++ )
+    {
+        crc = CRC_TABLE_CCITT16[ ( crc ^ *( data ++ )) & 0x00FF ] ^ ( crc >> 8 );
+    }
+    return crc;
+}
+
+
+/**
+ * Calculates the CRC16 checksum of an array by using a table.
+ * The crc16 polynomial is 0x1021 ( x^16 + x^12 + x^5 + 1 ).
+ * This function takes 0xffff as initial value.
+ *
+ * Note: The checksum generated by this function is NOT according
+ * to CCITT standard!
+ *
+ * @param *data		Points to the byte array from which checksum should
+ * 					be calculated
+ * @param size		Size of the byte array
+ *
+ * @return CRC16 checksum
+ */
+
+unsigned short Checksum::Crc16( unsigned char *data, unsigned int size )
+{
+    return( Update_crc16( data, size, 0xffff ) );
+}
+
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/Checksum.h b/source/RobotAPI/drivers/WeissHapticSensor/Checksum.h
new file mode 100644
index 0000000000000000000000000000000000000000..7603e1b69f4a71856b801a716c2454e18024ffca
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/Checksum.h
@@ -0,0 +1,14 @@
+#ifndef CHECKSUM_H
+#define CHECKSUM_H
+
+class Checksum
+{
+public:
+    static unsigned short Crc16( unsigned char *data, unsigned int size );
+    static unsigned short Update_crc16( unsigned char *data, unsigned int size, unsigned short crc );
+
+private:
+    static const unsigned short CRC_TABLE_CCITT16[256];
+};
+
+#endif // CHECKSUM_H
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/Response.h b/source/RobotAPI/drivers/WeissHapticSensor/Response.h
new file mode 100644
index 0000000000000000000000000000000000000000..933f45af0cc5372578274f6a21316fddd13c7691
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/Response.h
@@ -0,0 +1,104 @@
+#ifndef RESPONSE_H
+#define RESPONSE_H
+
+#include "Types.h"
+#include "TransmissionException.h"
+//#include <strstream>
+#include <stdexcept>
+#include <boost/format.hpp>
+#include <vector>
+#include <Core/core/logging/Logging.h>
+
+struct Response {
+public:
+    Response(int res, unsigned char cmdId, status_t status, const std::vector<unsigned char> &data, unsigned int len)
+        : res(res), cmdId(cmdId), status(status), data(data), len(len) {}
+
+    unsigned int getUInt(int index)
+    {
+        return (unsigned int)data[index] | ( (unsigned int)data[index + 1] << 8) | ( (unsigned int)data[index + 2] << 16) | ( (unsigned int)data[index + 3] << 24);
+    }
+
+    unsigned short getShort(int index)
+    {
+        return (unsigned short)data[index] | ( (unsigned short)data[index + 1] << 8);
+    }
+    unsigned char getByte(int index)
+    {
+        return data[index];
+    }
+
+    void ensureMinLength(int len)
+    {
+        if ( res < len ) {
+            //std::strstream strStream;
+            //strStream << "Response length is too short, should be = " << len << " (is " << res << ")";
+            //throw std::runtime_error(strStream.str());
+            throw TransmissionException(str(boost::format("Response length is too short, should be = %1% (is %2%)") % len % res));
+        }
+    }
+
+    void ensureSuccess()
+    {
+        if ( status != E_SUCCESS )
+        {
+            //std::strstream strStream;
+            //strStream << "Command not successful: " << status_to_str( status );
+            //throw std::runtime_error(strStream.str());
+            std::stringstream ss;
+            ss << " status != E_SUCCESS";
+            for(int i=0; i<(int)len; i++){
+                ss << boost::format("%02X ") % (int)data[i];
+            }
+            ARMARX_ERROR_S << ss.str();
+            throw TransmissionException(str(boost::format("Command not successful: %1% (0x%2$02X)") % status_to_str( status ) % status));
+        }
+    }
+
+    int res;
+    unsigned char cmdId;
+    status_t status;
+    std::vector<unsigned char> data;
+    unsigned int len;
+
+    static const char* status_to_str( status_t status )
+    {
+        switch( status )
+        {
+            case E_SUCCESS:					return( "No error" );
+            case E_NOT_AVAILABLE:			return( "Service or data is not available" );
+            case E_NO_SENSOR:				return( "No sensor connected" );
+            case E_NOT_INITIALIZED:			return( "The device is not initialized" );
+            case E_ALREADY_RUNNING:			return( "Service is already running" );
+            case E_FEATURE_NOT_SUPPORTED:	return( "The requested feature is not supported" );
+            case E_INCONSISTENT_DATA:		return( "One or more dependent parameters mismatch" );
+            case E_TIMEOUT:					return( "Timeout error" );
+            case E_READ_ERROR:				return( "Error while reading from a device" );
+            case E_WRITE_ERROR:				return( "Error while writing to a device" );
+            case E_INSUFFICIENT_RESOURCES:	return( "No memory available" );
+            case E_CHECKSUM_ERROR:			return( "Checksum error" );
+            case E_NO_PARAM_EXPECTED:		return( "No parameters expected" );
+            case E_NOT_ENOUGH_PARAMS:		return( "Not enough parameters" );
+            case E_CMD_UNKNOWN:				return( "Unknown command" );
+            case E_CMD_FORMAT_ERROR:		return( "Command format error" );
+            case E_ACCESS_DENIED:			return( "Access denied" );
+            case E_ALREADY_OPEN:			return( "Interface already open" );
+            case E_CMD_FAILED:				return( "Command failed" );
+            case E_CMD_ABORTED:				return( "Command aborted" );
+            case E_INVALID_HANDLE:			return( "Invalid handle" );
+            case E_NOT_FOUND:				return( "Device not found" );
+            case E_NOT_OPEN:				return( "Device not open" );
+            case E_IO_ERROR:				return( "General I/O-Error" );
+            case E_INVALID_PARAMETER:		return( "Invalid parameter" );
+            case E_INDEX_OUT_OF_BOUNDS:		return( "Index out of bounds" );
+            case E_CMD_PENDING:				return( "Command is pending..." );
+            case E_OVERRUN:					return( "Data overrun" );
+            case E_RANGE_ERROR:				return( "Value out of range" );
+            case E_AXIS_BLOCKED:			return( "Axis is blocked" );
+            case E_FILE_EXISTS:				return( "File already exists" );
+            default:						return( "Internal error. Unknown error code." );
+        }
+    }
+};
+
+#endif // RESPONSE_H
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/SerialInterface.cpp b/source/RobotAPI/drivers/WeissHapticSensor/SerialInterface.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..387d2f5236177c7f4583460325209259bad95993
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/SerialInterface.cpp
@@ -0,0 +1,170 @@
+#include "SerialInterface.h"
+
+#include <iostream>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <iostream>
+#include <boost/lexical_cast.hpp>
+#include <boost/format.hpp>
+
+static inline tcflag_t __bitrate_to_flag( unsigned int bitrate )
+{
+    switch( bitrate )
+    {
+        case   1200: return   B1200;
+        case   2400: return   B2400;
+        case   4800: return   B4800;
+        case   9600: return   B9600;
+        case  19200: return  B19200;
+        case  38400: return  B38400;
+        case  57600: return  B57600;
+        case 115200: return B115200;
+        case 230400: return B230400;
+        case 460800: return B460800;
+        default: return 0;
+    }
+}
+
+
+SerialInterface::SerialInterface(const char *device, unsigned int bitrate)
+{
+    this->device = device;
+    this->bitrate = bitrate;
+	this->fd = -1;
+}
+
+SerialInterface::~SerialInterface() {
+
+}
+
+int SerialInterface::open() {
+    // Convert bitrate to flag
+    tcflag_t bitrate = __bitrate_to_flag(this->bitrate);
+    if ( bitrate == 0 )
+    {
+        fprintf( stderr, "Invalid bitrate '%d' for serial device\n", this->bitrate );
+        return -1;
+    }
+
+
+    // Open serial device
+    fd = ::open( device, O_RDWR | O_NOCTTY );
+    if ( fd < 0 )
+    {
+        fprintf( stderr, "Failed to open serial device '%s' (errno: %s)\n", device, strerror(errno) );
+        return -1;
+    }
+    if (::ioctl(fd, TIOCEXCL))
+    {
+        fprintf( stderr, "Failed to lock serial device '%s' (errno: %s)\n", device, strerror(errno) );
+        return -1;
+    }
+
+
+
+    // Check if device is a terminal device
+    if ( !isatty( fd ) )
+    {
+        fprintf( stderr, "Device '%s' is not a terminal device (errno: %s)!\n", device, strerror(errno) );
+        ::close( fd );
+        return -1;
+    }
+
+    struct termios settings;
+    // Set input flags
+    settings.c_iflag =  IGNBRK          // Ignore BREAKS on Input
+                     |  IGNPAR;         // No Parity
+                                        // ICRNL: map CR to NL (otherwise a CR input on the other computer will not terminate input)
+
+    // Set output flags
+    settings.c_oflag = 0;				// Raw output
+
+    // Set controlflags
+    settings.c_cflag = bitrate
+                     | CS8              // 8 bits per byte
+                     | CSTOPB			// Stop bit
+                     | CREAD            // characters may be read
+                     | CLOCAL;          // ignore modem state, local connection
+
+    // Set local flags
+    settings.c_lflag = 0;				// Other option: ICANON = enable canonical input
+
+    // Set maximum wait time on input - cf. Linux Serial Programming HowTo, non-canonical mode
+    // http://tldp.org/HOWTO/Serial-Programming-HOWTO/x115.html
+    settings.c_cc[VTIME] = 10;			// 0 means timer is not uses
+
+    // Set minimum bytes to read
+    settings.c_cc[VMIN]  = 0;			// 1 means wait until at least 1 character is received
+
+    // Now clean the modem line and activate the settings for the port
+    tcflush( fd, TCIFLUSH );
+    tcsetattr( fd, TCSANOW, &settings );
+
+    connected = true;
+
+    return 0;
+}
+
+void SerialInterface::close()
+{
+    if(connected) ::close(fd);
+    connected = false;
+}
+
+int SerialInterface::readInternal( unsigned char *buf, unsigned int len)
+{
+    int res;
+
+    res = blockingReadAll( buf, len );
+    if ( res < 0 )
+    {
+        std::cerr << "Failed to read from serial device" << std::endl;
+    }
+
+    return res;
+
+}
+
+int SerialInterface::blockingReadAll(unsigned char *buf, unsigned int len)
+{
+    int dataToRead = len;
+    while (1)
+    {
+        int res = ::read( fd, buf, dataToRead );
+        if (res < 0)
+        {
+            return res;
+        }
+        dataToRead -= res;
+        buf += res;
+        if (dataToRead == 0)
+        {
+            return len;
+        }
+        if (dataToRead < 0)
+        {
+            throw new std::runtime_error("Internal error: dataToRead < 0");
+        }
+        usleep(1);
+    }
+}
+
+int SerialInterface::writeInternal( unsigned char *buf, unsigned int len)
+{
+    return( ::write( fd, (void *) buf, len ) );
+}
+
+
+std::string SerialInterface::toString() const
+{
+    return str(boost::format("SerialInterface(connected=%1%, device=%2%, bitrate=%3%, fd=%4%)")
+            % connected % device % bitrate % fd);
+}
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/SerialInterface.h b/source/RobotAPI/drivers/WeissHapticSensor/SerialInterface.h
new file mode 100644
index 0000000000000000000000000000000000000000..c64e501d085f0e752ec1ea8bd3f1e7bd018d3d01
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/SerialInterface.h
@@ -0,0 +1,30 @@
+#ifndef SERIALINTERFACE_H
+#define SERIALINTERFACE_H
+
+#include "AbstractInterface.h"
+#include <iostream>
+
+
+class SerialInterface : public AbstractInterface {
+public:
+    SerialInterface(const char *device, const unsigned int bitrate);
+    virtual ~SerialInterface();
+
+    virtual int open();
+    virtual void close();
+
+    virtual std::string toString() const;
+
+protected:
+    virtual int readInternal( unsigned char *, unsigned int );
+    virtual int writeInternal( unsigned char *, unsigned int );
+
+private:
+    const char *device;
+    unsigned int bitrate;
+    int fd;
+
+    int blockingReadAll(unsigned char *buf, unsigned int len);
+};
+
+#endif // SERIALINTERFACE_H
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/TactileSensor.cpp b/source/RobotAPI/drivers/WeissHapticSensor/TactileSensor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..b12d1163fbdccfca028d9b085e1a6194e0f6c78e
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/TactileSensor.cpp
@@ -0,0 +1,280 @@
+#include "TactileSensor.h"
+#include "AbstractInterface.h"
+#include "Response.h"
+#include "Types.h"
+#include <string>
+#include <stdio.h>
+
+TactileSensor::TactileSensor(boost::shared_ptr<AbstractInterface> interface)
+{
+    this->interface = interface;
+}
+
+TactileSensor::~TactileSensor() {
+    // TODO Auto-generated destructor stub
+}
+
+tac_matrix_info_t TactileSensor::getMatrixInformation( )
+{
+    unsigned char payload[0];
+
+    Response response = interface->submitCmd( 0x30, payload, sizeof(payload), false );
+    response.ensureMinLength(12);
+    response.ensureSuccess();
+
+    tac_matrix_info_t matrix_info;
+
+    matrix_info.res_x       = response.getShort(2);
+    matrix_info.res_y       = response.getShort(4);
+    matrix_info.cell_width  = response.getShort(6);
+    matrix_info.cell_height = response.getShort(8);
+    matrix_info.fullscale   = response.getShort(10);
+
+    //delete response;
+    return matrix_info;
+}
+
+void TactileSensor::printMatrixInfo( tac_matrix_info_t *mi )
+{
+    printf("res_x = %d, res_y = %d, cell_width = %d, cell_height = %d, fullscale = %X\n",
+        mi->res_x, mi->res_y, mi->cell_width, mi->cell_height, mi->fullscale);
+}
+
+void TactileSensor::printMatrix ( short *matrix, int width, int height )
+{
+    int x, y;
+    for (y = 0; y < height; y++)
+    {
+        printf("%03X", matrix[y * width]);
+        for (x = 1; x < width; x++)
+        {
+            printf(", %03X", matrix[y * width + x]);
+        }
+        printf("\n");
+    }
+}
+
+FrameData TactileSensor::readSingleFrame()
+{
+    unsigned char payload[1];
+    payload[0] = 0x00; // FLAGS = 0
+    Response response = interface->submitCmd( 0x20, payload, sizeof(payload), false );
+    return getFrameData(&response);
+}
+PeriodicFrameData TactileSensor::receicePeriodicFrame()
+{
+    Response response = interface->receiveWithoutChecks();
+    if(response.cmdId == 0x21)
+    {
+        response = interface->receiveWithoutChecks();
+    }
+    if(response.cmdId == 0x00)
+    {
+        return getPeriodicFrameData(&response);
+    }
+    else
+    {
+        throw TransmissionException(str(boost::format("Response ID (%02X) does not match submitted command ID (%02X)") % (int)response.cmdId % (int)0x00));
+    }
+}
+
+PeriodicFrameData TactileSensor::getPeriodicFrameData(Response *response)
+{
+    response->ensureMinLength(7);
+
+    unsigned int timestamp = response->getUInt( 0 );
+    int offset = 5;
+
+    int count = (response->len - offset) / 2;
+    int i;
+    boost::shared_ptr<std::vector<short> > data;
+    data.reset(new std::vector<short>(count, 0));
+    //short* data = new short[ count ];
+    for(i = 0; i < count; i++)
+    {
+        short value = response->getShort(i*2 + offset);
+        (*data)[i] = value;
+    }
+    return PeriodicFrameData(data, count, timestamp);
+}
+
+FrameData TactileSensor::getFrameData(Response *response)
+{
+    response->ensureMinLength(7);
+    response->ensureSuccess();
+
+    int offset = 5 + 2;
+
+    int count = (response->len - offset) / 2;
+    int i;
+    boost::shared_ptr<std::vector<short> > data;
+    data.reset(new std::vector<short>(count, 0));
+    for(i = 0; i < count; i++)
+    {
+        short value = response->getShort(i*2 + offset);
+        (*data)[i] = value;
+    }
+    return FrameData(data, count);
+}
+
+void TactileSensor::startPeriodicFrameAcquisition( unsigned short delay_ms )
+{
+    unsigned char payload[3];
+    payload[0] = 0x00; // FLAGS = 0
+    payload[1] = delay_ms & 0xFF;
+    payload[2] = (delay_ms >> 8) & 0xFF;
+    interface->fireAndForgetCmd( 0x21, payload, sizeof(payload), false );
+}
+void TactileSensor::stopPeriodicFrameAcquisition( void )
+{
+    while(1)
+    {
+        unsigned char payload[0];
+        interface->fireAndForgetCmd( 0x22, payload, sizeof(payload), false );
+        int waitCount = 10;
+        while (waitCount > 0)
+        {
+            Response response = interface->receiveWithoutChecks();
+            if(response.cmdId == 0x22) {
+                return;
+            }
+            else
+            {
+                cout <<  boost::format("stopPeriodicFrameAcquisition :: Discarding Response with ID 0x%02X") % (int)response.cmdId << endl;
+            }
+            waitCount--;
+        }
+    }
+}
+void TactileSensor::tareSensorMatrix( unsigned char operation )
+{
+    unsigned char payload[1];
+    payload[0] = operation; // OPERATION: 0 = un-tare the sensor matrix using the currently set threshold value, 1 = tare the sensor matrix
+    Response response = interface->submitCmd( 0x23, payload, sizeof(payload), false );
+    response.ensureMinLength(2);
+    response.ensureSuccess();
+}
+void TactileSensor::setAquisitionWindow( unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2 )
+{
+    unsigned char payload[4];
+    payload[0] = x1;
+    payload[1] = y1;
+    payload[2] = x2;
+    payload[3] = y2;
+    Response response = interface->submitCmd( 0x31, payload, sizeof(payload), false );
+    response.ensureMinLength(2);
+    response.ensureSuccess();
+}
+int TactileSensor::setAdvanvedAcquisitionMask( char *mask ) { return 0; }
+int TactileSensor::getAcquisitionMask( char **mask, int *mask_len ) { return 0; }
+void TactileSensor::setThreshold( short threshold )
+{
+    unsigned char payload[2];
+    payload[0] = threshold & 0xFF;
+    payload[1] = (threshold >> 8) & 0xFF;
+    Response response = interface->submitCmd( 0x34, payload, sizeof(payload), false );
+    response.ensureMinLength(2);
+    response.ensureSuccess();
+}
+unsigned short TactileSensor::getThreshold()
+{
+    unsigned char payload[0];
+    Response response = interface->submitCmd( 0x35, payload, sizeof(payload), false );
+    response.ensureMinLength(2);
+    response.ensureSuccess();
+    return response.getShort(2);
+}
+void TactileSensor::setFrontEndGain( unsigned char gain )
+{
+    /*
+     * Adjust the pressure sensitivity of a matrix by setting the gain of the Analog Front-End. The gain can
+     * be set using an integer value in the range of 0 to 255, where 0 is the most insensitive (lowest gain)
+     * and 255 is the most sensitive (highest gain) setting.
+     */
+    unsigned char payload[1];
+    payload[0] = gain;
+    Response response = interface->submitCmd( 0x36, payload, sizeof(payload), false );
+    response.ensureMinLength(2);
+    response.ensureSuccess();
+}
+unsigned char TactileSensor::getFrontEndGain()
+{
+    /*
+     * Get the currently set analog front-end gain value. The gain is as an integer value ranging from 0 to
+     * 255, where 0 is the most insensitive (lowest gain) and 255 is the most sensitive (highest gain) setting.
+     */
+    unsigned char payload[0];
+    Response response = interface->submitCmd( 0x37, payload, sizeof(payload), false );
+    response.ensureMinLength(3);
+    response.ensureSuccess();
+    unsigned char gain = response.getByte(2);
+    return gain;
+}
+std::string TactileSensor::getSensorType()
+{
+    /*
+     * Return a string containing the sensor type.
+     */
+    unsigned char payload[0];
+    Response response = interface->submitCmd( 0x38, payload, sizeof(payload), false );
+    response.ensureMinLength(2);
+    response.ensureSuccess();
+    std::string type = std::string((char*)response.data.data() + 2, response.len - 2);
+    return type;
+}
+float TactileSensor::readDeviceTemperature()
+{
+    unsigned char payload[0];
+    Response response = interface->submitCmd( 0x46, payload, sizeof(payload), false );
+    response.ensureMinLength(2);
+    short value = (short)response.getShort(2);
+    return value * 0.1f;
+}
+tac_system_information_t TactileSensor::getSystemInformation()
+{
+    unsigned char payload[0];
+    Response response = interface->submitCmd( 0x50, payload, sizeof(payload), false );
+    response.ensureMinLength(9);
+    response.ensureSuccess();
+    tac_system_information_t si;
+    si.type = response.getByte(2);
+    si.hw_rev = response.getByte(3);
+    si.fw_version = response.getShort(4);
+    si.sn = response.getShort(6);
+    return si;
+}
+void TactileSensor::printSystemInformation(tac_system_information_t si)
+{
+    int v1 = (si.fw_version & 0xF000) >> 12;
+    int v2 = (si.fw_version & 0x0F00) >> 8;
+    int v3 = (si.fw_version & 0x00F0) >> 4;
+    int v4 = (si.fw_version & 0x000F) >> 0;
+    cout << boost::format("System Type=%1%, Hardware Revision=%2%, Firmware Version=%3%.%4%.%5%.%6% (0x%7$04X), Serial Number=%8%")
+        % (int)si.type % (int)si.hw_rev % v1 % v2 % v3 % v4 % si.fw_version % si.sn << endl;
+}
+void TactileSensor::setDeviceTag(string tag)
+{
+    unsigned char *payload = (unsigned char*)tag.c_str();
+    Response response = interface->submitCmd( 0x51, payload, tag.length(), false );
+    response.ensureMinLength(2);
+    response.ensureSuccess();
+}
+string TactileSensor::getDeviceTag()
+{
+    unsigned char payload[0];
+    Response response = interface->submitCmd( 0x52, payload, sizeof(payload), false );
+    response.ensureMinLength(2);
+    response.ensureSuccess();
+    std::string type = std::string((char*)response.data.data() + 2, response.len - 2);
+    return type;
+}
+int TactileSensor::loop( char *data, int data_len ) { return 0; }
+
+string TactileSensor::getInterfaceInfo()
+{
+    return interface->toString();
+}
+
+ostream& operator<<(ostream &strm, const TactileSensor &a) {
+  return strm << a.interface;
+}
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/TactileSensor.h b/source/RobotAPI/drivers/WeissHapticSensor/TactileSensor.h
new file mode 100644
index 0000000000000000000000000000000000000000..393e4cf74d3004336f56e95d4e993eb59d27e3b3
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/TactileSensor.h
@@ -0,0 +1,103 @@
+#ifndef TACTILESENSOR_H
+#define TACTILESENSOR_H
+
+#include "AbstractInterface.h"
+using namespace std;
+#include <boost/shared_ptr.hpp>
+
+#define extract_short( array, index )				( (unsigned short)array[index] | ( (unsigned short)array[index + 1] << 8 ) )
+#define TAC_CHECK_RES( res, expected, resp )	{ \
+                                                    if ( res < expected ) { \
+                                                        dbgPrint( "Response length is too short, should be = %d (is %d)\n", expected, res ); \
+                                                        if ( res > 0 ) free( resp ); \
+                                                        return -1; \
+                                                    } \
+                                                    status_t status = cmd_get_response_status( resp ); \
+                                                    if ( status != E_SUCCESS ) \
+                                                    { \
+                                                        dbgPrint( "Command not successful: %s\n", status_to_str( status ) ); \
+                                                        free( resp ); \
+                                                        return -1; \
+                                                    } \
+                                                }
+
+typedef struct
+{
+    int res_x; // Horizontal matrix resolution. For non-rectangular matrices, this is the horizontal resolution of the surrounding rectangle measured in sensor cells.
+    int res_y; // Vertical matrix resolution. For non-rectangular matrices, this is the vertical resolution of the surrounding rectangle measured in sensor cells.
+    int cell_width; // Width of one sensor cell in 1/100 millimeters.
+    int cell_height; // Height of one sensor cell in 1/100 millimeters.
+    int fullscale; // Full scale value of the output signal.
+} tac_matrix_info_t;
+
+typedef struct
+{
+    unsigned char type; // System Type: 0 - unknown, 4 - WTS Tactile Sensor Module
+    unsigned char hw_rev; // Hardware Revision
+    unsigned short fw_version; // Firmware Version: D15...12: major version, D11...8: minor version 1, D7..4 minor version 2, D3..0: 0 for release version, 1..15 for release candidate versions
+    unsigned short sn; // Serial Number of the device
+} tac_system_information_t;
+
+struct FrameData
+{
+public:
+    FrameData(boost::shared_ptr<std::vector<short> > data, int count)
+        : data(data), count(count)
+    {}
+    boost::shared_ptr<std::vector<short> > data;
+    int count;
+};
+struct PeriodicFrameData
+{
+public:
+    PeriodicFrameData(boost::shared_ptr<std::vector<short> > data, int count, unsigned int timestamp)
+        : data(data), count(count), timestamp(timestamp)
+    {}
+    boost::shared_ptr<std::vector<short> > data;
+    int count;
+    unsigned int timestamp;
+};
+
+class TactileSensor {
+public:
+    TactileSensor(boost::shared_ptr<AbstractInterface> interface);
+    virtual ~TactileSensor();
+
+    tac_matrix_info_t getMatrixInformation(  );
+    static void printMatrixInfo( tac_matrix_info_t *mi );
+    FrameData readSingleFrame();
+    static void printMatrix ( short *matrix, int width, int height );
+
+    void startPeriodicFrameAcquisition( unsigned short delay_ms );
+    void stopPeriodicFrameAcquisition( void );
+    PeriodicFrameData receicePeriodicFrame();
+    void tareSensorMatrix( unsigned char operation );
+    void setAquisitionWindow( unsigned char x1, unsigned char y1, unsigned char x2, unsigned char y2 );
+    int setAdvanvedAcquisitionMask( char *mask );
+    int getAcquisitionMask( char **mask, int *mask_len );
+    void setThreshold( short threshold );
+    unsigned short getThreshold();
+    void setFrontEndGain( unsigned char gain );
+    unsigned char getFrontEndGain();
+    string getSensorType();
+    float readDeviceTemperature();
+    tac_system_information_t getSystemInformation();
+    static void printSystemInformation(tac_system_information_t si);
+    void setDeviceTag(string tag);
+    string getDeviceTag();
+    int loop( char *data, int data_len );
+
+    string getInterfaceInfo();
+
+private:
+    boost::shared_ptr<AbstractInterface> interface;
+    FrameData getFrameData(Response *response);
+    PeriodicFrameData getPeriodicFrameData(Response *response);
+
+private:
+    friend std::ostream& operator<<(std::ostream&, const TactileSensor&);
+};
+
+std::ostream& operator<<(std::ostream &strm, const TactileSensor &a);
+
+#endif // TACTILESENSOR_H
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/TextWriter.cpp b/source/RobotAPI/drivers/WeissHapticSensor/TextWriter.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..021bcc12ce7d98f9ef594a90d01bff701a4d152e
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/TextWriter.cpp
@@ -0,0 +1,20 @@
+#include "TextWriter.h"
+
+using namespace armarx;
+
+TextWriter::TextWriter(std::string filename)
+{
+    this->file.open(filename.c_str());
+}
+
+TextWriter::~TextWriter()
+{
+    file.close();
+}
+
+void TextWriter::writeLine(std::string message)
+{
+    file << message;
+    file << std::endl;
+    file.flush();
+}
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/TextWriter.h b/source/RobotAPI/drivers/WeissHapticSensor/TextWriter.h
new file mode 100644
index 0000000000000000000000000000000000000000..d0a87537ba549d958082b0849380b1863261ea74
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/TextWriter.h
@@ -0,0 +1,21 @@
+#ifndef TEXTWRITER_H
+#define TEXTWRITER_H
+
+#include <fstream>
+
+namespace armarx
+{
+    class TextWriter
+    {
+    public:
+        TextWriter(std::string filename);
+        ~TextWriter();
+
+        void writeLine(std::string message);
+
+    private:
+        std::ofstream file;
+    };
+}
+
+#endif
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/TransmissionException.h b/source/RobotAPI/drivers/WeissHapticSensor/TransmissionException.h
new file mode 100644
index 0000000000000000000000000000000000000000..93710f7ec2476bb1be56dfafc5757ab8277f4051
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/TransmissionException.h
@@ -0,0 +1,20 @@
+#ifndef TRANSMISSIONEXCEPTION_H
+#define TRANSMISSIONEXCEPTION_H
+
+#include <stdexcept>
+
+class TransmissionException : public std::runtime_error {
+public:
+    TransmissionException(std::string message)
+    : runtime_error(message)
+    { }
+};
+
+class ChecksumErrorException : public TransmissionException {
+public:
+    ChecksumErrorException(std::string message)
+    : TransmissionException(message)
+    { }
+};
+
+#endif // TRANSMISSIONEXCEPTION_H
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/Types.h b/source/RobotAPI/drivers/WeissHapticSensor/Types.h
new file mode 100644
index 0000000000000000000000000000000000000000..1fec7ca5f2535af8cea1197b7e87ca326d7d317e
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/Types.h
@@ -0,0 +1,51 @@
+#ifndef TYPES_H
+#define TYPES_H
+
+#include <vector>
+
+typedef struct
+{
+    unsigned char id;
+    unsigned int len;
+    std::vector<unsigned char> data;
+} msg_t;
+
+
+typedef enum {
+    E_SUCCESS = 0,              // No error
+    E_NOT_AVAILABLE,            // Device, service or data is not available
+    E_NO_SENSOR,                // No sensor connected
+    E_NOT_INITIALIZED,          // The device is not initialized
+    E_ALREADY_RUNNING,          // Service is already running
+    E_FEATURE_NOT_SUPPORTED,    // The asked feature is not supported
+    E_INCONSISTENT_DATA,        // One or more dependent parameters mismatch
+    E_TIMEOUT,                  // Timeout error
+    E_READ_ERROR,               // Error while reading from a device
+    E_WRITE_ERROR,              // Error while writing to a device
+    E_INSUFFICIENT_RESOURCES,   // No memory available
+    E_CHECKSUM_ERROR,           // Checksum error
+    E_NO_PARAM_EXPECTED,        // No parameters expected
+    E_NOT_ENOUGH_PARAMS,        // Not enough parameters
+    E_CMD_UNKNOWN,              // Unknown command
+    E_CMD_FORMAT_ERROR,         // Command format error
+    E_ACCESS_DENIED,            // Access denied
+    E_ALREADY_OPEN,             // The interface is already open
+    E_CMD_FAILED,               // Command failed
+    E_CMD_ABORTED,              // Command aborted
+    E_INVALID_HANDLE,           // invalid handle
+    E_NOT_FOUND,        		// device not found
+    E_NOT_OPEN,        			// device not open
+    E_IO_ERROR,                 // I/O error
+    E_INVALID_PARAMETER,        // invalid parameter
+    E_INDEX_OUT_OF_BOUNDS,      // index out of bounds
+    E_CMD_PENDING,              // Command was received correctly, but the execution needs more time. If the command was completely processed, another status message is returned indicating the command's result
+    E_OVERRUN,					// Data overrun
+    E_RANGE_ERROR,				// Range error
+    E_AXIS_BLOCKED,				// Axis is blocked
+    E_FILE_EXISTS				// File already exists
+} status_t;
+
+
+const char* status_to_str( status_t status );
+
+#endif // TYPES_H
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/WeissHapticSensor.cpp b/source/RobotAPI/drivers/WeissHapticSensor/WeissHapticSensor.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..f71bf414b5f4cac1bd917e57440aa2d7ab70db64
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/WeissHapticSensor.cpp
@@ -0,0 +1,137 @@
+#include "WeissHapticSensor.h"
+#include "TransmissionException.h"
+#include <boost/regex.hpp>
+#include <boost/format.hpp>
+
+using namespace armarx;
+
+WeissHapticSensor::WeissHapticSensor(std::string device)
+    : device(device), connected(false)
+{
+    sensorTask = new RunningTask<WeissHapticSensor>(this, &WeissHapticSensor::frameAcquisitionTaskLoop);
+    boost::smatch match;
+    boost::regex_search( device, match, boost::regex("\\w+$") );
+    this->deviceFileName = match[0];
+}
+
+void WeissHapticSensor::connect()
+{
+
+    //cout << "Open Serial" << endl;
+    this->interface.reset(new SerialInterface(device.c_str(), 115200));
+    interface->startLogging(deviceFileName + ".transmission.log");
+    interface->open();
+    //cout << *interface << endl;
+    this->sensor.reset(new TactileSensor(interface));
+
+    jsWriter.reset(new TextWriter(deviceFileName + ".js"));
+
+    //cout << "Stop Periodic Frame Acquisition" << endl;
+    sensor->stopPeriodicFrameAcquisition();
+
+    //string sensorType = sensor->getSensorType();
+    //cout << boost::format("Sensor Type = %1%") % sensorType << endl;
+
+    //tac_system_information_t si = sensor->getSystemInformation();
+    //TactileSensor::printSystemInformation(si);
+
+    //sensor->setDeviceTag("Test Tag");
+    this->tag = sensor->getDeviceTag();
+    //cout << boost::format("Sensor Tag = %1%") % tag << endl;
+
+    jsWriter->writeLine(str(boost::format("%s = {};") % deviceFileName));
+    jsWriter->writeLine(str(boost::format("sensors.push(%s);") % deviceFileName));
+    jsWriter->writeLine(str(boost::format("%s.device = \"%s\";") % deviceFileName % device));
+    jsWriter->writeLine(str(boost::format("%s.tag = \"%s\";") % deviceFileName % tag));
+    jsWriter->writeLine(str(boost::format("%s.data = [];") % deviceFileName));
+
+    //cout << "Tare Sensor Matrix" << endl;
+    //sensor->tareSensorMatrix(1);
+
+    //cout << "Get Matrix Information" << endl;
+    this->mi = sensor->getMatrixInformation();
+    TactileSensor::printMatrixInfo(&mi);
+
+    sensor->setAquisitionWindow(1, 1, mi.res_x, mi.res_y);
+
+    sensor->setFrontEndGain(255);
+
+    ARMARX_LOG << "Front end gain set to " << (int)sensor->getFrontEndGain();
+
+    connected = true;
+    cout << this << ": Connect done, Interface=" << sensor->getInterfaceInfo() << endl;
+}
+
+void WeissHapticSensor::disconnect()
+{
+    connected = false;
+    sensorTask->stop(true);
+}
+
+void WeissHapticSensor::setListenerPrx(HapticUnitListenerPrx listenerPrx)
+{
+    this->listenerPrx = listenerPrx;
+}
+
+void WeissHapticSensor::startSampling()
+{
+    cout << this << ": startSampling" << endl;
+    sensorTask->start();
+}
+
+void WeissHapticSensor::frameAcquisitionTaskLoop()
+{
+    cout << this << ": readAndReportSensorValues" << endl;
+    //bool periodic = false;
+    cout << sensor->getInterfaceInfo() << endl;
+
+    cout << this << ": startPeriodicFrameAcquisition" << endl;
+    sensor->startPeriodicFrameAcquisition(0);
+
+    while(!sensorTask->isStopped())
+    {
+        //ARMARX_INFO << deactivateSpam(1) << this << ": receicePeriodicFrame";
+
+        try
+        {
+            //long start = TimestampVariant::nowLong();
+            PeriodicFrameData data = sensor->receicePeriodicFrame();
+            //long end = TimestampVariant::nowLong();
+            //cout << end - start << endl;
+
+            MatrixFloatPtr matrix = new MatrixFloat(mi.res_y, mi.res_x);
+            for (int y = 0; y < mi.res_y; y++)
+            {
+                for (int x = 0; x < mi.res_x; x++)
+                {
+                    short val = (*data.data)[y * mi.res_x + x];
+                    (*matrix)(y, x) = val;
+                }
+            }
+
+            TimestampVariantPtr now = TimestampVariant::nowPtr();
+            writeMatrixToJs(matrix, now);
+            listenerPrx->reportSensorValues(device, tag, matrix, now);
+        }
+        catch(ChecksumErrorException)
+        {
+            ARMARX_WARNING << "Caught ChecksumErrorException on " << device << ", skipping frame";
+        }
+        //usleep(3000);
+
+        //usleep(3000);
+        //usleep(1000000);
+    }
+
+    cout << this << ": stopPeriodicFrameAcquisition" << endl;
+    sensor->stopPeriodicFrameAcquisition();
+}
+
+void WeissHapticSensor::writeMatrixToJs(MatrixFloatPtr matrix, TimestampVariantPtr timestamp)
+{
+    //std::cout << "writeMatrixToJs" << std::endl;
+    if(jsWriter != NULL)
+    {
+        jsWriter->writeLine(str(boost::format("%s.data.push([%i, %s]);") % deviceFileName % timestamp->getTimestamp() % matrix->toJsonRowMajor()));
+    }
+}
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/WeissHapticSensor.h b/source/RobotAPI/drivers/WeissHapticSensor/WeissHapticSensor.h
new file mode 100644
index 0000000000000000000000000000000000000000..acfaad733b767ab7242f0135761854145ae017c4
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/WeissHapticSensor.h
@@ -0,0 +1,43 @@
+#ifndef WEISSHAPTICSENSOR_H
+#define WEISSHAPTICSENSOR_H
+
+#include <Core/core/services/tasks/RunningTask.h>
+#include <Core/core/system/ImportExportComponent.h>
+#include "SerialInterface.h"
+#include "TactileSensor.h"
+#include <RobotAPI/interface/units/HapticUnit.h>
+#include <Core/util/variants/eigen3/VariantObjectFactories.h>
+#include "TextWriter.h"
+#include <Core/observers/variant/TimestampVariant.h>
+#include <Core/util/variants/eigen3/MatrixVariant.h>
+
+namespace armarx
+{
+    class WeissHapticSensor : public Logging
+    {
+    public:
+        WeissHapticSensor(std::string device);
+
+        void connect();
+        void disconnect();
+
+        void setListenerPrx(HapticUnitListenerPrx listenerPrx);
+        void startSampling();
+
+    private:
+        RunningTask<WeissHapticSensor>::pointer_type sensorTask;
+        boost::shared_ptr<TextWriter> jsWriter;
+        void frameAcquisitionTaskLoop();
+        std::string device;
+        std::string deviceFileName;
+        boost::shared_ptr<SerialInterface> interface;
+        boost::shared_ptr<TactileSensor> sensor;
+        bool connected;
+        string tag;
+        tac_matrix_info_t mi;
+        HapticUnitListenerPrx listenerPrx;
+        void writeMatrixToJs(MatrixFloatPtr matrix, TimestampVariantPtr timestamp);
+    };
+}
+
+#endif
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/WeissHapticSensorsUnit.cpp b/source/RobotAPI/drivers/WeissHapticSensor/WeissHapticSensorsUnit.cpp
new file mode 100755
index 0000000000000000000000000000000000000000..f71224bfed1f9e0b4bf6452eabe25f7cd3b55cd1
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/WeissHapticSensorsUnit.cpp
@@ -0,0 +1,154 @@
+/**
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    ArmarXCore::units
+ * @author     Peter Kaiser
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#include "WeissHapticSensorsUnit.h"
+
+#include <boost/regex.hpp>
+#include <boost/filesystem.hpp>
+
+
+using namespace armarx;
+
+void WeissHapticSensorsUnit::onInitHapticUnit()
+{
+    // Read pipe Id property
+    /*int firstPipeID = getProperty<int>("FirstPipeID").getValue();
+    if(firstPipeID < 0)
+    {
+        throw UserException("Value of FirstPipeID must be bigger than 0");
+    }*/
+
+    // Read pipe base name property
+   // std::string pipeBaseName = getProperty<std::string>("PipeBaseName").getValue();
+
+    // Read logfile property
+    //std::string logfile = getProperty<std::string>("Logfile").getValue();
+    
+    //std::vector<std::string> sensors;
+    //sensors.push_back("RightSole.HapticMatrix");
+    //hapticProtocol.defineSensorCategory(250, sensors);
+
+    // TODO: The task frequency needs to be adapted when the underlying sensor frequency changes
+    //sensorTask = new RunningTask<WeissHapticSensorsUnit>(this, &WeissHapticSensorsUnit::readAndReportSensorValues);
+
+    //remoteSystemReady = false;
+    /*
+    if(!hapticProtocol.openPipes(firstPipeID, firstPipeID + 1, false, true, pipeBaseName))
+    {
+        throw UserException("Failed to open pipes");
+    }
+
+    if(!hapticProtocol.openLogfile(logfile))
+    {
+        throw UserException("Failed to open logfile");
+    }*/
+
+    std::vector<std::string> devices = getDevices();
+    /*boost::shared_ptr<WeissHapticSensor> sensor(new WeissHapticSensor(devices.front()));
+    this->sensors.push_back(sensor);*/
+
+    for(std::vector<std::string>::iterator it = devices.begin(); it != devices.end(); ++it)
+    {
+        boost::shared_ptr<WeissHapticSensor> sensor(new WeissHapticSensor(*it));
+        this->sensors.push_back(sensor);
+    }
+
+    std::cout << "Connect Interfaces" << std::endl;
+
+    //sensors.front()->connect();
+
+    for(std::vector<boost::shared_ptr<WeissHapticSensor> >::iterator it = sensors.begin(); it != sensors.end(); ++it)
+    {
+        (*it)->connect();
+    }
+
+}
+
+std::vector< std::string > WeissHapticSensorsUnit::getDevices()
+{
+    const std::string target_path( "/dev/" );
+    const boost::regex my_filter( "ttyACM[0-9]+" );
+
+    std::vector< std::string > files;
+
+    boost::filesystem::directory_iterator end_itr; // Default ctor yields past-the-end
+    for( boost::filesystem::directory_iterator i( target_path ); i != end_itr; ++i )
+    {
+        // Skip if not a file
+        //if( !boost::filesystem::is_( i->status() ) ) continue;
+
+        boost::smatch what;
+
+        // Skip if no match
+        if( !boost::regex_match( i->path().filename().string(), what, my_filter ) ) continue;
+
+
+        // File matches, store it
+        files.push_back( "/dev/" + i->path().filename().string() );
+    }
+    std::sort(files.begin(), files.end());
+
+    std::cout << "Detected ACM-Interfaces: " << std::endl;
+    for(std::vector<std::string>::iterator it = files.begin(); it != files.end(); ++it)
+    {
+        std::cout << *it << std::endl;
+    }
+
+    return files;
+}
+
+void WeissHapticSensorsUnit::onStartHapticUnit()
+{
+
+    for(std::vector<boost::shared_ptr<WeissHapticSensor> >::iterator it = sensors.begin(); it != sensors.end(); ++it)
+    {
+        (*it)->setListenerPrx(listenerPrx);
+        (*it)->startSampling();
+    }
+
+}
+
+void WeissHapticSensorsUnit::onExitHapticUnit()
+{
+    for(std::vector<boost::shared_ptr<WeissHapticSensor> >::iterator it = sensors.begin(); it != sensors.end(); ++it)
+    {
+        (*it)->disconnect();
+    }
+}
+
+/*void WeissHapticSensorsUnit::onConnectComponent()
+{
+
+}*/
+
+void WeissHapticSensorsUnit::onDisconnectComponent()
+{
+
+}
+
+
+PropertyDefinitionsPtr WeissHapticSensorsUnit::createPropertyDefinitions()
+{
+    return PropertyDefinitionsPtr(new WeissHapticSensorsUnitPropertyDefinitions(getConfigIdentifier()));
+}
+
diff --git a/source/RobotAPI/drivers/WeissHapticSensor/WeissHapticSensorsUnit.h b/source/RobotAPI/drivers/WeissHapticSensor/WeissHapticSensorsUnit.h
new file mode 100755
index 0000000000000000000000000000000000000000..078bfe63905991b19026b71856470acf892e7d6a
--- /dev/null
+++ b/source/RobotAPI/drivers/WeissHapticSensor/WeissHapticSensorsUnit.h
@@ -0,0 +1,80 @@
+/**
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    Armar4::units
+ * @author     Peter Kaiser <peter dot kaiser at kit dot edu>
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#ifndef ARMAR4_FT_UNIT_ARMAR4_H
+#define ARMAR4_FT_UNIT_ARMAR4_H
+
+#include <RobotAPI/units/HapticUnit.h>
+//#include <Core/interface/units/
+#include <Core/core/system/ImportExportComponent.h>
+
+#include <string>
+#include "WeissHapticSensor.h"
+
+namespace armarx
+{
+    class WeissHapticSensorsUnitPropertyDefinitions : public HapticUnitPropertyDefinitions
+    {
+        public:
+            WeissHapticSensorsUnitPropertyDefinitions(std::string prefix):
+                HapticUnitPropertyDefinitions(prefix)
+            {
+                //defineOptionalProperty<int>("FirstPipeID", 1, "Id of the first Linux/Xenomai Pipe to use for data transfer");
+                //defineOptionalProperty<std::string>("PipeBaseName", "rtp", "Base name of pipe devices");
+                //defineOptionalProperty<std::string>("Logfile", "", "Logfile for logging pipe communication");
+            }
+    };
+    
+    class WeissHapticSensorsUnit : virtual public HapticUnit
+    {
+        public:
+            virtual std::string getDefaultName() { return "HapticUnitArmar4"; }
+            
+            virtual void onInitHapticUnit();
+            virtual void onStartHapticUnit();
+            virtual void onExitHapticUnit();
+            
+            //virtual void onConnectComponent();
+            virtual void onDisconnectComponent();
+            
+            virtual PropertyDefinitionsPtr createPropertyDefinitions();
+            
+        protected:
+            //void proceedSensorCategory(SensorCategoryDefinition<MatrixFloat> *category);
+
+            //std::map<std::string, MatrixFloatPtr> currentValues;
+            
+
+            
+            //HapticSensorProtocolMaster hapticProtocol;
+
+            //bool remoteSystemReady;
+
+    private:
+            std::vector< std::string > getDevices();
+
+            std::vector< boost::shared_ptr< WeissHapticSensor > > sensors;
+    };
+}
+    
+#endif
diff --git a/source/RobotAPI/motioncontrol/MotionControl.cpp b/source/RobotAPI/motioncontrol/MotionControl.cpp
index 4ef098ce9237ee53e5cd600967c2c2f636623f52..00de7039927261b0fda1950b738581ea1497e8ae 100644
--- a/source/RobotAPI/motioncontrol/MotionControl.cpp
+++ b/source/RobotAPI/motioncontrol/MotionControl.cpp
@@ -698,65 +698,219 @@ void MotionControl::StopRobot::onEnter()
     sendEvent<EvSuccess>();
 }
 
+void DoPreshapeSet::defineParameters()
+{
+    addToInput("useLeftHand", VariantType::Bool, false);
+    addToInput("preshapes", VariantType::List(VariantType::String), false);
+    addToLocal("counterRef", VariantType::ChannelRef);
+}
 
 
 
-void CloseHand::defineParameters()
+void DoPreshapeSet::defineSubstates()
+{
+    StatePtr preshape = addState<SelectAndDoPreshape>("SelectAndDoPreshape");
+    setInitState(preshape, PM::createMapping()->mapFromParent("*"));
+    StatePtr success = addState<SuccessState>("SuccessState");
+    StatePtr counter = addState<CounterStateTemplate<EvReachedIntermediatePreshape, EvReachedFinalPreshape> >("EvReachedIntermediatePreshape");
+
+    addTransition<EvSuccess>(preshape, counter, PM::createMapping()->mapFromParent("*"));
+    addTransition<EvReachedIntermediatePreshape>(counter, preshape, PM::createMapping()->mapFromParent("*"));
+    addTransition<EvReachedFinalPreshape>(counter, success, PM::createMapping()->mapFromParent("*"));
+
+}
+
+
+
+void DoPreshapeSet::onEnter()
+{
+
+    ChannelRefPtr counterRef = ChannelRefPtr::dynamicCast(getContext()->systemObserverPrx->startCounter(0,"preshapeCounter"));
+    setLocal("counterRef", counterRef);
+}
+
+void DoPreshapeSet::onExit()
+{
+    getContext()->systemObserverPrx->removeCounter(getLocal<ChannelRef>("counterRef"));
+}
+
+
+
+
+
+
+
+void SelectAndDoPreshape::defineParameters()
 {
     addToInput("useLeftHand", VariantType::Bool, false);
+    addToInput("preshapes", VariantType::List(VariantType::String), false);
+    addToInput("counterRef", VariantType::ChannelRef, false);
+
+    addToLocal("selectedPreshapeName", VariantType::String);
+
 }
 
+void SelectAndDoPreshape::defineSubstates()
+{
+    StatePtr doPreshape = addState<DoPreshape>("DoPreshape");
+    PMPtr mapping = PM::createMapping()->
+            mapFromParent("*")
+            ->mapFromParent("selectedPreshapeName", "preshapeName");
+    setInitState(doPreshape, mapping);
+    StatePtr success = addState<SuccessState >("SuccessState");
+    addTransition<EvSuccess>(doPreshape, success);
 
-void CloseHand::onEnter()
+}
+
+
+
+void SelectAndDoPreshape::onEnter()
+{
+    int index = getInput<ChannelRef>("counterRef")->getDataField("value")->getInt();
+    std::string preshapeName = getInput<SingleTypeVariantList>("preshapes")->getVariant(index)->getString();
+    setLocal("selectedPreshapeName", preshapeName);
+}
+
+void DoPreshape::defineParameters()
+{
+    addToInput("useLeftHand", VariantType::Bool, false);
+    addToInput("preshapeName", VariantType::String, false);
+}
+
+void DoPreshape::onEnter()
 {
     RobotStatechartContext* context = getContext<RobotStatechartContext>();
     bool useLeftHand = getInput<bool>("useLeftHand");
-    NameValueMap handConfig;
+    std::string handUnitName;
     if (useLeftHand)
     {
-        handConfig["left_hand_configuration_actual_float"] = 4;
+        handUnitName = "LeftHandUnit";
     }
     else
     {
-        handConfig["right_hand_configuration_actual_float"] = 4;
+        handUnitName = "RightHandUnit";
     }
 
-    context->kinematicUnitPrx->setJointAngles(handConfig);
+    std::map<std::string, HandUnitInterfacePrx>::iterator it = context->handUnits.find(handUnitName);
+    if(it != context->handUnits.end())
+    {
+//        NameValueMap jointValues =it->second->getPreshapeJointValues(getInput<std::string>("PreshapeName"));
+        it->second->preshape(getInput<std::string>("preshapeName"));
+        setTimeoutEvent(2000, createEvent<EvSuccess>());
+
+    }
+    else
+    {
+        ARMARX_ERROR  << "No Proxy for HandUnit with Name "  << handUnitName << " known";
+    }
 
-    sendEvent<EvSuccess>();
 }
 
+SingleTypeVariantList DoPreshape::GetPreshapeSet(const SingleTypeVariantListPtr preshapes, const std::string &preshapePrefix)
+{
+    SingleTypeVariantList result(VariantType::String);
+    for(int i=0; i < preshapes->getSize(); i++)
+    {
+        const std::string & preshapeName = preshapes->getVariant(i)->getString();
+        if(preshapeName.find(preshapePrefix) != std::string::npos)
+        {
+            result.addVariant(preshapeName);
+        }
+    }
+    return result;
+}
 
 
+void DoPrefixPreshapeSet::defineSubstates()
+{
+    StatePtr doPreshape = addState<DoPreshapeSet>("DoPreshapeSet");
+    PMPtr mapping = PM::createMapping()->
+            mapFromParent("*");
+    setInitState(doPreshape, mapping);
+    StatePtr success = addState<SuccessState>("SuccessState");
+    addTransition<EvSuccess>(doPreshape, success);
+}
 
-void OpenHand::defineParameters()
+void DoPrefixPreshapeSet::defineParameters()
 {
     addToInput("useLeftHand", VariantType::Bool, false);
+    addToInput("preshapePrefix", VariantType::String, false);
 }
 
 
-void OpenHand::onEnter()
+void DoPrefixPreshapeSet::onEnter()
 {
     RobotStatechartContext* context = getContext<RobotStatechartContext>();
+
     bool useLeftHand = getInput<bool>("useLeftHand");
-    NameValueMap handConfig;
+    std::string handUnitName;
     if (useLeftHand)
     {
-        handConfig["left_hand_configuration_actual_float"] = 1;
+        handUnitName = "LeftHandUnit";
     }
     else
     {
-        handConfig["right_hand_configuration_actual_float"] = 1;
+        handUnitName = "RightHandUnit";
     }
 
-    context->kinematicUnitPrx->setJointAngles(handConfig);
+    std::map<std::string, HandUnitInterfacePrx>::iterator it = context->handUnits.find(handUnitName);
+    if(it != context->handUnits.end())
+    {
+        setLocal("preshapes", DoPreshape::GetPreshapeSet(SingleTypeVariantListPtr::dynamicCast(it->second->getPreshapeNames()), getInput<std::string>("preshapePrefix")));
+    }
+    else
+    {
+        ARMARX_ERROR  << "No Proxy for HandUnit with Name "  << handUnitName << " known";
+    }
+}
 
-    sendEvent<EvSuccess>();
+
+
+
+
+void OpenHand::defineSubstates()
+{
+    StatePtr doPreshape = addState<DoPrefixPreshapeSet>("DoPreshapeSet");
+    PMPtr mapping = PM::createMapping()->
+            mapFromParent("*");
+    setInitState(doPreshape, mapping);
+    StatePtr success = addState<SuccessState>("SuccessState");
+    addTransition<EvSuccess>(doPreshape, success);
 }
 
+void OpenHand::defineParameters()
+{
+    addToInput("useLeftHand", VariantType::Bool, false);
+    addToLocal("preshapePrefix", VariantType::String);
+}
+
+void OpenHand::onEnter()
+{
+    setLocal("preshapePrefix", "Open");
+
+}
+
+void CloseHand::defineSubstates()
+{
+    StatePtr doPreshape = addState<DoPrefixPreshapeSet>("DoPreshapeSet");
+    PMPtr mapping = PM::createMapping()->
+            mapFromParent("*");
+    setInitState(doPreshape, mapping);
+    StatePtr success = addState<SuccessState>("SuccessState");
+    addTransition<EvSuccess>(doPreshape, success);
+}
 
+void CloseHand::defineParameters()
+{
+    addToInput("useLeftHand", VariantType::Bool, false);
+    addToLocal("preshapePrefix", VariantType::String);
+}
 
+void CloseHand::onEnter()
+{
+    setLocal("preshapePrefix", "Close");
 
+}
 
 void HeadLookAtTarget::defineParameters()
 {
@@ -866,3 +1020,6 @@ void CalculateHeadIK::run()
 }
 
 
+
+
+
diff --git a/source/RobotAPI/motioncontrol/MotionControl.h b/source/RobotAPI/motioncontrol/MotionControl.h
index 8132c75e7549a5c8421eff5e0760415df8937ef2..33e35cc5a1c688c89ff9903c32043ba073c0b1f7 100644
--- a/source/RobotAPI/motioncontrol/MotionControl.h
+++ b/source/RobotAPI/motioncontrol/MotionControl.h
@@ -224,15 +224,43 @@ namespace MotionControl
     };
 
 
-
-
     /**
     *  \ingroup MotionControl
     * Closes the given hand of the robot
     * \param useLeftHand True if left hand should be closed, false for the right hand
     */
-    struct CloseHand : StateTemplate<CloseHand>
+    struct DoPreshape : StateTemplate<DoPreshape>
+    {
+        void defineParameters();
+        void onEnter();
+        static SingleTypeVariantList GetPreshapeSet(const SingleTypeVariantListPtr preshapes, const std::string& preshapePrefix);
+    };
+
+    DEFINEEVENT(EvReachedIntermediatePreshape)
+    DEFINEEVENT(EvReachedFinalPreshape)
+    struct SelectAndDoPreshape : StateTemplate<SelectAndDoPreshape>
+    {
+        void defineSubstates();
+        void defineParameters();
+        void onEnter();
+    };
+
+    struct DoPreshapeSet : StateTemplate<DoPreshapeSet>
+    {
+        void defineSubstates();
+        void defineParameters();
+        void onEnter();
+        void onExit();
+
+    };
+
+
+
+
+
+    struct DoPrefixPreshapeSet : StateTemplate<DoPrefixPreshapeSet>
     {
+        void defineSubstates();
         void defineParameters();
         void onEnter();
     };
@@ -240,6 +268,7 @@ namespace MotionControl
 
 
 
+
     /**
     *  \ingroup MotionControl
     * Opens the given hand of the robot
@@ -247,6 +276,20 @@ namespace MotionControl
     */
     struct OpenHand : StateTemplate<OpenHand>
     {
+        void defineSubstates();
+        void defineParameters();
+        void onEnter();
+    };
+
+
+    /**
+    *  \ingroup MotionControl
+    * Closes the given hand of the robot
+    * \param useLeftHand True if left hand should be closed, false for the right hand
+    */
+    struct CloseHand : StateTemplate<CloseHand>
+    {
+        void defineSubstates();
         void defineParameters();
         void onEnter();
     };
diff --git a/source/RobotAPI/motioncontrol/ZeroForceControl.cpp b/source/RobotAPI/motioncontrol/ZeroForceControl.cpp
index f300daa87dc5631051d93c2f4a493b9754d9a946..2730d8056bca6899edc9dd9bc8b15a6b6ab97871 100644
--- a/source/RobotAPI/motioncontrol/ZeroForceControl.cpp
+++ b/source/RobotAPI/motioncontrol/ZeroForceControl.cpp
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/motioncontrol/ZeroForceControl.h b/source/RobotAPI/motioncontrol/ZeroForceControl.h
index c135c4d8bed284d148fb1d0740958f98420f1e91..ea9fd1414cbd178b415c25cd278e35322c9dd127 100644
--- a/source/RobotAPI/motioncontrol/ZeroForceControl.h
+++ b/source/RobotAPI/motioncontrol/ZeroForceControl.h
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/statecharts/GraspingWithTorques/GraspingWithTorques.cpp b/source/RobotAPI/statecharts/GraspingWithTorques/GraspingWithTorques.cpp
index 02ffd83b67625df55a4a7e03e4c5c61b62523642..bf645b464b934a1649d8659b22b6c3be45acdd96 100644
--- a/source/RobotAPI/statecharts/GraspingWithTorques/GraspingWithTorques.cpp
+++ b/source/RobotAPI/statecharts/GraspingWithTorques/GraspingWithTorques.cpp
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/statecharts/GraspingWithTorques/GraspingWithTorques.h b/source/RobotAPI/statecharts/GraspingWithTorques/GraspingWithTorques.h
index 984ca7781276a1a32bc3d09768188eab85122696..6d03db50ed60b5873931bfc4a1355585f028c6ad 100644
--- a/source/RobotAPI/statecharts/GraspingWithTorques/GraspingWithTorques.h
+++ b/source/RobotAPI/statecharts/GraspingWithTorques/GraspingWithTorques.h
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/statecharts/MovePlatform/MovePlatform.cpp b/source/RobotAPI/statecharts/MovePlatform/MovePlatform.cpp
index 970cf7f292e44a57cc0def318aaa6877077a04c7..51548f09b6f27dc4f66859b6b593049124f64b67 100644
--- a/source/RobotAPI/statecharts/MovePlatform/MovePlatform.cpp
+++ b/source/RobotAPI/statecharts/MovePlatform/MovePlatform.cpp
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
@@ -57,6 +57,7 @@ namespace armarx
         addToInput("positionalAccuracy", VariantType::Float, false);
         addToInput("orientationalAccuracy", VariantType::Float, false);
         addToInput("timeoutMoveTo", VariantType::Int, false);
+        addToInput("waitAfterLast", VariantType::Int, false);
         addToLocal("positionCounter", VariantType::ChannelRef);
     }
 
@@ -104,6 +105,7 @@ namespace armarx
         addToInput("positionalAccuracy", VariantType::Float, false);
         addToInput("orientationalAccuracy", VariantType::Float, false);
         addToInput("timeoutMoveTo", VariantType::Int, false);
+        addToInput("waitAfterLast", VariantType::Int, false);
         addToInput("positionCounter", VariantType::ChannelRef, false);
     }
 
@@ -128,7 +130,8 @@ namespace armarx
             condTargetReached = installCondition<EvTargetReached>(checkX && checkY && checkAngle);
             timeoutMoveToNext = setTimeoutEvent(getInput<int>("timeoutMoveTo"), createEvent<EvTimeoutTargetUnreachable>());
         } else {
-            sendEvent<EvNoTargets>();
+//            sendEvent<EvNoTargets>();
+            timeoutWaitAfterLast = setTimeoutEvent(getInput<int>("waitAfterLast"), createEvent<EvNoTargets>());
         }
 
         context->systemObserverPrx->incrementCounter(counter);
diff --git a/source/RobotAPI/statecharts/MovePlatform/MovePlatform.h b/source/RobotAPI/statecharts/MovePlatform/MovePlatform.h
index b3bb67b012e9514f7ee5c6d165e16a9cf27ba6c4..a8b104ece29790dead7da3c8b943175c3df456d1 100644
--- a/source/RobotAPI/statecharts/MovePlatform/MovePlatform.h
+++ b/source/RobotAPI/statecharts/MovePlatform/MovePlatform.h
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
@@ -75,6 +75,7 @@ namespace armarx
 
         ConditionIdentifier condTargetReached;
         StateUtility::ActionEventIdentifier timeoutMoveToNext;
+        StateUtility::ActionEventIdentifier timeoutWaitAfterLast;
     };
 
 }
diff --git a/source/RobotAPI/statecharts/MovePlatform/PlatformContext.h b/source/RobotAPI/statecharts/MovePlatform/PlatformContext.h
index f966ae28646e38dfc32293e0003cd60eab6ce61c..c1321c03e8c28177015bf3df4071373241d2c03e 100644
--- a/source/RobotAPI/statecharts/MovePlatform/PlatformContext.h
+++ b/source/RobotAPI/statecharts/MovePlatform/PlatformContext.h
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/statecharts/MovePlatformToLandmark/MovePlatformToLandmark.cpp b/source/RobotAPI/statecharts/MovePlatformToLandmark/MovePlatformToLandmark.cpp
index ddcd9e2a7c8040392c8ef5e6120e0e79a1b44897..9533b6d0f957e464b1ca0b1bbf1287ce7364219e 100644
--- a/source/RobotAPI/statecharts/MovePlatformToLandmark/MovePlatformToLandmark.cpp
+++ b/source/RobotAPI/statecharts/MovePlatformToLandmark/MovePlatformToLandmark.cpp
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
@@ -107,7 +107,7 @@ namespace armarx
 
     void StateCalculatePath::onEnter()
     {
-        ARMARX_LOG << eVERBOSE << "Entering StateCalculatePath";
+        ARMARX_LOG << eVERBOSE << "Entering StateCalculatePath" << flush;
 
         //read the nodes into a tag to node map
         SingleTypeVariantListPtr landmarkNodes = getInput<SingleTypeVariantList>("landmarkNodes");
@@ -120,6 +120,7 @@ namespace armarx
             nameToNodeMap[fv->frame] = n;
         }
 
+        ARMARX_LOG << eVERBOSE << "StateCalculatePath:onEnter(): Retrieving edges" << flush;
         //process the edges by adding the nodes to each other's successors
         SingleTypeVariantListPtr landmarkEdges = getInput<SingleTypeVariantList>("landmarkEdges");
         for (int i = 0; i < landmarkEdges->getSize(); i++) {
@@ -132,6 +133,7 @@ namespace armarx
             right->successors.push_back(left);
         }
 
+        ARMARX_LOG << eVERBOSE << "StateCalculatePath:onEnter(): Getting current pose from PlatformContext" << flush;
         PlatformContext* context = getContext<PlatformContext>();
         ChannelRefPtr poseRef = context->getChannelRef(context->getPlatformUnitObserverName(), "platformPose");
         const float platformPositionX = context->getDatafieldRef(poseRef, "positionX")->getDataField()->getFloat();
@@ -147,6 +149,7 @@ namespace armarx
             sendEvent<EvNoPathFound>();
             return;
         }
+        ARMARX_LOG << eVERBOSE << "StateCalculatePath:onEnter(): Finding closest point in graph" << flush;
         NodePtr closest = it->second;
         float minDist = getDist(platformPositionX, platformPositionY, closest->framedPos->x, closest->framedPos->y);
         it++;
@@ -158,7 +161,7 @@ namespace armarx
             }
         }
 
-        ARMARX_LOG << eVERBOSE << "Starting point: " << closest->name << flush;
+        ARMARX_LOG << eVERBOSE << "StateCalculatePath:onEnter(): Starting point: " << closest->name << flush;
 
         std::string landmark = getInput<std::string>("targetLandmark");
         NodePtr goal = nameToNodeMap[landmark];
@@ -167,6 +170,7 @@ namespace armarx
             sendEvent<EvNoPathFound>();
             return;
         }
+        ARMARX_LOG << eVERBOSE << "StateCalculatePath:onEnter(): Looking for path" << flush;
         std::list<NodePtr> path = aStar(closest, goal);
         if (path.empty()) {
             ARMARX_LOG << eWARN << "No path found" << flush;
@@ -207,10 +211,6 @@ namespace armarx
         cameFrom[goal] = start; //in case start==goal
 
         while (!openSet.empty()) {
-            for (size_t i = 0; i < openSet.size(); i++) {
-                std::cout << openSet[i] << ";";
-            }
-            std::cout << std::endl;
             float lowestScore = fScore[openSet[0]];
             std::vector<NodePtr>::iterator currentIT = openSet.begin();
             for (std::vector<NodePtr>::iterator it = openSet.begin() + 1; it != openSet.end(); it++) {
diff --git a/source/RobotAPI/statecharts/MovePlatformToLandmark/MovePlatformToLandmark.h b/source/RobotAPI/statecharts/MovePlatformToLandmark/MovePlatformToLandmark.h
index 4d0cae21270c6e552dcaee58d3c78d2c09cb4079..85b4a5d0d3fc4d4f67c74863f668919c8d5ddd6f 100644
--- a/source/RobotAPI/statecharts/MovePlatformToLandmark/MovePlatformToLandmark.h
+++ b/source/RobotAPI/statecharts/MovePlatformToLandmark/MovePlatformToLandmark.h
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/statecharts/OpenHand/OpenHand.cpp b/source/RobotAPI/statecharts/OpenHand/OpenHand.cpp
index 1baf286ef48decad27ad31f9499b5b58ac11faa7..74b3bdb8507ad92d5025a9721ba7aae8ae93f935 100644
--- a/source/RobotAPI/statecharts/OpenHand/OpenHand.cpp
+++ b/source/RobotAPI/statecharts/OpenHand/OpenHand.cpp
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/statecharts/OpenHand/OpenHand.h b/source/RobotAPI/statecharts/OpenHand/OpenHand.h
index 84af193269191fbad0d93fe75793c8139dd732d7..617c07165598c951f03025191739e022b6ea1e19 100644
--- a/source/RobotAPI/statecharts/OpenHand/OpenHand.h
+++ b/source/RobotAPI/statecharts/OpenHand/OpenHand.h
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/units/CMakeLists.txt b/source/RobotAPI/units/CMakeLists.txt
index 1ba7f2aa47fa199475e95e696017c9881baeffa3..0bc0993563f552e2dd27def2a175066064346bfe 100644
--- a/source/RobotAPI/units/CMakeLists.txt
+++ b/source/RobotAPI/units/CMakeLists.txt
@@ -13,16 +13,20 @@ if (Eigen3_FOUND AND Simox_FOUND)
         ${Simox_INCLUDE_DIRS})
 endif()
 
+message(status ${Eigen3_INCLUDE_DIR})
+
 set(LIB_NAME       RobotAPIUnits)
 set(LIB_VERSION    0.1.0)
 set(LIB_SOVERSION  0)
 
-set(LIBS RobotAPIInterfaces RobotAPICore ArmarXInterfaces ArmarXCore ArmarXCoreObservers ArmarXCoreRemoteRobot  ${Simox_LIBRARIES})
+set(LIBS RobotAPIInterfaces RobotAPICore ArmarXInterfaces ArmarXCore ArmarXCoreObservers ArmarXCoreEigen3Variants ArmarXCoreRemoteRobot  ${Simox_LIBRARIES})
 
 set(LIB_HEADERS
     ForceTorqueObserver.h
     ForceTorqueUnit.h
     HeadIKUnit.h
+    HapticUnit.h
+    HapticObserver.h
     TCPControlUnit.h
     TCPControlUnitObserver.h
 )
@@ -31,6 +35,8 @@ set(LIB_FILES
     ForceTorqueObserver.cpp
     ForceTorqueUnit.cpp
     HeadIKUnit.cpp
+    HapticUnit.cpp
+    HapticObserver.cpp
     TCPControlUnit.cpp
     TCPControlUnitObserver.cpp
 )
diff --git a/source/RobotAPI/units/ForceTorqueUnit.cpp b/source/RobotAPI/units/ForceTorqueUnit.cpp
index 021422634841857884b4063ec5cb61572c03f401..6f6c4fb808c4ee3269bb70ce322dc7a385331168 100644
--- a/source/RobotAPI/units/ForceTorqueUnit.cpp
+++ b/source/RobotAPI/units/ForceTorqueUnit.cpp
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/units/ForceTorqueUnit.h b/source/RobotAPI/units/ForceTorqueUnit.h
index 1b75f5e18310b3506875084aad2bfbdfc95ef985..ca4f4a83f0031775b4982639f8276d287f92cb5b 100644
--- a/source/RobotAPI/units/ForceTorqueUnit.h
+++ b/source/RobotAPI/units/ForceTorqueUnit.h
@@ -1,4 +1,4 @@
-/**
+/*
  * This file is part of ArmarX.
  *
  * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/units/HapticObserver.cpp b/source/RobotAPI/units/HapticObserver.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..033532980dbb7f79334d1196ebf2edf4d503bb24
--- /dev/null
+++ b/source/RobotAPI/units/HapticObserver.cpp
@@ -0,0 +1,111 @@
+#include "HapticObserver.h"
+
+#include <Core/observers/checks/ConditionCheckUpdated.h>
+#include <Core/observers/checks/ConditionCheckEquals.h>
+#include <Core/observers/checks/ConditionCheckInRange.h>
+#include <Core/observers/checks/ConditionCheckLarger.h>
+#include <Core/observers/checks/ConditionCheckSmaller.h>
+#include <Core/robotstate/remote/checks/ConditionCheckMagnitudeChecks.h>
+
+#include <Core/robotstate/remote/RobotStateObjectFactories.h>
+#include <Eigen/Dense>
+#include <Core/observers/variant/TimestampVariant.h>
+
+using namespace armarx;
+
+HapticObserver::HapticObserver()
+{
+    statisticsTask = new PeriodicTask<HapticObserver>(this, &HapticObserver::updateStatistics, 10, false);
+}
+
+void HapticObserver::setTopicName(std::string topicName)
+{
+    this->topicName = topicName;
+}
+
+void HapticObserver::onInitObserver()
+{
+    if(topicName.empty())
+        usingTopic(getProperty<std::string>("HapticTopicName").getValue());
+    else
+        usingTopic(topicName);
+
+    // register all checks
+    offerConditionCheck("updated", new ConditionCheckUpdated());
+    offerConditionCheck("larger", new ConditionCheckLarger());
+    offerConditionCheck("equals", new ConditionCheckEquals());
+    offerConditionCheck("smaller", new ConditionCheckSmaller());
+
+}
+
+void HapticObserver::onConnectObserver()
+{
+    statisticsTask->start();
+}
+
+void HapticObserver::onExitObserver()
+{
+    statisticsTask->stop();
+}
+
+void HapticObserver::reportSensorValues(const std::string& device, const std::string& name, const armarx::MatrixFloatBasePtr& values, const armarx::TimestampBasePtr& timestamp, const Ice::Current&)
+{
+    ScopedLock lock(dataMutex);
+    MatrixFloatPtr matrix = MatrixFloatPtr::dynamicCast(values);
+    TimestampVariantPtr timestampPtr = TimestampVariantPtr::dynamicCast(timestamp);
+    Eigen::MatrixXf eigenMatrix = matrix->toEigen();
+    float max = eigenMatrix.maxCoeff();
+    float mean = eigenMatrix.mean();
+    if(!existsChannel(device))
+    {
+        offerChannel(device, "Haptic data");
+        offerDataFieldWithDefault(device, "name", Variant(name), "Name of the tactile sensor");
+        offerDataFieldWithDefault(device, "matrix", matrix, "Raw tactile matrix data");
+        offerDataFieldWithDefault(device, "max", Variant(max), "Maximum value");
+        offerDataFieldWithDefault(device, "mean", Variant(mean), "Mean value");
+        offerDataFieldWithDefault(device, "timestamp", timestampPtr, "Timestamp");
+        offerDataFieldWithDefault(device, "rate", Variant(0.0f), "Sample rate");
+    }
+    else
+    {
+        setDataField(device, "matrix", matrix);
+        setDataField(device, "max", Variant(max));
+        setDataField(device, "mean", Variant(mean));
+        setDataField(device, "timestamp", timestampPtr);
+    }
+    /*if(statistics.count(device) > 0)
+    {
+        statistics.at(device).add(timestamp->timestamp);
+        HapticSampleStatistics stats = statistics.at(device);
+        long avg = stats.average();
+        float rate = avg == 0 ? 0 : 1000000.0f / (float)avg;
+        setDataField(device, "rate", Variant(rate));
+    }
+    else
+    {
+        statistics.insert(std::map<std::string,HapticSampleStatistics>::value_type(device, HapticSampleStatistics(100, timestamp->timestamp)));
+    }*/
+
+    updateChannel(device);
+}
+
+PropertyDefinitionsPtr HapticObserver::createPropertyDefinitions()
+{
+    return PropertyDefinitionsPtr(new HapticObserverPropertyDefinitions(getConfigIdentifier()));
+}
+
+void HapticObserver::updateStatistics()
+{
+    /*ScopedLock lock(dataMutex);
+    //ARMARX_LOG << "updateStatistics";
+    long now = TimestampVariant::nowLong();
+    for (std::map<std::string, HapticSampleStatistics>::iterator it = statistics.begin(); it != statistics.end(); ++it)
+    {
+        HapticSampleStatistics stats = it->second;
+        std::string device = it->first;
+        long avg = stats.average(now);
+        float rate = avg == 0 ? 0 : 1000000.0f / (float)avg;
+        setDataField(device, "rate", Variant(rate));
+        updateChannel(device);
+    }*/
+}
diff --git a/source/RobotAPI/units/HapticObserver.h b/source/RobotAPI/units/HapticObserver.h
new file mode 100644
index 0000000000000000000000000000000000000000..0ef0de0142ec4a8f2cfa35231d83ebde9e75a99d
--- /dev/null
+++ b/source/RobotAPI/units/HapticObserver.h
@@ -0,0 +1,136 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    ArmarXCore::units
+ * @author     Peter Kaiser <peter dot kaiser at kit dot edu>
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#ifndef _ARMARX_ROBOTAPI_HAPTIC_OBSERVER_H
+#define _ARMARX_ROBOTAPI_HAPTIC_OBSERVER_H
+
+#include <RobotAPI/interface/units/HapticUnit.h>
+#include <Core/observers/Observer.h>
+#include <Core/util/variants/eigen3/MatrixVariant.h>
+#include <Core/util/variants/eigen3/VariantObjectFactories.h>
+#include <Core/core/services/tasks/PeriodicTask.h>
+
+namespace armarx
+{
+
+    class HapticObserverPropertyDefinitions:
+            public ComponentPropertyDefinitions
+    {
+    public:
+        HapticObserverPropertyDefinitions(std::string prefix):
+            ComponentPropertyDefinitions(prefix)
+        {
+            defineRequiredProperty<std::string>("HapticTopicName", "Name of the HapticUnit Topic");
+        }
+    };
+
+    class HapticSampleStatistics
+    {
+    public:
+        HapticSampleStatistics(unsigned int count, long timestamp)
+        {
+            this->lastTimestamp = timestamp;
+            this->count = count;
+            this->pos = 0;
+        }
+
+        void add(long timestamp)
+        {
+            long delta = timestamp - lastTimestamp;
+            if(deltas.size() < count)
+            {
+                deltas.push_back(delta);
+            }
+            else
+            {
+                deltas.at(pos) = delta;
+                pos = (pos + 1) % count;
+            }
+            lastTimestamp = timestamp;
+        }
+
+        /*long average(long timestamp)
+        {
+            long sum = timestamp - lastTimestamp;
+            for(std::vector<long>::iterator it = deltas.begin(); it != deltas.end(); ++it)
+            {
+                sum += *it;
+            }
+            return sum / (deltas.size() + 1);
+        }*/
+
+        long average()
+        {
+            if(deltas.size() == 0)
+            {
+                return 0;
+            }
+            long sum = 0;
+            for(std::vector<long>::iterator it = deltas.begin(); it != deltas.end(); ++it)
+            {
+                sum += *it;
+            }
+            return sum / deltas.size();
+        }
+
+    private:
+        long lastTimestamp;
+        unsigned int count;
+        int pos;
+        std::vector<long> deltas;
+    };
+
+    class HapticObserver :
+            virtual public Observer,
+            virtual public HapticUnitObserverInterface
+    {
+    public:
+        HapticObserver();
+
+        void setTopicName(std::string topicName);
+
+        // framework hooks
+        virtual std::string getDefaultName() const { return "HapticUnitObserver"; }
+        void onInitObserver();
+        void onConnectObserver();
+        void onExitObserver();
+
+        void reportSensorValues(const ::std::string& device, const ::std::string& name, const ::armarx::MatrixFloatBasePtr& values, const ::armarx::TimestampBasePtr& timestamp, const ::Ice::Current& = ::Ice::Current());
+
+        /**
+         * @see PropertyUser::createPropertyDefinitions()
+         */
+        virtual PropertyDefinitionsPtr createPropertyDefinitions();
+    private:
+        armarx::Mutex dataMutex;
+        std::string topicName;
+        PeriodicTask<HapticObserver>::pointer_type statisticsTask;
+
+        void updateStatistics();
+
+        std::map<std::string, HapticSampleStatistics> statistics;
+
+    };
+}
+
+#endif
diff --git a/source/RobotAPI/units/HapticUnit.cpp b/source/RobotAPI/units/HapticUnit.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..cea65e6336bcf02052273894137db21c4e6df7c8
--- /dev/null
+++ b/source/RobotAPI/units/HapticUnit.cpp
@@ -0,0 +1,49 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    ArmarXCore::units
+ * @author     Peter Kaiser <peter dot kaiser at kit dot edu>
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#include "HapticUnit.h"
+
+using namespace armarx;
+
+void HapticUnit::onInitComponent()
+{
+    listenerName = "HapticValues";
+    offeringTopic(listenerName);
+    onInitHapticUnit();
+}
+
+void HapticUnit::onConnectComponent()
+{
+    listenerPrx = getTopic<HapticUnitListenerPrx>(listenerName);
+    onStartHapticUnit();
+}
+
+void HapticUnit::onExitComponent()
+{
+    onExitHapticUnit();
+}
+
+PropertyDefinitionsPtr HapticUnit::createPropertyDefinitions()
+{
+    return PropertyDefinitionsPtr(new HapticUnitPropertyDefinitions(getConfigIdentifier()));
+}
diff --git a/source/RobotAPI/units/HapticUnit.h b/source/RobotAPI/units/HapticUnit.h
new file mode 100644
index 0000000000000000000000000000000000000000..1d8c58283f066eb54a42673b6a90a1586b720d70
--- /dev/null
+++ b/source/RobotAPI/units/HapticUnit.h
@@ -0,0 +1,71 @@
+/*
+ * This file is part of ArmarX.
+ *
+ * ArmarX is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of
+ * the License, or (at your option) any later version.
+ *
+ * ArmarX 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 Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ *
+ * @package    ArmarXCore::units
+ * @author     Peter Kaiser <peter dot kaiser at kit dot edu>
+ * @date       2014
+ * @copyright  http://www.gnu.org/licenses/gpl.txt
+ *             GNU General Public License
+ */
+
+#ifndef _ARMARX_COMPONENT_HAPTIC_UNIT_H
+#define _ARMARX_COMPONENT_HAPTIC_UNIT_H
+
+#include <Core/core/Component.h>
+#include <Core/core/application/properties/Properties.h>
+#include <Core/core/system/ImportExportComponent.h>
+#include "Core/units/SensorActorUnit.h"
+
+#include <RobotAPI/interface/units/HapticUnit.h>
+
+#include <string>
+
+namespace armarx
+{
+    class HapticUnitPropertyDefinitions : public ComponentPropertyDefinitions
+    {
+        public:
+            HapticUnitPropertyDefinitions(std::string prefix) :
+                ComponentPropertyDefinitions(prefix)
+            {
+                // No required properties
+            }
+    };
+    
+    class HapticUnit :
+        virtual public HapticUnitInterface,
+        virtual public SensorActorUnit
+    {
+        public:
+            virtual std::string getDefaultName() const { return "HapticUnit"; }
+            
+            virtual void onInitComponent();
+            virtual void onConnectComponent();
+            virtual void onExitComponent();
+            
+            virtual void onInitHapticUnit() = 0;
+            virtual void onStartHapticUnit() = 0;
+            virtual void onExitHapticUnit() = 0;
+            
+            virtual PropertyDefinitionsPtr createPropertyDefinitions();
+            
+        protected:
+            HapticUnitListenerPrx listenerPrx;
+            std::string listenerName;
+    };
+}
+
+#endif
diff --git a/source/RobotAPI/units/HeadIKUnit.h b/source/RobotAPI/units/HeadIKUnit.h
index 3fff2ba7cb8ede33c93c9bf5e3d67d09c045d7a6..b9365e1e15b17aa0f289f2931e9826c9b71cb4fe 100644
--- a/source/RobotAPI/units/HeadIKUnit.h
+++ b/source/RobotAPI/units/HeadIKUnit.h
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/units/TCPControlUnit.cpp b/source/RobotAPI/units/TCPControlUnit.cpp
index 491ac01623e42682d1c5e3453e0e08bd91d20c28..7c49c31783e36d54fd4baeb8a5927601dcc21360 100644
--- a/source/RobotAPI/units/TCPControlUnit.cpp
+++ b/source/RobotAPI/units/TCPControlUnit.cpp
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
@@ -347,7 +347,7 @@ namespace armarx
             DIKMap::iterator itDIK = localDIKMap.find(data.nodeSetName);
             if(itDIK == localDIKMap.end())
                 localDIKMap[data.nodeSetName].reset(new EDifferentialIK(nodeSet, localRobot->getRootNode(), VirtualRobot::JacobiProvider::eSVDDamped));
-            EDifferentialIKPtr dIk = boost::shared_dynamic_cast<EDifferentialIK>(localDIKMap[data.nodeSetName]);
+            EDifferentialIKPtr dIk = boost::dynamic_pointer_cast<EDifferentialIK>(localDIKMap[data.nodeSetName]);
             dIk->clearGoals();
         }
 
@@ -433,7 +433,7 @@ namespace armarx
         for(; itDIK != localDIKMap.end(); itDIK++)
         {
             RobotNodeSetPtr robotNodeSet = localRobot->getRobotNodeSet(itDIK->first);
-            EDifferentialIKPtr dIK = boost::shared_dynamic_cast<EDifferentialIK>(itDIK->second);
+            EDifferentialIKPtr dIK = boost::dynamic_pointer_cast<EDifferentialIK>(itDIK->second);
 //            ARMARX_VERBOSE << deactivateSpam(1) << "Old Pos: \n" << robotNodeSet->getTCP()->getGlobalPose();
 //            dIK->setVerbose(true);
             Eigen::VectorXf jointDelta;
diff --git a/source/RobotAPI/units/TCPControlUnit.h b/source/RobotAPI/units/TCPControlUnit.h
index d586d1354005362930f329251de1e5d65bb5eb31..be466c92686f8f0933a8c3906b9f46026d2d6fe7 100644
--- a/source/RobotAPI/units/TCPControlUnit.h
+++ b/source/RobotAPI/units/TCPControlUnit.h
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify
diff --git a/source/RobotAPI/units/TCPControlUnitObserver.cpp b/source/RobotAPI/units/TCPControlUnitObserver.cpp
index 61e3edc04486a70195cbe943ae1280af3b4f5fbf..3514c6b9b0528b73e7cac2497810a60abe7c0e4b 100644
--- a/source/RobotAPI/units/TCPControlUnitObserver.cpp
+++ b/source/RobotAPI/units/TCPControlUnitObserver.cpp
@@ -1,4 +1,4 @@
-/**
+/*
 * This file is part of ArmarX.
 *
 * ArmarX is free software; you can redistribute it and/or modify