De todo un poco.

21/2/21

En cuanto descubrí que era posible programar un ESP32 con C#, lo primero que se me ocurrió fue reescribir OpenRaceLed en C#.

El primer problema que me encontré es que no había una librería para controla la tira de leds NeoPixel, así que programé un primer ejemplo que puedes consultar aquí: Hardware.Esp32.Rmt NeoPixelStrip

En esta primera versión tuve en cuenta más la semántica y la orientación a objetos que el limitado hardware sobre el que se iba a ejecutar y al intentar ejecutarlo obtuve un OutOfMemory. La memoria disponible en un ESP32 es muy limitada (desde mi punto de vista que no estoy acostumbrado a programar microcontroladores) y tenía que limitar el código para gestionar solo los primeros 50 leds como máximo.

El siguiente paso, fue buscar un ejemplo de como se programa en Arduino esta tira led y tomarlo como referencia para hacer una versión más liviana. El resultado es este segundo ejemplo: Hardware.Esp32.Rmt NeoPixelStripLowMemory

Ahora si que puedo direccionar los 300 leds pero tengo otro problema es extremadamente lento en comparación con la versión que probé de Arduino.

#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/event_groups.h"
#include "Arduino.h"

#include "esp32-hal.h"

#define NR_OF_LEDS   300
#define NR_OF_ALL_BITS 24*NR_OF_LEDS

rmt_data_t led_data[NR_OF_ALL_BITS];

rmt_obj_t* rmt_send = NULL;

void setup() 
{
    Serial.begin(115200);
    
    if ((rmt_send = rmtInit(5, true, RMT_MEM_64)) == NULL)
    {
        Serial.println("init sender failed\n");
    }

    float realTick = rmtSetTick(rmt_send, 100);
    Serial.printf("real tick set to: %fns\n", realTick);

}

int color[] =  { 0x00, 0xFF, 0x00 };  // GRB value
int led_index = 0;

