From 3d756be15fa57fa37dc46c56a26138a47743e2ea Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Fri, 21 Feb 2025 16:35:50 +0100
Subject: [PATCH 01/13] fix: update format precision for channelX and speedX
 attributes to match initialX attributes

---
 src/SingleShotAO.cpp | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/SingleShotAO.cpp b/src/SingleShotAO.cpp
index 699767f..ee7b789 100755
--- a/src/SingleShotAO.cpp
+++ b/src/SingleShotAO.cpp
@@ -429,7 +429,7 @@ void SingleShotAO::init_device()
 		dai_channel.tai.max_value = "10.0";
 		dai_channel.tai.min_value = "-10.0";
 		dai_channel.tai.description = "Output value for channel " + oss.str() + " (in measurementUnit).";
-		dai_channel.tai.format = "%1.1f";
+		dai_channel.tai.format = "%1.2f";
 		dai_channel.memorized = true;
 		dai_channel.cdb = false;
 
@@ -461,7 +461,7 @@ void SingleShotAO::init_device()
 		dai_speed.tai.standard_unit = "V/s";
 		dai_speed.tai.display_unit = "V/s";
 		dai_speed.tai.description = "Speed for ramp generation, in V/s. If speed is NULL, no ramp generated but direct write on channel output " + oss.str() + " (in measurementUnit).";
-		dai_speed.tai.format = "%1.1f";
+		dai_speed.tai.format = "%1.2f";
 
 		//- cleanup tango db option: cleanup tango db when removing this dyn. attr. (i.e. erase its properties from db)
 		dai_speed.memorized = true;
-- 
GitLab


From 915889e15d7f6e1d89442db101d42456127f3040 Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Fri, 21 Feb 2025 16:40:59 +0100
Subject: [PATCH 02/13] fix: rename get_device_properties back to
 get_device_property for consistency (+ add comments)

---
 src/SingleShotAO.cpp | 45 +++++++++++++++++++++-----------------------
 src/SingleShotAO.h   |  2 +-
 2 files changed, 22 insertions(+), 25 deletions(-)

diff --git a/src/SingleShotAO.cpp b/src/SingleShotAO.cpp
index ee7b789..96a72ba 100755
--- a/src/SingleShotAO.cpp
+++ b/src/SingleShotAO.cpp
@@ -247,7 +247,7 @@ void SingleShotAO::init_device()
 	//--------------------------------------------
 	try 
 	{
-		get_device_properties();
+		get_device_property();
 	}
 	catch (const Tango::DevFailed& df)
 	{
@@ -570,57 +570,52 @@ void SingleShotAO::init_device()
 
 //+----------------------------------------------------------------------------
 //
-// method : 		SingleShotAO::get_device_properties()
+// method : 		SingleShotAO::get_device_property()
 // 
 // description : 	Read the device properties from database.
 //
 //-----------------------------------------------------------------------------
-void SingleShotAO::get_device_properties()
+void SingleShotAO::get_device_property()
 {
-	//	Initialize your default values here (if not done with  POGO).
-	//------------------------------------------------------------------
-
-	//	Read device properties from database.(Automatic code generation)
+	//	Read device properties from database
 	//------------------------------------------------------------------
 	Tango::DbData	dev_prop;
 	dev_prop.push_back(Tango::DbDatum("BoardNum"));
 	dev_prop.push_back(Tango::DbDatum("BoardType"));
 
-	//	Call database and extract values
+	// Call database and extract values
 	//--------------------------------------------
-	if (Tango::Util::instance()->_UseDb==true)
+	if (Tango::Util::instance()->_UseDb==true) {
 		get_db_device()->get_property(dev_prop);
-	Tango::DbDatum	def_prop, cl_prop;
-	SingleShotAOClass	*ds_class =
-		(static_cast<SingleShotAOClass *>(get_device_class()));
+	}
+	Tango::DbDatum def_prop, cl_prop;
+	SingleShotAOClass *ds_class = (static_cast<SingleShotAOClass *>(get_device_class()));
 	int	i = -1;
 
-	//	Try to initialize BoardNum from class property
+	// Try to initialize BoardNum from class property
 	cl_prop = ds_class->get_class_property(dev_prop[++i].name);
 	if (cl_prop.is_empty()==false)	cl_prop  >>  boardNum;
 	else {
-		//	Try to initialize BoardNum from default device value
+		// Try to initialize BoardNum from default device value
 		def_prop = ds_class->get_default_device_property(dev_prop[i].name);
 		if (def_prop.is_empty()==false)	def_prop  >>  boardNum;
 	}
-	//	And try to extract BoardNum value from database
+	// And try to extract BoardNum value from database
 	if (dev_prop[i].is_empty()==false)	dev_prop[i]  >>  boardNum;
 
-	//	Try to initialize BoardType from class property
+	// Try to initialize BoardType from class property
 	cl_prop = ds_class->get_class_property(dev_prop[++i].name);
 	if (cl_prop.is_empty()==false)	cl_prop  >>  boardType;
 	else {
-		//	Try to initialize BoardType from default device value
+		// Try to initialize BoardType from default device value
 		def_prop = ds_class->get_default_device_property(dev_prop[i].name);
 		if (def_prop.is_empty()==false)	def_prop  >>  boardType;
 	}
-	//	And try to extract BoardType value from database
+	// And try to extract BoardType value from database
 	if (dev_prop[i].is_empty()==false)	dev_prop[i]  >>  boardType;
 
-
-
-	//	End of Automatic code generation
-	//------------------------------------------------------------------
+	// Check critical properties being present
+	//--------------------------------------------
 
 	critical_properties_missing = false;
 
@@ -639,8 +634,11 @@ void SingleShotAO::get_device_properties()
 		return;
 	}
 
+	// Check critical properties being valid
+	//--------------------------------------------
+
 	//- <BoardNum> -----------------------
-	if (boardNum > 7) 
+	if (boardNum < 0 || boardNum > 7)
 	{
 		boardNum = 0;
 		ERROR_STREAM << "device property <BoardNum> is invalid. Valid range is [0..7]" << endl;
@@ -665,7 +663,6 @@ void SingleShotAO::get_device_properties()
 		ERROR_STREAM << "device property <BoardType> is invalid [supported hw: MAO_6208 or MAO_6216]" << endl;
 		critical_properties_missing = true;
 	}
-
 }
 
 
diff --git a/src/SingleShotAO.h b/src/SingleShotAO.h
index 8e259d0..7faa182 100755
--- a/src/SingleShotAO.h
+++ b/src/SingleShotAO.h
@@ -219,7 +219,7 @@ public :
 /**
  *	Read the device properties from database
  */
-	 void get_device_properties();
+	 void get_device_property();
 //@}
 
 	//	Here is the end of the automatic code generation part
-- 
GitLab


From 1085d8888f3e1935e49ac54ab85630cf9c67c76f Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Fri, 21 Feb 2025 16:58:02 +0100
Subject: [PATCH 03/13] feat: add EnableRamps and WriteMemorizedValuesAtInit
 properties with default values (do nothing so far)

---
 doc/doc_html/Properties.html |  34 ++++++++-
 src/SingleShotAO.cpp         |  31 ++++++++
 src/SingleShotAO.h           |   8 ++
 src/SingleShotAOClass.cpp    | 144 ++++++++++-------------------------
 4 files changed, 109 insertions(+), 108 deletions(-)

diff --git a/doc/doc_html/Properties.html b/doc/doc_html/Properties.html
index 388a562..b0ca154 100755
--- a/doc/doc_html/Properties.html
+++ b/doc/doc_html/Properties.html
@@ -56,16 +56,34 @@
             <Font Size=-1>Tango::DEV_STRING</Font>
         </Td>
         <Td>
-            <Font Size=-1>The board type [MAO_xxxx - where <xxxx> is the ADlink board identifier - e.g. MAO_6208 - no
-                    default value]</Font>
+            <Font Size=-1>The board type [MAO_xxxx - where <xxxx> is the ADlink board identifier - e.g. MAO_6208 - no default value]</Font>
         </Td>
     </Tr>
 
-    </Table>
+    <Tr>
+        <Td><b><a href=#Dev_DefaultValues>EnableRamps </a></b></Td>
+        <Td>
+            <Font Size=-1>Tango::DEV_BOOLEAN</Font>
+        </Td>
+        <Td>
+            <Font Size=-1>Whether to enable or disable the ramp generation on the board output channels. If false, the speedX and initialX dynamic attributes will not be created for every channel, and the changes will happen instantly. [true/false - default value is true]</Font>
+        </Td>
+
+    </Tr>
 
+    <Tr>
+        <Td><b><a href=#Dev_DefaultValues>WriteMemorizedValuesAtInit </a></b></Td>
+        <Td>
+            <Font Size=-1>Tango::DEV_BOOLEAN</Font>
+        </Td>
+        <Td>
+            <Font Size=-1>Whether to write the memorized values to the board at the device initialization. [true/false - default value is false]</Font>
+        </Td>
+    </Tr>
+    </Table>
+    
     </Center>
 
