Archivo de la etiqueta: Arduino

IoT en 90 Minutos

Vamos a crear un sistema IoT sencillo utilizando una placa basada en ESP8266, la plataforma Thingspeak para registrar los datos y la APP Blynk para controlar y monitorizar desde el móvil.

El objetivo es:

  • Monitorizar la temperatura y humedad de una sala remotamente desde el móvil
  • Encender desde el móvil la iluminación de la sala
  • Registrar todos los datos históricos de temperatura y humedad
  • Registrar las veces que se abre una puerta
  • Mandar avisos por alta temperatura. 
  • Mandar avisos cuando el sensor de puerta se abra.

Los avisos o notificaciones pueden ser:

Material Necesario

Hardware:

  • Wemos D1 mini
  • Sensor DHT11
  • Led + resistencia 220 ohms (para simular la iluminación) o Relé para la iluminación
  • Pulsador + Resistencia 10 kohms  (para simular la apertura de la puerta) o sensor magnético/infrarrojos.

Coste aproximado: 5 – 20 € dependiendo del material usado.

Software:

Coste del software y licencias: 0 €

Conexión Hardware

Esquema de conexión:

Pines utilizados:

  • D4: Led y también es el led integrado de la placa
  • D3: pulsador/puerta, tiene una resistencia de pull up integrada: OJO, este pin va al GPIO0 que control el arranque, asegurarse de no estar a masa/pulsado al reiniciar o cargar un nuevo programa
  • D2: sonda DHT11

El pulsador simula la apertura de la puerta y el led simula la iluminación de la sala.

Blynk

Blynk es una plataforma que permite que cualquiera pueda controlar fácilmente su proyecto Arduino con un dispositivo con sistema iOS o Android. Los usuarios tendrán ahora la posibilidad de crear una interfaz gráfica de usuario de “arrastrar y soltar” para su proyecto en cuestión de minutos y sin ningún gasto extra.

Blynk vendría a ser como tener una protoboard en tu dispositivo móvil, tablet o teléfono, que cuenta con todo lo que necesites usar, desde deslizadores y pantallas a gráficos y otros widgets funcionales que se pueden organizar en la pantalla un Arduino. Además te da la opción de poder recopilar datos de los sensores que montes en un proyecto. Funciona nada más sacarlo de la caja y conectarlo a la placa por Internet.

Arquitectura de Blynk:

Thingspeak

ThingSpeak es un plataforma de Internet of Things (IoT) que permite recoger y almacenar datos de sensores en la nube y desarrollar aplicaciones IoT. Thinkspeak también ofrece aplicaciones que permiten analizar y visualizar tus datos en MATLAB y actuar sobre los datos. Los datos de los sensores pueden ser enviados desde Arduino, Raspberry Pi, BeagleBone Black y otro HW.

Web: https://thingspeak.com/

Precios: https://thingspeak.com/prices

Pasos a seguir

Crear una cuenta en Thingspeak y configurar

Web: https://thingspeak.com/users/sign_up

Tutoriales:

Crear cuenta:

Crear un Nuevo Canal llamado: “Curso IoT”

Crear 3 Fields:

  • Temperatura – Guarda los datos de temperatura
  • Humedad – Guarda los datos de humedad
  • Puerta – Guarda las aperturas de puerta

Guarda la API Key y el número de canal

Instalar Blynk 

Getting Started: https://blynk.io/en/getting-started

Docs: https://docs.blynk.cc/

Instalar Blynk en el móvil: https://blynk.io/

Crear una cuenta en Blynk

Crear un nuevo proyecto llamado “IoT en 90 minutos”

Elegir Hardware, en este caso “Wemos D1 Mini”

Guardar el Auth Token. Auth Token es un identificador único que se necesita para conectar su hardware a su smartphone. Cada nuevo proyecto que cree tendrá su propio Auth Token. Obtendrá Auth Token automáticamente en su correo electrónico después de la creación del proyecto. También se puede copiar manualmente.

Añadir 3 widgets:

  • Un botón (Conectado al Pin D4)
  • Dos Gauge en los pines Virtuales V0 y V1 para temperatura y humedad

Virtual Pin: http://help.blynk.cc/en/articles/512061-what-is-virtual-pins

Configuración del Gauge:

  • Temperatura pin virtual V0
  • Humedad pin virtual V1
  • Modo push
  • Label: C para temperatura y % para Humedad

Configuración del Botón. poner en modo switch:

Aspecto final de la APP:

Ejecutar el programa

 

Preparar IDE Arduino

Instalar el IDE de Arduino:

Instalar el soporte para las placas basadas en ESP8266 en el IDE de Arduino

Instalar librerías necesarias en IDE Arduino desde el gestor de librerías:

 Realizar montaje de Wemos D1 mini

Personalizar el Firmware y Ejecutarlo

Configurar la Vista Pública en Thingspeak

Crear una vista pública, para ello en el canal ir a “sharing” y seleccionar “Share channel view with everyone”

Configurar la vista pública de Thingspeak, es una especie de dashboard donde pondremos:

  • Gráfica de Temperatura (Tipo Spline)
  • Display numérico Temperatura
  • Gráfica de Humedad  (Tipo Spline)
  • Display numérico Humedad
  • Gráfica apertura de puerta (Tipo Step)
  • Lamp Indicator, para ver el estado de la puerta abierto/cerrado
  • Un histograma para ver la variación de la temperatura

La vista debe quedar como esta: https://thingspeak.com/channels/635134

Configurar las Notificaciones en Thingspeak

Configurar avisos en Thingspeak cuando la temperatura sea mayor de 24 grados y cuando se abra la puerta. Para ello usaremos estas utilidades de thingspeak.

Notificaciones posibles:

  • Mandar un mensaje al panel: https://www.aprendiendoarduino.com/servicios/mensajes/index.html, servicio de #aprendiendoarduino para hacer una demo.
  • Enviar un mensaje a Telegram mediante un bot al canal https://t.me/aprendiendoarduino. Para ello es necesario crear un bot, añadirlo al canal y usar su API KEY desde thingspeak con ThingHTTP
  • Enviar un mensaje al canal #arduino_iot de https://aprendiendoarduino.slack.com/
  • Mandar un tweet usando ThingTweet, para ello debemos enlazar nuestra cuenta de Twitter.
  • Mandar un email con mailgun https://www.mailgun.com/, para ello debemos darnos de alta en mailgun y usar la API Key para que dispare el webhook configurado en ThingHTTP y mande un email
  • Para cualquier otra interacción se puede usar IFTTT. Se crea un webhook que se usa desde ThingHTTP y desde IFTTT disparamos el servicio que queramos.
  • Y cualquier otra que disponga de un webhook o API

