#!/usr/bin/env python # -*- coding: utf-8 -*- """ lirc.pyx Provides a Python API for the lirc libraries Copyright (C) 2013 thomasmarkpreston@gmail.com This program 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 3 of the License, or (at your option) any later version. This program 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 this program. If not, see . """ from libc.stdlib cimport calloc, free from posix cimport fcntl, unistd cimport lirc_client ENCODING = 'utf-8' STRING_BUFFER_LEN = 256 LOCAL_CONFIG_FILE = "~/.lircrc" GLOBAL_CONFIG_FILE = "/etc/lirc/lircrc" initialised = False config = None class InitError(Exception): pass class DeinitError(Exception): pass class ConfigLoadError(Exception): pass class Code2CharError(Exception): pass class NextCodeError(Exception): pass class NoMoreStrings(Exception): pass cdef class LircConfig: cdef lirc_client.lirc_config * _c_lirc_config def __cinit__(self, config_filename): self.add_config_file(config_filename) def __dealloc__(self): if self._c_lirc_config is not NULL: lirc_client.lirc_freeconfig(self._c_lirc_config) def add_config_file(self, config_filename): if config_filename is not None: lirc_client.lirc_readconfig( config_filename, &self._c_lirc_config, NULL) else: lirc_client.lirc_readconfig( NULL, &self._c_lirc_config, NULL) if self._c_lirc_config is NULL: raise ConfigLoadError( "Could not load the config file (%s)" % config_filename) def code2char(self, char * code): """Returns the (byte) string associated with the code in the config file """ self.is_init_or_error() cdef char * string_buf = \ calloc(STRING_BUFFER_LEN, sizeof(char)) cdef char * string_buf_2 = string_buf # string_buf might be destroyed status = lirc_client.lirc_code2char( self._c_lirc_config, code, &string_buf) if status == -1: free(string_buf) raise Code2CharError( "There was an error determining the config string.") if string_buf == NULL: free(string_buf_2) raise NoMoreStrings() else: string = string_buf.decode(ENCODING) free(string_buf_2) return string def is_init_or_error(self): """Throws an error if not initialised""" if self._c_lirc_config is NULL: raise InitError("LircConfig has not been inititalised.") def init(program_name, config_filename=None, blocking=True, verbose=False): global initialised if initialised: return # init lirc b_program_name = program_name.encode(ENCODING) lirc_socket = lirc_client.lirc_init(b_program_name, 1 if verbose else 0) if lirc_socket == -1: raise InitError( "Unable to initialise lirc (socket was -1 from C library).") set_blocking(blocking, lirc_socket) initialised = True if config_filename: load_config_file(config_filename) else: try: load_default_config() except ConfigLoadError as e: raise InitError("Unable to load default config {} or {}.".format( LOCAL_CONFIG_FILE, GLOBAL_CONFIG_FILE)) from e return lirc_socket def deinit(): global initialised if not initialised: return if lirc_client.lirc_deinit() == -1: raise DeinitError("Unable to de-initialise lirc.") config = None initialised = False def load_default_config(): """Attempts to load the default lirc config files.""" try: load_config_file(LOCAL_CONFIG_FILE) except ConfigLoadError as local_conf_error: try: load_config_file(GLOBAL_CONFIG_FILE) except ConfigLoadError as global_conf_error: raise global_conf_error from local_conf_error def load_config_file(config_filename=None): """Adds a configuration file for this instance of lirc.""" _is_init_or_error() # read config if config_filename: b_config_filename = config_filename.encode(ENCODING) else: b_config_filename = None global config if config: config.add_config_file(b_config_filename) else: config = LircConfig(b_config_filename) def nextcode(): """Returns the list of codes in the lirc queue. May block, depending on initialisation parameters """ _is_init_or_error() cdef char * code if lirc_client.lirc_nextcode(&code) == -1: free(code) raise NextCodeError("There was an error reading the next code.") if code == NULL: # raise NextCodeError("There was no complete code available.") return list() # get all of the strings associated with this code strings = list() while True: global config try: strings.append(config.code2char(code)) except NoMoreStrings: break free(code) return strings def set_blocking(blocking, lirc_socket): """Sets whether the nextcode function blocks""" fcntl.fcntl(lirc_socket, fcntl.F_SETOWN, unistd.getpid()) flags = fcntl.fcntl(lirc_socket, fcntl.F_GETFL, 0) flags = (flags & ~fcntl.O_NONBLOCK) | (0 if blocking else fcntl.O_NONBLOCK) fcntl.fcntl(lirc_socket, fcntl.F_SETFL, flags) def _is_init_or_error(): global initialised if not initialised: raise InitError("%s has not been initialised." % __name__)