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}}">●</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}}">●</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}}">●</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}}">●</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