W&T verbindet
Interfaces für TCP/IP, Ethernet, RS-232, RS-485, USB, 20mA, Glas- und Kunststoff-LWL, http, SNMP, OPC, Modbus TCP, I/O digital, I/O analog, ISA, PCI

Tutorial zum Web-IO Digital;

Web-IO Digital mit Visual C++ steuern und überwachen


Für das Erstellen von Windows-Anwendungen ist Visual C++ eine der meistgenutzten Entwicklungsplattformen.

Mit C++ steuern und überwachen

Mit dem folgenden C++ Programmbeispiel können Sie Ihr Web-IO Digital mit seinen Inputs und Outputs in einer Windows-Anwendung abbilden. Darüber hinaus können Sie die Outputs des Web-IO schalten.


Vorbereitungen


Zusammenstellen der verschiedenen Bedienelemente und Anzeigeobjekte im Visual-C++-Form

Visual C++ Form

Definition in den Header-Dateien

Definition MyDlgData.h

Um eine Mehrfach-Definition zu vermeiden und die Membervariablen an das m_rcData-Objekt koppeln zu können, welches durch die Klassen gereicht wird, definieren wir in MyDlgData.h einige Membervariablen. Zusätzlich ist hier auch das von CStatusBarCtrl abgeleitete Objekt m_myStatBar definiert.

Um Zugriff auf weitreichende Memberfunktionen in Verbindung mit dem Datenobjekt zu ermöglichen, erbt diese Klasse von CObject. Da diese Klasse hauptsächlich als Schnittstelle dient, werden hier keinerlei Memberfunktionen definiert.

						 
							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 von Connection.h

Die Aufgabe dieser Klasse ist die zuverlässige Erstellung einer TCP-Verbindung über eine Socket-Schnittstelle. Zusätzlich beeinhaltet sie Memberfunktionen zum Senden und Empfangen von Daten. Nachfolgend stellen wir die vorab definierten Membervariablen sowie Memberfunktionen aus:

						
							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 );
						};
						
					

Diese Klasse erbt von ProcessingInputOutput, um die Funktionalitäten der später verarbeitenden Memberfunktionen nutzen zu können. Darüber hinaus erbt sie von CSocket, da die Klasse die Grundlage der Verbindung über Socket-Schnittstellen via UDP oder TCP bildet.
Die Verbindung findet asynchron statt, also erfolgt der Datenaustausch ohne vorab definierten Takt und die Prozesse werden nebenläufig verarbeitet.
Deswegen machen wir uns Callback-Funktionen zu nutze, die ausgelöst werden, wenn ein Receive-Event (Daten können empfangen werden) oder ein Close-Event (Die Verbindung wurde getrennt) gemeldet wird.
Der jeweilige Trigger entscheidet dann über den Programmablauf.

Definition von ProcessingInputOutput.h

Damit die vom Web-IO erhaltenen Daten korrekt verarbeitet werden, bedarf es einer eigenen Klasse, derer Definition wir hier nachfolgend beschreiben.


					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;
					};
					

Diese Klasse ist ausschließlich für das Verarbeiten der vom Datenobjekt übergebenen Daten zuständig.
Dazu nutzen wir die Verknüpfung der Klasse mit dem m_rcData-Objekt.
Neben den Memberfunktionen, welche die Antwort-Strings spalten, kopieren, speichern oder durch sie durch iterieren um Operationen anzuwenden, erstellen wir unter MyIODataStruct eine Struktur, die es uns ermöglicht eine größere Menge Nutzdaten komfortabel zu verwalten.

Definition von CWebIODlg.h

Um die grafische Darstellung des Dialogfensters sowie dessen Inhalts kümmert sich CWebIODlg. Diese Klasse bedient sich unter anderem von CDialogEx und wird aus MyDlgData mit m_rcData verknüpft und definiert selbst das Objekt m_ccConnect aus der Connection-Klasse.


						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{};
						};
					

