/* * IRCommandDispatcher.hpp * * Library to process IR commands by calling functions specified in a mapping array. * Commands can be tagged as blocking or non blocking. * * To run this example you need to install the "IRremote" or "IRMP" library. * Install it under "Tools -> Manage Libraries..." or "Ctrl+Shift+I" * * The IR library calls a callback function, which executes a non blocking command directly in ISR context! * A blocking command is stored and sets a stop flag for an already running blocking function to terminate. * The blocking command can in turn be executed by main loop by calling IRDispatcher.checkAndRunSuspendedBlockingCommands(). * * Copyright (C) 2019-2021 Armin Joachimsmeyer * armin.joachimsmeyer@gmail.com * * This file is part of ServoEasing https://github.com/ArminJo/ServoEasing. * This file is part of IRMP https://github.com/ukw100/IRMP. * This file is part of Arduino-IRremote https://github.com/Arduino-IRremote/Arduino-IRremote. * * ServoEasing is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef _IR_COMMAND_DISPATCHER_HPP #define _IR_COMMAND_DISPATCHER_HPP #include #include "IRCommandDispatcher.h" /* * Enable this to see information on each call. * Since there should be no library which uses Serial, it should only be enabled for development purposes. */ #if defined(INFO) && !defined(LOCAL_INFO) #define LOCAL_INFO #else //#define LOCAL_INFO // This enables info output only for this file #endif #if defined(DEBUG) && !defined(LOCAL_DEBUG) #define LOCAL_DEBUG // Propagate debug level #define LOCAL_INFO #else //#define LOCAL_DEBUG // This enables debug output only for this file #endif IRCommandDispatcher IRDispatcher; #if defined(USE_TINY_IR_RECEIVER) #include "TinyIRReceiver.hpp" // included in "IRremote" library #if defined(LOCAL_INFO) #define CD_INFO_PRINT(...) Serial.print(__VA_ARGS__); #define CD_INFO_PRINTLN(...) Serial.println(__VA_ARGS__); #else #define CD_INFO_PRINT(...) void(); #define CD_INFO_PRINTLN(...) void(); #endif void IRCommandDispatcher::init() { initPCIInterruptForTinyReceiver(); } /* * This is the TinyReceiver callback function which is called if a complete command was received * It checks for right address and then call the dispatcher */ #if defined(ESP32) || defined(ESP8266) void IRAM_ATTR handleReceivedTinyIRData(uint16_t aAddress, uint8_t aCommand, bool isRepeat) # else void handleReceivedTinyIRData(uint16_t aAddress, uint8_t aCommand, bool isRepeat) # endif { IRDispatcher.IRReceivedData.address = aAddress; IRDispatcher.IRReceivedData.command = aCommand; IRDispatcher.IRReceivedData.isRepeat = isRepeat; IRDispatcher.IRReceivedData.MillisOfLastCode = millis(); CD_INFO_PRINT(F("A=0x")); CD_INFO_PRINT(aAddress, HEX); CD_INFO_PRINT(F(" C=0x")); CD_INFO_PRINT(aCommand, HEX); if (isRepeat) { CD_INFO_PRINT(F("R")); } CD_INFO_PRINTLN(); if (aAddress == IR_ADDRESS) { // IR_ADDRESS is defined in IRCommandMapping.h IRDispatcher.IRReceivedData.isAvailable = true; if(!IRDispatcher.doNotUseDispatcher) { IRDispatcher.checkAndCallCommand(false); // only short commands are executed directly } } else { CD_INFO_PRINT(F("Wrong address. Expected 0x")); CD_INFO_PRINTLN(IR_ADDRESS, HEX); } } #elif defined(USE_IRMP_LIBRARY) #if !defined(IRMP_USE_COMPLETE_CALLBACK) # error IRMP_USE_COMPLETE_CALLBACK must be activated for IRMP library #endif void IRCommandDispatcher::init() { irmp_init(); } /* * This is the callback function is called if a complete command was received */ #if defined(ESP32) || defined(ESP8266) void IRAM_ATTR handleReceivedIRData() #else void handleReceivedIRData() #endif { IRMP_DATA tTeporaryData; irmp_get_data(&tTeporaryData); IRDispatcher.IRReceivedData.address = tTeporaryData.address; IRDispatcher.IRReceivedData.command = tTeporaryData.command; IRDispatcher.IRReceivedData.isRepeat = tTeporaryData.flags & IRMP_FLAG_REPETITION; IRDispatcher.IRReceivedData.MillisOfLastCode = millis(); CD_INFO_PRINT(F("A=0x")); CD_INFO_PRINT(IRDispatcher.IRReceivedData.address, HEX); CD_INFO_PRINT(F(" C=0x")); CD_INFO_PRINT(IRDispatcher.IRReceivedData.command, HEX); if (IRDispatcher.IRReceivedData.isRepeat) { CD_INFO_PRINT(F("R")); } CD_INFO_PRINTLN(); // To enable delay() for commands #if !defined(ARDUINO_ARCH_MBED) interrupts(); // be careful with always executable commands which lasts longer than the IR repeat duration. #endif if (IRDispatcher.IRReceivedData.address == IR_ADDRESS) { IRDispatcher.checkAndCallCommand(true); } else { CD_INFO_PRINT(F("Wrong address. Expected 0x")); CD_INFO_PRINTLN(IR_ADDRESS, HEX); } } #endif /* * The main dispatcher function * Sets flags justCalledRegularIRCommand, executingBlockingCommand */ void IRCommandDispatcher::checkAndCallCommand(bool aCallAlsoBlockingCommands) { if (IRReceivedData.command == COMMAND_EMPTY) { return; } /* * Search for command in Array of IRToCommandMappingStruct */ for (uint_fast8_t i = 0; i < sizeof(IRMapping) / sizeof(struct IRToCommandMappingStruct); ++i) { if (IRReceivedData.command == IRMapping[i].IRCode) { /* * Command found */ #if defined(LOCAL_INFO) const __FlashStringHelper *tCommandName = reinterpret_cast(IRMapping[i].CommandString); #endif /* * Check for repeat and if it is allowed for the current command */ if (IRReceivedData.isRepeat && !(IRMapping[i].Flags & IR_COMMAND_FLAG_REPEATABLE)) { #if defined(LOCAL_DEBUG) Serial.print(F("Repeats of command \"")); Serial.print(tCommandName); Serial.println("\" not accepted"); #endif return; } /* * Do not accept recursive call of the same command */ if (currentBlockingCommandCalled == IRReceivedData.command) { #if defined(LOCAL_DEBUG) Serial.print(F("Recursive command \"")); Serial.print(tCommandName); Serial.println("\" not accepted"); #endif return; } /* * Handle stop commands */ if (IRMapping[i].Flags & IR_COMMAND_FLAG_IS_STOP_COMMAND) { requestToStopReceived = true; CD_INFO_PRINTLN(F("Stop command received")); } else { // lets start a new turn requestToStopReceived = false; } bool tIsNonBlockingCommand = (IRMapping[i].Flags & IR_COMMAND_FLAG_NON_BLOCKING); if (tIsNonBlockingCommand) { // short command here, just call CD_INFO_PRINT(F("Run non blocking command: ")); CD_INFO_PRINTLN (tCommandName); IRMapping[i].CommandToCall(); } else { if (!aCallAlsoBlockingCommands) { /* * Store for main loop to execute */ BlockingCommandToRunNext = IRReceivedData.command; requestToStopReceived = true; // to stop running command CD_INFO_PRINT(F("Blocking command ")); CD_INFO_PRINT (tCommandName); CD_INFO_PRINTLN(F(" stored as next command and requested stop")); } else { if (executingBlockingCommand) { // Logical error has happened CD_INFO_PRINTLN( F("Request to execute blocking command while another command is running. This should not happen!")); /* * A blocking command may not be called as long as another blocking command is running. * Try to stop again */ BlockingCommandToRunNext = IRReceivedData.command; requestToStopReceived = true; // to stop running command return; } /* * here we are called from main loop and execute a command */ justCalledBlockingCommand = true; executingBlockingCommand = true; // set lock for recursive calls currentBlockingCommandCalled = IRReceivedData.command; /* * This call is blocking!!! */ CD_INFO_PRINT(F("Run blocking command: ")); CD_INFO_PRINTLN (tCommandName); IRMapping[i].CommandToCall(); #if defined(TRACE) Serial.println(F("End of blocking command")); #endif executingBlockingCommand = false; currentBlockingCommandCalled = COMMAND_INVALID; } } break; // command found } } // for loop return; } /* * Special delay function for the IRCommandDispatcher. Returns prematurely if requestToStopReceived is set. * To be used in blocking functions as delay * @return true - as soon as stop received */ bool IRCommandDispatcher::delayAndCheckForStop(uint16_t aDelayMillis) { uint32_t tStartMillis = millis(); do { if (IRDispatcher.requestToStopReceived) { return true; } } while (millis() - tStartMillis < aDelayMillis); return false; } /* * Intended to be called from main loop * @return true, if command was called */ bool IRCommandDispatcher::checkAndRunSuspendedBlockingCommands() { /* * Take last rejected command and call associated function */ if (BlockingCommandToRunNext != COMMAND_INVALID) { CD_INFO_PRINT(F("Take stored command = 0x")); CD_INFO_PRINTLN(BlockingCommandToRunNext, HEX); IRReceivedData.command = BlockingCommandToRunNext; BlockingCommandToRunNext = COMMAND_INVALID; IRReceivedData.isRepeat = false; checkAndCallCommand(true); return true; } return false; } void IRCommandDispatcher::printIRCommandString(Print *aSerial) { aSerial->print(F("IRCommand=")); for (uint_fast8_t i = 0; i < sizeof(IRMapping) / sizeof(struct IRToCommandMappingStruct); ++i) { if (IRReceivedData.command == IRMapping[i].IRCode) { aSerial->println(reinterpret_cast(IRMapping[i].CommandString)); return; } } aSerial->println(reinterpret_cast(unknown)); } void IRCommandDispatcher::setRequestToStopReceived() { requestToStopReceived = true; } #if defined(LOCAL_DEBUG) #undef LOCAL_DEBUG #endif #if defined(LOCAL_INFO) #undef LOCAL_INFO #endif #endif // _IR_COMMAND_DISPATCHER_HPP