From 45f5379752cdc13a70aaee0db24a144dd886e09a Mon Sep 17 00:00:00 2001 From: Chris Tallon Date: Sun, 3 May 2020 16:19:40 +0100 Subject: [PATCH] Implement InputLIRC TCP only and hard coded remote keys for now --- inputlirc.cc | 183 +++++++++++++++++++++++++++++++++++++++++++++++++++ inputlirc.h | 65 ++++++++++++++++++ inputman.cc | 27 ++++++++ inputman.h | 4 +- inputudp.h | 4 +- objects.mk | 6 +- tcp.cc | 136 ++++++++++++++++++++++++++++++++++---- tcp.h | 8 ++- 8 files changed, 412 insertions(+), 21 deletions(-) create mode 100644 inputlirc.cc create mode 100644 inputlirc.h diff --git a/inputlirc.cc b/inputlirc.cc new file mode 100644 index 0000000..a1a6c0b --- /dev/null +++ b/inputlirc.cc @@ -0,0 +1,183 @@ +/* + Copyright 2020 Chris Tallon + + This file is part of VOMP. + + VOMP 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 2 of the License, or + (at your option) any later version. + + VOMP 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 VOMP. If not, see . +*/ + +#include +#include +#include + +#include "log.h" + +#include "inputlirc.h" + +const char* InputLIRC::myModName = "InputLIRC"; + +bool InputLIRC::init() +{ + if (initted) return false; + initted = true; + log = Log::getInstance(); + log->log(myModName, Log::DEBUG, "Starting InputLIRC"); + + if (!tcp.init()) + { + log->log(myModName, Log::DEBUG, "TCP init error"); + initted = false; + return false; + } + + /* FIXME + * Hard code remote keys for now. Put lines like this in inputlirc.conf + * The 'THREE' is the VOMP key name, see input.h + * remotes["lirc-remote-name"]["lirc-button-name"] = THREE; + */ + #include "inputlirc.conf" + + return true; +} + +void InputLIRC::shutdown() +{ + stop(); + + remotes.clear(); // FIXME ? + + initted = false; +} + +bool InputLIRC::start(const std::string& ip, USHORT port) +{ + // FIXME implement unix domain socket connection + + bool tr = tcp.connect(ip, port); // NCONFIG + if (!tr) + { + log->log(myModName, Log::DEBUG, "InputLIRC TCP connect failed"); + return false; + } + + threadStartProtect.lock(); // Make sure listenThread is fully initted before start returns + listenThread = std::thread( [this] + { + threadStartProtect.lock(); + threadStartProtect.unlock(); + listenLoop(); + }); + threadStartProtect.unlock(); + + log->log(myModName, Log::DEBUG, "InputLIRC command client started"); + return true; +} + +void InputLIRC::stop() +{ + std::lock_guard lg(threadStartProtect); // Also use it to protect against starting while stopping + + if (!tcp.status()) return; + + if (listenThread.joinable()) + { + threadReqStop = true; + tcp.abortCall(); + listenThread.join(); + threadReqStop = false; + } + + tcp.shutdown(); +} + +void InputLIRC::listenLoop() +{ + bool readSuccess; + + while(1) + { + std::stringstream ss = tcp.readString(&readSuccess, 0); + + if (threadReqStop) return; + + if (!readSuccess) + { + if (!tcp.status()) + { + log->log(myModName, Log::CRIT, "TCP status fail"); + return; + } // return to stop this thread + + continue; + } + + log->log(myModName, Log::DEBUG, "got data: %s", ss.str().c_str()); + + std::string remoteName, remoteButton; + int repeatCount; + if (parse(ss.str(), remoteName, remoteButton, repeatCount)) + log->log(myModName, Log::DEBUG, "Remote: '%s', Button: '%s', Count: '%i'", remoteName.c_str(), remoteButton.c_str(), repeatCount); + else + log->log(myModName, Log::ERR, "Parse error"); + + UCHAR button; + try + { + button = remotes.at(remoteName).at(remoteButton); + } + catch (std::exception& e) + { + log->log(myModName, Log::ERR, "Remote button not found"); + continue; + } + + if (repeatCount == 0) sendInputKey(button); + } +} + +bool InputLIRC::parse(const std::string& input, std::string& remoteName, std::string& remoteButton, int& repeatCount) +{ + UINT pos{}, found{}; + found = input.find(" ", pos); + if (found == std::string::npos) return false; + pos = found + 1; // pos at 00 + found = input.find(" ", pos); + if (found == std::string::npos) return false; + std::string rcString = input.substr(pos, found - pos); + try { repeatCount = std::stoi(rcString, 0, 16); } catch (std::exception& e) { return false; } + pos = found + 1; // pos at KE + found = input.find(" ", pos); + if (found == std::string::npos) + { + remoteButton = input.substr(pos); + return true; + } + remoteButton = input.substr(pos, found - pos); + pos = found + 1; + if ((input.length() - pos) > 0) + remoteName = input.substr(pos); + return true; +} + + +// const char* InputLIRC::getHardCodedHardwareKeyNamesForVompKey(UCHAR /* vompKey */) +// { +// return ""; +// } + +std::string InputLIRC::getHardwareKeyName(HWC_TYPE /* hardwareKey */) +{ + std::string retval; + return retval; +} diff --git a/inputlirc.h b/inputlirc.h new file mode 100644 index 0000000..e4dee3b --- /dev/null +++ b/inputlirc.h @@ -0,0 +1,65 @@ +/* + Copyright 2020 Chris Tallon + + This file is part of VOMP. + + VOMP 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 2 of the License, or + (at your option) any later version. + + VOMP 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 VOMP. If not, see . +*/ + +#ifndef INPUTLIRC_H +#define INPUTLIRC_H + +#include +#include +#include +#include + +#include "defines.h" +#include "tcp.h" +#include "input.h" + +class Log; + +class InputLIRC : public Input +{ + public: + bool init(); + void shutdown(); + + bool start(const std::string& ip, USHORT port); + void stop(); + + void InitHWCListwithDefaults() {}; + std::string getHardwareKeyName(HWC_TYPE hardwareKey); + + private: + Log* log{}; + + static const char* myModName; + const char* modName() { return myModName; } + + std::thread listenThread; + std::mutex threadStartProtect; + void listenLoop(); + bool threadReqStop{}; + + bool parse(const std::string& input, std::string& remoteName, std::string& remoteButton, int& repeatCount); + UCHAR TranslateHWCFixed(HWC_TYPE code) { return static_cast(code); }; + + bool initted{}; + TCP tcp; + std::map> remotes; +}; + +#endif diff --git a/inputman.cc b/inputman.cc index 4020112..dc9e2fe 100644 --- a/inputman.cc +++ b/inputman.cc @@ -25,6 +25,7 @@ #include "inputcec.h" #endif #include "inputudp.h" +#include "inputlirc.h" #ifdef WIN32 #include "inputwin.h" #endif @@ -80,6 +81,13 @@ bool InputMan::init() else { delete inputUDP; inputUDP = NULL; } + inputLirc = new InputLIRC(); + ret = inputLirc->init(); + if (ret) + oneOK = true; + else + { delete inputLirc; inputLirc = NULL; } + #ifdef WIN32 inputWin = new InputWin(); ret = inputWin->init(); @@ -111,6 +119,15 @@ bool InputMan::start() if (inputUDP && inputUDP->start()) oneOK = true; + + // FIXME + std::string lircIP; + USHORT lircPort = 8765; + // Override lircIP in inputlirc.conf2 +#include "inputlirc.conf2" + if (inputLirc && inputLirc->start(lircIP, lircPort)) oneOK = true; + + if (!oneOK) Log::getInstance()->log("InputMan", Log::CRIT, "InputMan could not start any input module"); @@ -125,6 +142,7 @@ void InputMan::stop() if (inputLinux) inputLinux->stop(); #endif if (inputUDP) inputUDP->stop(); + if (inputLirc) inputLirc->stop(); } void InputMan::shutdown() @@ -161,6 +179,15 @@ void InputMan::shutdown() inputUDP = NULL; } + if (inputLirc) + { + Log::getInstance()->log("InputMan", Log::DEBUG, "Shutdown start - LIRC"); + inputLirc->stop(); + inputLirc->shutdown(); + delete inputLirc; + inputLirc = NULL; + } + initted = false; } diff --git a/inputman.h b/inputman.h index b60002b..1705ee2 100644 --- a/inputman.h +++ b/inputman.h @@ -85,7 +85,7 @@ class InputLinux; class InputCEC; class InputWin; class InputUDP; -class InputLirc; +class InputLIRC; class InputMan: public AbstractOption { @@ -132,7 +132,7 @@ class InputMan: public AbstractOption InputCEC* inputCEC{}; InputWin* inputWin{}; InputUDP* inputUDP{}; - InputLirc* inputLirc{}; + InputLIRC* inputLirc{}; bool initted{}; }; diff --git a/inputudp.h b/inputudp.h index 151227e..0b5096a 100644 --- a/inputudp.h +++ b/inputudp.h @@ -54,7 +54,7 @@ class InputUDP : public Input const char* modName() { return myModName; } bool initted{}; - UDP4 udp4; + UDP4 udp4; // FIXME UDP6 ? Log* log{}; std::thread listenThread; @@ -62,7 +62,7 @@ class InputUDP : public Input void listenLoop(); bool listenLoopStop{}; -#ifdef WIN32 +#ifdef WIN32 // FIXME move these into UDP4? SOCKET quitPipe; #else int pfds[2]; diff --git a/objects.mk b/objects.mk index 635fb0a..63c17a0 100644 --- a/objects.mk +++ b/objects.mk @@ -12,9 +12,9 @@ OBJ_COMMON = util.o control.o thread.o timers.o i18n.o udp4.o udp6.o vdpc.o tcp. vradiorec.o vaudioselector.o vscreensaver.o vopts.o \ wselectlist.o wjpeg.o wsymbol.o wbutton.o wtextbox.o \ woptionpane.o woptionbox.o wremoteconfig.o wtabbar.o led.o \ - inputman.o input.o inputudp.o vpicturebanner.o abstractoption.o \ - eventdispatcher.o vdrrequestpacket.o vdrresponsepacket.o \ - vvideolivetv.o vsleeptimer.o \ + inputman.o input.o inputudp.o inputlirc.o vpicturebanner.o \ + abstractoption.o eventdispatcher.o vdrrequestpacket.o \ + vdrresponsepacket.o vvideolivetv.o vsleeptimer.o \ wprogressbar.o bitmap.o dvbsubtitles.o tfeed.o vteletextview.o \ teletextdecodervbiebu.o teletxt/txtfont.o movieinfo.o seriesinfo.o \ wmovieview.o wseriesview.o tvmedia.o wtvmedia.o wpictureview.o \ diff --git a/tcp.cc b/tcp.cc index 96d315c..e435b52 100644 --- a/tcp.cc +++ b/tcp.cc @@ -36,6 +36,9 @@ #include // socket connect getaddrinfo #include // memset #include // errno var +#include // malloc / free + +#include #include "defines.h" #include "log.h" @@ -198,6 +201,14 @@ void TCP::shutdown() #else while (::read(abortPipe[0], waste, 10) > 0) ; #endif + + if (recStringBuf) + { + free(recStringBuf); + recStringBuf = NULL; + recStringBufStart = 0; + recStringBufUsed = 0; + } } bool TCP::status() @@ -226,7 +237,7 @@ bool TCP::write(void* src, ULONG numBytes) return true; } -bool TCP::read(void* dst, ULONG numBytes) +bool TCP::read(void* dst, ULONG numBytes, int timeoutSec) { if (!connected) return false; @@ -239,9 +250,9 @@ bool TCP::read(void* dst, ULONG numBytes) do { - if (abortCount == 5) + if (++abortCount == 1000) { - logger->log("TCP", Log::DEBUG, "abortCount = 5"); + logger->log("TCP", Log::ALERT, "abortCount = 1000 - runaway error, or packet arrived in > 1000 pieces??"); return false; } @@ -256,33 +267,132 @@ bool TCP::read(void* dst, ULONG numBytes) if (abortPipe[0] > maxfd) maxfd = abortPipe[0]; #endif - tv.tv_sec = 2; - tv.tv_usec = 0; - - int selectResult = select(maxfd + 1, &readfds, NULL, NULL, &tv); + int selectResult; + if (timeoutSec) + { + tv.tv_sec = timeoutSec; + tv.tv_usec = 0; + selectResult = select(maxfd + 1, &readfds, NULL, NULL, &tv); + } + else + { + selectResult = select(maxfd + 1, &readfds, NULL, NULL, NULL); + } if (selectResult == -1) { shutdown(); return false; } if (selectResult == 0) return false; #ifdef WIN32 - if (FD_ISSET(abortSocket, &readfds)) { logger->log("TCP", Log::DEBUG, "aborting..."); return false; } + if (FD_ISSET(abortSocket, &readfds)) { logger->log("TCP", Log::DEBUG, "Aborting..."); return false; } #else - - - - if (FD_ISSET(abortPipe[0], &readfds)) { logger->log("TCP", Log::DEBUG, "B aborting..."); return false; } + if (FD_ISSET(abortPipe[0], &readfds)) { logger->log("TCP", Log::DEBUG, "Aborting..."); return false; } #endif int recvResult = recv(sockfd, pointer, numBytes - totalReceived, 0); if (recvResult == -1) { shutdown(); return false; } totalReceived += recvResult; pointer += recvResult; - + } while (totalReceived < numBytes); return true; } +std::stringstream TCP::readString(bool* result, int timeoutSec) +{ + *result = false; + + std::stringstream ss; + + if (!connected) return ss; + + fd_set readfds; + struct timeval tv; + + int abortCount = 0; + + if (!recStringBuf) recStringBuf = static_cast(malloc(recStringBufSize)); + if (!recStringBuf) return ss; + + // Absorb over-read from last time? + if (recStringBufUsed) + { + ss.write(&recStringBuf[recStringBufStart], recStringBufUsed); + recStringBufStart = 0; + recStringBufUsed = 0; + } + + while(true) + { + if (++abortCount == 20) + { + logger->log("TCP", Log::DEBUG, "abortCount = 20"); + return ss; + } + + FD_ZERO(&readfds); + FD_SET(sockfd, &readfds); + int maxfd = sockfd; // WIN32 ignores + +#ifdef WIN32 + FD_SET(abortSocket, &readfds); +#else + FD_SET(abortPipe[0], &readfds); + if (abortPipe[0] > maxfd) maxfd = abortPipe[0]; +#endif + + int selectResult; + if (timeoutSec) + { + tv.tv_sec = timeoutSec; + tv.tv_usec = 0; + selectResult = select(maxfd + 1, &readfds, NULL, NULL, &tv); + } + else + { + selectResult = select(maxfd + 1, &readfds, NULL, NULL, NULL); + } + + if (selectResult == -1) { shutdown(); return ss; } + if (selectResult == 0) return ss; + +#ifdef WIN32 + if (FD_ISSET(abortSocket, &readfds)) { logger->log("TCP", Log::DEBUG, "Aborting..."); return ss; } +#else + if (FD_ISSET(abortPipe[0], &readfds)) { logger->log("TCP", Log::DEBUG, "Aborting..."); return ss; } +#endif + + int recvResult = recv(sockfd, &recStringBuf[recStringBufUsed], recStringBufSize - recStringBufUsed, 0); + if (recvResult == -1) { shutdown(); return ss; } + recStringBufUsed += recvResult; + + // Do we have a full string? + for(int i = 0; i < recStringBufUsed; i++) + { + if (recStringBuf[i] == '\n') + { + // Found. + ss.write(recStringBuf, i); + + if ((i + 1) != recStringBufUsed) // over read + { + i += 1; // Advance over \n + recStringBufStart = i; + recStringBufUsed -= i; + } + else + { + recStringBufUsed = 0; + } + + *result = true; + return ss; + } + } + // no \n in buffer, go around + } +} + MACAddress TCP::getMAC() { #ifndef WIN32 diff --git a/tcp.h b/tcp.h index 39398f9..d847e38 100644 --- a/tcp.h +++ b/tcp.h @@ -44,7 +44,8 @@ class TCP void abortCall(); // causes a read/connect call to immediately abort bool connect(const std::string& ip, USHORT port); - bool read(void* dest, ULONG numBytes); + bool read(void* dest, ULONG numBytes, int timeoutSec = 2); // Set timeoutSec to 0 for no timeout + std::stringstream readString(bool* result, int timeoutSec = 2); bool write(void* src, ULONG numBytes); bool status(); @@ -56,6 +57,11 @@ class TCP bool connected{}; std::mutex writeMutex; + char* recStringBuf{}; + const int recStringBufSize{1024}; + int recStringBufStart{}; + int recStringBufUsed{}; + #ifdef WIN32 SOCKET abortSocket; #else -- 2.39.2