IoT - mqttPubMsg, a MQTT Publisher: a simple DLL for usage in Winbatch of VB

Author: Jan Willem Teunisse, 28 March 2016

Design and development of a mqttPubMsg DLL, written in C for MQTT Publications

Introduction

Although there are examples on the internet how to build a MQTT publisher or a subscriber client in languages like Java, Javascript, Python, C#, etc. and even in C/C++, in my research on the internet I did not found any examples to be used in a Windows scripting language like VB or Winbatch. What I was looking for, is a MQTT Windows COM/OLE service, which I could call in one of my Winbatch scripts, but to no avail.

After some thoughts I decided to polish up my 30 odd years of old C knowledge and to develop a simple Windows DLL written in plain C, which I could call in a Winbatch script.
As a development tool I downloaded the Code::Blocks IDE with the minGW C/C++ compiler suite. The basis of my C DLL is an example MQTT synchronous publisher program, which I found in the Exclipse/Paho community.

As a second step I read the following Tutorial: Create a Sample DLL Project using CodeBlocks IDE in C/C++ and used the sample as the third step in order to test if after compiling and building the DLL, the Winbatch testing script could call the mqttPubMsg DLL without runtime errors. After fixing a couple of novice errors, the mqttPubMsg DLL was build with no errors or warnings, the test Winbatch script was succesfull in calling the DLL. And the message window with the MQTT_address setting was shown on the computer monitor.

Basic DLL frame



int DLL_EXPORT mqttPubMsg(const LPCSTR ADDRESS, const LPCSTR CLIENTID, const LPCSTR TOPIC, const LPCSTR PAYLOAD)
{
    int rc = 0 ;
    MessageBoxA(0, ADDRESS, "DLL Message", MB_OK | MB_ICONINFORMATION);
    rc += 2 ;
    return rc ;
}


mqttPubMsg DLL parameters

The following parameters will be used in the DLL mqttPubMsg:

In the Eclipse/Paho mqtt C code example these four variables are of the type String, in our case the parameters are of type IN String

The result of the DLL call is returned as an integer (type long),

DLL Test Example in Winbatch

According to the examples of how to call in Winbatch a DLL, the first statement we will try is the following:

Result = DllCallCdecl("mqttPubMsg.dll", long:"mqttPubMsg" , lpstr:MQTT_Address, lpstr:MQTT_ClientID, lpstr:MQTT_Topic, lpstr:MQTT_Payload)

Note: In Winbatch variable names are case-insensitive, unlike in C.

A sample of the Winbatch script is shown in the code block below. The source code is very simple. The variable DLL_Path contains the directory where the mqttPubMsg.dll is stored in the development environment. Later on the DLL is stored in the Winbatch script directory.
The second parameter defines the entry point of the function mqttPubMsg in the DLL.

Winbatch script



MQTT_Address =    "tcp://192.168.10.22:1883"
MQTT_Clientid =   "MicasaWBTPub"
MQTT_Topic =      "micasa/sunctrlr/stat/sunscreen"
MQTT_Payload =    "RolledIn;20160228T20:00"
QOS     =    1       ; this is a settings in the DLL
TIMEOUT =    10000   ; this is a settings in the DLL

DLL_Path = "D:\...\mqttPubMsg.dll"
Result = DllCallCdecl(DLL_Path, long:"mqttPubMsg", lpstr:MQTT_Address, lpstr:MQTT_ClientID, lpstr:MQTT_Topic, lpstr:MQTT_Payload)
Message("Result of DLL Call", "result: %Result%")
exit


Back to top

The real deal

After succesfully testing the third step, the time came to put the mqtt publishing code into action.

The idea was to change the basic C mqtt publishing program into an external DLL, which itself calls the necessary functions and structures from the paho-mqttv3c.dll as part of the Eclipse/Paho project.

Winbatch script mqttWBTPub.wbt --> mqttPubMsg.dll --> paho-mqttv3c.dll

As a novice Code::Blocks user I ran into trouble by getting errors when I tried to compile and building the fourth step. Errors related to 'undefined reference to MQTTClient_create' and so forth. So I did a step back and tried to compile and build the original C code with linking the paho-mqttv3c.lib and started to study the mingw32 manual in how to solve these errors. Something to do with project->build options and linker SearchDirectory options. Finally I was succesfull in getting the C code compiling, building and running without errors. Hurrah! I also noticed, that the published message is received by my running Groovy mqtt subscriber script.

So now back to source code of the DLL to be compiled and build into a working DLL , this time as a release version. After typing in the remaining source code and checking the build options for the linker to 'include' the paho-mqttv3c.lib library, I started to compile and build the source code into a new version of the DLL until no errors or warnings were found. After starting up the Winbatch script, I got an runtime Winbatch error 'DLL not loaded'.
How is this possible and how to resolve this.
When I started testing, a copy of the paho-mqttv3c.dll was stored in the same directory as the Winbatch script: D:\Winbatch\Ontw. Furthermore my assumption was that the paho-mqttv3c.lib was also included by the building process into the mqttPubMsg.dll file. Which turned out not to be true.
Slowly building up the source code: the error started with the line where it creates the client:

MQTTClient_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL);

In order to solve the problem I decided to take the following steps:

Searching on the internet I also came across some tools, as part of the mingw32 compiler suite, https://www3.ntu.edu.sg/home/ehchua/programming/cpp/gcc_make.html

Wrapping the final development up, I included a function getVersion to return a versionnumber of the mqttPubMsg.dll and also version information in the DLL file itself using a resource file.

A sample of the output published by the Winbatch script as it is read by the MQTT Subscriber, is shown in the code block below.

Output sample



D:\Ontwikkelomgeving\groovy>java -cp ".;D:\OtherProgramFiles\groovy\lib\*" Mqtt2Database
IoT MQTT subcriber - Oracle updates:
Environment MQTT Broker Host: 192.168.10.22, Port: 1883
Temp dir : D:\Temp
MQTT Url: tcp://192.168.10.22:1883
SQL collect MQTT settings:
  MQTT host 192.168.10.22
  MQTT port: 1883
Settings MQTT Broker Host: 192.168.10.22, Port: 1883
MQTT Broker url: tcp://192.168.10.22:1883
Topic: micasa/sunctrlr/stat/sunscreen - Payload Msg: RolledOut;20160328T12:26
  params: micasa/sunctrlr/stat/sunscreen, RolledOut;20160328T12:26
  payload/bericht lengte: 24, first: 9, last: 9
  SQL result: 1
Database result: 1
Topic: micasa/sunctrlr/stat/sunscreen - Payload Msg: RolledIn;20160328T12:27
  params: micasa/sunctrlr/stat/sunscreen, RolledIn;20160328T12:27
  payload/bericht lengte: 23, first: 8, last: 8
  SQL result: 1
Database result: 1


Back to top

Summary

The main goal in this article was to focus on a working example of an external DLL called by Winbatch or VBA using synchronous published MQTT messages.

Most time consuming was to get to know and gain experience on a Windows development platform and less the C programming.

The final C code of the DLL looks like the following code blocks. First the file mqttpubmsg.c, followed by the file mqttpubmsg.h and the Winbatch script.

File mqttpubmsg.c



#include "mqttpubmsg.h"
#include "stdio.h"
#include "stdlib.h"
#include "string.h"
#define MQTTPUB_VERSION L"V3.1.1Prod"  // related to BSTR
#define MQTTPUB_INTVERSION 301011      // format major.minor.maintenance.n  n=0 Test, n=1 Production
#define QOS         1
#define TIMEOUT     10000L
/* A DLL to be used in Winbatch in order to send synchronous MQTT Payload Messages
** Filename : mqttpubmsg.dll
** Author :   J.W. Teunisse
** Version :  3.1.1 P (Production)
** Date :     15-3-2016, edited 21-3-2016 after Winbatch support tips
** Based on Paho MQTT C library and sample program
** License :  Eclipse/Paho
**
*/
DLL_EXPORT LONG __stdcall WINAPI getVersion(void)
{
  // MessageBoxA(0, dllVersion, "DLL Publish Message Version", MB_OK);  // for testing purposes
  return MQTTPUB_INTVERSION ;
}

