413 lines
9.8 KiB
C++
413 lines
9.8 KiB
C++
#include "Adafruit_MQTT.h"
|
|
#include "Adafruit_MQTT_Client.h"
|
|
#include "Button2.h"
|
|
#include "melodies.h"
|
|
#include <Adafruit_GFX.h>
|
|
#include <Adafruit_SSD1306.h>
|
|
#include <ArduinoJson.h>
|
|
#include <PubSubClient.h> // src: https://github.com/knolleary/pubsubclient
|
|
#include <WiFi.h>
|
|
#include <Wire.h>
|
|
#include <mbedtls/md.h>
|
|
#include <time.h>
|
|
|
|
// TODO: define topics used to publish/subscribe
|
|
#define TOPIC_BR "brightness"
|
|
#define TOPIC_OLED "oled"
|
|
#define TOPIC_LED "led"
|
|
#define TOPIC_BUZZ "buzzer"
|
|
#define TOPIC_BTN "button"
|
|
|
|
#define HASH_SIZE 32 // TODO: is this needed?
|
|
|
|
#define SCREEN_WIDTH 128 // OLED display width, in pixels
|
|
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
|
|
#define MAX_WAIT_FOR_TIMER 3
|
|
#define SCREEN_ADDRESS 0x3C // See datasheet for Address; 0x3D for 128x64, 0x3C for 128x32
|
|
#define OLED_RESET 16 // Reset pin # (or -1 if sharing Arduino reset pin)
|
|
|
|
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
|
|
|
|
const char *ssid = "Segfault_2.4";
|
|
const char *password = "i3mlpgdOPLVwXeC";
|
|
const char *server = "192.168.1.153"; // TODO: server IP
|
|
const int port = 1883;
|
|
const char *mqtt_name = "user"; // TODO: username
|
|
const char *mqtt_pass = "test"; // TODO: password
|
|
char id[65];
|
|
/*
|
|
* by default, the max msg size is 256 bytes including header
|
|
* configurable with MQTT_MAX_PACKET_SIZE or PubSubClient::setBufferSize(size)
|
|
*/
|
|
char msg[256];
|
|
WiFiClient wclient;
|
|
PubSubClient client(wclient);
|
|
|
|
/*
|
|
* structures
|
|
*/
|
|
enum { EMPTY, FULL };
|
|
|
|
struct mailbox_s {
|
|
int state;
|
|
int val;
|
|
bool periodic;
|
|
};
|
|
|
|
struct Led_s {
|
|
int timer; // numéro du timer pour cette tâche utilisé par WaitFor
|
|
unsigned long period; // periode de clignotement
|
|
int pin; // numéro de la broche sur laquelle est la LED
|
|
int etat; // etat interne de la led
|
|
};
|
|
|
|
struct Oled_s {
|
|
int timer;
|
|
unsigned long period;
|
|
int i;
|
|
};
|
|
|
|
struct Lum_s {
|
|
int timer; // numéro de timer utilisé par WaitFor
|
|
unsigned long period; // periode d'affichage
|
|
uint val;
|
|
};
|
|
|
|
struct Buz_s {
|
|
int timer;
|
|
unsigned long period;
|
|
int n;
|
|
int pause;
|
|
int size;
|
|
int *melody;
|
|
int duration;
|
|
int tempo;
|
|
bool play;
|
|
};
|
|
|
|
/*
|
|
* structure initialisation
|
|
*/
|
|
struct Led_s Led1;
|
|
struct Oled_s Oled1;
|
|
struct Lum_s Lum1;
|
|
struct Buz_s Buz1;
|
|
Button2 button;
|
|
struct mailbox_s mb_led_state = {.state = EMPTY};
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
// Multi-tâches cooperatives : solution basique mais efficace :-)
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
// unsigned int waitFor(timer, period)
|
|
// Timer pour taches périodiques
|
|
// configuration :
|
|
// - MAX_WAIT_FOR_TIMER : nombre maximum de timers utilisés
|
|
// arguments :
|
|
// - timer : numéro de timer entre 0 et MAX_WAIT_FOR_TIMER-1
|
|
// - period : période souhaitée
|
|
// retour :
|
|
// - nombre de périodes écoulées depuis le dernier appel
|
|
// --------------------------------------------------------------------------------------------------------------------
|
|
unsigned int waitFor(int timer, unsigned long period)
|
|
{
|
|
static unsigned long waitForTimer[MAX_WAIT_FOR_TIMER]; // il y a autant de timers que de tâches périodiques
|
|
unsigned long newTime = micros() / period; // numéro de la période modulo 2^32
|
|
int delta = newTime - waitForTimer[timer]; // delta entre la période courante et celle enregistrée
|
|
if (delta < 0)
|
|
delta = 1 + newTime; // en cas de dépassement du nombre de périodes possibles sur 2^32
|
|
if (delta)
|
|
waitForTimer[timer] = newTime; // enregistrement du nouveau numéro de période
|
|
return delta;
|
|
}
|
|
|
|
/*
|
|
* setup functions
|
|
*/
|
|
|
|
void setup_Led(struct Led_s *ctx, int timer, unsigned long period, byte pin)
|
|
{
|
|
ctx->timer = timer;
|
|
ctx->period = period;
|
|
ctx->pin = pin;
|
|
ctx->etat = 1;
|
|
pinMode(pin, OUTPUT);
|
|
digitalWrite(pin, ctx->etat);
|
|
}
|
|
|
|
void setup_Oled()
|
|
{
|
|
Wire.begin(4, 15);
|
|
Serial.begin(115200);
|
|
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
|
|
if (!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
|
|
Serial.println(F("SSD1306 allocation failed"));
|
|
for (;;)
|
|
; // Don't proceed, loop forever
|
|
}
|
|
display.setTextColor(SSD1306_WHITE); // Draw white text
|
|
display.setCursor(0, 0); // Start at top-left corner
|
|
display.cp437(true); // Use full 256 char 'Code Page 437' font
|
|
display.clearDisplay();
|
|
display.setTextSize(1);
|
|
display.setCursor(1, 1);
|
|
display.write("Hello, World!");
|
|
display.display();
|
|
}
|
|
|
|
void setup_Lum(struct Lum_s *ctx, int timer, unsigned long period)
|
|
{
|
|
ctx->timer = timer;
|
|
ctx->period = period;
|
|
pinMode(36, INPUT);
|
|
}
|
|
|
|
void setup_Buz(struct Buz_s *ctx, int timer)
|
|
{
|
|
ctx->timer = timer;
|
|
ctx->play = false;
|
|
pinMode(17, OUTPUT);
|
|
ledcAttachPin(17, 0);
|
|
}
|
|
|
|
void setup_Btn()
|
|
{
|
|
button.begin(23);
|
|
// button.setChangedHandler(changed);
|
|
// button.setPressedHandler(pressed);
|
|
// button.setReleasedHandler(released);
|
|
|
|
// setTapHandler() is called by any type of click, longpress or shortpress
|
|
button.setTapHandler(tap);
|
|
}
|
|
|
|
void setup_wifi()
|
|
{
|
|
Serial.print("[WiFi] Connecting to ");
|
|
Serial.println(ssid);
|
|
WiFi.begin(ssid, password);
|
|
while (WiFi.status() != WL_CONNECTED) {
|
|
delay(300);
|
|
Serial.print(".");
|
|
}
|
|
Serial.println();
|
|
Serial.print("IP @:");
|
|
Serial.println(WiFi.localIP());
|
|
|
|
/* hash mac address and store it is ID */
|
|
byte id_hash[32];
|
|
String mac = WiFi.macAddress();
|
|
Serial.println(mac);
|
|
|
|
mbedtls_md_context_t ctx;
|
|
mbedtls_md_type_t md_type = MBEDTLS_MD_SHA256;
|
|
mbedtls_md_init(&ctx);
|
|
mbedtls_md_setup(&ctx, mbedtls_md_info_from_type(md_type), 0);
|
|
mbedtls_md_starts(&ctx);
|
|
mbedtls_md_update(&ctx, (const unsigned char *)mac.c_str(), mac.length());
|
|
mbedtls_md_finish(&ctx, id_hash);
|
|
mbedtls_md_free(&ctx);
|
|
|
|
for (int i = 0; i < sizeof(id_hash); i++) {
|
|
char tmp[3];
|
|
sprintf(tmp, "%02x", (int)id_hash[i]);
|
|
strcat(id, tmp);
|
|
}
|
|
id[65] = NULL;
|
|
}
|
|
|
|
void choose_song(int n)
|
|
{
|
|
switch (n) {
|
|
case 1:
|
|
Buz1.melody = hp;
|
|
Buz1.size = (sizeof(hp) / sizeof(hp[0]) / 2) * 2;
|
|
Buz1.tempo = tempo_hp;
|
|
break;
|
|
case 2:
|
|
Buz1.melody = zelda;
|
|
Buz1.size = (sizeof(zelda) / sizeof(zelda[0]) / 2) * 2;
|
|
Buz1.tempo = tempo_zelda;
|
|
break;
|
|
case 3:
|
|
Buz1.melody = sw;
|
|
Buz1.size = (sizeof(sw) / sizeof(sw[0]) / 2) * 2;
|
|
Buz1.tempo = tempo_sw;
|
|
break;
|
|
case 4:
|
|
Buz1.melody = gf;
|
|
Buz1.size = (sizeof(gf) / sizeof(gf[0]) / 2) * 2;
|
|
Buz1.tempo = tempo_gf;
|
|
break;
|
|
case 5:
|
|
Buz1.melody = rick;
|
|
Buz1.size = (sizeof(rick) / sizeof(rick[0]) / 2) * 2;
|
|
Buz1.tempo = tempo_rick;
|
|
break;
|
|
default:
|
|
Buz1.play = false;
|
|
Buz1.melody = NULL;
|
|
Buz1.n = 0;
|
|
return;
|
|
}
|
|
Buz1.n = 0;
|
|
Buz1.play = true;
|
|
}
|
|
|
|
void write_Oled(const char *str)
|
|
{
|
|
display.clearDisplay();
|
|
display.setTextSize(1);
|
|
display.setCursor(1, 1);
|
|
display.write(str);
|
|
display.display();
|
|
}
|
|
|
|
void callback(const char *topic, byte *payload, unsigned int length)
|
|
{
|
|
Serial.print("callback ");
|
|
Serial.println(topic);
|
|
payload[length] = '\0';
|
|
Serial.println((char *)payload);
|
|
DynamicJsonDocument doc(256);
|
|
DeserializationError error = deserializeJson(doc, payload);
|
|
if (error)
|
|
return;
|
|
String device_id = doc["device_id"];
|
|
if (strcmp(device_id.c_str(), id) != 0)
|
|
return;
|
|
|
|
int i;
|
|
if (strcmp(topic, TOPIC_OLED) == 0) {
|
|
String m = doc["text"];
|
|
Serial.println(m);
|
|
write_Oled(m.c_str());
|
|
} else if (strcmp(topic, TOPIC_LED) == 0) {
|
|
mb_led_state.val = doc["period"];
|
|
mb_led_state.state = FULL;
|
|
mb_led_state.periodic = mb_led_state.val != 0;
|
|
} else if (strcmp(topic, TOPIC_BUZZ) == 0) {
|
|
int value = doc["song"];
|
|
choose_song(value);
|
|
}
|
|
}
|
|
|
|
void setup_mqtt()
|
|
{
|
|
client.setServer(server, port);
|
|
client.connect(id, mqtt_name, mqtt_pass);
|
|
if (!client.subscribe(TOPIC_BUZZ))
|
|
Serial.println("fail");
|
|
if (!client.subscribe(TOPIC_OLED))
|
|
Serial.println("fail");
|
|
if (!client.subscribe(TOPIC_LED))
|
|
Serial.println("fail");
|
|
client.setCallback(callback);
|
|
}
|
|
|
|
/*
|
|
* Handlers
|
|
*/
|
|
void tap(Button2 &btn)
|
|
{
|
|
DynamicJsonDocument doc(256);
|
|
doc["device_id"] = id;
|
|
serializeJson(doc, msg);
|
|
client.publish(TOPIC_BTN, msg);
|
|
Serial.println("tap");
|
|
}
|
|
|
|
void loop_Led(struct Led_s *ctx, struct mailbox_s *mb_s)
|
|
{
|
|
if (mb_s->state != EMPTY) {
|
|
mb_s->state = EMPTY;
|
|
if (mb_s->val == 0) {
|
|
digitalWrite(ctx->pin, ctx->etat);
|
|
ctx->etat = 1 - ctx->etat;
|
|
return;
|
|
}
|
|
}
|
|
if (mb_s->periodic) {
|
|
int period = mb_s->val < 2 ? ctx->period : ctx->period * (mb_s->val / 2);
|
|
if (!waitFor(ctx->timer, period))
|
|
return;
|
|
digitalWrite(ctx->pin, ctx->etat);
|
|
ctx->etat = 1 - ctx->etat;
|
|
}
|
|
}
|
|
|
|
void loop_Lum(struct Lum_s *ctx)
|
|
{
|
|
if (!(waitFor(ctx->timer, ctx->period)))
|
|
return; // sort s'il y a moins d'une période écoulée
|
|
int perc;
|
|
DynamicJsonDocument doc(256);
|
|
|
|
ctx->val = analogRead(36);
|
|
perc = map(ctx->val, 0, 4095, 100, 0);
|
|
itoa(perc, msg, 10);
|
|
|
|
doc["device_id"] = id;
|
|
doc["val"] = perc;
|
|
|
|
serializeJson(doc, msg);
|
|
client.publish(TOPIC_BR, msg);
|
|
// Serial.println("published");
|
|
}
|
|
|
|
void loop_Buz(struct Buz_s *ctx)
|
|
{
|
|
if (!ctx->play) {
|
|
ledcDetachPin(17);
|
|
ctx->n = 0;
|
|
return;
|
|
}
|
|
int wholenote = (60000 * 4) / ctx->tempo;
|
|
int divider = ctx->melody[ctx->n + 1];
|
|
if (divider > 0)
|
|
ctx->duration = wholenote / divider;
|
|
else if (divider < 0)
|
|
ctx->duration = (wholenote / abs(divider)) * 1.5;
|
|
ctx->period = ctx->duration;
|
|
if (!(waitFor(ctx->timer, ctx->period)))
|
|
return;
|
|
tone(17, ctx->melody[ctx->n], ctx->duration * 0.9);
|
|
ctx->n = (ctx->n + 2) % ctx->size;
|
|
noTone(17);
|
|
}
|
|
|
|
void setup()
|
|
{
|
|
Serial.begin(115200);
|
|
delay(10);
|
|
Serial.println();
|
|
setup_wifi();
|
|
|
|
setup_mqtt();
|
|
|
|
setup_Lum(&Lum1, 1, 2000000);
|
|
setup_Buz(&Buz1, 0);
|
|
setup_Led(&Led1, 2, 100000, LED_BUILTIN); // Led est exécutée toutes les 100ms
|
|
setup_Btn();
|
|
setup_Oled();
|
|
}
|
|
|
|
void reconnect()
|
|
{
|
|
while (!client.connected()) {
|
|
Serial.println("Reconnecting");
|
|
setup_mqtt();
|
|
}
|
|
}
|
|
|
|
void loop()
|
|
{
|
|
reconnect();
|
|
loop_Led(&Led1, &mb_led_state);
|
|
loop_Buz(&Buz1);
|
|
loop_Lum(&Lum1);
|
|
client.loop();
|
|
button.loop();
|
|
}
|