Skip to Content
CTech Digital
  • Home
  • Odoo services
  • AI services
  • Contact us
  • 0
  • Nederlands (BE) English (UK) Français
CTech Digital
  • 0
    • Home
    • Odoo services
    • AI services
    • Contact us
  • Nederlands (BE) English (UK) Français

16/5/2025

  • All Blogs
  • Daily blog
  • 16/5/2025
  • 16 May 2025 by
    CTech Metrology, Luc Wens

    About commands and events


    Continuing on the command/event challenge.

    As seen yesterday commands are derived from CNode and for every command there is a derivation,... not good.

    And then we need to foresee a mechanism for events/warning, things send from the proxy to the engine.

    Basically commands and events are equivalent, the direction of sending is different, but for the rest they share:

    • It is identified by a string
    • They contain a list of all kinds of parameters
    • They require a response at the final destination

    The best way to tackle this:

    • We make it a CMessage, derived from CNode still, but no further derivations
      • Contains an ID
      • Contains a type
        • Command
        • Event
        • Maybe warning to make a more dramatic difference with Event
      • Contains a parameter container, we'll be using JSON for this
    • At the send side
      • We create the CMessage
      • Set the ID
      • Set all the parameters
      • Pack into a CTCPGram and send over, currently we have 
        • TCPGRAM_CODE_COMMAND        = 1;
        • TCPGRAM_CODE_EVENT          = 5;
        •  => this could be reduced to TCPGRAM_CODE_MESSAGE          = 1; and we pack the type inside
    • At the receiving side
      • There is a std::map<std::string,std::function> with a response function to each message


    Commands will send a result back, same ID possibly with another type field. Events don't send anything back.

    Note that status (TCPGRAM_CODE_STATUS         = 2;) could also be threated like this. If a state changes we also need to respond in an appropriate way, this could be in the same way.

    here are all current TCP telegram types


    constexpr unsigned char TCPGRAM_CODE_DOUBLES        = 0;         // array of doubles

    constexpr unsigned char TCPGRAM_CODE_COMMAND        = 1;         

    constexpr unsigned char TCPGRAM_CODE_STATUS         = 2;        

    constexpr unsigned char TCPGRAM_CODE_CONFIGURATION  = 3;        

    constexpr unsigned char TCPGRAM_CODE_STRING         = 4;         // string

    constexpr unsigned char TCPGRAM_CODE_EVENT          = 5;        

    constexpr unsigned char TCPGRAM_CODE_INTERRUPT      = 6;         // interrupt

    constexpr unsigned char TCPGRAM_CODE_ERROR          = 7;         // contains an error

    constexpr unsigned char TCPGRAM_CODE_TEST_BIG       = 10;        

    constexpr unsigned char TCPGRAM_CODE_INVALID        = 100;       // invalid return

    constexpr unsigned char TCPGRAM_CODE_DONT_USE       = UCHAR_MAX; // contains a warning

    All fields in yellow are candidates to be converted into CMessage.

    The ones in red, check if these are doing anything


    So the tasks are

    1. Create CMessage class
    2. Create CTCPGram support routines 
    3. Create action handlers in responders.cpp
    4. Replace 
      1. All CCommand derivations


    Progress

    Created Messages.h in ctrack_Data and added to folder States-Commands

    NO, i think messages will also be used by proxies, so it has to go where TCPTelegram.h is

    If we don't use this in proxies as well, then we risk doing too much translations.

    The of course we can derive from CNode, but then again, it that needed? 

    I think it is better to decouple then CMessage from CNode, and if it is required, then make a CNodeMessage that contains a CMessage.

    Code Snippet
    1. class CMessage
    2. {
    3.  
    4.   public:
    5.     CMessage() = default;
    6.     CMessage(const std::string &ID, const MessageType &messageType);
    7.     virtual ~CMessage();
    8.  
    9.   public: // serialization
    10.     virtual std::string Serialize() const;
    11.     virtual void        Deserialize(const std::string &data);
    12.  
    13.   public: // accessors
    14.     std::string GetID() const;
    15.     void        SetID(const std::string &id);
    16.     MessageType GetType() const;
    17.     void        SetType(MessageType type);
    18.     json       &GetParameters();
    19.  
    20.   protected:
    21.     json m_Parameters; // parameters can be accessed as a JSON object
    22. };

    We can have a complete empty message, for example to use with Deserialize, but normally you provide type and ID.

    Question is if we need type, do we actually need to make a difference between an event and a command? 

    A response structure could be like this:

    Code Snippet
    1. std::map<std::string, std::function<void(CMessage &)>> responderMap;
    2.  
    3. responderMap["HardwareDetect"] = respondFunction;
    4.  
    5.                auto CommandName= Message.GetID();
    6. if (responderMap.find(CommandName) != responderMap.end())
    7. {
    8.     responderMap[Message.GetID()](Message2);
    9. }
    10. else
    11. {
    12.     std::cout << "No function found for this message" << std::endl;
    13. }


    So we have a map that couples a string to a function, like a lambda or whatever, and it does not matter if this is an event or a command.

    We could extend the prototype of the responder to 

    std::unique_ptr<CMessage> ResponseFunction(std::unique_ptr<CMessage>)

    So if the message is a command and a return is required, the return smart pointer can be used to send a response.

    A further expansion is that we give the communication interface that received the telegram with the message, so that the response function can use this to send a response once finished. This gives the possibility to start a small thread if the function takes alot of time. This can be the case with lasertracker where some commands like Home take a long time. The response function could take the message and feed it to a thread that has a FIFO buffer with commands. Everytime a function is done and a response is available, we can use the communication interface to return the response.

    Another aspect is namespacing, for example a command HardwareDetect will appear in several moments:

    • CTrack UI to engine sends a HardwareDetect message
    • The engine will send the HardwareDetect to all proxies

    So we could add a namespace like CTrack or Vicon before the command and we could append _return if the message is a response. 

    This gives:

    • UI to Engine : CTrack::HardwareDetect
    • Engine to Proxy : Device::HardwareDetect
    • Proxy returns to Engine : Device::HardwareDetect_Return
    • Engine to UI : CTrack::HardwareDetect_Return

    The situation that we try to solve here is when there are 2 lasertrackers in the configuration and from the UI a command is send to tracker A. 

    In that case a devicename is not sufficient because tracker A and B can be the same type. 

    Let's take the situation where there are 3 trackers A1, A2 and B1, where A1 and A2 are the same type. So for A1 and A2 we have the same function and need a serial or a UUID to make a further distinction. For tracker B it may or may not be a different function. 

    Response function need to be registered by the objects that contain the response function.

    So if we introduce a new device, let's say a new laser tracker type C that has his own Home function, then we need to have this device to register a C::Home(Message) function.

    Can we have multiple responders for the same message ?

    We could have a list of response functions. 

    We need to have a singleton ResponseManager that keeps a list of message ID's and for each message ID a group of response functions. 

    Devices, functions, whatever, can then use this to register/deregister their response functions.

    This would basically take over the task of the statemanager, or we should derive our statemanager from it. Proxies can have their own ResponseManager.

    By the way, states are also messages which require specific responses.

    So you can see that the initial requirement to handle events from proxy devices is triggering a complete refactoring.

    In c# we have something similar with events and delegates by the way.

    Asking if c++ has something similar has yielded the next answers:

    • ChatGPT : https://chatgpt.com/share/68276666-c4e0-800b-b0bb-1cc1e846cfcf
    • Gemini : https://g.co/gemini/share/cda4c121956e


     Current implementation

    TCPCommunication.h has the next respond prototype

    typedef std::function<void()>               StateResponder;
    typedef std::function<void(SOCKET, size_t)> ConnectResponder;​
    typedef std::function<void(CNode *&)> ​ ​​CommandResponder;

    Which declares why all current messages are CNode derivations.

    There is a function SetCommandResponders in Responders.cpp that sets all responders to StateManager with CommandResponseAdd.

    Some remarks on this:

    • This is quite rigid, this makes it quite difficult for a laser tracker to set and remove responders on long operations
    • Does StateManager really have to be in charge of this. A better approach would be to have a singleton ResponseManager 

    The responsemanager would also handle state messages and set this in the StateManager.

    What about Socket state changes, as there is also a response approach for this. 

    Now this are set in CCommunicationInterface/CCommunicationObject. Before we already had the idea as well to organize all response functions to a CCommunicationInterface/CCommunicationObject since this is where the message come in and go out.

    Question remains then, do we have a single ResponseManager, or does every connection have its own response handler. 

    Maybe a careful initial approach is to start with a singleton and see how that works out.

    We could also extend the ResponseManager to handle all messages, also the data coming in. Now this is done in Device::Run which checks for availability of messages, and filters out only data, warning and error messages, so that approach is not great.

    On the other hand, if we put it in a CCommunicationInterface, we surely have to take care of lifetime 


    A bit more on the subject of interacting objects that might dissappear at any time:

    • https://g.co/gemini/share/8506c62e0202
    • https://chatgpt.com/share/68284b5d-1c48-800b-a40c-6b20c6f0c0f0



    in Daily blog
    15/5/2025
    Copyright © CTech
    Nederlands (BE) | English (UK) | Français
    Powered by Odoo - The #1 Open Source eCommerce