ioc-invicta/esp/esp.ino

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();
}