void loop() 
{
    // Init data with only one led ON
    int led, col, bit;
    int i=0;
    for (led=0; led < NR_OF_LEDS; led++) {
        for (col=0; col<3 bit="" col="" color="" duration0="4;" duration1="8;" else="" for="" i="" if="" in="" led="" led_data="" led_index="" level0="1;" level1="0;" make="" pannel="" the="" travel="">=NR_OF_LEDS) {
        led_index = 0;
    }

    // Send the data
    rmtWrite(rmt_send, led_data, NR_OF_ALL_BITS);

    delay(10);
}

Aquí tenéis un video a tiempo real de la velocidad máxima que consigo con el mismo hardware y los dos firmwares.

En un principio tiene sentido que al usar C# exista una penalización de rendimiento en comparación con una implementación nativa, pero ¿no es algo exagerada la diferencia? ¿estoy haciendo algo mal? ¿Es posible obtener mejor rendimiento de la versión nanoFramework? Seguro que a medida que aparezcan nuevas versiones se irán optimizando el código para conseguir mejor rendimiento.

31/1/21

nanoFramework es una plataforma para programar dispositivos embebidos utilizando C#. No confundir con .Net Micro Framework este es para dispositivos más grandes (Tipo RaspberryPi), que tienen sistema operativo (generalmente Linux). 

Al no requerir sistema operativo, puede utilizarse hardware más modesto y por lo tanto más barato. Yo para las pruebas he utilizado una placa de desarrollo basado en el chip ESP32 que cuesta sobre 6.5€ o incluso más barato si lo buscamos en páginas chinas.

Es posible utilizar tanto el Visual Studio Code, como el Visual Studio. Yo me decanté por el segundo ya que estoy acostumbrado a utilizarlo.

Primeros pasos con Visual Studio

Descargamos el Visual Studio Community 2019. Yo siempre lo instalo en inglés ya que es más facil buscar información sobre errores por internet.

Una vez instalado habrá que ir al menú "Tools"->"NuGet Package Manager"->"Package Manager Settings"->"Package Sources" y añadir un origen nuevo:

Name: nanoFrameworkk

Source: https://pkgs.dev.azure.com/nanoframework/feed/_packaging/sandbox/nuget/v3/index.json

Después ir a "Extensions"->"Manage Extensions"->"Online" y buscamos nanoFramework e instalamos esa extensión.

Preparando el hardware ESP32

Para poder trabajar con el hardware primero tenemos que cargarle un firmware con el Core de nanoFramework y que así Visual Studio lo detecte como dispositivo válido.

Para ello debemos conectar el ESP con el cable USB al PC. A continuación vamos al "Administrador de dispositivos" de windows dentro del apartado "Puertos (COM y LPT)" y tomamos nota del puerto asignado al ESP32. En mi caso el COM3.

El siguiente paso en el Visual Studio es ir al menú "Extensions"->"Tools"->"NuGet Package Manger"->"Package Manager console" y ejecutar lo siguiente:

nanoff --serialport COM3 --target ESP32_WROOM_32 --update -d v

 Tras ejecutar ese comando mantener presionado el botón BOOT en la placa para que se inicie la escritura del firmware.

Si devuelve el error E4005 puede ser que el Visual Studio esté bloqueando el puerto, para evitarlo hay que ir al menú del Visual Studio "View"->"Other Windows"->"Device Explorer" y darle al botón "Disable scan device in Visual Studio"

Una vez que se cargó el firmware en el ESP32 debemos asegurarnos que el Visual Studio ya reconoce el dispositivo, para ello vamos a "View"->"Other Windows"->"Device Explorer" Si no aparece tenemos que asegurarnos que no está activo el botón "Disable scan device in Visual Studio"

 


En este punto ya estamos preparados para hacer nuestro primer desarrollo.

Hola Mundo en ESP32

Para hacer nuestro primer programa tendremos que ir al Visual Studio y crear un nuevo proyecto de tipo "Blank Application (nanoFramework)" y ya nos creará nuestra primera aplicación.

public class Program
    {
        public static void Main()
        {
            Debug.WriteLine("Hello from nanoFramework!");

            Thread.Sleep(Timeout.Infinite);
        }
    }

Si ya tenemos seleccionado el ESP32 en la ventana "Device Explorer", dándole al botón de play el Visual Studio se encargará de compilar el código, desplegarlo en el dispositivo e iniciar la depuración.


Podremos poner interrupciones en el código y podremos ir depurando paso a paso o incluso ver el valor de variables como haríamos normalmente con cualquier otra aplicación desarrollada en C#.

Accediendo al hardware desde C#

Las placas de desarrollo ESP32 suelen tener un led conectado al pin 2, así que con el siguiente código podemos hacer parpadear dicho led sin necesidad de tener nada más.

using System.Device.Gpio;
using System.Threading;

namespace Blinky
{
	public class Program
    {
        private static GpioController _gpioController;
        public static void Main()
        {
            _gpioController = new GpioController();

            GpioPin led = _gpioController.OpenPin(2,PinMode.Output);

            led.Write(PinValue.Low);

            while (true)
            {
                led.Toggle();
                Thread.Sleep(125);               
            }
        }
    }
}


Si además conectamos un led a dicho pin (con su correspondiente resistencia de 100Ω) podremos ver cómo parpadean a la vez el led de la placa y el conectado al pin 2.


 ¿Donde conseguir más ejemplos?

El mejor sitio para obtener un montón de ejemplos es el propio github repositorio Samples del proyecto nanoFramework. 

Muchos de esos proyectos son independientes del hardware utilizado, por lo que algunos requieren pequeñas modificaciones para funcionar con nuestro ESP32.




 



9/2/16

Instalar  Strawberry Perl de 32 bits (aunque la maquina sea de 64bits)
Descargar zip AWStasts
Copiar el contenido de wwwroot a una carpeta que configuraremos en el IIS

Ir a "Activar o desacivar características de windows"->Roles->Servidor web(IIS)->Agregar servicios de rol->Marcar CGI

En el IIS agregar sitio web con el nombre deseado (por ejemplo AWStats) un grupo de aplicaciones nuevo y la ruta física apuntando a la carpeta comentada anteriormente.

Sobre el grupo de aplicaciones, ir a configuración avanzda del nuevo grupo creado y establecer a true el opción "Habilitar aplicaciones de 32 bits"

Sobre el sitio web creado ir a "Asignaciones de controlador"->"Agregar asignación de script":
Ruta de acceso de solicitudes: *.pl
Ejecutable: Ruta completa a perl.exe  "%s" %s
Nombre: Perl

Dentro de la carpeta cgi-bin copiar awstats.model.conf awstats.nombresitio.conf y cambiar lo siguiente:
LogFile="C:\inetpub\logs\LogFiles\W3SVC2\u_ex%YY-24%MM-24%DD-24.log"
LogFormat="date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) sc-status sc-substatus sc-win32-status time-taken"
HostAliases="localhost 127.0.0.1 <IP publica del servidor> REGEX[myserver\.com$]"
AllowFullYearView=3

Para procesar todos los archivos de log antiguos crear un bat similar al siguiente:
@echo off
for /f %%f in ('dir /O:d /b C:\inetpub\logs\LogFiles\W3SVC3\') do c:\perl\bin\perl awstats.pl -config=web.dominio.es -LogFile=C:\inetpub\logs\LogFiles\W3SVC3\%%f
pause

Crear otro bat para añadir a tareas programadas:
@echo off
c:\perl\perl\bin\perl awstats.pl -config=web.domino.es >> updateAWstats.log

Ir al "Programador de tareas"->Crear Tarea:
Nombre: Update AWStats
Marcar: Ejecutar tanto si el usuario inicó sesión como si no
Desencadenadores: Nuevo:
   Según una programación
   Diariamente

Acciones: Nueva
   Acción: Iniciar un programa
   Programa o script: archivo.bat creado anteriormente
   Iniciar en: ruta al archivo.bat (Importante)


Probar a acceder a la web http://localhost:81/cgi-bin/awstats.pl?config=web.domino.es




2/11/15


Si te ves en la necesidad de generar puntos aleatorios en una zona en concreta, pero además tienes la restricción de que todos los puntos tienen que ser en una zona donde haya agua, igual la siguiente guía te ayuda. Así es como lo hice yo:


En primer lugar obtener todas las líneas que separan la tierra y el agua (en mi caso con las líneas de costa me llegaba). El lugar con datos libres y con mayor detalle lo he encontrado en la web de OpenStreetMapData en el siguiente enlace: Water polygons

Para recortar la zona que me interesaba utilicé el software libre QGIS. Nunca antes había usado ese software, pero siguiendo el tutorial "Tutorial for creating polygons in QGIS" conseguí mi objetivo muy fácilmente.

En el punto 6  del tutorial, explica como cargar los datos descargados de OpenStreetMapData.

En el punto 8 como dibujar un polígono que delimitará la zona sobre la que se está interesado.

Después utilizando el polígono se puede recortar una zona del mapa generando la forma deseada.

Para exportar la forma obtenida (SHP) a formato WKT solo hay que seguir el siguiente artículo: "Cómo convertir un shapefile en WKT"

Una vez se ha obtenido el WKT ya se puede generar desde .Net puntos aleatorios entre la mínima y la máxima latitud y entre la mínima y máxima longitud de la figura y comprobar si el punto obtenido está dentro de la figura (ya que si la forma es irregular puede que cuadre fuera)

public static double GetRandomDouble(Random rnd, double minimum, double maximum)
{
    return rnd.NextDouble() * (maximum - minimum) + minimum;
}

public static DbGeography GetRandomPointInZone(DbGeography validZone,Random rnd)
{
    DbGeography pos = null;

    double minlat = double.MaxValue;
    double maxlat = double.MinValue;
    double minlon = double.MaxValue;
    double maxlon = double.MinValue;

    for (int i = 1; i <= validZone.PointCount; i++)
    {
        var point = validZone.PointAt(i);
        if (point.Latitude != null && minlat > point.Latitude) minlat = (double)point.Latitude;
        if (point.Longitude != null && minlon > point.Longitude) minlon = (double)point.Longitude;
        if (point.Latitude != null && maxlat < point.Latitude) maxlat = (double)point.Latitude;
        if (point.Longitude != null && maxlon < point.Longitude) maxlon = (double)point.Longitude;
    }

    int retry = 0;
    while (pos == null || !pos.Intersects(validZone)) //Aquí comprobamos que la posición aleatoria está dentro de la zona.
    {
        var latitude = GetRandomDouble(rnd, minlat, maxlat);
        var longitude = GetRandomDouble(rnd, minlon, maxlon);

        var wkt = String.Format("POINT({0} {1})", longitude.ToString(CultureInfo.InvariantCulture), latitude.ToString(CultureInfo.InvariantCulture));
        pos = DbGeography.PointFromText(wkt, 4326);
        retry++;

        if (retry > 1000)
            throw new Exception("Parece un bucle infinito, tras 1000 reintentos no se ha podido obtener una posición dentro de la zona válida");
    }
    return pos;
}

static void Main(string[] args)
{
    var rnd = new Random();

    var validZone = DbGeography.PolygonFromText(ConfigurationManager.AppSettings["ValidZone"], 4326);

    var posRandom = GetRandomPointInZone(validZone, rnd);

    Console.WriteLine("Posición aleatoria dentro de zona: Latitud {0}, Longitud {1}",posRandom.Latitude,posRandom.Longitude);
    Console.ReadKey();
}
En mi caso necesitaba ir generando los puntos aleatorios desde código en C#. Si en tu caso eso no es necesario, utiliza el propio QGIS para generar puntos aleatorios. Seguro que lo hace de una forma mucho más eficiente ;)

26/10/15

Si al intentar cargar un poligono WKT en un campo de tipo geográfico en SQL Server falla, puede que se trate del orden en que se ha especificado los puntos es incorrecto.

Ejemplo:
CREATE TABLE #ZONES(
 [ID] [int] IDENTITY(1,1) NOT NULL,
 [POLYGON] [geography] NOT NULL
 CONSTRAINT [PK_ZONES] PRIMARY KEY CLUSTERED ([ID] ASC))

--Falla
INSERT INTO #ZONES([POLYGON])VALUES(geography::STPolyFromText('POLYGON((-9.123 43.123, -8.123 43.123, -8.123 42.123, -9.123 42.123,-9.123 43.123))' ,4326))

--Funciona
INSERT INTO #ZONES([POLYGON])VALUES(geography::STPolyFromText('POLYGON((-9.123 43.123, -9.123 42.123, -8.123 42.123, -8.123 43.123,-9.123 43.123))' ,4326))

Solución para evitar cambiar a mano el orden de las coordenadas:
DECLARE @geom GEOMETRY = 'POLYGON((-9.123 43.123, -8.123 43.123, -8.123 42.123, -9.123 42.123,-9.123 43.123))';
DECLARE @geog GEOGRAPHY = @geom.MakeValid().STUnion(@geom.STStartPoint()).STAsText()
INSERT INTO #ZONES([POLYGON])VALUES(@geog)

13/10/15