= Handling Communications Messages in Plugins

Since 5.8.0 opencpn has a new system for handling messages in plugins.
The new model is based on that a plugin subscribes to the messages it is
interested in rather then the old model where plugins get all messages.

The new system is the only way to handle the new NMEA2000 devices.
For N0183 devices a compatiblity layer is installed which makes things work
as earlier.
However, the new system can be used also for nmea0183 where it offers more
flexibility and performance.

== Receiving messages

Receiving messages is done in six steps:

. Define the type of message to work with
. Declare a listening object.
. Declare an event which will carry the message.
. Declare a method which handles the message.
. Subscribe to this message i. e., start listening to it
. Handle the messages as they arrives.


The code handling messages must live in a wxEventClass instance.
In almost all cases this means that the code should be implemented in a wxWindow.
If there is a wxWindow available with a suitable lifespan, this could be used.
If there is no such window, it needs to be created (it can be hidden) or an
existing one can be modified to alwasy exist, using Show()/Hide() to enable and
disable.

As an example, the *ShipDrivergui_impl*  class is used.
First, in the header declare a listener on the class level, here with the shipdriver
plugin _ShipDrivergui_impl.h_ header as an example:

----
        class Dlg : public ShipDriverBase {
        public:
            ...
        private:
     ②      std::shared_ptr<ObservableListener> listener;
            ...
----
Then, in the implementation (.cpp) file  declare a method which handles the
message, something like this:

----
     ④ static void HandleGPGA(ObservedEvt ev) {
     ①      NMEA0183 id("GPGGA");
            std::string payload = GetN0183Payload(id, ev)
            ...
----
Then, still in the .cpp file, set up the listening in the window constructor:

----
         Dlg::Dlg(wxWindow* parent, ...)

         {
         ...
     ①   NMEA0183Id id("GPGGA");
     ③   wxDEFINE_EVENT(EVT_SHIPDRIVER, ObservedEvt);
     ⑤   listener = GetListener(id, EVT_SHIPDRIVER, this);
     ⑥   Bind(EVT_SHIPDRIVER, [&](ObservedEvt ev) { HandleGPGA(ev); });
----

Notes:

①:: This is the type of message we are listening to. There are similar types
    for NMEA2000, SignalK and decoded navigation data (lat, long, etc.). Check
    *ocpn_plugin.h* for details.

②:: Boiler-plate code, makes sure the listener is destroyed together with
    the plugin. It is important that the listener is declared in the class,
    a variable defined in a method will not work.

③:: This is the event generated for our message which we will listen to.
    It should have a unique name, including the plugin name is a good habit.
    However the name, here _EVT_SHIPDRIVER_, could be anything.

④:: The `GetN0183Payload()` method  gets the payload of the message into the
    `payload` string. From here it can be processed as required. There are similar
     methods for Nmea2000 and SignalK messages (signalK from upcoming 5.10.0).

⑤:: This activates the listener. From this point system sends us an
    _EVT_SHIPDRIVER_ for each received GPGGA message.

⑥:: This is the final piece in the puzzle. It defines what should happen when
    the _EVT_SHIPDRIVER_ arrives.  From this point all incoming GPGGA
    messages will land in the `HandleGPGGA()` method and be processed.

=== Simplified workflow using a helper class

The workflow above means quite a lot of typing for each message. Using a
helper class can automate a lot of this.
An example class called `MsgListener` is provided xref:msg_listen.adoc[here].

With this class available in a header listening to a message is simplified to

. Declare a MsgListener listener object
. Declare a method which handles the message.
. Initiate the listener to invoke the method  when message arrives.

The MsgListener object normally lives in the class context, like:

----

     class Dlg : public ShipDriverBase {
     public:
         ...
     private:
         MsgListener<NMEA0183Id> listener;
         ...

----
Other available types includes `MsgListener<NMEA2000Id>` and
 `MsgListener<SignalkId>`

The method is declared  as above:

----
    static void HandleGPGA(ObservedEvt ev) {
        NMEA0183Id id("GPGGA");
        std::string payload = GetN0183Payload(id, ev)
        ...
----
Similar methods exist to extract NMEA2000 and SignalK data:
`GetN2000Payload(NMEA2000Id id, ObservedEvt ev)` and
`GetSignalkPayload(ObservedEvt ev)`.

To invoke this method when a GPGGA message arrives, initiate the
MsgListener like:

----
    listener.Init(NMEA0183Id("GPGGA"),
                  [&](ObservedEvt ev) { HandleGPGA(ev); });
----


=== Epilog: Using the lambda

The last step above was
`Bind(EVT_SHIPDRIVER [](wxCommandEvent ev) { HandleGPGA(ev); })`.
The part between the curly braces is actually a C++ lambda expression.
There is no need to dive into this to get something running, but it offers
far larger possibilities than to just call a function.
Actually, if written like
 `Bind(EVT_SHIPDRIVER, [&](wxCommandEvent ev) { ... })` any code could
be written between the braces.

The interesting part here is that the `[&]` prefix makes this code "see"
anything defined in the plugin.
This is a convenient way to access plugin variables in the handler,
something which otherwise is a problem.

To get the feeling one need to experiment.
But then again, C++ lambdas is a complex step which is not necessary
to get something running.

=== Receiving SignalK messages

Handling signalk messages goes like
```
    auto payload = GetSignalkPayload(ev);
    const auto msg = *std::static_pointer_cast<const wxJSONValue>(payload);
```
`msg` is a const json map containing at least

* "Data": the parsed json message.
* "ErrorCount": int, the number of parsing errors.
* "WarningCount": int, the number of parsing warnings.
* "Errors": list of strings, error messages.
* "Warnings": list of strings, warning messages.
* "Context": string, message context.
* "ContextSelf": string, own ship context.

Since the map is const, expressions like `msg["Data"]` are not possible
(the [] operator is not const since it potentially inserts data into the
map if the index does not exist). Use `msg.ItemAt("Data")` instead.

== Sending messages

Plugin API supporting direct access to comm drivers for output purposes

Plugins may access comm ports for direct output.
The general program flow for a plugin may look something like this
pseudo-code:

.  Plugin will query OCPN core for a list of active comm drivers.
.  Plugin will inspect the list, and query OCPN core for driver attributes.
.  Plugin will select a comm driver with appropriate attributes for output.
.  Plugin will register a list of PGNs expected to be transmitted (N2K
   specific)
.  Plugin may then send a payload buffer to a specific comm driver for
   output as soon as possible.

The mechanism for specifying a particular comm driver uses the notion of
"handles". Each active comm driver has an associated opaque handle, managed
by OCPN core. All references by a plugin to a driver are by means of its
handle. Handles should be considered to be "opaque", meaning that the exact
contents of the handle are of no specific value to the plugin, and only have
meaning to the OCPN core management of drivers.

Some example code sending `payload_msg` to the */dev/ttyUSB0* port:

    using namespace std;
    string payload_msg("$GPRMC, some data \r\n");
    string my_port("/dev/ttyUSB0");

    for (const auto& handle : GetActiveDrivers()) {
      const auto& attributes = GetAttributes(handle);
      if (attributes.find("protocol") == attributes.end()) continue;
      if (attributes.at("protocol") != "nmea0183") continue;
      if (attributes.find("commPort") == attributes.end()) continue;
      if (attributes.at("commPort").find(my_port) == string::npos) continue;

      auto payload = make_shared<std::vector<uint8_t>>();
      for (const auto& ch : payload_msg) payload->push_back(ch);
      CommDriverResult result = WriteCommDriver(handle, payload);
      return result;
    }
    return RESULT_COMM_INVALID_PARMS;   // no such driver