Primero debe configurarse ThingHTTP para que llame a una API o webhook que dispare la notificación que deseamos. Para ello deberemos darnos de alta en el servicio que deseemos.

Para mandar un mensaje al panel: https://www.aprendiendoarduino.com/servicios/mensajes/index.html debo llamar a esta API:

Crear un nuevo ThingHTTP llamado “Manda Mensaje Alta Temperatura” y poner:

Crear un nuevo ThingHTTP llamado “Manda Mensaje Puerta Abierta” y poner:

NOTA: si no funciona la llamada al servicio de mensajes, mandar un correo a aprendiendoarduino@gmail.com

Una vez creados los elementos ThingHTTP que dispara la notificación queda crear los react, que son las condiciones en la que se disparan las notificaciones, donde diremos en qué condiciones se mandan las notificaciones. En nuestro caso:

  • Temperatura > 24 grados solo la primera vez que pase (Run action only the first time the condition is met: Trigger the action if the condition was previously false and is currently true.)
  • El valor del canal es 1 (Puerta abierta) cada vez que pase.

Crear un nuevo react llamado “Alta Temperatura IoT 90 minutos” con los siguientes parámetros:

Crear un nuevo react llamado “Puerta Abierta IoT 90 minutos” con los siguientes parámetros:

Probar que se muestran los mensajes en el panel https://www.aprendiendoarduino.com/servicios/mensajes/index.html

Si quisiéramos mandar un tweet, simplemente seleccionar en Action “ThingTweet” y poner el texto del tweet.

Identificación Horizontales Demo

Las horizontales o Building Blocks usados en IoT:

Para la demo:

  • Devices: Wemos D1 mini + sensor temperatura + pulsador + led + relé
  • Infraestructura de comunicación: Wifi
  • Gateway: Punto de Acceso Wifi
  • Protocolo: API HTTP y MQTT (transparente al usar las librerías de Blynk y Thingspeak)
  • Plataforma: Thingspeak y Blynk
  • Servicios: Almacenamiento de datos, gráficas, disparo de eventos y análisis de datos por Thingspeak, monitorización móvil por Blynk, notificaciones por IFTTT o plataforma propia.

Limitaciones de la solución utilizada:

  • Máximo número de envíos a plataforma: 15 segundos
  • Datos almacenados solo hasta un año o 3 millones de registros
  • Límite en el cálculo de datos
  • Gráficas simples
  • Depender de terceros para las notificaciones

Saber más de IoT 2019…

Slack: https://aprendiendoarduino.slack.com, enlace de invitación: link invitación


 

Día 1: “Presentación del Curso”

Sistema inteligente para ahorrar energía en edificios: https://www.eldia.com/nota/2019-10-10-3-24-26-crean-un-sistema-inteligente-para-ahorrar-energia-electrica-en-edificios-informacion-general

Slack: https://aprendiendoarduino.slack.com, enlace de invitación: link invitación

Saber más…: https://aprendiendoarduino.wordpress.com/2019/10/03/saber-mas-de-iot-2019/

Capítulos vistos día 1:

Día 2: “Raspberry Pi en IoT”

Preparación de todas las herramientas que necesitamos para IoT, hay que hacerlo pero SOLO UNA VEZ:

  • Cuenta de Blynk
  • Cuenta de Thingspeak
  • Instalación IDE Arduino
  • Instalación driver
  • Instalación soporte para ESP8266
  • Instalación librerías

Preparación para Raspberry Pi:

Capítulos vistos día 2:

Día 3: “Software Raspberry Pi y Arduino”

Numerar puestos y firmar hoja de material.

Repaso del día anterior:

  • Funcionamiento de la práctica de “IoT en 90 minutos”
  • ThingHTTP y React
  • Instalación Raspbian

¿Como añadir en “IoT en 90 minutos” un contador en thingspeak y en Blynk para ver el estado la puerta y un contador de cuantas veces se ha abierto la puerta?

Ejemplo PoC con toroidales y luego wibeee

Plataforma Energía (Vertical):

Capítulos vistos día 3:

Día 4: “Software Raspberry Pi y Comunicaciones”

Nuevo Arduino MKR WAN 1310: https://blog.arduino.cc/2019/10/10/new-mkr-wan-1310-for-lora-connectivity-comes-with-2mbyte-flash-and-extended-battery-life/

Monitorizar temperatura de un refrigerador: https://blog.arduino.cc/2019/09/25/logging-refrigerator-temperature-with-arduino/

Sistema de monitorización de temperatura: https://blog.temboo.com/temperature-monitoring-system/

Y esto es lo que se puede hacer con los datos recogidos por los Arduinos: https://aprendiendoarduino.grafana.net/

Empresas que usan estos sistemas: https://www.linkedin.com/posts/siceanz_itswc19-activity-6593044471758327808-omvU

Talleres específicos: emonCMS

Resumen del día 3:

Capítulos vistos día 4:

Día 5: “MQTT”

Nuevo IDE Arduino (Beta): https://blog.arduino.cc/2019/10/18/arduino-pro-ide-alpha-preview-with-advanced-features/

Glucómetro IoT:

Image Courtesy of Wall Street Journal, Published on line, Friday, Sept. 26, 2014

Capítulos vistos día 5:

Día 6: “Node-Red”

Limite de conexiones de mosquitto: 1015 en raspberry Pi

Clonar imágenes Raspberry Pi:

Cambiar memoria swap Raspberry Pi: https://raspberrypi.stackexchange.com/questions/70/how-to-set-up-swap-space

AEMET Open Data: https://opendata.aemet.es/centrodedescargas/inicio

Ejemplo de uso de mailgun conectando una máquina dispensadora de snacks:

Capítulos vistos día 6:

Día 7: “HTTP y API REST”

Ritmo del curso.

Fiware: https://www.fiware.org/

Curso Fiware: https://www.larioja.org/thinktic/es/cursos-jornadas/proximos-cursos-jornadas/curso-fundamentos-tecnologia-fiware-introduccion-fiware

Switchbot:

