From 4cfc74c5aad7a70d87e692bd44bee47f2afcaff1 Mon Sep 17 00:00:00 2001 From: Brandon Date: Sat, 23 May 2026 18:18:11 -0700 Subject: [PATCH 1/5] ASIwptr: simplify header with less #includes, use #pragma once --- DeviceAdapters/ASIWPTR/wptr.cpp | 58 ++++++++++++++------------------- DeviceAdapters/ASIWPTR/wptr.h | 35 ++++++-------------- 2 files changed, 35 insertions(+), 58 deletions(-) diff --git a/DeviceAdapters/ASIWPTR/wptr.cpp b/DeviceAdapters/ASIWPTR/wptr.cpp index 618eca115..1c86b938d 100644 --- a/DeviceAdapters/ASIWPTR/wptr.cpp +++ b/DeviceAdapters/ASIWPTR/wptr.cpp @@ -28,14 +28,12 @@ #include #include -#include "DeviceUtils.h" -#include "ModuleInterface.h" - -const char* g_WPTRobotName = "WPTRobot"; +const char* gWPTRobotName = "WPTRobot"; +const char* gSerialTerm = "\r\n"; // Exported MMDevice API MODULE_API void InitializeModuleData() { - RegisterDevice(g_WPTRobotName, MM::GenericDevice, "WPTRobot"); + RegisterDevice(gWPTRobotName, MM::GenericDevice, "WPTRobot"); } MODULE_API MM::Device* CreateDevice(const char* deviceName) { @@ -43,7 +41,7 @@ MODULE_API MM::Device* CreateDevice(const char* deviceName) { return nullptr; } - if (std::string(deviceName) == g_WPTRobotName) { + if (std::string(deviceName) == gWPTRobotName) { return new WPTRobot(); } @@ -54,8 +52,12 @@ MODULE_API void DeleteDevice(MM::Device* pDevice) { delete pDevice; } +// Error codes +static constexpr int ERR_PORT_CHANGE_FORBIDDEN = 10004; +static constexpr int ERR_UNRECOGNIZED_ANSWER = 10009; + // Clear contents of serial port -int ClearPort(MM::Device& device, MM::Core& core, const std::string& port) { +static int ClearPort(const MM::Device& device, MM::Core& core, const std::string& port) { constexpr size_t bufferSize = 255; unsigned char clear[bufferSize]; unsigned long read = bufferSize; @@ -69,20 +71,13 @@ int ClearPort(MM::Device& device, MM::Core& core, const std::string& port) { return DEVICE_OK; } -WPTRobot::WPTRobot() : - initialized_(false), - numPos_(0), - port_(""), - command_(""), - stage_(1), - slot_(1) { - +WPTRobot::WPTRobot() { InitializeDefaultErrorMessages(); - // Create Preinitialization Properties + // Create Pre-init Properties // Name - CreateProperty(MM::g_Keyword_Name, g_WPTRobotName, MM::String, true); + CreateProperty(MM::g_Keyword_Name, gWPTRobotName, MM::String, true); // Description CreateProperty(MM::g_Keyword_Description, "Wellplate Transfer Robot", MM::String, true); @@ -97,7 +92,7 @@ WPTRobot::~WPTRobot() { } void WPTRobot::GetName(char* name) const { - CDeviceUtils::CopyLimitedString(name, g_WPTRobotName); + CDeviceUtils::CopyLimitedString(name, gWPTRobotName); } int WPTRobot::Initialize() { @@ -107,19 +102,16 @@ int WPTRobot::Initialize() { // Stage and Slot passed as properties CPropertyAction* pAct = new CPropertyAction(this, &WPTRobot::OnStage); CreateProperty("Stage", "1", MM::Integer, false, pAct); - // SetPropertyLimits("Stage", 1, 3); // setting limits must be edited again, if set up changed pAct = new CPropertyAction(this, &WPTRobot::OnSlot); CreateProperty("Slot", "1", MM::Integer, false, pAct); - // SetPropertyLimits("Slot", 1, 10); // setting this property to different keywords leads to a command pAct = new CPropertyAction(this, &WPTRobot::OnCommand); CreateProperty("Command", "Undefined", MM::String, false, pAct); - int ret = UpdateStatus(); - if (ret != DEVICE_OK) { - return ret; + if (const int status = UpdateStatus(); status != DEVICE_OK) { + return status; } initialized_ = true; @@ -186,12 +178,12 @@ int WPTRobot::OnCommand(MM::PropertyBase* pProp, MM::ActionType eAct) { // user issued ORG command os << "ORG"; - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), "\r\n"); + ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); if (ret != DEVICE_OK) { return ret; } - ret = GetSerialAnswer(port_.c_str(), "\r\n", answer); + ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); if (ret != DEVICE_OK) { return ret; } @@ -208,12 +200,12 @@ int WPTRobot::OnCommand(MM::PropertyBase* pProp, MM::ActionType eAct) { // user issued GET command os << "GET " << stage_ << "," << slot_; - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), "\r\n"); + ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); if (ret != DEVICE_OK) { return ret; } - ret = GetSerialAnswer(port_.c_str(), "\r\n", answer); + ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); if (ret != DEVICE_OK) { return ret; } @@ -228,12 +220,12 @@ int WPTRobot::OnCommand(MM::PropertyBase* pProp, MM::ActionType eAct) { // user issued PUT command os << "PUT " << stage_ << "," << slot_; - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), "\r\n"); + ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); if (ret != DEVICE_OK) { return ret; } - ret = GetSerialAnswer(port_.c_str(), "\r\n", answer); + ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); if (ret != DEVICE_OK) { return ret; } @@ -250,12 +242,12 @@ int WPTRobot::OnCommand(MM::PropertyBase* pProp, MM::ActionType eAct) { // AES is command for emergency stop, stops the robot cold, issue DRT command to enable again os << "AES"; - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), "\r\n"); + ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); if (ret != DEVICE_OK) { return ret; } - ret = GetSerialAnswer(port_.c_str(), "\r\n", answer); + ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); if (ret != DEVICE_OK) { return ret; } @@ -272,12 +264,12 @@ int WPTRobot::OnCommand(MM::PropertyBase* pProp, MM::ActionType eAct) { // override errors os << "DRT"; - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), "\r\n"); + ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); if (ret != DEVICE_OK) { return ret; } - ret = GetSerialAnswer(port_.c_str(), "\r\n", answer); + ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); if (ret != DEVICE_OK) { return ret; } diff --git a/DeviceAdapters/ASIWPTR/wptr.h b/DeviceAdapters/ASIWPTR/wptr.h index 632088377..49b71622b 100644 --- a/DeviceAdapters/ASIWPTR/wptr.h +++ b/DeviceAdapters/ASIWPTR/wptr.h @@ -20,28 +20,14 @@ // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, // INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. // -// AUTHOR: Vikram Kopuri, based on Code by Nenad Amodaj Nico Stuurman and Jizhen Zhao +// AUTHOR: Vikram Kopuri, based on Code by Nenad Amodaj Nico Stuurman and Jizhen Zhao // -#ifndef ASIWPTR_H -#define ASIWPTR_H +#pragma once #include #include "DeviceBase.h" -#include "MMDevice.h" - -// Error codes -// constexpr int ERR_UNKNOWN_POSITION = 10002; -constexpr int ERR_PORT_CHANGE_FORBIDDEN = 10004; -constexpr int ERR_INVALID_STEP_SIZE = 10006; -constexpr int ERR_INVALID_MODE = 10008; -constexpr int ERR_UNRECOGNIZED_ANSWER = 10009; -constexpr int ERR_UNSPECIFIED_ERROR = 10010; - -constexpr int ERR_OFFSET = 10100; - -int ClearPort(MM::Device& device, MM::Core& core, const std::string& port); class WPTRobot : public CGenericBase { public: @@ -51,7 +37,7 @@ class WPTRobot : public CGenericBase { // MMDevice API bool Busy(); void GetName(char* name) const; - unsigned long GetNumberOfPositions() const { return numPos_; } + unsigned long GetNumberOfPositions() const { return 0; } int Initialize(); int Shutdown(); @@ -62,12 +48,11 @@ class WPTRobot : public CGenericBase { int OnCommand(MM::PropertyBase* pProp, MM::ActionType eAct); private: - bool initialized_; - unsigned int numPos_; - std::string port_; // MMCore name of serial port - std::string command_; // Command exchange with MMCore - long stage_; - long slot_; -}; + bool initialized_ = false; + long stage_ = 1; + long slot_ = 1; -#endif // ASIWPTR_H + // serial communication + std::string port_; + std::string command_; +}; From 9ecff50178f32f2904a4ac2817d0eb275b6efb70 Mon Sep 17 00:00:00 2001 From: Brandon Date: Sun, 24 May 2026 03:48:46 -0700 Subject: [PATCH 2/5] ASIwptr: use MM::ActionLambda for properties --- DeviceAdapters/ASIWPTR/wptr.cpp | 325 +++++++++++++++++--------------- DeviceAdapters/ASIWPTR/wptr.h | 12 +- 2 files changed, 176 insertions(+), 161 deletions(-) diff --git a/DeviceAdapters/ASIWPTR/wptr.cpp b/DeviceAdapters/ASIWPTR/wptr.cpp index 1c86b938d..50ad2a20c 100644 --- a/DeviceAdapters/ASIWPTR/wptr.cpp +++ b/DeviceAdapters/ASIWPTR/wptr.cpp @@ -83,8 +83,7 @@ WPTRobot::WPTRobot() { CreateProperty(MM::g_Keyword_Description, "Wellplate Transfer Robot", MM::String, true); // Port - CPropertyAction* pAct = new CPropertyAction(this, &WPTRobot::OnPort); - CreateProperty(MM::g_Keyword_Port, "Undefined", MM::String, false, pAct, true); + CreatePortProperty(); } WPTRobot::~WPTRobot() { @@ -96,20 +95,17 @@ void WPTRobot::GetName(char* name) const { } int WPTRobot::Initialize() { - // empty the Rx serial buffer before sending command + // empty the serial rx buffer before sending command ClearPort(*this, *GetCoreCallback(), port_.c_str()); - // Stage and Slot passed as properties - CPropertyAction* pAct = new CPropertyAction(this, &WPTRobot::OnStage); - CreateProperty("Stage", "1", MM::Integer, false, pAct); - - pAct = new CPropertyAction(this, &WPTRobot::OnSlot); - CreateProperty("Slot", "1", MM::Integer, false, pAct); + // stage and slot are passed as properties + CreateStageProperty(); + CreateSlotProperty(); // setting this property to different keywords leads to a command - pAct = new CPropertyAction(this, &WPTRobot::OnCommand); - CreateProperty("Command", "Undefined", MM::String, false, pAct); + CreateCommandProperty(); + // update all properties if (const int status = UpdateStatus(); status != DEVICE_OK) { return status; } @@ -129,158 +125,175 @@ bool WPTRobot::Busy() { return false; // reply is only given after move is completed, so Busy() not necessary } -int WPTRobot::OnPort(MM::PropertyBase* pProp, MM::ActionType eAct) { - if (eAct == MM::BeforeGet) { - pProp->Set(port_.c_str()); - } else if (eAct == MM::AfterSet) { - if (initialized_) { - // revert - pProp->Set(port_.c_str()); - return ERR_PORT_CHANGE_FORBIDDEN; - } - pProp->Get(port_); - } - return DEVICE_OK; +void WPTRobot::CreatePortProperty() { + CreateStringProperty(MM::g_Keyword_Port, "Undefined", false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(port_.c_str()); + } else if (eAct == MM::AfterSet) { + if (initialized_) { + // revert + pProp->Set(port_.c_str()); + return ERR_PORT_CHANGE_FORBIDDEN; + } + pProp->Get(port_); + } + return DEVICE_OK; + }), + true // pre-init property + ); } // Just reading and writing to the stage variable -int WPTRobot::OnStage(MM::PropertyBase* pProp, MM::ActionType eAct) { - if (eAct == MM::BeforeGet) { - pProp->Set(stage_); - } else if (eAct == MM::AfterSet) { - pProp->Get(stage_); - } - return DEVICE_OK; +void WPTRobot::CreateStageProperty() { + CreateIntegerProperty("Stage", 1, false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(stage_); + } else if (eAct == MM::AfterSet) { + pProp->Get(stage_); + } + return DEVICE_OK; + }) + ); } // Just reading and writing to the slot variable -int WPTRobot::OnSlot(MM::PropertyBase* pProp, MM::ActionType eAct) { - if (eAct == MM::BeforeGet) { - pProp->Set(slot_); - } else if (eAct == MM::AfterSet) { - pProp->Get(slot_); - } - return DEVICE_OK; -} - -int WPTRobot::OnCommand(MM::PropertyBase* pProp, MM::ActionType eAct) { - if (eAct == MM::BeforeGet) { - pProp->Set(command_.c_str()); - } else if (eAct == MM::AfterSet) { - // Read what keyword the user issued, and send the corresponding command - pProp->Get(command_); - - std::ostringstream os; - std::string answer; - int ret; - - if (command_.compare(0, 3, "ORG") == 0) { - // user issued ORG command - os << "ORG"; - - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); - if (ret != DEVICE_OK) { - return ret; - } - - ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); - if (ret != DEVICE_OK) { - return ret; - } - - // checking if the answer is what is expected, - // if not just give an error and quit - if (answer.length() != 3) { - return ERR_UNRECOGNIZED_ANSWER; - } - if (answer.compare(0, 3, "ORG") != 0) { - return ERR_UNRECOGNIZED_ANSWER; - } - } else if (command_.compare(0, 3, "GET") == 0) { - // user issued GET command - os << "GET " << stage_ << "," << slot_; - - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); - if (ret != DEVICE_OK) { - return ret; - } - - ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); - if (ret != DEVICE_OK) { - return ret; - } - - if (answer.length() != 3) { - return ERR_UNRECOGNIZED_ANSWER; - } - if (answer.compare(0, 3, "GET") != 0) { - return ERR_UNRECOGNIZED_ANSWER; - } - } else if (command_.substr(0, 3) == "PUT") { - // user issued PUT command - os << "PUT " << stage_ << "," << slot_; - - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); - if (ret != DEVICE_OK) { - return ret; - } - - ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); - if (ret != DEVICE_OK) { - return ret; - } - - if (answer.length() != 3) { - return ERR_UNRECOGNIZED_ANSWER; - } - if (answer.compare(0, 3, "PUT") != 0) { - return ERR_UNRECOGNIZED_ANSWER; - } - } else if (command_.compare(0, 3, "AES") == 0) { - // used issued STOP command - - // AES is command for emergency stop, stops the robot cold, issue DRT command to enable again - os << "AES"; - - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); - if (ret != DEVICE_OK) { - return ret; - } - - ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); - if (ret != DEVICE_OK) { - return ret; - } - - if (answer.length() != 3) { - return ERR_UNRECOGNIZED_ANSWER; - } - if (answer.compare(0, 3, "AES") != 0) { - return ERR_UNRECOGNIZED_ANSWER; - } - } else if (command_.compare(0, 3, "DRT") == 0) { - // user issued DRT command - - // override errors - os << "DRT"; - - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); - if (ret != DEVICE_OK) { - return ret; - } - - ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); - if (ret != DEVICE_OK) { - return ret; +void WPTRobot::CreateSlotProperty() { + CreateIntegerProperty("Slot", 1, false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(slot_); + } else if (eAct == MM::AfterSet) { + pProp->Get(slot_); } + return DEVICE_OK; + }) + ); +} - if (answer.length() != 3) { - return ERR_UNRECOGNIZED_ANSWER; - } - if (answer.compare(0, 3, "DRT") != 0) { - return ERR_UNRECOGNIZED_ANSWER; +void WPTRobot::CreateCommandProperty() { + CreateStringProperty("Command", "Undefined", false, + new MM::ActionLambda([this](MM::PropertyBase* pProp, MM::ActionType eAct) { + if (eAct == MM::BeforeGet) { + pProp->Set(command_.c_str()); + } else if (eAct == MM::AfterSet) { + // Read what keyword the user issued, and send the corresponding command + pProp->Get(command_); + + std::ostringstream os; + std::string answer; + int ret; + + if (command_.compare(0, 3, "ORG") == 0) { + // user issued ORG command + os << "ORG"; + + ret = SendSerialCommand(port_.c_str(), "ORG", gSerialTerm); + if (ret != DEVICE_OK) { + return ret; + } + + ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); + if (ret != DEVICE_OK) { + return ret; + } + + // checking if the answer is what is expected, + // if not just give an error and quit + if (answer.length() != 3) { + return ERR_UNRECOGNIZED_ANSWER; + } + if (answer.compare(0, 3, "ORG") != 0) { + return ERR_UNRECOGNIZED_ANSWER; + } + } else if (command_.compare(0, 3, "GET") == 0) { + // user issued GET command + os << "GET " << stage_ << "," << slot_; + + ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); + if (ret != DEVICE_OK) { + return ret; + } + + ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); + if (ret != DEVICE_OK) { + return ret; + } + + if (answer.length() != 3) { + return ERR_UNRECOGNIZED_ANSWER; + } + if (answer.compare(0, 3, "GET") != 0) { + return ERR_UNRECOGNIZED_ANSWER; + } + } else if (command_.compare(0, 3, "PUT") == 0) { + // user issued PUT command + os << "PUT " << stage_ << "," << slot_; + + ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); + if (ret != DEVICE_OK) { + return ret; + } + + ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); + if (ret != DEVICE_OK) { + return ret; + } + + if (answer.length() != 3) { + return ERR_UNRECOGNIZED_ANSWER; + } + if (answer.compare(0, 3, "PUT") != 0) { + return ERR_UNRECOGNIZED_ANSWER; + } + } else if (command_.compare(0, 3, "AES") == 0) { + // used issued STOP command + + // AES is command for emergency stop, stops the robot cold, issue DRT command to enable again + os << "AES"; + + ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); + if (ret != DEVICE_OK) { + return ret; + } + + ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); + if (ret != DEVICE_OK) { + return ret; + } + + if (answer.length() != 3) { + return ERR_UNRECOGNIZED_ANSWER; + } + if (answer.compare(0, 3, "AES") != 0) { + return ERR_UNRECOGNIZED_ANSWER; + } + } else if (command_.compare(0, 3, "DRT") == 0) { + // user issued DRT command + + // override errors + os << "DRT"; + + ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); + if (ret != DEVICE_OK) { + return ret; + } + + ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); + if (ret != DEVICE_OK) { + return ret; + } + + if (answer.length() != 3) { + return ERR_UNRECOGNIZED_ANSWER; + } + if (answer.compare(0, 3, "DRT") != 0) { + return ERR_UNRECOGNIZED_ANSWER; + } + } } - } - } - return DEVICE_OK; + return DEVICE_OK; + }) + ); } diff --git a/DeviceAdapters/ASIWPTR/wptr.h b/DeviceAdapters/ASIWPTR/wptr.h index 49b71622b..d14c26781 100644 --- a/DeviceAdapters/ASIWPTR/wptr.h +++ b/DeviceAdapters/ASIWPTR/wptr.h @@ -42,12 +42,14 @@ class WPTRobot : public CGenericBase { int Initialize(); int Shutdown(); - int OnPort(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnStage(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnSlot(MM::PropertyBase* pProp, MM::ActionType eAct); - int OnCommand(MM::PropertyBase* pProp, MM::ActionType eAct); - private: + // pre-init + void CreatePortProperty(); + // properties + void CreateStageProperty(); + void CreateSlotProperty(); + void CreateCommandProperty(); + bool initialized_ = false; long stage_ = 1; long slot_ = 1; From 9b6f1e90824a29f136a0c23e186670062be629e7 Mon Sep 17 00:00:00 2001 From: Brandon Date: Wed, 27 May 2026 09:26:49 -0700 Subject: [PATCH 3/5] ASIwptr: simplify command property --- DeviceAdapters/ASIWPTR/wptr.cpp | 178 ++++++++++---------------------- DeviceAdapters/ASIWPTR/wptr.h | 2 + 2 files changed, 55 insertions(+), 125 deletions(-) diff --git a/DeviceAdapters/ASIWPTR/wptr.cpp b/DeviceAdapters/ASIWPTR/wptr.cpp index 50ad2a20c..13f1cadb8 100644 --- a/DeviceAdapters/ASIWPTR/wptr.cpp +++ b/DeviceAdapters/ASIWPTR/wptr.cpp @@ -25,11 +25,9 @@ #include "wptr.h" -#include #include -const char* gWPTRobotName = "WPTRobot"; -const char* gSerialTerm = "\r\n"; +static const char* gWPTRobotName = "WPTRobot"; // Exported MMDevice API MODULE_API void InitializeModuleData() { @@ -53,6 +51,7 @@ MODULE_API void DeleteDevice(MM::Device* pDevice) { } // Error codes +static constexpr int ERR_UNKNOWN_COMMAND = 10002; static constexpr int ERR_PORT_CHANGE_FORBIDDEN = 10004; static constexpr int ERR_UNRECOGNIZED_ANSWER = 10009; @@ -61,28 +60,31 @@ static int ClearPort(const MM::Device& device, MM::Core& core, const std::string constexpr size_t bufferSize = 255; unsigned char clear[bufferSize]; unsigned long read = bufferSize; - int result; while (read == bufferSize) { - result = core.ReadFromSerial(&device, port.c_str(), clear, bufferSize, read); - if (result != DEVICE_OK) { - return result; + if (const int status = core.ReadFromSerial(&device, port.c_str(), clear, bufferSize, read); + status != DEVICE_OK) { + return status; } } return DEVICE_OK; } +// Returns true if the string starts with the prefix. +static bool StartsWith(const std::string& str, const std::string& prefix) { + return str.compare(0, prefix.size(), prefix) == 0; +} + WPTRobot::WPTRobot() { InitializeDefaultErrorMessages(); - // Create Pre-init Properties + // register custom errors + SetErrorText(ERR_UNKNOWN_COMMAND, "Unknown serial command, not ORG, GET, PUT, AES, or DRT"); + SetErrorText(ERR_PORT_CHANGE_FORBIDDEN, "Serial port cannot be changed after initialization"); + SetErrorText(ERR_UNRECOGNIZED_ANSWER, "Serial command replied with an unrecognized answer"); - // Name + // create pre-init properties CreateProperty(MM::g_Keyword_Name, gWPTRobotName, MM::String, true); - - // Description CreateProperty(MM::g_Keyword_Description, "Wellplate Transfer Robot", MM::String, true); - - // Port CreatePortProperty(); } @@ -125,6 +127,26 @@ bool WPTRobot::Busy() { return false; // reply is only given after move is completed, so Busy() not necessary } +// Returns the device status after sending a serial command. +int WPTRobot::SendCommand(const std::string& command) { + // send the command + if (const int status = SendSerialCommand(port_.c_str(), command.c_str(), "\r\n"); + status != DEVICE_OK) { + return status; + } + // read the answer + std::string answer; + if (const int status = GetSerialAnswer(port_.c_str(), "\r\n", answer); + status != DEVICE_OK) { + return status; + } + // Note: protocol specific, reply length is always 3, matches command length + if (answer.length() != 3 || answer.compare(0, 3, command) != 0) { + return ERR_UNRECOGNIZED_ANSWER; + } + return DEVICE_OK; +} + void WPTRobot::CreatePortProperty() { CreateStringProperty(MM::g_Keyword_Port, "Undefined", false, new MM::ActionLambda([this](MM::PropertyBase* pProp, MM::ActionType eAct) { @@ -132,7 +154,7 @@ void WPTRobot::CreatePortProperty() { pProp->Set(port_.c_str()); } else if (eAct == MM::AfterSet) { if (initialized_) { - // revert + // revert change pProp->Set(port_.c_str()); return ERR_PORT_CHANGE_FORBIDDEN; } @@ -178,120 +200,26 @@ void WPTRobot::CreateCommandProperty() { if (eAct == MM::BeforeGet) { pProp->Set(command_.c_str()); } else if (eAct == MM::AfterSet) { - // Read what keyword the user issued, and send the corresponding command pProp->Get(command_); - std::ostringstream os; - std::string answer; - int ret; - - if (command_.compare(0, 3, "ORG") == 0) { - // user issued ORG command - os << "ORG"; - - ret = SendSerialCommand(port_.c_str(), "ORG", gSerialTerm); - if (ret != DEVICE_OK) { - return ret; - } - - ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); - if (ret != DEVICE_OK) { - return ret; - } - - // checking if the answer is what is expected, - // if not just give an error and quit - if (answer.length() != 3) { - return ERR_UNRECOGNIZED_ANSWER; - } - if (answer.compare(0, 3, "ORG") != 0) { - return ERR_UNRECOGNIZED_ANSWER; - } - } else if (command_.compare(0, 3, "GET") == 0) { - // user issued GET command - os << "GET " << stage_ << "," << slot_; - - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); - if (ret != DEVICE_OK) { - return ret; - } - - ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); - if (ret != DEVICE_OK) { - return ret; - } - - if (answer.length() != 3) { - return ERR_UNRECOGNIZED_ANSWER; - } - if (answer.compare(0, 3, "GET") != 0) { - return ERR_UNRECOGNIZED_ANSWER; - } - } else if (command_.compare(0, 3, "PUT") == 0) { - // user issued PUT command - os << "PUT " << stage_ << "," << slot_; - - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); - if (ret != DEVICE_OK) { - return ret; - } - - ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); - if (ret != DEVICE_OK) { - return ret; - } - - if (answer.length() != 3) { - return ERR_UNRECOGNIZED_ANSWER; - } - if (answer.compare(0, 3, "PUT") != 0) { - return ERR_UNRECOGNIZED_ANSWER; - } - } else if (command_.compare(0, 3, "AES") == 0) { - // used issued STOP command - - // AES is command for emergency stop, stops the robot cold, issue DRT command to enable again - os << "AES"; - - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); - if (ret != DEVICE_OK) { - return ret; - } - - ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); - if (ret != DEVICE_OK) { - return ret; - } - - if (answer.length() != 3) { - return ERR_UNRECOGNIZED_ANSWER; - } - if (answer.compare(0, 3, "AES") != 0) { - return ERR_UNRECOGNIZED_ANSWER; - } - } else if (command_.compare(0, 3, "DRT") == 0) { - // user issued DRT command - - // override errors - os << "DRT"; - - ret = SendSerialCommand(port_.c_str(), os.str().c_str(), gSerialTerm); - if (ret != DEVICE_OK) { - return ret; - } - - ret = GetSerialAnswer(port_.c_str(), gSerialTerm, answer); - if (ret != DEVICE_OK) { - return ret; - } - - if (answer.length() != 3) { - return ERR_UNRECOGNIZED_ANSWER; - } - if (answer.compare(0, 3, "DRT") != 0) { - return ERR_UNRECOGNIZED_ANSWER; - } + // is the command valid? + if (!StartsWith(command_, "ORG") + && !StartsWith(command_, "GET") + && !StartsWith(command_, "PUT") + && !StartsWith(command_, "AES") // command for emergency stop + && !StartsWith(command_, "DRT")) { // command to enable after AES + return ERR_UNKNOWN_COMMAND; } + + // prefix is a valid command after check above + const std::string prefix = command_.substr(0, 3); + + // append stage and slot for GET and PUT commands + const std::string command = (prefix == "GET" || prefix == "PUT") + ? prefix + " " + std::to_string(stage_) + "," + std::to_string(slot_) + : prefix; + + return SendCommand(command); } return DEVICE_OK; }) diff --git a/DeviceAdapters/ASIWPTR/wptr.h b/DeviceAdapters/ASIWPTR/wptr.h index d14c26781..a38561256 100644 --- a/DeviceAdapters/ASIWPTR/wptr.h +++ b/DeviceAdapters/ASIWPTR/wptr.h @@ -43,6 +43,8 @@ class WPTRobot : public CGenericBase { int Shutdown(); private: + int SendCommand(const std::string& command); + // pre-init void CreatePortProperty(); // properties From cd012eef787ec52e9a22a0acfe03b5767cdf3252 Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 28 May 2026 15:43:11 -0700 Subject: [PATCH 4/5] ASIwptr: add #include back to file, simplify Shutdown() --- DeviceAdapters/ASIWPTR/wptr.cpp | 4 +--- DeviceAdapters/ASIWPTR/wptr.h | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/DeviceAdapters/ASIWPTR/wptr.cpp b/DeviceAdapters/ASIWPTR/wptr.cpp index 13f1cadb8..7ed9718f9 100644 --- a/DeviceAdapters/ASIWPTR/wptr.cpp +++ b/DeviceAdapters/ASIWPTR/wptr.cpp @@ -117,9 +117,7 @@ int WPTRobot::Initialize() { } int WPTRobot::Shutdown() { - if (initialized_) { - initialized_ = false; - } + initialized_ = false; return DEVICE_OK; } diff --git a/DeviceAdapters/ASIWPTR/wptr.h b/DeviceAdapters/ASIWPTR/wptr.h index a38561256..ecc46a6e2 100644 --- a/DeviceAdapters/ASIWPTR/wptr.h +++ b/DeviceAdapters/ASIWPTR/wptr.h @@ -27,6 +27,7 @@ #include +#include "MMDevice.h" #include "DeviceBase.h" class WPTRobot : public CGenericBase { From 999016fe9c4191aa6255b9d624094010c1999c3a Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 28 May 2026 17:00:08 -0700 Subject: [PATCH 5/5] ASIwptr: use braced init for in-class member init --- DeviceAdapters/ASIWPTR/wptr.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/DeviceAdapters/ASIWPTR/wptr.h b/DeviceAdapters/ASIWPTR/wptr.h index ecc46a6e2..77567e600 100644 --- a/DeviceAdapters/ASIWPTR/wptr.h +++ b/DeviceAdapters/ASIWPTR/wptr.h @@ -58,6 +58,6 @@ class WPTRobot : public CGenericBase { long slot_ = 1; // serial communication - std::string port_; - std::string command_; + std::string port_{}; + std::string command_{}; };