EPICS Controls Argonne National Laboratory

Experimental Physics and
Industrial Control System

1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  <20252026  Index 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  <20252026 
<== Date ==> <== Thread ==>

Subject: Re: synchronizing the value of a read/write record
From: "Thomas, Patrick via Tech-talk" <[email protected]>
To: Michael Davidsaver <[email protected]>
Cc: "[email protected] Talk" <[email protected]>
Date: Tue, 19 Aug 2025 17:44:04 +0000

The code I am using is attached.


From: Thomas, Patrick <[email protected]>
Sent: Tuesday, August 19, 2025 10:39 AM
To: Michael Davidsaver <[email protected]>
Cc: Mark Rivers <[email protected]>; [email protected] Talk <[email protected]>
Subject: Re: synchronizing the value of a read/write record
 
Thank you. It still seems to compile after adding the override keyword, but not get called.

From: Michael Davidsaver <[email protected]>
Sent: Tuesday, August 19, 2025 10:32 AM
To: Thomas, Patrick <[email protected]>
Cc: Mark Rivers <[email protected]>; [email protected] Talk <[email protected]>
Subject: Re: synchronizing the value of a read/write record
 
On 8/19/25 10:21, Thomas, Patrick via Tech-talk wrote:
Hello,

I'm working through implementing a class that inherits from asynPortDriver and attempting to override the 'connect' virtual method. However, the code I have overridden it with never appears to be called. Is there a configuration parameter or something else that I need to do to have this work?

This might be a simple c++ issue with attempting to override a method, but providing an incorrect argument list.  With C++11, the "override" keyword can help detect this.


http ://www.en.cppreference.com/w/cpp/language/override.html




Thank you,
Patrick

From: Tech-talk <[email protected]> on behalf of Thomas, Patrick via Tech-talk <[email protected]>
Sent: Wednesday, August 13, 2025 10:45 AM
To: Mark Rivers <[email protected]>; [email protected] Talk <[email protected]>
Subject: Re: synchronizing the value of a read/write record
 
Hi Mark,

I did see that and was starting to look at using asyn, but I was having trouble figuring out how to do so. Is there a minimal working example that I might be able to modify? Do I need to create separate driver and device support?

The other thought I had was if this could be handled by somehow creating record support for read/write records.

Thank you,
Patrick


From: Mark Rivers <[email protected]>
Sent: Wednesday, August 13, 2025 5:48 AM
To: [email protected] Talk <[email protected]>; Thomas, Patrick <[email protected]>
Subject: Re: synchronizing the value of a read/write record
 
Hi Patrick,

As Ralph said, if your driver is written using asyn then you can use the info(asyn:READBACK) tag on your output record.


  • Is there any way to synchronize things so that write requests coming from channel access are always written to the hardware and not overridden by periodic updates from reading the hardware?

I'm not sure what you mean here.  I think the EPICS record locking an asyn port locking will guarantee that all EPICS writes get sent to the hardware atomically, and that all callbacks from the driver that update the output record will be atomic.  However, once EPICS writes the value to the hardware, the next poll of the hardware could quickly change the output record.  Maybe I am not understanding your question.

Mark




From: Tech-talk <[email protected]> on behalf of Thomas, Patrick via Tech-talk <[email protected]>
Sent: Tuesday, August 12, 2025 7:41 PM
To: [email protected] Talk <[email protected]>
Subject: synchronizing the value of a read/write record
 
Hello,

I'm attempting to write device support to handle the scenario where changes to the value of a hardware device can be made from both EPICS and another source. I would like the value of the hardware to be changed if I write a value to the record through channel access, and also to continuously monitor the hardware device for changes made from the other source and update the value of the record to match. One approach I have considered is to use separate output and input records, where the output records write values set from channel access to the hardware and the input records periodically scan the hardware and update their values accordingly. I am wondering however if there is a way to do this with the val field of just an output record. Is there any way to synchronize things so that write requests coming from channel access are always written to the hardware and not overridden by periodic updates from reading the hardware?

Thank you,
Patrick


#ifndef ADS_ASYN_PORT_DRIVER_HPP
#define ADS_ASYN_PORT_DRIVER_HPP