Proyecto IoT: https://www.hackster.io/gatoninja236/smart-garden-system-with-arduino-nano-iot-791933

Capítulos vistos día 7:

Día 8: “Plataformas y Servicios. Grafana”

Arduino IoT Cloud: https://blog.arduino.cc/2019/10/28/arduino-iot-cloud-support-for-esp8266-and-other-third-party-boards/

Openweathermap Pricing: https://openweathermap.org/price

Agromonitoring: https://agromonitoring.com/

Librerías HTTP:

Cliente seguro para ESP8266:

Contraseña en el dashboard de Node-Red: https://nodered.org/docs/user-guide/runtime/securing-node-red

  • Configurar:  httpNodeAuth: {user:”user”,pass:”xxxxxxxxxxxxxxxxxxxxxxxxxxxx”},

Node-Red Dashboard Login: https://github.com/phyunsj/node-red-dashboard-login

Flow para poner un pin en Dashboard: https://flows.nodered.org/flow/7bcb0b049df4fa3c962294137ebaec19

Como exponer un Node-Red de forma segura en Internet: https://github.com/node-red/cookbook.nodered.org/wiki/How-to-safely-expose-Node-RED-to-the-Internet

Hardware para LoRa/TTN:

Plataforma Thinger.io: https://thinger.io/

¡¡¡Cerveza fin de curso!!!

Recoger todo el material

Capítulos vistos día 8:

Prácticas: Clases y Objetos

Montaje Arduino UNO:

Montaje Wemos:

Ejercicio19 – Clase Contador

Hacer un programa que cuente las pulsaciones de los botones A y B usando la clase contador guardada en un fichero “Contador.h”

Solución: https://codeshare.io/5QYNeq

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio19-Clase_Contador

Si solo usamos funciones como en el ejercicio 17, necesito dos funciones detecta flanco y no puedo usar solo una porque si llamo a una y a otra simultáneamente el valor static se mantiene entre la llamada de una y otra lo que hace que falle. Para resolver este problema, hacer una clase DetectaFlanco y entonces puedo reutilizar el código ya que cada vez que instancio una nueva clase es como una función nueva.

NOTA: la clase se puede definir en un fichero “Contador.h” o dentro del mismo fichero .ino, ver ejemplo en https://github.com/jecrespo/aprendiendoarduino-Curso_Programacion_Arduino_2019

Ejercicio20 – Clase Detecta Flanco

Hacer el programa anterior pero creando  una clase llamada DetectaFlanco en un fichero “DetectaFlanco.h”

Solución: https://codeshare.io/Gb6K0M

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio20-Clase_Detecta_Flanco

NOTA: Esta clase se puede definir en un fichero “DetectaFlanco.h” y otro “DetectaFlanco.cpp” ver ejemplo en https://github.com/jecrespo/aprendiendoarduino-Curso_Programacion_Arduino_2019

Ejercicio21: Sensor DHT

Para entender mejor las clases y objetos y antes de entrar en el apartado de librerías y cómo crearlas, vemos un ejemplo de la librería DHT22 para las sondas de temperatura y humedad, de forma que entendamos que cuando la usamos para una sonda, lo que hacemos es instanciar un objeto de tipo sonda DHT22 y cuando llamamos al método readTemperature() estamos ejecutando la función que consulta la temperatura. También vamos a ver cómo se estructura la clase en el fichero de cabecera y en el de contenido.

El código de la librería lo tenemos en https://github.com/adafruit/DHT-sensor-library y vemos que tenemos dos ficheros:

En el fichero de cabecera tenemos la definición de la clase:

class DHT {
  public:
   DHT(uint8_t pin, uint8_t type, uint8_t count=6);
   void begin(void);
   float readTemperature(bool S=false, bool force=false);
   float convertCtoF(float);
   float convertFtoC(float);
   float computeHeatIndex(float temperature, float percentHumidity, bool isFahrenheit=true);
   float readHumidity(bool force=false);
   boolean read(bool force=false);

 private:
  uint8_t data[5];
  uint8_t _pin, _type;
  #ifdef __AVR
    // Use direct GPIO access on an 8-bit AVR so keep track of the port and bitmask
    // for the digital pin connected to the DHT.  Other platforms will use digitalRead.
    uint8_t _bit, _port;
  #endif
  uint32_t _lastreadtime, _maxcycles;
  bool _lastresult;

  uint32_t expectPulse(bool level);
};

Y en el fichero de definiciones DHT.cpp tenemos el código.

Constructor:

 
DHT::DHT(uint8_t pin, uint8_t type, uint8_t count) {
  _pin = pin;
  _type = type;
  #ifdef __AVR
    _bit = digitalPinToBitMask(pin);
    _port = digitalPinToPort(pin);
  #endif
  _maxcycles = microsecondsToClockCycles(1000);  // 1 millisecond timeout for
                                                 // reading pulses from DHT sensor.
  // Note that count is now ignored as the DHT reading algorithm adjusts itself
  // basd on the speed of the processor.
}

Método begin():

 
void DHT::begin(void) {
  // set up the pins!
  pinMode(_pin, INPUT_PULLUP);
  // Using this value makes sure that millis() - lastreadtime will be
  // >= MIN_INTERVAL right away. Note that this assignment wraps around,
  // but so will the subtraction.
  _lastreadtime = -MIN_INTERVAL;
  DEBUG_PRINT("Max clock cycles: "); DEBUG_PRINTLN(_maxcycles, DEC);
}

Método readTemperature(), que llama a la función read() que es la que hace toda la operación de consultar a la sonda y guarda en la propiedad privada data la información leída y readTemeprature() se encarga de darle formato en función del tipo de sonda y devolver el float con la temperatura:

 
float DHT::readTemperature(bool S, bool force) {
  float f = NAN;

  if (read(force)) {
    switch (_type) {
    case DHT11:
      f = data[2];
      if(S) {
        f = convertCtoF(f);
      }
      break;
    case DHT22:
    case DHT21:
      f = data[2] & 0x7F;
      f *= 256;
      f += data[3];
      f *= 0.1;
      if (data[2] & 0x80) {
        f *= -1;
      }
      if(S) {
        f = convertCtoF(f);
      }
      break;
    }
  }
  return f;
}

Este esquema explica como funciona este sensor y el protocolo de comunicación. La librería implementa el protocolo y facilita el uso de la sonda con Arduino.

