Tutorial for the Web-IO Digital;
Control and monitor Web-IO Digital with Visual C++
Visual C++ is one of the most used development platform for creating Windows applications.
Using the following C++ program example you can represent your Web-IO Digital with its inputs and outputs in a Windows application. You can also switch the outputs on the Web-IO.
Preparations
- Provide power to the Web-IO and connect the IOs
- Connect the Web-IO to the network
- Assign IP addresses
On the Web-IO in Communication channels >> Socket API activate TCP-ASCII sockets and enable outputs for switching
Assemble the various operating elements and display objects in the Visual C++ form
Definition in the header files
Definition MyDlgData.h To prevent multiple definitions and be able to couple the member variables to the m_rcData
object which extends throughout the classes, we define in MyDlgData.h
several member variables. In addition, the object m_myStatBar
derived from CStatusBarCtrl
is defined.
To enable access to far ranging member functions related to the data object, this class inherits from CObject
. Since this class serves mainly as an interface, no member functions are defined here.
class MyDlgData : public CObject {
public: //--- Konstruktion
MyDlgData::MyDlgData() {};
virtual ~MyDlgData();
public:
//---MemberVariablen f. Connection
CString m_stIPNum;
CString m_stDataRequest;
CString m_stRecvbuf;
CString m_stLookingFor;
int m_iPortText{};
BOOL m_bButtonIsChecked{};
BOOL m_bAbleToRecvDataFromExtern{};
BOOL m_bCounterNeedUpdateInGUI{};
BOOL m_bIsThisMentForInput{};
//---MemberVariablen f. CWebIODlg
int m_iPolling{};
CString m_stCounter0;
CString m_stCounter1;
CString m_stPassword;
CString m_stStatBar;
HWND m_hMainWindow{};
//---Objekte f. CWebIODlg
CStatusBarCtrl m_myStatBar{};
//---MemberVariablen f. ProcessingInputOutput
int m_iHowMuchInputsDoWeHave{};
//---Objekte f. ProcessingInputOutput
CStringArray m_aShortenedValueOnlyArray;
};
Definition of Connection.h
The task of this class is reliable opening of a TCP connection using a socket interface. In addition is contains member functions for sending and receiving data. Below we show the pre-defined member variables as well as member functions:
class Connection : public ProcessingInputOutput, public CSocket {
public: //---Konstruktion
Connection( MyDlgData &rData );
~Connection();
DECLARE_DYNAMIC( Connection );
private: //--- Attribute
BOOL m_bResult{};
public: //--- Attribute
int m_iResult{};
CString m_stError;
public: //--- Implementierungen
BOOL CreateSocket( UINT uiPrefPort );
BOOL ConnectSocket( LPCTSTR lpPrefIp, UINT uiPrefPort );
BOOL SendDataViaSocket();
protected: //--- Ueberladungen MFC
virtual void OnReceive( int nErrorCode );
virtual void OnClose( int nErrorCode );
};
This class inherits from ProcessingInputOutput
in order to be able to use the functionalities of the member functions that process later. It also inherits from CSocket
, since the class represents the basis of the connection using socket interfaces via UDP or TCP.
The connection is asynchronous, so that data exchange is without a predefined cycle and the processes are handled concurrently.
This is why we make use of callback functions that are initiated when a receive event (data can bce received) or a close event (the connection was closed) is reported.
The respective trigger then decides how the program runs.
For the Web-IO to correctly process the data it receives, it requires its own class whose definition is described in the following.
class ProcessingInputOutput {
public: //--- Konstruktion
ProcessingInputOutput( MyDlgData &rData );
virtual ~ProcessingInputOutput();
private: //--- Attribute
CString m_stError;
CString m_stRawInputOutput;
public: //--- Implementierungen
void DefineSetOutputRequest( CString stIDNumber );
void DefineGetAllOutputRequest();
void DefineGetAllInputRequest();
void DefineGetAllOutRequest();
void DefineGetCounterRequest( CString stIDNumber );
void DefineGetCounterAllRequest();
void DefineSetClearRequest( CString stIDNumber );
void DefineClearAllRequest();
void RemoveDelimiterAndSaveIntoArray( CString stDel );
void ExtractValuesAndShortenArray();
void ChooseRightOperation();
void ProcessRequestAfterLookingForCounter0Only( int iStartingIndex );
void ProcessRequestAfterLookingForCounter1Only( int iStartingIndex );
void ProcessRequestAfterLookingForAllCounter( int iStartingIndex );
void ProcessRequestAfterLookingForAllInputs( int iStartingIndex );
void ProcessRequestAfterLookingForAllOutputs( int iStartingIndex );
void BinaryANDing(CString m_stRawInputOutput );
public: //---Datenstruktur fuer die Uebergabe von BinaryANDing zu ProcessCheckBoxStatus
typedef struct MyIODataStruct {
int aIOValues[4]{ 0 };
int Input0Value = 0;
int Input1Value = 0;
int Output0Value = 0;
int Output1Value = 0;
}MyIODataStruct;
MyIODataStruct m_IODataStruct;
protected: //--- Datenobjekte/ Datenarrays
MyDlgData &m_rcData;
CStringArray m_aAnswerStringArray;
};
This class is responsible exclusively for processing of the data passed by the data object.
Here we use the linking of the class with the m_rcData
object.
In addition to the member functions which split, copy, sae or apply the reply strings by interating operations, we use MyIODataStruct
to create a structure which allows us to conveniently manage a larger quantity of user data.
Graphic representation of the dialog window as well as its content is the job of CWebIODlg
. This class makes use among other things of CDialogEx
and is linked from MyDlgData
to m_rcData
and itself defines the object m_ccConnect
from the Connection
class.
class CWebIODlg : public CDialogEx {
DECLARE_DYNAMIC( CWebIODlg )
#ifdef AFX_DESIGN_TIME
enum {
IDD = IDD_MFCA_DIALOG
};
#endif
public: //--- Konstruktion
CWebIODlg( MyDlgData &rcData, CWnd* pParent = nullptr );
virtual ~CWebIODlg();
protected: //--- Implementierungen / Generierte MFC Message-Map Funktionen
//--- Timer
afx_msg void OnTimer( UINT nIDEvent );
//--- Buttons
afx_msg void OnBnClickedConnect();
afx_msg void OnBnClickedDisconnect();
afx_msg void OnBnClickedRead0();
afx_msg void OnBnClickedRead1();
afx_msg void OnBnClickedReadall();
afx_msg void OnBnClickedClear0();
afx_msg void OnBnClickedClear1();
afx_msg void OnBnClickedClearall();
afx_msg void OnBnClickedReadall0();
afx_msg void OnBnClickedReadall1();
//--- Checkboxes
afx_msg void OnBnClickedOutput0();
afx_msg void OnBnClickedOutput1();
afx_msg void OnBnClickedPolling0();
afx_msg void OnBnClickedPolling1();
afx_msg void OnBnClickedPollingcounter();
//--- Edit Controls
afx_msg void OnEnChangePollingtext();
afx_msg void OnEnChangeIptext();
afx_msg void OnEnChangePorttext();
afx_msg void OnEnChangePasswordtext();
protected: //--- Implementierung / Eigene Message-Map Funktionen
afx_msg LRESULT UpdateGUI( WPARAM CounterNeedUpdate, LPARAM Lparam );
afx_msg LRESULT ConnectionBrokeUp( WPARAM wParam, LPARAM lParam );
afx_msg LRESULT CWebIODlg::ProcessCheckBoxStatus(WPARAM wParam, LPARAM lParam);
afx_msg void OnSize( UINT nType, int cx, int cy );
DECLARE_MESSAGE_MAP()
protected: //--- Implementierung
void ChangeAccess( bool bCanConnect );
void ValidateDataAtStart();
void InitializeStatusBar();
void SendGotError();
protected: //--- Ueberladungen MFC
virtual BOOL OnInitDialog();
virtual void DoDataExchange( CDataExchange* pDX );
virtual BOOL DestroyWindow();
protected: //--- Datenobjekte
Connection m_ccConnect;
MyDlgData &m_rcData;
private: //--- Attribute
CString m_stBuffer;
CString m_stError;
int m_iResult{};
};
Here are mainly member functions which are opened as Callback if a button is clicked, a text field changed, or something created such as the dialog window itself. To this end several member functions are overloaded from CWebDialogEx
.
Starting the program
Setting up the operating elementsThe operating elements themselves can be created relatively simply using the Class Wizard which is integrated in VisualStudio. This lets you effortlessly also link for example buttons with member functions or control variables. To prevent improper use by the user, some operating elements are partially limited after certain function calls and at the start. This is defined under ChangeAccess()
.
void CWebIODlg::ChangeAccess( bool bCanConnect ) {
// Buttons
GetDlgItem( IDC_CONNECT )->EnableWindow( bCanConnect );
GetDlgItem( IDC_DISCONNECT )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_READ0 )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_READ1 )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_READALL )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_READALL0 )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_READALL1 )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_CLEAR0 )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_CLEAR1 )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_CLEARALL )->EnableWindow( !bCanConnect );
//Checkboxes
GetDlgItem( IDC_OUTPUT0 )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_OUTPUT1 )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_INPUT0 )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_INPUT1 )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_POLLING0 )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_POLLING1 )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_POLLINGCOUNTER )->EnableWindow( !bCanConnect );
if( bCanConnect ) {
CheckDlgButton( IDC_POLLING0, BST_UNCHECKED );
CheckDlgButton( IDC_POLLING1, BST_UNCHECKED );
CheckDlgButton( IDC_POLLINGCOUNTER, BST_UNCHECKED );
OnBnClickedPolling0();
OnBnClickedPolling1();
OnBnClickedPollingcounter();
}
// Edit Controls
GetDlgItem( IDC_POLLINGTEXT )->EnableWindow( !bCanConnect );
GetDlgItem( IDC_IPTEXT )->EnableWindow( bCanConnect );
GetDlgItem( IDC_PORTTEXT )->EnableWindow( bCanConnect );
GetDlgItem( IDC_PASSWORDTEXT )->EnableWindow( bCanConnect );
}
To initialize the status bar and be able to prepare the operating elements suitably for the user, we use the overloaded OnInitDialog()
function from CDialogEx
. The operations contained there are called once at each start. In addition we use the member function for linking the handle on the main window to the data object m_rcData
for other member functions.
BOOL CWebIODlg::OnInitDialog() {
CDialogEx::OnInitDialog();
InitializeStatusBar();
m_rcData.m_hMainWindow = m_hWnd;
ChangeAccess( TRUE );
return TRUE;
}
Description of how to create the status bar.
void CWebIODlg::InitializeStatusBar() {
m_rcData.m_myStatBar.Create(
WS_CHILD | WS_VISIBLE | WS_BORDER | CBRS_ALIGN_BOTTOM,
CRect( 0, 0, 0, 0 ), this, 0
);
m_rcData.m_myStatBar.SetText( "Ready! No Connection.", 0, 0 );
}
To ensure that the status bar is adjusted even after changing the window size, here the OnSize()
function is also overloaded and reports the change to the status bar.
void CWebIODlg::OnSize( UINT nType, int cx, int cy ) {
CDialogEx::OnSize( nType, cx, cy );
if( m_rcData.m_myStatBar.m_hWnd != NULL ) {
m_rcData.m_myStatBar.SendMessage( WM_SIZE, nType,
MAKELPARAM( cx, cy )
);
}
}
Connection control
Establishing the connection By entering the IP address of the Web-IO into the text field IDC_IPTEXT
and port 42280
to the text field IDC_PORTTEXT
clicking the button IDC_CONNECT
opens a connection. In addition, depending on the configuration, the password can be entered under IDC_PASSWORDTEXT
. By default no password is saved. To successfully open the connection, using ChangeAccess()
enables all the important buttons, checkboxes and text fields. In every case the user is informed of the current status by the status bar.
1. IPtext
void CWebIODlg::OnEnChangeIptext() {
GetDlgItemText( IDC_IPTEXT, m_rcData.m_stIPNum );
struct sockaddr_in soaddr;
m_iResult = inet_pton( AF_INET, m_rcData.m_stIPNum, &(
soaddr.sin_addr )
);
if( m_rcData.m_stIPNum == "" ) {
m_rcData.m_myStatBar.SetText(
"Please enter valid adressdata!", 0, 0
);
GetDlgItem( IDC_CONNECT )->EnableWindow( FALSE );
}
else if( m_iResult !=1 ) {
m_rcData.m_myStatBar.SetText(
"Please enter valid adressdata!", 0, 0
);
GetDlgItem( IDC_CONNECT )->EnableWindow( FALSE );
}
else {
m_rcData.m_myStatBar.SetText( "IPv4 adress is valid.", 0, 0 );
GetDlgItem( IDC_CONNECT )->EnableWindow( TRUE );
}
}
2. Porttext
void CWebIODlg::OnEnChangePorttext() {
int iPortBuffer;
GetDlgItemText( IDC_PORTTEXT, m_stBuffer );
iPortBuffer = atoi( m_stBuffer );
if( iPortBuffer > 0xFFFF ) {
m_rcData.m_myStatBar.SetText( "Please choose another port below 65535!", 0, 0 );
GetDlgItem( IDC_CONNECT )->EnableWindow( FALSE );
}
else if( iPortBuffer <= 0 ) {
m_rcData.m_myStatBar.SetText( "Please choose another port above 0!", 0, 0 );
GetDlgItem( IDC_CONNECT )->EnableWindow( FALSE );
}
else {
m_rcData.m_myStatBar.SetText( "Valid Port!", 0, 0 );
m_rcData.m_iPortText = iPortBuffer;
GetDlgItem( IDC_CONNECT )->EnableWindow( TRUE );
}
}
Opening the connection
To open a connection we call CreateSocket()
as well as ConnectSocket()
and use the previously checked values from the text fields IDC_IPTEXT
and IDC_PORTTEXT
plus the m_ccConnect
object which provides access to member functions of the Connection
class.
The member functions Create()
and Connect()
are derived from CSocket
.
BOOL Connection::CreateSocket( UINT uiPrefPort ) {
m_iResult = Create( uiPrefPort, SOCK_STREAM, NULL );
if( m_iResult == 0 ) {
m_iResult = GetLastError();
m_bResult = FALSE;
return m_bResult;
}
else {
m_bResult = TRUE;
return m_bResult;
}
}
BOOL Connection::ConnectSocket( LPCTSTR lpPrefIp, UINT uiPrefPort ) {
m_iResult = Connect( lpPrefIp, uiPrefPort );
if( m_iResult == 0 ) {
m_iResult = GetLastError();
m_bResult = FALSE;
return m_bResult;
}
else {
m_bResult = TRUE;
return m_bResult;
}
}
Connection is made
Once the connection has been made, the application is able to begin data exchange with the Web-IO. Here the user interface is enabled accordingly.
The status bar tells the user what the current connection status is.
void CWebIODlg::OnBnClickedConnect() {
LPCTSTR lpPrefIP = m_rcData.m_stIPNum;
UINT uiPrefPort = m_rcData.m_iPortText;
CWaitCursor m_cWaitCursor;
if( m_ccConnect.CreateSocket( uiPrefPort ) ) {
if( m_ccConnect.ConnectSocket( lpPrefIP, uiPrefPort ) ) {
ChangeAccess( FALSE );
m_rcData.m_stStatBar.Format(
_T( "Connected to %s on %i. " ),
( PCSTR ) m_rcData.m_stIPNum, m_rcData.m_iPortText
);
m_rcData.m_myStatBar.SetText( m_rcData.m_stStatBar, 0, 0 );
m_ccConnect.CancelBlockingCall();
m_ccConnect.AsyncSelect( FD_READ | FD_CLOSE );
ValidateDataAtStart();
}
else {
m_stError.Format( "Connection failed, error %d",
( PCTSTR ) m_ccConnect.m_iResult
);
m_rcData.m_myStatBar.SetText( m_stError, 0, 0 );
m_ccConnect.Close();
}
}
else {
m_stError.Format( "Socket creation failed, error %d",
( PCTSTR ) m_ccConnect.m_iResult
);
m_rcData.m_myStatBar.SetText( m_stError, 0, 0 );
}
}
Disconnecting
The connection remains open until the user ends it by clicking on the Disconnect button or it is ended autonomously by the Web-IO. Here the member function Close()
derived from the member function CSocket
is used. When the connection is closed the user is also informed by means of the status bar.
void CWebIODlg::OnBnClickedDisconnect() {
m_ccConnect.Close();
m_rcData.m_myStatBar.SetText(
"Disconnected successfully.", 0, 0
);
ChangeAccess( TRUE );
}
If the Web-IO should close the connection, this is detected by the file descriptor "CLOSE" and the callback function OnClose()
is called. This uses the Windows message to again block the user interface accordingly and alert the user of this via a MessageBox.
void Connection::OnClose( int nErrorCode ) {
AfxMessageBox(
( "Your Partner just closed the connection. There could be an password"
" or timeout issue.", MB_ICONINFORMATION )
);
if( !( m_iResult = PostMessageA(
m_rcData.m_hMainWindow, WM_CONNECTION_BROKE_UP, NULL, NULL ) ) ) {
m_iResult = GetLastError();
m_stError.Format( "OnClose PostMessage failed, error %d", m_iResult );
m_rcData.m_myStatBar.SetText( m_stError, 0, 0 );
}
}
Among other things the same code is used as of we had clicked on the Disconnect
button.
Then the application is again at the same place it was before the connection was broken off and a new connection can be opened.
LRESULT CWebIODlg::ConnectionBrokeUp( WPARAM wParam, LPARAM lParam ) {
OnBnClickedDisconnect();
return 1;
}
Operation and communication from the client side
Setting the outputs To switch the outputs on the Web-IO, the respective checkbox must be clicked, which then initiates the particular action. In the following example we see the member functions for setting IDC_OUTPUT0
and/or IDC_OUTPUT1
. After clicking in the checkbox, control variables are adjusted according to the case and the m_stLookingFor
string is manipulated, since it is needed later for performing particular operations.
Here are the control variables:
m_bAbleToRecvDataFromExtern
m_bIsThisMentForInput
m_stLookingFor
m_bCounterNeedUpdateInGUI
These have a significant effect on how the application runs and enable asynchronous processing of inputs as well as reliable decision making as to which operation is the correct one.
void CWebIODlg::OnBnClickedOutput0() {
m_rcData.m_bAbleToRecvDataFromExtern = FALSE;
m_rcData.m_bIsThisMentForInput = FALSE;
m_rcData.m_stLookingFor = "output";
m_rcData.m_bButtonIsChecked = IsDlgButtonChecked( IDC_OUTPUT0 ) ? TRUE : FALSE;
m_ccConnect.DefineSetOutputRequest( "0" );
if( m_ccConnect.SendDataViaSocket() == FALSE ) {
SendGotError();
}
}
void CWebIODlg::OnBnClickedOutput1() {
m_rcData.m_bAbleToRecvDataFromExtern = FALSE;
m_rcData.m_stLookingFor = "output";
m_rcData.m_bButtonIsChecked = IsDlgButtonChecked( IDC_OUTPUT1 ) ? TRUE : FALSE;
m_ccConnect.DefineSetOutputRequest( "1" );
if( m_ccConnect.SendDataViaSocket() == FALSE ) {
SendGotError();
}
}
In the next step we see whether the checkbox has already been set or not. Finally the GET command can be formulated as needed with the help of DefineSetOutputRequest()
. This prepares the command, which can then be sent via SendDataViaSocket()
.
void ProcessingInputOutput::DefineSetOutputRequest( CString stIDNumber ) {
if( m_rcData.m_bButtonIsChecked == FALSE ) {
m_rcData.m_stDataRequest = "GET /outputaccess" + stIDNumber +
"?PW=" + m_rcData.m_stPassword + "&State=ON&";
}
else {
m_rcData.m_stDataRequest = "GET /outputaccess" + stIDNumber +
"?PW=" + m_rcData.m_stPassword + "&State=OFF&";
}
}
To make the application usable for various different Web-IOs, each unique GET command has its own function with a fixed command string under m_stDataRequest
and commands which are intended to specifically drive or clear the outputs, inputs and counters.
In addition to specific setting of the outputs, we can also query the states of the outputs as well as the inputs as a whole. IDC_READALL0
reads the outputs and IDC_READALL1
the outputs. The command is easily distinguished from the one used for setting. Basically a command string is formed appropriate to the respective operation, control variables are adapted and thereby the path of the user data after their receipt is determined by OnReceive()
at a later point.
void CWebIODlg::OnBnClickedReadall0() {
m_rcData.m_bAbleToRecvDataFromExtern = FALSE;
m_rcData.m_bIsThisMentForInput = FALSE;
m_rcData.m_stLookingFor = "output";
m_ccConnect.DefineGetAllOutputRequest();
if( m_ccConnect.SendDataViaSocket() == FALSE ) {
SendGotError();
}
}
void CWebIODlg::OnBnClickedReadall1() {
m_rcData.m_bAbleToRecvDataFromExtern = TRUE;
m_rcData.m_bIsThisMentForInput = TRUE;
m_ccConnect.DefineGetAllInputRequest();
if( m_ccConnect.SendDataViaSocket() == FALSE ) {
SendGotError();
}
}
void ProcessingInputOutput::DefineGetAllOutputRequest() {
m_rcData.m_stDataRequest = "GET /output?PW="
+ m_rcData.m_stPassword + "&";
}
void ProcessingInputOutput::DefineGetAllInputRequest() {
m_rcData.m_stDataRequest = "GET /input?PW="
+ m_rcData.m_stPassword + "&";
}
Query counters
Since incoming input events are only noted in the Web-IO itself and thus the internal counter is incremented, this should also be able to be queried. The member function OnBnClickedRead0()
or OnBnClickedRead1()
sends a request to the Web-IO for the current status of the respective counter. Later the figure for the respective counter is taken from the reply of the Web-IO and displayed in the application.
void CWebIODlg::OnBnClickedRead0() {
m_rcData.m_bAbleToRecvDataFromExtern = FALSE;
m_rcData.m_stLookingFor = "counter0";
m_ccConnect.DefineGetCounterRequest( "0" );
if( m_ccConnect.SendDataViaSocket() == FALSE ) {
SendGotError();
}
}
void CWebIODlg::OnBnClickedRead1() {
m_rcData.m_bAbleToRecvDataFromExtern = FALSE;
m_rcData.m_stLookingFor = "counter1";
m_ccConnect.DefineGetCounterRequest( "1" );
if( m_ccConnect.SendDataViaSocket() == FALSE ) {
SendGotError();
}
}
Counter representation
In order to display the counters truthfully in our application based on the values we have just queried, we formulate member functions that do this using Windows messages.
Separation of the classes from each other is thus preserved. After we have sent the appropriate command string to the Web IO, we will of course receive a response. This contains the counter readings, if necessary with additional information about the Web-IO itself which can be configured individually.
This reply is captured by the OnReceive()
function described further below and sends, after processing is complete, a Windows message to UpdateGUI()
(The last step in processing of the string) -> The user data are now reliably saved and linked with the m_rcData
object so that it can be made usable in UpdateGUI()
:
void ProcessingInputOutput::ProcessRequestAfterLookingForCounter0Only(
int StartingIndex ) {
m_rcData.m_stCounter0 = m_rcData.m_aShortenedValueOnlyArray[StartingIndex];
m_rcData.m_bAbleToRecvDataFromExtern = TRUE;
m_rcData.m_bCounterNeedUpdateInGUI = TRUE;
}
void ProcessingInputOutput::ProcessRequestAfterLookingForCounter1Only(
int StartingIndex ) {
m_rcData.m_stCounter1 = m_rcData.m_aShortenedValueOnlyArray[StartingIndex];
m_rcData.m_bAbleToRecvDataFromExtern = TRUE;
m_rcData.m_bCounterNeedUpdateInGUI = TRUE;
}
( Auszug aus Connection::OnReceive )
...
if( !( m_iResult = PostMessageA(
m_rcData.m_hMainWindow, WM_PLEASE_UPDATE_GUI,
m_rcData.m_bCounterNeedUpdateInGUI, NULL ) ) ) {
m_iResult = GetLastError();
m_stError.Format( "OnReceive PostMessage failed, error %d", m_iResult );
m_rcData.m_myStatBar.SetText( m_stError, 0, 0 );
}
Receiving the Windows message:
LRESULT CWebIODlg::UpdateGUI( WPARAM CounterNeedUpdate, LPARAM Lparam ) {
if( CounterNeedUpdate )
{
SetDlgItemText( IDC_COUNTERTEXT0, m_rcData.m_stCounter0 );
SetDlgItemText( IDC_COUNTERTEXT1, m_rcData.m_stCounter1 );
m_rcData.m_bCounterNeedUpdateInGUI = FALSE;
return 1;
}
else {
// Do some ErrorHandling here
return 0;
}
}
After UpdateGUI()
has received the Windows message, it checks the control variable m_bCounterNeedUpdateInGUI
and updates the representation in the GUI. The control variable is then reset to enable multiple triggering for the run time.
If multiple counters with larger Web-IOs are used, these must be added here.
To reset the counts shown in the application back to 0, we use Clear commands. This is accomplished individually for specific counter states:
void CWebIODlg::OnBnClickedClear0() {
m_rcData.m_bAbleToRecvDataFromExtern = FALSE;
m_rcData.m_stLookingFor = "clear0";
m_ccConnect.DefineSetClearRequest( "0" );
if( m_ccConnect.SendDataViaSocket() == FALSE ) {
SendGotError();
}
}
void CWebIODlg::OnBnClickedClear1() {
m_rcData.m_bAbleToRecvDataFromExtern = FALSE;
m_rcData.m_stLookingFor = "clear1";
m_ccConnect.DefineSetClearRequest( "1" );
if( m_ccConnect.SendDataViaSocket() == FALSE ) {
SendGotError();
}
}
void ProcessingInputOutput::DefineSetClearRequest( CString stIDNumber ) {
m_rcData.m_stDataRequest = "GET /counterclear" + stIDNumber + "?PW="
+ m_rcData.m_stPassword + "&";
}
But we can also reset all the Web-IO counts in total:
void CWebIODlg::OnBnClickedClearall() {
m_rcData.m_bAbleToRecvDataFromExtern = FALSE;
m_rcData.m_stLookingFor = "clear";
m_ccConnect.DefineClearAllRequest();
if( m_ccConnect.SendDataViaSocket() == FALSE ) {
SendGotError();
}
}
void ProcessingInputOutput::DefineClearAllRequest() {
m_rcData.m_stDataRequest = "GET /counterclear?PW="
+ m_rcData.m_stPassword + "&";
}
Receiving data from the Web-IO
Process and display the received data:- All commands and requests to the Web-IO are acknowledged with a reply string. The replies have a specific structure depending on the type.
- All reply strings are finished off with a 0 byte.
- A distinction is made between the reply from an individual output or input and one from all outputs and inputs. The same applies to counter states as well as commands for resetting them.
- Queries which are directed to special inputs/outputs are acknowledged with "inputx; ON/OFF" or "outputx; ON/OFF"
- Queries which are directed to all inputs/outputs are acknowledged with "input; hex value" or "output; hex value"
- Queries directed to special counters are acknowledged with "counterx; decimal counter state"
- Queries directed to all counters are acknowledged with "counter; decimal counter state 0; decimal counter state 1; ..."
- Queries for clearing the counters are acknowledged the same as for querying the counter states.
For special counter states:
"counterx; decimal counter state"
For all counter states:
"counter; decimal counter state 0; decimal counter state 1; ..." - To receive such a message in the application the member function
OnReceive()
is called. In this function the reply from the Web-IO is processed and the processing functions are determined using control variables as already known.
In preparation for RemoveDelimiterAndSaveIntoArray()
a ";" (semicolon) is appended to the received reply string from the Web-IO and the array in which it it stored is terminated with "0" zero.
With OnReceive() corresponding functions
The following member functions are responsible for correct processing of the various values which the Web-IO sends us. This could be, among other things, the blunt splitting and reading of the strings, as well as the logical AND operation on the hexadecimal formatted user data.
void Connection::OnReceive( int nErrorCode ) {
int const default_buflen{ 512 };
int recvbuflen{ default_buflen };
char recvbuf[default_buflen];
memset( recvbuf, 0, sizeof( recvbuf ) );
m_rcData.m_stRecvbuf.Empty();
m_iResult = Receive( recvbuf, recvbuflen, 0 );
if( m_iResult == SOCKET_ERROR ) {
m_iResult = GetLastError();
m_stError.Format( "OnReceive failed, error %d", m_iResult );
m_rcData.m_myStatBar.SetText( m_stError, 0, 0 );
}
else if( m_iResult > 0 ) {
recvbuf[m_iResult] = ’\0’;
strcat_s( recvbuf, ";" );
m_rcData.m_stRecvbuf = recvbuf;
RemoveDelimiterAndSaveIntoArray( ";" );
ExtractValuesAndShortenArray();
ChooseRightOperation();
if( !( m_iResult = PostMessageA(
m_rcData.m_hMainWindow, WM_PLEASE_UPDATE_GUI,
m_rcData.m_bCounterNeedUpdateInGUI, NULL ) ) ) {
m_iResult = GetLastError();
m_stError.Format( "OnReceive PostMessage failed, error %d", m_iResult );
m_rcData.m_myStatBar.SetText( m_stError, 0, 0 );
}
}
}
In order to be better able to iterate using the answer string, this member function reads from ";" (semicolon) separator to separator, copies each part and saves the strings in an array under m_aAnswerStringArray
.
void ProcessingInputOutput::RemoveDelimiterAndSaveIntoArray( CString stDel ) {
int iStartPos = 0;
int iEndPos = m_rcData.m_stRecvbuf.Find( stDel );
int i = 0;
CString stCurrentStringPiece;
for( int iStringLength = 0; iStringLength < m_rcData.m_stRecvbuf.GetLength();
iStringLength += stCurrentStringPiece.GetLength() + 1 ) {
stCurrentStringPiece = m_rcData.m_stRecvbuf.Mid( iStartPos, iEndPos - iStartPos );
m_aAnswerStringArray.Add( stCurrentStringPiece );
iStartPos = iEndPos + strlen( stDel );
iEndPos = m_rcData.m_stRecvbuf.Find( stDel, iStartPos );
i += 1;
}
}
Since m_aAnswerStringArray
may contain very different values in various positions depending on whether the Web-IO is configured to send its own name as well as IP address (regardless of the user data), we must filter out the user data that are important to us.
Since the format for how the data are sent does not itself change, we can determine the position of the data which are of interest to us using the preceding keywords such as "counter". At the end of this member function we have filled m_aShortenedValueOnlyArray
only with user data, such as [0001, 0002,4,1]
void ProcessingInputOutput::ExtractValuesAndShortenArray() {
for( int i = 0; i < m_aAnswerStringArray.GetSize();) {
if( m_aAnswerStringArray[i] == "input" ||
m_aAnswerStringArray[i] == "output" ) {
m_rcData.m_aShortenedValueOnlyArray.Add(
m_aAnswerStringArray[i + 1]
);
m_aAnswerStringArray.RemoveAt( i );
}
else if( m_aAnswerStringArray[i] == "counter" ) {
m_rcData.m_aShortenedValueOnlyArray.Add(
m_aAnswerStringArray[i + 1]
);
m_rcData.m_aShortenedValueOnlyArray.Add(
m_aAnswerStringArray[i + 2]
);
m_aAnswerStringArray.RemoveAt( i );
}
else if( m_aAnswerStringArray[i] == "counter0" ) {
m_rcData.m_aShortenedValueOnlyArray.Add(
m_aAnswerStringArray[i + 1]
);
m_aAnswerStringArray.RemoveAt( i );
}
else if( m_aAnswerStringArray[i] == "counter1" ) {
m_rcData.m_aShortenedValueOnlyArray.Add(
m_aAnswerStringArray[i + 1]
);
m_aAnswerStringArray.RemoveAt( i );
}
else {
m_aAnswerStringArray.RemoveAt( i );
}
}
}
As per the previously manipulated control vartiables the final operations are applied to the respective case and the values from the m_aShortenedValueOnlyArray
linked with the m_rcData
object.
void ProcessingInputOutput::ChooseRightOperation() {
if( m_rcData.m_bAbleToRecvDataFromExtern ) {
ProcessRequestAfterLookingForAllInputs( 0 );
m_rcData.m_aShortenedValueOnlyArray.RemoveAll();
return;
}
if( m_rcData.m_stLookingFor == "counter" ||
m_rcData.m_stLookingFor == "clear" ) {
ProcessRequestAfterLookingForAllCounter( 0 );
m_rcData.m_aShortenedValueOnlyArray.RemoveAll();
}
else if( m_rcData.m_stLookingFor == "counter0" ||
m_rcData.m_stLookingFor == "clear0" ) {
ProcessRequestAfterLookingForCounter0Only( 0 );
m_rcData.m_aShortenedValueOnlyArray.RemoveAll();
}
else if( m_rcData.m_stLookingFor == "counter1" ||
m_rcData.m_stLookingFor == "clear1" ) {
ProcessRequestAfterLookingForCounter1Only( 0 );
m_rcData.m_aShortenedValueOnlyArray.RemoveAll();
}
else if( m_rcData.m_stLookingFor == "allout" ) {
ProcessRequestAfterLookingForAllInputs( 0 );
ProcessRequestAfterLookingForAllOutputs( 1 );
ProcessRequestAfterLookingForAllCounter( 2 );
m_rcData.m_aShortenedValueOnlyArray.RemoveAll();
}
else if( m_rcData.m_stLookingFor == "output" ) {
ProcessRequestAfterLookingForAllOutputs( 0 );
m_rcData.m_aShortenedValueOnlyArray.RemoveAll();
}
else {
// Do some ErrorHandling here
}
}
Once the correct operation has been determined, the user data are now linked with m_rcData
. Then the control variables are reset to the standard values. If both the inputs and the outputs are processed, the logical AND operation must be performed.
void ProcessingInputOutput::ProcessRequestAfterLookingForCounter0Only( int StartingIndex ) {
m_rcData.m_stCounter0 = m_rcData.m_aShortenedValueOnlyArray[StartingIndex];
m_rcData.m_bAbleToRecvDataFromExtern = TRUE;
m_rcData.m_bCounterNeedUpdateInGUI = TRUE;
}
void ProcessingInputOutput::ProcessRequestAfterLookingForCounter1Only( int StartingIndex ) {
m_rcData.m_stCounter1 = m_rcData.m_aShortenedValueOnlyArray[StartingIndex];
m_rcData.m_bAbleToRecvDataFromExtern = TRUE;
m_rcData.m_bCounterNeedUpdateInGUI = TRUE;
}
void ProcessingInputOutput::ProcessRequestAfterLookingForAllCounter( int StartingIndex ) {
m_rcData.m_stCounter0 = m_rcData.m_aShortenedValueOnlyArray[StartingIndex];
StartingIndex += 1;
m_rcData.m_stCounter1 = m_rcData.m_aShortenedValueOnlyArray[StartingIndex];
m_rcData.m_bAbleToRecvDataFromExtern = TRUE;
m_rcData.m_bCounterNeedUpdateInGUI = TRUE;
}
void ProcessingInputOutput::ProcessRequestAfterLookingForAllInputs( int StartingIndex ) {
m_stRawInputOutput = m_rcData.m_aShortenedValueOnlyArray[StartingIndex];
m_rcData.m_bIsThisMentForInput = TRUE;
BinaryANDing( m_stRawInputOutput );
}
void ProcessingInputOutput::ProcessRequestAfterLookingForAllOutputs( int StartingIndex ) {
m_stRawInputOutput = m_rcData.m_aShortenedValueOnlyArray[StartingIndex];
m_rcData.m_bIsThisMentForInput = FALSE;
BinaryANDing( m_stRawInputOutput );
}
In order for a directly understandable state of a particular input/output to result from the hex formatted value, the represented value must be logically ANDed with the power of 2.
The result for each place is either a value of 0 or != 0. (Result <0 -> set result to 1)
Basically 0 = OFF, 1 = ON.
To reliably convey the information and be able to control the checkboxes behind IDC_INPUT0
, IDC_INPUT1
, IDC_OUTPUT0
, IDC_OUTPUT1
according to their status, the 4 values, for example [0,1,1,0] are saved an array with their own data structure m_IODataStruct.aIOValues
.
void ProcessingInputOutput::BinaryANDing( CString m_stRawInputOutput ) {
int iResult = 0;
int iInOutAsDez = atoi( m_stRawInputOutput );
int iCurrentInput = 0;
unsigned int uiPotenz = 0;
for( int i = 0; i < m_rcData.m_iHowMuchInputsDoWeHave; i++ )
{
uiPotenz = 1 << i; // 2^0;2^1,2^2,2^3
int iBitStatus = iInOutAsDez & uiPotenz;
if( iBitStatus > 0 ) {
iBitStatus = 1;
}
if( m_rcData.m_bIsThisMentForInput ) {
m_IODataStruct.aIOValues[i] = iBitStatus;
}
else {
m_IODataStruct.aIOValues[i + 2] = iBitStatus;
}
iCurrentInput += 1;
}
if( !( iResult = PostMessageA(
m_rcData.m_hMainWindow, WM_SEND_INPUT_OUTPUT_STATUS, NULL, NULL ) ) ) {
iResult = GetLastError();
m_stError.Format( "BinaryANDing PostMessage failed, error %d", iResult );
m_rcData.m_myStatBar.SetText( m_stError, 0, 0 );
}
}
We have a total of 4 values (2x outputs, 2x inputs), which is why we address the values in chronological order of their associated operating elements and change the status from checked to unchecked or vice-versa. In the application this means the set inputs and outputs are represented by the checkbox.
LRESULT CWebIODlg::ProcessCheckBoxStatus( WPARAM wParam, LPARAM lParam ) {
CheckDlgButton( IDC_INPUT0,
( m_ccConnect.m_IODataStruct.aIOValues[0] == 0 ) ? BST_UNCHECKED : BST_CHECKED
);
CheckDlgButton( IDC_INPUT1,
( m_ccConnect.m_IODataStruct.aIOValues[1] == 0 ) ? BST_UNCHECKED : BST_CHECKED
);
CheckDlgButton( IDC_OUTPUT0,
( m_ccConnect.m_IODataStruct.aIOValues[2] == 0 ) ? BST_UNCHECKED : BST_CHECKED
);
CheckDlgButton( IDC_OUTPUT1,
( m_ccConnect.m_IODataStruct.aIOValues[3] == 0 ) ? BST_UNCHECKED : BST_CHECKED
);
m_rcData.m_bAbleToRecvDataFromExtern = TRUE;
m_rcData.m_bIsThisMentForInput = TRUE;
return 1;
}
So that we can be sure that all the values correspond to the correct status even though the application is closed, in OnBnClickedConnect()
after a successful connection we call ValidateDataAtStart()
. The member function ensures that we initially send a /GET allout command to the Web-IO and request all the information as well as then update the GUI accordingly. This request takes place only after a connection is successfully opened to the Web-IO.
void CWebIODlg::ValidateDataAtStart() {
OnEnChangePollingtext();
m_rcData.m_bAbleToRecvDataFromExtern = FALSE;
m_rcData.m_stLookingFor = "allout";
m_ccConnect.DefineGetAllOutRequest();
if( m_ccConnect.SendDataViaSocket() == FALSE ) {
SendGotError();
}
}
void ProcessingInputOutput::DefineGetAllOutRequest() {
m_rcData.m_stDataRequest = "GET /allout?PW="
+ m_rcData.m_stPassword + "&";
}
Polling
Cyclical polling of particular valuesIn order to avoid having to manually update the components while the application is running, you can use the polling procedure. If you enter a valid integer value in the IDC_POLLINGTEXT
, the 3 checkboxes important for the polling procedure are activated: IDC_POLLING0
, IDC_POLLING1
& IDC_POLLINGCOUNTER
.By setting the check mark you can trigger the respective polling process in the amount of the entered integer (in ms). The individual polling procedures are automatic reading of the outputs, inputs or counters.
A separate timer is initialized for each polling variant. A directly assigned timer ID is used for this purpose. If the checkmark is then removed from the checkbox, the timer is also cleared again.
Getting the user input:
void CWebIODlg::OnEnChangePollingtext() {
GetDlgItemText( IDC_POLLINGTEXT, m_stBuffer );
m_rcData.m_iPolling = atoi( m_stBuffer );
if( m_rcData.m_iPolling <= 0 ) {
m_rcData.m_myStatBar.SetText(
"Please enter a valid intervall for polling!", 0, 0
);
GetDlgItem( IDC_POLLING0 )->EnableWindow( FALSE );
GetDlgItem( IDC_POLLING1 )->EnableWindow( FALSE );
GetDlgItem( IDC_POLLINGCOUNTER )->EnableWindow( FALSE );
}
else if( m_rcData.m_iPolling >= 1 ) {
m_rcData.m_stStatBar.Format(
_T( "Range changed to %i ms!" ), m_rcData.m_iPolling
);
m_rcData.m_myStatBar.SetText( m_rcData.m_stStatBar, 0, 0 );
GetDlgItem( IDC_POLLING0 )->EnableWindow( TRUE );
GetDlgItem( IDC_POLLING1 )->EnableWindow( TRUE );
GetDlgItem( IDC_POLLINGCOUNTER )->EnableWindow( TRUE );
}
}
Timer for the outputs:
void CWebIODlg::OnBnClickedPolling0() {
IsDlgButtonChecked( IDC_POLLING0 ) ? SetTimer( 1, m_rcData.m_iPolling, NULL ) :
KillTimer( 1 );
}
Timer for the inputs:
void CWebIODlg::OnBnClickedPolling1() {
IsDlgButtonChecked( IDC_POLLING1 ) ? SetTimer( 2, m_rcData.m_iPolling, NULL ) :
KillTimer( 2 );
}
Timer for the counters:
void CWebIODlg::OnBnClickedPollingcounter() {
IsDlgButtonChecked( IDC_POLLINGCOUNTER ) ? SetTimer( 3, m_rcData.m_iPolling, NULL ) :
KillTimer( 3 );
}
Here the OnTimer)_
function overloaded from CDialogEx
is used. The time event just reported is captured and assigned to a unique action.
void CWebIODlg::OnTimer( UINT nIDEvent ) {
if( nIDEvent == 1 ) OnBnClickedReadall0();
if( nIDEvent == 2 ) OnBnClickedReadall1();
if( nIDEvent == 3 ) OnBnClickedReadall();
CDialogEx::OnTimer( nIDEvent );
}
The entire program is made available on this page, including some additional files and functions not mentioned here. The MFC application under Visual C++ generates an overhead which forms the graphical frame but is not directly linked to the functionality. The functions and variables that have been presented on this page are implemented in the frames generated by Visual C++ and then work as a unit with the graphical interface.
The sample program supports all common functions of the Web-IO in command string mode, optimized for the Web-IO 2x Digital Input, 2x Digital Output PoE. For the other Web-IO models you may have to adapt the program. Additional program examples for socket programming can be found on the tool pages for the Web-IO. A detailed description for the socket interface of the Web-IO Digital models can be found in the reference manual.