Hier befinden sich hauptsächlich Memberfunktionen, die als Callback aufgerufen werden, sollte ein Button geklickt, ein Textfeld verändert, oder etwas erstellt werden wie das Dialogfenster selbst. Dazu werden einige Memberfunktionen aus CWebDialogEx überladen.

Programmstart

Einrichten der Bedienelemente

Die Bedienelemente selbst können relativ einfach durch den Klassen-Assistenten, der in VisualStudio integriert ist, erstellt werden. Durch ihn lassen sich ebenfalls mühelos z.B.: Buttons mit Memberfunktionen oder Kontrollvariablen verknüpfen. Um unsachgemäße Benutzung durch den Nutzer vorzubeugen, werden einige Bedienelemente nach bestimmten Funktionsaufrufen und am Start partiell eingeschränkt. Dies ist unter ChangeAccess() definiert.


					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 );
				}
				

Um die Statusleiste zu initialisieren und die Bedienelemente passend für den Nutzer vorbereiten können, verwenden wir die überladene OnInitDialog()-Funktion aus CDialogEx. Die dort enthaltenen Operationen werden einmal pro Start aufgerufen. Zusätzlich nutzen wir die Memberfunktion, um für andere Memberfunktionen den Handle auf das Main-Window an das Datenobjekt m_rcData zu binden.


				BOOL CWebIODlg::OnInitDialog() {
					CDialogEx::OnInitDialog();
					InitializeStatusBar(); 
					m_rcData.m_hMainWindow = m_hWnd; 
					ChangeAccess( TRUE );
					return TRUE; 
				}
				

Nachfolgend die Erstellung der Statusleiste.


				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 );
				}
				

Damit sich die Statusleiste auch nach dem Verändern der Fenstergröße anpasst, wurde hier ebenfalls die OnSize()-Funktion überladen und meldet die Veränderung an die Statusleiste.


				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 ) 
						);
					}
				}
				

Die Verbindungskontrolle

Einleiten der Verbindung

Durch Eingabe der IP-Adresse des Web-IO in das Textfeld IDC_IPTEXT und dem Port 42280 in das Textfeld IDC_PORTTEXT kann durch Betätigung des Buttons IDC_CONNECT eine Verbindung aufgebaut werden. Zusätzlich kann, je nach Konfiguration, unter IDC_PASSWORDTEXT das Passwort eingetragen werden. Standardmäßig ist kein Passwort hinterlegt. Sollte die Verbindung erfolgreich erstellt worden sein, werden durch ChangeAccess() alle wichtigen Buttons, Checkboxen und Textfelder freigeschaltet. In jedem Fall wird der Nutzer über die Statusleiste auf den aktuellen Stand gebracht.

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 );
						}
					}
					
Verbindungsaufbau

Um eine Verbindung herzustellen callen wir CreateSocket() sowie ConnectSocket() und verwenden dazu die vorab geprüften Werte aus den Textfeldern IDC_IPTEXT und IDC_PORTTEXT plus das m_ccConnect-Objekt welches den Zugriff auf Memberfunktionen der Connection-Klasse bereitstellt.
Die Memberfunktionen Create() und Connect() sind aus CSocket abgeleitet.


					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;
						}
					}
					
Verbindung kommt zustande

Ist die Verbindung zustande gekommen, ist die Anwendung in der Lage mit dem Web-IO den Datenaustausch zu beginnen. Dafür wird die Benutzeroberfläche dementsprechend freigegeben.
Der Nutzer bekommt über die Statusleiste eine Meldung über die aktuellen Verbindungsstatus.


					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 );
						}
					}
					
Trennen der Verbindung