En un ejemplo de uso de esta clase, primero hacemos un include del fichero, luego instanciamos un nuevo objeto sonda llamado dht. En el setup hacemos el begin() para iniciarlo y en el loop llamamos a los métodos de leer temperatura y humedad.

#include "DHT.h"
DHT dht(DHTPIN, DHTTYPE);

void setup() {
  dht.begin();
}
void loop() {
  float h = dht.readHumidity();
  float t = dht.readTemperature();
}

Partiendo del ejemplo de la librería llamado DHTtester, adaptarlo para el montaje de Arduino Uno pero usando dos sondas DHT11 en los pines 11 y 12

Solución: https://codeshare.io/arJ0nv

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio21-DHT

Clases y Objetos

Un paradigma es el resultado de un proceso social en el cual un grupo de personas desarrolla nuevas ideas y crea principios y prácticas alrededor de estas ideas, resumiendo: un paradigma es una metodología de trabajo.

En programación, se trata de un enfoque concreto de desarrollar y estructurar el desarrollo de programas.

Paradigma imperativo

  • Consiste en una secuencia de instrucciones que el ordenador debe ejecutar.
  • Los elementos más importantes en esta forma de programar son:
    • Variables, zonas de memoria donde guardamos información.
    • Tipos de datos, son los valores que se pueden almacenar.
    • Expresiones, corresponde a operaciones entre variables (del mismo o distinto tipo)

Paradigma funcional:

  • Consiste en el uso de funciones que realizan su tarea como si de una caja negra se tratase
  • Pese a que trabajamos con funciones, el modelo desarrollado hasta ahora con Arduino no verifica todos los requisitos del paradigma de programación funcional ya que, en nuestro caso existe el concepto de variable, que no se da en programación funcional. 

Paradigma orientado a objetos:

  • Es el más popular en la actualidad.
  • Se fundamenta en la “fusión” de datos y funciones que operan sobre esos datos dentro de un nuevo tipo de dato.
  • Al nuevo tipo de dato se le llama CLASE.
  • A cada variable de una clase se le llama OBJETO.

Propiedades del paradigma orientado a objetos

  • Encapsulamiento
    • Significa que los datos pertenecen a un objeto (espacio de nombres del objeto).
    • Podemos ir más allá y ocultar los datos de un objeto a cualquier otro objeto o código que trate de hacer uso de ellos. Serían sólo accesibles al propio objeto y, en algunos casos, a objetos de sus clases descendientes.
  • Herencia:
    • Es la propiedad de crear nuevos datos a partir de un objeto
  • Una clase es un nuevo tipo de dato. Contiene :
    • otros datos (que pueden ser de cualquier tipo)
    • Funciones, que operan sobre esos datos.

Las variables incluidas en una clase se denominan ATRIBUTOS.

Las clases pueden contener funciones. A éstas se les denomina MÉTODOS.

Una vez definida la clase, crear un objeto es tarea sencilla. Basta con ejecutar la instrucción de asignación: objeto = Nombre_clase ()

Programación Orientada a Objetos

La programación orientada a objetos (POO, u OOP según sus siglas en inglés) es un paradigma de programación que viene a innovar la forma de obtener resultados. Los objetos manipulan los datos de entrada para la obtención de datos de salida específicos, donde cada objeto ofrece una funcionalidad especial.

La modularidad es la capacidad de dividir el problema en pequeñas partes independientes entre sí, esto va más allá de la encapsulación de un simple procedimiento de la programación estructurada que hemos visto hasta ahora. Aquí se encapsula un conjunto de operaciones y datos que tienen mucha relación entre sí formando un módulo.

Un lenguaje basado en objetos nos da la posibilidad de definir objetos y realizar operaciones sobre ellos y es similar al paradigma de abstracción de datos. La adición de de los conceptos de de clases y herencia nos hace hablar de lenguajes orientados a objetos. Un objeto es la instancia de una clase.

Mediante la herencia podemos reutilizar el comportamiento de una clase en la definición de nuevas clases.

Muchos de los objetos pre-diseñados de los lenguajes de programación actuales permiten la agrupación en bibliotecas o librerías, sin embargo, muchos de estos lenguajes permiten al usuario la creación de sus propias bibliotecas.

Está basada en varias técnicas, incluyendo herencia, cohesión, abstracción, polimorfismo, acoplamiento y encapsulamiento.

Los objetos son entidades que tienen un determinado “estado”, “comportamiento (método)” e “identidad”:

  • La identidad es una propiedad de un objeto que lo diferencia del resto; dicho con otras palabras, es su identificador (concepto análogo al de identificador de una variable o una constante).
  • Los métodos (comportamiento) y atributos (estado) están estrechamente relacionados por la propiedad de conjunto. Esta propiedad destaca que una clase requiere de métodos para poder tratar los atributos con los que cuenta.

Una clase es una plantilla para la creación de objetos de datos según un modelo predefinido. Las clases se utilizan para representar entidades o conceptos. Cada clase es un modelo que define un conjunto de variables (el estado), y métodos apropiados para operar con dichos datos (el comportamiento). Cada objeto creado a partir de la clase se denomina instancia de la clase.

Las clases son un pilar fundamental de la programación orientada a objetos. Permiten abstraer los datos y sus operaciones asociadas al modo de una caja negra. Los lenguajes de programación que soportan clases difieren sutilmente en su soporte para diversas características relacionadas con clases. La mayoría soportan diversas formas de herencia.

C++ está diseñado para la programación orientada a objetos (POO), y en ese paradigma, todas las entidades que podemos manejar son objetos. Los punteros en C++ sirven para señalar objetos, y también para manipularlos.

La OOP tiene tres características básicas que se suelen presentar como Encapsulación, Polimorfismo y Herencia.

  • La encapsulación es un procedimiento por el cual, los datos y las funciones se encierran en un contenedor llamado objeto, que usamos para aislar ambos elementos de la manipulación exterior y forzar a que esta se haga de un modo controlado y validado por nosotros mismos. El mecanismo que C++ utiliza para encapsular estos objetos se llaman clases.
  • Se suele definir el polimorfismo como: Un interface único, múltiples métodos. Y es algo que reduce la complejidad de los programas de una forma notable. A usar las funciones u operadores de diferentes maneras dependiendo del tipo de los datos, le llamamos polimorfismo y cuando redefinimos un operador o función para comportarse de forma diferente con esos datos, decimos que el operador esta sobrecargado (Overloaded)- Por ejemplo en C++ podemos sumar números enteros o números float con el mismo símbolo “+”, y nos parece tan normal, pero en realidad los procedimientos que se aplican son completamente distintos.
  • La herencia es un proceso por el cual un objeto hereda las propiedades y métodos de otro, sin necesidad de volverlas a definir desde el principio.

Más información: http://www.alegsa.com.ar/Dic/programacion_orientada_a_objetos.php

Ejemplo de uso de un objeto: Imaginemos que hemos hecho un coche coche controlado por bluetooth y tenemos 5 botones para las 4 direcciones y parar. Creamos una clase llamada ‘Coche’ que se inicializa con los pines donde conecto los dos motores del coche.

#include “Coche.h”

Coche MiCocheRC(6,7)	//Construyo el objeto poniendo los pines de los motores

void setup() {
MiCoche.Arranca()
}

void loop() {
char valor = leeBluetooth();

switch (valor) {
    case ‘F’:
      MiCoche.Adelante();
      break;
    case ‘R’:
      MiCoche.Derecha();
      break;
    case ‘L’:
       MiCoche.Izquierda();
      break;
    case ‘B’:
       MiCoche.Atras();
      break;
    case ‘S’:
       MiCoche.Para();
      break;
  }
}

Dentro del fichero ‘Coche.h’ se define la clase y las variables y métodos. Dentro del fichero ‘Coche.cpp’ está el código para mover los motores según cada una de las 5 funciones que he definido.

La función leeBluetooth() simplemente lee valores que llegan por el puerto serie al que está conectado el bluetooth.

Para llamar a los métodos de un objeto y en función de cómo esté construida la librería se puede hacer de varias formas:

Más información sobre clases y objetos:

Clases y Objetos en Arduino

Antes de que empecemos a hablar sobre Clases y Objetos, es importante insistir en que, la OOP no es tanto un lenguaje de programación diferente, sino más bien, una manera diferente de organizar tus programas y tus ideas, de acuerdo con unos principios guía que nos permiten modelar nuestro código de un modo distinto a como lo hemos hecho hasta ahora.

Para definir las Clases, existen una serie de reglas y de nuevas instrucciones, pero por lo demás el lenguaje sigue siendo el de siempre.

Si queremos hacer un contador en Arduino, creamos una variable global llamada “contador”. Pero esto ilustra bastante bien el problema de que si quiero 6 contadores voy a necesitar 6 variables globales. La idea en OOP es crear una Clase que nos permita definir Objetos tipo Contador que se pueda reutilizar y que nos permita mezclar diferentes contadores en un mismo programa.

Una de las ideas básicas tras la OOP es encapsular los datos y las funciones (o propiedades y métodos) de nuestro programa en un contenedor común, y más importante aún, aplicamos el principio de: “Esconder los datos y mostrar los métodos o funciones”.

Cuando definimos Clases, veremos que hay partes que son públicas y otras que son privadas. La sintaxis para definir la Clase contador que nos ocupa:

class Contador{
  private:
    int N;
 
  public:
    void SetContador( int n){
      N = n;
    }
 
    void Incrementar(){
      N++;
    }
 
    int GetCont(){
      return (N);
    }
};

Debajo de la cláusula “private:” viene las variables y funciones ocultas al exterior. Solo pueden ser invocadas desde el interior de la clase, es decir no se pueden ejecutar por una llamada exterior. Y lo contrario ocurre con lo que definamos tras la cláusula “public:”

Vamos a definir una variable privada llamada N, que llevará la situación del contador, y después necesitaremos los métodos necesarios para trabajar con ella. En principio vamos a definir tres funciones públicas: Una que ponga el contador a un valor dado antes de nada, Otra que sirva para incrementar el contador, y otra tercera para que nos entregue el valor del contador en un momento dado.

La variable N se comporta como si fuera una variable global pero sólo dentro del ámbito de la Clase (Encapsulación), y como está definida como private es inaccesible desde el exterior (Cualquier intento de llegar a ella causará un error del compilador)

Para usar esta clase, primero se instancian tantas ocurrencias de la clase como queramos. 

#include "Contador.h"

Contador C1,C2;

void setup()
   {
      C1.SetContador(10);
      C2.SetContador(100);
   }

void loop()
   {
      C1.Incrementar() ;
      Serial.print("C1 = ") ; Serial.println(C1.GetCont());
 
      C2.Incrementar() ; C2.Incrementar() ; C2.Incrementar();
      Serial.print("C2 = ") ; Serial.println(C2.GetCont());
   }

Constructures

Usamos el “.” para referir la función a la que queremos llamar, con el Objeto al que se le aplica, como hemos visto antes en otros programas aunque sin entrar en muchos detalles.

Podemos definir una función que se ejecuta siempre que se crea un objeto, y es tan habitual que tiene nombre. Se le llama Constructor de la Clase, y para ello basta con llamarla igual que la Clase (Sin tipo):

 
Class Contador{
  private:
    int N;  
 
  public:
    Contador( ){    	// Constructor
      N = 0;
    }
  
    void SetContador(int n){
      N = n;
    }
 
    void Incrementar(){
      N++;
    }
 
    int GetCont(){
      return(N);
    }
};

Una peculiaridad de los constructores es que no tienen un tipo definido, otra de las razones por las que el compilador sabe que es un constructor.

En este caso cuando hago Contador C1,C2 ; ya está inicializado el contador a 0 que lo hace el propio constructor.

Ficheros Cabecera (Header)

Cuando las clases y las funciones miembro son tan pequeñas y sencillas como en este caso, la forma que hemos visto de definirlas puede valer, pero en seguida se quedará corta. Por eso podemos declarar las funciones y variables miembros en la declaración de Clase, y definirlas fuera para mayor comodidad y evitar errores de sintaxis complicados de detectar.

Podemos reescribir la clase Contador así:

Fichero “Contador.h”:

 
class Contador
   {  private:
         int N ;

      public:
         Contador( ) ;               	// Constructor
         void SetContador( int n) ;  	// Declaracion de funcion externa
         void Incrementar() ;        	// Declaracion de funcion externa
         int GetCont() ;             	// Declaracion de funcion externa
   } ;

Fichero “Contador.cpp”:

 
#include <Contador.h>
 
  void Contador::SetContador( int n)
        {  N = n ;	}

   void Contador::Incrementar()
        {  N++ ; }
 
   int Contador::GetCont()
        { return (N) ;}

