En este artículo se describe una aplicación web basada en ASP.NET MVC 4 que obtiene datos de la estación meteorológica usb descrita en el artículo anterior permitiendo su análisis. El resultado se muestra en la siguiente imagen:

En primer lugar se modelan los datos. En este caso se ha definido una tabla con seis campos, el primero para la fecha de obtención de la medición y los cinco restantes para cada uno de los valores que retorna el sistema.

modelo de datos sensor estación meteorológica

Necesitamos obtener datos de los sensores de forma periódica y para ello utilizaremos Quarz.net. La instalación de Quartz.net se realiza utilizando Nuget desde el propio Visual Studio 11. Para representar gráficos instalaremos también DotNet.HighCharts.

Package Manager Console Host Version 1.7.30402.9028
Type 'get-help NuGet' to see all available NuGet commands.
PM> Install-Package Quartz
Attempting to resolve dependency 'Common.Logging (≥ 2.0.0)'.
Successfully installed 'Common.Logging 2.0.0'.
Successfully installed 'Quartz 2.0.0'.
Successfully added 'Common.Logging 2.0.0' to MvcApplication1.
Successfully added 'Quartz 2.0.0' to MvcApplication1.
PM> Install-Package DotNet.Highcharts
Successfully installed 'DotNet.Highcharts 1.2'.
Successfully added 'DotNet.Highcharts 1.2' to MvcApplication1.

A continuación en el fichero global.asax configuraremos una tarea periódica que se ejecute cada minuto con el objetivo de obtener datos de los sensores y almacenarlos en la base de datos.

void ScheduleReadSensor0()
{
    var schedFact = new StdSchedulerFactory();
    // get a scheduler
    IScheduler sched = schedFact.GetScheduler();
    sched.Start();
    // construct job info
    var schedule = SimpleScheduleBuilder.RepeatSecondlyForever(60);
    var trigger = TriggerBuilder.Create().StartNow().WithSchedule(schedule).Build();
    var job = new JobDetailImpl("Sensors", typeof(Jobs.ReadSensorsJob));
    sched.ScheduleJob(job, trigger);
}

La tarea simplemente obtiene datos del sensor y les almacena.

