Skip to content

Instantly share code, notes, and snippets.

@Kartoffel
Created April 11, 2020 16:04
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Kartoffel/6c617b232e3f39842af5577d5abd9e38 to your computer and use it in GitHub Desktop.
Save Kartoffel/6c617b232e3f39842af5577d5abd9e38 to your computer and use it in GitHub Desktop.
M5 Atom MQTT to IR bridge for controlling Edifier speakers
// Compile for ESP32 Pico Kit, upload rate 115200 baud
#include <Arduino.h>
#include <WiFi.h>
#include <PubSubClient.h>
#include <IRremoteESP8266.h>
#include <IRsend.h>
#define MQTT_SERVER "10.0.1.3"
#define MQTT_TOPIC_DEBUG "niek/debug/"
#define MQTT_TOPIC_BASE "niek/speakers/"
#define MQTT_WILDCARD "#"
#define WIFI_SSID "..."
#define WIFI_PSK "..."
// MQTT topics (starting with MQTT_TOPIC_BASE):
// power
// mute
// volup [no payload or 1..50]
// voldown [no payload or 1..50]
// volume [-50 .. +50]
// input [PC/AUX/OPT/COX/BT]
// media [prev/play/pause/next]
// raw [64-bit NEC IR code in hex format]
const char* ssid = WIFI_SSID;
const char* password = WIFI_PSK;
const char* mqtt_server = MQTT_SERVER;
String clientID = "M5-";
WiFiClient espClient;
PubSubClient client(espClient);
const uint16_t btnPin = 39; // Active LOW
const uint16_t irPin = 12;
IRsend irsend(irPin);
unsigned long lastTx = 0UL;
// Edifier RC20G remote
const uint64_t remoteCodes[14] = {
0x08E7827D, // Mute
0x08E7629D, // Power on/off
0x08E7E21D, // Volume down (outer)
0x08E7926D, // Volume down (inner)
0x08E7A05F, // Volume up (inner)
0x08E7609F, // Volume up (outer)
0x08E7E01F, // Input select PC
0x08E7906F, // Input select AUX
0x08E7A25D, // Input select OPT
0x08E7C03F, // Input select COX
0x08E73AC5, // Input select BT
0x08E77887, // Media control previous
0x08E77A85, // Media control play/pause
0x08E740BF // Media control next
};
// Inner or outer volume up/down: 50 button presses
// Volume codes can be retransmitted every ~100ms
#define CODE_MUTE 0
#define CODE_POWER 1
#define CODE_VOLDOWN 2
#define CODE_VOLDOWN_2 3
#define CODE_VOLUP_2 4
#define CODE_VOLUP 5
#define CODE_INPUT_PC 6
#define CODE_INPUT_AUX 7
#define CODE_INPUT_OPT 8
#define CODE_INPUT_COX 9
#define CODE_INPUT_BT 10
#define CODE_CTRL_PREV 11
#define CODE_CTRL_PLAYPAUSE 12
#define CODE_CTRL_NEXT 13
void setup() {
pinMode(btnPin, INPUT);
irsend.begin();
Serial.begin(115200);
Serial.print("\nConnect");
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
client.setServer(mqtt_server, 1883);
client.setCallback(callback);
char macAddr[13];
snprintf(macAddr, 13, "%12" PRIx64, ESP.getEfuseMac());
clientID += String(macAddr);
Serial.print("\nReady\n\n");
}
void irsend_wait(uint64_t code) {
while (millis() - lastTx < 100)
delay(1);
irsend.sendNEC(code);
lastTx = millis();
}
void sendIR(uint8_t code, uint8_t repeat = 1) {
if (code >= (sizeof(remoteCodes) / sizeof (remoteCodes[0])))
return;
irsend_wait(remoteCodes[code]);
for (uint8_t i = 1; i < repeat; i++) {
irsend_wait(remoteCodes[code]);
}
}
void handleButton() {
static bool lastState = HIGH;
bool state = digitalRead(btnPin);
if (!state && lastState) {
sendIR(CODE_POWER);
Serial.printf("Button pressed!\n");
delay(10); // Debounce
}
lastState = state;
}
void loop() {
if (!client.connected()){
reconnect();
}
handleButton();
client.loop();
}
void reconnect() {
// Loop until we're reconnected
while (!client.connected()) {
Serial.print("Connecting to MQTT server... ");
String debugTopic = String(MQTT_TOPIC_DEBUG) + clientID;
// Attempt to connect
if (client.connect(clientID.c_str())) {
Serial.println("connected");
client.publish(debugTopic.c_str(), "reconnect");
client.subscribe(MQTT_TOPIC_BASE MQTT_WILDCARD);
} else {
Serial.printf("failed, rc=%d\n", client.state());
Serial.println("Retry in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void callback(char* topic, byte* p_payload, unsigned int p_length) {
String payload;
for (uint8_t i = 0; i < p_length; i++) {
payload.concat((char)p_payload[i]);
}
Serial.print(topic);
Serial.print(": ");
Serial.println(payload);
if (String(MQTT_TOPIC_BASE "power").equals(topic)) {
sendIR(CODE_POWER);
} else if (String(MQTT_TOPIC_BASE "mute").equals(topic)) {
sendIR(CODE_MUTE);
} else if (String(MQTT_TOPIC_BASE "volup").equals(topic)) {
int repeat = constrain(payload.toInt(), 1, 50);
sendIR(CODE_VOLUP, repeat);
} else if (String(MQTT_TOPIC_BASE "voldown").equals(topic)) {
int repeat = constrain(payload.toInt(), 1, 50);
sendIR(CODE_VOLDOWN, repeat);
} else if (String(MQTT_TOPIC_BASE "volume").equals(topic)) {
int repeat = constrain(payload.substring(1).toInt(), 1, 50);
if (payload.startsWith("+")) {
sendIR(CODE_VOLUP, repeat);
} else if (payload.startsWith("-")) {
sendIR(CODE_VOLDOWN, repeat);
}
} else if (String(MQTT_TOPIC_BASE "input").equals(topic)) {
if (payload.startsWith("AUX")) {
sendIR(CODE_INPUT_AUX);
} else if (payload.startsWith("OPT")) {
sendIR(CODE_INPUT_OPT);
} else if (payload.startsWith("COX")) {
sendIR(CODE_INPUT_COX);
} else if (payload.startsWith("BT")) {
sendIR(CODE_INPUT_BT);
} else {
sendIR(CODE_INPUT_PC);
}
} else if (String(MQTT_TOPIC_BASE "media").equals(topic)) {
if (payload.startsWith("prev")) {
sendIR(CODE_CTRL_PREV);
} else if (payload.startsWith("next")) {
sendIR(CODE_CTRL_NEXT);
} else if (payload.startsWith("play") || payload.startsWith("pause")) {
sendIR(CODE_CTRL_PLAYPAUSE);
}
} else if (String(MQTT_TOPIC_BASE "raw").equals(topic)) {
if (p_length == 0 || p_length > 16)
return;
uint64_t irCode = strtoull(payload.c_str(), NULL, 16);
//Serial.printf("Code: %16" PRIx64"\n", irCode);
irsend_wait(irCode);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment