W&T collega
Adattatori per TCP/IP, Ethernet, RS-232, RS-485, USB, 20 mA, Fibra ottica di vetro e plastica, http, SNMP, OPC, Modbus TCP, I/O digitale, I/O analogico, ISA, PCI

Applicazione al Web-IO digitale:

Web-IO digitale con Visual C++ controllo e monitoraggio


Per la creazione di applicazioni Windows, Visual C++ rappresenta una delle piattaforme di sviluppo più utilizzate.

Controllo e monitoraggio con C++

Con il seguente esempio di programma C++ potete raffigurare il vostro Web-IO digitale con i suoi input e output in un’applicazione Windows. Inoltre potete collegare gli output del Web-IO.

Non disponete ancora di un Web-IO e desiderate semplicemente provarne il funzionamento come nell’esempio illustrato?

Nessun problema: vi mettiamo a disposizione gratuitamente per 30 giorni il Web-IO digitale 2x input, 2x output. Non dovete far altro che compilare l’ordinazione del campione e vi forniremo il Web-IO in prova in conto aperto. Se ci restituite l’apparecchio entro 30 giorni, vi accreditiamo completamente la fattura.

All’ordinazione del campione

Preparativi

Avete già alimentato con corrente

1. Combinazione dei diversi elementi di comando e oggetti di visualizzazione nella forma Visual C++

Forma Visual C++

2. Definizione nel file header di MySocket e CWeb_IO_ClientDlg

Per memorizzare le immissioni nei campi IDC_IPTEXT, IDC_PORTTEXT, IDC_PASSWORDTEXT e IDC_POLLINGTEXT in variabili locali private della classe CWeb_IO_ClientDlg, nel file header vengono definite alcune variabili.

Per non produrre una nuova variabile per ogni messaggio inviato e ricevuto, nell’header vengono prodotte a tale scopo anche variabili private. Anche la voce nella barra di stato riceve in questo punto una variabile.

 private:
					CString m_ip;
					CString m_message1;
					CString m_message2;
					CString m_rcv;
					CString m_password;
					int m_port;
					int m_range;
				public:
					CStatusBarCtrl*
					m_statusBar; 

Nell’header vengono anche predichiarati alcuni metodi necessari.

public:
					void changeAccess(int i);
					void TryConnect();
					void OnConnect();
					void OnDisconnect();
					void checkpass();
					void OnReceive();
					void OnSend();
					void OnClose();
					void OnTimer(UINT nIDEvent);
					void readAndClearCounter(CString data)

Per inizializzare ora anche un collegamento, è necessaria anche una classe che si occupi dell’invio e della ricezione di dati. A tale scopo è stato creato un file di nome MySocket che proviene dalla classe CAsyncSocket. In tal modo tutti i metodi pubblici di questa classe passano in tale classe e vengono dichiarati di nuovo nell’header. Per poter utilizzare successivamente i metodi della classe CWeb_IO_ClientDlg, nell’header viene dichiarato un oggetto.

public:
					CWeb_IO_ClientDlg*
					m_client;

				public:
					virtual void OnReceive(int nErrorCode);
					virtual void OnSend(int nErrorCode);
					virtual void OnClose(int nErrorCode);
				

3. Definizione in MySocket.cpp

Poiché la classe MySocket proviene dalla classe CAsyncSocket, alcuni dei metodi necessari per un collegamento in esecuzione si sovrapporranno. In tal modo l’applicazione potrà successivamente sia inviare e ricevere perfettamente dati in modo asincrono che reagire anche alla terminazione del collegamento da parte del Web-IO.

void MySocket::OnReceive(int nErrorCode)
				{
					m_client->OnReceive();
					CAsyncSocket::OnReceive(nErrorCode);
				}

				void MySocket::OnSend(int nErrorCode)
				{
					m_client->OnSend();
					CAsyncSocket::OnSend(nErrorCode);
				}

				void MySocket::OnClose(int nErrorCode)
				{
					m_client->OnClose();
					CAsyncSocket::OnClose(nErrorCode);
				}

				void MySocket::OnConnect(int nErrorCode);
				{
					if(nErrorCode == 0)
					{
						m_client->OnConnect();
						CAsyncSocket::OnConnect(nErrorCode);
					}
					else
					{
						m_client->OnDisconnect();
						m_client->m_statusBar->SetText("Error while connecting!", 0, 0);
					}
					} 