void ReadSensors()
{
    var portName = Properties.Settings.Default.Sensor0PortName;
    //Si el puerto serie existe leemos
    if (SerialPort.GetPortNames().Contains(portName))
    {
        using (SerialPort port = new SerialPort(portName, 9600))
        {
            port.ReadTimeout = 5000;
            port.DtrEnable = false;
            port.Open();
            port.Write("g");
            var line = port.ReadLine();
            var vals = (from v in line.Split("|".ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
                        select XmlConvert.ToSingle(v)).ToArray();
            //Si todo ha ido bien almacenamos los datos
            Store(vals[0], vals[1], vals[2], vals[3], vals[4]);
        }
    }
}

void Store(Single t1, Single h, Single l, Single p, Single t2)
{
    //Almacenamos los datos en la base de datos
    using (var db= new SantuarioEntities())
    {
        var sr = new Sensor0Readings()
        {
            TimeStamp = DateTime.Now,
            Temperature1 = t1,
            Temperature2 = t2,
            Pressure = p,
            Humidity = h,
            Luminance = l
        };
        db.Sensor0Readings.AddObject(sr);
        db.SaveChanges();
    }
}

Por defecto los procesos ASP.NET se detienen pasados 20 minutos de inactividad por lo que un primer paso para que el registro se produzca de manera continua es aumentar ese límite. Para ello en la configuración de IIS, dentro de la configuración avanzada del grupo de aplicaciones establecemos el valor a 0, que significa indefinido.

Aun así, por diversas razones el proceso se podría detener por lo que es recomendable configurar una tarea periódica que solicite una página al servidor provocando su arranque en caso de que esté detenido.

Ya solo queda implementar un controlador y una vista de ASP.NET MVC que se encargue de obtener los datos y representarles.

Se ha implementado una acción de controlador cuya lógica se divide en obtener los datos y generar un gráfico a partir de ellos.

La parte de obtención de los datos:

//span define el tamaño en minutos de cada intervalo
//nSpan el número de intervalos a representar
if (endTime == null)
    endTime = DateTime.Now;

var lastMeasure = (from m in db.Sensor0Readings
                    where m.TimeStamp <= endTime.Value
                    orderby m.TimeStamp descending
                    select m).FirstOrDefault();
//Si no hay medidas o la medida es muy vieja
if (lastMeasure == null || (endTime.Value - lastMeasure.TimeStamp) > TimeSpan.FromMinutes(60))
{
    ViewBag.Temperature = "Desconocida";
}
else
{
    ViewBag.ElapsedMinutes = (endTime.Value - lastMeasure.TimeStamp).TotalMinutes.ToString("F0");
    ViewBag.Temperature = string.Format("{0:F1} ºC", lastMeasure.Temperature1);
    ViewBag.Humidity = string.Format("{0:F1} %", lastMeasure.Humidity);
    ViewBag.Pressure = string.Format("{0:F1} mb", lastMeasure.Pressure);
}
//Obtenemos datos de los ultimos n mins
List<DateTime> limitPoints = new List<DateTime>();
List<DateTime> intervals = new List<DateTime>();
List<object> pressures = new List<object>();
List<object> temperatures = new List<object>();
List<object> humidities = new List<object>();
//Establecemos los puntos limite
for (int i = 0; i < nSpan + 1; i++)
{
    limitPoints.Add(endTime.Value - TimeSpan.FromMinutes(i * span));
}
//Obtenemos los datos para los intervalos
for (int i = 0; i < nSpan; i++)
{
    var upperLimit = limitPoints[i];
    var lowerLimit = limitPoints[i + 1];
    var measurements = (from m in db.Sensor0Readings
                        where m.TimeStamp < upperLimit
                        where m.TimeStamp >= lowerLimit
                        select m).ToArray();
    double pAverage = 0;
    double tAverage = 0;
    double hAverage = 0;
    if (measurements.Length > 0)
    {
        pAverage = (from p in measurements
                    select p.Pressure).Average();
        tAverage = (from p in measurements
                    select p.Temperature1).Average();

        hAverage = (from p in measurements
                    select p.Humidity).Average();
    }
    var intervalLabel = limitPoints[i] - TimeSpan.FromMinutes(0.5 * span);
    intervals.Add(intervalLabel);
    pressures.Add(pAverage);
    temperatures.Add(tAverage);
    humidities.Add(hAverage);
}
intervals.Reverse();
pressures.Reverse();
temperatures.Reverse();
humidities.Reverse();

Y el código de la parte de generación del gráfico:

Highcharts chart = new Highcharts("chart")
    .InitChart(new Chart { ZoomType = ZoomTypes.Xy })
    .SetTitle(new Title { Text = "Registro climático" })
    .SetSubtitle(new Subtitle { Text = "citadel labs" })
    .SetXAxis(new XAxis
    {
        Type = AxisTypes.Datetime,
    })
    .SetYAxis(new[]
                            {
                                new YAxis
                                {
                                    Labels = new YAxisLabels
                                            {
                                                Formatter = "function() { return this.value +C'; }",
                                                Style = "color: '#89A54E'"
                                            },
                                    Title = new XAxisTitle
                                            {
                                                Text = "Temperatura",
                                                Style = "color: '#89A54E'"
                                            },
                                    Opposite = true,
                                },
                                new YAxis
                                {
                                    Labels = new YAxisLabels
                                            {
                                                Formatter = "function() { return this.value +' %'; }",
                                                Style = "color: '#4572A7'"
                                            },
                                    Title = new XAxisTitle { Text = "Humedad relativa", Style = "color: '#4572A7'" },
                                    GridLineWidth = 0
                                },
                                new YAxis
                                {
                                    Labels = new YAxisLabels
                                            {
                                                Formatter = "function() { return this.value +' mb'; }",
                                                Style = "color: '#AA4643'"
                                            },
                                    Title = new XAxisTitle
                                            {
                                                Text = "Presión",
                                                Style = "color: '#AA4643'"
                                            },
                                    GridLineWidth = 0,
                                    Opposite = true
                                }
                            })
    .SetTooltip(new Tooltip { Formatter = "TooltipFormatter" })
    .AddJavascripFunction("TooltipFormatter",
                            @"var unit = {
                    'Humedad relativa': '%',
                    'Temperatura': C',
                    'Presión': 'mb'
                    }[this.series.name];
                    var xDate = new Date(this.x);
                return ''+
                    xDate+': '+ this.y.toFixed(2) +' '+ unit;")
    .SetPlotOptions(new PlotOptions
    {
        Spline = new PlotOptionsSpline
        {
            Marker = new PlotOptionsLineMarker { Enabled = false },
            LineWidth = 3,
            DashStyle = DashStyles.ShortDot,
            PointInterval = 3600000,
            PointStart = new PointStart(intervals[0])
        },
        Column = new PlotOptionsColumn
        {
            PointInterval = 3600000,
            PointStart = new PointStart(intervals[0])
        }
    })
    .SetSeries(new[]
                            {
                                new Series
                                {
                                    Name = "Humedad relativa",
                                    Color = ColorTranslator.FromHtml("#804572A7"),
                                    Type = ChartTypes.Column,
                                    YAxis = 1,
                                    Data = new Data(humidities.ToArray())
                                },
                                new Series
                                {
                                    Name = "Presión",
                                    Color = ColorTranslator.FromHtml("#AA4643"),
                                    Type = ChartTypes.Spline,
                                    YAxis = 2,
                                    Data = new Data(pressures.ToArray()),
                                },
                                new Series
                                {
                                    Name = "Temperatura",
                                    Color = ColorTranslator.FromHtml("#89A54E"),
                                    Type = ChartTypes.Spline,
                                    PlotOptionsSpline = new PlotOptionsSpline
                                        {
                                            Marker = new PlotOptionsLineMarker { Enabled = false },
                                            LineWidth = 3,
                                            DashStyle = DashStyles.Solid,
                                            PointInterval = 3600000,
                                            PointStart = new PointStart(intervals[0])
                                        },
                                    Data = new Data(temperatures.ToArray())
                                }
                            });

Por último, la vista quedaría:

@{
    ViewBag.Title = "Sensor0";
}

@model DotNet.Highcharts.Highcharts</pre>
<h2>Estado (Actualizado hace @ViewBag.ElapsedMinutes minutos)</h2>
<dl><dt>Temperatura actual:</dt><dd>@ViewBag.Temperature</dd><dt>Humedad:</dt><dd>@ViewBag.Humidity</dd><dt>Presión atmosférica:</dt><dd>@ViewBag.Pressure</dd></dl>
<pre>
@Model