Documentazione sistema di acquisizione Il sistema permette il reperimento dei dati ambientali dalle varie centraline connesse alla rete della serra. Esso è composto da un software c# in esecuzione sul server del sistema (in cui è in esecuzione anche il motore di database). La scelta di utilizzare un software C# per trasferire dati nel server è stata obbligata dall’impossibilità di alcune centraline di salvare autonomamente i dati sul database; in particolare per comunicare con il PLC si deve sfruttare il server http che ha al suo interno. In questo modo possiamo gestire il sistema di salvataggio dei dati in modo centralizzato (un unico orologio, intervallo slegato dalla centralina). Le centraline devono mettere a disposizione una pagina web aggiornata dinamicamente con i valori attuali, secondo uno schema e dei tag predefiniti. Esempio di pagina web fornita dalle centraline __________________________________________________________________________ <html> <head> <meta charset="utf-8"> <title>Pascal-Garibaldi</title> </head> <body> temperatura =<tem>28</tem><br/> umidità =<umi>76</umi><br/> luminosità =<lum>1234</lum><br/> </body> </html> ___________________________________________________________________________ Struttura dei dati nel software Il software, prima di prelevare le misurazioni dalle centraline, legge i dati necessari dal database per l’interazione con quest’ultime(ad esempio URL e tag) e le salva in una struttura temporanea interna al programma. Tramite queste informazioni scarica quindi la pagina web da tutte le centraline ad intervalli regolari, preleva le misurazioni attraverso i tag e le inserisce nel database. I tag sono volutamente case unsesitive, cioè non sensibili alle maiuscole. Carico le impostazioni dal file di configurazione Aspetto l’ora di lettura Carico dal database le informazioni delle centraline e dei punti di misura Leggo i valori dalle pagine web di ogni centralina e li salvo temporaneamente Inserisco nel database i valori letti precedentemente App.config Le impostazioni del software si possono modificare dal file di configurazione ([nome_exe].exe.config) presente nella cartella dell’eseguibile. In questo file possono essere modificate e/o aggiunte stringhe di connessione al Database oltre alle configurazioni prettamente del software come l’intervallo di campionamento, il tempo di timeout per scaricare la pagina web, ed il nome del file di log. Esempio del file di config __________________________________________________________________________ <?xml version="1.0" encoding="utf-8"?> <configuration> <startup> <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" /> </startup> <connectionStrings> <add name="ConnString" connectionString="Server=172.16.39.160; Database=serra; User Id=sa; Password=sql2014-15" /> </connectionStrings> <appSettings> <!-- Intervallo di campionamento in secondi --> <add key="intervallo" value="5" /> <add key="log_filename" value="C:\Serra\Logs\log-serra-scansione-centraline.txt" /> <add key="time-out-web-request" value="10" /> </appSettings> </configuration> __________________________________________________________________________ Struttura del file di log [Data] - [Centralina o Punto Misura o Database]: Id - Errore: [Messaggio errore] Esempio di un log 24/04/2015 15:06:50 - Centralina: 1 - Errore: Errore del server remoto: (404) Non trovato. 24/04/2015 15:09:55 - Centralina: 2 - Errore: Timeout dell'operazione. 27/04/2015 12:33:15 - Centralina: 3 - Errore: Errore del server remoto: (407) Richiesta autenticazione proxy. Comandi disponibili: Esc - Chiude il programma correttamente I - Stampa a video le informazioni temporanee di Centraline e PuntiMisura Listato del programma /* * ITT Pascal - Cesena * Garden of things * Sistema di acquisizione e consultazione dati ambientali della serra * * Programma: Scansione centraline * Versione: 1.0.0.6 * Ultima modifica: 27/04/2015 * * Autori: Edoardo Barbieri, Lorenzo Mondani, Emanuele Pancisi */ /* * ITT Pascal - Cesena * Garden of things * Sistema di acquisizione e consultazione dati ambientali della serra * * Programma: Scansione centraline * Versione: 1.0.0.8 * Ultima modifica: 08/05/2015 * * Autori: Edoardo Barbieri, Lorenzo Mondani, Emanuele Pancisi */ using using using using using using using using using using using using System; System.Collections.Generic; System.Linq; System.Text; System.Threading.Tasks; System.Net; System.Data.SqlClient; System.Data; System.Configuration; System.Threading; System.IO; System.Runtime.InteropServices; namespace client_http_msqls { class Program { static string vn = "1.0.0.8"; //aggiunto log_writer.Flush(); static string versione = vn + " (Ultima modifica: 08/05/2015)"; static int intervallo_secondi = int.Parse(ConfigurationManager.AppSettings["intervallo"]); static int time_out = int.Parse(ConfigurationManager.AppSettings["time-out-webrequest"]); //static string indirizzo_web_server = ConfigurationManager.AppSettings["indirizzo_web_server"]; static string conn_database = ConfigurationManager.ConnectionStrings["ConnString"].ToString(); static List<Centralina> Centraline = new List<Centralina>(); static StreamWriter log_writer = new StreamWriter(ConfigurationManager.AppSettings["log_filename"].ToString(), true); static SqlConnection sql_connetion; #region Exit Handler [DllImport("Kernel32")] private static extern bool SetConsoleCtrlHandler(EventHandler handler, bool add); private delegate bool EventHandler(CtrlType sig); static EventHandler _handler; enum CtrlType { CTRL_C_EVENT = 0, CTRL_BREAK_EVENT = 1, CTRL_CLOSE_EVENT = 2, CTRL_LOGOFF_EVENT = 5, CTRL_SHUTDOWN_EVENT = 6 } private static bool Handler(CtrlType sig) { if(sig != CtrlType.CTRL_C_EVENT) log_writer.Close(); return true; } #endregion static void Main(string[] args) { //Evento che mi gestisce la chiusura _handler += new EventHandler(Handler); SetConsoleCtrlHandler(_handler, true); //safe = senza eccezioni Console.Title = "Scansione centraline " + vn; //stampo le variabili dell'app.config e la versione Console.WriteLine("Versione: " +versione); Console.WriteLine("Intervallo: " + intervallo_secondi+ " secondi"); Console.WriteLine(conn_database); Console.WriteLine(); //Ciclo principale while (true) { log_writer.Flush(); if (!AspettaIntervallo()) { //è stato premuto escape Console.WriteLine("Chiusura programma..."); Thread.Sleep(1000); break;//Esco dal ciclo se viene premuto Escape } DateTime orario_lettura = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second); Console.ForegroundColor = ConsoleColor.Cyan; Console.WriteLine("Nuovo tentativo di lettura: " + orario_lettura.ToLongTimeString()); Console.ResetColor(); /*Apro la connessione con il server SQL*/ if (!ConnettiDB(orario_lettura)) { continue; }//safe /*Carico i dati dei punti misura e delle centraline*/ if (!CaricaInformazioni(orario_lettura)) { continue; }//safe foreach (Centralina c in Centraline) //Per ogni centralina scarico la pagina web e faccio le insert nel database { try { //webclient per il download della pagina html MyWebClient client = new MyWebClient(); MyWebClient.SecTimeOut = time_out; string contenuto = client.DownloadString(c.Indirizzo_web); contenuto = contenuto.ToLower(); /* Esempio di contenuto: <tem>28.8</temp></br> <umi>30</umi> */ foreach(PuntoMisura pm in c.PuntiMisura) { InserisciLettura(contenuto, pm, orario_lettura); //safe } } catch(Exception ex) //In caso di eccezione la salvo nel file di log { log_writer.WriteLine(orario_lettura + " - Centralina: " + c.Id + " - Errore: " + ex.Message); log_writer.Flush(); Console.WriteLine(orario_lettura + " - Centralina: " + c.Id + " Errore: " + ex.Message); } } //Chiudo la connessione con il db sql_connetion.Close(); //aspetto almeno un secondo per evitare di effettuare un'altra lettura nello stesso orario Thread.Sleep(1000); Console.WriteLine(); } //Chiudo lo scrittore del file di log log_writer.Close(); } /// <summary> /// Aspetto l'intervallo di tempo prestabilito /// </summary> /// <returns>Ritorna true se è stato premuto il tasto Escape</returns> static bool AspettaIntervallo()//safe { //Aspetto fino alla data tonda dell'intervallo richiesto while (((int)DateTime.Now.TimeOfDay.TotalSeconds) % intervallo_secondi != 0) { if (Console.KeyAvailable) { ConsoleKeyInfo k = Console.ReadKey(); Console.CursorLeft = 0; if (k.Key == ConsoleKey.Escape) return false; else Comandi(k); } Thread.Sleep(10); } //Torna true se bisogna continuare, false se si è spinto exit return true; } /// <summary> /// Eseguo i comandi [Ricarica = R, ] /// </summary> /// <param name="k">Tasto premuto</param> static void Comandi(ConsoleKeyInfo k)//safe { if (k.Key == ConsoleKey.I) { StampaInformazioniCentraline(); } } /// <summary> /// Stampa le informazioni delle varie centraline caricate /// </summary> static void StampaInformazioniCentraline()//safe { Console.ForegroundColor = ConsoleColor.White; Console.WriteLine("Centraline:"); foreach (Centralina c in Centraline) { Console.WriteLine("\n" + c + "\n"); Console.WriteLine("\tPuntiMisura:\n"); foreach (PuntoMisura p in c.PuntiMisura) Console.WriteLine("\t" + p + "\n"); } Console.ResetColor(); } /// <summary> /// Mi connetto al database /// </summary> static bool ConnettiDB(DateTime orario_lettura)//safe { //Console.WriteLine("Connessione al database..."); try { sql_connetion = new SqlConnection(conn_database); sql_connetion.Open(); return true; } catch(Exception ex) { Console.ForegroundColor = ConsoleColor.Red; Console.WriteLine(orario_lettura + " - Database - Errore: " + ex.Message); log_writer.WriteLine(orario_lettura + " - Database - Errore: " + ex.Message); log_writer.Flush(); Thread.Sleep(1000); Console.ResetColor(); sql_connetion = null; return false; } } /// <summary> /// Carico le informazioni necessarie /// </summary> static bool CaricaInformazioni(DateTime orario_lettura)//safe { /* Carico le centraline */ SqlDataReader reader = null; SqlCommand cmd = null; try { Centraline.Clear(); cmd = new SqlCommand("SELECT * FROM View_Configurazione"); cmd.Connection = sql_connetion; reader = cmd.ExecuteReader(); while (reader.Read()) { int id_cen = int.Parse(reader["IdCentralina"].ToString()); int find = Centraline.FindIndex(c => c.Id == id_cen); if(find==-1) { Centralina c = new Centralina(int.Parse(reader["IdCentralina"].ToString()), reader["Centralina"].ToString(), "", reader["Url"].ToString(), reader["TagCentralina"].ToString()); Centraline.Add(c); } find = Centraline.FindIndex(c => c.Id == id_cen); PuntoMisura pm = new PuntoMisura(int.Parse(reader["IdPunto"].ToString()), Centraline[find].Id, reader["Sensore"].ToString(), int.Parse(reader["IdGrandezza"].ToString()), reader["Posizione"].ToString(), reader["TagPunto"].ToString().ToLower(), reader["Grandezza"].ToString(), reader["UnitaMisura"].ToString(), ""/*reader["Formato"].ToString()*/); Centraline[find].PuntiMisura.Add(pm); } return true; } catch (Exception ex) { Console.WriteLine(orario_lettura + " - Database - Errore: " + ex.Message); log_writer.WriteLine(orario_lettura + " - Database - Errore: " + ex.Message); log_writer.Flush(); Thread.Sleep(1000); sql_connetion.Close(); return false; } finally { if (reader != null) reader.Close(); if (cmd != null) cmd.Dispose(); } } /// <summary> /// Inserisce i dati nel database partendo dal contenuto della pagina web /// </summary> /// <param name="contenuto">Contenuto della pagina scaricata dalla centralina</param> /// <param name="pm">Verranno estrapolati ed inseriti i dati di questo punto di misura (dal contenuto)</param> /// <param name="now">Data di inserimento</param> /// <returns>Ritorna true se l'operazione va a buon fine</returns> static bool InserisciLettura(string contenuto, PuntoMisura pm, DateTime now)//safe { try { string tag = "<" + pm.Tag + ">"; int i = contenuto.IndexOf(tag); i += tag.Length; tag = "</" + pm.Tag + ">"; int f = contenuto.IndexOf(tag); string val = contenuto.Substring(i, f - i); val = val.Replace("\n", "").Replace("\t", "").Replace(" ", ""); SqlCommand cmd = new SqlCommand("INSERT INTO Rilevazione(Tempo,Valore,PuntoMisura) VALUES(@tempo,@valore,@pm)"); cmd.Parameters.Add(new SqlParameter("tempo", now)); cmd.Parameters.Add(new SqlParameter("valore", val)); cmd.Parameters.Add(new SqlParameter("pm", pm.Id)); cmd.Connection = sql_connetion; cmd.ExecuteNonQuery(); correttamente Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine("Rilevazione sul punto misura " + pm.Id + " inserita [" + pm.Tag + "=" + val + "]"); Console.ResetColor(); } catch (Exception ex) { log_writer.WriteLine(now + " - PuntoMisura: " + pm.Id + " - Errore: " + ex.Message); log_writer.Flush(); Console.WriteLine(now + " - PuntoMisura: " + pm.Id + " - Errore: " + ex.Message); return false; } return true; } } }