Skip to content
Snippets Groups Projects
Commit 66e088c0 authored by Christian Koernig's avatar Christian Koernig
Browse files
parents b3556276 90c2ed73
No related branches found
No related tags found
No related merge requests found
......@@ -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.
......
......@@ -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():
......
......@@ -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
......@@ -42,6 +42,8 @@ namespace std {
}
}
%ignore AmptekSimulatorConnectionHandler::start_time;
%include "include/AmptekHardwareInterface.h"
%include "include/AmptekStatus.h"
%include "include/AmptekSpectrum.h"
......@@ -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
......@@ -25,6 +25,8 @@ class AmptekPX5(Device):
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,
fget = "get_maxinfoage", fset = "set_maxinfoage")
......@@ -50,6 +52,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()
......@@ -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";
......
......@@ -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;
......@@ -170,7 +173,7 @@ 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,6 +261,18 @@ void AmptekSimulatorConnectionHandler::readConfig(char* configs){
std::stringstream linestream(configline);
std::getline(linestream, config_name,'=');
linestream >> config_value;
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;
}
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;
......@@ -272,7 +288,7 @@ void AmptekSimulatorConnectionHandler::readConfig(char* configs){
}
}
text_configs[config_name] = config_value;
}
}catch(...){
std::cerr << "Failed reading config " << configline << std::endl;
}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment