]> git.vomp.tv Git - vompclient.git/commitdiff
Implement InputLIRC
authorChris Tallon <chris@vomp.tv>
Sun, 3 May 2020 15:19:40 +0000 (16:19 +0100)
committerChris Tallon <chris@vomp.tv>
Sun, 3 May 2020 15:19:40 +0000 (16:19 +0100)
TCP only and hard coded remote keys for now

inputlirc.cc [new file with mode: 0644]
inputlirc.h [new file with mode: 0644]
inputman.cc
inputman.h
inputudp.h
objects.mk
tcp.cc
tcp.h

diff --git a/inputlirc.cc b/inputlirc.cc
new file mode 100644 (file)
index 0000000..a1a6c0b
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+*/
+
+#include <sstream>
+#include <algorithm>
+#include <stdexcept>
+
+#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<std::mutex> 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 (file)
index 0000000..e4dee3b
--- /dev/null
@@ -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 <https://www.gnu.org/licenses/>.
+*/
+
+#ifndef INPUTLIRC_H
+#define INPUTLIRC_H
+
+#include <thread>
+#include <mutex>
+#include <map>
+#include <string>
+
+#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<UCHAR>(code); };
+
+    bool initted{};
+    TCP tcp;
+    std::map<std::string, std::map<std::string, UCHAR>> remotes;
+};
+
+#endif
index 402011210bbd0389228ff033b3e83dc307e360ac..dc9e2fe146f495a214660dc83c75b107c1541325 100644 (file)
@@ -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;
 }
 
index b60002b938ca83b0be927146d132cfcdb8f93c35..1705ee2f9476d54b64caf10621e521abfc1234db 100644 (file)
@@ -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{};
 };
index 151227eeb3130253101f8feb69dbea4bbce7ac51..0b5096a1cb679aa432a6017f9adbf442df114e09 100644 (file)
@@ -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];
index 635fb0af5d4d1c7ae143fc2e72944ab9828850ba..63c17a0bc90672bc1b06131a13f192ab2c8d8dc9 100644 (file)
@@ -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 96d315c6fc962a9c5a9f48f44a02fd2e297afcba..e435b52ac590b64cbc0d5ef3741669b8cae0b3f7 100644 (file)
--- a/tcp.cc
+++ b/tcp.cc
@@ -36,6 +36,9 @@
 #include <sys/types.h>  // socket connect getaddrinfo
 #include <string.h> // memset
 #include <errno.h> // errno var
+#include <stdlib.h> // malloc / free
+
+#include <sstream>
 
 #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<char*>(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 39398f9e6caac29f0632f27d456dc5773c71b283..d847e38942bede831c4288409b025308f05bb810 100644 (file)
--- 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