Die Verbindung bleibt solange bestehen, bis sie vom Nutzer durch Klick auf den Disconnect-Button oder durch das Web-IO selbstständig beendet wird. Hier wird die von CSocket abgeleitete Memberfunktion Close() verwendet. Beim Trennen der Verbindung wird der Nutzer ebenfalls durch die Statusleiste benachrichtigt.


					void CWebIODlg::OnBnClickedDisconnect() {
						m_ccConnect.Close();
								m_rcData.m_myStatBar.SetText(
								"Disconnected successfully.", 0, 0
								);
						ChangeAccess( TRUE );
					}
					

Sollte das Web-IO die Verbindung auflösen, wird dies über den Filedeskriptor "CLOSE" erfasst und die Callback-Funktion OnClose() wird aufgerufen. Diese nutzt eine Windows-Message, um die Benutzeroberfläche dementsprechend wieder zu sperren und den Nutzer durch eine MessageBox darüber in Kenntnis zu setzen.


					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 );
						}
					}
					

Es wird unter anderem derselbe Code ausgeführt, als hätten wir auf den Disconnect-Button geklickt.
Anschließend ist die Anwendung wieder auf dem Stand, wie vor dem Verbindungsabbruch und es kann eine neue Verbindung hergestellt werden.


					LRESULT CWebIODlg::ConnectionBrokeUp( WPARAM wParam, LPARAM lParam ) {
						OnBnClickedDisconnect();
						return 1;
					}
					

Bedienung und Kommunikation von Client-Seite

Setzen der Outputs

Um die Outputs am Web-IO zu schalten, muss ein Click in die jeweilige Checkbox gemacht werden, welche dann die bestimmte Aktion auslöst. Im folgenden Beispiel sehen wir die Memberfunktionen um IDC_OUTPUT0 und/oder IDC_OUTPUT1 zu setzen. Nach dem Click in die Checkbox, werden Kontroll-Variablen passend zu dem Case angepasst und der m_stLookingFor-String manipuliert, denn dieser wird zu späterem Zeitpunkt benötigt, um bestimmte Operationen durchzuführen.
Folgend die Kontroll-Variablen:

  • m_bAbleToRecvDataFromExtern
  • m_bIsThisMentForInput
  • m_stLookingFor
  • m_bCounterNeedUpdateInGUI
Diese beeinflussen Maßgeblich den Ablauf der Anwendung und ermöglichen die asynchrone Verarbeitung von Inputs als auch das zuverlässige Entscheiden welche Operation die Richtige ist.


					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();
						}

					}
					

Im nächsten Schritt wird geprüft, ob die Checkbox bereits gesetzt worden ist, oder nicht. Im Anschluss kann der GET-Befehl mithilfe von DefineSetOutputRequest() passend formuliert werden. Damit ist der Befehl vorbereitet und kann via SendDataViaSocket() versendet werden.


					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&";
						}
					}
					

Um die Anwendung für mehrere verschiedene Web-IOs nutzbar zu machen, erhält jeder einzigartige GET-Befehl eine eigene Funktion mit einem fixen Kommando-String unter m_stDataRequest und Befehle, die Outputs, Inputs und Counter gezielt ansteuern oder löschen sollen.

Output/Input-Status abfragen

Neben dem spezifischen Setzen der Outputs, können wir auch die Zustände der Outputs sowie Inputs in Gänze abfragen. Der Click auf IDC_READALL0 liest die Outputs und IDC_READALL1 die Inputs aus. Der Befehl unterscheidet sich leicht zu dem, der zum Setzen verwendet wird. Grundsätzlich wird passend zur jeweiligen Operation ein Kommando-String geformt, Kontroll-Variablen werden angepasst und somit der Weg der Nutzdaten nach dem Erhalt durch OnReceive() zu späterer Stelle bestimmt.


					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 + "&";
					}
					
Counter abfragen

Da eintreffende Input-Ereignisse nur im Web-IO selbst vermerkt werden und somit der innere Zähler hochgezählt wird, sollte dieser auch abgefragt werden können. Die Memberfunktion OnBnClickedRead0() oder OnBnClickedRead1() sendet eine Anfrage an das Web-IO und fordert den aktuellen Stand des jeweiligen Zählers an. Aus der Antwort des Web-IO wird später der Zählstand des jeweiligen Counters entnommen und in der Anwendung angezeigt.


					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 darstellen

