Wiesemann & Theis GmbH

Networking, sensors and interface technology for industry, office and IT

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.

Control and monitor with C++

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


Assemble the various operating elements and display objects in the Visual C++ form

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.

Definition of ProcessingInputOutput.h

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.

Definition of CWebIODLg.h

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 elements

The 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.

Query output/input status

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.

Reset counter

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 values

In 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.

Download program example


Products


^