From b180fe9c772b5b56c251fe17ef77c40a4770d9fa Mon Sep 17 00:00:00 2001 From: Tiyi Date: Tue, 2 Jun 2026 18:30:20 +0800 Subject: [PATCH 1/3] add 3Z light source --- DeviceAdapters/3Z_Optics/3Z_Optics.cpp | 1384 +++++++++++++++++ DeviceAdapters/3Z_Optics/3Z_Optics.h | 138 ++ DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj | 152 ++ .../3Z_Optics/3Z_Optics.vcxproj.filters | 27 + micromanager.sln | 6 + 5 files changed, 1707 insertions(+) create mode 100644 DeviceAdapters/3Z_Optics/3Z_Optics.cpp create mode 100644 DeviceAdapters/3Z_Optics/3Z_Optics.h create mode 100644 DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj create mode 100644 DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj.filters diff --git a/DeviceAdapters/3Z_Optics/3Z_Optics.cpp b/DeviceAdapters/3Z_Optics/3Z_Optics.cpp new file mode 100644 index 000000000..b4c00d905 --- /dev/null +++ b/DeviceAdapters/3Z_Optics/3Z_Optics.cpp @@ -0,0 +1,1384 @@ +/////////////////////////////////////////////////////////////////////////////// +// FILE: 3Z_Optics.cpp +// PROJECT: Micro-Manager +// SUBSYSTEM: DeviceAdapters +//----------------------------------------------------------------------------- +// DESCRIPTION: 3Z Optics Light Source driver +// COPYRIGHT: 3Z Optics +// LICENSE: BSD license + +#include "3Z_Optics.h" +#include "ModuleInterface.h" +#include +#include +#include + +using namespace std; + +/////////////////////////////////////////////////////////////////////////////// +// Static member initialization +/////////////////////////////////////////////////////////////////////////////// +MMThreadLock Controller::lock_; + +/////////////////////////////////////////////////////////////////////////////// +// Exported MMDevice API +/////////////////////////////////////////////////////////////////////////////// +MODULE_API void InitializeModuleData() +{ + RegisterDevice(g_DeviceName, MM::ShutterDevice, "3Z Optics Light Source"); +} + +MODULE_API MM::Device* CreateDevice(const char* deviceName) +{ + if (deviceName == 0) + return 0; + + if (strcmp(deviceName, g_DeviceName) == 0) + { + return new Controller(); + } + + return 0; +} + +MODULE_API void DeleteDevice(MM::Device* pDevice) +{ + delete pDevice; +} + +/////////////////////////////////////////////////////////////////////////////// +// PollingThread implementation +/////////////////////////////////////////////////////////////////////////////// +PollingThread::PollingThread(Controller& aController) : + aController_(aController), + stop_(false) +{ +} + +PollingThread::~PollingThread() +{ +} + +int PollingThread::svc(void) +{ + while (!stop_) + { + CDeviceUtils::SleepMs((long)aController_.pollIntervalMs_); + + if (!stop_) + { + MMThreadGuard guard(aController_.GetLock()); + aController_.PollDeviceStatus(); + } + } + return 0; +} + +void PollingThread::Start() +{ + activate(); +} + +/////////////////////////////////////////////////////////////////////////////// +// Controller implementation +/////////////////////////////////////////////////////////////////////////////// + +Controller::Controller() : + initialized_(false), + shutterState_(false), + deviceModelId_(0), + globalIntensity_(0), + globalSwitch_(false), + currentMode_(1), + pollIntervalMs_(2000.0), // 2 second poll interval + initializationComplete_(false), + initializationInProgress_(false), + globalSwitchUpdated_(false), + globalIntensityUpdated_(false), + modeUpdated_(false), + mThread_(nullptr), + channelSwitchUpdated_(), + channelIntensityUpdated_() +{ + InitializeDefaultErrorMessages(); + + // Set error messages + SetErrorText(ERR_PORT_CHANGE_FORBIDDEN, "Port cannot be changed after initialization"); + SetErrorText(ERR_DEVICE_NOT_FOUND, "Device model not found in configuration"); + SetErrorText(ERR_MODBUS_COMM_ERROR, "Modbus communication error"); + SetErrorText(ERR_INIT, "Initialization error"); + + // Create pre-initialization properties (Name and Description will be created/updated in Initialize) + + // Port property + CPropertyAction* pAct = new CPropertyAction(this, &Controller::OnPort); + CreateProperty(MM::g_Keyword_Port, "", MM::String, false, pAct, true); +} + +Controller::~Controller() +{ + Shutdown(); +} + +void Controller::GetName(char* pszName) const +{ + CDeviceUtils::CopyLimitedString(pszName, g_DeviceName); +} + +bool Controller::Busy() +{ + return false; +} + +uint16_t Controller::CalculateCRC(const uint8_t* data, int length) +{ + uint16_t crc = 0xFFFF; + for (int i = 0; i < length; i++) + { + crc ^= (uint16_t)data[i]; + for (int j = 0; j < 8; j++) + { + if (crc & 0x0001) + { + crc >>= 1; + crc ^= 0xA001; + } + else + { + crc >>= 1; + } + } + } + return crc; +} + +int Controller::SendModbusCommand(const std::vector& request, std::vector& response, int expectedResponseLength) +{ + if (port_.empty()) + return ERR_MODBUS_COMM_ERROR; + + MMThreadGuard guard(commLock_); + + // Clear port first + int ret = PurgeComPort(port_.c_str()); + if (ret != DEVICE_OK) + return ret; + + // Send request + ret = WriteToComPort(port_.c_str(), &request[0], (unsigned long)request.size()); + if (ret != DEVICE_OK) + return ret; + + // Wait a little longer for stable communication + CDeviceUtils::SleepMs(100); + + // Read response - try multiple times if needed + int maxAttempts = 3; + for (int attempt = 0; attempt < maxAttempts; attempt++) + { + response.resize(expectedResponseLength); + unsigned long read = 0; + ret = ReadFromComPort(port_.c_str(), &response[0], (unsigned long)expectedResponseLength, read); + + if (ret == DEVICE_OK && read == (unsigned long)expectedResponseLength) + { + // Check CRC + if (expectedResponseLength >= 2) + { + uint16_t crcCalc = CalculateCRC(&response[0], expectedResponseLength - 2); + uint16_t crcRecv = (uint16_t)response[expectedResponseLength - 1] << 8 | (uint16_t)response[expectedResponseLength - 2]; + if (crcCalc == crcRecv) + { + return DEVICE_OK; + } + } + else + { + return DEVICE_OK; + } + } + + // If not last attempt, wait and retry + if (attempt < maxAttempts - 1) + { + CDeviceUtils::SleepMs(50); + PurgeComPort(port_.c_str()); + } + } + + return ERR_MODBUS_COMM_ERROR; +} + +int Controller::ReadInputRegister(int addr, uint16_t& value) +{ + std::vector request; + request.push_back(0x01); // Slave address + request.push_back(0x04); // Function code: Read Input Registers + request.push_back((addr >> 8) & 0xFF); // Address high + request.push_back(addr & 0xFF); // Address low + request.push_back(0x00); // Number of registers high + request.push_back(0x01); // Number of registers low + + uint16_t crc = CalculateCRC(&request[0], request.size()); + request.push_back(crc & 0xFF); + request.push_back((crc >> 8) & 0xFF); + + std::vector response; + int ret = SendModbusCommand(request, response, 7); + if (ret != DEVICE_OK) return ret; + + if (response[1] & 0x80) // Check for exception + return ERR_MODBUS_COMM_ERROR; + + value = ((uint16_t)response[3] << 8) | (uint16_t)response[4]; + return DEVICE_OK; +} + +int Controller::WriteHoldingRegister(int addr, uint16_t value) +{ + std::vector request; + request.push_back(0x01); // Slave address + request.push_back(0x06); // Function code: Write Single Register + request.push_back((addr >> 8) & 0xFF); // Address high + request.push_back(addr & 0xFF); // Address low + request.push_back((value >> 8) & 0xFF); // Value high + request.push_back(value & 0xFF); // Value low + + uint16_t crc = CalculateCRC(&request[0], request.size()); + request.push_back(crc & 0xFF); + request.push_back((crc >> 8) & 0xFF); + + std::vector response; + int ret = SendModbusCommand(request, response, 8); + if (ret != DEVICE_OK) return ret; + + if (response[1] & 0x80) // Check for exception + return ERR_MODBUS_COMM_ERROR; + + return DEVICE_OK; +} + +int Controller::ReadHoldingRegister(int addr, uint16_t& value) +{ + std::vector request; + request.push_back(0x01); // Slave address + request.push_back(0x03); // Function code: Read Holding Registers + request.push_back((addr >> 8) & 0xFF); // Address high + request.push_back(addr & 0xFF); // Address low + request.push_back(0x00); // Number of registers high + request.push_back(0x01); // Number of registers low + + uint16_t crc = CalculateCRC(&request[0], request.size()); + request.push_back(crc & 0xFF); + request.push_back((crc >> 8) & 0xFF); + + std::vector response; + int ret = SendModbusCommand(request, response, 7); + if (ret != DEVICE_OK) return ret; + + if (response[1] & 0x80) // Check for exception + return ERR_MODBUS_COMM_ERROR; + + value = ((uint16_t)response[3] << 8) | (uint16_t)response[4]; + return DEVICE_OK; +} + +int Controller::ReadMultipleHoldingRegisters(int startAddr, int count, std::vector& values) +{ + std::vector request; + request.push_back(0x01); // Slave address + request.push_back(0x03); // Function code: Read Holding Registers + request.push_back((startAddr >> 8) & 0xFF); // Address high + request.push_back(startAddr & 0xFF); // Address low + request.push_back((count >> 8) & 0xFF); // Number of registers high + request.push_back(count & 0xFF); // Number of registers low + + uint16_t crc = CalculateCRC(&request[0], request.size()); + request.push_back(crc & 0xFF); + request.push_back((crc >> 8) & 0xFF); + + // Calculate expected response length + int expectedLength = 5 + count * 2; // addr(1) + func(1) + byteCount(1) + data(count*2) + crc(2) + + std::vector response; + int ret = SendModbusCommand(request, response, expectedLength); + if (ret != DEVICE_OK) return ret; + + if (response[1] & 0x80) // Check for exception + return ERR_MODBUS_COMM_ERROR; + + // Parse response + values.resize(count); + for (int i = 0; i < count; i++) + { + int dataIndex = 3 + i * 2; + values[i] = ((uint16_t)response[dataIndex] << 8) | (uint16_t)response[dataIndex + 1]; + } + + return DEVICE_OK; +} + +int Controller::ReadSingleCoil(int addr, bool& on) +{ + std::vector request; + request.push_back(0x01); // Slave address + request.push_back(0x01); // Function code: Read Coils + request.push_back((addr >> 8) & 0xFF); // Address high + request.push_back(addr & 0xFF); // Address low + request.push_back(0x00); // Number of coils high + request.push_back(0x01); // Number of coils low + + uint16_t crc = CalculateCRC(&request[0], request.size()); + request.push_back(crc & 0xFF); + request.push_back((crc >> 8) & 0xFF); + + std::vector response; + int ret = SendModbusCommand(request, response, 6); + if (ret != DEVICE_OK) return ret; + + if (response[1] & 0x80) // Check for exception + return ERR_MODBUS_COMM_ERROR; + + // Response: address, function, byte count, coil status (1 byte) + // Coil ON is 0xFF, OFF is 0x00 + on = (response[3] != 0); + return DEVICE_OK; +} + +int Controller::ReadMultipleCoils(int startAddr, int count, std::vector& values) +{ + std::vector request; + request.push_back(0x01); // Slave address + request.push_back(0x01); // Function code: Read Coils + request.push_back((startAddr >> 8) & 0xFF); // Address high + request.push_back(startAddr & 0xFF); // Address low + request.push_back((count >> 8) & 0xFF); // Number of coils high + request.push_back(count & 0xFF); // Number of coils low + + uint16_t crc = CalculateCRC(&request[0], request.size()); + request.push_back(crc & 0xFF); + request.push_back((crc >> 8) & 0xFF); + + // Calculate expected response length + int byteCount = (count + 7) / 8; + int expectedLength = 5 + byteCount; // addr(1) + func(1) + byteCount(1) + data(byteCount) + crc(2) + + std::vector response; + int ret = SendModbusCommand(request, response, expectedLength); + if (ret != DEVICE_OK) return ret; + + if (response[1] & 0x80) // Check for exception + return ERR_MODBUS_COMM_ERROR; + + // Parse response + values.resize(count); + for (int i = 0; i < count; i++) + { + int byteIndex = 3 + (i / 8); + int bitIndex = i % 8; + values[i] = ((response[byteIndex] >> bitIndex) & 0x01) != 0; + } + + return DEVICE_OK; +} + +int Controller::WriteSingleCoil(int addr, bool on) +{ + std::vector request; + request.push_back(0x01); // Slave address + request.push_back(0x05); // Function code: Write Single Coil + request.push_back((addr >> 8) & 0xFF); // Address high + request.push_back(addr & 0xFF); // Address low + request.push_back(on ? 0xFF : 0x00); // Value high (FF00=ON, 0000=OFF) + request.push_back(0x00); // Value low + + uint16_t crc = CalculateCRC(&request[0], request.size()); + request.push_back(crc & 0xFF); + request.push_back((crc >> 8) & 0xFF); + + std::vector response; + int ret = SendModbusCommand(request, response, 8); + if (ret != DEVICE_OK) return ret; + + if (response[1] & 0x80) // Check for exception + return ERR_MODBUS_COMM_ERROR; + + return DEVICE_OK; +} + +int Controller::ReadDeviceModel() +{ + uint16_t model; + int ret = ReadInputRegister(0x01, model); + if (ret != DEVICE_OK) + return ret; + + deviceModelId_ = (int)model; + return DEVICE_OK; +} + +string trim(const string& str) +{ + size_t first = str.find_first_not_of(" \t\n\r"); + size_t last = str.find_last_not_of(" \t\n\r"); + if (first == string::npos) + return ""; + return str.substr(first, (last - first + 1)); +} + +string readQuotedString(ifstream& file) +{ + string result; + char c; + // Skip leading whitespace + while (file.get(c) && (c == ' ' || c == '\t' || c == '\n' || c == '\r')); + if (c != '"') + return ""; + + while (file.get(c)) + { + if (c == '"') + break; + if (c == '\\') + { + file.get(c); + result += c; + } + else + { + result += c; + } + } + return result; +} + +int readInteger(ifstream& file) +{ + string result; + char c; + // Skip leading whitespace + while (file.get(c) && (c == ' ' || c == '\t' || c == '\n' || c == '\r')); + file.putback(c); + + while (file.get(c)) + { + if (isdigit(c)) + { + result += c; + } + else + { + file.putback(c); + break; + } + } + + if (result.empty()) + return 0; + + return stoi(result); +} + +bool Controller::LoadDeviceConfig(int modelId) +{ + // Try multiple possible paths in order + vector possiblePaths; + + // 1. Try current working directory + possiblePaths.push_back("models.json"); + possiblePaths.push_back("3z/models.json"); + + // 2. Try user Documents folder using Windows API + char userProfile[MAX_PATH] = { 0 }; + if (GetEnvironmentVariableA("USERPROFILE", userProfile, MAX_PATH)) + { + possiblePaths.push_back(string(userProfile) + "\\Documents\\3z\\models.json"); + possiblePaths.push_back(string(userProfile) + "\\Documents\\models.json"); + } + + // 3. Try relative to Micro-Manager's directory + possiblePaths.push_back("../../3z/models.json"); + possiblePaths.push_back("../../models.json"); + + ifstream file; + string foundPath; + + for (const string& path : possiblePaths) + { + file.open(path); + if (file.is_open()) + { + foundPath = path; + break; + } + } + + if (foundPath.empty()) + { + // Fall back to hardcoded default + currentDevice_.name = "Unknown"; + currentDevice_.channels = { "CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8" }; + currentDevice_.brightnessMin = 0; + currentDevice_.brightnessMax = 100; + return true; + } + + string modelKey = to_string(modelId); + bool foundModel = false; + + char c; + // Skip until we find opening { + while (file.get(c)) + { + if (c == '{') + break; + } + + // Parse the JSON + while (file.get(c)) + { + if (c == '}') + break; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') + continue; + + if (c == '"') + { + file.putback(c); + string key = readQuotedString(file); + + // Skip until : + while (file.get(c) && c != ':'); + + if (c == ':') + { + // Skip whitespace + while (file.get(c) && (c == ' ' || c == '\t' || c == '\n' || c == '\r')); + file.putback(c); + + if (c == '{') // Object + { + file.get(c); + + if (key == modelKey) + { + // Found our model, parse it + string name; + vector channels; + int brightnessMin = 0; // Default to 0 + int brightnessMax = 100; // Default to 100 + + // Parse the model object + while (file.get(c)) + { + if (c == '}') + break; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r') + continue; + + if (c == '"') + { + file.putback(c); + string propName = readQuotedString(file); + + // Skip until : + while (file.get(c) && c != ':'); + + // Skip whitespace + while (file.get(c) && (c == ' ' || c == '\t' || c == '\n' || c == '\r')); + file.putback(c); + + if (propName == "name") + { + name = readQuotedString(file); + } + else if (propName == "BrightnessMin" || propName == "brightnessMin") + { + brightnessMin = readInteger(file); + } + else if (propName == "BrightnessMax" || propName == "brightnessMax") + { + brightnessMax = readInteger(file); + } + else if (propName == "channels") + { + // Parse array + if (file.get(c) && c == '[') + { + while (file.get(c)) + { + if (c == ']') + break; + if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == ',') + continue; + if (c == '"') + { + file.putback(c); + string channel = readQuotedString(file); + if (!channel.empty()) + channels.push_back(channel); + } + } + } + } + } + } + + // Validate brightness range + if (brightnessMax < brightnessMin) + { + // Swap them if invalid + int temp = brightnessMin; + brightnessMin = brightnessMax; + brightnessMax = temp; + } + + currentDevice_.name = name; + currentDevice_.channels = channels; + currentDevice_.brightnessMin = brightnessMin; + currentDevice_.brightnessMax = brightnessMax; + foundModel = true; + break; + } + else + { + // Skip this object + int depth = 1; + while (file.get(c) && depth > 0) + { + if (c == '{') depth++; + else if (c == '}') depth--; + } + } + } + } + } + } + + file.close(); + + if (!foundModel) + { + currentDevice_.name = "Unknown"; + currentDevice_.channels = { "CH1", "CH2", "CH3", "CH4", "CH5", "CH6", "CH7", "CH8" }; + currentDevice_.brightnessMin = 0; + currentDevice_.brightnessMax = 100; + } + + return true; +} + +int Controller::Initialize() +{ + if (initialized_) + return DEVICE_OK; + + if (port_.empty()) + return ERR_INIT; + + // Mark that we're in initialization phase - prevents writing to hardware + initializationInProgress_ = true; + + // Read device model from input register 0x01 + int ret = ReadDeviceModel(); + if (ret != DEVICE_OK) + { + initializationInProgress_ = false; + return ret; + } + + // Load device configuration + if (!LoadDeviceConfig(deviceModelId_)) + { + initializationInProgress_ = false; + return ERR_DEVICE_NOT_FOUND; + } + + // Create Name and Description properties with device name + CreateStringProperty(MM::g_Keyword_Name, currentDevice_.name.c_str(), false); + string desc = "3Z Optics " + currentDevice_.name; + CreateStringProperty(MM::g_Keyword_Description, desc.c_str(), true); + + // Create device model and name properties + CreateProperty(g_Prop_DeviceModel, CDeviceUtils::ConvertToString(deviceModelId_), MM::String, true); + CreateProperty(g_Prop_DeviceName, currentDevice_.name.c_str(), MM::String, true); + + // Initialize channels + channels_ = currentDevice_.channels; + channelStates_.resize(channels_.size(), false); + channelIntensities_.resize(channels_.size(), 0); + channelSwitchUpdated_.resize(channels_.size(), false); + channelIntensityUpdated_.resize(channels_.size(), false); + + // Create channel properties in the style of "385 Switch" and "385 Intensity" + channelSwitchLookup_.clear(); + channelIntensityLookup_.clear(); + for (size_t i = 0; i < channels_.size(); i++) + { + // Channel Switch property (like "385 Switch") + ostringstream switchName; + switchName << channels_[i] << " Switch"; + CPropertyAction* pAct = new CPropertyAction(this, &Controller::OnChannelSwitch); + CreateProperty(switchName.str().c_str(), "off", MM::String, false, pAct); + AddAllowedValue(switchName.str().c_str(), "off"); + AddAllowedValue(switchName.str().c_str(), "on"); + channelSwitchLookup_[switchName.str()] = (int)i; + + // Channel Intensity property (like "385 Intensity"), range brightnessMin-brightnessMax + ostringstream intensityName; + intensityName << channels_[i] << " Intensity"; + pAct = new CPropertyAction(this, &Controller::OnChannelIntensity); + CreateProperty(intensityName.str().c_str(), to_string(currentDevice_.brightnessMin).c_str(), MM::Integer, false, pAct); + SetPropertyLimits(intensityName.str().c_str(), currentDevice_.brightnessMin, currentDevice_.brightnessMax); + channelIntensityLookup_[intensityName.str()] = (int)i; + } + + // Global Switch property + CPropertyAction* pAct = new CPropertyAction(this, &Controller::OnGlobalSwitch); + CreateProperty("Global Switch", "off", MM::String, false, pAct); + AddAllowedValue("Global Switch", "off"); + AddAllowedValue("Global Switch", "on"); + + // Global Intensity property, range brightnessMin-brightnessMax + pAct = new CPropertyAction(this, &Controller::OnGlobalIntensity); + CreateProperty("Global Intensity", to_string(currentDevice_.brightnessMin).c_str(), MM::Integer, false, pAct); + SetPropertyLimits("Global Intensity", currentDevice_.brightnessMin, currentDevice_.brightnessMax); + + // Mode property + pAct = new CPropertyAction(this, &Controller::OnMode); + CreateProperty("Mode", "Global", MM::String, false, pAct); + AddAllowedValue("Mode", "Global"); + AddAllowedValue("Mode", "independent"); + AddAllowedValue("Mode", "TTL"); + + // Refresh property - manual refresh button + pAct = new CPropertyAction(this, &Controller::OnRefresh); + CreateProperty("Refresh", "0", MM::Integer, false, pAct); + SetPropertyLimits("Refresh", 0, 1); + + // Mark initialization complete before reading device state + initializationComplete_ = true; + + // Read current device state from hardware instead of turning all off + ret = ReadCurrentDeviceState(); + if (ret != DEVICE_OK) + { + } + + // Update all properties with initial values + UpdatePropertiesFromDevice(); + + initialized_ = true; + + // End initialization phase - now allows writing to hardware + initializationInProgress_ = false; + + // Start polling thread after initialization is complete + mThread_ = new PollingThread(*this); + mThread_->Start(); + + return DEVICE_OK; +} + +int Controller::Shutdown() +{ + if (initialized_) + { + // Stop polling thread + if (mThread_) + { + mThread_->Stop(); + mThread_->wait(); + delete mThread_; + mThread_ = nullptr; + } + + TurnAllOff(); + initialized_ = false; + } + return DEVICE_OK; +} + +int Controller::SetOpen(bool open) +{ + // Skip writing to hardware during initialization + if (initializationInProgress_) + { + shutterState_ = open; + globalSwitch_ = open; + return DEVICE_OK; + } + + if (open) + { + shutterState_ = true; + globalSwitch_ = true; + + // Turn on global switch + if (currentMode_==1) + { + int ret = WriteSingleCoil(0x30, true); + if (ret != DEVICE_OK) + { + shutterState_ = false; + globalSwitch_ = false; + return ret; + } + } + + // Apply channel states + return ApplyChannelStates(); + } + else + { + return TurnAllOff(); + } +} + +int Controller::GetOpen(bool& open) +{ + open = shutterState_; + return DEVICE_OK; +} + +int Controller::ApplyChannelStates() +{ + // Skip writing to hardware during initialization + if (initializationInProgress_) + { + return DEVICE_OK; + } + + for (size_t i = 0; i < channelStates_.size(); i++) + { + // Set channel switch state with coil + int ret = WriteSingleCoil(0x31 + (int)i, channelStates_[i] && shutterState_); + if (ret != DEVICE_OK) + return ret; + } + return DEVICE_OK; +} + +int Controller::TurnAllOff() +{ + // Skip writing to hardware during initialization + if (initializationInProgress_) + { + for (size_t i = 0; i < channels_.size(); i++) + { + channelStates_[i] = false; + } + shutterState_ = false; + globalSwitch_ = false; + return DEVICE_OK; + } + + for (size_t i = 0; i < channels_.size(); i++) + { + // Turn off channel with coil + int ret = WriteSingleCoil(0x31 + (int)i, false); + if (ret != DEVICE_OK) + return ret; + } + + // Turn off global switch with coil + if (currentMode_ == 1) + { + int ret = WriteSingleCoil(0x30, false); + if (ret != DEVICE_OK) + return ret; + } + + shutterState_ = false; + globalSwitch_ = false; + return DEVICE_OK; +} + +int Controller::ReadDeviceStateByMode(int mode) +{ + int ret = DEVICE_OK; + + if (mode == 1) // Global mode + { + // Read global switch (coil 0x30) and global intensity (register 0x30) + bool globalSwitch = false; + ret = ReadSingleCoil(0x30, globalSwitch); + if (ret != DEVICE_OK) return ret; + globalSwitch_ = globalSwitch; + shutterState_ = globalSwitch; + + uint16_t globalIntensity = 0; + ret = ReadHoldingRegister(0x30, globalIntensity); + if (ret != DEVICE_OK) return ret; + globalIntensity_ = (int)globalIntensity; + } + else if (mode == 2 || mode == 3) // Independent mode or TTL mode + { + // Read all channel coils and registers in batch + int channelCount = (int)channels_.size(); + if (channelCount > 0) + { + // Read all channel coils at once + std::vector coilValues; + ret = ReadMultipleCoils(0x31, channelCount, coilValues); + if (ret != DEVICE_OK) return ret; + for (int i = 0; i < channelCount; i++) + { + channelStates_[i] = coilValues[i]; + } + + // Read all channel registers at once + std::vector regValues; + ret = ReadMultipleHoldingRegisters(0x31, channelCount, regValues); + if (ret != DEVICE_OK) return ret; + for (int i = 0; i < channelCount; i++) + { + channelIntensities_[i] = (int)regValues[i]; + } + } + } + + return DEVICE_OK; +} + +int Controller::ReadCurrentDeviceState() +{ + // Read Mode (register 0x20) + uint16_t modeVal = 0; + int ret = ReadHoldingRegister(0x20, modeVal); + if (ret != DEVICE_OK) return ret; + currentMode_ = (int)modeVal; + string modeStr; + if (currentMode_ == 1) + modeStr = "Global"; + else if (currentMode_ == 2) + modeStr = "independent"; + else + modeStr = "TTL"; + + // Read based on current mode + ret = ReadDeviceStateByMode(currentMode_); + if (ret != DEVICE_OK) return ret; + + // Update all properties with current values + // Update Mode property + SetProperty("Mode", modeStr.c_str()); + + // Update global properties + SetProperty("Global Switch", globalSwitch_ ? "on" : "off"); + SetProperty("Global Intensity", to_string(globalIntensity_).c_str()); + + // Update all channel properties + for (size_t i = 0; i < channels_.size(); i++) + { + ostringstream switchName; + switchName << channels_[i] << " Switch"; + SetProperty(switchName.str().c_str(), channelStates_[i] ? "on" : "off"); + + ostringstream intensityName; + intensityName << channels_[i] << " Intensity"; + SetProperty(intensityName.str().c_str(), to_string(channelIntensities_[i]).c_str()); + } + + return DEVICE_OK; +} + +int Controller::SetIntensity(int channel, int intensity) +{ + if (channel < 0 || channel >= (int)channelIntensities_.size()) + return DEVICE_ERR; + + channelIntensities_[channel] = intensity; + + if (shutterState_ && channelStates_[channel]) + { + return WriteHoldingRegister(channel, (uint16_t)intensity); + } + return DEVICE_OK; +} + +/////////////////////////////////////////////////////////////////////////////// +// Action handlers +/////////////////////////////////////////////////////////////////////////////// + +int Controller::OnPort(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(port_.c_str()); + } + else if (eAct == MM::AfterSet) + { + if (initialized_) + { + pProp->Set(port_.c_str()); + return ERR_PORT_CHANGE_FORBIDDEN; + } + pProp->Get(port_); + } + return DEVICE_OK; +} + +int Controller::OnMode(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + string modeStr; + if (currentMode_ == 1) + modeStr = "Global"; + else if (currentMode_ == 2) + modeStr = "independent"; + else + modeStr = "TTL"; + pProp->Set(modeStr.c_str()); + modeUpdated_ = false; + } + else if (eAct == MM::AfterSet) + { + // Skip writing to hardware during initialization + if (initializationInProgress_) + { + return DEVICE_OK; + } + + string newMode; + pProp->Get(newMode); + + int newModeValue = 1; + if (newMode == "Global") + newModeValue = 1; + else if (newMode == "independent") + newModeValue = 2; + else if (newMode == "TTL") + newModeValue = 3; + + int originalMode = currentMode_; + currentMode_ = newModeValue; + + int ret = WriteHoldingRegister(0x20, (uint16_t)newModeValue); + if (ret != DEVICE_OK) + { + currentMode_ = originalMode; + string originalModeStr; + if (originalMode == 1) + originalModeStr = "Global"; + else if (originalMode == 2) + originalModeStr = "independent"; + else if (originalMode == 3) + originalModeStr = "TTL"; + pProp->Set(originalModeStr.c_str()); + return ret; + } + + // After setting new mode, read the current state for that mode + ret = ReadDeviceStateByMode(newModeValue); + if (ret != DEVICE_OK) + { + return ret; + } + + // Update properties after reading new state + UpdatePropertiesFromDevice(); + } + return DEVICE_OK; +} + +int Controller::OnChannelSwitch(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + string propName = pProp->GetName(); + int index = -1; + auto it = channelSwitchLookup_.find(propName); + if (it != channelSwitchLookup_.end()) + { + index = it->second; + } + + if (index < 0 || index >= (int)channelStates_.size()) + return DEVICE_ERR; + + if (eAct == MM::BeforeGet) + { + pProp->Set(channelStates_[index] ? "on" : "off"); + channelSwitchUpdated_[index] = false; + } + else if (eAct == MM::AfterSet) + { + // Skip writing to hardware during initialization + if (initializationInProgress_) + { + return DEVICE_OK; + } + + string newVal; + pProp->Get(newVal); + bool newState = (newVal == "on"); + + // Save original state for rollback + bool originalState = channelStates_[index]; + + // Update the state + channelStates_[index] = newState; + + // Send channel switch state to coil (address 0x31 + index) + int ret = WriteSingleCoil(0x31 + index, newState); + if (ret != DEVICE_OK) + { + // Send failed, roll back + channelStates_[index] = originalState; + pProp->Set(originalState ? "on" : "off"); + return ret; + } + } + return DEVICE_OK; +} + +int Controller::OnChannelIntensity(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + string propName = pProp->GetName(); + int index = -1; + auto it = channelIntensityLookup_.find(propName); + if (it != channelIntensityLookup_.end()) + { + index = it->second; + } + + if (index < 0 || index >= (int)channelIntensities_.size()) + return DEVICE_ERR; + + if (eAct == MM::BeforeGet) + { + pProp->Set((long)channelIntensities_[index]); + channelIntensityUpdated_[index] = false; + } + else if (eAct == MM::AfterSet) + { + // Skip writing to hardware during initialization + if (initializationInProgress_) + { + return DEVICE_OK; + } + + long newVal; + pProp->Get(newVal); + int newIntensity = (int)newVal; + + // Validate and clamp the value + if (newIntensity < currentDevice_.brightnessMin) + newIntensity = currentDevice_.brightnessMin; + else if (newIntensity > currentDevice_.brightnessMax) + newIntensity = currentDevice_.brightnessMax; + + // Save original intensity for rollback + int originalIntensity = channelIntensities_[index]; + + // Update the intensity + channelIntensities_[index] = newIntensity; + + // Always send to device regardless of channel switch state + int ret = WriteHoldingRegister(0x31 + index, (uint16_t)newIntensity); + if (ret != DEVICE_OK) + { + // Send failed, roll back + channelIntensities_[index] = originalIntensity; + pProp->Set((long)originalIntensity); + return ret; + } + } + return DEVICE_OK; +} + +int Controller::OnGlobalSwitch(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(globalSwitch_ ? "on" : "off"); + globalSwitchUpdated_ = false; + } + else if (eAct == MM::AfterSet) + { + // Skip writing to hardware during initialization + if (initializationInProgress_) + { + return DEVICE_OK; + } + + string pos; + pProp->Get(pos); + bool newGlobalSwitch = (pos == "on"); + + // Save original state for rollback + bool originalGlobalSwitch = globalSwitch_; + + globalSwitch_ = newGlobalSwitch; + + // Write global switch to coil (address 0x30) + if (currentMode_ == 1) + { + int ret = WriteSingleCoil(0x30, newGlobalSwitch); + if (ret != DEVICE_OK) + { + // Roll back + globalSwitch_ = originalGlobalSwitch; + pProp->Set(originalGlobalSwitch ? "on" : "off"); + return ret; + } + } + + // Also update shutter state + shutterState_ = newGlobalSwitch; + } + return DEVICE_OK; +} + +int Controller::OnGlobalIntensity(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set((long)globalIntensity_); + globalIntensityUpdated_ = false; + } + else if (eAct == MM::AfterSet) + { + // Skip writing to hardware during initialization + if (initializationInProgress_) + { + return DEVICE_OK; + } + + long pos; + pProp->Get(pos); + int newGlobalIntensity = (int)pos; + + // Validate and clamp the value + if (newGlobalIntensity < currentDevice_.brightnessMin) + newGlobalIntensity = currentDevice_.brightnessMin; + else if (newGlobalIntensity > currentDevice_.brightnessMax) + newGlobalIntensity = currentDevice_.brightnessMax; + + // Save original global intensity for rollback + int originalGlobalIntensity = globalIntensity_; + + // Update global intensity + globalIntensity_ = newGlobalIntensity; + + // Write global intensity to register 0x30 + int ret = WriteHoldingRegister(0x30, (uint16_t)newGlobalIntensity); + if (ret != DEVICE_OK) + { + // Roll back everything + globalIntensity_ = originalGlobalIntensity; + pProp->Set((long)originalGlobalIntensity); + return ret; + } + } + return DEVICE_OK; +} + +int Controller::PollDeviceStatus() +{ + // Don't poll during initialization + if (!initializationComplete_) + { + return DEVICE_OK; + } + + // Read dirty bit coil 0x21 + bool dirtyBit = false; + int ret = ReadSingleCoil(0x21, dirtyBit); + if (ret != DEVICE_OK) + { + return ret; + } + + // Only read registers if dirty bit is set + if (dirtyBit) + { + // Read current mode first + uint16_t modeVal = 0; + ret = ReadHoldingRegister(0x20, modeVal); + if (ret != DEVICE_OK) return ret; + currentMode_ = (int)modeVal; + + // Read device state based on current mode + ret = ReadDeviceStateByMode(currentMode_); + if (ret != DEVICE_OK) return ret; + + // Update all properties + UpdatePropertiesFromDevice(); + } + + return DEVICE_OK; +} + +int Controller::ReadAllChannelRegisters() +{ + int channelCount = (int)channels_.size(); + if (channelCount > 0) + { + // Read all channel coils at once + std::vector coilValues; + int ret = ReadMultipleCoils(0x31, channelCount, coilValues); + if (ret != DEVICE_OK) return ret; + for (int i = 0; i < channelCount; i++) + { + channelStates_[i] = coilValues[i]; + } + + // Read all channel registers at once + std::vector regValues; + ret = ReadMultipleHoldingRegisters(0x31, channelCount, regValues); + if (ret != DEVICE_OK) return ret; + for (int i = 0; i < channelCount; i++) + { + channelIntensities_[i] = (int)regValues[i]; + } + } + return DEVICE_OK; +} + +void Controller::UpdatePropertiesFromDevice() +{ + // Update Mode property + modeUpdated_ = true; + UpdateProperty("Mode"); + + // Update global properties + globalSwitchUpdated_ = true; + UpdateProperty("Global Switch"); + + globalIntensityUpdated_ = true; + UpdateProperty("Global Intensity"); + + // Update all channel properties + for (size_t i = 0; i < channels_.size(); i++) + { + channelSwitchUpdated_[i] = true; + ostringstream switchName; + switchName << channels_[i] << " Switch"; + UpdateProperty(switchName.str().c_str()); + + channelIntensityUpdated_[i] = true; + ostringstream intensityName; + intensityName << channels_[i] << " Intensity"; + UpdateProperty(intensityName.str().c_str()); + } + + // Notify UI that properties have changed + OnPropertiesChanged(); +} + +int Controller::OnRefresh(MM::PropertyBase* pProp, MM::ActionType eAct) +{ + if (eAct == MM::BeforeGet) + { + pProp->Set(0L); + } + else if (eAct == MM::AfterSet) + { + // Read all device state + ReadCurrentDeviceState(); + // Reset the property back to 0 + pProp->Set(0L); + } + return DEVICE_OK; +} diff --git a/DeviceAdapters/3Z_Optics/3Z_Optics.h b/DeviceAdapters/3Z_Optics/3Z_Optics.h new file mode 100644 index 000000000..0475f8439 --- /dev/null +++ b/DeviceAdapters/3Z_Optics/3Z_Optics.h @@ -0,0 +1,138 @@ +#pragma once +/////////////////////////////////////////////////////////////////////////////// +// FILE: 3Z_Optics.h +// PROJECT: Micro-Manager +// SUBSYSTEM: DeviceAdapters +//----------------------------------------------------------------------------- +// DESCRIPTION: 3Z Optics Light Source driver +// COPYRIGHT: 3Z Optics +// LICENSE: BSD license + +#include "MMDevice.h" +#include "DeviceBase.h" +#include "DeviceThreads.h" +#include +#include +#include +#include +#include + +////////////////////////////////////////////////////////////////////////////// +// Error codes +// +#define ERR_PORT_CHANGE_FORBIDDEN 13001 +#define ERR_DEVICE_NOT_FOUND 13002 +#define ERR_MODBUS_COMM_ERROR 13003 +#define ERR_INIT 13005 + +static const char* g_DeviceName = "3Z_Optics"; +static const char* g_Prop_DeviceModel = "DeviceModel"; +static const char* g_Prop_DeviceName = "DeviceName"; + +struct DeviceConfig +{ + std::string name; + std::vector channels; + int brightnessMin; + int brightnessMax; +}; + +class PollingThread; + +class Controller : public CShutterBase +{ +public: + Controller(); + ~Controller(); + + friend class PollingThread; + + // MMDevice API + int Initialize(); + int Shutdown(); + + void GetName(char* pszName) const; + bool Busy(); + + // Shutter API + int SetOpen(bool open = true); + int GetOpen(bool& open); + int Fire(double /*interval*/) { return DEVICE_UNSUPPORTED_COMMAND; } + + // Action interface + int OnPort(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnMode(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnRefresh(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnChannelSwitch(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnChannelIntensity(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnGlobalSwitch(MM::PropertyBase* pProp, MM::ActionType eAct); + int OnGlobalIntensity(MM::PropertyBase* pProp, MM::ActionType eAct); + + static MMThreadLock& GetLock() { return lock_; } + +private: + bool initialized_; + std::string port_; + bool shutterState_; + int deviceModelId_; + DeviceConfig currentDevice_; + std::vector channels_; + std::vector channelStates_; + std::vector channelIntensities_; + std::map channelSwitchLookup_; + std::map channelIntensityLookup_; + int globalIntensity_; + bool globalSwitch_; + int currentMode_; + double pollIntervalMs_; + bool initializationComplete_; + bool initializationInProgress_; + + // Update flags for properties + std::vector channelSwitchUpdated_; + std::vector channelIntensityUpdated_; + bool globalSwitchUpdated_; + bool globalIntensityUpdated_; + bool modeUpdated_; + + static MMThreadLock lock_; + MMThreadLock commLock_; + PollingThread* mThread_; + + int ReadInputRegister(int addr, uint16_t& value); + int WriteHoldingRegister(int addr, uint16_t value); + int ReadHoldingRegister(int addr, uint16_t& value); + int ReadMultipleHoldingRegisters(int startAddr, int count, std::vector& values); + int WriteSingleCoil(int addr, bool on); + int ReadSingleCoil(int addr, bool& on); + int ReadMultipleCoils(int startAddr, int count, std::vector& values); + int ReadDeviceModel(); + bool LoadDeviceConfig(int modelId); + int ApplyChannelStates(); + int TurnAllOff(); + int SetIntensity(int channel, int intensity); + int ReadCurrentDeviceState(); + int PollDeviceStatus(); + int ReadAllChannelRegisters(); + int ReadDeviceStateByMode(int mode); + void UpdatePropertiesFromDevice(); + uint16_t CalculateCRC(const uint8_t* data, int length); + int SendModbusCommand(const std::vector& request, std::vector& response, int expectedResponseLength); +}; + +class PollingThread : public MMDeviceThreadBase +{ +public: + PollingThread(Controller& aController); + ~PollingThread(); + int svc(); + int open(void*) { return 0; } + int close(unsigned long) { return 0; } + + void Start(); + void Stop() { stop_ = true; } + +private: + Controller& aController_; + bool stop_; +}; diff --git a/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj b/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj new file mode 100644 index 000000000..8b17cdda6 --- /dev/null +++ b/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj @@ -0,0 +1,152 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {ed7a161c-fdb3-445d-9b01-3b662280d2ef} + My3ZOptics + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + DynamicLibrary + true + v143 + Unicode + true + + + DynamicLibrary + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + Default + + + Console + true + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + + + + + + + {b8c95f39-54bf-40a9-807b-598df2821d55} + + + + + + \ No newline at end of file diff --git a/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj.filters b/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj.filters new file mode 100644 index 000000000..34e994c68 --- /dev/null +++ b/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj.filters @@ -0,0 +1,27 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + 源文件 + + + + + 头文件 + + + \ No newline at end of file diff --git a/micromanager.sln b/micromanager.sln index 1c9487827..b48040bef 100644 --- a/micromanager.sln +++ b/micromanager.sln @@ -542,6 +542,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ThorlabsTSP01", "DeviceAdap EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SpinnakerC", "DeviceAdapters\SpinnakerC\SpinnakerC.vcxproj", "{4DEE8237-EF6F-426A-9DED-1909B6F3B18D}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "3Z_Optics", "DeviceAdapters\3Z_Optics\3Z_Optics.vcxproj", "{ED7A161C-FDB3-445D-9B01-3B662280D2EF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -1628,6 +1630,10 @@ Global {4DEE8237-EF6F-426A-9DED-1909B6F3B18D}.Debug|x64.Build.0 = Debug|x64 {4DEE8237-EF6F-426A-9DED-1909B6F3B18D}.Release|x64.ActiveCfg = Release|x64 {4DEE8237-EF6F-426A-9DED-1909B6F3B18D}.Release|x64.Build.0 = Release|x64 + {ED7A161C-FDB3-445D-9B01-3B662280D2EF}.Debug|x64.ActiveCfg = Debug|x64 + {ED7A161C-FDB3-445D-9B01-3B662280D2EF}.Debug|x64.Build.0 = Debug|x64 + {ED7A161C-FDB3-445D-9B01-3B662280D2EF}.Release|x64.ActiveCfg = Release|x64 + {ED7A161C-FDB3-445D-9B01-3B662280D2EF}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE From ca15dc6638314c3167cf4a265b778423da45432a Mon Sep 17 00:00:00 2001 From: Tiyi Date: Wed, 10 Jun 2026 11:54:20 +0800 Subject: [PATCH 2/3] Fix threading issues: protect shared state with Controller::lock_ - Added MMThreadGuard to all property action handlers - Protected ReadDeviceStateByMode, ReadCurrentDeviceState, etc. - Fixed potential data race between polling thread and core thread --- DeviceAdapters/3Z_Optics/3Z_Optics.cpp | 130 +++++++++++++++------ DeviceAdapters/3Z_Optics/3Z_Optics.h | 2 +- DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj | 2 +- 3 files changed, 96 insertions(+), 38 deletions(-) diff --git a/DeviceAdapters/3Z_Optics/3Z_Optics.cpp b/DeviceAdapters/3Z_Optics/3Z_Optics.cpp index b4c00d905..c1963940a 100644 --- a/DeviceAdapters/3Z_Optics/3Z_Optics.cpp +++ b/DeviceAdapters/3Z_Optics/3Z_Optics.cpp @@ -15,6 +15,19 @@ using namespace std; +#define MODE_ADDR 0x20 +#define CH1_INTENSITY_ADDR 0x31 +#define CH1_SWITCH_ADDR 0x31 +#define GLOBAL_INTENSITY_ADDR 0x30 +#define GLOBAL_SWITCH_ADDR 0x30 + +enum +{ + MODE_GLOBAL = 1, + MODE_INDEPENDENT, + MODE_TTL +}; + /////////////////////////////////////////////////////////////////////////////// // Static member initialization /////////////////////////////////////////////////////////////////////////////// @@ -76,6 +89,8 @@ int PollingThread::svc(void) void PollingThread::Start() { + // when thread is restarted, make sure to reset the stop flag + stop_ = false; activate(); } @@ -130,13 +145,13 @@ bool Controller::Busy() return false; } -uint16_t Controller::CalculateCRC(const uint8_t* data, int length) +uint16_t Controller::CalculateCRC(const uint8_t* data, size_t length) { uint16_t crc = 0xFFFF; - for (int i = 0; i < length; i++) + for (size_t i = 0; i < length; i++) { crc ^= (uint16_t)data[i]; - for (int j = 0; j < 8; j++) + for (size_t j = 0; j < 8; j++) { if (crc & 0x0001) { @@ -462,7 +477,7 @@ int readInteger(ifstream& file) while (file.get(c)) { - if (isdigit(c)) + if (isdigit(static_cast(c))) { result += c; } @@ -695,7 +710,7 @@ int Controller::Initialize() } // Create Name and Description properties with device name - CreateStringProperty(MM::g_Keyword_Name, currentDevice_.name.c_str(), false); + CreateStringProperty(MM::g_Keyword_Name, currentDevice_.name.c_str(), true); string desc = "3Z Optics " + currentDevice_.name; CreateStringProperty(MM::g_Keyword_Description, desc.c_str(), true); @@ -748,7 +763,7 @@ int Controller::Initialize() pAct = new CPropertyAction(this, &Controller::OnMode); CreateProperty("Mode", "Global", MM::String, false, pAct); AddAllowedValue("Mode", "Global"); - AddAllowedValue("Mode", "independent"); + AddAllowedValue("Mode", "Independent"); AddAllowedValue("Mode", "TTL"); // Refresh property - manual refresh button @@ -763,6 +778,8 @@ int Controller::Initialize() ret = ReadCurrentDeviceState(); if (ret != DEVICE_OK) { + initializationInProgress_ = false; + return ret; } // Update all properties with initial values @@ -801,23 +818,29 @@ int Controller::Shutdown() int Controller::SetOpen(bool open) { + MMThreadGuard guard(GetLock()); + // Skip writing to hardware during initialization if (initializationInProgress_) { shutterState_ = open; - globalSwitch_ = open; + if (currentMode_ == MODE_GLOBAL) + { + globalSwitch_ = open; + } + return DEVICE_OK; } if (open) { shutterState_ = true; - globalSwitch_ = true; // Turn on global switch - if (currentMode_==1) + if (currentMode_ == MODE_GLOBAL) { - int ret = WriteSingleCoil(0x30, true); + globalSwitch_ = true; + int ret = WriteSingleCoil(GLOBAL_SWITCH_ADDR, true); if (ret != DEVICE_OK) { shutterState_ = false; @@ -837,12 +860,16 @@ int Controller::SetOpen(bool open) int Controller::GetOpen(bool& open) { + MMThreadGuard guard(GetLock()); + open = shutterState_; return DEVICE_OK; } int Controller::ApplyChannelStates() { + MMThreadGuard guard(GetLock()); + // Skip writing to hardware during initialization if (initializationInProgress_) { @@ -852,7 +879,7 @@ int Controller::ApplyChannelStates() for (size_t i = 0; i < channelStates_.size(); i++) { // Set channel switch state with coil - int ret = WriteSingleCoil(0x31 + (int)i, channelStates_[i] && shutterState_); + int ret = WriteSingleCoil(CH1_SWITCH_ADDR + (int)i, channelStates_[i] && shutterState_); if (ret != DEVICE_OK) return ret; } @@ -861,6 +888,8 @@ int Controller::ApplyChannelStates() int Controller::TurnAllOff() { + MMThreadGuard guard(GetLock()); + // Skip writing to hardware during initialization if (initializationInProgress_) { @@ -876,15 +905,15 @@ int Controller::TurnAllOff() for (size_t i = 0; i < channels_.size(); i++) { // Turn off channel with coil - int ret = WriteSingleCoil(0x31 + (int)i, false); + int ret = WriteSingleCoil(CH1_SWITCH_ADDR + (int)i, false); if (ret != DEVICE_OK) return ret; } // Turn off global switch with coil - if (currentMode_ == 1) + if (currentMode_ == MODE_GLOBAL) { - int ret = WriteSingleCoil(0x30, false); + int ret = WriteSingleCoil(GLOBAL_SWITCH_ADDR, false); if (ret != DEVICE_OK) return ret; } @@ -896,19 +925,21 @@ int Controller::TurnAllOff() int Controller::ReadDeviceStateByMode(int mode) { + MMThreadGuard guard(GetLock()); + int ret = DEVICE_OK; if (mode == 1) // Global mode { // Read global switch (coil 0x30) and global intensity (register 0x30) bool globalSwitch = false; - ret = ReadSingleCoil(0x30, globalSwitch); + ret = ReadSingleCoil(GLOBAL_SWITCH_ADDR, globalSwitch); if (ret != DEVICE_OK) return ret; globalSwitch_ = globalSwitch; shutterState_ = globalSwitch; uint16_t globalIntensity = 0; - ret = ReadHoldingRegister(0x30, globalIntensity); + ret = ReadHoldingRegister(GLOBAL_INTENSITY_ADDR, globalIntensity); if (ret != DEVICE_OK) return ret; globalIntensity_ = (int)globalIntensity; } @@ -920,7 +951,7 @@ int Controller::ReadDeviceStateByMode(int mode) { // Read all channel coils at once std::vector coilValues; - ret = ReadMultipleCoils(0x31, channelCount, coilValues); + ret = ReadMultipleCoils(CH1_SWITCH_ADDR, channelCount, coilValues); if (ret != DEVICE_OK) return ret; for (int i = 0; i < channelCount; i++) { @@ -929,7 +960,7 @@ int Controller::ReadDeviceStateByMode(int mode) // Read all channel registers at once std::vector regValues; - ret = ReadMultipleHoldingRegisters(0x31, channelCount, regValues); + ret = ReadMultipleHoldingRegisters(CH1_INTENSITY_ADDR, channelCount, regValues); if (ret != DEVICE_OK) return ret; for (int i = 0; i < channelCount; i++) { @@ -943,18 +974,22 @@ int Controller::ReadDeviceStateByMode(int mode) int Controller::ReadCurrentDeviceState() { + MMThreadGuard guard(GetLock()); + // Read Mode (register 0x20) uint16_t modeVal = 0; int ret = ReadHoldingRegister(0x20, modeVal); if (ret != DEVICE_OK) return ret; currentMode_ = (int)modeVal; string modeStr; - if (currentMode_ == 1) + if (currentMode_ == MODE_GLOBAL) modeStr = "Global"; - else if (currentMode_ == 2) - modeStr = "independent"; - else + else if (currentMode_ == MODE_INDEPENDENT) + modeStr = "Independent"; + else if (currentMode_ == MODE_TTL) modeStr = "TTL"; + //else + // return DEVICE_ERR; // Read based on current mode ret = ReadDeviceStateByMode(currentMode_); @@ -985,6 +1020,8 @@ int Controller::ReadCurrentDeviceState() int Controller::SetIntensity(int channel, int intensity) { + MMThreadGuard guard(GetLock()); + if (channel < 0 || channel >= (int)channelIntensities_.size()) return DEVICE_ERR; @@ -992,7 +1029,7 @@ int Controller::SetIntensity(int channel, int intensity) if (shutterState_ && channelStates_[channel]) { - return WriteHoldingRegister(channel, (uint16_t)intensity); + return WriteHoldingRegister(CH1_INTENSITY_ADDR + channel, (uint16_t)intensity); } return DEVICE_OK; } @@ -1003,6 +1040,8 @@ int Controller::SetIntensity(int channel, int intensity) int Controller::OnPort(MM::PropertyBase* pProp, MM::ActionType eAct) { + MMThreadGuard guard(GetLock()); + if (eAct == MM::BeforeGet) { pProp->Set(port_.c_str()); @@ -1021,14 +1060,16 @@ int Controller::OnPort(MM::PropertyBase* pProp, MM::ActionType eAct) int Controller::OnMode(MM::PropertyBase* pProp, MM::ActionType eAct) { + MMThreadGuard guard(GetLock()); + if (eAct == MM::BeforeGet) { string modeStr; - if (currentMode_ == 1) + if (currentMode_ == MODE_GLOBAL) modeStr = "Global"; - else if (currentMode_ == 2) - modeStr = "independent"; - else + else if (currentMode_ == MODE_INDEPENDENT) + modeStr = "Independent"; + else if (currentMode_ == MODE_TTL) modeStr = "TTL"; pProp->Set(modeStr.c_str()); modeUpdated_ = false; @@ -1047,7 +1088,7 @@ int Controller::OnMode(MM::PropertyBase* pProp, MM::ActionType eAct) int newModeValue = 1; if (newMode == "Global") newModeValue = 1; - else if (newMode == "independent") + else if (newMode == "Independent") newModeValue = 2; else if (newMode == "TTL") newModeValue = 3; @@ -1063,7 +1104,7 @@ int Controller::OnMode(MM::PropertyBase* pProp, MM::ActionType eAct) if (originalMode == 1) originalModeStr = "Global"; else if (originalMode == 2) - originalModeStr = "independent"; + originalModeStr = "Independent"; else if (originalMode == 3) originalModeStr = "TTL"; pProp->Set(originalModeStr.c_str()); @@ -1085,6 +1126,8 @@ int Controller::OnMode(MM::PropertyBase* pProp, MM::ActionType eAct) int Controller::OnChannelSwitch(MM::PropertyBase* pProp, MM::ActionType eAct) { + MMThreadGuard guard(GetLock()); + string propName = pProp->GetName(); int index = -1; auto it = channelSwitchLookup_.find(propName); @@ -1120,7 +1163,7 @@ int Controller::OnChannelSwitch(MM::PropertyBase* pProp, MM::ActionType eAct) channelStates_[index] = newState; // Send channel switch state to coil (address 0x31 + index) - int ret = WriteSingleCoil(0x31 + index, newState); + int ret = WriteSingleCoil(CH1_SWITCH_ADDR + index, newState && shutterState_); if (ret != DEVICE_OK) { // Send failed, roll back @@ -1134,6 +1177,8 @@ int Controller::OnChannelSwitch(MM::PropertyBase* pProp, MM::ActionType eAct) int Controller::OnChannelIntensity(MM::PropertyBase* pProp, MM::ActionType eAct) { + MMThreadGuard guard(GetLock()); + string propName = pProp->GetName(); int index = -1; auto it = channelIntensityLookup_.find(propName); @@ -1175,7 +1220,7 @@ int Controller::OnChannelIntensity(MM::PropertyBase* pProp, MM::ActionType eAct) channelIntensities_[index] = newIntensity; // Always send to device regardless of channel switch state - int ret = WriteHoldingRegister(0x31 + index, (uint16_t)newIntensity); + int ret = WriteHoldingRegister(CH1_INTENSITY_ADDR + index, (uint16_t)newIntensity); if (ret != DEVICE_OK) { // Send failed, roll back @@ -1189,6 +1234,8 @@ int Controller::OnChannelIntensity(MM::PropertyBase* pProp, MM::ActionType eAct) int Controller::OnGlobalSwitch(MM::PropertyBase* pProp, MM::ActionType eAct) { + MMThreadGuard guard(GetLock()); + if (eAct == MM::BeforeGet) { pProp->Set(globalSwitch_ ? "on" : "off"); @@ -1212,9 +1259,9 @@ int Controller::OnGlobalSwitch(MM::PropertyBase* pProp, MM::ActionType eAct) globalSwitch_ = newGlobalSwitch; // Write global switch to coil (address 0x30) - if (currentMode_ == 1) + if (currentMode_ == MODE_GLOBAL) { - int ret = WriteSingleCoil(0x30, newGlobalSwitch); + int ret = WriteSingleCoil(GLOBAL_SWITCH_ADDR, newGlobalSwitch); if (ret != DEVICE_OK) { // Roll back @@ -1232,6 +1279,8 @@ int Controller::OnGlobalSwitch(MM::PropertyBase* pProp, MM::ActionType eAct) int Controller::OnGlobalIntensity(MM::PropertyBase* pProp, MM::ActionType eAct) { + MMThreadGuard guard(GetLock()); + if (eAct == MM::BeforeGet) { pProp->Set((long)globalIntensity_); @@ -1244,6 +1293,11 @@ int Controller::OnGlobalIntensity(MM::PropertyBase* pProp, MM::ActionType eAct) { return DEVICE_OK; } + if (currentMode_ != MODE_GLOBAL) + { + // Global Intensity only applies in Global mode + return DEVICE_OK; + } long pos; pProp->Get(pos); @@ -1262,7 +1316,7 @@ int Controller::OnGlobalIntensity(MM::PropertyBase* pProp, MM::ActionType eAct) globalIntensity_ = newGlobalIntensity; // Write global intensity to register 0x30 - int ret = WriteHoldingRegister(0x30, (uint16_t)newGlobalIntensity); + int ret = WriteHoldingRegister(GLOBAL_INTENSITY_ADDR, (uint16_t)newGlobalIntensity); if (ret != DEVICE_OK) { // Roll back everything @@ -1312,12 +1366,14 @@ int Controller::PollDeviceStatus() int Controller::ReadAllChannelRegisters() { + MMThreadGuard guard(GetLock()); + int channelCount = (int)channels_.size(); if (channelCount > 0) { // Read all channel coils at once std::vector coilValues; - int ret = ReadMultipleCoils(0x31, channelCount, coilValues); + int ret = ReadMultipleCoils(CH1_SWITCH_ADDR, channelCount, coilValues); if (ret != DEVICE_OK) return ret; for (int i = 0; i < channelCount; i++) { @@ -1326,7 +1382,7 @@ int Controller::ReadAllChannelRegisters() // Read all channel registers at once std::vector regValues; - ret = ReadMultipleHoldingRegisters(0x31, channelCount, regValues); + ret = ReadMultipleHoldingRegisters(CH1_INTENSITY_ADDR, channelCount, regValues); if (ret != DEVICE_OK) return ret; for (int i = 0; i < channelCount; i++) { @@ -1369,6 +1425,8 @@ void Controller::UpdatePropertiesFromDevice() int Controller::OnRefresh(MM::PropertyBase* pProp, MM::ActionType eAct) { + MMThreadGuard guard(GetLock()); + if (eAct == MM::BeforeGet) { pProp->Set(0L); diff --git a/DeviceAdapters/3Z_Optics/3Z_Optics.h b/DeviceAdapters/3Z_Optics/3Z_Optics.h index 0475f8439..c9fae9d28 100644 --- a/DeviceAdapters/3Z_Optics/3Z_Optics.h +++ b/DeviceAdapters/3Z_Optics/3Z_Optics.h @@ -116,7 +116,7 @@ class Controller : public CShutterBase int ReadAllChannelRegisters(); int ReadDeviceStateByMode(int mode); void UpdatePropertiesFromDevice(); - uint16_t CalculateCRC(const uint8_t* data, int length); + uint16_t CalculateCRC(const uint8_t* data, size_t length); int SendModbusCommand(const std::vector& request, std::vector& response, int expectedResponseLength); }; diff --git a/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj b/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj index 8b17cdda6..2e0ed778a 100644 --- a/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj +++ b/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj @@ -33,7 +33,7 @@ Unicode - Application + DynamicLibrary false v143 true From 60fcfa4d34a1e4b0aa070f9b0d9908870c1e702b Mon Sep 17 00:00:00 2001 From: "Mark A. Tsuchida" Date: Mon, 22 Jun 2026 12:44:00 -0500 Subject: [PATCH 3/3] 3Z_Optics: fix project, solution files - Remove Win32 (32-bit) builds from .vcxproj; other normalization - Use English filters in .vcxproj.filters - Missing EndProject in .sln --- DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj | 68 ++----------------- .../3Z_Optics/3Z_Optics.vcxproj.filters | 16 ++--- micromanager.sln | 1 + 3 files changed, 14 insertions(+), 71 deletions(-) diff --git a/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj b/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj index 2e0ed778a..72b2882d7 100644 --- a/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj +++ b/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj @@ -1,14 +1,6 @@ - - Debug - Win32 - - - Release - Win32 - Debug x64 @@ -22,23 +14,10 @@ 17.0 Win32Proj {ed7a161c-fdb3-445d-9b01-3b662280d2ef} - My3ZOptics + 3Z_Optics 10.0 - - Application - true - v143 - Unicode - - - DynamicLibrary - false - v143 - true - Unicode - DynamicLibrary true @@ -58,16 +37,6 @@ - - - - - - - - - - @@ -82,42 +51,15 @@ - - - Level3 - true - WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - - - - - Level3 - true - true - true - WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) - true - - - Console - true - - Level3 true - _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + _DEBUG;%(PreprocessorDefinitions) true - Default - Console + Windows true @@ -127,11 +69,11 @@ true true true - NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + NDEBUG;%(PreprocessorDefinitions) true - Console + Windows true diff --git a/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj.filters b/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj.filters index 34e994c68..88a6387d1 100644 --- a/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj.filters +++ b/DeviceAdapters/3Z_Optics/3Z_Optics.vcxproj.filters @@ -1,27 +1,27 @@  - - {4FC737F1-C7A5-4376-A066-2A32D752A2FF} - cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx - - + {93995380-89BD-4b04-88EB-625FBE52EBFB} h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd - + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms - 源文件 + Source Files - 头文件 + Header Files \ No newline at end of file diff --git a/micromanager.sln b/micromanager.sln index 10bd14be5..d04cd3d15 100644 --- a/micromanager.sln +++ b/micromanager.sln @@ -543,6 +543,7 @@ EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SpinnakerC", "DeviceAdapters\SpinnakerC\SpinnakerC.vcxproj", "{4DEE8237-EF6F-426A-9DED-1909B6F3B18D}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "3Z_Optics", "DeviceAdapters\3Z_Optics\3Z_Optics.vcxproj", "{ED7A161C-FDB3-445D-9B01-3B662280D2EF}" +EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NikonAZ100SDK", "SecretDeviceAdapters\NikonAZ100SDK\NikonAZ100SDK.vcxproj", "{F3A2D5B7-8C91-4E6A-B4D0-7E2F1A9C3D5E}" EndProject Global