Implemented CAM auto responses

This commit is contained in:
Klaus Schmidinger 2017-06-10 11:53:39 +02:00
parent 93102b45e0
commit 360d8fe6b1
6 changed files with 258 additions and 12 deletions

View File

@ -9113,3 +9113,6 @@ Video Disk Recorder Revision History
- Increased SLL_LENGTH in thread.c to better handle long caller lines, and enclosed
logCaller with DEBUG_LOCKCALL to preserve memory in normal operation.
- Fixed a typo in CAMMENURETYTIMEOUT and added logging CAM enquiries.
- The new configuration file 'camresponses.conf' can be used to define automatic
responses to CAM menus, for instance to avoid annyoing popup messages or entering
the parental rating PIN. See vdr.5 for details.

29
camresponses.conf Normal file
View File

@ -0,0 +1,29 @@
# CAM responses for VDR
#
# Format:
#
# nr text action
#
# nr: the number of the CAM this action applies to (0 = all CAMs)
# text: the text in the CAM menu to react on (must be quoted with '"' if it contains
# blanks, escape '"' with '\')
# action: the action to take if the given text is encountered
#
# Possible actions are:
#
# - DISCARD: simply discard the menu (equivalent to pressing 'Back' on the RC)
# - CONFIRM: confirm the menu (equivalent to pressing 'OK' without selecting a
# particular item)
# - SELECT: select the menu item containing the text (equivalent to positioning
# the cursor on the item and pressing 'OK')
# - <number>: the given number is sent to the CAM as if it were typed in by the user
# (provided this is an input field).
#
# Note that the text given in a rule must match exactly, including any leading or
# trailing blanks. If in doubt, you can get the exact text from the log file.
# Action keywords are case insensitive.
#
# Examples:
# * "Hello! This is your annoying \"nag\" message!" DISCARD
# 3 "Please enter your PIN" 1234

