Skip to content
Snippets Groups Projects
testDirectDeviceToCS.cc 9.22 KiB
/*
 * testDirectDeviceToCS.cc
 *
 *  Created on: Jun 22, 2016
 *      Author: Martin Hierholzer
 */

#define BOOST_TEST_MODULE testDirectDeviceToCS

#include <boost/test/included/unit_test.hpp>

#include <ChimeraTK/Device.h>
#include "Application.h"
#include "PeriodicTrigger.h"
#include "DeviceModule.h"
#include "ControlSystemModule.h"
#include "TestFacility.h"

using namespace boost::unit_test_framework;
namespace ctk = ChimeraTK;

// list of user types the accessors are tested with
typedef boost::mpl::list<int8_t,uint8_t,
                         int16_t,uint16_t,
                         int32_t,uint32_t,
                         float,double>        test_types;

#define CHECK_TIMEOUT(condition, maxMilliseconds)                                                                   \
    {                                                                                                               \
      std::chrono::steady_clock::time_point t0 = std::chrono::steady_clock::now();                                  \
      while(!(condition)) {                                                                                         \
        bool timeout_reached = (std::chrono::steady_clock::now()-t0) > std::chrono::milliseconds(maxMilliseconds);  \
        BOOST_CHECK( !timeout_reached );                                                                            \
        if(timeout_reached) break;                                                                                  \
        usleep(1000);                                                                                               \
      }                                                                                                             \
    }

/*********************************************************************************************************************/
/* the ApplicationModule for the test is a template of the user type */

template<typename T>
struct TestModule : public ctk::ApplicationModule {
    using ctk::ApplicationModule::ApplicationModule;

    ctk::ScalarPushInput<T> consumer{this, "consumer", "", "No comment."};
    ctk::ScalarOutput<T> feeder{this, "feeder", "MV/m", "Some fancy explanation about this variable"};

    void mainLoop() {}
};

/*********************************************************************************************************************/
/* dummy application */

template<typename T>
struct TestApplication : public ctk::Application {
    TestApplication() : Application("testSuite") {
      ChimeraTK::BackendFactory::getInstance().setDMapFilePath("test.dmap");
    }
    ~TestApplication() { shutdown(); }

    using Application::makeConnections;     // we call makeConnections() manually in the tests to catch exceptions etc.
    void defineConnections() {}             // the setup is done in the tests

    TestModule<T> testModule{this, "TestModule", "The test module"};
    ctk::ControlSystemModule cs;

    ctk::DeviceModule dev{"Dummy0"};
};
/*********************************************************************************************************************/
/* dummy application for connectTo() test */

struct TestApplicationConnectTo : ctk::Application {
    TestApplicationConnectTo() : Application("testSuite") {}
    ~TestApplicationConnectTo() { shutdown(); }

    using Application::makeConnections;     // we call makeConnections() manually in the tests to catch exceptions etc.
    void defineConnections() {
      dev.connectTo(cs, trigger.tick);
    }

    ctk::PeriodicTrigger trigger{this, "trigger", ""};

    ctk::DeviceModule dev{"(dummy?map=test3.map)"};
    ctk::ControlSystemModule cs;
};

/*********************************************************************************************************************/

template<typename T, typename LAMBDA>
void testDirectRegister(ctk::TestFacility &test, ChimeraTK::ScalarRegisterAccessor<T> sender,
                                                 ChimeraTK::ScalarRegisterAccessor<T> receiver,
                                                 LAMBDA trigger, bool testMinMax=true) {

    sender = 42;
    sender.write();
    trigger();
    test.stepApplication();
    receiver.read();
    BOOST_CHECK_EQUAL( receiver, 42 );

    if(std::numeric_limits<T>::is_signed) {
      sender = -120;
      sender.write();
      trigger();
      test.stepApplication();
      receiver.read();
      BOOST_CHECK_EQUAL( receiver, -120 );
    }

    if(testMinMax) {
      sender = std::numeric_limits<T>::max();
      sender.write();
      trigger();
      test.stepApplication();
      receiver.read();
      BOOST_CHECK_EQUAL( receiver, std::numeric_limits<T>::max() );

      sender = std::numeric_limits<T>::min();
      sender.write();
      trigger();
      test.stepApplication();
      receiver.read();
      BOOST_CHECK_EQUAL( receiver, std::numeric_limits<T>::min() );

      sender = std::numeric_limits<T>::epsilon();
      sender.write();
      trigger();
      test.stepApplication();
      receiver.read();
      BOOST_CHECK_EQUAL( receiver, std::numeric_limits<T>::epsilon() );
    }

}

/*********************************************************************************************************************/
/* test direct control system to device connections */