Declaramos las funciones miembros dentro de la Clase (Para informar al compilador), pero no incluimos su código aquí, porque sería muy confuso en cuanto crezcan de tamaño (Pero fijaros que ahora hay un punto y coma al final de las declaraciones que antes no había en el fichero header).

En cualquier otro lugar podemos definir esas funciones sin más que hacer referencia a la Clase a la que pertenecen usando el operador ‘::’ (Scope Operator u Operador Ámbito) y el compilador entiende que son miembros de la clase que precede al operador. Este operador le indica al compilador, que estas funciones o variables son miembros de la clase, y sólo pueden invocarse de acuerdo a las condiciones que se especifican en la declaración de la Clase (Que debe coincidir con esta, claro está).

Si editais cualquiera de las librerías de Arduino, encontrareis que ésta es la forma habitual de programar las clases y librerías.

Cuando declaramos una propiedad miembro de una clase como static, el compilador crea una única variable para ella, que es compartida por todas las instancias de los objetos de esa Clase, rompiendo así la regla de que cada instancia tiene su propio juego de memoria y propiedades.

Polimorfismo y Sobrecarga

El polimorfismo podemos verlo en la clase Serial:

 
Serial.println(5) ;
Serial.println(3.1416 ) ;
Serial.println(“Buenos días”) ;

Según lo que hemos aprendido hasta ahora, una función solo puede aceptar un tipo definido de parámetros.  Es una característica inherente a C++ y que no existía en C, y no es otra que una característica llamada function overloading.

Gracias al Polimorfismo los println() anteriores funcionan, aunque todo indica que no deberían, porque va en contra de todo lo que hemos aprendido hasta ahora de las funciones. El misterio está en que no existe una única función println(), sino que las líneas anteriores invocan 3 funciones completamente diferentes… que se llaman igual.

Hacer un Overloading del Constructor, en la clase contador

 
class Contador
   {  private:
         int N ;

      public:	
         Contador( ) ;           	// Constructor
         Contador( int k ) ;     	// Constructor
         void SetContador( int n) ;
         void Incrementar() ;
         int GetCont() ;
   } ;

Contador::Contador( )          	// Constructor
    { N = 0 ; }       
Contador::Contador( int k)         // Constructor
    { N = k ; }   

Contador C1, C2(23) ;

Así podemos instar un objeto sin una variable y el contador empieza en 0 o con una variable entera y el contador empieza con ese valor. Hemos hecho un Overloading del Constructor de la Clase.

El function Overloading es un aspecto del Polimorfismo que nos permite manejar diferentes objetos con los mismos métodos o propiedades. El Polimorfismo es una cualidad abstracta de los objetos que nos permite usar un interface único, de métodos y propiedades, en una colección de objetos de distintos tipos o Clases.

Operadores

No solo se pueden sobrecargar las funciones, sino también los operadores para que hagan cosas diferentes en función del tipo de los operadores. De hecho, cuando definimos una nueva Clase, lo que estamos haciendo es crear un nuevo tipo de datos, tipo en el sentido de int, long, etc. y dentro de cada clase podemos hacer el Overloading de los operadores que nos interesen, para indicarle al compilador, cómo debe ejecutarse la operación que representa el símbolo del operador.

 
class Contador
   {  private:
         int N ;

      public:
         Contador( ) : N(0) {}    	            // Constructor
         Contador(int k ) : N(k) {}           	// Constructor
         void SetContador( int n) ;
         int GetCont() ;
         void operator ++ ();                     	// Aqui esta ++
   } ;

void Contador::SetContador( int n)  {  N = n ;	}
int  Contador::GetCont() { return (N) ;}
void Contador::operator ++ ()                     	//  <---
     {  ++N }

Usamos la keyword “operator”, para identificar el operador a definir y la definimos como void porque no devolvemos nada, simplemente incrementamos su valor. Ahora podemos hacer:

 
Contador C1(10)  ;
++C1 ;
Serial.println(C1.GetCont());

Para cada objeto declarado de una clase se mantiene una copia de sus datos, pero todos comparten la misma copia de las funciones de esa clase. Esto ahorra memoria y hace que los programas ejecutables sean más compactos, pero plantea un problema. Cada función de una clase puede hacer referencia a los datos de un objeto, modificarlos o leerlos, pero si sólo hay una copia de la función y varios objetos de esa clase, ¿cómo hace la función para referirse a un dato de un objeto en concreto?

La respuesta es: usando el puntero especial llamado this. Se trata de un puntero que tiene asociado cada objeto y que apunta a si mismo. Ese puntero se puede usar, y de hecho se usa, para acceder a sus miembros.

El operador “this” es un puntero que se pasa a disposición de todas las funciones miembro de la clase, (Y eso incluye a todas los funciones de operadores sobrecargados), que apunta al objeto al que pertenecen. Cuando instanciamos C1, cualquier función miembro que reclame el operador this, recibe un puntero a la dirección de memoria que almacena sus datos, que por definición es una la dirección del objeto C1.

 
const Contador &Contador::operator ++()
    { ++N;
      return *this ;
    }

Definimos la función operator ++ como tipo Contador porque va a devolver un objeto de este tipo. La particularidad está en que avisamos al compilador con el símbolo &, de que lo que vamos a devolver es un puntero a un objeto de la clase Contador, y no un objeto. Tras incrementar N, ya hemos realizado la operación que buscábamos y el objeto presente, por ejemplo C1, ya tiene el valor adecuado. Y ahora devolvemos el puntero a nuestra propia instancia del Objeto con la referencia que indica el operador this y de ese modo nos ahorramos el trasiego de crear y eliminar objetos temporales. Lo de especificar la función como const, es para evitar que al pasar la referencia de nuestro objeto actual, haya posibilidad de modificarlo por error.

Sumar contadores, podemos sumar los registros internos de dos contadores, de modo que el resultado sea otro contador con un valor de N interno igual a la suma de los dos operandos. No sería complicado definir una función miembro, que podemos llamar Suma, que devuelva un objeto Contador tras operar con dos contadores. Podríamos hacer algo así:

 
Contador  Contador::Suma( const Contador & C1 ) 
      {   return Contador  ( N + C1.GetCont() ) ; }

Aunque este método funcionará, su uso es un poco extraño :

 
Contador C1(), C2(23) ;
Contador C3 = C1.Suma(C2) ;

