LIRC libraries
LinuxInfraredRemoteControl
lirctool.py
1 # Copyright (C) 2017 Bengt Martensson.
2 
3 # This program is free software: you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or (at
6 # your option) any later version.
7 #
8 # This program is distributed in the hope that it will be useful, but
9 # WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11 # General Public License for more details.
12 #
13 # You should have received a copy of the GNU General Public License along with
14 # this program. If not, see http://www.gnu.org/licenses/.
15 
16 """
17 This is a new and independent implementation of the Lirc irsend(1) program.
18 It offers a Python API and a command line interface. The command line
19 interface is almost, but not quite, compatible with irsend. Instead, it is
20 organized as a program with subcommands, send_once, etc.
21 
22 There are some other subtile differences from irsend:
23 
24 * subcommand must be lower case,
25 * send_once only takes one command (irsend takes several),
26 * send_stop without arguments uses the remote and the command from the last
27  send_start command,
28 * no need to give dummy empty arguments for list,
29 * The --count argument to send_once is argument to the subcommand.
30 * the code in list remote is suppressed, unless -c is given,
31 * port number must be given with the --port (-p) argument; hostip:portnumber
32  is not recognized,
33 * verbose option --verbose (-v)
34 * selectable timeout with --timeout (-t) option
35 * better error messages
36 
37 It is using the new lirc Python API, including a C extension module.
38 
39 For a GUI version, look at IrScrutinizer.
40 For a Java version, look at Javairtool
41 https://github.com/bengtmartensson/JavaLircClient
42 """
43 
44 import argparse
45 import socket
46 import sys
47 
48 import client
49 
50 _DEFAULT_PORT = 8765
51 
52 
53 def _parse_commandline():
54  ''' Parse the command line, returns a filled-in parser. '''
55  # pylint: disable=bad-continuation
56  parser = argparse.ArgumentParser(
57  prog='irtool',
58  description="Tool to send IR codes and manipulate lircd(8)")
59  parser.add_argument(
60  "-a", "--address",
61  help='lircd host IP name or address, overrides --device.',
62  metavar="host", dest='address', default=None)
63  path = client.get_default_socket_path()
64  parser.add_argument(
65  '-d', '--device',
66  help='lircd socket path [%s]' % path, metavar="path",
67  dest='socket_pathname', default=None)
68  parser.add_argument(
69  '-p', '--port',
70  help='lircd IP port, use with --address [%d] ' % _DEFAULT_PORT,
71  dest='port', default=_DEFAULT_PORT, type=int)
72  parser.add_argument(
73  '-t', '--timeout',
74  help='Timeout in milliseconds [No timeout]', metavar="ms",
75  dest='timeout', type=int, default=None)
76  parser.add_argument(
77  '-V', '--version',
78  help='Display version information for irtool',
79  dest='versionRequested', action='store_true')
80  parser.add_argument(
81  '-v', '--verbose',
82  help='Have some commands executed verbosely',
83  dest='verbose', action='store_true')
84  subparsers = parser.add_subparsers(
85  dest='subcommand',
86  metavar='sub-commands')
87 
88  # Command send_once
89  parser_send_once = subparsers.add_parser(
90  'send-once',
91  help='Send one command')
92  parser_send_once.add_argument(
93  '-#', '-c', '--count',
94  help='Number of times to send command in send_once',
95  dest='count', type=int, default=1)
96  parser_send_once.add_argument('remote', help='Name of remote')
97  parser_send_once.add_argument('command', help='Name of command')
98 
99  # Command send_start
100  parser_send_start = subparsers.add_parser(
101  'send-start',
102  help='Start sending one command until stopped')
103  parser_send_start.add_argument(
104  'remote',
105  help='Name of remote')
106  parser_send_start.add_argument(
107  'command',
108  help='Name of command')
109 
110  # Command send_stop
111  parser_send_stop = subparsers.add_parser(
112  'send-stop',
113  help='Stop sending the command from send_start')
114  parser_send_stop.add_argument(
115  'remote',
116  help='remote command')
117  parser_send_stop.add_argument(
118  'command',
119  help='remote command')
120 
121  # Command list-remotes
122  subparsers.add_parser('list-remotes', help='List available remotes')
123 
124  # Command list-keys
125  parser_list_keys = subparsers.add_parser(
126  'list-keys',
127  help='list defined keys in given remote')
128  parser_list_keys.add_argument(
129  'remote',
130  help='Name of remote')
131  parser_list_keys.add_argument(
132  "-c", "--codes",
133  help='List the numerical codes in lircd.conf, not just names',
134  dest='codes', action='store_true')
135 
136  # Command driver-option
137  parser_drv_option = subparsers.add_parser(
138  'driver-option',
139  help='Set driver option to given value')
140  parser_drv_option.add_argument('option', help='Option name')
141  parser_drv_option.add_argument('value', help='Option value')
142 
143  # Command set_input_logging
144  parser_set_input_log = \
145  subparsers.add_parser('set-inputlog', help='Set input logging')
146  parser_set_input_log.add_argument(
147  'log_file', nargs='?',
148  help='Path to log file, empty to inhibit logging', default='')
149 
150  # Command set_driver_options
151  parser_set_driver_options = subparsers.add_parser(
152 
153  'set-driver-options',
154  help='Set driver options')
155  parser_set_driver_options.add_argument('key', help='Option name')
156  parser_set_driver_options.add_argument('value', help='Option value')
157 
158  # Command get version
159  subparsers.add_parser('version', help='Get lircd version')
160 
161  # Command simulate
162  parser_simulate = subparsers.add_parser(
163  'simulate',
164  help='Fake the reception of IR signals')
165  parser_simulate.add_argument(
166  'remote',
167  help='remote part of simulated event')
168  parser_simulate.add_argument(
169  'key',
170  help='Name of command to be faked')
171  parser_simulate.add_argument(
172  'data',
173  help='Key press data to be sent to the Lircd')
174 
175  # Command set_transmitters
176  parser_set_transmitters = subparsers.add_parser(
177  'set-transmitters',
178  help='Set transmitters')
179  parser_set_transmitters.add_argument(
180  'transmitters',
181  metavar='N', nargs='+', help="transmitter...")
182 
183  args = parser.parse_args()
184 
185  if args.versionRequested:
186  print("@version@")
187  sys.exit(0)
188  return args
189 
190 
191 def _send_once_command(connection, args):
192  ''' Perform a SEND_ONCE ... socket command. '''
193  if isinstance(args.keys, str):
194  args.keys = [args.keys]
195  command = client.SendCommand(connection, args.remote, args.keys)
196  parser = command.run(args.timeout)
197  if not parser.success:
198  print(parser.data[0])
199  return 0 if parser.success else 1
200 
201 
202 def _start_repeat_command(conn, args):
203  ''' Perform a SEND_START <remote> <key> socket command. '''
204  command = client.StartRepeatCommand(conn, args.remote, args.key)
205  parser = command.run(args.timeout)
206  if not parser.success:
207  print(parser.data[0])
208  return 0 if parser.success else 1
209 
210 
211 def _stop_repeat_command(conn, args):
212  ''' Perform a SEND_STOP <remote> <key> socket command. '''
213  command = client.StopRepeatCommand(conn, args.remote, args.key)
214  parser = command.run(args.timeout)
215  if not parser.success:
216  print(parser.data[0])
217  return 0 if parser.success else 1
218 
219 
220 def _drv_option_command(conn, args):
221  ''' Perform a "DRV_OPTION <option> <value>" socket command. '''
222  command = client.DrvOptionCommand(conn, args.option, args.value)
223  parser = command.run(args.timeout)
224  if not parser.success:
225  print(parser.data[0])
226  return 0 if parser.success else 1
227 
228 
229 def _list_keys_command(conn, args):
230  ''' Perform a irsend LIST <remote> socket command. '''
231  command = client.ListKeysCommand(conn, args.remote)
232  parser = command.run(args.timeout)
233  if not parser.success:
234  print(parser.data[0])
235  else:
236  if not args.codes and args.remote:
237  parser.data = [x.split()[-1] for x in parser.data]
238  for key in parser.data:
239  print(key)
240  return 0 if parser.success else 1
241 
242 
243 def _list_remotes_command(conn):
244  ''' Perform a irsend LIST command. '''
245  command = client.ListRemotesCommand(conn)
246  parser = command.run()
247  if not parser.success:
248  print(parser.data[0])
249  else:
250  for key in parser.data:
251  print(key)
252  return 0 if parser.success else 1
253 
254 
255 def _set_input_log_command(conn, args):
256  ''' Start or stop lircd logging using SET_LOGFILE socket command. '''
257  command = client.SetLogCommand(conn, args.logfile)
258  parser = command.run(args.timeout)
259  if not parser.success:
260  print(parser.data[0])
261  return 0 if parser.success else 1
262 
263 
264 def _simulate_command(conn, args, repeat=0):
265  ''' Roughly a irsend SIMULATE equivalent. '''
266  command = \
267  client.SimulateCommand(
268  conn, args.remote, args.key, repeat, args.data)
269  parser = command.run(args.timeout)
270  if not parser.success:
271  print(parser.data[0])
272  return 0 if parser.success else 1
273 
274 
275 def _transmitters_cmd(conn, args):
276  ''' Perform an irsend SET_TRANSMITTERS command. '''
277  command = client.SetTransmittersCommand(conn, args.transmitters)
278  parser = command.run(args.timeout)
279  if not parser.success:
280  print(parser.data[0])
281  return 0 if parser.success else 1
282 
283 
284 def _version_command(conn):
285  ''' Retrieve lircd version using the VERSION socket command. '''
286  command = client.VersionCommand(conn)
287  parser = command.run()
288  print(parser.data[0])
289  return 0 if parser.success else 1
290 
291 
292 def _not_implemented():
293  ''' Indeed, the not-implemented message '''
294  print("Subcommand not implemented yet, are YOU volunteering?")
295  return 2
296 
297 
298 def main():
299  ''' Indeed: main function. '''
300 
301  args = _parse_commandline()
302  if args.address:
303  s = socket.socket((socket.AF_INET, socket.SOCK_STREAM))
304  s.connect((args.address, args.port))
305  conn = client.CommandConnection(s)
306  else:
307  conn = client.CommandConnection(args.socket_pathname)
308 
309  cmd_table = {
310  'send-once':
311  lambda: _send_once_command(conn, args),
312  'send-start':
313  lambda: _start_repeat_command(conn, args),
314  'send-stop':
315  lambda: _stop_repeat_command(conn, args),
316  'list-keys':
317  lambda: _list_keys_command(conn, args),
318  'list-remotes':
319  lambda: _list_remotes_command(conn),
320  'set-inputlog':
321  lambda: _set_input_log_command(conn, args),
322  'simulate':
323  lambda: _simulate_command(conn, args),
324  'set-transmitters':
325  lambda: _transmitters_cmd(conn, args),
326  'driver-option':
327  lambda: _drv_option_command(conn, args),
328  'version':
329  lambda: _version_command(conn),
330  }
331  try:
332  if args.subcommand in cmd_table:
333  exitstatus = cmd_table[args.subcommand]()
334  else:
335  print('Unknown subcommand, use --help for syntax.')
336  exitstatus = 3
337 
338  except ConnectionRefusedError:
339  print("Connection refused")
340  exitstatus = 5
341  except FileNotFoundError:
342  print("Could not find {0}".format(args.socket_pathname))
343  exitstatus = 5
344  except PermissionError:
345  print("No permission to open {0}".format(args.socket_pathname))
346  exitstatus = 5
347 
348  sys.exit(exitstatus)
349 
350 
351 if __name__ == "__main__":
352  main()