4. Avvio del programma

Inizializzazione degli elementi di comando
Il gruppo con gli elementi di comando per il Web-IO viene innanzitutto bloccato per l’uso. Non appena viene realizzato un collegamento, vengono abilitati tutti gli elementi per i quali ha senso l’abilitazione.

Per visualizzare i messaggi in una barra di stato, con questo metodo viene creata una barra di stato. Inoltre la classe trasmette alla classe MySocket in modo tale che entrambe siano collegate tra loro.

Per collegare le variabili o i metodi con i componenti dell’applicazione, si può utilizzare la procedura guidata per le classi di Visual C++, oppure a partire da Visual Studio 2003 la corrispondente funzionalità nelle caratteristiche della classe.

BOOL CWeb_IO_ClientDlg::OnInitDialog()
				{
					CDialog::OnInitDialog();
					// weitere Implementierungen...

					m_statusBar = new CStatusBarCtrl;
					m_statusBar->Create(WS_CHILD|WS_VISIBLE, CRect(0, 0, 0, 0), this, 0);
					clientSocket.m_client = this;
					changeAccess(FALSE);
					SendMessage(DM_SETDEFID,
					IDC_CONNECT);

					return TRUE;
				}
void CWeb_IO_ClientDlg::changeAccess(bool b)
				{
					if(b == TRUE)
					{
					GetDlgItem(IDC_CONNECT)->EnableWindow(FALSE);
					GetDlgItem(IDC_DISCONNECT)->EnableWindow(TRUE);
					GetDlgItem(IDC_OUTPUT0)->EnableWindow(TRUE);
					GetDlgItem(IDC_OUTPUT1)->EnableWindow(TRUE);
					GetDlgItem(IDC_READALL0)->EnableWindow(TRUE);
					GetDlgItem(IDC_READALL1)->EnableWindow(TRUE);
					GetDlgItem(IDC_POLLING0)->EnableWindow(TRUE);
					GetDlgItem(IDC_POLLING1)->EnableWindow(TRUE);
					GetDlgItem(IDC_POLLINGTEXT)->EnableWindow(TRUE);
					GetDlgItem(IDC_POLLINGCOUNTER)->EnableWindow(TRUE);
					GetDlgItem(IDC_READ0)->EnableWindow(TRUE);
					GetDlgItem(IDC_READ1)->EnableWindow(TRUE);
					GetDlgItem(IDC_CLEAR0)->EnableWindow(TRUE);
					GetDlgItem(IDC_CLEAR1)->EnableWindow(TRUE);
					GetDlgItem(IDC_READALL)->EnableWindow(TRUE);
					GetDlgItem(IDC_CLEARALL)->EnableWindow(TRUE);
					}
					else
					{
					GetDlgItem(IDC_CONNECT)->EnableWindow(TRUE);
					GetDlgItem(IDC_DISCONNECT)->EnableWindow(FALSE);
					GetDlgItem(IDC_OUTPUT0)->EnableWindow(FALSE);
					GetDlgItem(IDC_OUTPUT1)->EnableWindow(FALSE);
					GetDlgItem(IDC_INPUT0)->EnableWindow(FALSE);
					GetDlgItem(IDC_INPUT1)->EnableWindow(FALSE);
					GetDlgItem(IDC_READALL0)->EnableWindow(FALSE);
					GetDlgItem(IDC_READALL1)->EnableWindow(FALSE);
					GetDlgItem(IDC_POLLING0)->EnableWindow(FALSE);
					GetDlgItem(IDC_POLLING1)->EnableWindow(FALSE);
					GetDlgItem(IDC_POLLINGTEXT)->EnableWindow(FALSE);
					GetDlgItem(IDC_POLLINGCOUNTER)->EnableWindow(FALSE);
					GetDlgItem(IDC_COUNTERTEXT0)->EnableWindow(FALSE);
					GetDlgItem(IDC_COUNTERTEXT1)->EnableWindow(FALSE);
					GetDlgItem(IDC_READ0)->EnableWindow(FALSE);
					GetDlgItem(IDC_READ1)->EnableWindow(FALSE);
					GetDlgItem(IDC_CLEAR0)->EnableWindow(FALSE);
					GetDlgItem(IDC_CLEAR1)->EnableWindow(FALSE);
					GetDlgItem(IDC_READALL)->EnableWindow(FALSE);
					GetDlgItem(IDC_CLEARALL)->EnableWindow(FALSE);
					}
				}
				