DLL_EXPORT int __stdcall WINAPI mqttPubMsg(LPCSTR ADDRESS, LPCSTR CLIENTID, LPCSTR TOPIC, LPCSTR PAYLOAD)
{
    int rc = 0 ;
    // char msgtoken[60];  // for Messagebox test purposes
    MQTTClient client;

    //  MessageBoxA(0, ADDRESS, "DLL Publish Message to address", MB_OK | MB_ICONINFORMATION);  // for test purposes
    MQTTClient_connectOptions conn_opts = MQTTClient_connectOptions_initializer;
    MQTTClient_message pubmsg = MQTTClient_message_initializer;
    MQTTClient_deliveryToken token = 2;
    MQTTClient_create(&client, ADDRESS, CLIENTID, MQTTCLIENT_PERSISTENCE_NONE, NULL);  // throws error in Winbatch DLLCall
    conn_opts.keepAliveInterval = 20;
    conn_opts.cleansession = 1;
    if ((rc = MQTTClient_connect(client, &conn_opts)) != MQTTCLIENT_SUCCESS)
    {
        // printf("Failed to connect, return code %d\n", rc);
        // MessageBoxA(0, "Failed to connect, return code, ", "DLL Message connection", MB_OK | MB_ICONINFORMATION);
       return rc;
    }
    pubmsg.payload = (char *)PAYLOAD;
    pubmsg.payloadlen = strlen(PAYLOAD);
    pubmsg.qos = QOS;
    pubmsg.retained = 0;
    MQTTClient_publishMessage(client, TOPIC, &pubmsg, &token);
    /* printf("Waiting for up to %d seconds for publication of %s\n"
            "on topic %s for client with ClientID: %s\n",
            (int)(TIMEOUT/1000), PAYLOAD, TOPIC, CLIENTID);
    */
    rc = MQTTClient_waitForCompletion(client, token, TIMEOUT);
    // sprintf(msgtoken, "Message with delivery token %d delivered\n", token);
    // MessageBoxA(0, msgtoken, "DLL Message", MB_OK | MB_ICONINFORMATION);
    MQTTClient_disconnect(client, 10000);
    MQTTClient_destroy(&client);
    return rc ;
}

DLL_EXPORT BOOL __stdcall WINAPI APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch (fdwReason)
    {
        case DLL_PROCESS_ATTACH:
            // attach to process
            // return FALSE to fail DLL load
            break;

        case DLL_PROCESS_DETACH:
            // detach from process
            break;

        case DLL_THREAD_ATTACH:
            // attach to thread
            break;

        case DLL_THREAD_DETACH:
            // detach from thread
            break;
    }
    return TRUE; // successful
}


File mqttpubmsg.h



#ifndef __MAIN_H__
#define __MAIN_H__

#include 
#include "MQTTClient.h"

/*  To use this exported function of dll, include this header
 *  in your project.
 */

#ifdef BUILD_DLL
    #define DLL_EXPORT __declspec(dllexport)
#else
    #define DLL_EXPORT __declspec(dllimport)
#endif

#ifdef __cplusplus
extern "C"
{
#endif

// _declspec(dllimport)
extern int MQTTClient_create(MQTTClient* handle, const char* serverURI, const char* clientId,
		int persistence_type, void* persistence_context);
extern int MQTTClient_connect(MQTTClient handle, MQTTClient_connectOptions* options);

extern int MQTTClient_publishMessage(MQTTClient handle, const char* topicName, MQTTClient_message* msg, MQTTClient_deliveryToken* dt);

extern int MQTTClient_waitForCompletion(MQTTClient handle, MQTTClient_deliveryToken dt, unsigned long timeout);

extern int MQTTClient_disconnect(MQTTClient handle, int timeout);

extern void MQTTClient_destroy(MQTTClient* handle);

DLL_EXPORT LONG __stdcall getVersion(void) ;

DLL_EXPORT int __stdcall mqttPubMsg(const LPCSTR ADDRESS, const LPCSTR CLIENTID, const LPCSTR TOPIC, const LPCSTR PAYLOAD);

#ifdef __cplusplus
}
#endif