#include "initHooks.h"

#include "asynPortDriver.h"

#include "devADS.hpp"


class adsAsynPortDriver : public asynPortDriver {
public:
  adsAsynPortDriver(std::string portName, AmsNetId ams_address_net_id, uint16_t ams_address_port, std::string ip_address_string);

  //virtual ~adsAsynPortDriver();

  virtual asynStatus drvUserCreate(asynUser *pasynUser, const char *drvInfo, const char **pptypeName, size_t *psize) override;

  virtual asynStatus readInt32(asynUser *pasynUser, epicsInt32 *value) override;
  virtual asynStatus writeInt32(asynUser *pasynUser, epicsInt32 value) override;
  virtual asynStatus readInt64(asynUser *pasynUser, epicsInt64 *value) override;
  virtual asynStatus writeInt64(asynUser *pasynUser, epicsInt64 value) override;
  virtual asynStatus readFloat64(asynUser *pasynUser, epicsFloat64 *value) override;
  virtual asynStatus writeFloat64(asynUser *pasynUser, epicsFloat64 value) override;

  virtual asynStatus connect(asynUser *pasynUser) override;
  virtual asynStatus disconnect(asynUser *pasynUser) override;

  void init();
private:

  // The AMS address of the computer running the Beckhoff PLC.
  AmsNetId ams_address_net_id;

  uint16_t ams_address_port;

  // The IP address of the computer running the Beckhoff PLC.
  std::string ip_address_string;


  // The symbol names specified in the INP and OUT fields of all the records.
  std::vector<std::string> name_vec;

  std::map<std::string, ads_symbol_entry_type> name_to_ads_symbol_entry_map;

  std::map<std::string, size_t> name_to_value_buffer_size_map;

  std::map<std::string, SYMBOL_HANDLE_TYPE> name_to_handle_map;

  std::mutex handle_and_value_buffer_vec_mutex;
  std::vector<std::pair<SYMBOL_HANDLE_TYPE, std::vector<uint8_t>>> handle_and_value_buffer_vec;

  std::mutex name_to_return_code_and_value_buffer_pair_map_mutex;
  std::map<std::string, std::pair<RETURN_CODE_TYPE, std::vector<uint8_t>>> name_to_return_code_and_value_buffer_pair_map;
};


#endif
#include "adsAsynPortDriver.hpp"

#include <string.h>

#include "alarm.h"
#include "cvtTable.h"
#include "dbDefs.h"
#include "dbAccess.h"
#include "initHooks.h"
#include "recGbl.h"
#include "recSup.h"
#include "devSup.h"
#include "link.h"
#include "epicsExport.h"
#include "epicsExit.h"

#include "iocsh.h"


#include <ads-utils/ads.hpp>
#include <ads-utils/error_codes.hpp>
#include <ads-utils/symbol_table_change.hpp>
#include <ads-utils/ads_state_change.hpp>
#include <ads-utils/ads_symbol_upload_info_2.hpp>
#include <ads-utils/ads_symbol_entry.hpp>
#include <ads-utils/ads_data_type_entry.hpp>
#include <ads-utils/create_handles.hpp>
#include <ads-utils/release_handles.hpp>
#include <ads-utils/read.hpp>
#include <ads-utils/write.hpp>


// 500 is recommended by the documentation
// https://infosys.beckhoff.com/english.php?content=../content/1033/tc3_adssamples_c/html/tcadsdll_api_cpp_sample17.htm&id=7873012517228788493
#define PARTITION_SIZE 500


adsAsynPortDriver::adsAsynPortDriver(std::string portName, AmsNetId ams_address_net_id, uint16_t ams_address_port, std::string ip_address_string) :
  asynPortDriver(
    portName.c_str(),
    1,
    asynInt32Mask | asynInt64Mask | asynFloat64Mask | asynDrvUserMask,
    asynInt32Mask | asynInt64Mask | asynFloat64Mask,
    0,
    1, 0, 0)
{
  printf("adsAsynPortDriver\n");
}


