1 ''' Top-level python bindings for the lircd socket interface. ''' 28 from abc
import ABCMeta, abstractmethod
40 _DEFAULT_PROG =
'lircd-client' 44 ''' Get default value for the lircd socket path, using (falling priority): 46 - The environment variable LIRC_SOCKET_PATH. 47 - The 'output' value in the lirc_options.conf file if value and the 48 corresponding file exists. 49 - A hardcoded default lirc.config.VARRUNDIR/lirc/lircd, possibly 53 if 'LIRC_SOCKET_PATH' in os.environ:
54 return os.environ[
'LIRC_SOCKET_PATH']
55 path = lirc.config.SYSCONFDIR +
'/lirc/lirc_options.conf' 56 parser = configparser.SafeConfigParser()
59 except configparser.Error:
62 if parser.has_section(
'lircd'):
64 path = str(parser.get(
'lircd',
'output'))
65 if os.path.exists(path):
67 except configparser.NoOptionError:
69 return lirc.config.VARRUNDIR +
'/lirc/lircd' 73 ''' Get default path to the lircrc file according to (falling priority): 75 - $XDG_CONFIG_HOME/lircrc if environment variable and file exists. 76 - ~/.config/lircrc if it exists. 77 - ~/.lircrc if it exists 78 - A hardcoded default lirc.config.SYSCONFDIR/lirc/lircrc, whether 81 if 'XDG_CONFIG_HOME' in os.environ:
82 path = os.path.join(os.environ[
'XDG_CONFIG_HOME'],
'lircrc')
83 if os.path.exists(path):
85 path = os.path.join(os.path.expanduser(
'~'),
'.config' 'lircrc')
86 if os.path.exists(path):
88 path = os.path.join(os.path.expanduser(
'~'),
'.lircrc')
89 if os.path.exists(path):
91 return os.path.join(lirc.config.SYSCONFDIR,
'lirc',
'lircrc')
94 class BadPacketException(Exception):
95 ''' Malformed or otherwise unparsable packet received. ''' 99 class TimeoutException(Exception):
100 ''' Timeout receiving data from remote host.''' 155 ''' Abstract interface for all connections. ''' 160 def __exit__(self, exc_type, exc, traceback):
164 def readline(self, timeout: float =
None) -> str:
165 ''' Read a buffered line 169 - If set to 0 immediately return either a line or None. 170 - If set to None (default mode) use blocking read. 172 Returns: code string as described in lircd(8) without trailing 175 Raises: TimeoutException if timeout > 0 expires. 181 ''' Return the file nr used for IO, suitable for select() etc. ''' 186 ''' Return true if next readline(None) won't block . ''' 191 ''' Close/release all resources ''' 195 class RawConnection(AbstractConnection):
196 ''' Interface to receive code strings as described in lircd(8). 199 - socket_path: lircd output socket path, see get_default_socket_path() 201 - prog: Program name used in lircrc decoding, see ircat(1). Could be 202 omitted if only raw keypresses should be read. 207 def __init__(self, socket_path: str =
None, prog: str = _DEFAULT_PROG):
209 os.environ[
'LIRC_SOCKET_PATH'] = socket_path
212 _client.lirc_deinit()
213 fd = _client.lirc_init(prog)
214 self.
_socket = socket.fromfd(fd, socket.AF_UNIX, socket.SOCK_STREAM)
215 self.
_select = selectors.DefaultSelector()
219 def readline(self, timeout: float =
None) -> str:
220 ''' Implements AbstractConnection.readline(). ''' 225 start + timeout - time.clock()
if timeout
else timeout)
229 "readline: no data within %f seconds" % timeout)
234 return line.decode(
'ascii',
'ignore')
237 ''' Implements AbstractConnection.fileno(). ''' 241 ''' Implements AbstractConnection.has_data() ''' 245 ''' Implements AbstractConnection.close() ''' 247 _client.lirc_deinit()
250 AbstractConnection.register(RawConnection)
254 ''' Interface to receive lircrc-translated keypresses. This is basically 255 built on top of lirc_code2char() and as such supporting centralized 256 translations using lircrc_class. See lircrcd(8). 259 - program: string, used to identify client. See ircat(1) 260 - lircrc: lircrc file path. See get_default_lircrc_path() for defaults. 261 - socket_path: lircd output socket path, see get_default_socket_path() 266 def __init__(self, program: str,
267 lircrc_path: str =
None,
268 socket_path: str =
None):
272 raise FileNotFoundError(
'Cannot find lircrc config file.')
274 self.
_lircrc = _client.lirc_readconfig(lircrc_path)
278 def readline(self, timeout: float =
None):
279 ''' Implements AbstractConnection.readline(). ''' 286 if not strings
or len(strings) == 0:
294 ''' Implements AbstractConnection.has_data() ''' 298 ''' Implements AbstractConnection.fileno(). ''' 302 ''' Implements AbstractConnection.close() ''' 304 _client.lirc_freeconfig(self.
_lircrc)
307 AbstractConnection.register(LircdConnection)
364 ''' Extends the parent with a send() method. ''' 366 def __init__(self, socket_path: str =
None):
367 RawConnection.__init__(self, socket_path)
369 def send(self, command: (bytearray, str)):
370 ''' Send single line over socket ''' 371 if not isinstance(command, bytearray):
372 command = command.encode(
'ascii')
373 while len(command) > 0:
375 command = command[sent:]
379 ''' Public reply parser result, available when completed. ''' 386 ''' Command, parser and connection container with a run() method. ''' 388 def __init__(self, cmd: str,
389 connection: AbstractConnection,
390 timeout: float = 0.4):
391 self.
_conn = connection
395 def run(self, timeout: float =
None):
396 ''' Run the command and return a Reply. Timeout as of 397 AbstractConnection.readline() 400 while not self.
_parser.is_completed():
409 ''' The status/result from parsing a command reply. 412 result: Enum Result, reflects parser state. 413 success: bool, reflects SUCCESS/ERROR. 414 data: List of lines, the command DATA payload. 415 sighup: bool, reflects if a SIGHUP package has been received 416 (these are otherwise ignored). 417 last_line: str, last input line (for error messages). 420 self.
result = Result.INCOMPLETE
428 ''' Handles the actual parsing of a command reply. ''' 432 self._state = self._State.BEGIN
433 self._lines_expected =
None 434 self._buffer = bytearray(0)
436 def is_completed(self) -> bool:
437 ''' Returns true if no more reply input is required. ''' 440 def feed(self, line: str):
441 ''' Enter a line of data into parsing FSM, update state. ''' 444 self._State.BEGIN: self._begin,
445 self._State.COMMAND: self._command,
446 self._State.RESULT: self._result,
447 self._State.DATA: self._data,
448 self._State.LINE_COUNT: self._line_count,
449 self._State.LINES: self._lines,
450 self._State.END: self._end,
451 self._State.SIGHUP_END: self._sighup_end
458 if self.
_state == self._State.DONE:
468 ''' Internal FSM state. ''' 480 def _bad_packet_exception(self, line):
483 'Cannot parse: %s\nat state: %s\n' % (line, self.
_state))
485 def _begin(self, line):
487 self._state = self._State.COMMAND
489 def _command(self, line):
491 self._bad_packet_exception(line)
492 elif line ==
'SIGHUP':
493 self._state = self._State.SIGHUP_END
496 self._state = self._State.RESULT
498 def _result(self, line):
499 if line
in [
'SUCCESS',
'ERROR']:
500 self.success = line ==
'SUCCESS' 501 self._state = self._State.DATA
503 self._bad_packet_exception(line)
505 def _data(self, line):
507 self._state = self._State.DONE
509 self._state = self._State.LINE_COUNT
511 self._bad_packet_exception(line)
513 def _line_count(self, line):
515 self._lines_expected = int(line)
517 self._bad_packet_exception(line)
518 if self._lines_expected == 0:
519 self._state = self._State.END
521 self._state = self._State.LINES
523 def _lines(self, line):
524 self.data.append(line)
525 if len(self.data) >= self._lines_expected:
526 self._state = self._State.END
528 def _end(self, line):
530 self._bad_packet_exception(line)
531 self._state = self._State.DONE
533 def _sighup_end(self, line):
535 ReplyParser.__init__(self)
538 self._bad_packet_exception(line)
556 ''' Simulate a button press, see SIMULATE in lircd(8) manpage. ''' 559 def __init__(self, connection: AbstractConnection,
560 remote: str, key: str, repeat: int = 1, keycode: int = 0):
561 cmd =
'SIMULATE %016d %02d %s %s\n' % \
562 (int(keycode), int(repeat), key, remote)
563 Command.__init__(self, cmd, connection)
567 ''' List available remotes, see LIST in lircd(8) manpage. ''' 569 def __init__(self, connection: AbstractConnection):
570 Command.__init__(self,
'LIST\n', connection)
574 ''' List available keys in given remote, see LIST in lircd(8) manpage. ''' 576 def __init__(self, connection: AbstractConnection, remote: str):
577 Command.__init__(self,
'LIST %s\n' % remote, connection)
581 ''' Start repeating given key, see SEND_START in lircd(8) manpage. ''' 583 def __init__(self, connection: AbstractConnection,
584 remote: str, key: str):
585 cmd =
'SEND_START %s %s\n' % (remote, key)
586 Command.__init__(self, cmd, connection)
590 ''' Stop repeating given key, see SEND_STOP in lircd(8) manpage. ''' 592 def __init__(self, connection: AbstractConnection,
593 remote: str, key: str):
594 cmd =
'SEND_STOP %s %s\n' % (remote, key)
595 Command.__init__(self, cmd, connection)
599 ''' Send given key, see SEND_ONCE in lircd(8) manpage. ''' 601 def __init__(self, connection: AbstractConnection,
602 remote: str, keys: str):
604 raise ValueError(
'No keys to send given')
605 cmd =
'SEND_ONCE %s %s\n' % (remote,
' '.join(keys))
606 Command.__init__(self, cmd, connection)
610 ''' Set transmitters to use, see SET_TRANSMITTERS in lircd(8) manpage. 613 transmitter: Either a bitmask or a list of int describing active 617 def __init__(self, connection: AbstractConnection,
618 transmitters: (int, list)):
619 if isinstance(transmitters, list):
621 for transmitter
in transmitters:
622 mask |= (1 << (int(transmitter) - 1))
625 cmd =
'SET_TRANSMITTERS %d\n' % mask
626 Command.__init__(self, cmd, connection)
630 ''' Get lircd version, see VERSION in lircd(8) manpage. ''' 632 def __init__(self, connection: AbstractConnection):
633 Command.__init__(self,
'VERSION\n', connection)
637 ''' Set a driver option value, see DRV_OPTION in lircd(8) manpage. ''' 639 def __init__(self, connection: AbstractConnection,
640 option: str, value: str):
641 cmd =
'DRV_OPTION %s %s\n' % (option, value)
642 Command.__init__(self, cmd, connection)
646 ''' Start/stop logging lircd output , see SET_INPUTLOG in lircd(8) 650 def __init__(self, connection: AbstractConnection,
651 logfile: str =
None):
652 cmd =
'SET_INPUTLOG' + (
' ' + logfile
if logfile
else '') +
'\n' 653 Command.__init__(self, cmd, connection)
667 ''' Identify client using the prog token, see IDENT in lircrcd(8) ''' 669 def __init__(self, connection: AbstractConnection,
672 raise ValueError(
'The prog argument cannot be None')
673 cmd =
'IDENT {}\n'.format(prog)
674 Command.__init__(self, cmd, connection)
678 '''Translate a keypress to application string, see CODE in lircrcd(8) ''' 680 def __init__(self, connection: AbstractConnection,
683 raise ValueError(
'The prog argument cannot be None')
684 Command.__init__(self,
'CODE {}\n'.format(code), connection)
688 '''Get current translation mode, see GETMODE in lircrcd(8) ''' 690 def __init__(self, connection: AbstractConnection):
691 Command.__init__(self,
"GETMODE\n", connection)
695 '''Set current translation mode, see SETMODE in lircrcd(8) ''' 697 def __init__(self, connection: AbstractConnection,
700 raise ValueError(
'The mode argument cannot be None')
701 Command.__init__(self,
'SETMODE {}\n'.format(mode), connection)
def close(self)
Implements AbstractConnection.close()
Get lircd version, see VERSION in lircd(8) manpage.
Send given key, see SEND_ONCE in lircd(8) manpage.
bool has_data(self)
Implements AbstractConnection.has_data()
str get_default_socket_path()
Get default value for the lircd socket path, using (falling priority):
List available keys in given remote, see LIST in lircd(8) manpage.
Simulate a button press, see SIMULATE in lircd(8) manpage.
result
Enum Result, reflects parser state.
success
bool, reflects SUCCESS/ERROR.
bool has_data(self)
Return true if next readline(None) won't block .
sighup
bool, reflects if a SIGHUP package has been received
Public reply parser result, available when completed.
def close(self)
Close/release all resources.
Identify client using the prog token, see IDENT in lircrcd(8)
def readline(self, float timeout=None)
Implements AbstractConnection.readline().
Command, parser and connection container with a run() method.
str get_default_lircrc_path()
Get default path to the lircrc file according to (falling priority):
bool has_data(self)
Implements AbstractConnection.has_data()
last_line
str, last input line (for error messages).
Interface to receive code strings as described in lircd(8).
Interface to receive lircrc-translated keypresses.
int fileno(self)
Return the file nr used for IO, suitable for select() etc.
Extends the parent with a send() method.
def run(self, float timeout=None)
Run the command and return a Reply.
str readline(self, float timeout=None)
Read a buffered line.
The status/result from parsing a command reply.
Abstract interface for all connections.
data
List of lines, the command DATA payload.
Set transmitters to use, see SET_TRANSMITTERS in lircd(8) manpage.
Timeout receiving data from remote host.
Malformed or otherwise unparsable packet received.
str readline(self, float timeout=None)
Implements AbstractConnection.readline().
Handles the actual parsing of a command reply.
Start/stop logging lircd output , see SET_INPUTLOG in lircd(8) manpage.
Start repeating given key, see SEND_START in lircd(8) manpage.
Get current translation mode, see GETMODE in lircrcd(8)
Stop repeating given key, see SEND_STOP in lircd(8) manpage.
Set a driver option value, see DRV_OPTION in lircd(8) manpage.
def send(self,(bytearray, str) command)
Send single line over socket.
Translate a keypress to application string, see CODE in lircrcd(8)
int fileno(self)
Implements AbstractConnection.fileno().
List available remotes, see LIST in lircd(8) manpage.
def close(self)
Implements AbstractConnection.close()
int fileno(self)
Implements AbstractConnection.fileno().
Set current translation mode, see SETMODE in lircrcd(8)