Tutorial sobre Web-IO digital;
Dirigir y controlar Web-IO digital con Visual C++
Para la creación de aplicaciones Windows Visual C++ es una de las plataformas de desarrollo más utilizadas.
Con el ejemplo siguiente de programa C++ puede representar su Web-IO Digital con sus Inputs y Outputs en una aplicación Windows. Además de ello puede conectar los Outputs del Web-IO.
Preparativos
- Conectar la tensión para Web-IO y cablear las IO
- Conectar el Web-IO a la red
- Asignar direcciones de IP
En Web-IO, dentro de la sección Vías de comunicación >> Zócalo-API activar los Zócalos TCP-ASCII y habilitar las salidas a conmutar
Recopilación de los diferentes elementos de manejo y objetos de visualización en formulario Visual C++

Definición en los archivos de cabecera (header)
Definición MyDlgData.h Para evitar repeticiones de una definición y poder acoplar las variables de miembro en el objeto m_rcData
que será pasado por las clases, definimos en MyDlgData.h
algunas variables de miembro. Además, el objeto CStatusBarCtrl
derivado de m_myStatBar
.
Para hacer posible el acceso a amplias funciones de miembro en conexión con el objeto de datos, esa clase hereda de CObject
. Puesto que esa clase sirve principalmente de interfaz, no se define aquí ninguna función de miembro.
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;
};
Definición de Connection.h
La tarea de esta clase es establecer una conexión TCP segura a través de una interfaz de zócalo. Además incluye funciones de miembro para Enviar y Recibir datos. A continuación exponemos las variables de miembro y las funciones de miembro definidas previamente:
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 );
};
Esta clase hereda de ProcessingInputOutput
, para poder utilizar la finalidad de las funciones de miembro a procesar más tarde. Por otra parte, hereda de CSocket
, porque la clase es la base para la conexión a través de interfaces de zócalo vía UDP o TCP.
La conexión es asíncrona, por tanto el intercambio de datos tiene lugar sin un ciclo previamente definido y los procesos son ejecutados en paralelo.
Por eso hacemos uso de funciones de devolución de llamada (callback) que se activan cuando sucede un evento Receive (se puede recibir datos) o un evento Close (corte de la conexión).
El trigger respectivo decide entonces el desarrollo del programa.
Para que los datos obtenidos del Web-IO sean tratados correctamente, se necesita una clase propia, cuya definición describimos a continuación.
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;
};
Esta clase se encarga únicamente del tratamiento de los datos transmitidos por el objeto de datos.
Para ello utilizamos el vínculo de la clase con el objeto m_rcData
.
Además de las funciones de miembro que dividen, copian y guardan las cadenas de respuestas o iteran a través de ellas para aplicar operaciones; creamos en MyIODataStruct
una estructura que nos permite administrar una gran cantidad de datos útiles con comodidad.
De la representación gráfica de la ventana de diálogo y su contenido se encarga CWebIODlg
. Esta clase se sirve, entre otros, de CDialogEx
, está vinculada desde MyDlgData
con m_rcData
y define ella misma el objeto m_ccConnect
de la clase Connection
.
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{};
};
Aquí se encuentran sobre todo las funciones de miembro que se activan como callback en caso de que se pulse un botón, se modifique un campo de texto o se elabore algo como la ventana de diálogo misma. Para ello se trasladan algunas funciones de miembro desde CWebDialogEx
.
Arranque de programa
Instalar los elementos de manejoCrear los elementos de mando propiamente dichos es relativamente fácil con el asistente de clases integrado en VisualStudio. Con él es muy fácil también vincular, por ejemplo: botones con funciones de miembro o variables de control. Para prevenir un uso inadecuado por parte de los usuarios, después de un número determinado de veces en las que se haya activado las funciones se restringe parcialmente algunos elementos de mando al iniciar. Esto está definido en 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 );
}
Para inicializar la barra de estado y poder preparar los elementos de mando ajustados al usuario empleamos la función OnInitDialog()
cargada desde CDialogEx
. Las operaciones contenidas en ella se activan una vez por cada inicio. Utilizamos la función de miembro además para vincular el handle de la ventana principal con el objeto de datos m_rcData
para otras funciones de miembro.
BOOL CWebIODlg::OnInitDialog() {
CDialogEx::OnInitDialog();
InitializeStatusBar();
m_rcData.m_hMainWindow = m_hWnd;
ChangeAccess( TRUE );
return TRUE;
}
A continuación la creación de la barra de estado.
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 );
}
Para que la barra de estado se ajuste también cuando se modifica el tamaño de la ventana se ha cargado aquí también la función OnSize()
que notifica el cambio a la barra de estado.
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 )
);
}
}
El control de conexión
Introducir la conexión La conexión se arranca entrando la dirección IP del Web-IO en el campo de texto IDC_IPTEXT
y el puerto 42280
en el campo de texto IDC_PORTTEXT
y pulsando el botón IDC_CONNECT
se puede establecer una conexión. Además, según la configuración, se puede introducir en IDC_PASSWORDTEXT
la contraseña. No se ha consignado ninguna contraseña por defecto. Cuando se establece correctamente la conexión, con ChangeAccess()
se activan todos los botones, casillas de verificación y campos de texto importantes. En todo caso la barra de estado informa al usuario del estado actual.
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 );
}
}
La conexión
Para establecer una conexión llamamos a CreateSocket()
así como ConnectSocket()
utilizando para ello los valores previamente verificados de los campos de texto IDC_IPTEXT
y IDC_PORTTEXT
más el objeto m_ccConnect
que pone a disposición el acceso a las funciones de miembro de la clase Connection
.
Las funciones de miembro Create()
y Connect()
están derivadas de 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;
}
}
Se establece la conexión
Una vez establecida la conexión, la aplicación está en situación de empezar el intercambio de datos con Web-IO. Para ello se habilita el entorno de usuario como corresponda.
En la barra de estado se muestra un mensaje sobre el estado actual de la conexión.
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 );
}
}
Separar la conexión
La conexión se mantiene hasta que el usuario la finaliza pulsando el botón de desconectar o hasta que Web-IO la finaliza automáticamente. Aquí se emplea, derivada de CSocket
, la función de miembro Close()
. En la barra de estado también se informa al usuario cuando se interrumpe la conexión.
void CWebIODlg::OnBnClickedDisconnect() {
m_ccConnect.Close();
m_rcData.m_myStatBar.SetText(
"Disconnected successfully.", 0, 0
);
ChangeAccess( TRUE );
}
Cuando Web-IO finaliza la conexión, esto es registrado por el descriptor de archivos "CLOSE" y se activa la función OnClose()
Callback. Esta utiliza un mensaje de Windows para bloquear de nuevo el entorno de usuario como corresponda e informar de ello al usuario a través de una 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 );
}
}
Entre otros, se ejecuta el mismo código que si hubiésemos pulsado el botón Disconnect
.
A continuación, la aplicación se encuentra en el mismo estado que antes del corte de la conexión y se puede establecer una nueva conexión.
LRESULT CWebIODlg::ConnectionBrokeUp( WPARAM wParam, LPARAM lParam ) {
OnBnClickedDisconnect();
return 1;
}
Manejo y comunicación por parte del cliente
Poner Outputs Para conmutar las salidas en Web-IO hay que marcar la correspondiente casilla de verificación, que luego activa la acción determinada. En el siguiente ejemplo vemos las funciones de miembro para aplicar IDC_OUTPUT0
o IDC_OUTPUT1
. Después de marcar la casilla de verificación se adaptan las variables de control al caso y se manipula la cadena m_stLookingFor
pues esta será necesaria más tarde para ejecutar determinadas operaciones.
A continuación, las variables de control:
m_bAbleToRecvDataFromExtern
m_bIsThisMentForInput
m_stLookingFor
m_bCounterNeedUpdateInGUI
Estas determinan notablemente el desarrollo de la aplicación y permiten el tratamiento asíncrono de las entradas, así como la decisión fiable de qué operación es la correcta.
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();
}
}
En el siguiente paso se comprueba si están marcadas o no las casillas de verificación. Acto seguido se puede formular el comando GET con ayuda de DefineSetOutputRequest()
. Así el comando está preparado y puede ser enviado vía 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&";
}
}
Para que la aplicación pueda ser utilizada por varios Web-IO diferentes, cada comando GET único contiene una función propia con una cadena de comando fija en m_stDataRequest
y comandos que deben activar o borrar salidas, entradas y contadores concretos.
Además de la aplicación específica de salidas, también podemos consultar los estados de las salidas y de las entradas en su totalidad. El clic en IDC_READALL0
lee las salidas y IDC_READALL1
las entradas. El comando se diferencia ligeramente del utilizado para aplicar. Básicamente, para una operación concreta, se genera una cadena de comandos, se adaptan las variables de control y de ese modo se determina el camino de los datos útiles tras la recepción por OnReceive()
hasta el punto posterior.
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 + "&";
}
Solicitar Contadores
Puesto que los eventos que suceden en la entrada solo con registrados por el contador interno del Web-IO mismo, se debería poder consultar también este. La función de miembro OnBnClickedRead0()
o OnBnClickedRead1()
envía una consulta al Web-IO y solicita el estado actual del contador respectivo. De la respuesta del Web-IO se extraer luego el estado del contador respectivo y se muestra este en la aplicación.
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();
}
}
Representación de contadores
Para ofrecer una representación verídica de los contadores en nuestra aplicación en base a los datos solicitados, formulamos funciones de miembro que realizamos con ayuda de Windows-Messages.
De ese modo se mantiene la separación de las clases entre sí. Una vez enviada la cadena de comandos correspondiente al Web-IO, recibimos naturalmente una respuesta. Esta contiene los estados de los contadores, dado el caso con datos adicionales sobre el Web-IO mismo, que pueden ser configurados individualmente.
Esa respuesta es captada por la función OnReceive()
descrita más abajo y, después de que haya tenido lugar el tratamiento, envía un mensaje de Windows a UpdateGUI()
(último paso en el tratamiento de la cadena) -> ahora los datos útiles están guardados con seguridad y serán vinculados al objeto m_rcData
para hacerlos útiles en 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 );
}
Recepción del mensaje de Windows:
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;
}
}
Una vez que UpdateGUI()
ha recibido el mensaje de Windows, comprueba la variable de control m_bCounterNeedUpdateInGUI
y actualiza la presentación en GUI. Luego se restablece la variable de control para que pueda volver a ser activada más veces durante la ejecución.
Si es necesario consultar varios contadores en los casos de Web-IO grandes, estos tienen que ser agregados, entre otros puntos, aquí.
Para poner de nuevo a 0 los contadores representados en la aplicación utilizamos comandos Clear. Esto puede hacerse por separado para estados específicos de los contadores:
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 + "&";
}
Pero también podemos poner a cero todos los estados de los contadores del Web-IO en su totalidad:
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 + "&";
}
Recepción de datos del Web-IO
Evaluación y presentación de los datos recibidos:- Todos los comandos y todas las consultas al Web-IO son confirmados con una cadena de respuesta. Las respuestas tienen estructuras específicas en función del tipo.
- Todos los Strings de respuesta se finalizan con un Byte 0.
- Las respuestas son diferentes para una única salida o entrada o para todas las salidas y entradas. Y lo mismo es aplicable a los estados de los contadores y los comandos de puesta a cero.
- Las consultas dirigidas a entradas o salidas especiales son confirmadas con "inputx; ON/OFF" o "outputx; ON/OFF"
- Las consultas dirigidas a todas las entradas o salidas son confirmadas con "input; valor hexadecimal representado" o "output; valor hexadecimal representado"
- Las consultas dirigidas a contadores especiales son confirmadas con "counterx; valor decimal del contador"
- Las consultas dirigidas a todos los contadores son confirmadas con "counter; valor decimal 0; valor decimal 1; ..."
- La confirmación de las consultas para borrar los contadores es igual que la de consulta de los estados de los contadores.
Para estados de contadores especiales:
"counterx; valor decimal del contador"
Para todos los estados de contadores:
"counter; valor decimal 0; valor decimal 1; ..." - Para la recepción de tales mensajes se activa en la abre la función de miembro
OnReceive()
. Como ya es sabido, en esa función se determina la respuesta de las funciones a procesar o ya procesadas por Web-IO con ayuda de variables de control.
Como preparación para RemoveDelimiterAndSaveIntoArray()
se añade un ";" {punto y coma} a la cadena de respuesta del Web-IO y el array en el que está guardado termina con un cero "0".
Funciones que se corresponden con OnReceive()
Las siguientes funciones de miembro son responsables del correcto tratamiento de los diferentes valores que nos envía el Web-IO. Podría tratarse, por ejemplo, de piezas obtusas y la lectura de cadenas, pero también el enlace lógico Y de los datos útiles en formato hexadecimal.
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 );
}
}
}
Para poder iterar luego mejor por la cadena de respuesta, esa función de miembro lee a partir del ";" {punto y coma} de carácter de separación en carácter de separación, copia cada parte y guarda las cadenas en un array bajo 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;
}
}
Puesto que ahora en m_aAnswerStringArray
pueden encontrarse valores muy diferentes en posiciones distintas, según haya sido configurado el Web-IO para enviar el nombre propio o la dirección de IP, (independientemente de los datos útiles), tenemos que filtrar los datos útiles que sean importantes para nosotros.
Puesto que el formato no cambia según cómo sean enviados los datos, podemos determinar la posición de los datos que nos interesen en base a las palabras clave precedentes como, por ejemplo: "counter". Al final de esa función de miembro tenemos m_aShortenedValueOnlyArray
exclusivamente datos útiles como, por ejemplo: [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 );
}
}
}
Según las variables de control previamente manipuladas, se aplican ahora las operaciones finales definitivas al caso correspondiente y se vincula los valores de m_aShortenedValueOnlyArray
con el objeto m_rcData
.
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
}
}
Una vez determinada la operación correcta, se vincula los datos útiles con m_rcData
. Para finalizar se restablecen las variables de control con los valores estándar. En el caso de un tratamiento de entradas y salidas tiene que seguir aún el enlace lógico con Y.
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 );
}
Para que un valor hexadecimal se convierta en un estado de entradas o salidas comprensible para nosotros, el valor representado tiene que estar vinculado con la potencia de 2 por el enlace lógico Y.
El resultado es para cada posición bien un valor de 0 o ! = 0. (Resultado <0 -> poner resultado a 1)
Básicamente rige que 0 = OFF, 1 = ON.
Para transmitir la información de forma fiable y, dado el caso, poder controlar las casilla de verificación detrás de IDC_INPUT0
, IDC_INPUT1
, IDC_OUTPUT0
, IDC_OUTPUT1
según su estado, se guardan 4 valores, por ejemplo: [0,1,1,0] como array de una estructura propia creada 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 );
}
}
En total tenemos 4 valores (2 salidas y 2 entradas), por eso dirigimos los valores en orden cronológico a sus elementos de mando asociados y modificamos el estado de chequeado a no chequeado o viceversa. Esto significa que en la aplicación se representan las entradas y salidas aplicadas mediante casillas de verificación.
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;
}
Para poder estar seguros de que, en el siguiente inicio, todos los valores se correspondan con el estado correcto a pesar de estar cerrada la aplicación, abrimos OnBnClickedConnect()
después de habernos conectado correctamente ValidateDataAtStart()
. La función de miembro se encarga de que enviamos al inicio un comando /GET allout al Web-IO y consultemos toda la información, así como de que actualicemos el GUI según corresponda. Esa consulta tiene lugar únicamente después de una correcta conexión con el 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
Solicitud cíclica de determinados valoresPara no tener que actualizar manualmente los componentes con la aplicación en funcionamiento se puede utilizar el proceso de polling. Introduciendo un valor entero válido en IDC_POLLINGTEXT
se activan las 3 casillas de verificación relevantes para el proceso de polling: IDC_POLLING0
, IDC_POLLING1
& IDC_POLLINGCOUNTER
. Marcando la casilla se puede activar el proceso de polling respectivo a la altura del valor entero introducido (en ms). Los diferentes procesos de polling son la lectura automática de las salidas, las entradas y los contadores.
Para cada variante del polling se inicializa un temporizador propio. Para ello se emplea un ID de temporizador asignado directamente. El temporizador se destruye de nuevo cuando se retira la marca de la casilla de verificación.
Captura de las entradas de usuario:
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 );
}
}
Temporizador para las salidas:
void CWebIODlg::OnBnClickedPolling0() {
IsDlgButtonChecked( IDC_POLLING0 ) ? SetTimer( 1, m_rcData.m_iPolling, NULL ) :
KillTimer( 1 );
}
Temporizador para las entradas:
void CWebIODlg::OnBnClickedPolling1() {
IsDlgButtonChecked( IDC_POLLING1 ) ? SetTimer( 2, m_rcData.m_iPolling, NULL ) :
KillTimer( 2 );
}
Temporizador para los contadores:
void CWebIODlg::OnBnClickedPollingcounter() {
IsDlgButtonChecked( IDC_POLLINGCOUNTER ) ? SetTimer( 3, m_rcData.m_iPolling, NULL ) :
KillTimer( 3 );
}
Aquí se utiliza, descargada de CDialogEx
, la función OnTimer()
. Se captura el evento respectivo del temporizador que avisa en ese momento y es asignado a una acción unívoca.
void CWebIODlg::OnTimer( UINT nIDEvent ) {
if( nIDEvent == 1 ) OnBnClickedReadall0();
if( nIDEvent == 2 ) OnBnClickedReadall1();
if( nIDEvent == 3 ) OnBnClickedReadall();
CDialogEx::OnTimer( nIDEvent );
}
En esta página se pone a disposición el programa completo, aunque contiene además algunos archivos y funciones que no han sido comentados aquí. La aplicación MFC bajo Visual C++ genera un overhead que conforma el marco gráfico pero no está conectado directamente con la funcionalidad. Las funciones y variables que les hemos mostrado en esta página son implementadas en el marco generado por Visual C++ y luego trabajan como unidad con el entorno gráfico.
El ejemplo de programación asiste todas las funciones corrientes del Web-IO en el modo String de comando, optimado para el Web-IO 2x Digital Input, 2x Digital Output PoE. Para los otros modelos Web-IO tienen que realizarse en caso necesario adaptaciones en el programa. Otros ejemplos de programa para la programación del zócalo los encontrarán en las páginas de herramientas al Web-IO. Una descripción detallada de la interfaz del zócalo de los modelos Web-IO digitales la encontrarán en el manual de referencia.