asynStatus adsAsynPortDriver::drvUserCreate(asynUser *pasynUser, const char *drvInfo, const char **pptypeName, size_t *psize) {
  printf("drvUserCreate\n");

  if (pasynUser && drvInfo) {
    this->name_vec.push_back(std::string(drvInfo));

    pasynUser->userData = (void*) drvInfo;

    return asynSuccess;
  }
  else {
    return asynError;
  }
}


asynStatus adsAsynPortDriver::connect(asynUser *pasynUser) {
  asynStatus status = asynPortDriver::connect(pasynUser);

  printf("connect\n");

  return status;
}

asynStatus adsAsynPortDriver::disconnect(asynUser *pasynUser) {
  asynStatus status = asynPortDriver::disconnect(pasynUser);

  printf("disconnect\n");

  return status;
}


void adsAsynPortDriver::init() {
  const AmsAddr ams_address = { ams_address_net_id, ams_address_port };


  // ADS add route

  try {
    printf("AdsAddRoute\n");
    long error_code = AdsAddRoute(ams_address_net_id, ip_address_string.c_str());
    if (error_code) {
      try {
        printf("Error: AdsAddRoute: %s\n", general_ads_error_codes.at(error_code).c_str());
      }
      catch (const std::out_of_range& e) {
        printf("Unrecognized error code: %ld.\n", error_code);
      }

      exit(EXIT_FAILURE);
    }
  }
  catch (const std::exception &e) {
    exit(EXIT_FAILURE);
  }


  // ADS port open

  // Establishes a connection (communication port) to the TwinCAT message router.
  printf("AdsPortOpenEx\n");
  long port = AdsPortOpenEx();
  if (!port) {
    printf("Error: AdsPortOpenEx.\n");

    AdsDelRoute(ams_address_net_id);

    exit(EXIT_FAILURE);
  }


  // symbol table change notifications

  try {
    printf("register_symbol_table_change_callback_function\n");
    register_symbol_table_change_callback_function(port, ams_address);
  }
  catch (const std::exception& e) {
    AdsDelRoute(ams_address_net_id);

    exit(EXIT_FAILURE);
  }


  // ADS state change notifications

  try {
    printf("register_ads_state_change_callback_function\n");
    register_ads_state_change_callback_function(port, ams_address);
  }
  catch (const std::exception& e) {
    AdsDelRoute(ams_address_net_id);

    exit(EXIT_FAILURE);
  }


  // symbol upload information

  struct ads_symbol_upload_info_2_type ads_symbol_upload_info_2;

  try {
    printf("get_ads_symbol_upload_info_2\n");
    ads_symbol_upload_info_2 = get_ads_symbol_upload_info_2(port, ams_address);
  }
  catch (const std::exception& e) {
    AdsDelRoute(ams_address_net_id);

    exit(EXIT_FAILURE);
  }


  // symbol entry vector

  std::vector<ads_symbol_entry_type> ads_symbol_entry_vec;

  try {
    printf("get_ads_symbol_entry_vec_one_by_one\n");
    ads_symbol_entry_vec = get_ads_symbol_entry_vec_one_by_one(port, ams_address, name_vec);
  }
  catch (const std::exception& e) {
    AdsDelRoute(ams_address_net_id);

    exit(EXIT_FAILURE);
  }


  // name to ads symbol entry map
  for (const auto& ads_symbol_entry : ads_symbol_entry_vec) {
    name_to_ads_symbol_entry_map.insert({ads_symbol_entry.name_string, ads_symbol_entry});
  }


  // name to value buffer size map
  for (const auto& ads_symbol_entry : ads_symbol_entry_vec) {
    name_to_value_buffer_size_map.insert({ads_symbol_entry.name_string, ads_symbol_entry.header.size});
  }


  // name to handle map
  name_to_handle_map = get_name_to_handle_map_split(port, ams_address, name_vec, PARTITION_SIZE);


  for (const auto& name : name_vec) {
    name_to_return_code_and_value_buffer_pair_map[name] = {};
  }
}


asynStatus adsAsynPortDriver::readInt32(asynUser *pasynUser, epicsInt32 *value) {
  //printf("readInt32\n");

  if (pasynUser && pasynUser->userData) {
    //printf("Reading parameter %s\n", (char*) pasynUser->userData);
  }
  else {
    printf("Reading parameter (no userData available)\n");
  }

  return asynSuccess;
}


