Skip to content

Let's code !

Sébastien Gallou edited this page Jun 14, 2016 · 8 revisions

Here are exposed the main principles to write the code of your plugin. In example below, we suppose that the created plugin name is MyPlugin. Here is only discussed on the C++ plugin code. See How to start for more information.

Your plugin will be an executable, written in C++ language. Yadoms will start, stop and dial with it. A plugin can have several instances. This executable depends on (=link with) these libraries :

  • plugin_cpp_api
  • yadoms-shared

To make Yadoms recognize your plugin, your code must contain :

  • A class implementing the plugin_cpp_api::IPlugin interface, which will do the main plugin job.
  • A declaration of the plugin, by a call to IMPLEMENT_PLUGIN macro (takes the class name as parameter)

Recommended file structure

Plugin is generally composed by these files (at least !) :

  • MyPlugin.h : declaration of the class (CMyPlugin) implementing the plugin_cpp_api::IPlugin interface
  • MyPlugin.cpp : definition of CMyPlugin. Also declare the main plugin class with IMPLEMENT_PLUGIN macro.
  • MyPluginConfiguration.h : declaration of the configuration getter class (CMyPluginConfiguration)
  • MyPluginConfiguration.cpp : definition of CMyPluginConfiguration

Declare the plugin main class

To make plugin_cpp_api instance your plugin, you have to declare the main class using the IMPLEMENT_PLUGIN macro. In MyPlugin.cpp, simply call this macro like :

#include <plugin_cpp_api/ImplementationHelper.h>
IMPLEMENT_PLUGIN(CMyPlugin)

How is your plugin invoked

When user creates an instance of your plugin, Yadoms will start the plugin executable, construct the CMyPlugin class, and call CMyPlugin::dowork method.

Namespace

We recommend to use an alias for the plugin API namespace (as in the rest of this example), adding this line in your MyPlugin.h file :

// Shortcut to yPluginApi namespace
namespace yApi = shared::plugin::yPluginApi;

In the rest of your code, you can then access to a function/object of the yPluginApi like : yApi::IDeviceCommand

The main class CMyPlugin

This class manage all your plugin. It must implement plugin_cpp_api::IPlugin, like :

#pragma once
#include <plugin_cpp_api/IPlugin.h>
#include "MyPluginConfiguration.h"

// Shortcut to yPluginApi namespace
namespace yApi = shared::plugin::yPluginApi;

class CMyPlugin: public plugin_cpp_api::IPlugin
{
public:
   //--------------------------------------------------------------
   /// \brief	Constructor
   //--------------------------------------------------------------
   CMyPlugin();

   //--------------------------------------------------------------
   /// \brief	Destructor
   //--------------------------------------------------------------
   virtual ~CMyPlugin();

   // IPlugin implementation
   virtual void doWork(boost::shared_ptr<yApi::IYPluginApi> api);
  // [END] IPlugin implementation

private:
   //--------------------------------------------------------------
   /// \brief	The plugin configuration
   //--------------------------------------------------------------
   CMyPluginConfiguration m_configuration;
};

Note that this class do only 2 things :

  • Implement doWork function : all your working code will be here. This method is called by Yadoms when plugin instance starts.
  • Store the plugin configuration

doWork function

The dowork function contains the plugin main loop. This function receives an instantiation of the IYPluginApi (api). This object allows your plugin to interact with Yadoms. Your plugin will receive commands from Yadoms by the event handler (api->getEventHandler()). This object enables to wait for a Yadoms command without CPU load. The example for MyPlugin.cpp file, containing a simple doWork function :

#include "stdafx.h"
#include "MyPlugin.h"
#include <plugin_cpp_api/ImplementationHelper.h>

// Use this macro to define all necessary to make your library a Yadoms valid plugin.
// Note that you have to provide some extra files, like package.json, and icon.png
IMPLEMENT_PLUGIN(CMyPlugin)

CMyPlugin::CMyPlugin()
{
}

CMyPlugin::~CMyPlugin()
{
}

void CMyPlugin::doWork(boost::shared_ptr<yApi::IYPluginApi> api)
{
   YADOMS_LOG(debug) << "CMyPlugin is starting...";

   // Load configuration values (provided by database)
   m_configuration.initializeWith(api->getConfiguration());

   // Informs Yadoms about the plugin actual state
   context->setPluginState(yApi::historization::EPluginState::kRunning);

   // the main loop
   while (1)
   {
      // Wait for an event
      switch(api->getEventHandler().waitForEvents())
      {
      case yApi::IYPluginApi::kEventStopRequested:
         {
             // Yadoms request the plugin to stop. Note that plugin must be stop in 10 seconds max, otherwise it will be killed.
             api->setPluginState(yApi::historization::EPluginState::kStopped);
             return;
         }
      case yApi::IYPluginApi::kEventDeviceCommand:
         {
            // A command was received from Yadoms
            ...
            break;
         }
      case yApi::IYPluginApi::kEventUpdateConfiguration:
         {
            // Configuration was updated
            ...
            break;
         }
      default:
         {
            YADOMS_LOG(error) << "Unknown or unsupported message id " << api->getEventHandler().getEventId();
            break;
         }
      }
   }

   api->setPluginState(yApi::historization::EPluginState::kStopped);
}

Note that the only way to stop gracefully a plugin is on Yadoms request (yApi::IYPluginApi::kEventStopRequestedevent). All other exit ways will be considered as a plugin error.

The event handler

You can use the event handler for custom events or timers. First declare the event IDs (note that some values are reserved for Yadoms, so custom ID values must begin with yApi::IYPluginApi::kPluginFirstEventId) :

enum
{
   kCustomEvent1Id = yApi::IYPluginApi::kPluginFirstEventId,
   kCustomEvent2Id
};

We can for example create a periodic timer, sending an event every 30 seconds to event handler :

context->getEventHandler().createTimer(kCustomEvent1Id, shared::event::CEventTimer::kPeriodic, boost::posix_time::seconds(30));

To receive the event, simply add, in the doWork function the case on your event ID :

void CMyPlugin::doWork(boost::shared_ptr<yApi::IYPluginApi> api)
{
   try
   {
      ...
      while (1)
      {
         // Wait for an event
         switch(api->getEventHandler().waitForEvents())
         {
         case yApi::IYPluginApi::kEventDeviceCommand:
            ...
            break;
         case yApi::IYPluginApi::kEventUpdateConfiguration:
            ...
            break;
         
         case kCustomEvent1Id:
            ... Put here the code to execute every 30 seconds ...
            break;
         
         default:
            {
               YADOMS_LOG(error) << "Unknown or unsupported message id " << api->getEventHandler().getEventId();
               break;
            }
         }
      }
   }
   catch (boost::thread_interrupted&)
   {
      YADOMS_LOG(information) << "Thread is stopping...";
   }
}

The configuration helper

We recommend you to write a configuration helper, the easier way to read a value from the configuration.

class CMyPluginConfiguration
{
public:
virtual ~CMyPluginConfiguration() {}

   //--------------------------------------------------------------
   /// \brief		   Load configuration data
   /// \param [in] data The data container
   //--------------------------------------------------------------
   void initializeWith(const shared::CDataContainer &data)
   {
      m_configuration.initializeWith(data);
   }

   //--------------------------------------------------------------
   // Your accessors
   //--------------------------------------------------------------
   std::string getConfiguredString() const
   {
      return m_configuration.get<std::string>("String");
   }
   std::string isAdvancedModeChecked() const
   {
      return m_configuration.get<bool>("AdvancedModeChecked");
   }

private:
   yApi::YPluginConfiguration m_configuration;
};

Note that the keys used here ("String", "AdvancedModeChecked") must be declared in the configuration schema in the package.json file.

##More help

To get more help, please visit our forum.

Clone this wiki locally