Advertencia - 220V AC
Módulo de detección de AC
Este módulo permite conectar tres cables monofásicos de entrada y detectar en ellos la existencia de corriente alterna. Las tres correspondientes salidas, aisladas con optoacopladores de la entradas, se pueden conectar directamente a las GPIO de la Raspberry.
El esquema del circuito
Después de un análisis somero del módulo cuando detecta AC, vemos que el zener es de 3V, que en la entrada del optoacoplador tenemos 1.1V y que en la salida del módulo, si la alimentamos a 3V3, tenemos un consumo de 2.18 mA. Además, el transistor hace que la detección de alterna sea a nivel bajo en la entrada GPIO de la Raspberry.
Sensor de Movimiento de AC
Utilizamos un detector de movimiento de infrarrojos de los que se utilizan para encender lámparas con corriente alterna por la noche. El detector es de exterior con IP65, un alcance de 12 metros y en el que podemos configurar la luz máxima exterior (3-2000 lumens) a la que actúa como sensor de movimiento y el tiempo que permanece conectado una vez se ha detectado el movimiento (10s-15min). Para el uso que le vamos a dar lo configuramos a 2000 lumens (siempre) y 10 segundos (mínimo).
Dentro del sensor de movimiento, a parte del PIR (Passive Infrared sensor) y el LDR (Light Dependent Resistor) tenemos algún tipo de convertidor de alterna a continua y un relé.
Las conexiones
El sensor de movimiento tiene de entrada la fase (L) y el neutro (N) que lo alimentan y devuelve una fase (L1) que tiene tensión cuando se detecta movimiento. Esta fase (L1) se conecta al módulo de detección de alterna que lo transforma en un nivel bajo (0V sobre 3V3) en la GPIO 6 de la Raspberry Pi.
En la entrada del módulo de relés, también usado en este proyecto, hay más información sobre las conexiones que permiten encender la lámpara.
Cómo lo probamos
Ponemos el sensor de movimiento en nuestro escritorio y hacemos que la Raspberry PI nos envíe una alarma a nuestro móvil y encienda la lámpara cuando se detecta movimiento.
Escaneando las GPIO de Raspberry PI
Para programar las GPIO usamos la librería gpiod. Debajo encontramos el programa principal que básicamente espera a que la GPIO 6, donde está conectado el sensor de movimiento, se ponga a nivel bajo y entonces envía una alarma y enciende la luz conectada a la la GPIO 21. Como cuando la línea del sensor vuelve a nivel alto se generan varios rebotes, optamos por filtrarlos leyendo los eventos existentes 50 ms después del cambio.
struct gpiod_line *lamp;
// lamp is on when GPIO is low levelvoid turn_on_lamp(){ gpiod_line_set_value(lamp, 0);}
// lamp is off when GPIO is high levelvoid turn_off_lamp(){ gpiod_line_set_value(lamp, 1);}
int main(void){ const char *chipname = "gpiochip0"; struct gpiod_chip *chip; struct gpiod_line *motion_sensor; struct timespec ts = {1, 0}; struct gpiod_line_event event[3]; int ret;
// Open GPIO chip chip = gpiod_chip_open_by_name(chipname);
// Open GPIO lines lamp = gpiod_chip_get_line(chip, 21); motion_sensor = gpiod_chip_get_line(chip, 6);
// output in state high by default gpiod_line_request_output(lamp, "test", GPIOD_LINE_ACTIVE_STATE_HIGH); // input with pull-up resistor gpiod_line_request_falling_edge_events_flags(motion_sensor, "test", GPIOD_LINE_REQUEST_FLAG_BIAS_PULL_UP);
for (;;) { for (;;) { ret = gpiod_line_event_wait(motion_sensor, &ts); if (ret == 1) { break; } }
ret = gpiod_line_event_read(motion_sensor, &event[0]); if (ret == 0) { // AC detected - motion detected if (event[0].event_type == GPIOD_LINE_EVENT_FALLING_EDGE) { turn_on_lamp();
sendAlarm();
do { usleep(500000); // 500ms // AC is still detected ? ret = gpiod_line_get_value(motion_sensor); if (ret == 1) { turn_off_lamp();
// bounces --> multiple events usleep(50000); // 50ms gpiod_line_event_wait(motion_sensor, &ts); ret = gpiod_line_event_read_multiple(motion_sensor, event, 3); break; } } while (1); } } }
// turn off lamp; gpiod_line_set_value(lamp, 0); // Release lines and chip gpiod_line_release(lamp); gpiod_line_release(motion_sensor); gpiod_chip_close(chip); // return 0;}
Enviando la alarma al móvil
Esta parte se basa en la entrada Cómo enviar una alarma a un móvil que explica cómo implementar las diferentes partes del proyecto. Aquí sólo explicamos cómo implementar la parte del dispositivo cliente en C, ya que en la entrada referenciada está todo implementado en Java.
// it sends an alarm to a server which forwards it to the mobile subscribervoid sendAlarm(){ char *jwt; // creates authentification token jwt = jwtCreate("rsa_private.pem", "Project Id", "MyFistDeviceID");
// send an alarm restPostJSON("192.168.1.36", "8080", "resource/alarm/notification", notificationJSONCreate(), jwt);}
Para enviar la alarma se crea un token JWT que permite la autentificación y autorización del dispositivo cliente en el servidor, se empaquetan los datos de la notificación que queremos enviar al móvil en un objeto JSON y se envía todo al método notification del recurso alarm de un servidor que tenemos en Google Clould Platform.
No entramos en cómo se crea el socket, ni en el envío de la solicitud en HTTP, ni en la recepción de la respuesta en HTTP que lo podemos encontrar en el repositorio, sólo comentamos las partes que nos parecen interesantes.
REST - HTTP POST
Detalle de la implementación en C de la solicitud HTTP POST con datos en JSON y un Bearer Authentication.
void sendPost(int socket, char *hostname, char *port, char *path,char *jwt, char *json){ char buffer[2048];
sprintf(buffer, "POST /%s HTTP/1.1\r\n", path); sprintf(buffer + strlen(buffer), "Host: %s:%s\r\n", hostname, port); sprintf(buffer + strlen(buffer), "Connection: close\r\n"); sprintf(buffer + strlen(buffer), "User-Agent: RPI4B\r\n"); sprintf(buffer + strlen(buffer), "Content-Type: application/json\r\n"); sprintf(buffer + strlen(buffer), "Authorization: Bearer %s\r\n", jwt); sprintf(buffer + strlen(buffer), "Content-Length: %d\r\n", strlen(json)); sprintf(buffer + strlen(buffer), "\r\n"); sprintf(buffer + strlen(buffer), "%s\r\n", json);
send(socket, buffer, strlen(buffer), 0); }
Datos de la notificación en JSON
Para empaquetar los datos de la notificación en JSON utilizamos la librería Jansson.
char* notificationJSONCreate(){ // current time time_t current_time = time(NULL); struct tm *local_time = localtime(¤t_time); char aux[100]; char *dump;
// json notification json_t *root = json_object();
json_object_set_new(root, "title", json_string("Desk Alarm")); sprintf(aux, "Motion detected at %s", asctime(local_time)); json_object_set_new(root, "body", json_string(aux));
dump = json_dumps(root, 0);
json_decref(root);
return(dump);
}
JWT autentificación y autorización - Bearer Authentication
Implementación de la generación del token JWT en C utilizando la JWT C library.
El parámetro opt_key_name de la función jwtCreate és la clave rsa privada, en formato PEM (Privacy Enhanced Mail), del dispositivo con la identificación en el servidor deviceId. En la entrada Cómo autentificar un dispositivo en el servidor se explican los detalles.
Ponemos un exp claim (Expiration Time) de 1 día para el token.
#include <stdio.h>#include <stdlib.h>#include <jwt.h>#include <getopt.h>#include <string.h>#include <time.h>#include <string.h>#include <libgen.h>
// gets iat and exp claims// expiration time: 24 hstatic void getIatExp(char *iat, char *exp, int time_size){ time_t now_seconds = time(NULL); snprintf(iat, time_size, "%lu", now_seconds); snprintf(exp, time_size, "%lu", now_seconds + 24 * 60 * 60);}
// creates an authentication token - jwtchar* jwtCreate(char *opt_key_name, char* aud, char* deviceId){ size_t key_len = 0; FILE *fp_priv_key; unsigned char key[10240]; jwt_t *jwt = NULL; char iat_time[sizeof(time_t) * 3 + 2]; char exp_time[sizeof(time_t) * 3 + 2]; char* out = NULL;
fp_priv_key = fopen(opt_key_name, "r"); key_len = fread(key, 1, sizeof(key), fp_priv_key); fclose(fp_priv_key); key[key_len] = '\0';
jwt_new(&jwt); getIatExp(iat_time, exp_time, sizeof(iat_time)); jwt_add_grant(jwt, "iat", iat_time); jwt_add_grant(jwt, "exp", exp_time); jwt_add_grant(jwt, "aud", aud); jwt_add_grant(jwt, "deviceId", deviceId); jwt_set_alg(jwt, JWT_ALG_RS256, key, key_len); out = jwt_encode_str(jwt);
jwt_free(jwt); return out;}
Para finalizar
El código se ha hecho específicamente para este post, sólo se ha buscado que fuera funcional y breve, se ha depurado superficialmente y puede contener errores.
El código de la Raspberry PI
Puedes encontrar el código de la Raspberry PI de esta entrada en el repositorio de github: Consolidando.
Más información
- Repositorio libgpiod: Librería C que hace de interfaz con las GPIO.
- Librería Jansson para JSON.
- JWT C library.
- Cómo enviar una alarma a un móvil.
- Cómo autentificar un dispositivo en el servidor.
- Encendiendo lámparas con una Raspberry y un relé..
Comentarios
Publicar un comentario