Source code for philander.stc311x

# TODO: all the comments

# -*- coding: utf-8 -*-
"""A module to provide base classes and data types for gas gauge driver implementations.
"""
__author__ = "Carl Bellgardt"
__version__ = "0.1"
__all__ = ["STC311x", "ChipType"]

from enum import Enum, auto
from .serialbus import SerialBusDevice
from .gasgauge import GasGauge, SOCChangeRate
from .battery import Status as BatStatus, Level as BatLevel
from .primitives import Current, Voltage, Percentage, Temperature
from .systypes import ErrorCode, RunLevel, Info
from .interruptable import Interruptable, Event
from .gpio import GPIO
from .stc311x_reg import _STC311x_Reg, STC3115_Reg, STC3117_Reg, ChipType


class OperatingMode(Enum):
    opModeUnknown = auto()
    opModeStandby = auto()
    opModeVoltage = auto()
    opModeMixed = auto()


[docs] class STC311x(GasGauge, SerialBusDevice, Interruptable): """Driver implementation for the stc3117 and stc3118 gas gauge chips by ST microelectronics. A gas gauge allows to keep track of the state of charge (SOC), remaining capacity, current voltage etc. of a battery. Info about the specific gas gauge ICs can be found at https://www.st.com/en/power-management/stc3117.html This class was tested using a STC3117 but should also work for STC3118 and possibly other similar chips. """ def __init__(self): super().__init__() self.REGISTER = None # chip specific register information, set in open() via a parameter ADDRESSES_ALLOWED = [0x70] pin_int = GPIO()
[docs] @classmethod def Params_init(cls, paramDict): """Initializes configuration parameters with defaults. The following settings are supported: ================================= ========================================================================================================== Key name Value type, meaning and default ================================= ========================================================================================================== TODO: See def_dict =============================================================================================================================================== Also see: :meth:`.Gasgauge.Params_init`, :meth:`.SerialBusDevice.Params_init`, :meth:`.GPIO.Params_init`. """ def_dict = { "SerialBusDevice.address": cls.ADDRESSES_ALLOWED[0], "Gasgauge.chip_type": ChipType.STC3115, # TODO: implement auto "Gasgauge.gpio_alarm_idx": None, # GPIO index for the reset pin "Gasgauge.gpio_cd_idx": None, # PIO index for the charger driver pin "Gasgauge.battery_idx": None, # TODO: what is this pin used for? "Gasgauge.senseResistor": _STC311x_Reg.CONFIG_RSENSE_DEFAULT, # Sense resistor in milli Ohm "Gasgauge.cc_cnf": _STC311x_Reg.CC_CNF_DEFAULT, # Coulomb-counter mode configuration "Gasgauge.vm_cnf": _STC311x_Reg.VM_CNF_DEFAULT, # Voltage mode configuration "Gasgauge.alarm_soc": _STC311x_Reg.ALARM_SOC_DEFAULT, # SOC lower threshold; SOC alarm level [0.5%] "Gasgauge.alarm_voltage": _STC311x_Reg.ALARM_VOLTAGE_DEFAULT, # Voltage lower threshold; 3.0 V "Gasgauge.current_thres": _STC311x_Reg.CURRENT_THRES_DEFAULT, # Current monitoring threshold; +/-470 V drop "Gasgauge.cmonit_max": _STC311x_Reg.CMONIT_MAX_DEFAULT # Monitoring timing threshold; CC-VM: 4 minutes; VM->CC: 1 minute # TODO: GPIO options should be available to be configured and in docs } def_dict.update(paramDict) paramDict.update(def_dict) # update again to apply changes to original reference return None
[docs] def open(self, paramDict): """Opens the instance and sets it in a usable state. Allocate necessary hardware resources and configure user-adjustable parameters to meaningful defaults. In this case the registers for the specific chip are defined and optionally the GPIO-Pin for interrupts is initialized. This function must be called prior to any further usage of the instance. Involving it in the system ramp-up procedure could be a good choice. After usage of this instance is finished, the application should call :meth:`close`. :param paramDict(str, object) paramDict: Configuration parameters as obtained from :meth:`Params_init`, possibly. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ self.Params_init(paramDict) err = ErrorCode.errOk if paramDict["Gasgauge.chip_type"] == ChipType.STC3115: self.REGISTER = STC3115_Reg(paramDict) elif paramDict["Gasgauge.chip_type"] == ChipType.STC3117: self.REGISTER = STC3117_Reg(paramDict) else: err = ErrorCode.errInvalidParameter self._setup() if err.isOk(): # Extend ErrorCode class to have a ok() function to replace all these occurrences err = SerialBusDevice.open(self, paramDict) if err.isOk(): # TODO: should I check for err or use .isAttached? # SerialBusDevice is attached # setup GPIO pin for interrupts pin_int_params = {} for key, value in paramDict.items(): if key.startswith("Gasgauge.pinInt.gpio."): pin_int_params[key.replace("Gasgauge.pinInt.", "")] = value # open GPIO pin err = self.pin_int.open(pin_int_params) self.enableInterrupt() return err
[docs] def close(self): """Shut down the device after usage. This method should be called when the device is not used, anymore, e.g. as part of the application exit procedure. The following steps are executed: * close I2C-Bus connection * close GPIO pin for interrupts After return, the device can still be re-used, by calling :meth:`.open` again. Also see: :meth:`.GPIO.close`, :meth:`.Module.close`. """ self.setRunLevel(RunLevel.shutdown) err = SerialBusDevice.close(self) if err.isOk() and self.pin_int is not None: # TODO: should GPIO be closed, even if close of SerialBusDevice failed? err = self.pin_int.close() return err
[docs] def getInfo(self): """Retrieves an information block from the gas gauge device. Typically, this kind of information is rather static in that, it does not change over time. Usually, this information is somewhat unique for the charger origin, manufacturing date, hardware/firmware revision, product/model ID, chip series and alike. For that reason, this function can be used to see, if communication works reliably. For more dynamic meta-information see :meth:`getStatus`. The method returns both, an instance of :class:`Info`, carrying the information block as well as an error code, indicating success or failure. The info block shall be evaluated only, if the method returned successfully. Even then, the caller should still evaluate the ``validity`` attribute of the returned info block to find out, which of the information is actually valid. :return: The information object and an error code indicating either success or the reason of failure. :rtype: Info, ErrorCode """ info = Info() chip_id, err = self.readByteRegister(self.REGISTER.REG_ID) if err.isOk(): info.chipID = chip_id if chip_id == self.REGISTER.CHIP_ID: info.validity = Info.validChipID return info, err
[docs] def getStatus(self, statusID): """Retrieves status data from the device. Typically, this kind of information is more dynamic in that, it changes (much) over time. Usually, it further describes the IC's current shape and condition, such as the availability of new data, the cause of an interrupt or the status of certain hardware functions. Also, secondary measurements such as the die temperature could be subject to status data. For more static meta-information see :meth:`getInfo`. The given ``statusID`` parameter specifies, exactly which status information should be retrieved. Its type and interpretation depends on the implementation. The method returns both, resulting status data and an error code indicating success or failure. The status data should be considered valid only, if the error code indicates a successful execution of this method. The type and interpretation of the status data depends on the specific implementation. :param int statusID: Identifies the status information to be retrieved. :return: The status object and an error code indicating either success or the reason of failure. :rtype: Object, ErrorCode """ # TODO: not implemented yet return None, ErrorCode.errNotSupported
[docs] def getBatteryVoltage(self): """Retrieves the battery voltage in milli Volt. :return: A on-negative integer value [mV] or :attr:`Voltage.invalid`\ to indicate that this information could not be retrieved. :rtype: Voltage """ voltage, err = self.readWordRegister(self.REGISTER.REG_VOLTAGE) if err.isOk(): ret = self._transferVoltage(voltage) else: ret = Voltage.invalid return ret
[docs] def getBatteryCurrent(self): """Retrieves the battery current in micro Ampere at the time this\ function is executed. See also: :meth:`getBatteryCurrentAvg` :return: A non-negative integer value [micro A] or :attr:`Current.invalid`\ to indicate that this information could not be retrieved. :rtype: Current """ current, err = self.readWordRegister(self.REGISTER.REG_CURRENT) if err.isOk(): ret = self._transferCurrent(current) else: ret = Current.invalid return ret
[docs] def getBatteryCurrentAvg(self): """Retrieves the average battery current. The average is taken over some time interval, e.g. 2 seconds. The length of the time window is at the discretion of the implementation and cannot be adjusted by the caller. See also: :meth:`getBatteryCurrent` :return: A non-negative integer value [micro A] or :attr:`Current.invalid`\ to indicate that this information could not be retrieved. :rtype: Current """ ret = Current.invalid if self.REGISTER.CHIP_TYPE == ChipType.STC3117: # Function is STC3117 exclusive if self._getOperatingMode() == OperatingMode.opModeMixed: data, err = SerialBusDevice.readWordRegister(self, self.REGISTER.REG_AVG_CURRENT) # TODO: implement this register properly if err.isOk(): ret = self._transferCurrentAvg(data) return ret
# Local functions for internal use @staticmethod def _transferSOC(data): # LSB is 1/512 %, so shift by 9 bits. ret = data + 0x0100 >> 9 return Percentage(ret) @staticmethod def _transferChangeRate(data): # STC3117 exclusive # LSB is 8.789 mC, scaling factor is 8.789 = 8789/1000 ret = (data * 8789 + 500) / 1000 return ret @staticmethod def _transferVoltage(data): # LSB is 2.2mV, so scale by factor 2.20 = 22/10 ret = (data * 22 + 5) / 10 return Voltage(ret) def _transferCurrent(self, data): # Actually, we read out the voltage drop over the sense resistor. # LSB is 5.88V, so first scaling factor is 5.88 = 294/50 # Value is signed! # R = U/I so we get I = U/R; Note that R is given in milliOhm! # So, finally we scale by 294 / 50 * 1000 / rs = 294 * 20 / rs = 5880 / rs. rs = self.REGISTER.CONFIG_GASGAUGE_0_RSENSE if data >= 0: ret = (data * 5880 + (rs / 2)) / rs else: ret = (data * 5880 - (rs / 2)) / rs return ret # /** # * AVG_CURRENT transfer function # */ def _transferCurrentAvg(self, data): # STC3117 exclusive if self.REGISTER.CHIP_TYPE is not ChipType.STC3117: ret = Current.invalid # Function is not implemented for this chip else: # Again, we actually read out the voltage drop over the sense resistor. # LSB is 1.47V, partial scaling factor is 1.47 = 147/100 # Value is signed! # Total scaling factor is: 147 / 100 * 1000 / rs = 147 * 10 / rs = 1470 / rs. rs = self.REGISTER.CONFIG_GASGAUGE_0_RSENSE if data >= 0: ret = (data * 1470 + rs/2) / rs else: ret = (data * 1470 - rs/2) / rs ret = Current(ret) return ret @staticmethod def _transferTemperature(data): # LSB is 1 °C, so we don't need any scaling. return Temperature(data) @staticmethod def _transfertOCV(data): # LSB is 0.55 mV, so scale by factor 0.55 = 55/100 = 11/20. ret = (data * 11 + 10) / 20 return Voltage(ret) @staticmethod def _invTransferOCV(value): # LSB is 0.55 mV, so scale by factor 1/0.55 = 100/55 = 20/11. ret = (value * 20 + 5) / 11 return ret @staticmethod def _crc(data, length): ret = 0 for idx in range(length): ret ^= data[idx] return ret @staticmethod def _checkRamConsistency(self, data, length): # check if RAM test register value is correct if len(data) < length or data[self.REGISTER.IDX_RAM_TEST] != self.REGISTER.RAM_TEST: ret = ErrorCode.errCorruptData else: code = self._crc(data, self.REGISTER.RAM_SIZE - 1) if code != data[self.REGISTER.IDX_RAM_CRC]: ret = ErrorCode.errCorruptData else: ret = ErrorCode.errOk return ret def _getOperatingMode(self): err, data = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_MODE) if err.isOk(): if data and self.REGISTER.MODE_GG_RUN: if data and self.REGISTER.MODE_VMODE: ret = OperatingMode.opModeVoltage else: ret = OperatingMode.opModeMixed else: ret = OperatingMode.opModeStandby else: ret = OperatingMode.opModeUnknown return ret def _checkID(self): data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_ID) if err.isOk(): err = ErrorCode.errOk if (data == self.REGISTER.CHIP_ID) else ErrorCode.errFailure return err def _setup(self): # TODO: implement this function into open()? # TODO: this function is still Work In Progress and will not work at all return ErrorCode.errNotImplemented ramContent = None # TODO: where does RAM content come from? params = None # TODO: insert according confs where params is used memset = None # TODO: see original implementation buffer = None # TODO: see original implementation # check communication err = self._checkID() # read RAM if err.isOk(): data, err = SerialBusDevice.readWriteBuffer(self, self.REGISTER.RAM_SIZE, 1) # check RAM consistency canRestore = False # disable RAM restoration if err.isOk(): err = self._checkRamConsistency(ramContent, self.REGISTER.RAM_SIZE) if err.isOk(): # check CTRL_PORDET and CTRL_BATFAIL data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_CTRL) if err.isOk(): if data & (self.REGISTER.CTRL_BATFAIL | self.REGISTER.CTRL_PORDET): # battery removed / voltage dropped below threshold # no restoration, start anew, instead! canRestore = False else: canRestore = False err = ErrorCode.errOk if err.isOk(): # common steps (pre-phase) if canRestore: # restore configuration from RAM # ensure that GG_RUN is cleared SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_MODE, self.REGISTER.MODE_OFF) # restore REG_CC_CNF data16 = (ramContent[self.REGISTER.IDX_RAM_CC_CNF_H] << 8) | ramContent[self.REGISTER.IDX_RAM_CC_CNF_L] SerialBusDevice.writeWordRegister(self, self.REGISTER.REG_CC_CNF, data16) # restore REG_VM_CNF data16 = (ramContent[self.REGISTER.IDX_RAM_VM_CNF_H] << 8) | ramContent[self.REGISTER.IDX_RAM_VM_CNF_L] SerialBusDevice.writeWordRegister(self, self.REGISTER.REG_VM_CNF, data16) # restore REG_SOC data16 = (ramContent[self.REGISTER.IDX_RAM_SOC_H] << 8) | ramContent[self.REGISTER.IDX_RAM_SOC_L] SerialBusDevice.writeWordRegister(self, self.REGISTER.REG_SOC, data16) else: # initialize configuration with defaults # run gas gauge to get first OCV and current measurement data8 = self.REGISTER.MODE_OFF | self.REGISTER.MODE_GG_RUN |self.REGISTER.MODE_FORCE_CC SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_MODE, data8) # read OCV data, _ = SerialBusDevice.readWordRegister(self, self.REGISTER.REG_OCV) ocv = self._transfertOCV(data) # read current data, _ = SerialBusDevice.readWordRegister(self, self.REGISTER.REG_CURRENT) current = self._transferCurrent(data) # ensure that GG_RUN is cleared SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_MODE, self.REGISTER.MODE_OFF) # REG_CC_CNF SerialBusDevice.writeWordRegister(self, self.REGISTER.REG_CC_CNF, params.regCCcnf) # TODO: get from params # REG_VM_CNF SerialBusDevice.writeWordRegister(self, self.REGISTER.REG_VM_CNF, params.regVMcnf) # TODO: same as above # compensate OCV if current > 1000000: current /= 1000 ocv = ocv - current * CFG_SUBSECTATR( BATTERY, CONFIG_GASGAUGE_0_BATTERY_IDX, IMPEDANCE ) / 1000 else: ocv = ocv - current * CFG_SUBSECTATR(BATTERY, CONFIG_GASGAUGE_0_BATTERY_IDX, IMPEDANCE) / (1000 * 1000) data16 = self._invTransferOCV(ocv) # write OCV back SerialBusDevice.writeWordRegister(self, self.REGISTER.REG_OCV, data16) # wait 1ßßms to get valid SOC data, _ = SerialBusDevice.readWordRegister(self, self.REGISTER.REG_SOC) # store new backup to RAM memset(ramContent, 0, self.REGISTER.RAM_SIZE) ramContent[self.REGISTER.IDX_RAM_TEST] = self.REGISTER.RAM_TEST ramContent[self.REGISTER.IDX_RAM_SOC_L] = data16 & 0xFF ramContent[self.REGISTER.IDX_RAM_SOC_H] = data16 >> 8 ramContent[self.REGISTER.IDX_RAM_CC_CNF_L] = params.regCCcnf & 0xFF ramContent[self.REGISTER.IDX_RAM_CC_CNF_H] = params.regCCcnf >> 8 ramContent[self.REGISTER.IDX_RAM_VM_CNF_L] = params.regVMcnf & 0xFF ramContent[self.REGISTER.IDX_RAM_VM_CNF_H] = params.regVMcnf >> 8 ramContent[self.REGISTER.IDX_RAM_CRC] = crc(ramContent, self.REGISTER.RAM_SIZE - 1) buffer[0] = self.REGISTER.REG_RAM_FIRST SerialBusDevice.writeBuffer(self, self.REGISTER.RAM_SIZE + 1) # API functions exported to the outside def _setRunLevel(self, level): mode = self.REGISTER.MODE_OFF if level in [RunLevel.active, RunLevel.idle]: # TODO: not sure if this is the best "pythonic^TM" way # Mixed mode: coulomb counter + voltage gas gauge -> Leave VMODE off mode = self.REGISTER.MODE_OFF | self.REGISTER.MODE_GG_RUN | self.REGISTER.MODE_FORCE_CC if self.pin_int._fIntEnabled: mode |= self.REGISTER.MODE_ALM_ENA ret = ErrorCode.errOk elif level in [RunLevel.relax, RunLevel.snooze, RunLevel.nap, RunLevel.sleep, RunLevel.deepSleep]: # Power saving mode: voltage gas gauge, only. -> VMODE = 1 mode = self.REGISTER.MODE_OFF | self.REGISTER.MODE_VMODE | self.REGISTER.MODE_GG_RUN | self.REGISTER.MODE_FORCE_VM if self.pin_int._fIntEnabled: mode |= self.REGISTER.MODE_ALM_ENA ret = ErrorCode.errOk elif level == RunLevel.shutdown: # ret = backupRam(self) mode = self.REGISTER.MODE_VMODE | self.REGISTER.MODE_FORCE_VM ret = ErrorCode.errOk else: ret = ErrorCode.errNotSupported # set mode and return ErrorCode if ret.isOk(): ret = SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_MODE, mode) return ret # Gas gauge specific API implementation
[docs] def reset(self): """Soft resets the device. The device is in some default state, afterwards and must be re-configured according to the application's needs. :return: An error code indicating either success or the reason of failure. :rtype: ErrorCode """ # UNDOCUMENTED: At the end of the reset phase, the MODE_GG_RUN bit is cleared. # In order to detect this, we have to set it, first: mode_data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_MODE) if err.isOk() and not (mode_data & self.REGISTER.MODE_GG_RUN): mode_data |= self.REGISTER.MODE_GG_RUN err = SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_MODE, mode_data) # same applies for beneath # Do a soft reset by asserting CTRL:PORDET if err.isOk(): # TODO: consider adding a set_ctrl / get_ctrl / add_ctrl method for this purpose; same for mdoe ctrl_data = self.REGISTER.CTRL_IO0DATA | self.REGISTER.CTRL_GG_RST | self.REGISTER.CTRL_PORDET err = SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_CTRL, ctrl_data) # Delay: Loop until we see the MODE_GG_RUN bit cleared: if err.isOk(): has_timed_out = True for i in range(self.REGISTER.POR_DELAY_LOOPS_MAX): mode_data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_MODE) if not (err.isOk() and (mode_data & self.REGISTER.MODE_GG_RUN)): has_timed_out = False # loop ended before i == POR_DELAY_LOOPS_MAY break if has_timed_out: err = ErrorCode.errMalfunction # Then, re-initialize the device if err.isOk(): STC311x._setup(self) return err
[docs] def getID(self): data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_ID) ret = data if err.isOk() else err return ret
[docs] def getStateOfCharge(self): """Retrieves the state of charge. That is the fraction of electric energy from the total capacity, that is still or already stored in the battery. This information is valid for both, the charging and the discharging process. :return: A percentage [0...100] value or :attr:`Percentage.invalid`\ to indicate that this information could not be retrieved. :rtype: Percentage """ # SOC is a 16bit value with LSB = 1/512 % # But reading just the high-byte results in an inconsistent response. # So, read the full word. data, err = SerialBusDevice.readWordRegister(self, self.REGISTER.REG_SOC) if err.isOk(): ret = self._transferSOC(data) # future RAM-functions could be implemented here # if ret != Percentage.invalid: # updateRamWord(self, self.REGISTER._IDX_RAM_SOC) else: ret = Percentage.invalid return ret
[docs] def getChangeRate(self): # function is STC3117 exclusive """Retrieves the SOC change rate in milli C. Remember that 1C = 100% in 1 hour. This information may be used to estimate the remaining stamina or how long the charging process will still take. :return: A SOC change rate (non-negative) or :attr:'SOCChangeRate.invalid`\ to indicate that this information could not be retrieved. :rtype: SOCChangeRate """ if self.REGISTER.CHIP_TYPE != ChipType.STC3117: # STC3117 exclusive ret = SOCChangeRate.invalid else: opMode = self._getOperatingMode() if opMode == opMode.opModeVoltage: data, err = SerialBusDevice.readWordRegister(self, self.REGISTER.REG_AVG_CURRENT) if err.isOk(): ret = self._transferChangeRate(data) else: ret = SOCChangeRate.invalid else: ret = SOCChangeRate.invalid return ret
[docs] def getBatteryTemperature(self): # This device does not measure any temperature data return Temperature.invalid
[docs] def getChipTemperature(self): """Retrieve the current temperature of the chip. :return: Temperature value. :rtype: Temperature """ data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_TEMPERATURE) if err.isOk(): ret = self._transferTemperature(data) else: ret = Temperature.invalid return ret
# Interruptable API implementation
[docs] def registerInterruptHandler(self, onEvent=Event.evtInt1, callerFeedBack=None, handler=None): if handler is not None: # Enable; from app (=sink) to hardware (=source) self.pin_int.registerInterruptHandler(onEvent, callerFeedBack, handler) err = self.pin_int.enableInterrupt() if err.isOk(): data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_MODE) if err.isOk(): data |= self.REGISTER.MODE_ALM_ENA err = SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_MODE, data) if err.isOk(): # check if there already is an interrupt present data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_CTRL) if data & self.REGISTER.CTRL_IO0DATA: handler(Event.evtInt1, callerFeedBack) else: self.disableInterrupt() else: err = ErrorCode.errInvalidParameter # TODO: is this the right error code? else: # Disable; from hardware to app. data, err = SerialBusDevice.readByteRegister(self, self.REGISTER.REG_MODE) if err.isOk(): data &= ~self.REGISTER.MODE_ALM_ENA # TODO: ModeValues class need to be adjusted to work with all binary operations as expected err = SerialBusDevice.writeByteRegister(self, self.REGISTER.REG_MODE, data) self.disableInterrupt() return err
[docs] def enableInterrupt(self): # err = GPIO.enableInterrupt(self, self.REGISTER.CONFIG_GASGAUGE_0_GPIO_ALARM) # TODO: implement GPIO interrupt, check if it does infact return an error (not sure) # if err.is_ok(): # TODO: do we really want these lines to override this error, and not just return the given error? # ret = ErrorCode.errOk # else: # ret = ErrorCode.errInvalidParameter # return ret return ErrorCode.errOk # TODO: why doesn't this function do anything (see max77960)
[docs] def disableInterrupt(self): # err = GPIO_disableInterrupt(self) # TODO: implement GPIO interrupt, check if it does infact return an error (not sure) # if err.is_ok(): # TODO: do we really want these lines to override this error, and not just return the given error? # ret = ErrorCode.errOk # else: # ret = ErrorCode.errInvalidParameter # return ret return ErrorCode.errOk # TODO: why doesn't this function do anything (see max77960)
def _getEventContext(self, event): pass # TODO: this function, see original implementation