BOOST_AUTO_TEST_CASE_TEMPLATE( testDirectCStoDev, T, test_types ) {

  TestApplication<T> app;

  auto pvManagers = ctk::createPVManager();
  app.setPVManager(pvManagers.second);

  app.cs("myFeeder", typeid(T), 1) >> app.dev("/MyModule/actuator");
  app.initialise();
  app.run();

  ChimeraTK::Device dev;
  dev.open("Dummy0");

  BOOST_CHECK_EQUAL(pvManagers.first->getAllProcessVariables().size(), 1);
  auto myFeeder = pvManagers.first->getProcessArray<T>("/myFeeder");
  BOOST_CHECK( myFeeder->getName() == "/myFeeder" );

  myFeeder->accessData(0) = 18;
  myFeeder->write();
  CHECK_TIMEOUT( dev.read<T>("/MyModule/actuator") == 18, 3000);

  myFeeder->accessData(0) = 20;
  myFeeder->write();
  CHECK_TIMEOUT( dev.read<T>("/MyModule/actuator") == 20, 3000);

}

/*********************************************************************************************************************/
/* test direct control system to device connections with fan out */

BOOST_AUTO_TEST_CASE_TEMPLATE( testDirectCStoDevFanOut, T, test_types ) {

  TestApplication<T> app;

  auto pvManagers = ctk::createPVManager();
  app.setPVManager(pvManagers.second);

  app.cs("myFeeder", typeid(T), 1) >> app.dev("/MyModule/actuator")
                                   >> app.dev("/MyModule/readBack");
  app.initialise();
  app.run();

  ChimeraTK::Device dev;
  dev.open("Dummy0");

  BOOST_CHECK_EQUAL(pvManagers.first->getAllProcessVariables().size(), 1);
  auto myFeeder = pvManagers.first->getProcessArray<T>("/myFeeder");
  BOOST_CHECK( myFeeder->getName() == "/myFeeder" );

  myFeeder->accessData(0) = 18;
  myFeeder->write();
  CHECK_TIMEOUT( dev.read<T>("/MyModule/actuator") == 18, 3000);
  CHECK_TIMEOUT( dev.read<T>("/MyModule/readBack") == 18, 3000);

  myFeeder->accessData(0) = 20;
  myFeeder->write();
  CHECK_TIMEOUT( dev.read<T>("/MyModule/actuator") == 20, 3000);
  CHECK_TIMEOUT( dev.read<T>("/MyModule/readBack") == 20, 3000);

}

/*********************************************************************************************************************/
/* test connectTo */

BOOST_AUTO_TEST_CASE( testConnectTo ) {
  std::cout << "testConnectTo" << std::endl;

  ctk::Device dev;
  dev.open("(dummy?map=test3.map)");
  TestApplicationConnectTo app;

  ctk::TestFacility test;
  auto devActuator = dev.getScalarRegisterAccessor<int32_t>("/MyModule/actuator");
  auto devReadback = dev.getScalarRegisterAccessor<int32_t>("/MyModule/readBack");
  auto devint32 = dev.getScalarRegisterAccessor<int32_t>("/Integers/signed32");
  auto devuint32 = dev.getScalarRegisterAccessor<uint32_t>("/Integers/unsigned32");
  auto devint16 = dev.getScalarRegisterAccessor<int16_t>("/Integers/signed16");
  auto devuint16 = dev.getScalarRegisterAccessor<uint16_t>("/Integers/unsigned16");
  auto devint8 = dev.getScalarRegisterAccessor<int8_t>("/Integers/signed8");
  auto devuint8 = dev.getScalarRegisterAccessor<uint8_t>("/Integers/unsigned8");
  auto devfloat = dev.getScalarRegisterAccessor<double>("/FixedPoint/value");
  auto csActuator = test.getScalar<int32_t>("/MyModule/actuator");
  auto csReadback = test.getScalar<int32_t>("/MyModule/readBack");
  auto csint32 = test.getScalar<int32_t>("/Integers/signed32");
  auto csuint32 = test.getScalar<uint32_t>("/Integers/unsigned32");
  auto csint16 = test.getScalar<int16_t>("/Integers/signed16");
  auto csuint16 = test.getScalar<uint16_t>("/Integers/unsigned16");
  auto csint8 = test.getScalar<int8_t>("/Integers/signed8");
  auto csuint8 = test.getScalar<uint8_t>("/Integers/unsigned8");
  auto csfloat = test.getScalar<double>("/FixedPoint/value");
  test.runApplication();

  testDirectRegister(test, csActuator, devActuator, []{});
  testDirectRegister(test, devReadback, csReadback, [&]{app.trigger.sendTrigger();});
  testDirectRegister(test, csint32, devint32, []{});
  testDirectRegister(test, csuint32, devuint32, []{});
  testDirectRegister(test, csint16, devint16, []{});
  testDirectRegister(test, csuint16, devuint16, []{});
  testDirectRegister(test, csint8, devint8, []{});
  testDirectRegister(test, csuint8, devuint8, []{});
  testDirectRegister(test, csfloat, devfloat, []{}, false);

}