5. Controllo del collegamento

Inizializzazione del collegamento

Indicando l’indirizzo IP del Web-IO nel campo di testo IDC_IPTEXT e la porta 80 nel campo di testo IDC_PORTTEXT può essere creato un collegamento azionando il pulsante IDC_CONNECT. Se non viene inserito alcun indirizzo IP o alcuna porta, compare un messaggio nella barra di stato

void CWeb_IO_ClientDlg::OnClickConnect()
				{
					GetDlgItemText(IDC_IPTEXT, m_ip);
					GetDlgItemText(IDC_PORTTEXT, m_message2);
					sscanf(m_message2, "%d", &m_port);

					if(m_ip == "") {
					m_statusBar->SetText("No IP entered!", 0, 0); }
					else if(m_port == 0) {
					m_statusBar->SetText("No port
					entered!", 0, 0);
					}
					else {
					TryConnect(); }
				}
				
Creazione del collegamento

Per potere creare ora un collegamento TCP, utilizziamo nel nostro esempio un oggetto globale della classe MySocket che mette a disposizione metodi quali Create(), Connect() e Close().

Se dovesse essere stata indicata una porta negativa oppure se la creazione del collegamento non fosse riuscita, viene visualizzato un messaggio.

Poiché non è stato possibile creare un collegamento, anche il socket viene chiuso di nuovo, in modo tale che un nuovo collegamento possa essere riavviato di nuovo senza problemi.

Se tutto ha funzionato, vengono abilitati tutti i componenti che possono essere utilizzati dopo la creazione riuscita e il pulsante IDC_CONNECT viene bloccato, per evitare la creazione di un nuovo collegamento in presenza del collegamento corrente.

void CWeb_IO_ClientDlg::TryConnect()
				{
					if(m_port < 0)
					{
					m_statusBar->SetText("No
					negative port allowed!", 0, 0);
					return;
					}
					clientSocket.Create();
					clientSocket.Connect(m_ip, m_port);
					GetDlgItem(IDC_CONNECT)->EnableWindow(FALSE);
				}
				
Collegamento realizzato

Se il collegamento è stato realizzato, l’applicazione può stabilire una comunicazione con il Web-IO per la quale vengono utilizzati i diversi componenti.

void CWeb_IO_ClientDlg::OnConnect()
				{
					m_statusBar->SetText("Connection made!", 0, 0);
					changeAccess(TRUE);
				}
Disinserzione del collegamento

Il collegamento rimane fino a quando non viene terminato dall’utente facendo clic sul pulsante Disconnect oppure dal Web-IO. Se il collegamento viene terminato, viene visualizzato un messaggio.

void CWeb_IO_ClientDlg::OnClickDisconnect()
				{
					OnDisconnect();
				}

Se il collegamento viene terminato, tutti gli elementi devono essere riportati di nuovo nella loro posizione iniziale, poiché non deve più essere possibile, fare clic sul pulsante IDC_DISCONNECT.

void CWeb_IO_ClientDlg::OnDisconnect()
				{
					clientSocket.Close();
					m_statusBar->SetText("Disconnected!", 0, 0);
					changeAccess(FALSE);
				}

Ora il collegamento è stato di nuovo terminato e l’applicazione viene riportata al suo stato iniziale.

6. Utilizzo e comunicazione del lato client

Non appena viene realizzato un collegamento con il Web-IO, l’utente può inviare comandi al Web-IO utilizzando i corrispondenti elementi del programma.

Impostazione degli output

Gli output del Web-IO possono essere commutati per mezzo delle due caselle di spunta IDC_OUTPUT0 e IDC_OUTPUT1. La casella di spunta è contrassegnata, dopo che vi si è fatto clic sopra, e successivamente innesca un’azione. Per poter inviare una richiesta di informazioni al Web-IO, viene innanzitutto verificato se nel campo di testo IDC_PASSWORDTEXT è stata inserita una password, senza la quale il Web-IO non accetterebbe alcuna richiesta. Se non è stata indicata alcuna password, la richiesta di informazioni viene inviata senza password.

void CWeb_IO_ClientDlg::checkpass()
				{
					GetDlgItemText(IDC_PASSWORDTEXT, m_password);
				}

Nella fase successiva viene verificato se la casella di spunta è già stata impostata oppure no e di conseguenza l’output viene di nuovo ripristinato o impostato.

void CWeb_IO_ClientDlg::OnOutput0()
				{
					checkpass();
					m_message1 = "GET /outputaccess0?PW=" + password + "&State=ON&";
					m_message2 = "GET /outputaccess0?PW=" + password + "&State=OFF&";
					if(IsDlgButtonChecked(IDC_OUTPUT0))
					clientSocket.Send(m_message1, m_message1.GetLength());
					else
					clientSocket.Send(m_message2, m_message2.GetLength());
				}
void CWeb_IO_ClientDlg::OnOutput1()
				{
					checkpass();
					m_message1 = "GET /outputaccess1?PW=" + password + "&State=ON&";
					m_message2 = "GET /outputaccess1?PW=" + password + "&State=OFF&";
					if(IsDlgButtonChecked(IDC_OUTPUT1))
					clientSocket.Send(m_message1, m_message1.GetLength());
					else
					clientSocket.Send(m_message2, m_message2.GetLength());
				}
Interrogazione dello stato degli output/input

Ora lo stato degli output e degli input può anche essere verificato per aggiornare di volta in volta le caselle di spunta nell’applicazione. Il pulsante IDC_READALL0 legge lo stato degli output e il pulsante IDC_READALL1 legge lo stato degli input. Il comando per interrogare lo stato è leggermente diverso da quello per l’impostazione. Pertanto lo stato degli output può essere interrogato con "GET /output?PW=&".

void CWeb_IO_ClientDlg::OnClickReadall0()
				{
					checkpass();
					m_message1 = "GET /output?PW=" + password + "&";
					clientSocket.Send(m_message1, m_message1.GetLength());
				}
void CWeb_IO_ClientDlg::OnClickReadall1()
				{
					checkpass();
					m_message1 = "GET /input?PW=" + password + "&";
					clientSocket.Send(m_message1,
					m_message1.GetLength());
				}
Interrogazione dei counter

Poiché gli episodi in ingresso vengono annotati soltanto nel Web-IO stesso facendo salire il numero sul contatore interno, questo deve anche poter essere interrogato. Il seguente metodo invia una richiesta di informazioni a un determinato counter e richiede una risposta con lo stato corrente del contatore. Dalla risposta del Web-IO viene dedotto il numero del counter e lo stato del relativo conteggio che viene visualizzato nell’applicazione.

void CWeb_IO_ClientDlg::OnClickRead0()
				{
					checkpass();
					m_message1 = "GET /counter0?PW=" + password + "&";
					clientSocket.Send(m_message1, m_message1.GetLength());
				}
void CWeb_IO_ClientDlg::OnClickRead1()
				{
					checkpass();
					m_message1 = "GET /counter1?PW=" + password + "&";
					clientSocket.Send(m_message1, m_message1.GetLength());
				}

Naturalmente con un unico comando possono anche essere interrogati gli stati di tutti i counter.

 void CWeb_IO_ClientDlg::OnClickReadall()
				{
					checkpass();
					m_message1 = "GET /counter?PW=" + password + "&";
					clientSocket.Send(m_message1, m_message1.GetLength());
				}
Azzeramento dei counter

Poiché lo stato del conteggio può essere letto, dovrebbe anche essere possibile riportare il contatore su 0. A tale scopo viene inviato un messaggio al relativo counter che lo azzera.

void CWeb_IO_ClientDlg::OnClickClear0()
				{
					checkpass();
					m_message1 = "GET /counterclear0?PW=" + password + "&";
					clientSocket.Send(m_message1, m_message1.GetLength());
				}
void CWeb_IO_ClientDlg::OnClickClear1()
				{
					checkpass();
					m_message1 = "GET /counterclear1?PW=" + password + "&";
					clientSocket.Send(m_message1, m_message1.GetLength());
				}

Naturalmente è possibile anche azzerare tutti i counter con un comando.

void CWeb_IO_ClientDlg::OnClickClearall()
				{
					checkpass();
					m_message1 = "GET /counterclear?PW=" + password + "&";
					clientSocket.Send(m_message1, m_message1.GetLength());
				}

Poiché gli stati di tutti i counter possono essere letti o azzerati con un unico comando, deve essere implementato un ulteriore metodo che elabora la stringa di risposta del Web-IO e assegna a ogni counter nell’applicazione il suo stato specifico.

void CWeb_IO_ClientDlg::readAndClearCounter(CString data)
				{
					int j = 0;
					CString counter[12];

					for(int i = 0; i < data.GetLength(); i++)
					{
					if(data[i] == ';')
						j++;
					else
						counter[j]+= data[i];
					}

					SetDlgItemText(IDC_COUNTERTEXT0, counter[0]);
					SetDlgItemText(IDC_COUNTERTEXT1, counter[1]);
				}

7. Ricezione dei dati dal Web-IO

Analisi e visualizzazione dei dati ricevuti
  • Tutti i comandi e le richieste al Web-IO vengono confermati con una stringa di risposta. Le risposte hanno una struttura specifica in base al tipo.
  • Si distingue la risposta di un singolo output o di un singolo input e la risposta di tutti gli output e input.
  • Per gli output: output;<valore binario dello stato degli output in formato esadecimale>
  • Per un output specifico: outputx;<ON o OFF>
  • Per gli input: input;<valore binario dello stato degli input in formato esadecimale>
  • Per un input specifico: inputx;<ON o OFF>
  • Quindi segue la stringa di risposta per un counter che appare come segue:
    counterx;<stato del conteggio decimale>
    oppure counter;<stato del conteggio decimale 0 >; <stato del conteggio decimale 1 >; ... se tutti i counter devono essere letti in un’unica volta.
    Tutte le stringe di risposta terminano con 0 byte.
  • Nella nostra applicazione viene richiamato il metodo OnReceive() per la ricezione di un tale messaggio. In questo metodo la risposta del Web-IO viene elaborata.

						void CWeb_IO_ClientDlg::OnReceive()
							{
							clientSocket.Receive(m_rcv.GetBuffer(50),50);
							m_rcv.ReleaseBuffer();

							if(m_rcv.IsEmpty())
							OnDisconnect();
							else if(m_rcv.GetLength() > 1)
								{
								if(m_rcv[0] == 'o')
								{
								int i;
								sscanf(m_rcv.Right(m_rcv.GetLength() - 7), "%x", &i);
								if((i & 1) == 1) CheckDlgButton(IDC_OUTPUT0, BST_CHECKED);
								else             CheckDlgButton(IDC_OUTPUT0, BST_UNCHECKED);
								if((i & 2) == 2) CheckDlgButton(IDC_OUTPUT1, BST_CHECKED);
								else             CheckDlgButton(IDC_OUTPUT0, BST_UNCHECKED);
								}
								if(m_rcv[0] == 'i')
								{
								int i;
								sscanf(m_rcv.Right(m_rcv.GetLength() - 6), "%x", &i);
								if((i & 1) == 1) CheckDlgButton(IDC_INPUT0, BST_CHECKED);
								else             CheckDlgButton(IDC_INPUT0, BST_UNCHECKED);
								if((i & 2) == 2) CheckDlgButton(IDC_INPUT1, BST_CHECKED);
								else             CheckDlgButton(IDC_INPUT1, BST_UNCHECKED);
								}
								if(m_rcv[0] == 'c')
									{
									if(m_rcv[7] == '0')
										SetDlgItemText(IDC_COUNTERTEXT0, m_rcv.Right(m_rcv.GetLength() - 9));
									if(m_rcv[7] == '1')
										SetDlgItemText(IDC_COUNTERTEXT1, m_rcv.Right(m_rcv.GetLength() - 9));
									if(m_rcv[7] == ';')
										readAndClearCounter(m_rcv.Right(m_rcv.GetLength() - 8));
									}
								}
							} 

8. Polling

Interrogazione ciclica di determinati valori
Ora è auspicabile che lo stato di un singolo componente si aggiorni da sé e con ciò che l’applicazione presenti sempre il suo stato corrente. A tale scopo viene utilizzato in questo programma un timer che invia ciclicamente interrogazioni al Web-IO in un intervallo ciclico determinato dell’utente.

A tale scopo può essere immesso per primo un valore intero nel campo IDC_POLLINGTEXT che definisce il tempo in millisecondi per l’interrogazione ciclica. Se non viene inserito alcun valore, il tempo per l’intervallo viene impostato di serie su 1 secondo (1000 ms).

Naturalmente viene intercettato anche il caso in cui l’utente ha inserito un’indicazione senza senso come ad es. un valore di tempo negativo.. Segue subito un messaggio e il valore non viene naturalmente accettato.

void CWeb_IO_ClientDlg::OnChangePollingtext()
				{
					GetDlgItemText(IDC_POLLINGTEXT, m_message2);
					int check;
					sscanf(m_message2, "%d", &check);
					if(check <= 0)
					{
					m_statusBar->SetText("No negative
					value or character allowed!", 0, 0);
					return;
					}
					m_range = check;
					m_statusBar->SetText("Range
					changed!", 0, 0);
				}

Per eseguire anche l’interrogazione ciclica degli stati del Web-IO, definita anche polling, è possibile scegliere tra polling degli output, degli input o dei counter.

Per ogni variante di polling viene inizializzato un proprio timer. Il richiamo del timer è quindi ogni volta "SetTimer(numero del timer, intervallo, ZERO)".

Se si attiva la casella di spunta IDC_POLLING0, il polling viene utilizzato sugli output. Con ciò viene inizializzato un timer, in caso di impostazione. Se la casella di spunta viene azzerata, il timer con il corrispondente numero viene di nuovo azzerato.

void CWeb_IO_ClientDlg::OnPolling0()
				{
					if(IsDlgButtonChecked(IDC_POLLING0))
					SetTimer(1, m_range, NULL);
					else
					KillTimer(1);
				}

Azionando la casella di spunta IDC_POLLING1 il polling viene utilizzato sugli input. Con ciò viene inizializzato anche un nuovo timer che poi viene anche di nuovo azzerato da questo metodo.

void CWeb_IO_ClientDlg::OnPolling1()
				{
					if(IsDlgButtonChecked(IDC_POLLING1))
					SetTimer(2, m_range, NULL);
					else
					KillTimer(2);
				}

Se ora devono essere interrogati con il polling anche i computer, può essere utilizzata a tale scopo la casella di spunta IDC_POLLINGCOUNTER.

void CWeb_IO_ClientDlg::OnPollingcounter()
				{
					if(IsDlgButtonChecked(IDC_POLLINGCOUNTER))
						SetTimer(3,m_range, NULL);
					else
						KillTimer(3);
				}

Ora sono stati inizializzati tre diversi timer che, in base all’intervallo, attivano in determinati lassi di tempo un’azione che tuttavia sinora non viene rilevata. Infatti per rilevare gli eventi deve essere implementato un ulteriore metodo.

In questo metodo l’evento attuale del timer, appena segnalato, viene rilevato e assegnato a una determinata azione.

Come promemoria: nel nostro caso vengono interrogati ciclicamente gli stati degli output, degli input e dei counter in base all’evento.

void CWeb_IO_ClientDlg::OnTimer(UINT nIDEvent)
				{
					if(nIDEvent == 1) OnClickReadall0();
					if(nIDEvent == 2) OnClickReadall1();
					if(nIDEvent == 3) OnClickReadall();
					CDialog::OnTimer(nIDEvent);
				}

L’intero programma viene messo a disposizione su questa pagina dove sono contenuti anche alcuni file e metodi che non sono stati citati qui. L’applicazione MFC in ambiente Visual C++ produce un overhead che crea la struttura grafica ma che non è collegato direttamente alla funzionalità. I metodi e le variabili che sono stati presentati in questa pagina saranno implementati nella struttura prodotta da Visual C++ e funzioneranno come un’unità con l’interfaccia grafica.

Il programma esempio supporta tutte le comuni funzioni del Web-IO nella modalità stringa di comando, ottimizzata per il Web-IO 2x Digital Input, 2x Digital Output PoE. Gli altri modelli Web-IO devono eventualmente essere adattati al programma. Ulteriori esempi di programma per la programmazione socket sono riportati nelle pagine dei tool per il Web-IO. Una descrizione dettagliata sull’interfaccia socket dei modelli Web-IO digitali è riportata nel manuale di riferimento.

Download esempio di programma

Non disponete ancora di un Web-IO e desiderate semplicemente provarne il funzionamento come nell’esempio illustrato?

Nessun problema: vi mettiamo a disposizione gratuitamente per 30 giorni il Web-IO digitale 2x input, 2x output. Non dovete far altro che compilare l’ordinazione del campione e vi forniremo il Web-IO in prova in conto aperto. Se ci restituite l’apparecchio entro 30 giorni, vi accreditiamo completamente la fattura.

All’ordinazione del campione