// owlcms refereeing device using MQTT
//
// ESP32 Arduino-style module that communicates with owlcms over WiFi.
// Both owlcms and the the refereeing device connect to a MQTT server.
// The refereeing device publishes the decisions and
// subscribes to requests to remind or summon referees
// ====== START CONFIG SECTION ======================================================
// MQTT passwords
// copy secrets_example.h to secrets.h and include secrets.h (which is .gitignored)
#include "secrets_local.h"
// owlcms parameters
// referee = 0 is used when all three referees are wired to the same device
// if each referee has their own device, set referee to 1, 2 or 3
const int referee = 0;
const char* platform = "A";
// pins for refs 1 2 and 3 (1 good, 1 bad, 2 good, etc.)
int decisionPins[] = {14, 27, 26, 25, 33, 32};
int ledPins[] = {15, 5, 19};
int buzzerPins[] = {4, 18, 21};
// number of beeps when referee wakeup is received
// set to 0 to disable beeps.
const int nbBeeps = 3;
const note_t cfgBeepNote = NOTE_C;
const int cfgBeepOctave = 4; // C4
const int cfgBeepMilliseconds = 100;
const int cfgSilenceMilliseconds = 50; // time between beeps
const int cfgLedDuration = 1000; // led stays for this maximum time;
// referee summon parameters
const int nbSummonBeeps = 1;
const note_t cfgSummonNote = NOTE_F;
const int cfgSummonOctave = 7; // F5
const int cfgSummonBeepMilliseconds = 3000;
const int cfgSummonSilenceMilliseconds = 0;
const int cfgSummonLedDuration = cfgSummonBeepMilliseconds;
// ====== END CONFIG SECTION ======================================================
#ifdef TLS
#include <WiFiClientSecure.h>
#include "certificates.h"
const int mqttPort = 8883;
#else
#include <WiFi.h>
const int mqttPort = 1883;
#endif
#include "Tone32.hpp"
#include "PubSubClient.h"
#define ELEMENTCOUNT(x) (sizeof(x) / sizeof(x[0]))
#ifdef TLS
WiFiClientSecure wifiClient;
#else
WiFiClient wifiClient;
#endif
PubSubClient mqttClient;
// networking values
String macAddress;
char mac[50];
char clientId[50];
// owlcms values
char fop[20];
int ref13Number = 0;
// 6 pins where we have to detect transitions. initial state unknown.
int prevDecisionPinState[] = {-1, -1, -1, -1, -1, -1};
// for each referee, a Tone generator, and control for a LED
Tone32 tones[3] = {Tone32(buzzerPins[0], 0), Tone32(buzzerPins[1], 1), Tone32(buzzerPins[2], 2)};
int beepingIterations[] = {0, 0, 0};
int ledStartedMillis[] = {0, 0, 0};
int ledDuration[] = {0, 0, 0};
note_t beepNote;
int beepOctave;
int silenceMilliseconds;
int beepMilliseconds;
void setup() {
#ifdef TLS
wifiClient.setCACert(rootCABuff);
wifiClient.setInsecure();
#endif
mqttClient.setKeepAlive(20);
mqttClient.setClient(wifiClient);
Serial.begin(115200);
randomSeed(analogRead(0));
wifiConnect();
macAddress = WiFi.macAddress();
macAddress.toCharArray(clientId, macAddress.length() + 1);
Serial.print("MQTT server: ");
Serial.println(mqttServer);
mqttClient.setServer(mqttServer, mqttPort);
mqttClient.setCallback(callback);
setupPins();
setupTones();
strcpy(fop, platform);
mqttReconnect();
}
void loop() {
delay(10);
if (!mqttClient.connected()) {
mqttReconnect();
}
mqttClient.loop();
buttonLoop();
buzzerLoop();
ledLoop();
}
void wifiConnect() {
Serial.print("Connecting to WiFi ");
Serial.print(wifiSSID);
WiFi.mode(WIFI_STA);
WiFi.begin(wifiSSID, wifiPassword);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println(" connected");
}
void mqttReconnect() {
long r = random(1000);
sprintf(clientId, "owlcms-%ld", r);
if (WiFi.status() != WL_CONNECTED) {
wifiConnect();
}
while (!mqttClient.connected()) {
Serial.print(macAddress);
Serial.print(" connecting to MQTT server...");
if (mqttClient.connect(clientId, mqttUserName, mqttPassword)) {
Serial.println(" connected");
char requestTopic[50];
sprintf(requestTopic, "owlcms/decisionRequest/%s/+", fop);
mqttClient.subscribe(requestTopic);
char ledTopic[50];
sprintf(ledTopic, "owlcms/led/#", fop);
mqttClient.subscribe(ledTopic);
char summonTopic[50];
sprintf(summonTopic, "owlcms/summon/#", fop);
mqttClient.subscribe(summonTopic);
} else {
Serial.print("MQTT connection failed, rc=");
Serial.print(mqttClient.state());
Serial.println(" try again in 5 second");
delay(5000);
}
}
}
void setupPins() {
for (int j = 0; j < ELEMENTCOUNT(decisionPins); j++) {
pinMode(decisionPins[j], INPUT_PULLUP);
prevDecisionPinState[j] = digitalRead(decisionPins[j]);
}
for (int j = 0; j < ELEMENTCOUNT(ledPins); j++) {
pinMode(ledPins[j], OUTPUT);
}
for (int j = 0; j < ELEMENTCOUNT(buzzerPins); j++) {
pinMode(buzzerPins[j], OUTPUT);
}
}
void setupTones() {
for (int j = 0; j < ELEMENTCOUNT(tones); j++) {
tones[j] = Tone32(buzzerPins[j], j);
}
}
void buttonLoop() {
for (int j = 0; j < ELEMENTCOUNT(decisionPins); j++) {
int state = digitalRead(decisionPins[j]);
int prevState = prevDecisionPinState[j];
if (state != prevState) {
prevDecisionPinState[j] = state;
if (state == LOW) {
if (j % 2 == 0) {
sendDecision(j / 2, "good");
} else {
sendDecision(j / 2, "bad");
}
return;
}
}
}
}
void buzzerLoop() {
for (int j = 0; j < ELEMENTCOUNT(beepingIterations); j++) {
if (beepingIterations[j] > 0 && !tones[j].isPlaying()) {
if (((beepingIterations[j] % 2) == 0)) {
Serial.print(millis()); Serial.println(" sound on");
tones[j].playNote(beepNote, beepOctave, beepMilliseconds);
} else {
Serial.print(millis()); Serial.println(" sound off");
tones[j].silence(silenceMilliseconds);
}
}
tones[j].update(); // turn off sound if duration reached.
if (!tones[j].isPlaying()) {
beepingIterations[j]--;
}
}
}
void ledLoop() {
for (int j = 0; j < ELEMENTCOUNT(ledStartedMillis); j++) {
if (ledStartedMillis[j] > 0) {
;
if (millis() - ledStartedMillis[j] >= ledDuration[j]) {
digitalWrite(ledPins[j], LOW);
ledStartedMillis[j] = 0;
}
}
}
}
void sendDecision(int ref02Number, const char* decision) {
if (referee > 0) {
// software configuration
ref02Number = referee - 1;
}
char topic[50];
sprintf(topic, "owlcms/decision/%s", fop);
char message[32];
sprintf(message, "%i %s", ref02Number + 1, decision);
mqttClient.publish(topic, message);
Serial.print(topic); Serial.print(" "); Serial.print(message); Serial.println(" sent.");
}
String decisionRequestTopic = String("owlcms/decisionRequest/") + fop;
String summonTopic = String("owlcms/summon/") + fop;
String ledTopic = String("owlcms/led/") + fop;
void callback(char* topic, byte* message, unsigned int length) {
String stTopic = String(topic);
Serial.print("Message arrived on topic: "); Serial.print(stTopic); Serial.print("; Message: ");
String stMessage;
// convert byte to char
for (int i = 0; i < length; i++) {
stMessage += (char)message[i];
}
Serial.println(stMessage);
int refIndex = stTopic.lastIndexOf("/") + 1;
String refString = stTopic.substring(refIndex);
int ref13Number = refString.toInt();
if (stTopic.startsWith(decisionRequestTopic)) {
changeReminderStatus(ref13Number - 1, stMessage.startsWith("on"));
} else if (stTopic.startsWith(summonTopic)) {
if (ref13Number == 0) {
// topic did not end with number, blink all devices
for (int j = 0; j < ELEMENTCOUNT(ledPins); j++) {
changeSummonStatus(j, stMessage.startsWith("on"));
}
} else {
changeSummonStatus(ref13Number - 1, stMessage.startsWith("on"));
}
} else if (stTopic.startsWith(ledTopic)) {
if (ref13Number == 0) {
// topic did not end with number, blink all devices
for (int j = 0; j < ELEMENTCOUNT(ledPins); j++) {
changeSummonStatus(j, stMessage.startsWith("on"));
}
} else {
changeSummonStatus(ref13Number - 1, stMessage.startsWith("on"));
}
}
}
void changeReminderStatus(int ref02Number, boolean warn) {
Serial.print("reminder "); Serial.print(warn); Serial.print(" "); Serial.println(ref02Number+1);
if (warn) {
digitalWrite(ledPins[ref02Number], HIGH);
beepingIterations[ref02Number] = nbBeeps * 2;
ledStartedMillis[ref02Number] = millis();
ledDuration[ref02Number] = cfgLedDuration;
beepNote = cfgBeepNote;
beepOctave = cfgBeepOctave;
silenceMilliseconds = cfgSilenceMilliseconds;
beepMilliseconds = cfgBeepMilliseconds;
} else {
digitalWrite(ledPins[ref02Number], LOW);
beepingIterations[ref02Number] = 0;
tones[ref02Number].stopPlaying();
}
}
void changeSummonStatus(int ref02Number, boolean warn) {
Serial.print("summon "); Serial.print(warn); Serial.print(" "); Serial.println(ref02Number+1);
if (warn) {
digitalWrite(ledPins[ref02Number], HIGH);
beepingIterations[ref02Number] = nbSummonBeeps * 2;
ledStartedMillis[ref02Number] = millis();
ledDuration[ref02Number] = cfgSummonLedDuration;
beepNote = cfgSummonNote;
beepOctave = cfgSummonOctave;
silenceMilliseconds = cfgSummonSilenceMilliseconds;
beepMilliseconds = cfgSummonBeepMilliseconds;
} else {
digitalWrite(ledPins[ref02Number], LOW);
beepingIterations[ref02Number] = 0;
tones[ref02Number].stopPlaying();
}
}