Um die Counter wahrheitsgemäß anhand der soeben abgefragten Werte in unserer Anwendung darzustellen, formulieren wir Memberfunktionen, die das mithilfe von Windows-Messages erledigen.
Die Trennung der Klassen untereinander bleibt somit erhalten. Nachdem wir den passenden Kommando-String an das Web-IO gesendet haben, erhalten wir selbstverständlich eine Antwort. Diese beeinhaltet die Zählerstände, ggf. mit zusätzlichen Informationen zum Web-IO selbst, die individuell konfiguriert werden können.
Diese Antwort wird von der weiter unten beschriebenen OnReceive()-Funktion gefangen und sendet, nachdem die Verarbeitung stattgefunden hat, eine Windows-Message an UpdateGUI() (Letzter Schritt in der Verarbeitung des Strings) -> Die Nutzdaten sind nun zuverlässig gespeichert und werden mit dem m_rcData-Objekt verknüpft, um es in UpdateGUI() nutzbar zu machen:

 
					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 );
					}
					

Empfangen der 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;
						}
					}
					

Nachdem UpdateGUI() die Windows-Message erhalten hat, prüft sie die Kontroll-Variable m_bCounterNeedUpdateInGUI und aktualisiert die Darstellung im GUI. Anschließend wird die Kontroll-Variable wieder zurückgesetzt um mehrfaches Auslösen zur Laufzeit zu ermöglichen.
Sollten mehrere Counter bei größeren Web-IOs abgefragt werden, müssen diese dann unter anderem, hier hinzugefügt werden.

Counter zurücksetzen

Um die in der Anwendung dargestellten Zählerstände wieder auf 0 zurückzusetzen, nutzen wir Clear-Befehle. Dies ist für spezifsche Zählerstände einzeln machbar:


					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 + "&";
					}
					

Aber wir können auch in Summe alle Zählerstände des Web-IOs zurücksetzen:


					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 + "&";
					}
					

Datenempfang vom Web-IO

Auswerten und Anzeigen der empfangenen Daten:
  • Alle Kommandos und Anfragen an das Web-IO werden mit einem Antwort-String quittiert. Dabei haben die Antworten je nach Typ einen spezifischen Aufbau.
  • Alle Antwort-Strings sind mit einem 0-Byte abgeschlossen.
  • Man unterscheidet zwischen der Antwort von einem einzelnen Output bzw. Input und der aller Outputs und Inputs. Gleiches gilt bei Zählerständen sowie bei Befehlen zum Zurücksetzen dieser.
  • Anfragen die sich an spezielle Inputs / Outputs richten, werden mit "inputx; ON/OFF" oder "outputx; ON/OFF" quittiert
  • Anfragen die sich an an alle Inputs/ Outputs richten, werden mit "input; Hexadezimal dargestellter Wert" oder "output; Hexadezimal dargestellter Wert" quittiert
  • Anfragen die sich an spezielle Counter richten, werden mit "counterx; dezimaler Zählerstand" quittiert
  • Anfragen die sich an alle Counter richten, werden mit "counter; dezimaler Zählerstand 0; dezimaler Zählerstand 1; ..." quittiert
  • Anfragen zum Löschen der Zählerstände werden gleich denen der Anfrage der Zählerstände quittiert.
    Bei speziellen Zählerständen:
    "counterx; dezimaler Zählerstand"
    Bei allen Zählerständen:
    "counter; dezimaler Zählerstand 0; dezimaler Zählerstand 1; ..."
  • In der Anwendung wird zum Empfang einer solchen Nachricht die Memberfunktion OnReceive() aufgerufen. In dieser Funktion wird die Antwort des vom Web-IO verarbeitet, bzw. die verarbeitenden Funktionen werden mithilfe von Kontroll-Variablen, wie bereits bekannt, bestimmt.