-    <A name=Dev_DefaultValues><!---  ---></a>
     <Font Size=+1>Device Properties Default Values:</Font><Br>
     <Table Border=2 Cellpadding=2 CELLSPACING=2>
         <tr BGCOLOR="#CCCCFF" CLASS="TableHeadingColor">
@@ -80,6 +98,14 @@
             <Td>BoardType</Td>
             <td>No default value</td>
         </Tr>
+        <Tr>
+            <Td>EnableRamps</Td>
+            <td>true</td>
+        </Tr>
+        <Tr>
+            <Td>WriteMemorizedValuesAtInit</Td>
+            <td>false</td>
+        </Tr>
     </Table>
 
     <Br><Br><Br>
diff --git a/src/SingleShotAO.cpp b/src/SingleShotAO.cpp
index 96a72ba..56cb44d 100755
--- a/src/SingleShotAO.cpp
+++ b/src/SingleShotAO.cpp
@@ -582,6 +582,8 @@ void SingleShotAO::get_device_property()
 	Tango::DbData	dev_prop;
 	dev_prop.push_back(Tango::DbDatum("BoardNum"));
 	dev_prop.push_back(Tango::DbDatum("BoardType"));
+	dev_prop.push_back(Tango::DbDatum("EnableRamps"));
+	dev_prop.push_back(Tango::DbDatum("WriteMemorizedValuesAtInit"));
 
 	// Call database and extract values
 	//--------------------------------------------
@@ -592,6 +594,7 @@ void SingleShotAO::get_device_property()
 	SingleShotAOClass *ds_class = (static_cast<SingleShotAOClass *>(get_device_class()));
 	int	i = -1;
 
+	//- <BoardNum> -----------------------
 	// Try to initialize BoardNum from class property
 	cl_prop = ds_class->get_class_property(dev_prop[++i].name);
 	if (cl_prop.is_empty()==false)	cl_prop  >>  boardNum;
@@ -603,6 +606,7 @@ void SingleShotAO::get_device_property()
 	// And try to extract BoardNum value from database
 	if (dev_prop[i].is_empty()==false)	dev_prop[i]  >>  boardNum;
 
+	//- <BoardType> -----------------------
 	// Try to initialize BoardType from class property
 	cl_prop = ds_class->get_class_property(dev_prop[++i].name);
 	if (cl_prop.is_empty()==false)	cl_prop  >>  boardType;
@@ -614,6 +618,33 @@ void SingleShotAO::get_device_property()
 	// And try to extract BoardType value from database
 	if (dev_prop[i].is_empty()==false)	dev_prop[i]  >>  boardType;
 
+	//- <EnableRamps> -----------------------
+	//	Try to initialize EnableRamps from class property
+	cl_prop = ds_class->get_class_property(dev_prop[++i].name);
+	if (cl_prop.is_empty() == false) cl_prop >> enableRamps;
+	else
+	{
+		//	Try to initialize EnableRamps from default device value
+		def_prop = ds_class->get_default_device_property(dev_prop[i].name);
+		if (def_prop.is_empty() == false) def_prop >> enableRamps;
+	}
+	//	And try to extract EnableRamps value from database
+	if (dev_prop[i].is_empty() == false) dev_prop[i] >> enableRamps;
+
+	//- <WriteMemorizedValuesAtInit> -----------------------
+	//	Try to initialize WriteMemorizedValuesAtInit from class property
+	cl_prop = ds_class->get_class_property(dev_prop[++i].name);
+	if (cl_prop.is_empty() == false) cl_prop >> writeMemorizedValuesAtInit;
+	else
+	{
+		//	Try to initialize WriteMemorizedValuesAtInit from default device value
+		def_prop = ds_class->get_default_device_property(dev_prop[i].name);
+		if (def_prop.is_empty() == false) def_prop >> writeMemorizedValuesAtInit;
+	}
+	//	And try to extract WriteMemorizedValuesAtInit value from database
+	if (dev_prop[i].is_empty() == false) dev_prop[i] >> writeMemorizedValuesAtInit;
+
+
 	// Check critical properties being present
 	//--------------------------------------------
 
diff --git a/src/SingleShotAO.h b/src/SingleShotAO.h
index 7faa182..f77367e 100755
--- a/src/SingleShotAO.h
+++ b/src/SingleShotAO.h
@@ -110,6 +110,14 @@ public :
  *	The board type [MAO_xxxx - where <xxxx> is the ADlink board identifier - e.g. MAO_6208 - no default value]
  */
 	string	boardType;
+/**
+ *  Whether to enable or disable the ramp generation on the board output channels. If false, the speedX and initialX dynamic attributes will not be created for every channel, and the changes will happen instantly. [true/false - default value is true]
+ */
+	Tango::DevBoolean	enableRamps;
+/**
+ *  Whether to write the memorized values to the board at the device initialization. [true/false - default value is false]
+ */
+	Tango::DevBoolean	writeMemorizedValuesAtInit;
 //@}
 
 /**
diff --git a/src/SingleShotAOClass.cpp b/src/SingleShotAOClass.cpp
index e93187a..87816eb 100755
--- a/src/SingleShotAOClass.cpp
+++ b/src/SingleShotAOClass.cpp
@@ -369,10 +369,14 @@ void SingleShotAOClass::set_default_property()
 	string	prop_def;
 
 	vector<string>	vect_data;
+
 	//	Set Default Class Properties
+	// ...
+
 	//	Set Default Device Properties
+	//- <BoardNum> -----------------------
 	prop_name = "BoardNum";
-	prop_desc = "The the board identifier in the cPCI crate [valid range is 0...7 - no default value] .";
+	prop_desc = "The board identifier in the cPCI crate [valid range is 0...7 - no default value].";
 	prop_def  = "";
 	vect_data.clear();
 	if (prop_def.length()>0)
@@ -385,6 +389,7 @@ void SingleShotAOClass::set_default_property()
 	else
 		add_wiz_dev_prop(prop_name, prop_desc);
 
+	//- <BoardType> -----------------------
 	prop_name = "BoardType";
 	prop_desc = "The board type [MAO_xxxx - where <xxxx> is the ADlink board identifier - e.g. MAO_6208 - no default value]";
 	prop_def  = "";
@@ -399,7 +404,40 @@ void SingleShotAOClass::set_default_property()
 	else
 		add_wiz_dev_prop(prop_name, prop_desc);
 
+	//- <EnableRamps> -----------------------
+	prop_name = "EnableRamps";
+	prop_desc = "Whether to enable or disable the ramp generation on the board output channels. If false, the speedX and initialX dynamic attributes will not be created for every channel, and the changes will happen instantly. [true/false - default value is true]";
+	prop_def = "true";
+	vect_data.clear();
+	vect_data.push_back("true");
+	if (prop_def.length() > 0)
+	{
+		Tango::DbDatum data(prop_name);
+		data << vect_data;
+		dev_def_prop.push_back(data);
+		add_wiz_dev_prop(prop_name, prop_desc, prop_def);
+	}
+	else
+		add_wiz_dev_prop(prop_name, prop_desc);
+	
+	//- <WriteMemorizedValuesAt> -----------------------
+	prop_name = "WriteMemorizedValuesAtInit";
+	prop_desc = "Whether to write the memorized values to the board at the device initialization. [true/false - default value is false]";
+	prop_def = "false";
+	vect_data.clear();
+	vect_data.push_back("false");
+	if (prop_def.length() > 0)
+	{
+		Tango::DbDatum data(prop_name);
+		data << vect_data;
+		dev_def_prop.push_back(data);
+		add_wiz_dev_prop(prop_name, prop_desc, prop_def);
+	}
+	else
+		add_wiz_dev_prop(prop_name, prop_desc);
 }
+
+
 //+----------------------------------------------------------------------------
 //
 // method : 		SingleShotAOClass::write_class_property
@@ -431,107 +469,6 @@ void SingleShotAOClass::write_class_property()
 	str_desc.push_back("ADLink boards support for single shot AO operations [PCI-6208 and compatible boards]");
 	description << str_desc;
 	data.push_back(description);
-		
-	//	put cvs or svn location
-	string	filename(classname);
-	filename += "Class.cpp";
-	
-	// Create a string with the class ID to
-	// get the string into the binary
-	string	class_id(ClassId);
-	
-	// check for cvs information
-	string	src_path(CvsPath);
-	start = src_path.find("/");
-	if (start!=string::npos)
-	{
-		end   = src_path.find(filename);
-		if (end>start)
-		{
-			string	strloc = src_path.substr(start, end-start);
-			//	Check if specific repository
-			start = strloc.find("/cvsroot/");
-			if (start!=string::npos && start>0)
-			{
-				string	repository = strloc.substr(0, start);
-				if (repository.find("/segfs/")!=string::npos)
-					strloc = "ESRF:" + strloc.substr(start, strloc.length()-start);
-			}
-			Tango::DbDatum	cvs_loc("cvs_location");
-			cvs_loc << strloc;
-			data.push_back(cvs_loc);
-		}
-	}
-	// check for svn information
-	else
-	{
-		string	src_path(SvnPath);
-		start = src_path.find("://");
-		if (start!=string::npos)
-		{
-			end = src_path.find(filename);
-			if (end>start)
-			{
-				header = "$HeadURL: ";
-				start = header.length();
-				string	strloc = src_path.substr(start, (end-start));
-				
-				Tango::DbDatum	svn_loc("svn_location");
-				svn_loc << strloc;
-				data.push_back(svn_loc);
-			}
-		}
-	}
-
-	//	Get CVS or SVN revision tag
-	
-	// CVS tag
-	string	tagname(TagName);
-	header = "$Name: ";
-	start = header.length();
-	string	endstr(" $");
-	
-	end   = tagname.find(endstr);
-	if (end!=string::npos && end>start)
-	{
-		string	strtag = tagname.substr(start, end-start);
-		Tango::DbDatum	cvs_tag("cvs_tag");
-		cvs_tag << strtag;
-		data.push_back(cvs_tag);
-	}
-	
-	// SVN tag
-	string	svnpath(SvnPath);
-	header = "$HeadURL: ";
-	start = header.length();
-	
-	end   = svnpath.find(endstr);
-	if (end!=string::npos && end>start)
-	{
-		string	strloc = svnpath.substr(start, end-start);
-		
-		string tagstr ("/tags/");
-		start = strloc.find(tagstr);
-		if ( start!=string::npos )
-		{
-			start = start + tagstr.length();
-			end   = strloc.find(filename);
-			string	strtag = strloc.substr(start, end-start-1);
-			
-			Tango::DbDatum	svn_tag("svn_tag");
-			svn_tag << strtag;
-			data.push_back(svn_tag);
-		}
-	}
-
-	//	Get URL location
-	string	httpServ(HttpServer);
-	if (httpServ.length()>0)
-	{
-		Tango::DbDatum	db_doc_url("doc_url");
-		db_doc_url << httpServ;
-		data.push_back(db_doc_url);
-	}
 
 	//  Put inheritance
 	Tango::DbDatum	inher_datum("InheritedFrom");
@@ -540,8 +477,7 @@ void SingleShotAOClass::write_class_property()
 	inher_datum << inheritance;
 	data.push_back(inher_datum);
 
-	//	Call database and and values
-	//--------------------------------------------
+	// Call database and add values
 	get_db_class()->put_property(data);
 }
 
-- 
GitLab


From a1e6b1c7fff680f637fdbd2f3031cc36943c6520 Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Fri, 21 Feb 2025 17:01:31 +0100
Subject: [PATCH 04/13] feat: write memorized value at init if
 writeMemorizedValuesAtInit is true (instead of just set_channel)

---
 src/SingleShotAO.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/SingleShotAO.cpp b/src/SingleShotAO.cpp
index 56cb44d..dd92d5a 100755
--- a/src/SingleShotAO.cpp
+++ b/src/SingleShotAO.cpp
@@ -537,8 +537,10 @@ void SingleShotAO::init_device()
 		// Get and set memorized values for speed, initial and channel
 		applyMemorizedAttr(kSPEED, &SingleShotAOManager::set_speed);
 		applyMemorizedAttr(kINITIAL, &SingleShotAOManager::set_initial); 
-		applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::set_channel);
-		// TODO: add a property to call write_channel instead of set_channel (false by default)
+		if (writeMemorizedValuesAtInit)
+			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::write_channel);
+		else
+			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::set_channel);
     }
 
 	//- GO for task
-- 
GitLab


From 2fbc7f47a8820d8af04f917b601c49f85bc442ee Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Fri, 21 Feb 2025 17:26:14 +0100
Subject: [PATCH 05/13] feat: deactive ramp functionnality if enableRamps is
 false (don't create speed and initial attributes at init, and set changes
 instantly)

---
 src/SingleShotAO.cpp        | 23 ++++++++++++++++-------
 src/SingleShotAOManager.cpp | 18 ++++++++++++++++--
 src/SingleShotAOManager.h   |  7 +++++--
 3 files changed, 37 insertions(+), 11 deletions(-)

diff --git a/src/SingleShotAO.cpp b/src/SingleShotAO.cpp
index dd92d5a..5bce000 100755
--- a/src/SingleShotAO.cpp
+++ b/src/SingleShotAO.cpp
@@ -361,7 +361,7 @@ void SingleShotAO::init_device()
 	//--------------------------------------------
 	try
 	{
-		m_manager->init(m_ssao, m_nb_chan, m_frequency);
+		m_manager->init(m_ssao, m_nb_chan, m_frequency, enableRamps);
 	}
 	catch (Tango::DevFailed & df)
 	{
@@ -443,6 +443,11 @@ void SingleShotAO::init_device()
 
 		l_dynAttrList.push_back(dai_channel);
 
+		// if enableRamps if false, skip speed and initial attributes
+		if (!enableRamps) {
+			DEBUG_STREAM << "Ramps are disabled. Skipping speed and initial attributes for channel " << l_cpt << std::endl;
+			continue;
+		}
 
 		// ╔═══════════════╗
 		// ║  Speed value  ║
@@ -535,12 +540,16 @@ void SingleShotAO::init_device()
 		};
 
 		// Get and set memorized values for speed, initial and channel
-		applyMemorizedAttr(kSPEED, &SingleShotAOManager::set_speed);
-		applyMemorizedAttr(kINITIAL, &SingleShotAOManager::set_initial); 
-		if (writeMemorizedValuesAtInit)
-			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::write_channel);
-		else
-			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::set_channel);
+		if (enableRamps) {
+			applyMemorizedAttr(kSPEED, &SingleShotAOManager::set_speed);
+			applyMemorizedAttr(kINITIAL, &SingleShotAOManager::set_initial); 
+		}
+
+		if (writeMemorizedValuesAtInit) {
+			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::write_channel); // write memorized value to board output
+		} else {
+			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::set_channel); // only apply memorized value to the device
+		}
     }
 
 	//- GO for task
diff --git a/src/SingleShotAOManager.cpp b/src/SingleShotAOManager.cpp
index e0519ec..0eb1333 100755
--- a/src/SingleShotAOManager.cpp
+++ b/src/SingleShotAOManager.cpp
@@ -128,7 +128,7 @@ void SingleShotAOManager::write_frequency(double p_frequency)
 // ============================================================================
 // SingleShotAOManager::init ()
 // ============================================================================ 
-void SingleShotAOManager::init(asl::SingleShotAO * p_ssao, unsigned short p_nb_chan, double p_frequency)
+void SingleShotAOManager::init(asl::SingleShotAO * p_ssao, unsigned short p_nb_chan, double p_frequency, bool p_enable_ramps)
 {
 	m_ssao = p_ssao;
 	CHECK_SSAO();
@@ -145,6 +145,8 @@ void SingleShotAOManager::init(asl::SingleShotAO * p_ssao, unsigned short p_nb_c
 		enable_periodic_msg(true);
 	}
 
+	m_enable_ramps = p_enable_ramps;
+
 	// initialize channel indexes (-1 means no ramp in progress)
 	// and ramp states
 	for (unsigned int l_cpt = 0; l_cpt < m_nb_chan; l_cpt++)
@@ -288,7 +290,7 @@ void SingleShotAOManager::write_channel(ChannelId_t p_chIdx, double p_val)
 	DEBUG_STREAM << "write_channel " << p_chIdx << " : " << p_val << endl;
 
 	// if the speed is 0, write the value directly and skip ramp
-	if (m_speeds[p_chIdx] == 0.0)
+	if (m_speeds[p_chIdx] == 0.0 || !m_enable_ramps)
 	{
 		try 
 		{
@@ -409,6 +411,12 @@ void SingleShotAOManager::set_initial(ChannelId_t p_chIdx, Intial_t p_initial)
 			"could not write initial : a ramp is still in progress on this channel",
 			"SingleShotAOManager::set_initial");
 	}
+	else if (!m_enable_ramps)
+	{
+		THROW_DEVFAILED("DEVICE_FAILURE",
+			"could not write initial : ramps are disabled",
+			"SingleShotAOManager::set_initial");
+	}
 	else
 	{
 		m_initials[p_chIdx] = p_initial;
@@ -434,6 +442,12 @@ void SingleShotAOManager::set_speed(ChannelId_t p_chIdx, Intial_t p_speed)
 			"could not write speed : a ramp is still in progress on this channel",
 			"SingleShotAOManager::set_speed");
 	}
+	else if (!m_enable_ramps)
+	{
+		THROW_DEVFAILED("DEVICE_FAILURE",
+			"could not write speed : ramps are disabled",
+			"SingleShotAOManager::set_speed");
+	}
 	else
 	{
 		m_speeds[p_chIdx] = p_speed;
diff --git a/src/SingleShotAOManager.h b/src/SingleShotAOManager.h
index a021036..5351b59 100755
--- a/src/SingleShotAOManager.h
+++ b/src/SingleShotAOManager.h
@@ -48,7 +48,7 @@ public:
   std::string get_status ();
 
   //- init
-  void init(asl::SingleShotAO * p_ssao, unsigned short p_nb_chan, double p_frequency);
+  void init(asl::SingleShotAO *p_ssao, unsigned short p_nb_chan, double p_frequency, bool p_enable_ramps);
 
   //- get current channel value
   double get_channel(ChannelId_t p_chIdx);
@@ -100,7 +100,10 @@ private:
 
   //- frequency
   double m_frequency;
-  
+
+  //-enable ramps
+  bool m_enable_ramps;
+
   //- initial buffer for all channels
   std::map<ChannelId_t, Intial_t> m_initials;
 
-- 
GitLab


From 9547d4baa7101eac7b0ea23562f1be24fe3ff15c Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Fri, 21 Feb 2025 17:27:51 +0100
Subject: [PATCH 06/13] bump: update version to 2.2.0

---
 conanfile.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/conanfile.py b/conanfile.py
index 24ed606..27e4801 100644
--- a/conanfile.py
+++ b/conanfile.py
@@ -3,7 +3,7 @@ from conan import ConanFile
 class SingleShotAORecipe(ConanFile):
     name = "singleshotao"
     executable = "ds_SingleShotAO"
-    version = "2.1.0"
+    version = "2.2.0"
     package_type = "application"
     user = "soleil"
     python_requires = "base/[>=1.0]@soleil/stable"
-- 
GitLab


From acaf27f501d07516df4cb72b913951affc84d7e3 Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Fri, 21 Feb 2025 17:35:14 +0100
Subject: [PATCH 07/13] feat: add logging for parsed device properties in
 get_device_property method

---
 src/SingleShotAO.cpp | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/src/SingleShotAO.cpp b/src/SingleShotAO.cpp
index 5bce000..b5ed3f3 100755
--- a/src/SingleShotAO.cpp
+++ b/src/SingleShotAO.cpp
@@ -601,6 +601,7 @@ void SingleShotAO::get_device_property()
 	if (Tango::Util::instance()->_UseDb==true) {
 		get_db_device()->get_property(dev_prop);
 	}
+
 	Tango::DbDatum def_prop, cl_prop;
 	SingleShotAOClass *ds_class = (static_cast<SingleShotAOClass *>(get_device_class()));
 	int	i = -1;
@@ -616,6 +617,7 @@ void SingleShotAO::get_device_property()
 	}
 	// And try to extract BoardNum value from database
 	if (dev_prop[i].is_empty()==false)	dev_prop[i]  >>  boardNum;
+	INFO_STREAM << "Raw BoardNum parsed: " << boardNum << endl;
 
 	//- <BoardType> -----------------------
 	// Try to initialize BoardType from class property
@@ -628,39 +630,37 @@ void SingleShotAO::get_device_property()
 	}
 	// And try to extract BoardType value from database
 	if (dev_prop[i].is_empty()==false)	dev_prop[i]  >>  boardType;
+	INFO_STREAM << "Raw BoardType parsed: " << boardType << endl;
 
 	//- <EnableRamps> -----------------------
 	//	Try to initialize EnableRamps from class property
 	cl_prop = ds_class->get_class_property(dev_prop[++i].name);
 	if (cl_prop.is_empty() == false) cl_prop >> enableRamps;
-	else
-	{
+	else {
 		//	Try to initialize EnableRamps from default device value
 		def_prop = ds_class->get_default_device_property(dev_prop[i].name);
 		if (def_prop.is_empty() == false) def_prop >> enableRamps;
 	}
 	//	And try to extract EnableRamps value from database
 	if (dev_prop[i].is_empty() == false) dev_prop[i] >> enableRamps;
+	INFO_STREAM << "EnableRamps parsed: " << (enableRamps ? "true" : "false") << endl;
 
 	//- <WriteMemorizedValuesAtInit> -----------------------
 	//	Try to initialize WriteMemorizedValuesAtInit from class property
 	cl_prop = ds_class->get_class_property(dev_prop[++i].name);
 	if (cl_prop.is_empty() == false) cl_prop >> writeMemorizedValuesAtInit;
-	else
-	{
+	else {
 		//	Try to initialize WriteMemorizedValuesAtInit from default device value
 		def_prop = ds_class->get_default_device_property(dev_prop[i].name);
 		if (def_prop.is_empty() == false) def_prop >> writeMemorizedValuesAtInit;
 	}
 	//	And try to extract WriteMemorizedValuesAtInit value from database
 	if (dev_prop[i].is_empty() == false) dev_prop[i] >> writeMemorizedValuesAtInit;
-
+	INFO_STREAM << "WriteMemorizedValuesAtInit parsed: " << (writeMemorizedValuesAtInit ? "true" : "false") << endl;
 
 	// Check critical properties being present
 	//--------------------------------------------
-
 	critical_properties_missing = false;
-
 	if (dev_prop[0].is_empty())
 	{
 		ERROR_STREAM << "Required device property <BoardNum> is missing" << endl;
@@ -671,20 +671,20 @@ void SingleShotAO::get_device_property()
 		ERROR_STREAM << "Required device property <BoardType> is missing" << endl;
 		critical_properties_missing = true;
 	}
-
 	if (critical_properties_missing) {
 		return;
 	}
 
 	// Check critical properties being valid
 	//--------------------------------------------
-
 	//- <BoardNum> -----------------------
 	if (boardNum < 0 || boardNum > 7)
 	{
 		boardNum = 0;
 		ERROR_STREAM << "device property <BoardNum> is invalid. Valid range is [0..7]" << endl;
 		critical_properties_missing = true;
+	} else {
+		INFO_STREAM << "BoardNum resolved to " << boardNum << endl;
 	}
 
 	//- <BoardType> -----------------------
@@ -692,11 +692,13 @@ void SingleShotAO::get_device_property()
 	{
 		boardType = k6208_BOARD_TYPE;
 		boardTypeId = adl::PCI6208;
+		INFO_STREAM << "BoardType resolved to PCI6208" << endl;
 	}
 	else if (boardType == "MAO_6216")
 	{
 		boardType = k6216_BOARD_TYPE;
 		boardTypeId = adl::PCI6216;
+		INFO_STREAM << "BoardType resolved to PCI6216" << endl;
 	}
 	else
 	{
-- 
GitLab


From 64fc34e7f901f62dd54aa092b5354ffdbd13aa66 Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Mon, 17 Mar 2025 18:20:47 +0100
Subject: [PATCH 08/13] feat: rename WriteMemorizedValuesAtInit to
 OutputMemorizedChannelsAtInit and update documentation

---
 doc/doc_html/Properties.html |  6 +++---
 src/SingleShotAO.cpp         | 20 ++++++++++----------
 src/SingleShotAO.h           |  2 +-
 src/SingleShotAOClass.cpp    |  6 +++---
 4 files changed, 17 insertions(+), 17 deletions(-)

diff --git a/doc/doc_html/Properties.html b/doc/doc_html/Properties.html
index b0ca154..95f05a3 100755
--- a/doc/doc_html/Properties.html
+++ b/doc/doc_html/Properties.html
@@ -72,12 +72,12 @@
     </Tr>
 
     <Tr>
-        <Td><b><a href=#Dev_DefaultValues>WriteMemorizedValuesAtInit </a></b></Td>
+        <Td><b><a href=#Dev_DefaultValues>OutputMemorizedChannelsAtInit </a></b></Td>
         <Td>
             <Font Size=-1>Tango::DEV_BOOLEAN</Font>
         </Td>
         <Td>
-            <Font Size=-1>Whether to write the memorized values to the board at the device initialization. [true/false - default value is false]</Font>
+            <Font Size=-1>Whether to send the memorized values for the channels to the board at the device initialization (the device attribute will show the last memorized value regardless) [true/false - default value is false]</Font>
         </Td>
     </Tr>
     </Table>
@@ -103,7 +103,7 @@
             <td>true</td>
         </Tr>
         <Tr>
-            <Td>WriteMemorizedValuesAtInit</Td>
+            <Td>OutputMemorizedChannelsAtInit</Td>
             <td>false</td>
         </Tr>
     </Table>
diff --git a/src/SingleShotAO.cpp b/src/SingleShotAO.cpp
index b5ed3f3..748d7f1 100755
--- a/src/SingleShotAO.cpp
+++ b/src/SingleShotAO.cpp
@@ -545,7 +545,7 @@ void SingleShotAO::init_device()
 			applyMemorizedAttr(kINITIAL, &SingleShotAOManager::set_initial); 
 		}
 
-		if (writeMemorizedValuesAtInit) {
+		if (outputMemorizedChannelsAtInit) {
 			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::write_channel); // write memorized value to board output
 		} else {
 			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::set_channel); // only apply memorized value to the device
@@ -594,7 +594,7 @@ void SingleShotAO::get_device_property()
 	dev_prop.push_back(Tango::DbDatum("BoardNum"));
 	dev_prop.push_back(Tango::DbDatum("BoardType"));
 	dev_prop.push_back(Tango::DbDatum("EnableRamps"));
-	dev_prop.push_back(Tango::DbDatum("WriteMemorizedValuesAtInit"));
+	dev_prop.push_back(Tango::DbDatum("OutputMemorizedChannelsAtInit"));
 
 	// Call database and extract values
 	//--------------------------------------------
@@ -645,18 +645,18 @@ void SingleShotAO::get_device_property()
 	if (dev_prop[i].is_empty() == false) dev_prop[i] >> enableRamps;
 	INFO_STREAM << "EnableRamps parsed: " << (enableRamps ? "true" : "false") << endl;
 
-	//- <WriteMemorizedValuesAtInit> -----------------------
-	//	Try to initialize WriteMemorizedValuesAtInit from class property
+	//- <OutputMemorizedChannelsAtInit> -----------------------
+	//	Try to initialize OutputMemorizedChannelsAtInit from class property
 	cl_prop = ds_class->get_class_property(dev_prop[++i].name);
-	if (cl_prop.is_empty() == false) cl_prop >> writeMemorizedValuesAtInit;
+	if (cl_prop.is_empty() == false) cl_prop >> outputMemorizedChannelsAtInit;
 	else {
-		//	Try to initialize WriteMemorizedValuesAtInit from default device value
+		//	Try to initialize OutputMemorizedChannelsAtInit from default device value
 		def_prop = ds_class->get_default_device_property(dev_prop[i].name);
-		if (def_prop.is_empty() == false) def_prop >> writeMemorizedValuesAtInit;
+		if (def_prop.is_empty() == false) def_prop >> outputMemorizedChannelsAtInit;
 	}
-	//	And try to extract WriteMemorizedValuesAtInit value from database
-	if (dev_prop[i].is_empty() == false) dev_prop[i] >> writeMemorizedValuesAtInit;
-	INFO_STREAM << "WriteMemorizedValuesAtInit parsed: " << (writeMemorizedValuesAtInit ? "true" : "false") << endl;
+	//	And try to extract OutputMemorizedChannelsAtInit value from database
+	if (dev_prop[i].is_empty() == false) dev_prop[i] >> outputMemorizedChannelsAtInit;
+	INFO_STREAM << "OutputMemorizedChannelsAtInit parsed: " << (outputMemorizedChannelsAtInit ? "true" : "false") << endl;
 
 	// Check critical properties being present
 	//--------------------------------------------
diff --git a/src/SingleShotAO.h b/src/SingleShotAO.h
index f77367e..7cee441 100755
--- a/src/SingleShotAO.h
+++ b/src/SingleShotAO.h
@@ -117,7 +117,7 @@ public :
 /**
  *  Whether to write the memorized values to the board at the device initialization. [true/false - default value is false]
  */
