Created
April 11, 2020 16:04
-
-
Save Kartoffel/6c617b232e3f39842af5577d5abd9e38 to your computer and use it in GitHub Desktop.
M5 Atom MQTT to IR bridge for controlling Edifier speakers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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