diff --git a/README.md b/README.md index 4aeebb25176d5d6baaff5e436e797ad7bed6d9fb..e9a73780d10af690bc9581f910541bff5d043ed3 100644 --- a/README.md +++ b/README.md @@ -2,10 +2,10 @@ AmptekHardwareInterface ======================= Implementation of [Amptek's](https://www.amptek.com) DP5 Protocol for controlling PX5/DP5 Digital Pulse Processors. The code is tested with both, the PX5/XR-100 combination and the X-123 CdTe and SDD detectors. -The low level communication is implemented in C++ and supports both Ethernet via UDP and USB communication. A autogenerated high-level interface python as well as a Device Server for the [Tango Controls](https://www.tango-controls.org) SCADA system is available, allowing simple integration into many synchrotron beamlines. +The low level communication is implemented in C++ and supports both Ethernet via UDP and USB communication. A autogenerated high-level python interface as well as a Device Server for the [Tango Controls](https://www.tango-controls.org) SCADA system is available, allowing simple integration into many synchrotron beamlines. The low level implementation is based on [previous work](https://github.com/ALBA-Synchrotron/AmptekPX5DS) at the ALBA synchrotron. However, the communication layer has been completely redesigned for more stability and added support for USB connections, circumventing the bottleneck of the low speed base-10 ethernet PHY of the hardware. -This code is meant to be a mostly drop-in replacement of the original Tango server, therefore the same server name and many commands & attributes are used. So far, the SCA channels and the MCS mode is not implemented, if those are needed, the original code should be used. +This code is meant to be a mostly drop-in replacement of the original Tango server, therefore the same server name and many commands & attributes are used. So far, the SCA channels and the MCS mode are not implemented. If those are needed, the original code should be used. Additionally, a basic simulator interface can be used during DAQ logic development if no hardware is available. diff --git a/examples/simple_connection.py b/examples/simple_connection.py index e2a9e7db0ad738efd447ae032429c461e64d9918..2fb28c166a88755dc9f06fb45804d0b59f16f43b 100644 --- a/examples/simple_connection.py +++ b/examples/simple_connection.py @@ -15,9 +15,9 @@ import amptek_hardware_interface as ahi # create the interface amptek = ahi.AmptekHardwareInterface() -# connect the intrerface via USB to the first DP5 device. +# connect the interface via USB to the first DP5 device. # To connect to a specific device, change the -1 to the serial number -# For basic tests, the simulator interface can be used, of no hardware is available. +# For basic tests, the simulator interface can be used if no hardware is available. # Use amptek.connectSimulator() instead amptek.connectUSB(-1) @@ -60,7 +60,7 @@ print("Acquisition started") while True: time.sleep(1) status = amptek.updateStatus(-1) - print("\rAccumulation Time: {:.2f}s, Fast Counts: {:d}, Slow Counts: {:d}".format( status.AccTime(), status.FastCount(), status.SlowCount() ), end="", flush=True) + print(f"\rAccumulation Time: {status.AccTime():.2f}s, Fast Counts: {status.FastCount():d}, Slow Counts: {status.SlowCount():d}", end="", flush=True) # test if finished if not status.IsEnabled(): diff --git a/include/AmptekSimulatorConnectionHandler.h b/include/AmptekSimulatorConnectionHandler.h index b1823e4a2d2714345409de30c7116867eaa34669..411445b3dce00dc8ad70c9ad79e72f8ca0878487 100644 --- a/include/AmptekSimulatorConnectionHandler.h +++ b/include/AmptekSimulatorConnectionHandler.h @@ -4,6 +4,7 @@ #include "AmptekConnectionHandler.h" #include <thread> // std::thread #include <map> +#include <random> #define STATUS_SIZE 64 class AmptekSimulatorConnectionHandler : public AmptekConnectionHandler{ @@ -36,6 +37,10 @@ private: double acquisition_time = -1; std::thread* spectrum_thread; + std::chrono::system_clock::time_point start_time; + + std::default_random_engine delay_gen; + std::normal_distribution<double> delay_dist; }; #endif \ No newline at end of file diff --git a/python/amptek_hardware_interface/AmptekHardwareInterface.i b/python/amptek_hardware_interface/AmptekHardwareInterface.i index 4101576439fab34d6387e7d8e095d8ab16299216..40e4b733b691be4c7e2b1d5a8c9ca2773cc920a0 100644 --- a/python/amptek_hardware_interface/AmptekHardwareInterface.i +++ b/python/amptek_hardware_interface/AmptekHardwareInterface.i @@ -42,6 +42,8 @@ namespace std { } } +%ignore AmptekSimulatorConnectionHandler::start_time; + %include "include/AmptekHardwareInterface.h" %include "include/AmptekStatus.h" %include "include/AmptekSpectrum.h" diff --git a/python/amptek_hardware_interface/AmptekPX5 b/python/amptek_hardware_interface/AmptekPX5 index 9d9f913fb053f3221243c202a714f318eb0166e1..82451d0e201be64cc5d60acd19e1fc7a4f6098de 100644 --- a/python/amptek_hardware_interface/AmptekPX5 +++ b/python/amptek_hardware_interface/AmptekPX5 @@ -6,7 +6,7 @@ logging.basicConfig( level=logging.DEBUG, datefmt='%Y-%m-%d %H:%M:%S') -from tango import AttrQuality, AttrWriteType, DispLevel, DevState, DebugIt +from tango import AttrQuality, AttrWriteType, DispLevel, DevState, DebugIt, AttributeProxy, EventType from tango.server import Device, attribute, command, pipe, device_property from collections import OrderedDict import tango @@ -24,6 +24,8 @@ class AmptekPX5(Device): detector_model = device_property(dtype=str, default_value="CdTe") connection_mode = device_property(dtype=str, default_value="UDP") configuration_file = device_property(dtype=str, default_value="") + + incident_channel = device_property(dtype=str, default_value = "") MaxInfoAge = attribute(label = "MaxInfoAge", dtype=float, @@ -49,6 +51,9 @@ class AmptekPX5(Device): SlowCount = attribute(label="SlowCount", dtype=int, fget=lambda self: self.GetSlowCount(self._max_info_age)) + + FastRate = attribute(label="FastRate", dtype=int, + fget=lambda self: self.GetFastRate(self._max_info_age)) GpCount = attribute(label="GpCount", dtype=int, fget=lambda self: self.GetGpCount(self._max_info_age)) @@ -246,6 +251,21 @@ class AmptekPX5(Device): self.load_config_dict(c) + if self.connection_mode == 'Simulator': + if self.incident_channel != "": + logging.debug(f"Add change event callback to determine incident rate. Channel is {self.incident_channel}") + self.incident_proxy = AttributeProxy(self.incident_channel) + d = self.incident_proxy.get_device_proxy() + self.incident_proxy.poll(50) + cnf = d.get_attribute_config(self.incident_proxy.name()) + cnf.events.ch_event.abs_change = "1" + d.set_attribute_config(cnf) + self.incident_proxy.subscribe_event(EventType.CHANGE_EVENT, self.update_simulator_rate, [], False) + else: + logging.debug(f"No (valid) channel for incident rate is given. Value will be fixed (channel: '{self.incident_channel}')") + else: + logging.debug("Device not in Simulator Mode. 'inciden_channel' property will be ignored") + def load_config_dict( self, configs ): cmd_strings = [] for cmd, setting in configs.items(): @@ -287,6 +307,8 @@ class AmptekPX5(Device): self.set_state(tango.DevState.ALARM) self.error_stream("Failed starting the detector") raise RuntimeError("Failed starting the detector") + else: + self.set_state(tango.DevState.MOVING) @command def Disable(self): @@ -563,6 +585,16 @@ class AmptekPX5(Device): def GetFastCount(self, max_age_ms): return self.get_status_attribute(max_age_ms, "FastCount") + + @command(dtype_in = float, dtype_out=float) + def GetFastRate(self, max_age_ms): + counts = self.get_status_attribute(max_age_ms, "FastCount") + acctime = self.get_status_attribute(max_age_ms, "AccTime") + if (acctime) > 0: + return counts/acctime + else: + return 0 + @command(dtype_in=float, dtype_out=(int,)) def GetSpectrum(self, max_age_ms): return self.interface.GetSpectrum(max_age_ms) @@ -802,5 +834,9 @@ class AmptekPX5(Device): outstring += "<<END>>\n" return outstring + def update_simulator_rate(self, event): + cnf_str = f"SCR={event.attr_value.value:.0f}" + self.interface.SetTextConfiguration( [cnf_str] ) + if __name__ == "__main__": AmptekPX5.run_server() diff --git a/src/AmptekHardwareInterface.cpp b/src/AmptekHardwareInterface.cpp index 44c92da50481bf7138d70436550b16415fd441b9..23bbb799dabda34a15eaa4530ce168b9013fe363 100644 --- a/src/AmptekHardwareInterface.cpp +++ b/src/AmptekHardwareInterface.cpp @@ -181,7 +181,7 @@ bool AmptekHardwareInterface::ClearSpectrum(){ * or any other mode that temporarily blockes the MCA. Therefore, accumulation time is larger than the real measurement duration * * @see SetPresetAccumulationTime, SetPresetRealTime, SetPresetCounts - * @param t accumulation time in seconds. Minimum is 0.1. If below zero, the accumulation time is not part of the stop condition during acquisition + * @param t accumulation time in seconds. Minimum is 0.1s. If below or equal zero, the accumulation time is not part of the stop condition during acquisition * @return true on success * @return false on failure */ @@ -202,7 +202,7 @@ bool AmptekHardwareInterface::SetPresetAccumulationTime(double t){ * @brief Set the real time for a measurement. The detector will stop automatically when the duration is reached * * @see SetPresetAccumulationTime, SetPresetRealTime, SetPresetCounts - * @param t real time in seconds. If below zero, the real time is not part of the stop condition during acquisition + * @param t real time in seconds. Minimum is 10ms. If below or equal zero, the real time is not part of the stop condition during acquisition * @return true on success * @return false on failure */ @@ -210,7 +210,7 @@ bool AmptekHardwareInterface::SetPresetRealTime(double t){ std::stringstream cmd_stream; cmd_stream << "PRER="; if (t>0){ - cmd_stream << std::fixed << std::setprecision(1) << t; + cmd_stream << std::fixed << std::setprecision(2) << t; } else{ cmd_stream << "OFF"; diff --git a/src/AmptekSimulatorConnectionHandler.cpp b/src/AmptekSimulatorConnectionHandler.cpp index eb52b5a751dba2bfb87c3c0c293d72c298940a51..fec5a0c78d5730bdd79355c70cf2ba0869838b04 100644 --- a/src/AmptekSimulatorConnectionHandler.cpp +++ b/src/AmptekSimulatorConnectionHandler.cpp @@ -5,6 +5,8 @@ #include <chrono> #include <iostream> #include <iomanip> +#include <cmath> + Packet AmptekSimulatorConnectionHandler::sendAndReceive( const Packet& request){ byte pid1 = request.at(PID1); @@ -142,11 +144,12 @@ Packet AmptekSimulatorConnectionHandler::sendAndReceive( const Packet& request){ AmptekSimulatorConnectionHandler::AmptekSimulatorConnectionHandler(){ spectrum = new unsigned int[speclen]; + start_time = std::chrono::system_clock::now(); clear(); initConfigs(); + delay_dist = std::normal_distribution<double>( 1000.,31. ); // default count rate is 1000cps, i.e. 1000us delay spectrum_thread = new std::thread( [&]{ - auto start_time = std::chrono::system_clock::now(); while( is_running ){ if (flag_enable){ is_enabled = true; @@ -169,8 +172,8 @@ AmptekSimulatorConnectionHandler::AmptekSimulatorConnectionHandler(){ } } } - - std::this_thread::sleep_for(std::chrono::milliseconds(2)); + + std::this_thread::sleep_for(std::chrono::microseconds( std::max(1, std::min((int) delay_dist(delay_gen),100000) ) ) ); } @@ -195,6 +198,7 @@ void AmptekSimulatorConnectionHandler::disable(){ void AmptekSimulatorConnectionHandler::clear(){ total_counts = 0; acc_time = 0; + start_time = std::chrono::system_clock::now(); for (int i = 0; i < speclen; ++i){ spectrum[i] = 0; } @@ -257,22 +261,34 @@ void AmptekSimulatorConnectionHandler::readConfig(char* configs){ std::stringstream linestream(configline); std::getline(linestream, config_name,'='); linestream >> config_value; - if (config_name == "MCAC"){ - speclen = std::stoi(config_value); - delete[] spectrum; - spectrum = new unsigned int[speclen]; - for(int i = 0; i < speclen; ++i){ - spectrum[i] = 0; + + if (config_name == "SCR"){ + // this is a special command to set the simulator count rate, not part of the DP5 protocol! + // calculate average delay in us between pulses for target count rate + double scr = std::stoi(config_value); + if (scr < 1){ + scr = 1; } - }else if (config_name == "PRET"){ - if (config_value == "OFF"){ - acquisition_time = -1; - }else{ - acquisition_time = std::stod( config_value ); + double dt = 1e6/scr; + delay_dist = std::normal_distribution<double>( dt,sqrt(dt) ); + } + else{ + if (config_name == "MCAC"){ + speclen = std::stoi(config_value); + delete[] spectrum; + spectrum = new unsigned int[speclen]; + for(int i = 0; i < speclen; ++i){ + spectrum[i] = 0; + } + }else if (config_name == "PRET"){ + if (config_value == "OFF"){ + acquisition_time = -1; + }else{ + acquisition_time = std::stod( config_value ); + } } + text_configs[config_name] = config_value; } - text_configs[config_name] = config_value; - }catch(...){ std::cerr << "Failed reading config " << configline << std::endl; }