190
ci.c
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: ci.c 4.16 2017/05/18 09:05:46 kls Exp $
* $Id: ci.c 4.17 2017/06/10 11:53:39 kls Exp $
*/
#include "ci.h"
@ -78,17 +78,18 @@ static char *CopyString(int Length, const uint8_t *Data)
///< Copies the string at Data.
///< Returns a pointer to a newly allocated string.
{
// Some CAMs send funny characters at the beginning of strings.
// Let's just skip them:
while (Length > 0 && (*Data == ' ' || *Data == 0x05 || *Data == 0x96 || *Data == 0x97)) {
char *s = MALLOC(char, Length + 1);
char *p = s;
while (Length > 0) {
char c = *Data;
if (isprint(c)) // some CAMs send funny characters in their strings, let's just skip them
*p++ = c;
else if (c == 0x8A) // the character 0x8A is used as newline, so let's put a real '\n' in there
*p++ = '\n';
Length--;
Data++;
}
char *s = MALLOC(char, Length + 1);
strncpy(s, (char *)Data, Length);
s[Length] = 0;
// The character 0x8A is used as newline, so let's put a real '\n' in there:
strreplace(s, 0x8A, '\n');
*p = 0;
return s;
}
@ -290,6 +291,137 @@ void cCaActivationReceiver::Receive(const uchar *Data, int Length)
}
}
// --- cCamResponse ----------------------------------------------------------
// CAM Response Actions:
#define CRA_NONE 0
#define CRA_DISCARD -1
#define CRA_CONFIRM -2
#define CRA_SELECT -3
class cCamResponse : public cListObject {
private:
int camNumber;
char *text;
int action;
public:
cCamResponse(void);
~cCamResponse();
bool Parse(const char *s);
int Matches(int CamNumber, const char *Text) const;
};
cCamResponse::cCamResponse(void)
{
camNumber = -1;
text = NULL;
action = CRA_NONE;
}
cCamResponse::~cCamResponse()
{
free(text);
}
bool cCamResponse::Parse(const char *s)
{
// Number:
s = skipspace(s);
if (*s == '*') {
camNumber = 0; // all CAMs
s++;
}
else {
char *e;
camNumber = strtol(s, &e, 10);
if (e == s || camNumber <= 0)
return false;
s = e;
}
// Text:
s = skipspace(s);
char *t = const_cast<char *>(s); // might have to modify it
char *q = NULL; // holds a copy in case of backslashes
bool InQuotes = false;
while (*t) {
if (*t == '"') {
if (t == s) { // opening quotes
InQuotes = true;
s++;
}
else if (InQuotes) // closing quotes
break;
}
else if (*t == '\\') {
if (!q) { // need to make a copy in order to strip backslashes
q = strdup(s);
t = q + (t - s);
s = q;
}
memmove(t, t + 1, strlen(t));
}
else if (*t == ' ') {
if (!InQuotes)
break;
}
t++;
}
free(text); // just for safety
text = NULL;
if (t != s) {
text = strndup(s, t - s);
s = t + 1;
}
free(q);
if (!text)
return false;
// Action:
s = skipspace(s);
if (strcasecmp(s, "DISCARD") == 0) action = CRA_DISCARD;
else if (strcasecmp(s, "CONFIRM") == 0) action = CRA_CONFIRM;
else if (strcasecmp(s, "SELECT") == 0) action = CRA_SELECT;
else if (isnumber(s)) action = atoi(s);
else
return false;
return true;
}
int cCamResponse::Matches(int CamNumber, const char *Text) const
{
if (!camNumber || camNumber == CamNumber) {
if (strcmp(text, Text) == 0)
return action;
}
return CRA_NONE;
}
// --- cCamResponses --------------------------------------------------------
class cCamResponses : public cConfig<cCamResponse> {
public:
int GetMatch(int CamNumber, const char *Text) const;
};
int cCamResponses::GetMatch(int CamNumber, const char *Text) const
{
for (const cCamResponse *cr = First(); cr; cr = Next(cr)) {
int Action = cr->Matches(CamNumber, Text);
if (Action != CRA_NONE) {
dsyslog("CAM %d: auto response %4d to '%s'\n", CamNumber, Action, Text);
return Action;
}
}
return CRA_NONE;
}
cCamResponses CamResponses;
bool CamResponsesLoad(const char *FileName, bool AllowComments, bool MustExist)
{
return CamResponses.Load(FileName, AllowComments, MustExist);
}
// --- cTPDU -----------------------------------------------------------------
#define MAX_TPDU_SIZE 4096
@ -1292,15 +1424,41 @@ void cCiMMI::Process(int Length, const uint8_t *Data)
if (l > 0) menu->titleText = GetText(l, &d);
if (l > 0) menu->subTitleText = GetText(l, &d);
if (l > 0) menu->bottomText = GetText(l, &d);
int Action = CRA_NONE;
int Select = -1;
int Item = 0;
while (l > 0) {
char *s = GetText(l, &d);
if (s) {
if (!menu->AddEntry(s))
free(s);
else if (Action == CRA_NONE) {
Action = CamResponses.GetMatch(CamSlot()->SlotNumber(), s);
if (Action == CRA_SELECT)
Select = Item;
}
}
else
break;
Item++;
}
if (Action != CRA_NONE) {
delete menu;
menu = NULL;
cCondWait::SleepMs(100);
if (Action == CRA_DISCARD) {
SendCloseMMI();
dsyslog("CAM %d: DISCARD", CamSlot()->SlotNumber());
}
else if (Action == CRA_CONFIRM) {
SendMenuAnswer(1);
dsyslog("CAM %d: CONFIRM", CamSlot()->SlotNumber());
}
else if (Action == CRA_SELECT) {
SendMenuAnswer(Select + 1);
dsyslog("CAM %d: SELECT %d", CamSlot()->SlotNumber(), Select + 1);
}
}
}
}
break;
@ -1319,6 +1477,19 @@ void cCiMMI::Process(int Length, const uint8_t *Data)
l--;
// I really wonder why there is no text length field here...
enquiry->text = CopyString(l, d);
int Action = CamResponses.GetMatch(CamSlot()->SlotNumber(), enquiry->text);
if (Action > CRA_NONE) {
char s[enquiry->expectedLength * 2];
snprintf(s, sizeof(s), "%d", Action);
if (int(strlen(s)) == enquiry->expectedLength) {
delete enquiry;
enquiry = NULL;
SendAnswer(s);
dsyslog("CAM %d: PIN", CamSlot()->SlotNumber());
}
else
esyslog("CAM %d: ERROR: unexpected PIN length %d, expected %d", CamSlot()->SlotNumber(), int(strlen(s)), enquiry->expectedLength);
}
}
}
break;
@ -1449,6 +1620,7 @@ void cCiMenu::Abort(void)
cCiEnquiry::cCiEnquiry(cCiMMI *MMI)
{
mmi = MMI;
mutex = NULL;
text = NULL;
blind = false;
expectedLength = 0;

4
ci.h
View File

@ -4,7 +4,7 @@
* See the main source file 'vdr.c' for copyright information and
* how to reach the author.
*
* $Id: ci.h 4.9 2017/05/18 09:05:46 kls Exp $
* $Id: ci.h 4.10 2017/06/10 11:53:39 kls Exp $
*/
#ifndef __CI_H
@ -529,4 +529,6 @@ public:
extern cChannelCamRelations ChannelCamRelations;
bool CamResponsesLoad(const char *FileName, bool AllowComments = false, bool MustExist = false);
#endif //__CI_H

41
vdr.5
View File

@ -8,7 +8,7 @@
.\" License as specified in the file COPYING that comes with the
.\" vdr distribution.
.\"
.\" $Id: vdr.5 4.2 2017/04/02 11:41:51 kls Exp $
.\" $Id: vdr.5 4.3 2017/06/10 11:53:39 kls Exp $
.\"
.TH vdr 5 "19 Feb 2015" "2.2" "Video Disk Recorder Files"
.SH NAME
@ -918,6 +918,45 @@ in this file, other CAMs will be tried just as well. The main purpose of this
file is to speed up channel switching in systems with more than one CAM.
This file will be read at program startup and saved when the program ends.
.SS CAM AUTO RESPONSE
If your CAM keeps popping up annoying messages or you want to make sure VDR
can record programmes with parental rating without having to enter the PIN
(in case you can't turn that off in your CAM), you can set up auto responses
in the file \fIcamresponses.conf\fR.
Each line in this file specifies one rule to apply to texts received from
the CAM. If the CAM's menu text matches the text in one of these rules,
the given action is taken and sent to the CAM as an automatic response,
without any menu appearing on the screen. The first match wins.
The format of these rules is:
nr text action
where
.TS
tab (@);
l l.
nr @is the number of the CAM this action applies to (0 = all CAMs)
text @is the text in the CAM menu to react on (must be quoted with '"' if it contains blanks, escape '"' with '\\')
action @is the action to take if the given text is encountered
.TE
Possible actions are:
.TS
tab (@);
l l.
DISCARD @simply discard the menu (equivalent to pressing 'Back' on the RC)
CONFIRM @confirm the menu (equivalent to pressing 'OK' without selecting a particular item)
SELECT @select the menu item containing the text (equivalent to positioning the cursor on the item and pressing 'OK')
<number> @the given number is sent to the CAM as if it were tyed in by the user (provided this is an input field).
.TE
Note that the text given in a rule must match exactly, including any leading or
trailing blanks. If in doubt, you can get the exact text from the log file.
Action keywords are case insensitive.
Everything following (and including) a '#' character is considered to be comment.
.SS COMMANDLINE OPTIONS
If started without any options, vdr tries to read any files in the directory
/etc/vdr/conf.d with names that do not begin with a '.' and that end with '.conf'.

3
vdr.c
View File

@ -22,7 +22,7 @@
*
* The project's page is at http://www.tvdr.de
*
* $Id: vdr.c 4.17 2017/06/06 10:53:44 kls Exp $
* $Id: vdr.c 4.18 2017/06/10 11:53:39 kls Exp $
*/
#include <getopt.h>
@ -763,6 +763,7 @@ int main(int argc, char *argv[])
Keys.Load(AddDirectory(ConfigDirectory, "remote.conf"));
KeyMacros.Load(AddDirectory(ConfigDirectory, "keymacros.conf"), true);
Folders.Load(AddDirectory(ConfigDirectory, "folders.conf"));
CamResponsesLoad(AddDirectory(ConfigDirectory, "camresponses.conf"), true);
if (!*cFont::GetFontFileName(Setup.FontOsd)) {
const char *msg = "no fonts available - OSD will not show any text!";