Vorbereitend für RemoveDelimiterAndSaveIntoArray() wird dem erhaltenen Antwort-String vom Web-IO ein ";" {semikolon} angehängt und das Array in dem es gespeichert ist, wird mit "0" Null terminiert.

Mit OnReceive() korrespondierende Funktionen

Die folgenden Memberfunktionen sind für die korrekte Verarbeitung, der verschiedenen Werte, die uns das Web-IO sendet, zuständig. Das könnte unter anderem das stumpfe Teilen und Auslesen der Strings, als auch das logische UND-verknüpfen der hexadezimal formatierten Nutzdaten sein.


					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 );
							}
						}
					}
					

Um später besser durch den Antwort-String iterieren zu können, liest diese Memberfunktion von ";" {semikolon} Trennzeichen zu Trennzeichen, kopiert jenen Teil und speichert die Strings in einem Array unter 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;
						}
					}
					

Da sich jetzt in m_aAnswerStringArray ganz unterschiedliche Werte an verschiedenen Positionen befinden können, je nach dem ob das Web-IO so konfiguriert ist den eigenen Namen, sowie IP-Adresse zu senden, (unabhängig der Nutzdaten) müssen wir die für uns wichtigen Nutzdaten herausfiltern.
Da das Format wie die Daten versendet werden sich selbst nicht ändert, können wir die Position der für uns interessanten Daten anhand der vorabstehenden Keywords wie z.B.: "counter", festmachen. Am Ende dieser Memberfunktion haben wir m_aShortenedValueOnlyArray mit ausschließlich Nutzdaten befüllt wie z.B.: [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 );
							}
						}
					}
					

Gemäß den vorab manipulierten Kontroll-Variablen werden nun die letztendlich finalen Operationen auf den jeweiligen Case angewendet und die Werte aus dem m_aShortenedValueOnlyArray mit dem m_rcData-Objekt verknüpft.

 
					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
						}
					}
					

Nach dem die richtige Operation bestimmt worden ist, werden die Nutzdaten nun mit m_rcData verknüpft. Anschließend werden die Kontrollvariablen auf die Standardwerte zurückgesetzt. Im Falle der Verarbeitung der Inputs sowie Outputs muss noch die logische UND-Verknüpfung erfolgen.


					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 ); 
					}
					

Damit aus dem hexadezimal formatiertem Wert ein für uns direkt verständlicher Zustand eines bestimmten Inputs / Outputs wird, muss der dargestellte Wert mit der Potenz von 2 logisch UND-verknüpft werden.
Das Ergebnis ist für jede Stelle entweder ein Wert von 0 oder != 0. (Ergebnis <0 -> Ergebnis auf 1 setzen)
Grundsätzlich gilt 0 = OFF, 1 = ON.

Um die Information zuverlässig zu vermitteln und ggf. die Checkboxen hinter IDC_INPUT0, IDC_INPUT1, IDC_OUTPUT0, IDC_OUTPUT1 entsprechend ihres Status steuern zu können, werden die 4 Werte z.B.: [0,1,1,0] als Array einer eigens erstellten Datenstruktur m_IODataStruct.aIOValues gespeichert.

 
					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 );
						}
					}
					

Wir haben insgesamt 4 Werte (2x Outputs, 2x Inputs), deswegen adressieren wir die Werte in chronologischer Reihenfolge ihrer zugehörigen Bedienelemente und verändern den Status von Checked auf Unchecked oder anders herum. Das heißt in der Anwendung werden die gesetzten Inputs und Outputs durch die Checkbox dargestellt.


					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;
					}
					