Pero si queremos escribir la suma así: Contador C3 = C1 + C2 ;  para eso está la sobrecarga de operadores binarios.

 
class Contador
  {   private:
     	int N ; 

  	public:
     	Contador( ) : N(0) {}                 // Constructor
     	Contador(int k ) : N(k) {}            // Constructor
     	void SetContador( int n) ;
     	int GetCont() ;                 
     	const Contador &operator ++ ();
     	Contador operator ++ (int) ;
         Contador operator + (  Contador &) ;  // Pasamos una referencia a un contador
  } ;

Contador  Contador::operator + (  Contador & C1 )
       	{ return Contador  (  N + C1.GetCont() ) ; }

Herencia

La herencia es la reusabilidad del código. Supongamos que ya tenemos probada y depurada la clase Contador y que ahora necesitamos una Clase nueva que en vez de ir creciendo sin fin sea un descontador, para que haga cuentas a cero desde el número que le damos, como para lanzar un cohete.

Podemos coger el código fuente de contador y modificarlo para incluir un decrementador del mismo, pero también podemos hacer uso de la herencia para que la nueva clase herede de la anterior. El método es derivar una nueva clase de una que ya existe. Esto hace que la clase derivada herede todas las características y métodos de la Clase Base sin tocarla y ahora podamos añadir lo que nos interese, garantizando que la Clase original permanece inalterada.

