/* Adept MobileRobots Robotics Interface for Applications (ARIA) Copyright (C) 2004, 2005 ActivMedia Robotics LLC Copyright (C) 2006, 2007, 2008, 2009, 2010 MobileRobots Inc. Copyright (C) 2011, 2012, 2013 Adept Technology 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA If you wish to redistribute ARIA under different terms, contact Adept MobileRobots for information about a commercial version of ARIA at robots@mobilerobots.com or Adept MobileRobots, 10 Columbia Drive, Amherst, NH 03031; +1-603-881-7960 */ #include "ArExport.h" #include "ariaOSDef.h" #include "ArLaser.h" #include "ArRobot.h" #include "ArDeviceConnection.h" bool ArLaser::ourUseSimpleNaming = false; AREXPORT ArLaser::ArLaser( int laserNumber, const char *name, unsigned int absoluteMaxRange, bool locationDependent, bool appendLaserNumberToName) : ArRangeDeviceThreaded( 361, 200, name, absoluteMaxRange, 0, 0, 0, locationDependent) { myLaserNumber = laserNumber; if (appendLaserNumberToName) { char buf[1024]; snprintf(buf, sizeof(buf) - 20, "%s", name); sprintf(buf, "%s_%d", buf, myLaserNumber); myName = buf; } else { if (laserNumber != 1) ArLog::log(ArLog::Verbose, "ArLaser::%s: Laser created with number %d, but the number is not appended to the name which may break things (especially since this number is greater than 1_", name, laserNumber); myName = name; } laserSetName(myName.c_str()); myAbsoluteMaxRange = absoluteMaxRange; myMaxRangeSet = false; setSensorPosition(0, 0, 0, 0); myTimeoutSeconds = 8; myHaveSensorPose = false; myFlipped = false; myFlippedSet = false; myCanSetDegrees = false; myStartDegreesMin = HUGE_VAL; myStartDegreesMax = -HUGE_VAL; myStartDegreesSet = false; myStartDegrees = 0; myEndDegreesMin = HUGE_VAL; myEndDegreesMax = -HUGE_VAL; myEndDegreesSet = false; myEndDegrees = 0; myCanChooseDegrees = false; myDegreesChoiceDouble = -HUGE_VAL; myCanSetIncrement = 0; myIncrementMin = HUGE_VAL; myIncrementMax = -HUGE_VAL; myIncrementSet = false; myIncrement = 0; myCanChooseIncrement = false; myIncrementChoiceDouble = -HUGE_VAL; myCanChooseUnits = false; myCanChooseReflectorBits = false; myCanSetPowerControlled = false; myPowerControlled = true; myPowerControlledSet = false; myCanChooseStartingBaud = false; myCanChooseAutoBaud = false; myDefaultTcpPort = 8102; myInfoLogLevel = ArLog::Verbose; myRobotRunningAndConnected = false; } AREXPORT ArLaser::~ArLaser() { } /** This can be used to set the name on mutexes and such to match the laser's new name. **/ AREXPORT void ArLaser::laserSetName(const char *name) { if (ourUseSimpleNaming) { myName = "Laser_"; char buf[1024]; sprintf(buf, "%d", myLaserNumber); myName += buf; } else { myName = name; } myTask.setThreadName(myName.c_str()); myConnectCBList.setNameVar("%s::myConnectCBList", myName.c_str()); myFailedConnectCBList.setNameVar("%s::myFailedConnectCBList", myName.c_str()); myDisconnectOnErrorCBList.setNameVar( "%s::myDisconnectOnErrorCBList", myName.c_str()); myDisconnectNormallyCBList.setNameVar( "%s::myDisconnectNormallyCBList", myName.c_str()); myDataCBList.setNameVar("%s::myDataCBList", myName.c_str()); //myDataCBList.setLogging(false); // supress debug logging since it drowns out all other logging } AREXPORT void ArLaser::setMaxRange(unsigned int maxRange) { if (maxRange > myAbsoluteMaxRange) { ArLog::log(ArLog::Terse, "%s::setMaxRange: Tried to set the max range to %u which is above the absoluteMaxRange on the device of %d, capping it", getName(), maxRange, getAbsoluteMaxRange()); ArRangeDevice::setMaxRange(myAbsoluteMaxRange); } else ArRangeDevice::setMaxRange(maxRange); myMaxRangeSet = true; } AREXPORT void ArLaser::setCumulativeBufferSize(size_t size) { ArRangeDevice::setCumulativeBufferSize(size); myCumulativeBufferSizeSet = true; } AREXPORT void ArLaser::laserSetAbsoluteMaxRange(unsigned int absoluteMaxRange) { ArLog::log(myInfoLogLevel, "%s: Setting absolute max range to %u", getName(), absoluteMaxRange); myAbsoluteMaxRange = absoluteMaxRange; setMaxRange(getMaxRange()); } /** Filter readings, moving them from the raw current buffer to filtered current buffer (see ArRangeDevice), and then also to the cumulative buffer. This must be called for the laser subclass to work right. This also calls the reading callbacks. **/ void ArLaser::laserProcessReadings(void) { // if we have no readings... don't do anything if (myRawReadings == NULL || myRawReadings->begin() == myRawReadings->end()) return; std::list::iterator sensIt; ArSensorReading *sReading; double x, y; double lastX = 0.0, lastY = 0.0; //unsigned int i = 0; ArTime len; len.setToNow(); bool clean; if (myCumulativeCleanInterval <= 0 || (myCumulativeLastClean.mSecSince() > myCumulativeCleanInterval)) { myCumulativeLastClean.setToNow(); clean = true; } else { clean = false; } myCurrentBuffer.setPoseTaken(myRawReadings->front()->getPoseTaken()); myCurrentBuffer.setEncoderPoseTaken( myRawReadings->front()->getEncoderPoseTaken()); myCurrentBuffer.beginRedoBuffer(); // walk the buffer of all the readings and see if we want to add them for (sensIt = myRawReadings->begin(); sensIt != myRawReadings->end(); ++sensIt) { sReading = (*sensIt); // if we have ignore readings then check them here if (!myIgnoreReadings.empty() && (myIgnoreReadings.find( (int) ceil(sReading->getSensorTh())) != myIgnoreReadings.end()) || myIgnoreReadings.find( (int) floor(sReading->getSensorTh())) != myIgnoreReadings.end()) sReading->setIgnoreThisReading(true); // see if the reading is valid if (sReading->getIgnoreThisReading()) continue; // if we have a max range then check it here... if (myMaxRange != 0 && sReading->getRange() > myMaxRange) { sReading->setIgnoreThisReading(true); } // see if the reading is valid... this is set up this way so that // max range readings can cancel out other readings, but will // still be ignored other than that... ones ignored for other // reasons were skipped above if (sReading->getIgnoreThisReading()) { internalProcessReading(sReading->getX(), sReading->getY(), sReading->getRange(), clean, true); continue; } // get our coords x = sReading->getX(); y = sReading->getY(); // see if we're checking on the filter near dist... if we are // and the reading is a good one we'll check the cumulative // buffer if (myMinDistBetweenCurrentSquared > 0.0000001) { // see where the last reading was //squaredDist = (x-lastX)*(x-lastX) + (y-lastY)*(y-lastY); // see if the reading is far enough from the last reading if (ArMath::squaredDistanceBetween(x, y, lastX, lastY) > myMinDistBetweenCurrentSquared) { lastX = x; lastY = y; // since it was a good reading, see if we should toss it in // the cumulative buffer... internalProcessReading(x, y, sReading->getRange(), clean, false); /* we don't do this part anymore since it wound up leaving // too many things not really tehre... if its outside of our // sensor angle to use to filter then don't let this one // clean (ArMath::fabs(sReading->getSensorTh()) > 50) // filterAddAndCleanCumulative(x, y, false); else*/ } // it wasn't far enough, skip this one and go to the next one else { continue; } } // we weren't filtering the readings, but see if it goes in the // cumulative buffer anyways else { internalProcessReading(x, y, sReading->getRange(), clean, false); } // now drop the reading into the current buffer myCurrentBuffer.redoReading(x, y); //i++; } myCurrentBuffer.endRedoBuffer(); /* Put this in to see how long the cumulative filtering is taking if (clean) printf("### %ld %d\n", len.mSecSince(), myCumulativeBuffer.getBuffer()->size()); */ internalGotReading(); } void ArLaser::internalProcessReading(double x, double y, unsigned int range, bool clean, bool onlyClean) { if (myCumulativeBuffer.getSize() == 0) return; // make sure we really want to clean if (clean && myCumulativeCleanDistSquared < 1) clean = false; std::list::iterator cit; bool addReading = true; //double squaredDist; ArLineSegment line; double xTaken = myCurrentBuffer.getPoseTaken().getX(); double yTaken = myCurrentBuffer.getPoseTaken().getY(); ArPose intersection; ArPoseWithTime reading(x, y); // if we're not cleaning and its further than we're keeping track of // readings ignore it... replaced with the part thats 'until here' /* if (!clean && myMaxInsertDistCumulative > 1 && range > myMaxInsertDistCumulative) return; */ if (onlyClean) addReading = false; if (myMaxInsertDistCumulative > 1 && range > myMaxInsertDistCumulative) addReading = false; if (!clean && !addReading) return; // until here // if we're cleaning we start our sweep if (clean) myCumulativeBuffer.beginInvalidationSweep(); // run through all the readings for (cit = getCumulativeBuffer()->begin(); cit != getCumulativeBuffer()->end(); ++cit) { // if its closer to a reading than the filter near dist, just return if (addReading && myMinDistBetweenCumulativeSquared < .0000001 || (ArMath::squaredDistanceBetween(x, y, (*cit)->getX(), (*cit)->getY()) < myMinDistBetweenCumulativeSquared)) { // if we're not cleaning it and its too close just return, // otherwise keep going (to clear out invalid readings) if (!clean) return; addReading = false; } // see if this reading invalidates some other readings by coming too close if (clean) { // set up our line line.newEndPoints(x, y, xTaken, yTaken); // see if the cumulative buffer reading perpindicular intersects // this line segment, and then see if its too close if it does, // but if the intersection is very near the endpoint then leave it if (line.getPerpPoint((*cit), &intersection) && (intersection.squaredFindDistanceTo(*(*cit)) < myCumulativeCleanDistSquared) && (intersection.squaredFindDistanceTo(reading) > 50 * 50)) { //printf("Found one too close to the line\n"); myCumulativeBuffer.invalidateReading(cit); } } } // if we're cleaning finish the sweep if (clean) myCumulativeBuffer.endInvalidationSweep(); // toss the reading in if (addReading) myCumulativeBuffer.addReading(x, y); } AREXPORT bool ArLaser::laserPullUnsetParamsFromRobot(void) { if (myRobot == NULL) { ArLog::log(ArLog::Normal, "%s: Trying to connect, but have no robot, continuing under the assumption this is intentional", getName()); return true; } const ArRobotParams *params = myRobot->getRobotParams(); if (params == NULL) { ArLog::log(ArLog::Terse, "%s: Robot has no params, cannot pull unset params from robot", getName()); return false; } const char *paramStr; char *endPtr; double paramDouble; int paramInt; bool paramBool; paramBool = params->getLaserFlipped(getLaserNumber()); if (!myFlippedSet) { if (paramBool) { ArLog::log(myInfoLogLevel, "%s: Setting flipped to true from robot params", getName()); setFlipped(true); } else if (!paramBool) { ArLog::log(myInfoLogLevel, "%s: Setting flipped to false from robot params", getName()); setFlipped(false); } } paramInt = params->getLaserMaxRange(getLaserNumber()); if (!myMaxRangeSet) { if(paramInt < 0) { ArLog::log(ArLog::Terse, "%s: LaserMaxRange in robot param file was negative but shouldn't be (it was '%d'), failing", getName(), paramInt); return false; } if (paramInt > 0) { ArLog::log(myInfoLogLevel, "%s: Setting max range to %d from robot params", getName(), paramInt); setMaxRange(paramInt); } } paramInt = params->getLaserCumulativeBufferSize(getLaserNumber()); if (!myCumulativeBufferSizeSet) { if(paramInt < 0) { ArLog::log(ArLog::Terse, "%s: LaserCumulativeBufferSize in robot param file was negative but shouldn't be (it was '%d'), failing", getName(), paramInt); return false; } if (paramInt > 0) { ArLog::log(myInfoLogLevel, "%s: Setting cumulative buffer size to %d from robot params", getName(), paramInt); setCumulativeBufferSize(paramInt); } } paramStr = params->getLaserStartDegrees(getLaserNumber()); if (canSetDegrees() && !myStartDegreesSet && paramStr != NULL && paramStr[0] != '\0') { paramDouble = strtod(paramStr, &endPtr); if(endPtr == paramStr) { ArLog::log(ArLog::Terse, "%s: LaserStartDegrees in robot param file was not a double (it was '%s'), failing", getName(), paramStr); return false; } ArLog::log(myInfoLogLevel, "%s: Setting start degrees to %g from robot params", getName(), paramDouble); setStartDegrees(paramDouble); } paramStr = params->getLaserEndDegrees(getLaserNumber()); if (canSetDegrees() && !myEndDegreesSet && paramStr != NULL && paramStr[0] != '\0') { paramDouble = strtod(paramStr, &endPtr); if(endPtr == paramStr) { ArLog::log(ArLog::Terse, "%s: LaserEndDegrees in robot param file was not a double (it was '%s'), failing", getName(), paramStr); return false; } ArLog::log(myInfoLogLevel, "%s: Setting end degrees to %g from robot params", getName(), paramDouble); setEndDegrees(paramDouble); } paramStr = params->getLaserDegreesChoice(getLaserNumber()); if (canChooseDegrees() && !myDegreesChoiceSet && paramStr != NULL && paramStr[0] != '\0') { ArLog::log(myInfoLogLevel, "%s: Setting degrees choice to %s from robot params", getName(), paramStr); chooseDegrees(paramStr); } paramStr = params->getLaserIncrement(getLaserNumber()); if (canSetDegrees() && !myIncrementSet && paramStr != NULL && paramStr[0] != '\0') { paramDouble = strtod(paramStr, &endPtr); if(endPtr == paramStr) { ArLog::log(ArLog::Terse, "%s: LaserIncrement in robot param file was not a double (it was '%s'), failing", getName(), paramStr); return false; } ArLog::log(myInfoLogLevel, "%s: Setting increment to %g from robot params", getName(), paramDouble); setIncrement(paramDouble); } paramStr = params->getLaserIncrementChoice(getLaserNumber()); if (canChooseIncrement() && !myIncrementChoiceSet && paramStr != NULL && paramStr[0] != '\0') { ArLog::log(myInfoLogLevel, "%s: Setting increment choice to %s from robot params", getName(), paramStr); chooseIncrement(paramStr); } paramStr = params->getLaserUnitsChoice(getLaserNumber()); if (canChooseUnits() && !myUnitsChoiceSet && paramStr != NULL && paramStr[0] != '\0') { ArLog::log(myInfoLogLevel, "%s: Setting units choice to %s from robot params", getName(), paramStr); chooseUnits(paramStr); } paramStr = params->getLaserReflectorBitsChoice(getLaserNumber()); if (canChooseReflectorBits() && !myReflectorBitsChoiceSet && paramStr != NULL && paramStr[0] != '\0') { ArLog::log(myInfoLogLevel, "%s: Setting reflectorBits choice to %s from robot params", getName(), paramStr); chooseReflectorBits(paramStr); } paramBool = params->getLaserPowerControlled(getLaserNumber()); if (canSetPowerControlled() && !myPowerControlledSet) { if (paramBool) { ArLog::log(myInfoLogLevel, "%s: Setting powerControlled to true from robot params", getName()); setPowerControlled(true); } else if (!paramBool) { ArLog::log(myInfoLogLevel, "%s: Setting powerControlled to false from robot params", getName()); setPowerControlled(false); } } paramStr = params->getLaserStartingBaudChoice(getLaserNumber()); if (canChooseStartingBaud() && !myStartingBaudChoiceSet && paramStr != NULL && paramStr[0] != '\0') { ArLog::log(myInfoLogLevel, "%s: Setting startingBaud choice to %s from robot params", getName(), paramStr); chooseStartingBaud(paramStr); } paramStr = params->getLaserAutoBaudChoice(getLaserNumber()); if (canChooseAutoBaud() && !myAutoBaudChoiceSet && paramStr != NULL && paramStr[0] != '\0') { ArLog::log(myInfoLogLevel, "%s: Setting autoBaud choice to %s from robot params", getName(), paramStr); chooseAutoBaud(paramStr); } if (!addIgnoreReadings(params->getLaserIgnore(getLaserNumber()))) return false; setSensorPosition(params->getLaserX(getLaserNumber()), params->getLaserY(getLaserNumber()), params->getLaserTh(getLaserNumber()), params->getLaserZ(getLaserNumber())); return true; } AREXPORT void ArLaser::setDeviceConnection(ArDeviceConnection *conn) { myConnMutex.lock(); myConn = conn; myConn->setDeviceName(getName()); myConnMutex.unlock(); } AREXPORT ArDeviceConnection *ArLaser::getDeviceConnection(void) { return myConn; } /** Sets the time to go without a response from the laser until it is assumed that the connection with the laser has been broken and the disconnect on error events will happen. If there is no robot then there is a straightforward check of last reading time against this value. If there is a robot then it will not start the check until the laser is running and connected. @param seconds if 0 or less then the connection timeout feature will be disabled, otherwise disconnect on error will be triggered after this number of miliseconds... **/ AREXPORT void ArLaser::setConnectionTimeoutSeconds(double seconds) { ArLog::log(ArLog::Normal, "%s::setConnectionTimeoutSeconds: Setting timeout to %g secs", getName(), seconds); myLastReading.setToNow(); if (seconds > 0) myTimeoutSeconds = seconds; else myTimeoutSeconds = 0; } /** Gets the time (miliseconds) to go without response from the laser until it is assumed that the connection with the laser has been broken and the disconnect on error events will happen. If 0, then the timeout is disabled. If there is no robot then there is a straightforward check of last reading time against this value. If there is a robot then it will not start the check until the laser is running and connected. **/ double ArLaser::getConnectionTimeoutSeconds(void) { return myTimeoutSeconds; } AREXPORT void ArLaser::laserConnect(void) { // figure out how many readings we can have and set the current // buffer size to that double degrees; myLastReading.setToNow(); if (canSetDegrees()) { //degrees = fabs(ArMath::subAngle(getStartDegrees(), getEndDegrees())); degrees = fabs(getStartDegrees() - getEndDegrees()); ArLog::log(myInfoLogLevel, "%s: Using degrees settings of %g to %g for %g degrees", getName(), getStartDegrees(), getEndDegrees(), degrees); } else if (canChooseDegrees()) { degrees = getDegreesChoiceDouble(); ArLog::log(myInfoLogLevel, "%s: Using choice of %g degrees", getName(), degrees); } else { degrees = 360; ArLog::log(ArLog::Terse, "%s: Don't have any settings for degrees, arbitrarily using 360", getName()); } double increment; if (canSetIncrement()) { increment = getIncrement(); ArLog::log(myInfoLogLevel, "%s: Using increment setting of %g degrees", getName(), increment); } else if (canChooseIncrement()) { increment = getIncrementChoiceDouble(); ArLog::log(myInfoLogLevel, "%s: Using increment setting of %g degrees", getName(), increment); } else { // PS 10/20/11 - This was missing causing buffer size to be very large // set this to the lowest, note both the SZ and S3 are setting the buffer // size but it's being overriden by this procedure - do we want to fix // this or just leave it at the max value 360/.25=1440??? increment = .25; ArLog::log(ArLog::Terse, "%s: Don't have any settings for increment, arbitrarily using .25", getName()); } int size = (int)ceil(degrees / increment) + 1; ArLog::log(myInfoLogLevel, "%s: Setting current buffer size to %d", getName(), size); setCurrentBufferSize(size); ArLog::log(myInfoLogLevel, "%s: Connected", getName()); myConnectCBList.invoke(); } AREXPORT void ArLaser::laserFailedConnect(void) { ArLog::log(myInfoLogLevel, "%s: Failed to connect", getName()); myFailedConnectCBList.invoke(); } AREXPORT void ArLaser::laserDisconnectNormally(void) { ArLog::log(myInfoLogLevel, "%s: Disconnected normally", getName()); myDisconnectNormallyCBList.invoke(); } AREXPORT void ArLaser::laserDisconnectOnError(void) { ArLog::log(ArLog::Normal, "%s: Disconnected because of error", getName()); myDisconnectOnErrorCBList.invoke(); } AREXPORT void ArLaser::internalGotReading(void) { if (myTimeLastReading != time(NULL)) { myTimeLastReading = time(NULL); myReadingCount = myReadingCurrentCount; myReadingCurrentCount = 0; } myReadingCurrentCount++; myLastReading.setToNow(); myDataCBList.invoke(); } AREXPORT int ArLaser::getReadingCount() { if (myTimeLastReading == time(NULL)) return myReadingCount; if (myTimeLastReading == time(NULL) - 1) return myReadingCurrentCount; return 0; } AREXPORT void ArLaser::setSensorPosition( double x, double y, double th, double z) { setSensorPosition(ArPose(x, y, th), z); } AREXPORT void ArLaser::setSensorPosition(ArPose pose, double z) { myHaveSensorPose = true; mySensorPose.setPose(pose); mySensorZ = z; } bool ArLaser::internalCheckChoice(const char *check, const char *choice, std::list *choices, const char *choicesStr) { if (check == NULL || choices == NULL || choice == NULL || choice[0] == '\0') { ArLog::log(ArLog::Terse, "%s::%s: Internal error in setup"); return false; } std::list::iterator it; std::string str; for (it = choices->begin(); it != choices->end(); it++) { str = (*it); if (ArUtil::strcasecmp(choice, str) == 0) return true; } ArLog::log(ArLog::Terse, "%s::%s: Invalid choice, choices are <%s>.", myName.c_str(), check, choicesStr); return false; } bool ArLaser::internalCheckChoice(const char *check, const char *choice, std::map *choices, const char *choicesStr, double *choiceDouble) { if (check == NULL || choices == NULL || choice == NULL || choice[0] == '\0') { ArLog::log(ArLog::Terse, "%s::%s: Internal error in setup"); return false; } std::map::iterator it; std::string str; for (it = choices->begin(); it != choices->end(); it++) { str = (*it).first; if (ArUtil::strcasecmp(choice, str) == 0) { *choiceDouble = (*it).second; return true; } } ArLog::log(ArLog::Terse, "%s::%s: Invalid choice, choices are <%s>.", myName.c_str(), check, choicesStr); return false; } void ArLaser::internalBuildChoicesString( std::list *choices, std::string *str) { std::list::iterator it; bool first; std::string choiceStr; for (it = choices->begin(), first = true; it != choices->end(); it++) { choiceStr = (*it); if (!first) (*str) += "|"; first = false; (*str) += choiceStr; } } void ArLaser::internalBuildChoices( std::map *choices, std::string *str, std::list *choicesList) { std::map::iterator it; bool first; std::string choiceStr; for (it = choices->begin(), first = true; it != choices->end(); it++) { choiceStr = (*it).first; choicesList->push_back(choiceStr); if (!first) (*str) += "|"; first = false; (*str) += choiceStr; } } /** This allows the setting of the degrees the laser will use from a range for both starting and ending degrees. Only one of this and laserAllowDegreesChoices should be used. @param defaultStartDegrees The default start degrees to use, this default should probably be for the max range. @param startDegreesMin The minimum value for start degrees @param startDegreesMax The maximum value for start degrees @param defaultEndDegrees The default end degrees to use, this default should probably be for the max range. @param endDegreesMin The minimum value for end degrees @param endDegreesMax The maximum value for end degrees **/ AREXPORT void ArLaser::laserAllowSetDegrees(double defaultStartDegrees, double startDegreesMin, double startDegreesMax, double defaultEndDegrees, double endDegreesMin, double endDegreesMax) { myCanSetDegrees = true; myStartDegreesMin = startDegreesMin; myStartDegreesMax = startDegreesMax; setStartDegrees(defaultStartDegrees); myStartDegreesSet = false; myEndDegreesMin = endDegreesMin; myEndDegreesMax = endDegreesMax; setEndDegrees(defaultEndDegrees); myEndDegreesSet = false; } AREXPORT bool ArLaser::setStartDegrees(double startDegrees) { if (!myCanSetDegrees) { ArLog::log(ArLog::Terse, "%s::setStartDegrees: Cannot set angles on this laser", myName.c_str()); return false; } if (startDegrees < myStartDegreesMin) { ArLog::log(ArLog::Terse, "%s::setStartDegrees: Start degrees (%g) tried to be set to less than the minimum (%g))", myName.c_str(), startDegrees, myStartDegreesMin); return false; } if (startDegrees > myStartDegreesMax) { ArLog::log(ArLog::Terse, "%s::setStartDegrees: Start degrees (%g) tried to be set to greater than the minimum (%g))", myName.c_str(), startDegrees, myStartDegreesMax); return false; } if (myEndDegreesSet && startDegrees >= myEndDegrees) { ArLog::log(ArLog::Terse, "%s::setStartDegrees: Start degrees (%g) tried to be set to greater than or equal to end degrees %g)", myName.c_str(), startDegrees, myEndDegrees); return false; } myStartDegreesSet = true; myStartDegrees = startDegrees; return true; } AREXPORT bool ArLaser::setEndDegrees(double endDegrees) { if (!myCanSetDegrees) { ArLog::log(ArLog::Terse, "%s::setEndDegrees: Cannot set angles on this laser", myName.c_str()); return false; } if (endDegrees < myEndDegreesMin) { ArLog::log(ArLog::Terse, "%s::setEndDegrees: End degrees (%g) tried to be set to less than the minimum (%g))", myName.c_str(), endDegrees, myEndDegreesMin); return false; } if (endDegrees > myEndDegreesMax) { ArLog::log(ArLog::Terse, "%s::setEndDegrees: End degrees (%g) tried to be set to greater than the minimum (%g))", myName.c_str(), endDegrees, myEndDegreesMax); return false; } if (myStartDegreesSet && endDegrees <= myStartDegrees) { ArLog::log(ArLog::Terse, "%s::setEndDegrees: End degrees (%g) tried to be set to less than or equal to end degrees %g)", myName.c_str(), endDegrees, myStartDegrees); return false; } myEndDegreesSet = true; myEndDegrees = endDegrees; return true; } /** Allows the choice of the laser degrees from one of a number of choices, only one of this and laserAllowSetDegrees should be used. @param defaultDegreesChoice The default degrees, this should be the largest value. @param degreesChoices this is a mapping of std::strings to doubles, the strings should be the actual available choices, and the doubles should be the numerical representation... this is so the simulated laser can behave more easily like the real lasers... and because the original sick driver used words typed out (to make problems more obvious). **/ AREXPORT void ArLaser::laserAllowDegreesChoices( const char *defaultDegreesChoice, std::map degreesChoices) { myCanChooseDegrees = true; myDegreesChoices = degreesChoices; internalBuildChoices(&myDegreesChoices, &myDegreesChoicesString, &myDegreesChoicesList); chooseDegrees(defaultDegreesChoice); myDegreesChoiceSet = false; } AREXPORT bool ArLaser::chooseDegrees( const char *degreesChoice) { if (!myCanChooseDegrees) { ArLog::log(ArLog::Terse, "%s::chooseDegrees: Cannot choose degrees on this laser", myName.c_str()); return false; } double degreesChoiceDouble; if (!internalCheckChoice("chooseDegrees", degreesChoice, &myDegreesChoices, myDegreesChoicesString.c_str(), °reesChoiceDouble)) return false; myDegreesChoice = degreesChoice; myDegreesChoiceDouble = degreesChoiceDouble; return true; } /** Allows the choice of increment from a range, only one of this and laserAllowIncrementChoices should be used. @param defaultIncrement The default increment to use, this default should be a reasonable value. @param incrementMin The minimum value for the increment @param incrementMax The maximum value for the increment **/ AREXPORT void ArLaser::laserAllowSetIncrement( double defaultIncrement, double incrementMin, double incrementMax) { myCanSetIncrement = true; myIncrementMin = incrementMin; myIncrementMax = incrementMax; setIncrement(defaultIncrement); myIncrementSet = false; } AREXPORT bool ArLaser::setIncrement(double increment) { if (!myCanSetIncrement) { ArLog::log(ArLog::Terse, "%s::setIncrement: Cannot set increment on this laser", myName.c_str()); return false; } if (increment < myIncrementMin) { ArLog::log(ArLog::Terse, "%s::setIncrement: Increment (%g) tried to be set to less than the minimum (%g))", myName.c_str(), increment, myIncrementMin); return false; } if (increment > myIncrementMax) { ArLog::log(ArLog::Terse, "%s::setIncrement: End degrees (%g) tried to be set to greater than the maximum (%g))", myName.c_str(), increment, myIncrementMax); return false; } myIncrementSet = true; myIncrement = increment; return true; } /** Allows the choice of increment from a limited set of values, only one of this and laserAllowSetIncrement should be used. @param defaultIncrementChoice The default increment, this should be a reasonable value. @param incrementChoices this is a mapping of std::strings to doubles, the strings should be the actual available choices, and the doubles should be the numerical representation... this is so the simulated laser can behave more easily like the real lasers... and because the original sick driver used words typed out (to make problems more obvious). **/ AREXPORT void ArLaser::laserAllowIncrementChoices( const char *defaultIncrementChoice, std::map incrementChoices) { myCanChooseIncrement = true; myIncrementChoices = incrementChoices; internalBuildChoices(&myIncrementChoices, &myIncrementChoicesString, &myIncrementChoicesList); chooseIncrement(defaultIncrementChoice); myIncrementChoiceSet = false; } AREXPORT bool ArLaser::chooseIncrement(const char *incrementChoice) { if (!myCanChooseIncrement) { ArLog::log(ArLog::Terse, "%s::chooseIncrement: Cannot choose increment on this laser", myName.c_str()); return false; } double incrementChoiceDouble; if (!internalCheckChoice("chooseIncrement", incrementChoice, &myIncrementChoices, myIncrementChoicesString.c_str(), &incrementChoiceDouble)) return false; myIncrementChoice = incrementChoice; myIncrementChoiceDouble = incrementChoiceDouble; return true; } /** @param defaultUnitsChoice This is the default units choice, it should be a reasonable value. @param unitsChoices The possible choices for units. **/ AREXPORT void ArLaser::laserAllowUnitsChoices( const char *defaultUnitsChoice, std::list unitsChoices) { myCanChooseUnits = true; myUnitsChoices = unitsChoices; internalBuildChoicesString(&myUnitsChoices, &myUnitsChoicesString); chooseUnits(defaultUnitsChoice); myUnitsChoiceSet = false; } AREXPORT bool ArLaser::chooseUnits(const char *unitsChoice) { if (!myCanChooseUnits) { ArLog::log(ArLog::Terse, "%s::chooseUnits: Cannot choose units on this laser", myName.c_str()); return false; } if (!internalCheckChoice("chooseUnits", unitsChoice, &myUnitsChoices, myUnitsChoicesString.c_str())) return false; myUnitsChoice = unitsChoice; return true; } /** @param defaultReflectorBitsChoice The default choice for reflector bits, should be a reasonable value. @param reflectorBitsChoices The possible choices for reflector bits **/ AREXPORT void ArLaser::laserAllowReflectorBitsChoices( const char *defaultReflectorBitsChoice, std::list reflectorBitsChoices) { myCanChooseReflectorBits = true; myReflectorBitsChoices = reflectorBitsChoices; internalBuildChoicesString(&myReflectorBitsChoices, &myReflectorBitsChoicesString); chooseReflectorBits(defaultReflectorBitsChoice); myReflectorBitsChoiceSet = false; } AREXPORT bool ArLaser::chooseReflectorBits(const char *reflectorBitsChoice) { if (!myCanChooseReflectorBits) { ArLog::log(ArLog::Terse, "%s::chooseReflectorBits: Cannot choose reflectorBits on this laser", myName.c_str()); return false; } if (!internalCheckChoice("chooseReflectorBits", reflectorBitsChoice, &myReflectorBitsChoices, myReflectorBitsChoicesString.c_str())) return false; myReflectorBitsChoice = reflectorBitsChoice; return true; } /** Allows settings of whether the power can be controlled or not. This is mostly for devices that respond differently at power up than they do if they are already on (ie the lms2xx where it doesn't respond at all while powering up). If the communication is the same either way, you can just not set this. @param defaultPowerControlled The default value for power controlled. **/ AREXPORT void ArLaser::laserAllowSetPowerControlled(bool defaultPowerControlled) { myCanSetPowerControlled = true; setPowerControlled(defaultPowerControlled); myPowerControlledSet = false; } AREXPORT bool ArLaser::setPowerControlled( bool powerControlled) { if (!myCanSetPowerControlled) { ArLog::log(ArLog::Terse, "%s::setPowerControlled: Cannot set if the laser power is controlled on this laser", myName.c_str()); return false; } myPowerControlledSet = true; myPowerControlled = powerControlled; return true; } /** @param defaultStartingBaudChoice Default starting baud choice. This should probably stay the same as what the sensor ships with. @param startingBaudChoices The available choices for starting baud **/ AREXPORT void ArLaser::laserAllowStartingBaudChoices( const char *defaultStartingBaudChoice, std::list startingBaudChoices) { myCanChooseStartingBaud = true; myStartingBaudChoices = startingBaudChoices; internalBuildChoicesString(&myStartingBaudChoices, &myStartingBaudChoicesString); chooseStartingBaud(defaultStartingBaudChoice); myStartingBaudChoiceSet = false; } AREXPORT bool ArLaser::chooseStartingBaud(const char *startingBaudChoice) { if (!myCanChooseStartingBaud) { ArLog::log(ArLog::Terse, "%s::chooseStartingBaud: Cannot choose startingBaud on this laser", myName.c_str()); return false; } if (!internalCheckChoice("chooseStartingBaud", startingBaudChoice, &myStartingBaudChoices, myStartingBaudChoicesString.c_str())) return false; myStartingBaudChoice = startingBaudChoice; return true; } /** @param defaultAutoBaudChoice Default auto baud choice. This should probably be the maximum reasonable reliable robust rate that the laser supports. The laser should autobaud up to this choice after it connects. @param autoBaudChoices The available choices for auto baud **/ AREXPORT void ArLaser::laserAllowAutoBaudChoices( const char *defaultAutoBaudChoice, std::list autoBaudChoices) { myCanChooseAutoBaud = true; myAutoBaudChoices = autoBaudChoices; internalBuildChoicesString(&myAutoBaudChoices, &myAutoBaudChoicesString); chooseAutoBaud(defaultAutoBaudChoice); myAutoBaudChoiceSet = false; } AREXPORT bool ArLaser::chooseAutoBaud(const char *autoBaudChoice) { if (!myCanChooseAutoBaud) { ArLog::log(ArLog::Terse, "%s::chooseAutoBaud: Cannot choose autoBaud on this laser", myName.c_str()); return false; } if (!internalCheckChoice("chooseAutoBaud", autoBaudChoice, &myAutoBaudChoices, myAutoBaudChoicesString.c_str())) return false; myAutoBaudChoice = autoBaudChoice; return true; } AREXPORT void ArLaser::laserSetDefaultTcpPort(int defaultTcpPort) { myDefaultTcpPort = defaultTcpPort; } AREXPORT void ArLaser::laserSetDefaultPortType(const char *defaultPortType) { myDefaultPortType = defaultPortType; } AREXPORT bool ArLaser::addIgnoreReadings(const char *ignoreReadings) { // if we have , then use it as the separator, otherwise use space // like normal char separator = ' '; if (strstr(ignoreReadings, ",") != NULL) separator = ','; ArArgumentBuilder args(1024, separator); args.add(ignoreReadings); if (args.getArgc() == 0) return true; size_t i; const char *str; float begin, end; float ignore; for (i = 0; i < args.getArgc(); i++) { if (args.isArgDouble(i)) { ignore = args.getArgDouble(i); addIgnoreReading(ignore); ArLog::log(ArLog::Verbose, "%s: Added ignore reading %g", getName(), ignore); } else { str = args.getArg(i); if (sscanf(str, "%f:%f", &begin, &end) == 2 || sscanf(str, "%f-%f", &begin, &end) == 2) { ArLog::log(ArLog::Verbose, "%s: Adding ignore reading from %g to %g", getName(), begin, end); // reorder them for easier looping if (begin > end) { ignore = begin; begin = end; end = ignore; } ArLog::log(ArLog::Verbose, "%s: Added ignore reading (beginning) %g", getName(), begin); addIgnoreReading(begin); for (ignore = begin; ignore <= end; ignore += 1.0) { ArLog::log(ArLog::Verbose, "%s: Added ignore reading %g", getName(), ignore); addIgnoreReading(ignore); } ArLog::log(ArLog::Verbose, "%s: Added ignore reading (ending) %g", getName(), end); addIgnoreReading(end); } else { ArLog::log(ArLog::Terse, "%s: Bad syntax for ignore readings, had string '%s' as one of the arguments (the values need to either be individual doubles, or begin:end (75:77) or begin-end (75-77))", getName(), str); return false; } } } return true; } /** Applies a transform to the buffers. this is mostly useful for translating to/from local/global coordinates, but may have other uses. This is different from the class because it also transforms the raw readings. @param trans the transform to apply to the data @param doCumulative whether to transform the cumulative buffer or not */ AREXPORT void ArLaser::applyTransform(ArTransform trans, bool doCumulative) { myCurrentBuffer.applyTransform(trans); std::list::iterator it; for (it = myRawReadings->begin(); it != myRawReadings->end(); ++it) (*it)->applyTransform(trans); if (doCumulative) myCumulativeBuffer.applyTransform(trans); } /** This will check if the laser has lost connection. If there is no robot it is a straightforward check of last reading time against getConnectionTimeoutSeconds. If there is a robot then it will not start the check until the laser is running and connected. **/ AREXPORT bool ArLaser::laserCheckLostConnection(void) { if ((myRobot == NULL || myRobotRunningAndConnected) && getConnectionTimeoutSeconds() > 0 && myLastReading.mSecSince() > getConnectionTimeoutSeconds() * 1000) return true; if (!myRobotRunningAndConnected && myRobot != NULL && myRobot->isRunning() && myRobot->isConnected()) { myRobotRunningAndConnected = true; myLastReading.setToNow(); } return false; } AREXPORT void ArLaser::copyReadingCount(const ArLaser* laser) { myTimeLastReading = laser->myTimeLastReading; myReadingCurrentCount = laser->myReadingCurrentCount; myReadingCount = laser->myReadingCount; } AREXPORT void ArLaser::useSimpleNamingForAllLasers(void) { ArLog::log(ArLog::Normal, "ArLaser: Will use simple naming for all lasers"); ourUseSimpleNaming = true; }