Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add IR receiving support to IRMQTTServer. #543

Merged
merged 3 commits into from
Oct 11, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 105 additions & 12 deletions examples/IRMQTTServer/IRMQTTServer.ino
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
/*
* Send arbitrary IR codes via a web server or MQTT.
* Send & receive arbitrary IR codes via a web server or MQTT.
* Copyright David Conran 2016, 2017, 2018
*
* NOTE: An IR LED circuit *MUST* be connected to ESP8266 GPIO4 (D2). See IR_LED
* NOTE: An IR LED circuit *MUST* be connected to ESP8266 GPIO4 (D2) if
* you want to send IR messages. See IR_LED below.
* A compatible IR RX modules *MUST* be connected to ESP8266 GPIO14 (D5)
* if you want to capture & decode IR nessages. See IR_RX below.
*
* WARN: This is very advanced & complicated example code. Not for beginners.
* You are strongly suggested to try & look at other example code first.
Expand Down Expand Up @@ -94,6 +97,19 @@
* # Listen to MQTT acknowledgements.
* $ mosquitto_sub -h 10.20.0.253 -t ir_server/sent
*
* Incoming IR messages (from an IR remote control) will be transmitted to
* the MQTT topic 'ir_server/received'. The MQTT message will be formatted
* similar to what is required to for the 'sent' topic.
* e.g. "3,C1A2F00F,32" (Protocol,Value,Bits) for simple codes
* or "18,110B805000000060110B807000001070" (Protocol,Value) for complex codes
* Note: If the protocol is listed as -1, then that is an UNKNOWN IR protocol.
* You can't use that to recreate/resend an IR message. It's only for
* matching purposes and shouldn't be trusted.
*
* Unix command line usage example:
* # Listen via MQTT for IR messages captured by this server.
* $ mosquitto_sub -h 10.20.0.253 -t ir_server/received
*
* If DEBUG is turned on, there is additional information printed on the Serial
* Port.
*
Expand Down Expand Up @@ -136,6 +152,10 @@
// GPIO the IR LED is connected to/controlled by. GPIO 4 = D2.
#define IR_LED 4
// define IR_LED 3 // For an ESP-01 we suggest you use RX/GPIO3/Pin 7.
//
// GPIO the IR RX module is connected to/controlled by. GPIO 14 = D5.
// Comment this out to disable receiving/decoding IR messages entirely.
#define IR_RX 14
const uint16_t kHttpPort = 80; // The TCP port the HTTP server is listening on.
// Name of the device you want in mDNS.
// NOTE: Changing this will change the MQTT path too unless you override it
Expand Down Expand Up @@ -164,17 +184,31 @@ const uint32_t kMqttReconnectTime = 5000; // Delay(ms) between reconnect tries.
// independent of the hostname.
#define MQTTack MQTTprefix "/sent" // Topic we send back acknowledgements on
#define MQTTcommand MQTTprefix "/send" // Topic we get new commands from.
#define MQTTrecv MQTTprefix "/received" // Topic we send received IRs to.
#endif // MQTT_ENABLE

// HTML arguments we will parse for IR code information.
#define argType "type"
#define argData "code"
#define argBits "bits"
#define argRepeat "repeats"

#define _MY_VERSION_ "v0.6.0"

#if IR_LED != 1 // Disable debug output if the LED is on the TX (D1) pin.
// Let's use a larger than normal buffer so we can handle AirCon remote codes.
const uint16_t kCaptureBufferSize = 1024;
#if DECODE_AC
// Some A/C units have gaps in their protocols of ~40ms. e.g. Kelvinator
// A value this large may swallow repeats of some protocols
const uint8_t kCaptureTimeout = 50;
#else // DECODE_AC
// Suits most messages, while not swallowing many repeats.
const uint8_t kCaptureTimeout = 15;
#endif // DECODE_AC
// Ignore unknown messages with <10 pulses
const uint16_t kMinUnknownSize = 20;

#define _MY_VERSION_ "v0.7.0"

// Disable debug output if any of the IR pins are on the TX (D1) pin.
#if (IR_LED != 1 && IR_RX != 1)
#undef DEBUG
#define DEBUG true // Change to 'false' to disable all serial output.
#else
Expand All @@ -187,6 +221,10 @@ const uint32_t kMqttReconnectTime = 5000; // Delay(ms) between reconnect tries.
// Globals
ESP8266WebServer server(kHttpPort);
IRsend irsend = IRsend(IR_LED);
#ifdef IR_RX
IRrecv irrecv(IR_RX, kCaptureBufferSize, kCaptureTimeout, true);
decode_results capture; // Somewhere to store inbound IR messages.
#endif // IR_RX
MDNSResponder mdns;
WiFiClient espClient;
WiFiManager wifiManager;
Expand All @@ -203,6 +241,15 @@ int8_t offset; // The calculated period offset for this chip and library.
#ifdef MQTT_ENABLE
String lastMqttCmd = "None";
uint32_t lastMqttCmdTime = 0;
uint32_t lastConnectedTime = 0;
uint32_t lastDisconnectedTime = 0;
uint32_t mqttDisconnectCounter = 0;
bool wasConnected = true;
#ifdef IR_RX
String lastIrReceived = "None";
uint32_t lastIrReceivedTime = 0;
uint32_t irRecvCounter = 0;
#endif // IR_RX


// MQTT client parameters
Expand Down Expand Up @@ -278,17 +325,31 @@ void handleRoot() {
"Period Offset: " + String(offset) + "us<br>"
"IR Lib Version: " _IRREMOTEESP8266_VERSION_ "<br>"
"ESP8266 Core Version: " + ESP.getCoreVersion() + "<br>"
"IR Send GPIO: " + String(IR_LED) + "<br>"
"Total send requests: " + String(sendReqCounter) + "<br>"
"Last message sent: " + String(lastSendSucceeded ? "Ok" : "FAILED") +
" <i>(" + timeSince(lastSendTime) + ")</i></p>"
" <i>(" + timeSince(lastSendTime) + ")</i><br>"
#ifdef IR_RX
"IR Recv GPIO: " + String(IR_RX) + "<br>"
"Total IR Received: " + String(irRecvCounter) + "<br>"
"Last IR Received: " + lastIrReceived +
" <i>(" + timeSince(lastIrReceivedTime) + ")</i><br>"
#endif // IR_RX
"</p>"
#ifdef MQTT_ENABLE
"<h4>MQTT Information</h4>"
"<p>Server: " MQTT_SERVER ":" + String(kMqttPort) + " <i>(" +
(mqtt_client.connected() ? "Connected" : "Disconnected") + ")</i><br>"
(mqtt_client.connected() ? "Connected " + timeSince(lastDisconnectedTime)
: "Disconnected " + timeSince(lastConnectedTime)) +
")</i><br>"
"Disconnections: " + String(mqttDisconnectCounter - 1) + "<br>"
"Client id: " + mqtt_clientid + "<br>"
"Command topic: " MQTTcommand "<br>"
"Acknowledgements topic: " MQTTack "<br>"
"Last command seen: " +
#ifdef IR_RX
"IR Received topic: " MQTTrecv "<br>"
#endif // IR_RX
"Last MQTT command seen: " +
// lastMqttCmd is unescaped untrusted input.
// Avoid any possible HTML/XSS when displaying it.
(hasUnsafeHTMLChars(lastMqttCmd) ?
Expand Down Expand Up @@ -945,6 +1006,13 @@ void setup_wifi() {
void setup(void) {
irsend.begin();
offset = irsend.calibrate();
#if IR_RX
#if DECODE_HASH
// Ignore messages with less than minimum on or off pulses.
irrecv.setUnknownThreshold(kMinUnknownSize);
#endif // DECODE_HASH
irrecv.enableIRIn(); // Start the receiver
#endif // IR_RX

#ifdef DEBUG
// Use SERIAL_TX_ONLY so that the RX pin can be freed up for GPIO/IR use.
Expand Down Expand Up @@ -1054,32 +1122,57 @@ bool reconnect() {
#endif // MQTT_ENABLE

void loop(void) {
server.handleClient();
server.handleClient(); // Handle any web activity

#ifdef MQTT_ENABLE
uint32_t now = millis();
// MQTT client connection management
if (!mqtt_client.connected()) {
uint32_t now = millis();
if (wasConnected) {
lastDisconnectedTime = now;
wasConnected = false;
mqttDisconnectCounter++;
}
// Reconnect if it's longer than kMqttReconnectTime since we last tried.
if (now - lastReconnectAttempt > kMqttReconnectTime) {
lastReconnectAttempt = now;
debug("client mqtt not connected, trying to connect");
// Attempt to reconnect
if (reconnect()) {
lastReconnectAttempt = 0;
wasConnected = true;
if (boot) {
mqtt_client.publish(MQTTack, "IR Server just booted");
boot = false;
} else {
mqtt_client.publish(MQTTack, "IR Server just (re)connected to MQTT");
String text = "IR Server just (re)connected to MQTT. "
"Lost connection about " + timeSince(lastConnectedTime);
mqtt_client.publish(MQTTack, text.c_str());
}
lastConnectedTime = now;
debug("successful client mqtt connection");
}
}
} else {
lastConnectedTime = now;
// MQTT loop
mqtt_client.loop();
}
#endif // MQTT_ENABLE
#ifdef IR_RX
// Check if an IR code has been received via the IR RX module.
if (irrecv.decode(&capture)) {
lastIrReceivedTime = millis();
lastIrReceived = String(capture.decode_type) + "," +
resultToHexidecimal(&capture);
// If it isn't an AC code, add the bits.
if (!hasACState(capture.decode_type))
lastIrReceived += "," + String(capture.bits);
mqtt_client.publish(MQTTrecv, lastIrReceived.c_str());
irRecvCounter++;
debug("Incoming IR message sent to MQTT: " + lastIrReceived);
}
#endif // IR_RX
delay(100);
}

Expand Down
1 change: 1 addition & 0 deletions keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ printState KEYWORD2
readbits KEYWORD2
renderTime KEYWORD2
reset KEYWORD2
resultToHexidecimal KEYWORD2
resultToHumanReadableBasic KEYWORD2
resultToSourceCode KEYWORD2
resultToTimingInfo KEYWORD2
Expand Down
33 changes: 23 additions & 10 deletions src/IRutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,28 @@ std::string resultToTimingInfo(const decode_results *results) {
return output;
}

// Convert the decode_results structure's value/state to simple hexadecimal.
//
#ifdef ARDUINO
String resultToHexidecimal(const decode_results *result) {
String output = "";
#else
std::string resultToHexidecimal(const decode_results *result) {
std::string output = "";
#endif
if (hasACState(result->decode_type)) {
#if DECODE_AC
for (uint16_t i = 0; result->bits > i * 8; i++) {
if (result->state[i] < 0x10) output += "0"; // Zero pad
output += uint64ToString(result->state[i], 16);
}
#endif // DECODE_AC
} else {
output += uint64ToString(result->value, 16);
}
return output;
}

// Dump out the decode_results structure.
//
#ifdef ARDUINO
Expand All @@ -318,16 +340,7 @@ std::string resultToHumanReadableBasic(const decode_results *results) {

// Show Code & length
output += "Code : ";
if (hasACState(results->decode_type)) {
#if DECODE_AC
for (uint16_t i = 0; results->bits > i * 8; i++) {
if (results->state[i] < 0x10) output += "0"; // Zero pad
output += uint64ToString(results->state[i], 16);
}
#endif // DECODE_AC
} else {
output += uint64ToString(results->value, 16);
}
output += resultToHexidecimal(results);
output += " (" + uint64ToString(results->bits) + " bits)\n";
return output;
}
Expand Down
2 changes: 2 additions & 0 deletions src/IRutils.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,15 @@ void serialPrintUint64(uint64_t input, uint8_t base = 10);
String resultToSourceCode(const decode_results *results);
String resultToTimingInfo(const decode_results *results);
String resultToHumanReadableBasic(const decode_results *results);
String resultToHexidecimal(const decode_results *result);
#else
std::string uint64ToString(uint64_t input, uint8_t base = 10);
std::string typeToString(const decode_type_t protocol,
const bool isRepeat = false);
std::string resultToSourceCode(const decode_results *results);
std::string resultToTimingInfo(const decode_results *results);
std::string resultToHumanReadableBasic(const decode_results *results);
std::string resultToHexidecimal(const decode_results *result);
#endif
bool hasACState(const decode_type_t protocol);
uint16_t getCorrectedRawLength(const decode_results *results);
Expand Down