-	Tango::DevBoolean	writeMemorizedValuesAtInit;
+	Tango::DevBoolean	outputMemorizedChannelsAtInit;
 //@}
 
 /**
diff --git a/src/SingleShotAOClass.cpp b/src/SingleShotAOClass.cpp
index 87816eb..29a1ef5 100755
--- a/src/SingleShotAOClass.cpp
+++ b/src/SingleShotAOClass.cpp
@@ -420,9 +420,9 @@ void SingleShotAOClass::set_default_property()
 	else
 		add_wiz_dev_prop(prop_name, prop_desc);
 	
-	//- <WriteMemorizedValuesAt> -----------------------
-	prop_name = "WriteMemorizedValuesAtInit";
-	prop_desc = "Whether to write the memorized values to the board at the device initialization. [true/false - default value is false]";
+	//- <OutputMemorizedChannelsAtInit> -----------------------
+	prop_name = "OutputMemorizedChannelsAtInit";
+	prop_desc = "Whether to send the memorized values for the channels to the board at the device initialization (the device attribute will show the last memorized value regardless) [true/false - default value is false]";
 	prop_def = "false";
 	vect_data.clear();
 	vect_data.push_back("false");
-- 
GitLab


From 2c69e3b7c591c5cc9563e950096954a8b0a35a6d Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Mon, 17 Mar 2025 18:21:51 +0100
Subject: [PATCH 09/13] feat: add ASL dependency in info attribute (hardcoded
 version because I could not find a variable for the version)

---
 src/SingleShotAO.cpp | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/src/SingleShotAO.cpp b/src/SingleShotAO.cpp
index 748d7f1..3257a7b 100755
--- a/src/SingleShotAO.cpp
+++ b/src/SingleShotAO.cpp
@@ -216,8 +216,7 @@ void SingleShotAO::init_device()
 		
 		INFO_STREAM << "Create the DeviceInfo in order to manage info on versions." << endl;
 		yat4tango::DeviceInfo::initialize(this, YAT_XSTR(PROJECT_NAME), YAT_XSTR(PROJECT_VERSION));
-		// yat4tango::DeviceInfo::add_dependency(this, "Dependency Name", YAT_XSTR(dependency_name_PROJECT_VERSION));
-		// TODO: Add dependencies
+		yat4tango::DeviceInfo::add_dependency(this, "ASL", ">=1.0");
 	}
 	catch (Tango::DevFailed &df)
 	{
-- 
GitLab


From 646f4650d6e3e4a4818b6be371dec2521b8cbfe5 Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Mon, 17 Mar 2025 18:22:45 +0100
Subject: [PATCH 10/13] feat: added logging and consolidate dynamic attribute
 management

---
 src/SingleShotAO.cpp | 66 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 65 insertions(+), 1 deletion(-)

diff --git a/src/SingleShotAO.cpp b/src/SingleShotAO.cpp
index 3257a7b..cb204a0 100755
--- a/src/SingleShotAO.cpp
+++ b/src/SingleShotAO.cpp
@@ -318,6 +318,7 @@ void SingleShotAO::init_device()
 		m_nb_chan = 16;
 	}
 
+	INFO_STREAM << "Board has " << m_nb_chan << " channels" << endl;
 
 	// construct the AO manager
 	//--------------------------------------------
@@ -377,6 +378,20 @@ void SingleShotAO::init_device()
 		return;
 	}
 
+	// Remove existing dynamic attributes to avoid duplicates or other issues
+	// --------------------------------------------
+	if (m_dyn_attr_manager) {
+		try {
+			m_dyn_attr_manager->remove_attributes();
+			delete m_dyn_attr_manager;
+			m_dyn_attr_manager = NULL;
+			DEBUG_STREAM << "Existing dynamic attributes manager cleaned up" << endl;
+		} 
+		catch (...) {
+			ERROR_STREAM << "Error cleaning up existing dynamic attributes manager, continuing..." << endl;
+			// Continue anyway - we'll create a new one
+		}
+	}
 
 	// Create dynamic attributes
 	//--------------------------------------------
@@ -404,6 +419,17 @@ void SingleShotAO::init_device()
 
 	// add dynamic attributes: channel, speed & initial for each channel
   	std::vector<yat4tango::DynamicAttributeInfo> l_dynAttrList;
+	
+	// Log how many attributes we're going to create
+	INFO_STREAM << "Creating dynamic attributes for " << m_nb_chan << " channels.";
+	INFO_STREAM << "Total attributes: " << m_nb_chan * (enableRamps ? 3 : 1) << endl;
+
+	// if enableRamps if false, skip speed and initial attributes
+	if (!enableRamps)
+	{
+		INFO_STREAM << "Ramps are disabled. Skipping speed and initial attributes" << std::endl;
+	}
+
 	for (unsigned int l_cpt = 0; l_cpt < m_nb_chan; l_cpt++)
 	{
 		yat::OSStream oss;
@@ -515,8 +541,45 @@ void SingleShotAO::init_device()
 		
     	l_dynAttrList.push_back(dai_initial);
 	}
-  	m_dyn_attr_manager->add_attributes(l_dynAttrList);
+	
+	// Log the size of our attribute list before adding to manager
+	INFO_STREAM << "Prepared " << l_dynAttrList.size() << " dynamic attributes for creation" << endl;
+	
+	// Add all attributes
+	try {
+  		m_dyn_attr_manager->add_attributes(l_dynAttrList);
+		INFO_STREAM << "Successfully added all dynamic attributes" << endl;
+	}
+	catch (Tango::DevFailed &df) {
+		ERROR_STREAM << "Failed to add dynamic attributes: " << df << endl;
+		m_currStatus = "Failed to add dynamic attributes. See log for details";
+		m_state = Tango::FAULT;
+		return;
+	}
+	catch (...) {
+		ERROR_STREAM << "Unknown exception when adding dynamic attributes" << endl;
+		m_currStatus = "Failed to add dynamic attributes. Unknown error";
+		m_state = Tango::FAULT;
+		return;
+	}
 
+	// Initialize maps in manager class for all channels
+	try {
+		for (unsigned int l_cpt = 0; l_cpt < m_nb_chan; l_cpt++) {
+			// Initialize with default values
+			m_manager->set_channel(l_cpt, 0.0);
+			if (enableRamps) {
+				m_manager->set_speed(l_cpt, 0.0);
+				m_manager->set_initial(l_cpt, 0.0);
+			}
+		}
+	}
+	catch (Tango::DevFailed &df) {
+		ERROR_STREAM << "Failed to initialize channel maps: " << df << endl;
+		m_currStatus = "Failed to initialize channel maps. See log for details";
+		m_state = Tango::FAULT;
+		return;
+	}
 
 	// Get memorized values from database
 	for (unsigned int l_cpt = 0; l_cpt < m_nb_chan; l_cpt++)
@@ -531,6 +594,7 @@ void SingleShotAO::init_device()
 			try {
 				std::string attrName = attrPrefix + oss.str();
 				double val = yat4tango::PropertyHelper::get_memorized_attribute<double>(this, attrName);
+				DEBUG_STREAM << "Found memorized value for " << attrName << ": " << val << endl;
 				(m_manager->*setter)(l_cpt, val);
 			}
 			catch (...) {
-- 
GitLab


From 5551cf17caf5eefd2a2e6aa40bbe3f2b8d645dde Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Mon, 24 Mar 2025 17:50:41 +0100
Subject: [PATCH 11/13] refactor: split write_channel method into
 write_channel_direct and start_channel_ramp

---
 src/SingleShotAO.cpp        |   5 +-
 src/SingleShotAOManager.cpp | 121 ++++++++++++++++++++----------------
 src/SingleShotAOManager.h   |   6 ++
 3 files changed, 79 insertions(+), 53 deletions(-)

diff --git a/src/SingleShotAO.cpp b/src/SingleShotAO.cpp
index cb204a0..7bcea21 100755
--- a/src/SingleShotAO.cpp
+++ b/src/SingleShotAO.cpp
@@ -609,7 +609,7 @@ void SingleShotAO::init_device()
 		}
 
 		if (outputMemorizedChannelsAtInit) {
-			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::write_channel); // write memorized value to board output
+			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::write_channel_direct); // write memorized value to board output (directly, without ramp)
 		} else {
 			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::set_channel); // only apply memorized value to the device
 		}
@@ -978,8 +978,11 @@ void SingleShotAO::write_channel(yat4tango::DynamicAttributeWriteCallbackData &
 			"could not write channel [unknown error]",
 			"SingleShotAO::write_channel");
 	}
+	DEBUG_STREAM << "SingleShotAO::write_channel(): channel " << l_idx << " value set to " << l_val << endl;
 
 	yat4tango::PropertyHelper::set_memorized_attribute(this, l_attr_name, l_val);
+	yat4tango::PropertyHelper::set_memorized_attribute(this, kINITIAL + std::to_string(l_idx), l_val);
+	DEBUG_STREAM << "SingleShotAO::write_channel(): memorized attribute " << l_attr_name << " set to " << l_val << endl;
 }
 
 
diff --git a/src/SingleShotAOManager.cpp b/src/SingleShotAOManager.cpp
index 0eb1333..e988c22 100755
--- a/src/SingleShotAOManager.cpp
+++ b/src/SingleShotAOManager.cpp
@@ -250,6 +250,7 @@ void SingleShotAOManager::periodic_job_i()
 		m_currentIndex[l_cpt] += 1;
 		if (m_currentIndex[l_cpt] == m_ramps[l_cpt].capacity())
 		{
+			DEBUG_STREAM << "Ramp finished for channel" << l_cpt << endl;
 			m_currentIndex[l_cpt] = -1;
 			m_initials[l_cpt] = m_channels[l_cpt];
 			m_ramps[l_cpt].clear();
@@ -283,67 +284,43 @@ void SingleShotAOManager::set_channel(ChannelId_t p_chIdx, double p_val)
 }
 
 // ============================================================================
-// SingleShotAOManager::write_channel ()
+// SingleShotAOManager::write_channel_direct ()
 // ============================================================================ 
-void SingleShotAOManager::write_channel(ChannelId_t p_chIdx, double p_val)
+void SingleShotAOManager::write_channel_direct(ChannelId_t p_chIdx, double p_val)
 {
-	DEBUG_STREAM << "write_channel " << p_chIdx << " : " << p_val << endl;
-
-	// if the speed is 0, write the value directly and skip ramp
-	if (m_speeds[p_chIdx] == 0.0 || !m_enable_ramps)
+	try 
 	{
-		try 
-		{
-			CHECK_SSAO();
-			m_ssao->write_scaled_channel((adl::ChanId)p_chIdx, p_val);
-			m_channels[p_chIdx] = p_val;
-			m_initials[p_chIdx] = p_val;
-			DEBUG_STREAM << "Speed is 0, writing directly the value" << std::endl;
-		}
-		catch (const asl::DAQException &de)
-		{
-			Tango::DevFailed df = daq_to_tango_exception(de);
-			ERROR_STREAM << df << std::endl;
-			m_state = Tango::FAULT;
-			RETHROW_DEVFAILED(df,
-				"DRIVER_FAILURE",
-				"could not write channel [caught asl::DAQException]",
-				"SingleShotAOManager::write_channel");
-		}
-		catch (...)
-		{
-			ERROR_STREAM << "SingleShotAOManager::write_channel::unknown exception caught" << std::endl;
-			m_state = Tango::FAULT;
-			THROW_DEVFAILED("DRIVER_FAILURE",
-				"could not write channel [unknown error]",
-				"SingleShotAOManager::write_channel");
-		}
-		return;
+		CHECK_SSAO();
+		m_ssao->write_scaled_channel((adl::ChanId)p_chIdx, p_val);
+		m_channels[p_chIdx] = p_val;
+		m_initials[p_chIdx] = p_val;
+		DEBUG_STREAM << "Writing directly the value" << std::endl;
 	}
-
-	// if a ramp is running, error
-	if (m_isRunning[p_chIdx])
+	catch (const asl::DAQException &de)
 	{
-		THROW_DEVFAILED("DEVICE_FAILURE",
-			"could not write channel : a ramp is still in progress on this channel",
-			"SingleShotAOManager::write_channel");
+		Tango::DevFailed df = daq_to_tango_exception(de);
+		ERROR_STREAM << df << std::endl;
+		m_state = Tango::FAULT;
+		RETHROW_DEVFAILED(df,
+			"DRIVER_FAILURE",
+			"could not write channel [caught asl::DAQException]",
+			"SingleShotAOManager::write_channel_direct");
 	}
-
-	// if frequency = 0, error
-	if (m_frequency == 0)
+	catch (...)
 	{
+		ERROR_STREAM << "SingleShotAOManager::write_channel_direct::unknown exception caught" << std::endl;
+		m_state = Tango::FAULT;
 		THROW_DEVFAILED("DRIVER_FAILURE",
-			"could not set a ramp on this channel. The frequency is 0",
-			"SingleShotAOManager::write_channel");
-	}
-
-	// if initial = channel, skip
-	if (m_initials[p_chIdx] == p_val)
-	{
-		DEBUG_STREAM << "Initial value is the same as the given value, skipping" << endl;
-		return;
+			"could not write channel [unknown error]",
+			"SingleShotAOManager::write_channel_direct");
 	}
+}
 
+// ============================================================================
+// SingleShotAOManager::start_channel_ramp ()
+// ============================================================================ 
+void SingleShotAOManager::start_channel_ramp(ChannelId_t p_chIdx, double p_val)
+{
 	// ramp determination
 	double l_delta = p_val - m_initials[p_chIdx];
 	bool isDown = false;
@@ -389,7 +366,47 @@ void SingleShotAOManager::write_channel(ChannelId_t p_chIdx, double p_val)
 	m_ramps[p_chIdx].force_length(0);
 	m_currentIndex[p_chIdx] = 0;
 	m_ramps[p_chIdx] = l_buffer;
-	//m_channels[p_chIdx] = m_ramps[p_chIdx][0]; -- soso on ne met rien ici => à l'application
+}
+
+// ============================================================================
+// SingleShotAOManager::write_channel ()
+// ============================================================================ 
+void SingleShotAOManager::write_channel(ChannelId_t p_chIdx, double p_val)
+{
+	DEBUG_STREAM << "write_channel " << p_chIdx << " : " << p_val << endl;
+
+	// if the speed is 0, write the value directly and skip ramp
+	if (m_speeds[p_chIdx] == 0.0 || !m_enable_ramps)
+	{
+		write_channel_direct(p_chIdx, p_val);
+		return;
+	}
+
+	// if a ramp is running, error
+	if (m_isRunning[p_chIdx])
+	{
+		THROW_DEVFAILED("DEVICE_FAILURE",
+			"could not write channel : a ramp is still in progress on this channel",
+			"SingleShotAOManager::write_channel");
+	}
+
+	// if frequency = 0, error
+	if (m_frequency == 0)
+	{
+		THROW_DEVFAILED("DRIVER_FAILURE",
+			"could not set a ramp on this channel. The frequency is 0",
+			"SingleShotAOManager::write_channel");
+	}
+
+	// if initial = channel, skip
+	if (m_initials[p_chIdx] == p_val)
+	{
+		DEBUG_STREAM << "Initial value is the same as the given value, skipping" << endl;
+		return;
+	}
+
+	// Create and start a ramp
+	start_channel_ramp(p_chIdx, p_val);
 }
 
 // ============================================================================
diff --git a/src/SingleShotAOManager.h b/src/SingleShotAOManager.h
index 5351b59..25c002e 100755
--- a/src/SingleShotAOManager.h
+++ b/src/SingleShotAOManager.h
@@ -59,6 +59,12 @@ public:
   //- write channel
   void write_channel(ChannelId_t p_chIdx, double p_val);
 
+  //- write a channel directly (without ramp)
+  void write_channel_direct(ChannelId_t p_chIdx, double p_val);
+
+  //- create and start a ramp for channel
+  void start_channel_ramp(ChannelId_t p_chIdx, double p_val);
+
   //- change period
   void write_frequency(double p_frequency);
 
-- 
GitLab


From e3d05be346d74b5a50b53dae0f9be0eb234fbf70 Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Tue, 25 Mar 2025 10:26:35 +0100
Subject: [PATCH 12/13] fix: always synchronize read and write values (except
 during ramps)

---
 src/SingleShotAO.cpp | 61 +++++++++++++++++++++++++++++++++++++-------
 src/SingleShotAO.h   |  4 +++
 2 files changed, 56 insertions(+), 9 deletions(-)

diff --git a/src/SingleShotAO.cpp b/src/SingleShotAO.cpp
index 7bcea21..76ce807 100755
--- a/src/SingleShotAO.cpp
+++ b/src/SingleShotAO.cpp
@@ -455,7 +455,6 @@ void SingleShotAO::init_device()
 		dai_channel.tai.min_value = "-10.0";
 		dai_channel.tai.description = "Output value for channel " + oss.str() + " (in measurementUnit).";
 		dai_channel.tai.format = "%1.2f";
-		dai_channel.memorized = true;
 		dai_channel.cdb = false;
 
 		//- read callback
@@ -494,7 +493,6 @@ void SingleShotAO::init_device()
 		dai_speed.tai.format = "%1.2f";
 
 		//- cleanup tango db option: cleanup tango db when removing this dyn. attr. (i.e. erase its properties from db)
-		dai_speed.memorized = true;
 		dai_speed.cdb = false;
 
 		//- read callback
@@ -528,7 +526,6 @@ void SingleShotAO::init_device()
 		dai_initial.tai.format = "%1.2f";
 
 		//- cleanup tango db option: cleanup tango db when removing this dyn. attr. (i.e. erase its properties from db)
-		dai_initial.memorized = true;
 		dai_initial.cdb = false;
 
 		//- read callback
@@ -595,7 +592,12 @@ void SingleShotAO::init_device()
 				std::string attrName = attrPrefix + oss.str();
 				double val = yat4tango::PropertyHelper::get_memorized_attribute<double>(this, attrName);
 				DEBUG_STREAM << "Found memorized value for " << attrName << ": " << val << endl;
+
+				// Write the value to the manager (that contains the "read" attributes values)
 				(m_manager->*setter)(l_cpt, val);
+
+				// Write the value to the "write" attributes using helper function
+				setDynamicAttributeWriteValue(attrName, val);
 			}
 			catch (...) {
 				// nothing to do
@@ -954,6 +956,7 @@ void SingleShotAO::write_channel(yat4tango::DynamicAttributeWriteCallbackData &
 {
 	double l_val;
 	cbd.tga->get_write_value(l_val);
+	cbd.tga->set_write_value(l_val);
 
 	std::string l_attr_name = cbd.dya->get_name();
 	yat::uint16 l_idx = extractNumber(l_attr_name, kCHANNEL); // extract channel nb
@@ -981,7 +984,12 @@ void SingleShotAO::write_channel(yat4tango::DynamicAttributeWriteCallbackData &
 	DEBUG_STREAM << "SingleShotAO::write_channel(): channel " << l_idx << " value set to " << l_val << endl;
 
 	yat4tango::PropertyHelper::set_memorized_attribute(this, l_attr_name, l_val);
-	yat4tango::PropertyHelper::set_memorized_attribute(this, kINITIAL + std::to_string(l_idx), l_val);
+	
+	// Update the initial attribute to match the channel value
+	std::string initialAttrName = kINITIAL + std::to_string(l_idx);
+	yat4tango::PropertyHelper::set_memorized_attribute(this, initialAttrName, l_val);
+	setDynamicAttributeWriteValue(initialAttrName, l_val);
+	
 	DEBUG_STREAM << "SingleShotAO::write_channel(): memorized attribute " << l_attr_name << " set to " << l_val << endl;
 }
 
@@ -1116,6 +1124,37 @@ void SingleShotAO::write_initial(yat4tango::DynamicAttributeWriteCallbackData &
 }
 
 
+//+------------------------------------------------------------------
+/**
+ * set the write value of a dynamic attribute
+ * used to keep read and write values in sync
+ */
+bool SingleShotAO::setDynamicAttributeWriteValue(const std::string& attrName, double value)
+{
+    try {
+        Tango::DeviceImpl* dev = static_cast<Tango::DeviceImpl*>(this);
+        Tango::DevDouble val_to_write = value;
+        
+        // Get the attribute
+        Tango::WAttribute& attr = dev->get_device_attr()->get_w_attr_by_name(attrName.c_str());
+        // Set the write value
+        attr.set_write_value(&val_to_write, 1);
+        DEBUG_STREAM << "Set write value for " << attrName << " to " << value << endl;
+        return true;
+    }
+    catch (Tango::DevFailed &df) {
+        ERROR_STREAM << "Failed to set write value for " << attrName 
+                    << ": " << df << endl;
+        return false;
+    }
+    catch (...) {
+        ERROR_STREAM << "Unknown exception setting write value for " 
+                    << attrName << endl;
+        return false;
+    }
+}
+
+
 //+------------------------------------------------------------------
 /**
  *	method:	SingleShotAO::abort
@@ -1160,11 +1199,15 @@ void SingleShotAO::_abort()
 			DEBUG_STREAM << "Channel " << l_cpt << " is running. Memorizing values..." << std::endl;
 
 			double l_val_channel = m_manager->get_channel(l_cpt);
-			yat4tango::PropertyHelper::set_memorized_attribute(this, kCHANNEL + std::to_string(l_cpt), l_val_channel);
-			DEBUG_STREAM << "Memorizing channel " << l_cpt << " to " << l_val_channel << std::endl;
-
-			yat4tango::PropertyHelper::set_memorized_attribute(this, kINITIAL + std::to_string(l_cpt), l_val_channel);
-			DEBUG_STREAM << "Memorizing initial " << l_cpt << " to " << l_val_channel << std::endl;
+			std::string l_channel_attr_name = kCHANNEL + std::to_string(l_cpt);
+			yat4tango::PropertyHelper::set_memorized_attribute(this, l_channel_attr_name, l_val_channel);
+			setDynamicAttributeWriteValue(l_channel_attr_name, l_val_channel);
+			DEBUG_STREAM << "Memorizing " << l_channel_attr_name << " to " << l_val_channel << std::endl;
+
+			std::string l_initial_attr_name = kINITIAL + std::to_string(l_cpt);
+			yat4tango::PropertyHelper::set_memorized_attribute(this, l_initial_attr_name, l_val_channel);
+			setDynamicAttributeWriteValue(l_initial_attr_name, l_val_channel);
+			DEBUG_STREAM << "Memorizing " << l_initial_attr_name << " to " << l_val_channel << std::endl;
 		}
 	}
 	m_manager->abort();
diff --git a/src/SingleShotAO.h b/src/SingleShotAO.h
index 7cee441..17e2b74 100755
--- a/src/SingleShotAO.h
+++ b/src/SingleShotAO.h
@@ -290,6 +290,10 @@ protected :
 	
 	void	_abort();
 
+	// set the write value of a dynamic attribute
+	// used to keep read and write values in sync
+	bool setDynamicAttributeWriteValue(const std::string& attrName, double value);
+
 };
 
 }	// namespace_ns
-- 
GitLab


From d77c4643378126b3d588ba492a619170dae27130 Mon Sep 17 00:00:00 2001
From: MALFREYT <alexandre.malfreyt@synchrotron-soleil.fr>
Date: Wed, 26 Mar 2025 11:30:58 +0100
Subject: [PATCH 13/13] fix: applied requested changes (by @langlois in !3)

---
 src/SingleShotAO.cpp      | 75 +++++++++++++++++++++++----------------
 src/SingleShotAOClass.cpp |  3 +-
 src/SingleShotAOManager.h |  2 ++
 3 files changed, 49 insertions(+), 31 deletions(-)

diff --git a/src/SingleShotAO.cpp b/src/SingleShotAO.cpp
index 76ce807..0c95671 100755
--- a/src/SingleShotAO.cpp
+++ b/src/SingleShotAO.cpp
@@ -380,14 +380,17 @@ void SingleShotAO::init_device()
 
 	// Remove existing dynamic attributes to avoid duplicates or other issues
 	// --------------------------------------------
-	if (m_dyn_attr_manager) {
-		try {
+	if (m_dyn_attr_manager)
+	{
+		try
+		{
 			m_dyn_attr_manager->remove_attributes();
 			delete m_dyn_attr_manager;
 			m_dyn_attr_manager = NULL;
 			DEBUG_STREAM << "Existing dynamic attributes manager cleaned up" << endl;
 		} 
-		catch (...) {
+		catch (...)
+		{
 			ERROR_STREAM << "Error cleaning up existing dynamic attributes manager, continuing..." << endl;
 			// Continue anyway - we'll create a new one
 		}
@@ -424,12 +427,6 @@ void SingleShotAO::init_device()
 	INFO_STREAM << "Creating dynamic attributes for " << m_nb_chan << " channels.";
 	INFO_STREAM << "Total attributes: " << m_nb_chan * (enableRamps ? 3 : 1) << endl;
 
-	// if enableRamps if false, skip speed and initial attributes
-	if (!enableRamps)
-	{
-		INFO_STREAM << "Ramps are disabled. Skipping speed and initial attributes" << std::endl;
-	}
-
 	for (unsigned int l_cpt = 0; l_cpt < m_nb_chan; l_cpt++)
 	{
 		yat::OSStream oss;
@@ -454,7 +451,7 @@ void SingleShotAO::init_device()
 		dai_channel.tai.max_value = "10.0";
 		dai_channel.tai.min_value = "-10.0";
 		dai_channel.tai.description = "Output value for channel " + oss.str() + " (in measurementUnit).";
-		dai_channel.tai.format = "%1.2f";
+		dai_channel.tai.format = "%2.1f";
 		dai_channel.cdb = false;
 
 		//- read callback
@@ -468,7 +465,8 @@ void SingleShotAO::init_device()
 		l_dynAttrList.push_back(dai_channel);
 
 		// if enableRamps if false, skip speed and initial attributes
-		if (!enableRamps) {
+		if (!enableRamps)
+		{
 			DEBUG_STREAM << "Ramps are disabled. Skipping speed and initial attributes for channel " << l_cpt << std::endl;
 			continue;
 		}
@@ -490,7 +488,7 @@ void SingleShotAO::init_device()
 		dai_speed.tai.standard_unit = "V/s";
 		dai_speed.tai.display_unit = "V/s";
 		dai_speed.tai.description = "Speed for ramp generation, in V/s. If speed is NULL, no ramp generated but direct write on channel output " + oss.str() + " (in measurementUnit).";
-		dai_speed.tai.format = "%1.2f";
+		dai_speed.tai.format = "%2.1f";
 
 		//- cleanup tango db option: cleanup tango db when removing this dyn. attr. (i.e. erase its properties from db)
 		dai_speed.cdb = false;
@@ -523,7 +521,7 @@ void SingleShotAO::init_device()
 		dai_initial.tai.standard_unit = "V";
 		dai_initial.tai.display_unit = "V";
 		dai_initial.tai.description = "Initial value for ramp function, in V. Defaults to last written value in channel attribute " + oss.str() + ".";
-		dai_initial.tai.format = "%1.2f";
+		dai_initial.tai.format = "%2.1f";
 
 		//- cleanup tango db option: cleanup tango db when removing this dyn. attr. (i.e. erase its properties from db)
 		dai_initial.cdb = false;
@@ -543,17 +541,20 @@ void SingleShotAO::init_device()
 	INFO_STREAM << "Prepared " << l_dynAttrList.size() << " dynamic attributes for creation" << endl;
 	
 	// Add all attributes
-	try {
+	try 
+	{
   		m_dyn_attr_manager->add_attributes(l_dynAttrList);
 		INFO_STREAM << "Successfully added all dynamic attributes" << endl;
 	}
-	catch (Tango::DevFailed &df) {
+	catch (Tango::DevFailed &df) 
+	{
 		ERROR_STREAM << "Failed to add dynamic attributes: " << df << endl;
 		m_currStatus = "Failed to add dynamic attributes. See log for details";
 		m_state = Tango::FAULT;
 		return;
 	}
-	catch (...) {
+	catch (...) 
+	{
 		ERROR_STREAM << "Unknown exception when adding dynamic attributes" << endl;
 		m_currStatus = "Failed to add dynamic attributes. Unknown error";
 		m_state = Tango::FAULT;
@@ -561,17 +562,21 @@ void SingleShotAO::init_device()
 	}
 
 	// Initialize maps in manager class for all channels
-	try {
-		for (unsigned int l_cpt = 0; l_cpt < m_nb_chan; l_cpt++) {
+	try
+	{
+		for (unsigned int l_cpt = 0; l_cpt < m_nb_chan; l_cpt++)
+		{
 			// Initialize with default values
 			m_manager->set_channel(l_cpt, 0.0);
-			if (enableRamps) {
+			if (enableRamps)
+			{
 				m_manager->set_speed(l_cpt, 0.0);
 				m_manager->set_initial(l_cpt, 0.0);
 			}
 		}
 	}
-	catch (Tango::DevFailed &df) {
+	catch (Tango::DevFailed &df)
+	{
 		ERROR_STREAM << "Failed to initialize channel maps: " << df << endl;
 		m_currStatus = "Failed to initialize channel maps. See log for details";
 		m_state = Tango::FAULT;
@@ -588,7 +593,8 @@ void SingleShotAO::init_device()
 		auto applyMemorizedAttr = [&](const std::string& attrPrefix, 
 			void (SingleShotAOManager::*setter)(yat::uint16, double))
 		{
-			try {
+			try
+			{
 				std::string attrName = attrPrefix + oss.str();
 				double val = yat4tango::PropertyHelper::get_memorized_attribute<double>(this, attrName);
 				DEBUG_STREAM << "Found memorized value for " << attrName << ": " << val << endl;
@@ -599,18 +605,21 @@ void SingleShotAO::init_device()
 				// Write the value to the "write" attributes using helper function
 				setDynamicAttributeWriteValue(attrName, val);
 			}
-			catch (...) {
+			catch (...)
+			{
 				// nothing to do
 			}
 		};
 
 		// Get and set memorized values for speed, initial and channel
-		if (enableRamps) {
+		if (enableRamps)
+		{
 			applyMemorizedAttr(kSPEED, &SingleShotAOManager::set_speed);
 			applyMemorizedAttr(kINITIAL, &SingleShotAOManager::set_initial); 
 		}
 
-		if (outputMemorizedChannelsAtInit) {
+		if (outputMemorizedChannelsAtInit)
+		{
 			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::write_channel_direct); // write memorized value to board output (directly, without ramp)
 		} else {
 			applyMemorizedAttr(kCHANNEL, &SingleShotAOManager::set_channel); // only apply memorized value to the device
@@ -663,7 +672,8 @@ void SingleShotAO::get_device_property()
 
 	// Call database and extract values
 	//--------------------------------------------
-	if (Tango::Util::instance()->_UseDb==true) {
+	if (Tango::Util::instance()->_UseDb==true)
+	{
 		get_db_device()->get_property(dev_prop);
 	}
 
@@ -736,7 +746,8 @@ void SingleShotAO::get_device_property()
 		ERROR_STREAM << "Required device property <BoardType> is missing" << endl;
 		critical_properties_missing = true;
 	}
-	if (critical_properties_missing) {
+	if (critical_properties_missing)
+	{
 		return;
 	}
 
@@ -1131,7 +1142,8 @@ void SingleShotAO::write_initial(yat4tango::DynamicAttributeWriteCallbackData &
  */
 bool SingleShotAO::setDynamicAttributeWriteValue(const std::string& attrName, double value)
 {
-    try {
+    try
+	{
         Tango::DeviceImpl* dev = static_cast<Tango::DeviceImpl*>(this);
         Tango::DevDouble val_to_write = value;
         
@@ -1142,12 +1154,14 @@ bool SingleShotAO::setDynamicAttributeWriteValue(const std::string& attrName, do
         DEBUG_STREAM << "Set write value for " << attrName << " to " << value << endl;
         return true;
     }
-    catch (Tango::DevFailed &df) {
+    catch (Tango::DevFailed &df)
+	{
         ERROR_STREAM << "Failed to set write value for " << attrName 
                     << ": " << df << endl;
         return false;
     }
-    catch (...) {
+    catch (...)
+	{
         ERROR_STREAM << "Unknown exception setting write value for " 
                     << attrName << endl;
         return false;
@@ -1195,7 +1209,8 @@ void SingleShotAO::_abort()
 	// memorize the current initial_values and value of channels
 	for (unsigned int l_cpt = 0; l_cpt < m_nb_chan; l_cpt++)
 	{
-		if (m_manager->is_running(l_cpt)) {
+		if (m_manager->is_running(l_cpt))
+		{
 			DEBUG_STREAM << "Channel " << l_cpt << " is running. Memorizing values..." << std::endl;
 
 			double l_val_channel = m_manager->get_channel(l_cpt);
diff --git a/src/SingleShotAOClass.cpp b/src/SingleShotAOClass.cpp
index 29a1ef5..d5b5ee1 100755
--- a/src/SingleShotAOClass.cpp
+++ b/src/SingleShotAOClass.cpp
@@ -87,7 +87,8 @@ __declspec(dllexport)
 
 #endif
 
-	Tango::DeviceClass *_create_SingleShotAO_class(const char *name) {
+	Tango::DeviceClass *_create_SingleShotAO_class(const char *name)
+	{
 		return SingleShotAO_ns::SingleShotAOClass::init(name);
 	}
 }
diff --git a/src/SingleShotAOManager.h b/src/SingleShotAOManager.h
index 25c002e..325e00f 100755
--- a/src/SingleShotAOManager.h
+++ b/src/SingleShotAOManager.h
@@ -54,9 +54,11 @@ public:
   double get_channel(ChannelId_t p_chIdx);
 
   //- set channel
+  // Updates the value for the channel in the device without sending it to hardware
   void set_channel(ChannelId_t p_chIdx, double p_val);
 
   //- write channel
+  // Writes value to the channel to the device and the hardware, with a ramp if speed is not null and EnableRamps property is true
   void write_channel(ChannelId_t p_chIdx, double p_val);
 
   //- write a channel directly (without ramp)
-- 
GitLab