Damit wir sicher sein können, dass auch trotz geschlossener Anwendung beim nächsten Start alle Werte dem korrekten Status entsprechen, callen wir in OnBnClickedConnect() nach dem wir uns erfolgreich verbunden haben ValidateDataAtStart(). Die Memberfunktion sorgt dafür dass wir inital einen /GET allout-Befehl an das Web-IO senden und alle Informationen abfragen, sowie dann das GUI entsprechend aktualisieren. Diese Abfrage findet nur nach dem erfolgreichen Verbinden mit dem Web-IO statt.

 
					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

Zyklisches Abfragen bestimmter Werte

Um bei der laufenden Anwendung nicht händisch die Komponenten aktualisieren zu müssen, kann man das Polling-Verfahren nutzen. Wenn man einen gültigen Integer-Wert in das IDC_POLLINGTEXT einträgt, aktivieren sich die 3 für das Polling-Verfahren wichtigen Checkboxen: IDC_POLLING0, IDC_POLLING1 & IDC_POLLINGCOUNTER. Durch das Setzen des Häkchens kann man in Höhe des eingegeben Integers (in ms) den jeweiligen Polling-Prozess anstoßen. Die einzelnen Polling-Verfahren sind das automatische Auslesen von den Outputs, Inputs oder der Counter.

Für jede Polling-Variante wird ein eigener Timer initialisiert. Dazu wird eine direkt zugewiesene Timer-ID verwendet. Wenn das Häkchen aus der Checkbox wieder entfernt wird, wird auch der Timer wieder zerstört.

Fangen der Benutzereingabe:


					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 für die Outputs:


					void CWebIODlg::OnBnClickedPolling0() {

						IsDlgButtonChecked( IDC_POLLING0 ) ? SetTimer( 1, m_rcData.m_iPolling, NULL ) :
							KillTimer( 1 );
					}
					

Timer für die Inputs:


					void CWebIODlg::OnBnClickedPolling1() {

						IsDlgButtonChecked( IDC_POLLING1 ) ? SetTimer( 2, m_rcData.m_iPolling, NULL ) :
							KillTimer( 2 );
					}
					

Timer für die Counter:


					void CWebIODlg::OnBnClickedPollingcounter() {

						IsDlgButtonChecked( IDC_POLLINGCOUNTER ) ? SetTimer( 3, m_rcData.m_iPolling, NULL ) :
							KillTimer( 3 );
					}
					

Hier wird die aus CDialogEx überladene OnTimer()-Funktion verwendet. Es wird das jeweilige Event des Timers, der sich gerade meldet, gefangen und einer eindeutigen Aktion zugeordnet.


					void CWebIODlg::OnTimer( UINT nIDEvent ) {
						if( nIDEvent == 1 ) OnBnClickedReadall0();
						if( nIDEvent == 2 ) OnBnClickedReadall1();
						if( nIDEvent == 3 ) OnBnClickedReadall();
						CDialogEx::OnTimer( nIDEvent ); 
					}
					

Das gesamte Programm wird auf dieser Seite zur Verfügung gestellt, wobei es einige hier nicht erwähnte Dateien und Funktionen zusätzlich enthält. Die MFC Anwendung unter Visual C++ erzeugt einen Overhead, der den graphischen Rahmen bildet, aber mit der Funktionalität nicht direkt in Verbindung steht. Die Funktionen und Variablen, die Ihnen auf dieser Seite präsentiert worden sind, werden in den von Visual C++ erzeugten Rahmen implementiert und arbeiten dann als Einheit mit der graphischen Oberfläche.


Das Beispielprogramm unterstützt alle gängigen Funktionen des Web-IO im Kommando-String-Modus, optimiert für das Web-IO 2x Digital Input, 2x Digital Output PoE. Für die anderen Web-IO-Modelle müssen ggf. Anpassung am Programm vorgenommen werden. Weitere Programmbeispiele zur Socket-Programmierung finden Sie auf den Tool-Seiten zum Web-IO. Eine detaillierte Beschreibung zur Socket-Schnittstelle der Web-IO Digital Modelle finden Sie im Referenzhandbuch.

Programmbeispiel herunterladen


Produkte