#endif // __MAIN_H__



Winbatch script



if RtStatus() == 10 then DirChange(DirScript())  ;; if running in Winbatch Studio, then change working directory
; File  : paho_mqttPub.wbt
; Description: test bed in order to test the mqttPubMsg.ddl based on the Eclipse/Paho mqtt DLL's
; Author: J.W. Teunisse
; License: Eclipse/Paho
; Remarks: The mqttPubMsg.dll contains 2 entry points
;          - long getVersion()                             returns the version number of the DLL as 301011 (== 3.1.1P) 
;          - long mqttPubMsg(address, id, topic, payload)  publish the MQTT topic and payload message to the MQTT broker and returns
;                                                          the result.
MQTT_ADDRESS =    "tcp://192.168.10.22:1883"
MQTT_CLIENTID =   "MicasaWBTPub"
MQTT_TOPIC =      "micasa/sunctrlr/stat/sunscreen"
MQTT_PAYLOAD =    "RolledIn;20160321T23:15"
PayloadMsg = "RolledIn"
systijd = TimeYmdHms( )
DatumTijd = StrCat(StrReplace(StrSub(systijd,1,10), ":", ""), "T", StrSub(systijd, 12, 5))
i = 0  ;; counter for 3..x times
mqtt_version = 0
QOS     =    1
TIMEOUT =    10000   ; both are used in the DLL itself
;
ScriptDir = DirScript()
DLL_Path = Strcat(ScriptDir, "mqttPubMsg.dll")
if FileExist(DLL_Path) then
;   dllhandle = DllCall('kernel32.dll', long:'LoadLibraryA', lpstr:DLL_Path)
;   if !dllhandle
;     error = DllLastError()
;     Message("System Error", error)
;   endif
   dllhandle=DllLoad(DLL_Path)
   mqtt_version = DllCall(dllhandle, long:"getVersion")
   while i < 3
     i = i + 1
     if PayloadMsg == "RolledIn" then
        PayloadMsg = "RolledOut"
     else
        PayloadMsg = "RolledIn"
     endif
     systijd = TimeYmdHms( )
     DatumTijd = StrCat(StrReplace(StrSub(systijd,1,10), ":", ""), "T", StrSub(systijd, 12, 5))
     MQTT_PAYLOAD = StrCat(PayloadMsg,";",DatumTijd)
     Result = DllCall(dllhandle, long:"mqttPubMsg", lpstr:MQTT_Address, lpstr:MQTT_ClientID, lpstr:MQTT_Topic, lpstr:MQTT_Payload)
     ;;Result = DllCall(DLL_Path, long:"mqttPubMsg", lpstr:MQTT_Address, lpstr:MQTT_ClientID, lpstr:MQTT_Topic, lpstr:MQTT_Payload)
     TimeDelay(60) ; wait a minute 
   end while  
   Message("Result of DLL Call", "Version %mqtt_version%, result: %Result%")
   DllFree(dllhandle)
else
  Message("Result of DLL Call", "DLL mqttPubMsg.dll not found.")
end if
exit


Depending on my needs in my Domotica project the DLL could be expanded with MQTT Subscriber functionality, which demand for a callback routine. Also I noticed during my research that since 2013 Winbatch supports a .net interface like iDispatch. So experimenting with the C# implemenation of MQTT and using this Winbatch looks interesting. Maybe stuff for a future article.

Licenses and copyright

Licenses of the used software components.


© Copyright 2016 by J.W. Teunisse
This article and this piece of software as presented in this article is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this software.

Back to top

References

Back to top

Comments or advice

Your comments or advice for improvement are most welcome, you can send them to the following email-address pr@jwteunisse.nl

Back to top