Queremos definir una nueva clase que se llame CountDown derivada de Contador y añadirle una función de decremento. Para ello lo primero es ver cómo derivamos una clase de otra. La sintaxis es esta:

 
Class CountDown :  public Contador          // Es una clase derivada
   {   public:
       	Counter Operator –()
           	{ return Counter(--N) ; 
   }

En la primera línea declaramos una nueva clase CountDown que deriva de Counter y es de acceso público, y después definimos un prefix operator para decrementar la variable interna. Aunque la sintaxis es buena, el compilador no tragaría con esto. Si te fijas en la definición de Contador, hemos definido N, el contador interno, como private, y eso significa que no permitirá el acceso a ninguna función externa a la clase Contador (Incluido CountDown), lo que nos hace imposible acceder desde la nueva clase derivada.

Para que podamos acceder a propiedades o métodos internos desde clase derivadas (Pero no desde cualquier otro medio), necesitamos definirlo no como private, sino como protected en la clase base:

 
class Contador
   {  protected:                    	// Aqui esta el truco
         int N ;
   
      public:
     	Contador( ) : N(0) {}      	// Constructor
     	Contador(int k ) : N(k) {} 	// Constructor
   
         int GetCont() 
        	{ return (N) ;      }
     
     	Contador operator ++()
     	  {  return Contador( ++N) ;	}
   } ;

Al definir N como protected, significa que podemos acceder a esta variable desde clases derivadas de ella, pero sigue siendo imposible acceder desde un programa externo. A esta capacidad de acceder a los miembros públicos o protected de una clase se le conoce genéricamente por accesibilidad.

 
void loop()
   { CountDown C1 ;
     ++C1; ++C1; ++C1;
     Serial.println (C1.GetCont()) ;

     --C1 ; --C1 ;
     Serial.println (C1.GetCont()) ;
   }

Una clase derivada hereda los  métodos y propiedades, de la clase original, (Que sean public o private, claro) y podemos usarlas sin problema, lo que le confiere una potencia inusitada para definir jerarquías conceptuales.

El compilador puede usar un constructor por defecto sin parámetros, pero cualquier otro debe ser definido en la clase derivada independientemente y no puedo usar CountDown C1(25) ;

 
class CountDown : public Contador
  {   public:
         CountDown( )  :  Contador() {}     	// Constructor
         CountDown(int k )  :  Contador(k) {}

         Contador operator -- ()
            {  return Contador( --N) ;       }
  } ;

Donde simplemente le especificamos al compilador que use los constructores disponibles en  la clase base (O definir unos completamente nuevos), y así podamos crear C1 con un valor especificado. Donde especificamos al compilador que cuando se cree una instancia de CountDown, debe invocar el constructor de la clase base que le indicamos. La primera podríamos omitirla porque ya sabemos que el compilador proporciona un constructor por defecto, pero es buena política definirlo aquí para evitar sobresaltos.

Function Overriding

Hemos visto que podemos definir nuevos constructores porque el compilador no aplicará per se más que el default constructor sin parámetros, y también  hemos visto que las funciones disponibles en la clase original están gentilmente a disposición de las clases derivadas, pero puede ocurrir que nos interese redefinir una de ellas para que funcione de otra manera en la nueva clase derivada. A esta capacidad de redefinir una función miembro con el mismo nombre se le llama Function Overriding.

Podemos forzar un Override de la función miembro GetCont (), en nuestra clase CountDown para hacer que nos devuelva el doble del valor interno del contador.

 
class CountDown : public Contador
   {  public:
         CountDown( ) : Contador() {}     	// Constructor
         CountDown(int k ) : Contador(k) {}

         Contador operator -- ()
             {   return Contador( --N) ;	}

         int GetCont()
             {   return(2*N) ; }
   } ;

En donde simplemente creamos una nueva función miembro de CountDown con el mismo nombre y distinta ejecución.

Destructores

Los destructores son funciones miembro especiales que sirven para eliminar un objeto de una determinada clase. El destructor realizará procesos necesarios cuando un objeto termine su ámbito temporal, por ejemplo liberando la memoria dinámica utilizada por dicho objeto o liberando recursos usados, como ficheros, dispositivos, etc.

Al igual que los constructores, los destructores también tienen algunas características especiales:

  • También tienen el mismo nombre que la clase a la que pertenecen, pero tienen el símbolo ˜ delante.
  • No tienen tipo de retorno, y por lo tanto no retornan ningún valor.
  • No tienen parámetros.
  • No pueden ser heredados.
  • Deben ser públicos, no tendría ningún sentido declarar un destructor como privado, ya que siempre se usan desde el exterior de la clase, ni tampoco como protegido, ya que no puede ser heredado.
  • No pueden ser sobrecargados, lo cual es lógico, puesto que no tienen valor de retorno ni parámetros, no hay posibilidad de sobrecarga.

Cuando se define un destructor para una clase, éste es llamado automáticamente cuando se abandona el ámbito en el que fue definido. Esto es así salvo cuando el objeto fue creado dinámicamente con el operador new, ya que en ese caso, cuando es necesario eliminarlo, hay que hacerlo explícitamente usando el operador delete. En general, será necesario definir un destructor cuando nuestra clase tenga datos miembro de tipo puntero, aunque esto no es una regla estricta.

Ejemplo:

 
#include <iostream>
#include <cstring>
using namespace std;
 
class cadena {
  public:
   cadena();        // Constructor por defecto
   cadena(const char *c); // Constructor desde cadena c
   cadena(int n);   // Constructor de cadena de n caracteres
   cadena(const cadena &);   // Constructor copia
   ~cadena();       // Destructor

   void Asignar(const char *dest);
   char *Leer(char *c);
  private:
   char *cad;       // Puntero a char: cadena de caracteres
};
cadena::~cadena() {
   delete[] cad;        // Libera la memoria reservada a cad
}

Más información:

Clase Serial Arduino

Un ejemplo de clase es una que usamos habitualmente, la clase Serial: https://www.arduino.cc/en/Reference/Serial que está definida en los ficheros:

Por lo tanto cuando estamos Serial.begin(9600) estamos llamando al método begin del objeto Serial. En este caso no hemos hecho un include del fichero donde está incluida esta clase porque el IDE lo incluye automáticamente en el proceso de compilación ni tampoco hacemos una declaración del objeto con el constructor, puesto que también lo hace el IDE de Arduino.

Si vemos los ficheros donde está la clase HardwareSerial, vemos que se  trata de una clase heredada de la clase base Stream (class HardwareSerial : public Stream). La clase Stream está definida en el reference de Arduino en https://www.arduino.cc/en/Reference/Stream que es la clase de la que heredan otras clases como Serial, Wire, Ethernet Client, Ethernet Server y SD.

Vemos que los métodos de la clase Stream son los mismo que para Serial, Ethernet, etc… y por lo tanto nos es más fácil entender las clases derivadas si entendemos la clase base.

Un ejemplo de function overriding lo vemos con el método flush() que en la clase Stream sun función el limpiar el buffer una vez que todos los caracteres han sido enviados, pero en el caso de la clase derivada Serial, el método flush() lo que hace es esperar a la transmisión de todos los caracteres hasta su finalización.

La clase base Stream está definida en los ficheros:

De la librería Stream heredan muchas otras librerías https://www.arduino.cc/en/Reference/Stream

Prácticas: Funciones Definidas por Usuario

Montaje Arduino UNO:

Montaje Wemos:

Ejercicio15 – Funciones

Hacer un menú interactivo con Arduino. Con todo lo visto anteriormente, hacer un ejemplo de un menú interactivo donde se dan 4 opciones y pulsando cada una de ellas se ejecuta una acción concreta. Si el valor pulsado no es ninguna de las opciones avisar y volver a mostrar el menú hasta que se pulse una opción correcta. Usar funciones para cada una de las opciones.

Opciones:

  • 1 – Encender led siguiente (paso por referencia la posición del led)
  • 2 – Sacar por pantalla el LCD que está encendido
  • 3 – Sonar el buzzer 5 segundos
  • 4 – Fin (entra en un bucle infinito y no sale)

Solución: https://codeshare.io/5NYRvm

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio15-Funciones

Ejercicio16 – Función Detecta flanco

Señales digitales:

Hacer una función que detecte flancos ascendentes y otras flancos descendentes, para ser reutilizada en otros proyectos.

Unificar estas dos funciones en una única función llamada detectaFlanco() donde le paso el pin y devuelve 1 si es flanco ascendente, -1 si es flanco descendente y 0 si no hay cambio de estado.

Ponerla en un ejemplo con alguno de los botones, usando este loop:

 
void loop() {
  int flanco = detectaFlanco(PIN_BOTON_A);
  if (flanco == 1)
    Serial.println("flanco ascendente");
  if (flanco == -1)
    Serial.println("flanco descendente");
}

Solución: https://codeshare.io/amkrV1

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio16-Funcion_Detecta_Flanco

Ejercicio17 – Función Detecta flanco dos pines

Para ejercicio detecta flanco, probar la función con los dos botones en los pines 2 y 3. La función detecta flanco solo funciona con un pulsador, pero cuando se intenta usar con dos pulsadores ya no funciona. Comprobar porqué.

Solución: https://codeshare.io/5NYrqr

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio17-Funcion_Detecta_Flanco_2Pines

La función para detectar flanco es la base para luego entender las clases y objetos y luego las librerías.

La solución es crear un objeto detecta flanco, para ello crear una clase y se puede distribuir mediante una librería como https://github.com/jecrespo/Detecta_Flanco_Libreria que se puede descargar desde https://github.com/jecrespo/Detecta_Flanco_Libreria/releases/tag/Version_1.0:

#include <DetectaFlanco.h>
#define PIN_BOTON_A 2
#define PIN_BOTON_B 3

DetectaFlanco df1(PIN_BOTON_A);
DetectaFlanco df2(PIN_BOTON_B);

void setup() {
  Serial.begin(9600);
  df1.inicio(INPUT_PULLUP);
  df2.inicio(INPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
  int flanco1 = df1.comprueba();
  int flanco2 = df2.comprueba();

  if (flanco1 == 1)
    Serial.println("Flanco asc A");

  if (flanco1 == -1)
    Serial.println("Flanco desc A");

  if (flanco2 == 1)
    Serial.println("Flanco asc B");

  if (flanco2 == -1)
    Serial.println("Flanco asc B");

  delay(50); //Evitar rebotes
}

Ejercicio18 – Dado Digital

Usando las funciones de números aleatorios hacer un dado digital que genere un número aleatorio entre 1 y 6 y encienda un led aleatorio cada vez que se pulse el botón A. Usar el montaje del Wemos D1 mini

Usar la función de detección de flanco hecha en el anterior ejercicio.

Random Numbers

  • randomSeed() – Inicializa el generador de número pseudo-aleatorios
  • random() – Genera números pseudo-aleatorios

Paso 1 – Generar un valor aleatorio entre 1 y 6 al pulsar el botón

Paso 2 – Hacer girar el anillo led haciendo el efecto y que baje la velocidad

Paso 2 – Dejar fijo el nuevo número aleatorio

Solución: https://codeshare.io/anmypv

Hacer commit y pull del código en el repositorio “Curso Programacion Arduino 2019” que esté en una carpeta llamada Ejercicio18-Dado