Por cuestiones de trabajo suelo ausentarme de mi casa y a pesar de tener sistemas de cámaras uno no siempre esta viendo el móvil para saber que esta pasando es por eso que se me ocurrió un pequeño sistema que conectado a las puertas de acceso me envía un mensaje por Telegram avisando de que tal puerta se ha abierto o cerrado. El sistema tiene ademas una interfaz web para cambiar la configuración por ejemplo la red Wi-Fi o las credenciales Telegram, para esto un pequeño botón que al ser accionado borra el archivo de configuración y envía un reset a la placa que inicia en modo setup. Desde luego que para todo esto tenemos que tener instalado en el móvil la aplicación de Telegram. Lo primero que hacemos es crear un grupo dentro de Telegram y luego mediante el BotFather creamos un bot que agregamos al grupo creado.
El proceso de crear un bot es muy sencillo y la propia aplicación te guía de como hacerlo con el comando /newbot. Pero lo importante aquí es el Token, algo parecido a esto 123456:ABC-DEF1234ghIkl-zyx57W2v1u123ew11 este será el ID del bot y lo necesitará para configurar el acceso y poder enviar mensajes al bot. Lo siguiente es obtener el ID del usuario, es importante que este bot este agregado al grupo, luego desde un computador enviamos un mensaje al bot y tomamos nota del número ID que aparece en la barra del navegador. Observe la siguiente imagen donde se puede ver el nombre de mi bot y el ID correspondiente (no olvide el signo menos).
Tenemos los datos necesarios para conectar con Telegram y el bot, ahora solo necesitamos un programa en Pico W que haga el trabajo. La aplicación que construí puede funcionar de dos formas.
Modo Usuario: Controla el estado de las puertas asignadas.
Modo Programación: El sistema crea una red Wi-Fi propia abierta con el SSID firadmin. En esa red se accede http://firtec.net que básicamente es una página de configuración donde se cargan las credenciales de acceso tanto a la red Wi-Fi adonde se conectará Pico W como las credenciales de acceso a Telegram.
En la siguiente imagen se puede ver el aspecto de la página web de configuración para el sistema.
Una vez que los datos son salvados la red firadmin desaparece y el sistema pasa al modo usuario intentando validar las credenciales de acceso a la red Wi-Fi y cuando sea necesario las credenciales de Telegram. Para entrar al modo programación se ha colocado un pequeño pulsador en el GPIO_16 que si se oprime el sistema se desconecta de la red Wi-Fi, borra todas las credenciales y pasa a modo programación. Para detectar la actividad de las puertas se usaron sensores magnéticos que activan una interrupción por cambio de estado en el GPIO_11.
defISR_11(p):globalbanderaglobalenviadoif(sensor.value()==0):bandera=2led_azul.value(1)if(sensor.value()==1):bandera=1enviado=0led_azul.value(1)if(bandera==1andenviado==0):send_message(wifi_credentials['telegramDmUid'],text_1)enviado=1wdt.feed()# Borra el WD print("Puerta de calle Abierta!!")if(bandera==2andenviado==1):wdt.feed()# Borra el WD send_message(wifi_credentials['telegramDmUid'],text_2)print("Puerta de calle Cerrada!!")enviado=3
Para el servidor web y todo lo referente a los procesos web usaremos el módulo phew.
Módulo phew.
Es un pequeño servidor web y una biblioteca de plantillas ya diseñadas específicamente para Micropython en Pico W. Su objetivo es proporcionar un juego de herramientas completo para crear fácilmente interfaces basadas en la web de alta calidad para todos los proyectos que requieran soporte web. Es un módulo ideal para crear interfaces de configuración basadas páginas web embebidas que se conectan con sistemas electrónicos.
Que podemos hacer con phew:
Un servidor web básico optimizado para la velocidad y recursos de pico w.
Uso mínimo de los recursos de memoria y CPU.
Motor de plantilla que permite expresiones de Python en línea con los métodos Get y Post.
Soporte de conexión Wi-Fi.
Como en ejemplos anteriores lo instalamos en la memoria de pico w usando el administrador de paquetes de Thonny.
Como trabaja el ejemplo propuesto.
Cuando el sistema ha sido configurado crea un archivo que en mi caso he llamado wifi.json y dentro de este archivo encontramos los siguientes datos.
Estos datos son las credenciales de acceso tanto a la red Wi-Fi como al bot de Telegram. Este archivo será borrado automáticamente si se oprime el botón de programación cuando el sistema pasa a modo programación y es por eso que cuando el sistema inicia la primer tarea es verificar que este archivo está en memoria, si no lo encuentra pasa automáticamente al modo programación. La presencia de este archivo determina en que modo arranca el sistema. Continuando con el encabezado del programa podemos ver las declaraciones de las variables usadas como el son el punto de acceso que se crea cuando el sistema es programado, el nombre del dominio para acceder a la página de programación etc.
text_1='Puerta de calle abierta!!!'text_2='Puerta de calle cerrada!!!'AP_NAME="firadmin"# Nombre del punto de accesoAP_DOMAIN="firtec.net"# Nombre del dominioAP_TEMPLATE_PATH="ap_templates"# Carpetas sitios htmlAPP_TEMPLATE_PATH="app_templates"# Carpetas sitios htmlWIFI_FILE="wifi.json"#Donde se guardan las credencialesLOG_FILE="log.txt"WIFI_MAX_ATTEMPTS=3bandera=0enviado=0sensor=Pin(11,Pin.IN,Pin.PULL_UP)boton=Pin(16,Pin.IN,Pin.PULL_UP)
Se han colocado unos indicadores LED´s para conocer el estado operativo del sistema. En este caso el indicador verde indica que el sistema está conectado a la red Wi-Fi y el indicador rojo indica que el sistema está en modo programación.
El código completo de la aplicación es el siguiente.
fromphewimportaccess_point,connect_to_wifi,is_connected_to_wifi,dns,serverfromphew.templateimportrender_templateimportjsonimportmachineimportosimportutimeimport_threadimportustructasstructfrommachineimportPinfrommachineimportWDTimportrp2importnetworkimportubinasciiimporturequestsasrequestsimporttimewlan=network.WLAN(network.STA_IF)wlan.active(True)wdt=''led_rojo=Pin(22,Pin.OUT)led_rojo.value(0)red_ok=Pin(21,Pin.OUT)red_ok.value(0)led_azul=Pin(20,Pin.OUT)led_azul.value(0)text_1='Puerta del Quincho Abierta!!!'text_2='Puerta del Quincho Cerrada!!!'AP_NAME="firadmin"#Nombre del punto de accesoAP_DOMAIN="firtec.net"#Nombre del dominioAP_TEMPLATE_PATH="ap_templates"APP_TEMPLATE_PATH="app_templates"WIFI_FILE="wifi.json"#Donde se guardan las credencialesLOG_FILE="log.txt"WIFI_MAX_ATTEMPTS=3bandera=0enviado=0sensor=Pin(11,Pin.IN,Pin.PULL_UP)boton=Pin(16,Pin.IN,Pin.PULL_UP)defISR_11(p):globalbanderaglobalenviadoif(sensor.value()==0):bandera=2led_azul.value(1)if(sensor.value()==1):bandera=1enviado=0led_azul.value(1)if(bandera==1andenviado==0):send_message(wifi_credentials['telegramDmUid'],text_1)enviado=1wdt.feed()print("Puerta de calle Abierta!!")if(bandera==2andenviado==1):wdt.feed()send_message(wifi_credentials['telegramDmUid'],text_2)print("Puerta de calle Cerrada!!")enviado=3sensor.irq(trigger=Pin.IRQ_RISING|Pin.IRQ_FALLING,handler=ISR_11)defmachine_reset():utime.sleep(1)print("Reseteando sistema...")machine.reset()defborrar():# Borrar el archivo de configuracion y hacer un reboot# como acces point.os.remove(WIFI_FILE)# Reboot en un hilo nuevo_thread.start_new_thread(machine_reset,())red_ok.value(0)defsetup_mode():led_rojo.value(1)red_ok.value(0)print("Entra al modo setup...")defap_index(request):ifrequest.headers.get("host").lower()!=AP_DOMAIN.lower():wdt.feed()# Borra el WD returnrender_template(f"{AP_TEMPLATE_PATH}/redirect.html",domain=AP_DOMAIN.lower())returnrender_template(f"{AP_TEMPLATE_PATH}/index.html")defap_configure(request):print("Salvando las credenciales del wifi...")withopen(WIFI_FILE,"w")asf:json.dump(request.form,f)f.close()# Reboot en un hilo nuevo_thread.start_new_thread(machine_reset,())returnrender_template(f"{AP_TEMPLATE_PATH}/configured.html",ssid=request.form["ssid"])defap_catch_all(request):ifrequest.headers.get("host")!=AP_DOMAIN:returnrender_template(f"{AP_TEMPLATE_PATH}/redirect.html",domain=AP_DOMAIN)return"Not found.",404server.add_route("/",handler=ap_index,methods=["GET"])server.add_route("/configure",handler=ap_configure,methods=["POST"])server.set_callback(ap_catch_all)ap=access_point(AP_NAME)ip=ap.ifconfig()[0]dns.run_catchall(ip)#-------- Envía el mensaje a Telegram ---------------defsend_message(chatId,message):wdt.feed()# Borra el WD -------response=requests.post(sendURL+"?chat_id="+wifi_credentials['telegramDmUid']+"&text="+message)print('Mensaje Enviado')wdt.feed()# Borra el WD -------response.close()# IMPORTANTE!! Cerrar para no desbordar la RAMdefapplication_mode():globalwdtled_azul.value(0)wdt=WDT(timeout=8000)# Habilita el watchdog para 8 Segundoswdt.feed()# Borra el WD led_rojo.value(0)globalsendURLsendURL='https://api.telegram.org/bot'+wifi_credentials['botToken']+'/sendMessage'print("Entrando en modo aplicacion.")whileTrue:wdt.feed()# Borra el WD led_azul.value(0)if(boton.value()==0):borrar()# En que modo debe iniciar...try:os.stat(WIFI_FILE)withopen(WIFI_FILE)asf:wifi_current_attempt=1wifi_credentials=json.load(f)while(wifi_current_attempt<WIFI_MAX_ATTEMPTS):ip_address=connect_to_wifi(wifi_credentials["ssid"],wifi_credentials["password"])ifis_connected_to_wifi():print(f"Conectado con la IP {ip_address}")breakelse:wifi_current_attempt+=1ifis_connected_to_wifi():red_ok.value(1)application_mode()else:print("ERROR!")print(wifi_credentials)#os.remove(WIFI_FILE) # Esta linea hace que pase al setup si falla wifi habilitar si # queremos que esto pase, machine_reset()exceptException:setup_mode()server.run()
El archivo index.html tiene el siguiente contenido.
<!DOCTYPE html><html><metacharset="UTF-8"><bodystyle=background:#F0FFFF><FONTCOLOR="black"><center><head><metaname="viewport"content="width=device-width, initial-scale=1"><title>SETUP</title></head><body><h1>Setup del Sistema.</h1><h2>Credenciales de acceso:</h2><formaction="/configure"method="POST"autocomplete="off"autocapitalize="none"><labelfor="ssid">Red Wi-Fi:</label><br><inputtype="text"id="ssid"name="ssid"><br><labelfor="password">Password:</label><br><inputtype="text"id="password"name="password"><br><labelfor="telegramDmUid">Telegram ID:</label><br><inputtype="text"id="telegramDmUid"name="telegramDmUid"><br><labelfor="botToken">Token Telegram:</label><br><inputtype="text"id="botToken"name="botToken"><br><br><button>Salvar los Datos</button><hrSize=5noshade/><H5><fontcolor='black'>by. Firtec Argentina </H5></form></body></center></html>
En la carpeta ap_templates también vamos a necesitar el archivo configured.py que tiene el siguiente contenido.
<!DOCTYPE html><html><head><metaname="viewport"content="width=device-width, initial-scale=1.0"><title>Wifi Configured</title></head><body><h1>Wifi Configured</h1><p>The Raspberry Pi Pico will now reboot and attempt to connect to the "{{ssid}}"wireless network...</p></body></html>
También el archivo redirect.html con el contenido.
Recuerde que en wifi_credentials se encuentran las credenciales de acceso tanto para el Wi-Fi como para Telegram. Cuando el mensaje se envía se extraen las credenciales de la siguiente forma send_message(wifi_credentials['telegramDmUid'], text_1) por ejemplo para el primer mensaje. El método que envía el mensaje es bastante simple y también lee el Token Telegram desde el archivo de credenciales. En la siguiente imagen se puede ver la estructura de archivos que debe contener el proyecto.
En lo personal he editado la biblioteca para eliminar la creación del archivo log.txt que eventualmente podría crecer demasiado con el tiempo y ocupar mucha memoria. Una vez que el sistema esta funcionado los mensajes enviados tienen el siguiente aspecto.
Todo el sistema lleva funcionando mas de un año en distintas puertas sin problemas enviando mensajes a mi móvil. Si tiene alguna duda puede consultarnos en Esta dirección de correo electrónico está siendo protegida contra los robots de spam. Necesita tener JavaScript habilitado para poder verlo.. Este y otros muchos ejemplos se encuentran en nuestro libro "Electrónica con MicroPython".
Si usted esta programando PIC, ATMEL, ARDUINO en lenguaje C sin duda es momento de investigar un poco sobre MicroPython. El continuo avance en el hardware de los microcontroladores hace que la línea que los separa de los microprocesadores sea cada vez mas borrosa. Al tener microcontroladores mas rápidos y con capacidad para gestionar mucha mas memoria ahora es posible correr programas que son derivaciones directas de los entornos informáticos. Es el caso de MicroPython, una derivación de lenguaje Python 3 escrita en C. MicroPython es un compilador completo del lenguaje Python y un motor e intérprete en tiempo de ejecución, que funciona en el hardware del microcontrolador. Incluye una selección de bibliotecas y módulos que permiten al programador el acceso completo al hardware en bajo nivel.
MicroPython lo creó originalmente el programador y físico australiano Damien George y soporta un amplio número de arquitecturas basadas en ARM, siendo de código libre y actualizado constantemente, en la actualidad es una de las herramientas de programación que se perfila como de gran desarrollo y uso común en el futuro inmediato.
Con MicroPyton tenemos el control de la electrónica que ofrece C pero con el agregado de las bibliotecas y módulos de Python 3. Otra gran ventaja es que al ser un lenguaje interpretado no se necesita de un compilador y un sistema operativo especifico, tampoco necesitamos licencias de programas. Podemos escribir un código completo en un editor de texto plano, tomar el archivo escrito agregarle la extensión py y simplemente transferirlo a la memoria del microcontrolador, con esto tendremos el controlador programado y funcionando.
Esto suena bien pero puedo hacer esto con cualquier microcontrolador? La respuesta es NO, para poder usar MicroPython necesitamos un microcontrolador con una arquitectura que permita manejar archivos. Esto es un microcontrolador rápido y con capacidad para gestionar grandes cantidades de memoria RAM y FLASH. Es necesario cargar en esta memoria el propio interprete MicroPython, es decir que antes de que el microcontrolador ejecute el código que el programador a escrito el interprete debe estar en memoria para poder “traducir” lo que el programador a escrito a un nivel entendible por la propia CPU del controlador.
Tenemos en la actualidad opciones como el microcontrolador RP2040 que viene ya montado en placas Arduino y Raspberry Pico entre otras. Si compara el costo de una Raspberry Pico con un Arduino UNO, verá que es mucho mas conveniente usar Raspberry Pico. El controlador RP2040 tiene a bordo dos CPU ARM Cortex M0 trabajando a 133 Mhz, 264 KB de memoria RAM y 2 MB de memoria FLASH, puertos I2C, SPI, UART, etc. Como se puede ver hay una gran diferencia con un Arduino UNO. Una de las criticas que se suele escuchar sobre MicroPython es que al ser un lenguaje interpretado el código escrito por el programador esta en texto plano lo que hace que sea sencillo copiarlo o duplicarlo pero podríamos tomar un trozo del código y encriptarlo en una memoria exterior de tal forma que si alguien copia el programa principal tendrá también que reescribir los métodos encriptados para que todo funcione. Otra forma de proteger el código sería compilar y generar un archivo uf2 y descargar este archivo en la memoria de Pico, esto no solo ofrece una protección ante la copia del código, también mejora la velocidad de ejecución ya que al ser MicroPython un lenguaje interpretado para que el código llegue a ejecutarse en la CPU se necesitan algunos pasos extra si lo comparamos con un código compilado donde el interprete ya no es necesario.
Sin embargo con estos nuevos microcontroladores como el RP2040 tenemos el control y manejo de sensores y electrónica que tendríamos con un Arduino, PIC, Atmel, etc pero al ser MicroPython un lenguaje mas limpio es mucho mas sencillo de aprender lo que significa tiempos de aprendizaje y estudio mucho mas cortos. Si necesitamos conectividad TCP-IP, algo absolutamente necesario si vamos a trabajar en plataformas de IOT, entonces tenemos Pico W que suma a lo anterior un enlace WiFi muy simple de usar. Con Pico W tenemos la posibilidad de desarrollar aplicaciones con páginas web embebidas. El poder desarrollar este tipo de aplicaciones son muy interesantes puesto que no necesitamos de costosas pantallas gráficas para desplegar información, la pantalla gráfica la tiene el cliente con su móvil, tableta o computadora además de poder acceder al dispositivo desde cualquier lugar del mundo. Se podría por ejemplo acceder a variables de campo mediante un microcontrolador RP2040 consultando el estado de sensores y actividad de terreno para publicar los datos en la red mediante su propio enlace WiFi. Con estos nuevos microcontroladores y MicroPython el desarrollar sistemas de enlaces TCP-IP, bases de datos, control de casas inteligentes o sistemas domóticos es ahora mucho mas sencillo y sobre todo económico. Todos estos temas y muchos otros los encontrará explicados en nuestro libro "Electrónica con MicroPython".
Puede consultarnos en Esta dirección de correo electrónico está siendo protegida contra los robots de spam. Necesita tener JavaScript habilitado para poder verlo.
Originalmente mi casa tiene un timbre que se instaló hace muchos años, tiene un sonido muy agradable con el clásico “ding-dong” pero por su edad es electro-mecánico con un solenoide que empuja una pequeña barra metálica que a su vez golpea una campana que produce el sonido. Realmente funciona muy bien y me gusta el sonido pero para su funcionamiento tiene que estar cableado con el interruptor que se encuentra en la calle. Sumado a que su sonido no llega a todos los puntos de la casa y mucho menos a la planta alta y aprovechando el descanso de verano me propuse mejorar el alcance de este timbre pero sin cambiarlo de lugar ni cambiar el circuito eléctrico, una solución sería subir el estado del timbre a un enlace inalámbrico que será leído desde cualquier parte de la casa. Como no puedo reproducir el exacto sonido que tiene el timbre original por ser mecánico se me ocurrió construir un receptor que cuando reciba información de que hay alguien en la puerta “hable” mediante un sintetizador de voz diciendo la novedad que hay gente tocando el timbre. En realidad todo el proyecto ha sido construido como hobby reciclando componentes de proyectos anteriores, el emisor se colocó en una pequeña caja plástica junto al timbre original y todo el receptor se colocó en una caja que contiene parlante y amplificador de un viejo sistema multimedia en desuso. Para el enlace de radio un par de APC220 también sobrantes de otro proyecto pero cualquier enlace de radio funcionará ya que solo debe comunicarle al receptor la activación del timbre. Para la detección del voltaje en el solenoide del timbre usé un PC817 conectado a un transistor BC337. Este transistor coloca un nivel bajo el pin IRQ 2 de un Arduino Nano disparando una interrupción. Cuando la interrupción se dispara se envía un un byte comando por el enlace de radio, cuando el receptor recibe la transmisión decodifica el mensaje y si está el byte comando envía al sintetizador de voz la frase o mensaje que debe reproducir. En lugar del sintetizador de voz se podría conectar un piezoeléctrico o cualquier sistema para avisar que el timbre de calle se activó, yo use un sintetizador porque ya lo tenía. En el emisor un Arduino Nano y en el receptor un Arduino Mega Pro ya que necesitaba algo pequeño para colocar en la caja del parlante y el Nano no puede manejar el sintetizador de voz.
Circuitos de emisor y receptor.
El código del emisor contenido en el Arduino Nano es bastante simple, solo debe enviar un byte comando cada vez que ocurra una interrupción en el Pin 2.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/********************************************************************* Recoge el estado de activación de la campanilla y envía la novedad* por un enlace de radio.*********************************************************************/#include <avr/wdt.h>#include <avr/sleep.h>#define ledPin 13#define power_tx 4byteTimbre_SI=0x55;// Código para indicar que el timbre se activóvoidsetup(){pinMode(2,INPUT_PULLUP);Serial.begin(9600);pinMode(13,OUTPUT);digitalWrite(13,LOW);pinMode(power_tx,OUTPUT);digitalWrite(power_tx,LOW);attachInterrupt(0,ISR_Timbre,LOW);wdt_disable();wdt_enable(WDTO_4S);}voidISR_Timbre(){wdt_reset();delay(2500);digitalWrite(power_tx,HIGH);Serial.write(Timbre_SI);digitalWrite(13,HIGH);}voidloop(){digitalWrite(13,LOW);digitalWrite(power_tx,LOW);wdt_reset();}
Para asegurar el correcto funcionamiento tanto el emisor como el receptor tienen el Watch Dog del microcontrolador activado con un tiempo fijo de 4 segundos mas que suficiente para mantener el sistema controlado. El código del Arduino Mega es el siguiente.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
#include <avr/pgmspace.h>#include <avr/wdt.h>#include <SPI.h>#include <Wire.h>#include <string.h>#include <math.h>#include "text_to_speech_img.h"#define S1V30120_RST 49#define S1V30120_RDY 10#define S1V30120_CS 53#define S1V30120_MUTE A0//------------------------------------------#define F_CPU 16000000#define USART_BAUDRATE 9600#define UBRR_VALUE (((F_CPU / (USART_BAUDRATE * 16UL))) - 1)#define led 13#define sirena 4volatilebytedato=0;volatilebytebandera=0;// Define parameteros para S1V30120#define ISC_VERSION_REQ 0x0005#define ISC_BOOT_LOAD_REQ 0x1000#define ISC_BOOT_RUN_REQ 0x1002#define ISC_TEST_REQ 0x0003#define ISC_AUDIO_CONFIG_REQ 0x0008#define ISC_AUDIO_VOLUME_REQ 0x000A#define ISC_AUDIO_MUTE_REQ 0x000C#define ISC_TTS_CONFIG_REQ 0x0012#define ISC_TTS_SAMPLE_RATE 0x01#define ISC_TTS_VOICE 0x00#define ISC_TTS_EPSON_PARSE 0x01#define ISC_TTS_LANGUAGE 0x04 /// Lenguaje español#define ISC_TTS_SPEAK_RATE_LSB 0xC8#define ISC_TTS_SPEAK_RATE_MSB 0x00#define ISC_TTS_DATASOURCE 0x00#define ISC_TTS_SPEAK_REQ 0x0014#define ISC_VERSION_RESP 0x0006#define ISC_BOOT_LOAD_RESP 0x1001#define ISC_BOOT_RUN_RESP 0x1003#define ISC_TEST_RESP 0x0004#define ISC_AUDIO_CONFIG_RESP 0x0009#define ISC_AUDIO_VOLUME_RESP 0x000B#define ISC_AUDIO_MUTE_RESP 0x000D#define ISC_TTS_CONFIG_RESP 0x0013#define ISC_TTS_SPEAK_RESP 0x0015#define ISC_ERROR_IND 0x0000#define ISC_MSG_BLOCKED_RESP 0x0007#define ISC_TTS_FINISHED_IND 0x0021#define TTS_AUDIO_CONF_AS 0x00#define TTS_AUDIO_CONF_AG 0x43#define TTS_AUDIO_CONF_AMP 0x00#define TTS_AUDIO_CONF_ASR 0x01#define TTS_AUDIO_CONF_AR 0x00#define TTS_AUDIO_CONF_ATC 0x00#define TTS_AUDIO_CONF_ACS 0x00#define TTS_AUDIO_CONF_DC 0x00//-----------------------------------------------------voidS1V30120_reset(void);unsignedshortS1V30120_get_version(void);boolS1V30120_download(void);boolS1V30120_load_chunk(unsignedshort);boolS1V30120_boot_run(void);voidshow_response(bool);boolS1V30120_registration(void);boolS1V30120_parse_response(unsignedshort,unsignedshort,unsignedshort);voidS1V30120_send_padding(unsignedshort);voidS1V30120_send_message(volatilechar,unsignedchar);boolS1V30120_configure_audio(void);boolS1V30120_configure_tts(void);boolS1V30120_speech(String,unsignedchar);boolS1V30120_set_volume(void);boolS1V30120_load_chunk(unsignedshort);Stringm0="Atencion atencion";Stringm1="Estan tocando el timbre";Stringm2="Sistema listo y atento";charrcvd_msg[20]={0};staticvolatilecharsend_msg[200]={0};staticvolatileunsignedshortmsg_len;staticvolatileunsignedshorttxt_len;unsignedshorttmp;longidx;boolsuccess;staticvolatileunsignedshortTTS_DATA_IDX;voidsetup(){pinMode(led,OUTPUT);digitalWrite(led,LOW);pinMode(S1V30120_RST,OUTPUT);pinMode(S1V30120_RDY,INPUT);pinMode(S1V30120_CS,OUTPUT);pinMode(S1V30120_MUTE,OUTPUT);// UnmutedigitalWrite(S1V30120_MUTE,LOW);// Configura los baudiosUBRR2H=(uint8_t)(UBRR_VALUE>>8);UBRR2L=(uint8_t)UBRR_VALUE;UCSR2C|=(1<<UCSZ20)|(1<<UCSZ21);UCSR2B|=(1<<RXEN2)|(0<<TXEN2)|(1<<RXCIE2);UCSR2A=0x00;interrupts();bandera=0;SPI.begin();S1V30120_reset();success=S1V30120_download();success=S1V30120_boot_run();show_response(success);delay(150);success=S1V30120_registration();show_response(success);S1V30120_get_version();success=S1V30120_configure_audio();show_response(success);success=S1V30120_set_volume();show_response(success);success=S1V30120_configure_tts();show_response(success);show_response(success);success=S1V30120_speech(m2,1);wdt_enable(WDTO_4S);}/**************************************************************************** Función ISR para la UART2.* Procesa el caracter recibido. Si es 0x55 cambia bandera* Argumentos: NINGUNO* Retorna: Bandera*****************************************************************************/ISR(USART2_RX_vect){byteRX_Byte=UDR2;;UDR2=RX_Byte;dato=UDR2;if(dato==0x55)bandera=1;dato=0;}//####################################################################voidloop(){if(bandera==1){wdt_reset();success=S1V30120_speech(m0,1);delay(500);wdt_reset();success=S1V30120_speech(m1,1);bandera=0;}wdt_reset();}//########################################################################voidS1V30120_reset(void){digitalWrite(S1V30120_CS,HIGH);digitalWrite(S1V30120_RST,LOW);SPI.beginTransaction(SPISettings(750000,MSBFIRST,SPI_MODE3));SPI.transfer(0x00);SPI.endTransaction();delay(200);digitalWrite(S1V30120_RST,HIGH);delay(200);}unsignedshortS1V30120_get_version(void){unsignedshortS1V30120_version=0;unsignedshorttmp_disp;charmsg_ver[]={0x04,0x00,0x05,0x00};S1V30120_send_message(msg_ver,0x04);while(digitalRead(S1V30120_RDY)==0);digitalWrite(S1V30120_CS,LOW);SPI.beginTransaction(SPISettings(750000,MSBFIRST,SPI_MODE3));while(SPI.transfer(0x00)!=0xAA);for(inti=0;i<20;i++){rcvd_msg[i]=SPI.transfer(0x00);}S1V30120_send_padding(16);SPI.endTransaction();digitalWrite(S1V30120_CS,HIGH);S1V30120_version=rcvd_msg[4]<<8|rcvd_msg[5];Serial.print("HW version ");Serial.print(rcvd_msg[4],HEX);Serial.print(".");Serial.println(rcvd_msg[5],HEX);Serial.print("Firmware version ");Serial.print(rcvd_msg[6],HEX);Serial.print(".");Serial.print(rcvd_msg[7],HEX);Serial.print(".");Serial.println(rcvd_msg[16],HEX);Serial.print("Firmware features ");Serial.println(((rcvd_msg[11]<<24)|(rcvd_msg[10]<<16)|(rcvd_msg[9]<<8)|rcvd_msg[8]),HEX);Serial.print("Firmware extended features ");Serial.println(((rcvd_msg[15]<<24)|(rcvd_msg[14]<<16)|(rcvd_msg[13]<<8)|rcvd_msg[12]),HEX);returnS1V30120_version;}boolS1V30120_load_chunk(unsignedshortchunk_len){charlen_msb=((chunk_len+4)&0xFF00)>>8;charlen_lsb=(chunk_len+4)&0xFF;digitalWrite(S1V30120_CS,LOW);SPI.beginTransaction(SPISettings(750000,MSBFIRST,SPI_MODE3));SPI.transfer(0xAA);SPI.transfer(len_lsb);SPI.transfer(len_msb);SPI.transfer(0x00);SPI.transfer(0x10);for(intchunk_idx=0;chunk_idx<chunk_len;chunk_idx++){SPI.transfer(pgm_read_byte_near(TTS_INIT_DATA+TTS_DATA_IDX));TTS_DATA_IDX++;}SPI.endTransaction();digitalWrite(S1V30120_CS,HIGH);returnS1V30120_parse_response(ISC_BOOT_LOAD_RESP,0x0001,16);}boolS1V30120_download(void){unsignedshortlen=sizeof(TTS_INIT_DATA);unsignedshortfullchunks;unsignedshortremaining;boolchunk_result;longdata_index=0;fullchunks=len/2044;remaining=len-fullchunks*2044;return1;}boolS1V30120_boot_run(void){charboot_run_msg[]={0x04,0x00,0x02,0x10};S1V30120_send_message(boot_run_msg,0x04);returnS1V30120_parse_response(ISC_BOOT_RUN_RESP,0x0001,8);}voidshow_response(boolresponse){if(response)Serial.println("OK!");else{Serial.println("Failed. System halted!");while(1);}}boolS1V30120_registration(void){SPI.beginTransaction(SPISettings(750000,MSBFIRST,SPI_MODE3));charreg_code[]={0x0C,0x00,0x03,0x00,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00};S1V30120_send_message(reg_code,0x0C);returnS1V30120_parse_response(ISC_TEST_RESP,0x0000,16);}boolS1V30120_parse_response(unsignedshortexpected_message,unsignedshortexpected_result,unsignedshortpadding_bytes){unsignedshortrcvd_tmp;while(digitalRead(S1V30120_RDY)==0);digitalWrite(S1V30120_CS,LOW);SPI.beginTransaction(SPISettings(750000,MSBFIRST,SPI_MODE3));while(SPI.transfer(0x00)!=0xAA);for(inti=0;i<6;i++){rcvd_msg[i]=SPI.transfer(0x00);}S1V30120_send_padding(padding_bytes);SPI.endTransaction();digitalWrite(S1V30120_CS,HIGH);rcvd_tmp=rcvd_msg[3]<<8|rcvd_msg[2];if(rcvd_tmp==expected_message){rcvd_tmp=rcvd_msg[5]<<8|rcvd_msg[4];if(rcvd_tmp==expected_result)return1;elsereturn0;}elsereturn0;}voidS1V30120_send_padding(unsignedshortnum_padding_bytes){for(inti=0;i<num_padding_bytes;i++){SPI.transfer(0x00);}}voidS1V30120_send_message(volatilecharmessage[],unsignedcharmessage_length){while(digitalRead(S1V30120_RDY)==1);digitalWrite(S1V30120_CS,LOW);SPI.beginTransaction(SPISettings(750000,MSBFIRST,SPI_MODE3));SPI.transfer(0xAA);for(inti=0;i<message_length;i++){SPI.transfer(message[i]);}SPI.endTransaction();}//#########################################################################boolS1V30120_configure_audio(void){msg_len=0x0C;send_msg[0]=msg_len&0xFF;send_msg[1]=(msg_len&0xFF00)>>8;send_msg[2]=ISC_AUDIO_CONFIG_REQ&0xFF;send_msg[3]=(ISC_AUDIO_CONFIG_REQ&0xFF00)>>8;send_msg[4]=TTS_AUDIO_CONF_AS;send_msg[5]=TTS_AUDIO_CONF_AG;send_msg[6]=TTS_AUDIO_CONF_AMP;send_msg[7]=TTS_AUDIO_CONF_ASR;send_msg[8]=TTS_AUDIO_CONF_AR;send_msg[9]=TTS_AUDIO_CONF_ATC;send_msg[10]=TTS_AUDIO_CONF_ACS;send_msg[11]=TTS_AUDIO_CONF_DC;S1V30120_send_message(send_msg,msg_len);returnS1V30120_parse_response(ISC_AUDIO_CONFIG_RESP,0x0000,16);}//###########################################################################boolS1V30120_set_volume(void){charsetvol_code[]={0x06,0x00,0x0A,0x00,0x00,0x00};S1V30120_send_message(setvol_code,0x06);returnS1V30120_parse_response(ISC_AUDIO_VOLUME_RESP,0x0000,16);}boolS1V30120_configure_tts(void){msg_len=0x0C;send_msg[0]=msg_len&0xFF;send_msg[1]=(msg_len&0xFF00)>>8;send_msg[2]=ISC_TTS_CONFIG_REQ&0xFF;send_msg[3]=(ISC_TTS_CONFIG_REQ&0xFF00)>>8;send_msg[4]=ISC_TTS_SAMPLE_RATE;send_msg[5]=ISC_TTS_VOICE;send_msg[6]=ISC_TTS_EPSON_PARSE;send_msg[7]=ISC_TTS_LANGUAGE;send_msg[8]=ISC_TTS_SPEAK_RATE_LSB;send_msg[9]=ISC_TTS_SPEAK_RATE_MSB;send_msg[10]=ISC_TTS_DATASOURCE;send_msg[11]=0x00;S1V30120_send_message(send_msg,msg_len);returnS1V30120_parse_response(ISC_TTS_CONFIG_RESP,0x0000,16);}boolS1V30120_speech(Stringtext_to_speech,unsignedcharflush_enable){boolresponse;txt_len=text_to_speech.length();msg_len=txt_len+6;send_msg[0]=msg_len&0xFF;send_msg[1]=(msg_len&0xFF00)>>8;send_msg[2]=ISC_TTS_SPEAK_REQ&0xFF;send_msg[3]=(ISC_TTS_SPEAK_REQ&0xFF00)>>8;send_msg[4]=flush_enable;for(inti=0;i<txt_len;i++){send_msg[i+5]=text_to_speech[i];}send_msg[msg_len-1]='\0';S1V30120_send_message(send_msg,msg_len);response=S1V30120_parse_response(ISC_TTS_SPEAK_RESP,0x0000,16);while(!S1V30120_parse_response(ISC_TTS_FINISHED_IND,0x0000,16));returnresponse;}
Como el manejo del receptor UART que tiene Arduino de manera nativa es muy básico y necesitaba un poco mas de control en la recepción de datos se escribió la rutina para la interrupción en la UART 2 configurando en el Setup los correspondientes registros para su funcionamiento.
Enviar información por Bluetooth a un móvil con la interfaz HC-06 es bastante simple. Esto debido a que la propia interfaz resuelve todas las cuestiones relacionadas con el transporte y protocolo de la comunicación y solo requiere de una conexión serial (UART) para conectar con el microcontrolador o dispositivo que procesará los datos. Está claro que con Bluetooth se facilitan las comunicaciones entre equipos móviles, se eliminan los cables y conexiones y además podemos crear pequeñas redes inalámbricas y facilitar la sincronización de datos entre equipos personales. Además un detalle no menor es que cuando trabajamos con microcontroladores, la visualización de los datos o información que leemos de sensores puede ser engorrosa. Usar pantallas gráficas o cualquier dispositivo de Hardware que sumemos a nuestro proyecto lo encarece sumando recursos, conexiones, cantidad de memoria para el código que maneja la parte gráfica, etc. Con esta tecnología el propio móvil del usuario sirve como pantalla de visualización pudiendo incluso ver los datos no estando frente al propio equipo. Los dispositivos que incorporan este protocolo pueden comunicarse entre sí cuando se encuentran dentro de su alcance. Las comunicaciones se realizan por radiofrecuencia de forma que los dispositivos no tienen que estar alineados y pueden incluso estar en lugares separadas siempre que la potencia de transmisión es suficiente para alcanzar el destino.
La placa HC-06 tiene cuatro pines necesarios para la comunicación propuesta.
Vcc, Voltaje positivo de alimentación, aquí hay tener cuidado porque hay módulos que solo soportan voltajes de 3.3V, pero en su mayoría ya vienen acondicionados para trabajar en el rango de 3.3V a 6V.
GND, Voltaje negativo de alimentación, se tienen que conectar al GND de la placa que se esté usando, en este caso Raspberry PI Pico.
TX, Pin de Transmisión de datos, por este pin el HC-06 transmite los datos que le llegan desde el computador o el Móvil mediante Bluetooth, este pin debe ir conectado al pin RX de la placa Pico.
RX, pin de Recepción, a través de este pin el HC-06 recibirá los datos desde la placa Pico los cuales se transmitirán por Bluetooth, este pin va conectado al Pin TX de la placa Pico. Recuerde que los pines de comunicaciones van cruzados.
En el siguiente ejemplo con MicroPython vamos a vincular dos canales analógicos (pines 26 y 27) de una placa Raspberry Pico a un móvil Android. El siguiente código envía los datos a la interfaz HC-06, el pin 0 de Raspberry Pico se ha conectado el pin RX del HC-06 y el pin 1 de Raspberry Pico al TX del HC-06.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
importtimefrommachineimportADC,UART,Pinbandera=0uart0=UART(0,baudrate=9600,tx=Pin(0),rx=Pin(1))defmain():canal_0=machine.ADC(26)# Canal 0 ADC en pin 26canal_1=machine.ADC(27)# Canal 1 ADC en pin 27factor_16=3.29/(65535)whileTrue:globalbanderaif(bandera==0):bits_16=canal_0.read_u16();volts_16=bits_16*factor_16datos_tx="%.02f"%(volts_16)+';'uart0.write(datos_tx)print('Voltios Canal 0: {}'.format(datos_tx))bandera=1print('-----------------------')if(bandera==1):bits_16=canal_1.read_u16();volts_16=bits_16*factor_16datos_tx="%.02f"%(volts_16)+';'uart0.write(datos_tx)print('Voltios Canal 1: {}'.format(datos_tx))bandera=0print('-----------------------')time.sleep(.5)if__name__=='__main__':main()
Para construir la aplicación usamos App Inventor, siendo la imagen siguiente el conjunto principal de bloques para el despliegue de datos recibidos. Se reciben cuatro bytes que incluyen el separador de campos ";" para separar y ubicar los datos de los dos canales analógicos en su correcto lugar.
Si no está familiarizado con la programación para Android puede descargarla aplicación desde el siguiente linkhabilitando la instalación de aplicaciones desconocidas en su móvil.
Resultado obtenido con el ejemplo.
Esta dirección de correo electrónico está siendo protegida contra los robots de spam. Necesita tener JavaScript habilitado para poder verlo.r