asynStatus adsAsynPortDriver::writeInt32(asynUser *pasynUser, epicsInt32 value) { return asynSuccess; }

asynStatus adsAsynPortDriver::readInt64(asynUser *pasynUser, epicsInt64 *value) { return asynSuccess; }
asynStatus adsAsynPortDriver::writeInt64(asynUser *pasynUser, epicsInt64 value) { return asynSuccess; }

asynStatus adsAsynPortDriver::readFloat64(asynUser *pasynUser, epicsFloat64 *value) { return asynSuccess; }
asynStatus adsAsynPortDriver::writeFloat64(asynUser *pasynUser, epicsFloat64 value) { return asynSuccess; }


// ----------------------------------------------------------------------------


static void devADSConfigureCallFunc(const iocshArgBuf *args) {
  std::string asyn_port_name_string = std::string(args[0].sval);
  printf("Asyn Port Name: %s\n", asyn_port_name_string.c_str());

  // The AMS address of the computer running the Beckhoff PLC.
  std::string ams_address_net_id_string = std::string(args[1].sval);
  AmsNetId ams_address_net_id = AmsNetId(ams_address_net_id_string);
  printf("NetID: %s\n", ams_address_net_id_string.c_str());

  uint16_t ams_address_port = (uint16_t) args[2].ival;
  printf("Port: %u\n", ams_address_port);

  // The IP address of the computer running the Beckhoff PLC.
  std::string ip_address_string = std::string(args[3].sval);
  printf("IP Address: %s\n", ip_address_string.c_str());

  new adsAsynPortDriver(asyn_port_name_string, ams_address_net_id, ams_address_port, ip_address_string);
}

static const iocshArg Arg_0 = { "Asyn_Port_Name", iocshArgString };
static const iocshArg Arg_1 = { "NetID", iocshArgString };
static const iocshArg Arg_2 = { "Port", iocshArgInt };
static const iocshArg Arg_3 = { "IP_Address", iocshArgString };
static const iocshArg *const Arg_List[] = { &Arg_0, &Arg_1, &Arg_2, &Arg_3 };
static const iocshFuncDef devADSConfigureFuncDef = { "devADS_Configure", 4, Arg_List, "asyn port name, AMS address, port, IP address" };


// ----------------------------------------------------------------------------


static void asyn_devADSRegistrar(void) {
  iocshRegister(&devADSConfigureFuncDef, devADSConfigureCallFunc);
}
extern "C" { epicsExportRegistrar(asyn_devADSRegistrar); }


// ----------------------------------------------------------------------------

Replies:
asynPortDriver derived class connect() method not being called when expected Mark Rivers via Tech-talk
References:
synchronizing the value of a read/write record Thomas, Patrick via Tech-talk
Re: synchronizing the value of a read/write record Mark Rivers via Tech-talk
Re: synchronizing the value of a read/write record Thomas, Patrick via Tech-talk
Re: synchronizing the value of a read/write record Thomas, Patrick via Tech-talk
Re: synchronizing the value of a read/write record Michael Davidsaver via Tech-talk
Re: synchronizing the value of a read/write record Thomas, Patrick via Tech-talk

Navigate by Date:
Prev: Re: synchronizing the value of a read/write record Thomas, Patrick via Tech-talk
Next: asynPortDriver derived class connect() method not being called when expected Mark Rivers via Tech-talk
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  <20252026 
Navigate by Thread:
Prev: Re: synchronizing the value of a read/write record Thomas, Patrick via Tech-talk
Next: asynPortDriver derived class connect() method not being called when expected Mark Rivers via Tech-talk
Index: 1994  1995  1996  1997  1998  1999  2000  2001  2002  2003  2004  2005  2006  2007  2008  2009  2010  2011  2012  2013  2014  2015  2016  2017  2018  2019  2020  2021  2022  2023  2024  <20252026 
ANJ, 19 Mar 2026 · Home · News · About · Talk · Base · Modules · Extensions ·
· Distributions · Download · Documents · Links · Licensing ·