From 06e2db553ea374cee606621720b913d16efcafac Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 26 Feb 2025 10:25:45 -0800 Subject: [PATCH 01/19] initial implementaiton with runtime validity checks --- MMDevice/DeviceBase.h | 57 ++++++++++++++++++++++++++++++++++++ MMDevice/MMDevice.h | 34 +++++++++++++++++++++ MMDevice/MMDeviceConstants.h | 1 + MMDevice/Property.cpp | 18 +++++++++++- MMDevice/Property.h | 45 +++++++++++++++++++++++++++- 5 files changed, 153 insertions(+), 2 deletions(-) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 84cef7177..73ca0de03 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -584,6 +584,62 @@ class CDeviceBase : public T return properties_.CreateProperty(name, value, eType, readOnly, pAct, isPreInitProperty); } + /** + * Creates a standard property -- a property with a predefined name, type, and possibly values. + * Standard properties can apply to all devices, or only some types, as specified in the + * switch statement below. + * Type, read-only and pre-init are fixed for the standard property + * + * @param property - the standard property enum + * @param value - initial value + * @param pAct - function object called on the property actions + */ + int CreateStandardProperty(MM::StandardProperty property, const char* value, MM::ActionFunctor* pAct = 0) + { + if (!IsValidStandardProperty(property)) { + return DEVICE_INVALID_PROPERTY; + } + + // TODO: check if value is valid? + + // Prepend the prefix marking it as a standard property and the delimiter + std::string fullName = g_KeywordStandardPropertyPrefix + "//" + property.name; + + return properties_.CreateStandardProperty(property, value, pAct); + } + + /** + * Checks if a standard property is applicable to this device type + * @param property - the standard property to check + * @return - true if applicable, false otherwise + */ + bool IsValidStandardProperty(MM::StandardProperty property) const + { + MM::DeviceType deviceType = T::Type; + return IsPropertyApplicableToDeviceType(property, deviceType); + } + + /** + * Helper function to check if a standard property is applicable to a specific device type + * @param property - the standard property to check + * @param deviceType - the device type to check against + * @return - true if the property is applicable to the device type, false otherwise + */ + bool IsPropertyApplicableToDeviceType(MM::StandardProperty property, MM::DeviceType deviceType) const + { + // Iterate through the standard property list to find if this property + // is applicable to this device type + for (const MM::StandardPropAssociation& association : MM::g_StandardPropertyList) + { + if (strcmp(association.property.name, property.name) == 0 && + association.deviceType == deviceType) + { + return true; + } + } + return false; + } + /** * Creates a new property for the device. * @param name - property name @@ -596,6 +652,7 @@ class CDeviceBase : public T */ int CreatePropertyWithHandler(const char* name, const char* value, MM::PropertyType eType, bool readOnly, int(U::*memberFunction)(MM::PropertyBase* pProp, MM::ActionType eAct), bool isPreInitProperty=false) { + // Check for reserved delimiter (handled in CreateProperty) CPropertyAction* pAct = new CPropertyAction((U*) this, memberFunction); return CreateProperty(name, value, eType, readOnly, pAct, isPreInitProperty); } diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index c674ac0b7..795407057 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -43,6 +43,7 @@ #include "DeviceUtils.h" #include "ImageMetadata.h" #include "DeviceThreads.h" +#include "Property.h" #include #include @@ -205,6 +206,39 @@ namespace MM { }; + //////////////////////////// + ///// Standard properties + //////////////////////////// + + // Specific standard properties + static const MM::StandardProperty g_BinningStandardProperty( + "Binning", // name + String, // type + false, // isReadOnly + false, // isPreInit + nullptr, // allowedValues + PropertyLimitUndefined, // lowerLimit + PropertyLimitUndefined // upperLimit + ); + + // A struct to encode which which device types have/require which standard properties + struct StandardPropAssociation { + const MM::StandardProperty& property; + MM::DeviceType deviceType; // which device types can use + bool required; + }; + + // This data structure associates standard properties with device types. + // each entry defines one property-device association and whether the + // standard property is required + static const StandardPropAssociation g_StandardPropertyList[] = + { + // propertyname deviceTypes required + {g_BinningStandardProperty, MM::CameraDevice, false} + }; + + + /** * Generic device interface. */ diff --git a/MMDevice/MMDeviceConstants.h b/MMDevice/MMDeviceConstants.h index 65b0a5c90..149d72705 100644 --- a/MMDevice/MMDeviceConstants.h +++ b/MMDevice/MMDeviceConstants.h @@ -88,6 +88,7 @@ #define DEVICE_SEQUENCE_TOO_LARGE 39 #define DEVICE_OUT_OF_MEMORY 40 #define DEVICE_NOT_YET_IMPLEMENTED 41 +#define DEVICE_MISSING_REQUIRED_PROPERTY 42 namespace MM { diff --git a/MMDevice/Property.cpp b/MMDevice/Property.cpp index 7088a8f81..5cd5b7d78 100644 --- a/MMDevice/Property.cpp +++ b/MMDevice/Property.cpp @@ -326,7 +326,23 @@ unsigned MM::PropertyCollection::GetSize() const return (unsigned) properties_.size(); } -int MM::PropertyCollection::CreateProperty(const char* pszName, const char* pszValue, MM::PropertyType eType, bool bReadOnly, MM::ActionFunctor* pAct, bool isPreInitProperty) +int MM::PropertyCollection::CreateStandardProperty(StandardProperty property, const char* value, ActionFunctor* pAct) +{ + return DoCreateProperty(property.name, value, property.type, property.isReadOnly, pAct, property.isPreInit); +} + +int MM::PropertyCollection::CreateProperty(const char* name, const char* value, PropertyType eType, bool bReadOnly, ActionFunctor* pAct=0, bool isPreInitProperty=false) +{ + // disallow properties that begin with the reserved prefix for standard properties + // std prefic + delimiter + property name + std::string prefixAndDelim = std::string(g_KeywordStandardPropertyPrefix) + std::string("//"); + if (std::string(name).find(prefixAndDelim) == 0) + return DEVICE_INVALID_PROPERTY; + + return DoCreateProperty(name, value, eType, bReadOnly, pAct, isPreInitProperty); +} + +int MM::PropertyCollection::DoCreateProperty(const char* pszName, const char* pszValue, MM::PropertyType eType, bool bReadOnly, MM::ActionFunctor* pAct, bool isPreInitProperty) { // check if the name already exists if (Find(pszName)) diff --git a/MMDevice/Property.h b/MMDevice/Property.h index 24c1a8920..320141695 100644 --- a/MMDevice/Property.h +++ b/MMDevice/Property.h @@ -30,6 +30,48 @@ namespace MM { +// Standard Properties +const char* const g_KeywordStandardPropertyPrefix = "api"; + +// Define NaN for use in property definitions +const double PropertyLimitUndefined = std::numeric_limits::quiet_NaN(); + +// Standard property metadata structure +class StandardProperty { +public: + StandardProperty( + const char* name, + PropertyType type, + bool isReadOnly, + bool isPreInit, + const char* const* allowedValues, + double lowerLimit, + double upperLimit + ) : + name(name), + type(type), + isReadOnly(isReadOnly), + isPreInit(isPreInit), + allowedValues(allowedValues), + lowerLimit(lowerLimit), + upperLimit(upperLimit) + {} + + // Helper to check if limits are defined + bool hasLimits() const { + return !std::isnan(lowerLimit) && !std::isnan(upperLimit); + } + + const char* name; // Full property name (without prefix) + PropertyType type; // Float, String, or Integer + bool isReadOnly; // Whether property is read-only + bool isPreInit; // Whether property should be set before initialization + const char* const* allowedValues; // Array of allowed string values (nullptr if not restricted) + double lowerLimit; // Lower limit for numeric properties (NaN if not limited) + double upperLimit; // Upper limit for numeric properties (NaN if not limited) +}; + + /** * Base API for all device properties. * This interface is used by action functors. @@ -437,6 +479,7 @@ class PropertyCollection PropertyCollection(); ~PropertyCollection(); + int CreateStandardProperty(StandardProperty property, const char* value, ActionFunctor* pAct=0); int CreateProperty(const char* name, const char* value, PropertyType eType, bool bReadOnly, ActionFunctor* pAct=0, bool isPreInitProperty=false); int RegisterAction(const char* name, ActionFunctor* fpAct); int SetAllowedValues(const char* name, std::vector& values); @@ -457,9 +500,9 @@ class PropertyCollection int Apply(const char* Name); private: + int DoCreateProperty(const char* name, const char* value, PropertyType eType, bool bReadOnly, ActionFunctor* pAct=0, bool isPreInitProperty=false); typedef std::map CPropArray; CPropArray properties_; }; - } // namespace MM From 19319939017c6877052010de37a6002d10235761 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Thu, 27 Feb 2025 15:26:19 -0800 Subject: [PATCH 02/19] Working with runtime checks for required properties --- DeviceAdapters/DemoCamera/DemoCamera.cpp | 43 +++++ DeviceAdapters/DemoCamera/DemoCamera.h | 2 + MMCore/Devices/DeviceInstance.cpp | 8 + MMDevice/DeviceBase.h | 182 +++++++++++++----- MMDevice/MMDevice-SharedRuntime.vcxproj | 3 +- .../MMDevice-SharedRuntime.vcxproj.filters | 5 +- MMDevice/MMDevice-StaticRuntime.vcxproj | 3 +- .../MMDevice-StaticRuntime.vcxproj.filters | 5 +- MMDevice/MMDevice.cpp | 51 ----- MMDevice/MMDevice.h | 117 +++++++---- MMDevice/Property.cpp | 27 +-- MMDevice/Property.h | 53 +++-- 12 files changed, 313 insertions(+), 186 deletions(-) delete mode 100644 MMDevice/MMDevice.cpp diff --git a/DeviceAdapters/DemoCamera/DemoCamera.cpp b/DeviceAdapters/DemoCamera/DemoCamera.cpp index cde5082f7..c47eb1826 100644 --- a/DeviceAdapters/DemoCamera/DemoCamera.cpp +++ b/DeviceAdapters/DemoCamera/DemoCamera.cpp @@ -308,6 +308,16 @@ int CDemoCamera::Initialize() else LogMessage(NoHubError); + // Standard properties + CPropertyAction *pActsp = new CPropertyAction (this, &CDemoCamera::OnTestStandardProperty); + int nRett = CreateTestStandardProperty("123", pActsp); + assert(nRett == DEVICE_OK); + + CPropertyAction *pActsp2 = new CPropertyAction (this, &CDemoCamera::OnTestWithValuesStandardProperty); + int nRettt = CreateTestWithValuesStandardProperty("value1", pActsp2); + assert(nRettt == DEVICE_OK); + + // set property list // ----------------- @@ -1384,6 +1394,39 @@ int CDemoCamera::OnBinning(MM::PropertyBase* pProp, MM::ActionType eAct) return ret; } +int CDemoCamera::OnTestStandardProperty(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set("test"); + return DEVICE_OK; + } + else if (eAct == MM::AfterSet) + { + std::string val; + pProp->Get(val); + return DEVICE_OK; + } + return DEVICE_OK; +} + +int CDemoCamera::OnTestWithValuesStandardProperty(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set("value1"); + return DEVICE_OK; + } + else if (eAct == MM::AfterSet) + { + std::string val; + pProp->Get(val); + return DEVICE_OK; + } + return DEVICE_OK; +} + + /** * Handles "PixelType" property. */ diff --git a/DeviceAdapters/DemoCamera/DemoCamera.h b/DeviceAdapters/DemoCamera/DemoCamera.h index 1de99da9b..5e7a115ee 100644 --- a/DeviceAdapters/DemoCamera/DemoCamera.h +++ b/DeviceAdapters/DemoCamera/DemoCamera.h @@ -175,6 +175,8 @@ class CDemoCamera : public CCameraBase // ---------------- int OnMaxExposure(MM::PropertyBase* pProp, MM::ActionType eAct); int OnTestProperty(MM::PropertyBase* pProp, MM::ActionType eAct, long); + int OnTestStandardProperty(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnTestWithValuesStandardProperty(MM::PropertyBase* pProp, MM::ActionType eAct); int OnAsyncFollower(MM::PropertyBase* pProp, MM::ActionType eAct); int OnAsyncLeader(MM::PropertyBase* pProp, MM::ActionType eAct); void SlowPropUpdate(std::string leaderValue); diff --git a/MMCore/Devices/DeviceInstance.cpp b/MMCore/Devices/DeviceInstance.cpp index a36ea75b7..cbee0d2a6 100644 --- a/MMCore/Devices/DeviceInstance.cpp +++ b/MMCore/Devices/DeviceInstance.cpp @@ -376,6 +376,14 @@ DeviceInstance::Initialize() ThrowError("Device already initialized (or initialization already attempted)"); initializeCalled_ = true; ThrowIfError(pImpl_->Initialize()); + + // Check for that all required standard properties implemented + char failedProperty[MM::MaxStrLength]; + if (!pImpl_->ImplementsRequiredStandardProperties(failedProperty)) { + ThrowError("Device " + GetLabel() + + " does not implement required standard property: " + + std::string(failedProperty)); + } initialized_ = true; } diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 73ca0de03..2865cefe4 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -38,6 +38,7 @@ #include #include #include +#include // common error messages const char* const g_Msg_ERR = "Unknown error in the device"; @@ -569,6 +570,38 @@ class CDeviceBase : public T return true; } + bool ImplementsRequiredStandardProperties(char* failedProperty) const { + // Get the device type + MM::DeviceType deviceType = this->GetType(); + + // Look up properties for this device type + auto it = MM::GetDeviceTypeStandardPropertiesMap().find(deviceType); + if (it != MM::GetDeviceTypeStandardPropertiesMap().end()) { + // Iterate through all properties for this device type + const auto& properties = it->second; + for (const auto& prop : properties) { + // Check if this property is required + if (prop.required) { + // Construct the full property name with prefix + std::string fullName = MM::g_KeywordStandardPropertyPrefix; + fullName += prop.name; + + // Check if the device has implemented it + if (!HasProperty(fullName.c_str())) { + // If not, copy in the name of the property and return false + CDeviceUtils::CopyLimitedString(failedProperty, fullName.c_str()); + return false; + } + } + } + } + + // All required properties are implemented + return true; + } + + + /** * Creates a new property for the device. * @param name - property name @@ -584,60 +617,16 @@ class CDeviceBase : public T return properties_.CreateProperty(name, value, eType, readOnly, pAct, isPreInitProperty); } - /** - * Creates a standard property -- a property with a predefined name, type, and possibly values. - * Standard properties can apply to all devices, or only some types, as specified in the - * switch statement below. - * Type, read-only and pre-init are fixed for the standard property - * - * @param property - the standard property enum - * @param value - initial value - * @param pAct - function object called on the property actions - */ - int CreateStandardProperty(MM::StandardProperty property, const char* value, MM::ActionFunctor* pAct = 0) - { - if (!IsValidStandardProperty(property)) { - return DEVICE_INVALID_PROPERTY; - } - - // TODO: check if value is valid? - - // Prepend the prefix marking it as a standard property and the delimiter - std::string fullName = g_KeywordStandardPropertyPrefix + "//" + property.name; - - return properties_.CreateStandardProperty(property, value, pAct); - } - - /** - * Checks if a standard property is applicable to this device type - * @param property - the standard property to check - * @return - true if applicable, false otherwise - */ - bool IsValidStandardProperty(MM::StandardProperty property) const - { - MM::DeviceType deviceType = T::Type; - return IsPropertyApplicableToDeviceType(property, deviceType); + //// Standard properties are created using only these dedicated functions + // Such functions should all be defined here, and which device types they apply + // to is handled in MMDevice.h using the LINK_STANDARD_PROP_TO_DEVICE_TYPE macro + int CreateTestStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { + return CreateStandardProperty(value, pAct); } - /** - * Helper function to check if a standard property is applicable to a specific device type - * @param property - the standard property to check - * @param deviceType - the device type to check against - * @return - true if the property is applicable to the device type, false otherwise - */ - bool IsPropertyApplicableToDeviceType(MM::StandardProperty property, MM::DeviceType deviceType) const - { - // Iterate through the standard property list to find if this property - // is applicable to this device type - for (const MM::StandardPropAssociation& association : MM::g_StandardPropertyList) - { - if (strcmp(association.property.name, property.name) == 0 && - association.deviceType == deviceType) - { - return true; - } - } - return false; + int CreateTestWithValuesStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { + return CreateStandardProperty(value, pAct, + MM::g_TestWithValuesStandardProperty.requiredValues); } /** @@ -1276,6 +1265,95 @@ class CDeviceBase : public T } private: + + // template + // struct TypePrinter; + + // // This will cause a compilation error that shows the type + // TypePrinter show_type; + + /** + * Low-level implementation for creating standard properties. + * + * This template method uses SFINAE (Substitution Failure Is Not An Error) to ensure + * that standard properties can only be created for device types they're valid for. + * The IsStandardPropertyValid template specializations determine which properties + * are valid for which device types at compile time. + * + * Note: This is a private implementation method. Device implementations should use + * the specific convenience methods like CreateStandardBinningProperty() instead. + * + * @param PropPtr - Pointer to the standard property definition + * @param value - Initial value for the property + * @param pAct - Optional action functor to handle property changes + * @return DEVICE_OK if successful, error code otherwise + */ + template + typename std::enable_if::value, int>::type + CreateStandardProperty(const char* value, MM::ActionFunctor* pAct = 0, const std::vector& values = {}) { + + // Create the full property name with prefix + std::string fullName = MM::g_KeywordStandardPropertyPrefix; + fullName += PropRef.name; + + // Create the property with all appropriate fields + int ret = properties_.CreateProperty(fullName.c_str(), value, PropRef.type, + PropRef.isReadOnly, pAct, PropRef.isPreInit, true); + if (ret != DEVICE_OK) + return ret; + + // Set limits if they exist + if (PropRef.hasLimits()) { + ret = SetPropertyLimits(fullName.c_str(), PropRef.lowerLimit, PropRef.upperLimit); + if (ret != DEVICE_OK) + return ret; + } + + // Set allowed values if they exist + if (!PropRef.allowedValues.empty()) { + // If the property has predefined allowed values, validate that all supplied values are allowed + if (std::find(PropRef.allowedValues.begin(), PropRef.allowedValues.end(), value) == PropRef.allowedValues.end()) { + return DEVICE_INVALID_PROPERTY_VALUE; + } + + for (const std::string& val : values) { + if (std::find(PropRef.allowedValues.begin(), PropRef.allowedValues.end(), val) == PropRef.allowedValues.end()) { + return DEVICE_INVALID_PROPERTY_VALUE; + } + } + } + // if there are required values, make sure they are all present + if (!PropRef.requiredValues.empty()) { + // throw an error if required values are not present + for (const std::string& val : PropRef.requiredValues) { + if (std::find(values.begin(), values.end(), val) == values.end()) { + return DEVICE_INVALID_PROPERTY_VALUE; + } + } + } + // now that all required values are present, and all supplied values are allowed, + // add the user-supplied values to the property + for (const std::string& val : values) { + ret = AddAllowedValue(fullName.c_str(), val.c_str()); + if (ret != DEVICE_OK) + return ret; + } + + return DEVICE_OK; + } + + // This one is purely for providing better error messages at compile time + // When an function for setting an invalid standard property is called, + // this function will be called and will cause a compilation error. + template + typename std::enable_if::value, int>::type + CreateStandardProperty(const char* /*value*/, MM::ActionFunctor* /*pAct*/ = 0, + const std::vector& /*values*/ = std::vector()) { + static_assert(MM::IsStandardPropertyValid::value, + "This standard property is not valid for this device type. Check the LINK_STANDARD_PROP_TO_DEVICE_TYPE definitions in MMDevice.h"); + return DEVICE_UNSUPPORTED_COMMAND; // This line will never execute due to the static_assert + } + bool PropertyDefined(const char* propName) const { return properties_.Find(propName) != 0; diff --git a/MMDevice/MMDevice-SharedRuntime.vcxproj b/MMDevice/MMDevice-SharedRuntime.vcxproj index 8df751203..a0ff43c76 100644 --- a/MMDevice/MMDevice-SharedRuntime.vcxproj +++ b/MMDevice/MMDevice-SharedRuntime.vcxproj @@ -14,7 +14,6 @@ - @@ -88,4 +87,4 @@ - + \ No newline at end of file diff --git a/MMDevice/MMDevice-SharedRuntime.vcxproj.filters b/MMDevice/MMDevice-SharedRuntime.vcxproj.filters index 2f38adf00..80e136161 100644 --- a/MMDevice/MMDevice-SharedRuntime.vcxproj.filters +++ b/MMDevice/MMDevice-SharedRuntime.vcxproj.filters @@ -20,9 +20,6 @@ Source Files - - Source Files - Source Files @@ -62,4 +59,4 @@ Header Files - + \ No newline at end of file diff --git a/MMDevice/MMDevice-StaticRuntime.vcxproj b/MMDevice/MMDevice-StaticRuntime.vcxproj index b1317341a..0770c8ce7 100644 --- a/MMDevice/MMDevice-StaticRuntime.vcxproj +++ b/MMDevice/MMDevice-StaticRuntime.vcxproj @@ -14,7 +14,6 @@ - @@ -90,4 +89,4 @@ - + \ No newline at end of file diff --git a/MMDevice/MMDevice-StaticRuntime.vcxproj.filters b/MMDevice/MMDevice-StaticRuntime.vcxproj.filters index 2f38adf00..80e136161 100644 --- a/MMDevice/MMDevice-StaticRuntime.vcxproj.filters +++ b/MMDevice/MMDevice-StaticRuntime.vcxproj.filters @@ -20,9 +20,6 @@ Source Files - - Source Files - Source Files @@ -62,4 +59,4 @@ Header Files - + \ No newline at end of file diff --git a/MMDevice/MMDevice.cpp b/MMDevice/MMDevice.cpp deleted file mode 100644 index 36c82ef97..000000000 --- a/MMDevice/MMDevice.cpp +++ /dev/null @@ -1,51 +0,0 @@ -// AUTHOR: Mark Tsuchida, May 2014 -// -// COPYRIGHT: University of California, San Francisco, 2014 -// -// LICENSE: This file is distributed under the BSD license. -// License text is included with the source distribution. -// -// This file 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. -// -// IN NO EVENT SHALL THE COPYRIGHT OWNER OR -// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, -// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES. - -#include "MMDevice.h" - -namespace MM { - -// Definitions for static const data members. -// -// Note: Do not try to move these initializers to the header. The C++ standard -// allows initializing a static const enum data member inline (inside the class -// definition, where the member is _declared_), but still requires a -// _definition_ (in which case, the definition should not have an initializer). -// However, Microsoft VC++ has a nonstandard extension that allows you to leave -// out the definition altogether, if an initializer is supplied at the -// declaration. Because of that nonstandard behavior, VC++ issues a warning -// (LNK4006) if the initializer is supplied with the declaration _and_ a -// definition is (correctly) provided. So, to compile correctly with a -// standards-conformant compiler _and_ avoid warnings from VC++, we need to -// leave the initializers out of the declarations, and supply them here with -// the definitions. See: -// http://connect.microsoft.com/VisualStudio/feedback/details/802091/lnk4006-reported-for-static-const-members-that-is-initialized-in-the-class-definition - -const DeviceType Generic::Type = GenericDevice; -const DeviceType Camera::Type = CameraDevice; -const DeviceType Shutter::Type = ShutterDevice; -const DeviceType Stage::Type = StageDevice; -const DeviceType XYStage::Type = XYStageDevice; -const DeviceType State::Type = StateDevice; -const DeviceType Serial::Type = SerialDevice; -const DeviceType AutoFocus::Type = AutoFocusDevice; -const DeviceType ImageProcessor::Type = ImageProcessorDevice; -const DeviceType SignalIO::Type = SignalIODevice; -const DeviceType Magnifier::Type = MagnifierDevice; -const DeviceType SLM::Type = SLMDevice; -const DeviceType Galvo::Type = GalvoDevice; -const DeviceType Hub::Type = HubDevice; - -} // namespace MM diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index 795407057..7a8d345da 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -48,10 +48,12 @@ #include #include #include +#include #include #include #include #include +#include // To be removed once the deprecated Get/SetModuleHandle() is removed: #ifdef _WIN32 @@ -209,35 +211,83 @@ namespace MM { //////////////////////////// ///// Standard properties //////////////////////////// + + // Map from device types to standard properties + inline std::unordered_map>& GetDeviceTypeStandardPropertiesMap() { + static std::unordered_map> devicePropertiesMap; + return devicePropertiesMap; + } + + // Register a standard property with its valid device types + inline void RegisterStandardProperty(const StandardProperty& prop, std::initializer_list deviceTypes) { + for (MM::DeviceType deviceType : deviceTypes) { + GetDeviceTypeStandardPropertiesMap()[deviceType].push_back(prop); + } + } + + // Check if a property is valid for a specific device type + inline bool IsPropertyValidForDeviceType(const StandardProperty& prop, MM::DeviceType deviceType) { + auto it = GetDeviceTypeStandardPropertiesMap().find(deviceType); + if (it == GetDeviceTypeStandardPropertiesMap().end()) { + return false; + } + + const auto& validProperties = it->second; + return std::find(validProperties.begin(), validProperties.end(), prop) != validProperties.end(); + } // Specific standard properties - static const MM::StandardProperty g_BinningStandardProperty( - "Binning", // name + static const MM::StandardProperty g_TestStandardProperty( + "Test", // name String, // type - false, // isReadOnly - false, // isPreInit - nullptr, // allowedValues + false, // isReadOnly + false, // isPreInit + {}, // allowedValues (empty vector) + {}, // requiredValues (empty vector) PropertyLimitUndefined, // lowerLimit - PropertyLimitUndefined // upperLimit + PropertyLimitUndefined, // upperLimit + false // required ); - // A struct to encode which which device types have/require which standard properties - struct StandardPropAssociation { - const MM::StandardProperty& property; - MM::DeviceType deviceType; // which device types can use - bool required; - }; + static const std::vector testRequiredValues = {"value1", "value2", "value3"}; + static const MM::StandardProperty g_TestWithValuesStandardProperty( + "TestWithValues", // name + String, // type + false, // isReadOnly + false, // isPreInit + {}, // allowedValues (empty vector) + testRequiredValues, // requiredValues + PropertyLimitUndefined, // lowerLimit + PropertyLimitUndefined, // upperLimit + false // required + ); - // This data structure associates standard properties with device types. - // each entry defines one property-device association and whether the - // standard property is required - static const StandardPropAssociation g_StandardPropertyList[] = - { - // propertyname deviceTypes required - {g_BinningStandardProperty, MM::CameraDevice, false} + + // The below code is a way to enable compile time checking of which + // standard properties are are valid for which device types. This enables + // methods for setting these standard properties defined once + // in CDeviceBase and then conditionally enabled in child classes based on + // the LINK_STANDARD_PROP_TO_DEVICE_TYPE macro below + // In addition to making this link, a dedicated method for the standard property + // should be created in CDeviceBase. + template + struct IsStandardPropertyValid { + static const bool value = false; // Default to false }; + #define LINK_STANDARD_PROP_TO_DEVICE_TYPE(DeviceType, PropertyRef) \ + template <> \ + struct IsStandardPropertyValid { \ + static const bool value = true; \ + }; \ + namespace { \ + static const bool PropertyRef##_registered = (RegisterStandardProperty(PropertyRef, {DeviceType}), true); \ + } + + // This determines which standard properties are valid for which device types + LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestStandardProperty) + LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestWithValuesStandardProperty) /** * Generic device interface. @@ -260,6 +310,7 @@ namespace MM { virtual int GetPropertyType(const char* name, MM::PropertyType& pt) const = 0; virtual unsigned GetNumberOfPropertyValues(const char* propertyName) const = 0; virtual bool GetPropertyValueAt(const char* propertyName, unsigned index, char* value) const = 0; + virtual bool ImplementsRequiredStandardProperties(char* failedProperty) const = 0; /** * Sequences can be used for fast acquisitions, synchronized by TTLs rather than * computer commands. @@ -343,7 +394,7 @@ namespace MM { { public: virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = GenericDevice; }; /** @@ -355,7 +406,7 @@ namespace MM { virtual ~Camera() {} virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = CameraDevice; // Camera API /** @@ -591,7 +642,7 @@ namespace MM { // Device API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = ShutterDevice; // Shutter API virtual int SetOpen(bool open = true) = 0; @@ -614,7 +665,7 @@ namespace MM { // Device API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = StageDevice; // Stage API virtual int SetPositionUm(double pos) = 0; @@ -714,7 +765,7 @@ namespace MM { // Device API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = XYStageDevice; // XYStage API // it is recommended that device adapters implement the "Steps" methods @@ -800,7 +851,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = StateDevice; // MMStateDevice API virtual int SetPosition(long pos) = 0; @@ -826,7 +877,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = SerialDevice; // Serial API virtual PortType GetPortType() const = 0; @@ -848,7 +899,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = AutoFocusDevice; // AutoFocus API virtual int SetContinuousFocusing(bool state) = 0; @@ -874,7 +925,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = ImageProcessorDevice; // image processor API virtual int Process(unsigned char* buffer, unsigned width, unsigned height, unsigned byteDepth) = 0; @@ -893,7 +944,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = SignalIODevice; // signal io API virtual int SetGateOpen(bool open = true) = 0; @@ -977,7 +1028,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = MagnifierDevice; virtual double GetMagnification() = 0; }; @@ -997,7 +1048,7 @@ namespace MM { virtual ~SLM() {} virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = SLMDevice; // SLM API /** @@ -1156,7 +1207,7 @@ namespace MM { virtual ~Galvo() {} virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = GalvoDevice; //Galvo API: @@ -1283,7 +1334,7 @@ namespace MM { // MMDevice API virtual DeviceType GetType() const { return Type; } - static const DeviceType Type; + static constexpr DeviceType Type = HubDevice; /** * Instantiate all available child peripheral devices. diff --git a/MMDevice/Property.cpp b/MMDevice/Property.cpp index 5cd5b7d78..eff757530 100644 --- a/MMDevice/Property.cpp +++ b/MMDevice/Property.cpp @@ -326,28 +326,21 @@ unsigned MM::PropertyCollection::GetSize() const return (unsigned) properties_.size(); } -int MM::PropertyCollection::CreateStandardProperty(StandardProperty property, const char* value, ActionFunctor* pAct) -{ - return DoCreateProperty(property.name, value, property.type, property.isReadOnly, pAct, property.isPreInit); -} - -int MM::PropertyCollection::CreateProperty(const char* name, const char* value, PropertyType eType, bool bReadOnly, ActionFunctor* pAct=0, bool isPreInitProperty=false) -{ - // disallow properties that begin with the reserved prefix for standard properties - // std prefic + delimiter + property name - std::string prefixAndDelim = std::string(g_KeywordStandardPropertyPrefix) + std::string("//"); - if (std::string(name).find(prefixAndDelim) == 0) - return DEVICE_INVALID_PROPERTY; - - return DoCreateProperty(name, value, eType, bReadOnly, pAct, isPreInitProperty); -} - -int MM::PropertyCollection::DoCreateProperty(const char* pszName, const char* pszValue, MM::PropertyType eType, bool bReadOnly, MM::ActionFunctor* pAct, bool isPreInitProperty) +int MM::PropertyCollection::CreateProperty(const char* pszName, const char* pszValue, MM::PropertyType eType, + bool bReadOnly, MM::ActionFunctor* pAct, bool isPreInitProperty, bool standard) { // check if the name already exists if (Find(pszName)) return DEVICE_DUPLICATE_PROPERTY; + if (!standard) + { + // make sure it doesn't begin with the reserved prefix for standard properties + std::string prefixAndDelim = std::string(g_KeywordStandardPropertyPrefix); + if (std::string(pszName).find(prefixAndDelim) == 0) + return DEVICE_INVALID_PROPERTY; + } + MM::Property* pProp=0; switch(eType) diff --git a/MMDevice/Property.h b/MMDevice/Property.h index 320141695..0494bdb07 100644 --- a/MMDevice/Property.h +++ b/MMDevice/Property.h @@ -27,11 +27,12 @@ #include #include #include +#include namespace MM { // Standard Properties -const char* const g_KeywordStandardPropertyPrefix = "api"; +const char* const g_KeywordStandardPropertyPrefix = "api//"; // Define NaN for use in property definitions const double PropertyLimitUndefined = std::numeric_limits::quiet_NaN(); @@ -39,22 +40,19 @@ const double PropertyLimitUndefined = std::numeric_limits::quiet_NaN(); // Standard property metadata structure class StandardProperty { public: - StandardProperty( - const char* name, - PropertyType type, - bool isReadOnly, - bool isPreInit, - const char* const* allowedValues, - double lowerLimit, - double upperLimit - ) : + StandardProperty(const char* name, PropertyType type, bool isReadOnly, + bool isPreInit, const std::vector& allowedValues, + const std::vector& requiredValues, + double lowerLimit, double upperLimit, bool required) : name(name), type(type), isReadOnly(isReadOnly), isPreInit(isPreInit), allowedValues(allowedValues), + requiredValues(requiredValues), lowerLimit(lowerLimit), - upperLimit(upperLimit) + upperLimit(upperLimit), + required(required) {} // Helper to check if limits are defined @@ -62,13 +60,28 @@ class StandardProperty { return !std::isnan(lowerLimit) && !std::isnan(upperLimit); } - const char* name; // Full property name (without prefix) - PropertyType type; // Float, String, or Integer - bool isReadOnly; // Whether property is read-only - bool isPreInit; // Whether property should be set before initialization - const char* const* allowedValues; // Array of allowed string values (nullptr if not restricted) - double lowerLimit; // Lower limit for numeric properties (NaN if not limited) - double upperLimit; // Upper limit for numeric properties (NaN if not limited) + // Equality operator for comparison in containers + bool operator==(const StandardProperty& other) const { + return name == other.name && + type == other.type && + isReadOnly == other.isReadOnly && + isPreInit == other.isPreInit && + allowedValues == other.allowedValues && + requiredValues == other.requiredValues && + lowerLimit == other.lowerLimit && + upperLimit == other.upperLimit && + required == other.required; + } + + std::string name; // Full property name (without prefix) + PropertyType type; // Float, String, or Integer + bool isReadOnly; // Whether property is read-only + bool isPreInit; // Whether property should be set before initialization + std::vector allowedValues; // if empty, no restrictions + std::vector requiredValues; // if empty, no restrictions + double lowerLimit; // Lower limit for numeric properties (NaN if not limited) + double upperLimit; // Upper limit for numeric properties (NaN if not limited) + bool required; // Whether to throw a runtime error if the property is not found on init }; @@ -479,8 +492,7 @@ class PropertyCollection PropertyCollection(); ~PropertyCollection(); - int CreateStandardProperty(StandardProperty property, const char* value, ActionFunctor* pAct=0); - int CreateProperty(const char* name, const char* value, PropertyType eType, bool bReadOnly, ActionFunctor* pAct=0, bool isPreInitProperty=false); + int CreateProperty(const char* name, const char* value, PropertyType eType, bool bReadOnly, ActionFunctor* pAct=0, bool isPreInitProperty=false, bool standard=false); int RegisterAction(const char* name, ActionFunctor* fpAct); int SetAllowedValues(const char* name, std::vector& values); int ClearAllowedValues(const char* name); @@ -500,7 +512,6 @@ class PropertyCollection int Apply(const char* Name); private: - int DoCreateProperty(const char* name, const char* value, PropertyType eType, bool bReadOnly, ActionFunctor* pAct=0, bool isPreInitProperty=false); typedef std::map CPropArray; CPropArray properties_; }; From 30de537067bb71bf38a3e0474258c3046d746d71 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Thu, 27 Feb 2025 15:28:33 -0800 Subject: [PATCH 03/19] add comment --- MMDevice/DeviceBase.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 2865cefe4..275e8f67e 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -625,6 +625,8 @@ class CDeviceBase : public T } int CreateTestWithValuesStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { + // just make the values the required ones here. Also option to add + // additional ones in real situations return CreateStandardProperty(value, pAct, MM::g_TestWithValuesStandardProperty.requiredValues); } From 8789d6703cd446e6d7d3e92993767a1965565943 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 09:15:33 -0800 Subject: [PATCH 04/19] cleanup --- MMDevice/DeviceBase.h | 6 ---- MMDevice/MMDevice.h | 67 +++++++++++++++++++++++++++---------------- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 275e8f67e..0a3679c9b 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -1268,12 +1268,6 @@ class CDeviceBase : public T private: - // template - // struct TypePrinter; - - // // This will cause a compilation error that shows the type - // TypePrinter show_type; - /** * Low-level implementation for creating standard properties. * diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index 7a8d345da..07697e3a7 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -212,6 +212,8 @@ namespace MM { ///// Standard properties //////////////////////////// + //// Helper functions/macros for compile time and runtime checking of standard properties + // Map from device types to standard properties inline std::unordered_map>& GetDeviceTypeStandardPropertiesMap() { static std::unordered_map> devicePropertiesMap; @@ -236,6 +238,42 @@ namespace MM { return std::find(validProperties.begin(), validProperties.end(), prop) != validProperties.end(); } + // The below code is a way to enable compile time checking of which + // standard properties are are valid for which device types. This enables + // methods for setting these standard properties defined once + // in CDeviceBase and then conditionally enabled in child classes based on + // the LINK_STANDARD_PROP_TO_DEVICE_TYPE macro below + // In addition to making this link, a dedicated method for the standard property + // should be created in CDeviceBase. + template + struct IsStandardPropertyValid { + static const bool value = false; // Default to false + }; + + // The top struct enables compile time checking of standard property + // creation methods (ie that you can call the CreateXXXXStandardProperty + // in a device type that doesn't support it) + // The bottom part adds the standard property to a runtime registry + // which enables higher level code the query the standard properties + // associated with a particular device type and check that everything + // is correct (ie all required properties are implemented after + // device initialization) + #define LINK_STANDARD_PROP_TO_DEVICE_TYPE(DeviceType, PropertyRef) \ + template <> \ + struct IsStandardPropertyValid { \ + static const bool value = true; \ + }; \ + namespace { \ + static const bool PropertyRef##_registered = (RegisterStandardProperty(PropertyRef, {DeviceType}), true); \ + } + + /////// Standard property definitions + // Each standard property is defined here, and then linked to one or more + // device types using the LINK_STANDARD_PROP_TO_DEVICE_TYPE macro. + // This allows compile time checking that the property is valid for a + // particular device type, and also allows runtime querying of which + // properties are supported by a given device. + // Specific standard properties static const MM::StandardProperty g_TestStandardProperty( "Test", // name @@ -249,6 +287,9 @@ namespace MM { false // required ); + LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestStandardProperty) + + static const std::vector testRequiredValues = {"value1", "value2", "value3"}; static const MM::StandardProperty g_TestWithValuesStandardProperty( "TestWithValues", // name @@ -262,33 +303,9 @@ namespace MM { false // required ); - - // The below code is a way to enable compile time checking of which - // standard properties are are valid for which device types. This enables - // methods for setting these standard properties defined once - // in CDeviceBase and then conditionally enabled in child classes based on - // the LINK_STANDARD_PROP_TO_DEVICE_TYPE macro below - // In addition to making this link, a dedicated method for the standard property - // should be created in CDeviceBase. - template - struct IsStandardPropertyValid { - static const bool value = false; // Default to false - }; - - - #define LINK_STANDARD_PROP_TO_DEVICE_TYPE(DeviceType, PropertyRef) \ - template <> \ - struct IsStandardPropertyValid { \ - static const bool value = true; \ - }; \ - namespace { \ - static const bool PropertyRef##_registered = (RegisterStandardProperty(PropertyRef, {DeviceType}), true); \ - } - - // This determines which standard properties are valid for which device types - LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestStandardProperty) LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestWithValuesStandardProperty) + /** * Generic device interface. */ From 5759cc0985e86e3bc9bd69228de17e0037679c8f Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 09:17:51 -0800 Subject: [PATCH 05/19] more cleanup --- MMDevice/DeviceBase.h | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 0a3679c9b..8293fd9ac 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -118,6 +118,20 @@ class CDeviceBase : public T CDeviceUtils::CopyLimitedString(name, moduleName_.c_str()); } + //// Standard properties are created using only these dedicated functions + // Such functions should all be defined here, and which device types they apply + // to is handled in MMDevice.h using the LINK_STANDARD_PROP_TO_DEVICE_TYPE macro + int CreateTestStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { + return CreateStandardProperty(value, pAct); + } + + int CreateTestWithValuesStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { + // just make the values the required ones here. Also option to add + // additional ones in real situations + return CreateStandardProperty(value, pAct, + MM::g_TestWithValuesStandardProperty.requiredValues); + } + /** * Assigns description string for a device (for use only by the calling code). */ @@ -617,20 +631,6 @@ class CDeviceBase : public T return properties_.CreateProperty(name, value, eType, readOnly, pAct, isPreInitProperty); } - //// Standard properties are created using only these dedicated functions - // Such functions should all be defined here, and which device types they apply - // to is handled in MMDevice.h using the LINK_STANDARD_PROP_TO_DEVICE_TYPE macro - int CreateTestStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { - return CreateStandardProperty(value, pAct); - } - - int CreateTestWithValuesStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { - // just make the values the required ones here. Also option to add - // additional ones in real situations - return CreateStandardProperty(value, pAct, - MM::g_TestWithValuesStandardProperty.requiredValues); - } - /** * Creates a new property for the device. * @param name - property name From 88833b169666b17847f5635e78562eebc115de3f Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 28 Feb 2025 09:47:38 -0800 Subject: [PATCH 06/19] change comment --- MMDevice/DeviceBase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 8293fd9ac..ef8979d90 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -1305,7 +1305,7 @@ class CDeviceBase : public T return ret; } - // Set allowed values if they exist + // Ensure only allowed values are set if (!PropRef.allowedValues.empty()) { // If the property has predefined allowed values, validate that all supplied values are allowed if (std::find(PropRef.allowedValues.begin(), PropRef.allowedValues.end(), value) == PropRef.allowedValues.end()) { From e5ea9ce51e49d64e3eb9dc97459dfc9da94c3d62 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Tue, 4 Mar 2025 11:22:19 -0800 Subject: [PATCH 07/19] add code review fixes --- MMCore/Devices/DeviceInstance.cpp | 4 +- MMDevice/DeviceBase.h | 8 +-- MMDevice/MMDevice.h | 108 +++++++++++++++--------------- MMDevice/Property.h | 24 ++----- 4 files changed, 66 insertions(+), 78 deletions(-) diff --git a/MMCore/Devices/DeviceInstance.cpp b/MMCore/Devices/DeviceInstance.cpp index cbee0d2a6..bc21bda20 100644 --- a/MMCore/Devices/DeviceInstance.cpp +++ b/MMCore/Devices/DeviceInstance.cpp @@ -380,7 +380,9 @@ DeviceInstance::Initialize() // Check for that all required standard properties implemented char failedProperty[MM::MaxStrLength]; if (!pImpl_->ImplementsRequiredStandardProperties(failedProperty)) { - ThrowError("Device " + GetLabel() + + // shutdown the device + Shutdown(); + ThrowError("Device " + GetLabel() + " does not implement required standard property: " + std::string(failedProperty)); } diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index ef8979d90..9a9a2d6e1 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -120,7 +120,7 @@ class CDeviceBase : public T //// Standard properties are created using only these dedicated functions // Such functions should all be defined here, and which device types they apply - // to is handled in MMDevice.h using the LINK_STANDARD_PROP_TO_DEVICE_TYPE macro + // to is handled in MMDevice.h using the MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE macro int CreateTestStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { return CreateStandardProperty(value, pAct); } @@ -589,8 +589,8 @@ class CDeviceBase : public T MM::DeviceType deviceType = this->GetType(); // Look up properties for this device type - auto it = MM::GetDeviceTypeStandardPropertiesMap().find(deviceType); - if (it != MM::GetDeviceTypeStandardPropertiesMap().end()) { + auto it = MM::internal::GetDeviceTypeStandardPropertiesMap().find(deviceType); + if (it != MM::internal::GetDeviceTypeStandardPropertiesMap().end()) { // Iterate through all properties for this device type const auto& properties = it->second; for (const auto& prop : properties) { @@ -1346,7 +1346,7 @@ class CDeviceBase : public T CreateStandardProperty(const char* /*value*/, MM::ActionFunctor* /*pAct*/ = 0, const std::vector& /*values*/ = std::vector()) { static_assert(MM::IsStandardPropertyValid::value, - "This standard property is not valid for this device type. Check the LINK_STANDARD_PROP_TO_DEVICE_TYPE definitions in MMDevice.h"); + "This standard property is not valid for this device type. Check the MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE definitions in MMDevice.h"); return DEVICE_UNSUPPORTED_COMMAND; // This line will never execute due to the static_assert } diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index 07697e3a7..8ecf53398 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -212,64 +212,66 @@ namespace MM { ///// Standard properties //////////////////////////// - //// Helper functions/macros for compile time and runtime checking of standard properties - - // Map from device types to standard properties - inline std::unordered_map>& GetDeviceTypeStandardPropertiesMap() { - static std::unordered_map> devicePropertiesMap; - return devicePropertiesMap; - } - - // Register a standard property with its valid device types - inline void RegisterStandardProperty(const StandardProperty& prop, std::initializer_list deviceTypes) { - for (MM::DeviceType deviceType : deviceTypes) { - GetDeviceTypeStandardPropertiesMap()[deviceType].push_back(prop); + namespace internal { + //// Helper functions/macros for compile time and runtime checking of standard properties + + // Map from device types to standard properties + inline std::unordered_map>& GetDeviceTypeStandardPropertiesMap() { + static std::unordered_map> devicePropertiesMap; + return devicePropertiesMap; } - } - // Check if a property is valid for a specific device type - inline bool IsPropertyValidForDeviceType(const StandardProperty& prop, MM::DeviceType deviceType) { - auto it = GetDeviceTypeStandardPropertiesMap().find(deviceType); - if (it == GetDeviceTypeStandardPropertiesMap().end()) { - return false; + // Register a standard property with its valid device types + inline void RegisterStandardProperty(const StandardProperty& prop, std::initializer_list deviceTypes) { + for (MM::DeviceType deviceType : deviceTypes) { + GetDeviceTypeStandardPropertiesMap()[deviceType].push_back(prop); + } } - - const auto& validProperties = it->second; - return std::find(validProperties.begin(), validProperties.end(), prop) != validProperties.end(); - } - - // The below code is a way to enable compile time checking of which - // standard properties are are valid for which device types. This enables - // methods for setting these standard properties defined once - // in CDeviceBase and then conditionally enabled in child classes based on - // the LINK_STANDARD_PROP_TO_DEVICE_TYPE macro below - // In addition to making this link, a dedicated method for the standard property - // should be created in CDeviceBase. - template - struct IsStandardPropertyValid { - static const bool value = false; // Default to false - }; - // The top struct enables compile time checking of standard property - // creation methods (ie that you can call the CreateXXXXStandardProperty - // in a device type that doesn't support it) - // The bottom part adds the standard property to a runtime registry - // which enables higher level code the query the standard properties - // associated with a particular device type and check that everything - // is correct (ie all required properties are implemented after - // device initialization) - #define LINK_STANDARD_PROP_TO_DEVICE_TYPE(DeviceType, PropertyRef) \ - template <> \ - struct IsStandardPropertyValid { \ - static const bool value = true; \ - }; \ - namespace { \ - static const bool PropertyRef##_registered = (RegisterStandardProperty(PropertyRef, {DeviceType}), true); \ - } + // Check if a property is valid for a specific device type + inline bool IsPropertyValidForDeviceType(const StandardProperty& prop, MM::DeviceType deviceType) { + auto it = GetDeviceTypeStandardPropertiesMap().find(deviceType); + if (it == GetDeviceTypeStandardPropertiesMap().end()) { + return false; + } + + const auto& validProperties = it->second; + return std::find(validProperties.begin(), validProperties.end(), prop) != validProperties.end(); + } + + // The below code is a way to enable compile time checking of which + // standard properties are are valid for which device types. This enables + // methods for setting these standard properties defined once + // in CDeviceBase and then conditionally enabled in child classes based on + // the MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE macro below + // In addition to making this link, a dedicated method for the standard property + // should be created in CDeviceBase. + template + struct IsStandardPropertyValid { + static const bool value = false; // Default to false + }; + + // The top struct enables compile time checking of standard property + // creation methods (ie that you can call the CreateXXXXStandardProperty + // in a device type that doesn't support it) + // The bottom part adds the standard property to a runtime registry + // which enables higher level code the query the standard properties + // associated with a particular device type and check that everything + // is correct (ie all required properties are implemented after + // device initialization) + #define MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(DeviceType, PropertyRef) \ + template <> \ + struct IsStandardPropertyValid { \ + static const bool value = true; \ + }; \ + namespace { \ + static const bool PropertyRef##_registered = (RegisterStandardProperty(PropertyRef, {DeviceType}), true); \ + } + } // namespace internal /////// Standard property definitions // Each standard property is defined here, and then linked to one or more - // device types using the LINK_STANDARD_PROP_TO_DEVICE_TYPE macro. + // device types using the MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE macro. // This allows compile time checking that the property is valid for a // particular device type, and also allows runtime querying of which // properties are supported by a given device. @@ -287,7 +289,7 @@ namespace MM { false // required ); - LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestStandardProperty) + MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestStandardProperty) static const std::vector testRequiredValues = {"value1", "value2", "value3"}; @@ -303,7 +305,7 @@ namespace MM { false // required ); - LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestWithValuesStandardProperty) + MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestWithValuesStandardProperty) /** diff --git a/MMDevice/Property.h b/MMDevice/Property.h index 0494bdb07..d8a9443b4 100644 --- a/MMDevice/Property.h +++ b/MMDevice/Property.h @@ -38,23 +38,7 @@ const char* const g_KeywordStandardPropertyPrefix = "api//"; const double PropertyLimitUndefined = std::numeric_limits::quiet_NaN(); // Standard property metadata structure -class StandardProperty { -public: - StandardProperty(const char* name, PropertyType type, bool isReadOnly, - bool isPreInit, const std::vector& allowedValues, - const std::vector& requiredValues, - double lowerLimit, double upperLimit, bool required) : - name(name), - type(type), - isReadOnly(isReadOnly), - isPreInit(isPreInit), - allowedValues(allowedValues), - requiredValues(requiredValues), - lowerLimit(lowerLimit), - upperLimit(upperLimit), - required(required) - {} - +struct StandardProperty { // Helper to check if limits are defined bool hasLimits() const { return !std::isnan(lowerLimit) && !std::isnan(upperLimit); @@ -79,9 +63,9 @@ class StandardProperty { bool isPreInit; // Whether property should be set before initialization std::vector allowedValues; // if empty, no restrictions std::vector requiredValues; // if empty, no restrictions - double lowerLimit; // Lower limit for numeric properties (NaN if not limited) - double upperLimit; // Upper limit for numeric properties (NaN if not limited) - bool required; // Whether to throw a runtime error if the property is not found on init + double lowerLimit = PropertyLimitUndefined; // Lower limit for numeric properties (NaN if not limited) + double upperLimit = PropertyLimitUndefined; // Upper limit for numeric properties (NaN if not limited) + bool required = false; // Whether to throw a runtime error if the property is not found on init }; From 49b01c532eac38c2b03b139af4bf3a87ea62fb8d Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 5 Mar 2025 10:57:46 -0800 Subject: [PATCH 08/19] fix namespace and constructor errors from previous commits --- MMDevice/MMDevice.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index 8ecf53398..3d254af14 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -261,11 +261,11 @@ namespace MM { // device initialization) #define MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(DeviceType, PropertyRef) \ template <> \ - struct IsStandardPropertyValid { \ + struct MM::internal::IsStandardPropertyValid { \ static const bool value = true; \ }; \ namespace { \ - static const bool PropertyRef##_registered = (RegisterStandardProperty(PropertyRef, {DeviceType}), true); \ + static const bool PropertyRef##_registered = (MM::internal::RegisterStandardProperty(PropertyRef, {DeviceType}), true); \ } } // namespace internal @@ -277,7 +277,7 @@ namespace MM { // properties are supported by a given device. // Specific standard properties - static const MM::StandardProperty g_TestStandardProperty( + static const MM::StandardProperty g_TestStandardProperty{ "Test", // name String, // type false, // isReadOnly @@ -287,13 +287,13 @@ namespace MM { PropertyLimitUndefined, // lowerLimit PropertyLimitUndefined, // upperLimit false // required - ); + }; MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestStandardProperty) static const std::vector testRequiredValues = {"value1", "value2", "value3"}; - static const MM::StandardProperty g_TestWithValuesStandardProperty( + static const MM::StandardProperty g_TestWithValuesStandardProperty{ "TestWithValues", // name String, // type false, // isReadOnly @@ -303,7 +303,7 @@ namespace MM { PropertyLimitUndefined, // lowerLimit PropertyLimitUndefined, // upperLimit false // required - ); + }; MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestWithValuesStandardProperty) From 9ff9c1f8e7066ff9608e659702a6560973ef76a3 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 5 Mar 2025 11:01:08 -0800 Subject: [PATCH 09/19] fix more namespace issues --- MMDevice/DeviceBase.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 9a9a2d6e1..94e123041 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -1285,7 +1285,7 @@ class CDeviceBase : public T * @return DEVICE_OK if successful, error code otherwise */ template - typename std::enable_if::value, int>::type + typename std::enable_if::value, int>::type CreateStandardProperty(const char* value, MM::ActionFunctor* pAct = 0, const std::vector& values = {}) { // Create the full property name with prefix @@ -1342,10 +1342,10 @@ class CDeviceBase : public T // When an function for setting an invalid standard property is called, // this function will be called and will cause a compilation error. template - typename std::enable_if::value, int>::type + typename std::enable_if::value, int>::type CreateStandardProperty(const char* /*value*/, MM::ActionFunctor* /*pAct*/ = 0, const std::vector& /*values*/ = std::vector()) { - static_assert(MM::IsStandardPropertyValid::value, + static_assert(MM::internal::IsStandardPropertyValid::value, "This standard property is not valid for this device type. Check the MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE definitions in MMDevice.h"); return DEVICE_UNSUPPORTED_COMMAND; // This line will never execute due to the static_assert } From 979d571a0b7547f928d2272de713c251c23d712b Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Wed, 5 Mar 2025 14:42:36 -0800 Subject: [PATCH 10/19] added mechanism for skipping required standard property --- MMDevice/DeviceBase.h | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 94e123041..f5b24d96d 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -39,6 +39,7 @@ #include #include #include +#include // common error messages const char* const g_Msg_ERR = "Unknown error in the device"; @@ -132,6 +133,12 @@ class CDeviceBase : public T MM::g_TestWithValuesStandardProperty.requiredValues); } + // Required standard properties can be skipped by adding methods like this. + // The TestStandardProperty is not required, this is just an example. + // void SkipTestStandardProperty() { + // SkipStandardProperty(); + // } + /** * Assigns description string for a device (for use only by the calling code). */ @@ -600,6 +607,11 @@ class CDeviceBase : public T std::string fullName = MM::g_KeywordStandardPropertyPrefix; fullName += prop.name; + // Skip checking if this property is in the skipped list + if (skippedStandardProperties_.find(fullName) != skippedStandardProperties_.end()) { + continue; + } + // Check if the device has implemented it if (!HasProperty(fullName.c_str())) { // If not, copy in the name of the property and return false @@ -610,7 +622,7 @@ class CDeviceBase : public T } } - // All required properties are implemented + // All required properties are implemented or explicitly skipped return true; } @@ -1350,6 +1362,17 @@ class CDeviceBase : public T return DEVICE_UNSUPPORTED_COMMAND; // This line will never execute due to the static_assert } + // Helper method to mark a required standard property as skipped + template + void SkipStandardProperty() { + // Only allow skipping properties that are valid for this device type + if (MM::internal::IsStandardPropertyValid::value) { + std::string fullName = MM::g_KeywordStandardPropertyPrefix; + fullName += PropRef.name; + skippedStandardProperties_.insert(fullName); + } + } + bool PropertyDefined(const char* propName) const { return properties_.Find(propName) != 0; @@ -1392,6 +1415,10 @@ class CDeviceBase : public T // specific information about the errant property, etc. mutable std::string morePropertyErrorInfo_; std::string parentID_; + + // Set to track which standard properties are explicitly skipped + std::set skippedStandardProperties_; + }; // Forbid instantiation of CDeviceBase From 79017f4702a98c51b6efa32df058ae6ab4278e4a Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Thu, 6 Mar 2025 08:23:16 -0800 Subject: [PATCH 11/19] remove required flag from standard properties. Default to required and add explicit skipping --- DeviceAdapters/DemoCamera/DemoCamera.cpp | 16 +++---- MMCore/Devices/DeviceInstance.cpp | 2 +- MMDevice/DeviceBase.h | 56 +++++++++++------------ MMDevice/MMDevice.h | 58 ++++++++++++------------ MMDevice/Property.h | 8 ++-- 5 files changed, 67 insertions(+), 73 deletions(-) diff --git a/DeviceAdapters/DemoCamera/DemoCamera.cpp b/DeviceAdapters/DemoCamera/DemoCamera.cpp index c47eb1826..827793408 100644 --- a/DeviceAdapters/DemoCamera/DemoCamera.cpp +++ b/DeviceAdapters/DemoCamera/DemoCamera.cpp @@ -308,14 +308,14 @@ int CDemoCamera::Initialize() else LogMessage(NoHubError); - // Standard properties - CPropertyAction *pActsp = new CPropertyAction (this, &CDemoCamera::OnTestStandardProperty); - int nRett = CreateTestStandardProperty("123", pActsp); - assert(nRett == DEVICE_OK); - - CPropertyAction *pActsp2 = new CPropertyAction (this, &CDemoCamera::OnTestWithValuesStandardProperty); - int nRettt = CreateTestWithValuesStandardProperty("value1", pActsp2); - assert(nRettt == DEVICE_OK); + // Example of how to create standard properties + // CPropertyAction *pActsp = new CPropertyAction (this, &CDemoCamera::OnTestStandardProperty); + // int nRett = CreateTestStandardProperty("123", pActsp); + // assert(nRett == DEVICE_OK); + + // CPropertyAction *pActsp2 = new CPropertyAction (this, &CDemoCamera::OnTestWithValuesStandardProperty); + // int nRettt = CreateTestWithValuesStandardProperty("value1", pActsp2); + // assert(nRettt == DEVICE_OK); // set property list diff --git a/MMCore/Devices/DeviceInstance.cpp b/MMCore/Devices/DeviceInstance.cpp index bc21bda20..0c2602297 100644 --- a/MMCore/Devices/DeviceInstance.cpp +++ b/MMCore/Devices/DeviceInstance.cpp @@ -379,7 +379,7 @@ DeviceInstance::Initialize() // Check for that all required standard properties implemented char failedProperty[MM::MaxStrLength]; - if (!pImpl_->ImplementsRequiredStandardProperties(failedProperty)) { + if (!pImpl_->ImplementsOrSkipsStandardProperties(failedProperty)) { // shutdown the device Shutdown(); ThrowError("Device " + GetLabel() + diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index f5b24d96d..9cc71e2b0 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -121,20 +121,21 @@ class CDeviceBase : public T //// Standard properties are created using only these dedicated functions // Such functions should all be defined here, and which device types they apply + // // to is handled in MMDevice.h using the MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE macro - int CreateTestStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { - return CreateStandardProperty(value, pAct); - } + // int CreateTestStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { + // return CreateStandardProperty(value, pAct); + // } - int CreateTestWithValuesStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { - // just make the values the required ones here. Also option to add - // additional ones in real situations - return CreateStandardProperty(value, pAct, - MM::g_TestWithValuesStandardProperty.requiredValues); - } + // int CreateTestWithValuesStandardProperty(const char* value, MM::ActionFunctor* pAct = 0) { + // // just make the values the required ones here. Also option to add + // // additional ones in real situations + // return CreateStandardProperty(value, pAct, + // MM::g_TestWithValuesStandardProperty.requiredValues); + // } - // Required standard properties can be skipped by adding methods like this. - // The TestStandardProperty is not required, this is just an example. + // Every standard property must either be created or explicitly skipped using + // a method like this // void SkipTestStandardProperty() { // SkipStandardProperty(); // } @@ -591,7 +592,7 @@ class CDeviceBase : public T return true; } - bool ImplementsRequiredStandardProperties(char* failedProperty) const { + bool ImplementsOrSkipsStandardProperties(char* failedProperty) const { // Get the device type MM::DeviceType deviceType = this->GetType(); @@ -601,23 +602,20 @@ class CDeviceBase : public T // Iterate through all properties for this device type const auto& properties = it->second; for (const auto& prop : properties) { - // Check if this property is required - if (prop.required) { - // Construct the full property name with prefix - std::string fullName = MM::g_KeywordStandardPropertyPrefix; - fullName += prop.name; - - // Skip checking if this property is in the skipped list - if (skippedStandardProperties_.find(fullName) != skippedStandardProperties_.end()) { - continue; - } - - // Check if the device has implemented it - if (!HasProperty(fullName.c_str())) { - // If not, copy in the name of the property and return false - CDeviceUtils::CopyLimitedString(failedProperty, fullName.c_str()); - return false; - } + // Construct the full property name with prefix + std::string fullName = MM::g_KeywordStandardPropertyPrefix; + fullName += prop.name; + + // Skip checking if this property is in the skipped list + if (skippedStandardProperties_.find(fullName) != skippedStandardProperties_.end()) { + continue; + } + + // Check if the device has implemented it + if (!HasProperty(fullName.c_str())) { + // If not, copy in the name of the property and return false + CDeviceUtils::CopyLimitedString(failedProperty, fullName.c_str()); + return false; } } } diff --git a/MMDevice/MMDevice.h b/MMDevice/MMDevice.h index 3d254af14..05dde50d0 100644 --- a/MMDevice/MMDevice.h +++ b/MMDevice/MMDevice.h @@ -277,35 +277,33 @@ namespace MM { // properties are supported by a given device. // Specific standard properties - static const MM::StandardProperty g_TestStandardProperty{ - "Test", // name - String, // type - false, // isReadOnly - false, // isPreInit - {}, // allowedValues (empty vector) - {}, // requiredValues (empty vector) - PropertyLimitUndefined, // lowerLimit - PropertyLimitUndefined, // upperLimit - false // required - }; - - MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestStandardProperty) - - - static const std::vector testRequiredValues = {"value1", "value2", "value3"}; - static const MM::StandardProperty g_TestWithValuesStandardProperty{ - "TestWithValues", // name - String, // type - false, // isReadOnly - false, // isPreInit - {}, // allowedValues (empty vector) - testRequiredValues, // requiredValues - PropertyLimitUndefined, // lowerLimit - PropertyLimitUndefined, // upperLimit - false // required - }; - - MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestWithValuesStandardProperty) + // static const MM::StandardProperty g_TestStandardProperty{ + // "Test", // name + // String, // type + // false, // isReadOnly + // false, // isPreInit + // {}, // allowedValues (empty vector) + // {}, // requiredValues (empty vector) + // PropertyLimitUndefined, // lowerLimit + // PropertyLimitUndefined, // upperLimit + // }; + + // MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestStandardProperty) + + + // static const std::vector testRequiredValues = {"value1", "value2", "value3"}; + // static const MM::StandardProperty g_TestWithValuesStandardProperty{ + // "TestWithValues", // name + // String, // type + // false, // isReadOnly + // false, // isPreInit + // {}, // allowedValues (empty vector) + // testRequiredValues, // requiredValues + // PropertyLimitUndefined, // lowerLimit + // PropertyLimitUndefined, // upperLimit + // }; + + // MM_INTERNAL_LINK_STANDARD_PROP_TO_DEVICE_TYPE(CameraDevice, g_TestWithValuesStandardProperty) /** @@ -329,7 +327,7 @@ namespace MM { virtual int GetPropertyType(const char* name, MM::PropertyType& pt) const = 0; virtual unsigned GetNumberOfPropertyValues(const char* propertyName) const = 0; virtual bool GetPropertyValueAt(const char* propertyName, unsigned index, char* value) const = 0; - virtual bool ImplementsRequiredStandardProperties(char* failedProperty) const = 0; + virtual bool ImplementsOrSkipsStandardProperties(char* failedProperty) const = 0; /** * Sequences can be used for fast acquisitions, synchronized by TTLs rather than * computer commands. diff --git a/MMDevice/Property.h b/MMDevice/Property.h index d8a9443b4..ba65e69bd 100644 --- a/MMDevice/Property.h +++ b/MMDevice/Property.h @@ -53,19 +53,17 @@ struct StandardProperty { allowedValues == other.allowedValues && requiredValues == other.requiredValues && lowerLimit == other.lowerLimit && - upperLimit == other.upperLimit && - required == other.required; + upperLimit == other.upperLimit; } std::string name; // Full property name (without prefix) PropertyType type; // Float, String, or Integer bool isReadOnly; // Whether property is read-only bool isPreInit; // Whether property should be set before initialization - std::vector allowedValues; // if empty, no restrictions - std::vector requiredValues; // if empty, no restrictions + std::vector allowedValues; // (for String properties) if empty, no restrictions + std::vector requiredValues; // (for String properties) if empty, no restrictions double lowerLimit = PropertyLimitUndefined; // Lower limit for numeric properties (NaN if not limited) double upperLimit = PropertyLimitUndefined; // Upper limit for numeric properties (NaN if not limited) - bool required = false; // Whether to throw a runtime error if the property is not found on init }; From a39a41c6eab445ebbd5f807357193c60c4c6e383 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Thu, 6 Mar 2025 10:33:51 -0800 Subject: [PATCH 12/19] remove from skipped registry upon creation --- MMDevice/DeviceBase.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 9cc71e2b0..519df2852 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -1345,6 +1345,9 @@ class CDeviceBase : public T return ret; } + // Remove from skipped properties if it was previously marked as skipped + skippedStandardProperties_.erase(fullName); + return DEVICE_OK; } From 758e20b9bf9bf86d8615cf8c373ed90a62a78826 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Thu, 6 Mar 2025 11:42:26 -0800 Subject: [PATCH 13/19] standard properties must declare values at initialization --- MMDevice/DeviceBase.h | 2 +- MMDevice/Property.cpp | 40 ++++++++++++++++++++++++++++++++++++---- MMDevice/Property.h | 8 ++++---- 3 files changed, 41 insertions(+), 9 deletions(-) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 519df2852..52d0449b3 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -1340,7 +1340,7 @@ class CDeviceBase : public T // now that all required values are present, and all supplied values are allowed, // add the user-supplied values to the property for (const std::string& val : values) { - ret = AddAllowedValue(fullName.c_str(), val.c_str()); + ret = properties_.AddAllowedValue(fullName.c_str(), val.c_str(), true); if (ret != DEVICE_OK) return ret; } diff --git a/MMDevice/Property.cpp b/MMDevice/Property.cpp index eff757530..d53d31248 100644 --- a/MMDevice/Property.cpp +++ b/MMDevice/Property.cpp @@ -372,12 +372,20 @@ int MM::PropertyCollection::CreateProperty(const char* pszName, const char* pszV return DEVICE_OK; } -int MM::PropertyCollection::SetAllowedValues(const char* pszName, std::vector& values) +int MM::PropertyCollection::SetAllowedValues(const char* pszName, std::vector& values, bool standard) { MM::Property* pProp = Find(pszName); if (!pProp) return DEVICE_INVALID_PROPERTY; // name not found + if (!standard) + { + // make sure it doesn't begin with the reserved prefix for standard properties + std::string prefixAndDelim = std::string(g_KeywordStandardPropertyPrefix); + if (std::string(pszName).find(prefixAndDelim) == 0) + return DEVICE_INVALID_PROPERTY; + } + pProp->ClearAllowedValues(); for (unsigned i=0; iAddAllowedValue(values[i].c_str()); @@ -385,32 +393,56 @@ int MM::PropertyCollection::SetAllowedValues(const char* pszName, std::vectorClearAllowedValues(); return DEVICE_OK; } -int MM::PropertyCollection::AddAllowedValue(const char* pszName, const char* value, long data) +int MM::PropertyCollection::AddAllowedValue(const char* pszName, const char* value, long data, bool standard) { MM::Property* pProp = Find(pszName); if (!pProp) return DEVICE_INVALID_PROPERTY; // name not found + if (!standard) + { + // make sure it doesn't begin with the reserved prefix for standard properties + std::string prefixAndDelim = std::string(g_KeywordStandardPropertyPrefix); + if (std::string(pszName).find(prefixAndDelim) == 0) + return DEVICE_INVALID_PROPERTY; + } + pProp->AddAllowedValue(value, data); return DEVICE_OK; } -int MM::PropertyCollection::AddAllowedValue(const char* pszName, const char* value) +int MM::PropertyCollection::AddAllowedValue(const char* pszName, const char* value, bool standard) { MM::Property* pProp = Find(pszName); if (!pProp) return DEVICE_INVALID_PROPERTY; // name not found + if (!standard) + { + // make sure it doesn't begin with the reserved prefix for standard properties + std::string prefixAndDelim = std::string(g_KeywordStandardPropertyPrefix); + if (std::string(pszName).find(prefixAndDelim) == 0) + return DEVICE_INVALID_PROPERTY; + } + pProp->AddAllowedValue(value); return DEVICE_OK; } diff --git a/MMDevice/Property.h b/MMDevice/Property.h index ba65e69bd..5a06a3211 100644 --- a/MMDevice/Property.h +++ b/MMDevice/Property.h @@ -476,10 +476,10 @@ class PropertyCollection int CreateProperty(const char* name, const char* value, PropertyType eType, bool bReadOnly, ActionFunctor* pAct=0, bool isPreInitProperty=false, bool standard=false); int RegisterAction(const char* name, ActionFunctor* fpAct); - int SetAllowedValues(const char* name, std::vector& values); - int ClearAllowedValues(const char* name); - int AddAllowedValue(const char* name, const char* value, long data); - int AddAllowedValue(const char* name, const char* value); + int SetAllowedValues(const char* name, std::vector& values, bool standard=false); + int ClearAllowedValues(const char* name, bool standard=false); + int AddAllowedValue(const char* name, const char* value, long data, bool standard=false); + int AddAllowedValue(const char* name, const char* value, bool standard=false); int GetPropertyData(const char* name, const char* value, long& data); int GetCurrentPropertyData(const char* name, long& data); int Set(const char* propName, const char* Value); From da30e68604f674166a8e7fb539e7885f8c45e390 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 7 Mar 2025 09:43:22 -0800 Subject: [PATCH 14/19] add method for updating standard property values --- MMDevice/DeviceBase.h | 89 ++++++++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 26 deletions(-) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 52d0449b3..6b0fe9cca 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -1315,34 +1315,18 @@ class CDeviceBase : public T return ret; } - // Ensure only allowed values are set + // Ensure the initial value is allowed if the property has predefined allowed values if (!PropRef.allowedValues.empty()) { - // If the property has predefined allowed values, validate that all supplied values are allowed - if (std::find(PropRef.allowedValues.begin(), PropRef.allowedValues.end(), value) == PropRef.allowedValues.end()) { - return DEVICE_INVALID_PROPERTY_VALUE; - } - - for (const std::string& val : values) { - if (std::find(PropRef.allowedValues.begin(), PropRef.allowedValues.end(), val) == PropRef.allowedValues.end()) { - return DEVICE_INVALID_PROPERTY_VALUE; - } - } - } - // if there are required values, make sure they are all present - if (!PropRef.requiredValues.empty()) { - // throw an error if required values are not present - for (const std::string& val : PropRef.requiredValues) { - if (std::find(values.begin(), values.end(), val) == values.end()) { - return DEVICE_INVALID_PROPERTY_VALUE; - } - } + if (std::find(PropRef.allowedValues.begin(), PropRef.allowedValues.end(), value) == PropRef.allowedValues.end()) { + return DEVICE_INVALID_PROPERTY_VALUE; + } } - // now that all required values are present, and all supplied values are allowed, - // add the user-supplied values to the property - for (const std::string& val : values) { - ret = properties_.AddAllowedValue(fullName.c_str(), val.c_str(), true); - if (ret != DEVICE_OK) - return ret; + + // Set the allowed values using the existing SetStandardPropertyValues function + if (!values.empty() || !PropRef.requiredValues.empty()) { + ret = SetStandardPropertyValues(values); + if (ret != DEVICE_OK) + return ret; } // Remove from skipped properties if it was previously marked as skipped @@ -1351,6 +1335,59 @@ class CDeviceBase : public T return DEVICE_OK; } + /** + * Sets allowed values for a standard property, clearing any existing values first. + * Performs the same validation as when creating the property. + * + * @param PropRef - Reference to the standard property definition + * @param values - Vector of values to set as allowed values + * @return DEVICE_OK if successful, error code otherwise + */ + template + typename std::enable_if::value, int>::type + SetStandardPropertyValues(const std::vector& values) { + // Create the full property name with prefix + std::string fullName = MM::g_KeywordStandardPropertyPrefix; + fullName += PropRef.name; + + // Check if the property exists + if (!HasProperty(fullName.c_str())) { + return DEVICE_INVALID_PROPERTY; + } + + // Ensure all supplied values are allowed if the property has predefined allowed values + if (!PropRef.allowedValues.empty()) { + for (const std::string& val : values) { + if (std::find(PropRef.allowedValues.begin(), PropRef.allowedValues.end(), val) == PropRef.allowedValues.end()) { + return DEVICE_INVALID_PROPERTY_VALUE; + } + } + } + + // Check if all required values are present + if (!PropRef.requiredValues.empty()) { + for (const std::string& val : PropRef.requiredValues) { + if (std::find(values.begin(), values.end(), val) == values.end()) { + return DEVICE_INVALID_PROPERTY_VALUE; + } + } + } + + // Clear existing values + int ret = ClearAllowedValues(fullName.c_str(), true); + if (ret != DEVICE_OK) + return ret; + + // Add the new values + for (const std::string& val : values) { + ret = properties_.AddAllowedValue(fullName.c_str(), val.c_str(), true); + if (ret != DEVICE_OK) + return ret; + } + + return DEVICE_OK; + } + // This one is purely for providing better error messages at compile time // When an function for setting an invalid standard property is called, // this function will be called and will cause a compilation error. From c3bd38eaf2e410cd26d5477b4bea0e4ae69cb920 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 7 Mar 2025 10:23:43 -0800 Subject: [PATCH 15/19] fix bug --- MMDevice/DeviceBase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 6b0fe9cca..dfc7b77f5 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -1374,7 +1374,7 @@ class CDeviceBase : public T } // Clear existing values - int ret = ClearAllowedValues(fullName.c_str(), true); + int ret = properties_.ClearAllowedValues(fullName.c_str(), true); if (ret != DEVICE_OK) return ret; From 9eac5050ad410d3b7b3c6f4a345f422f1dc95a54 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 7 Mar 2025 12:29:09 -0800 Subject: [PATCH 16/19] add checker method --- MMDevice/DeviceBase.h | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index dfc7b77f5..f3d1b5867 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -557,6 +557,14 @@ class CDeviceBase : public T return false; } + virtual bool HasStandardProperty(const char* name) const + { + // prepend standard property prefix to name + std::string fullName = MM::g_KeywordStandardPropertyPrefix; + fullName += name; + return HasProperty(fullName.c_str()); + } + /** * Returns the number of allowed property values. * If the set of property values is not defined, not bounded, From a73d75ed0a8c58f14405a7bff507dfcd4d151d6a Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 7 Mar 2025 12:47:31 -0800 Subject: [PATCH 17/19] add ability to dynamically delete properties --- MMDevice/DeviceBase.h | 8 ++++++++ MMDevice/Property.cpp | 11 +++++++++++ MMDevice/Property.h | 1 + 3 files changed, 20 insertions(+) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index f3d1b5867..55a25c830 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -1416,6 +1416,14 @@ class CDeviceBase : public T std::string fullName = MM::g_KeywordStandardPropertyPrefix; fullName += PropRef.name; skippedStandardProperties_.insert(fullName); + // Check if the property already exists. If so, delete it. + // This is needed because standard properties may be created dynamically not during + // initialization. For example, if they depend on the value of another property, + // and this is not known other than by setting that value on the device. In this case, + // the standard property will be created and destroyed as the values change. + if (HasProperty(fullName.c_str())) { + properties_.DeleteProperty(fullName.c_str()); + } } } diff --git a/MMDevice/Property.cpp b/MMDevice/Property.cpp index d53d31248..93fcf5575 100644 --- a/MMDevice/Property.cpp +++ b/MMDevice/Property.cpp @@ -540,3 +540,14 @@ int MM::PropertyCollection::Apply(const char* pszName) return pProp->Apply(); } + +int MM::PropertyCollection::Delete(const char* pszName) +{ + MM::Property* pProp = Find(pszName); + if (!pProp) + return DEVICE_INVALID_PROPERTY; + + delete pProp; + + return DEVICE_OK; +} diff --git a/MMDevice/Property.h b/MMDevice/Property.h index 5a06a3211..1a3fa5f35 100644 --- a/MMDevice/Property.h +++ b/MMDevice/Property.h @@ -492,6 +492,7 @@ class PropertyCollection int ApplyAll(); int Update(const char* Name); int Apply(const char* Name); + int Delete(const char* pszName); private: typedef std::map CPropArray; From 9ca35fa20e00217141a581cc2c82e8df324e0d80 Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 7 Mar 2025 13:04:10 -0800 Subject: [PATCH 18/19] fix method name --- MMDevice/DeviceBase.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MMDevice/DeviceBase.h b/MMDevice/DeviceBase.h index 55a25c830..9d452cf35 100644 --- a/MMDevice/DeviceBase.h +++ b/MMDevice/DeviceBase.h @@ -1422,7 +1422,7 @@ class CDeviceBase : public T // and this is not known other than by setting that value on the device. In this case, // the standard property will be created and destroyed as the values change. if (HasProperty(fullName.c_str())) { - properties_.DeleteProperty(fullName.c_str()); + properties_.Delete(fullName.c_str()); } } } From e1ebc2766a65e14896210c1fffeeca9a95d4ddbc Mon Sep 17 00:00:00 2001 From: Henry Pinkard <7969470+henrypinkard@users.noreply.github.com> Date: Fri, 7 Mar 2025 13:19:11 -0800 Subject: [PATCH 19/19] remove from map correctly --- MMDevice/Property.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/MMDevice/Property.cpp b/MMDevice/Property.cpp index 93fcf5575..32e8e72dd 100644 --- a/MMDevice/Property.cpp +++ b/MMDevice/Property.cpp @@ -546,6 +546,9 @@ int MM::PropertyCollection::Delete(const char* pszName) MM::Property* pProp = Find(pszName); if (!pProp) return DEVICE_INVALID_PROPERTY; + + // remove it from the map + properties_.erase(pszName); delete pProp;