Module pycti.channel_interface

Expand source code
import logging
import os
from pydantic import BaseModel
from pydantic import validator
from .messages import Msg

from .cycler_interface import CyclerInterface

logger = logging.getLogger(__name__)


class ChannelInterface(CyclerInterface):
    """
    Class for interfacing with Arbin battery cycler at a channel level.
    """

    def __init__(self, config: dict, env_path: str = os.path.join(os.getcwd(), '.env')):
        """
        Creates a class instance for interfacing with Arbin battery cycler at a channel level.

        Parameters
        ----------
        config : dict
            A configuration dictionary. Must contain the following keys:
                channel : int
                    The channel to target with the ChannelInterface class instance.
                test_name : *optional* : str
                    The test name to use if using the ChannelInterface to start a test.
                schedule_name : *optional* : str
                    The name of the schedule file to use if using the ChannelInterface to start a test.
                ip_address : str 
                    The IP address of the Arbin host computer.
                port : int 
                    The TCP port to communicate through.
                timeout_s : *optional* : float 
                    How long to wait before timing out on TCP communication. Defaults to 3 seconds. 
                msg_buffer_size : *optional* : int 
                    How big of a message buffer to use for sending/receiving messages. 
                    A minimum of 1024 bytes is recommended. Defaults to 4096 bytes. 
        env_path : *optional* : str
            The path to the `.env` file containing the Arbin CTI username,`ARBIN_CTI_USERNAME`, and password, `ARBIN_CTI_PASSWORD`.
            Defaults to looking in the working directory.
        """
        self.__config = ChannelInterfaceConfig(**config)
        super().__init__(self.__config.dict(), env_path)

    def read_channel_status(self) -> dict:
        """
        Method to read the status of the channel defined in the config.

        Returns
        -------
        status : dict
            A dictionary detailing the status of the channel. Returns None if there is an issue.
        """
        # Add to channel value to account for zero indexing subtraction in parent method.
        return super().read_channel_status(channel=(self.__config.channel+1))

    def assign_schedule(self) -> bool:
        """
        Method to assign a schedule to the channel defined in the config.

        Returns
        -------
        success : bool
            True/False based on whether the schedule was assigned without issue.
        """
        success = False

        if not self.__config.schedule_name:
            logger.error("Schedule name undefined!")
            return success

        assign_schedule_msg_tx_bin = Msg.AssignSchedule.Client.pack(
            {'channel': self.__config.channel, 'schedule': self.__config.schedule_name})
        response_msg_bin = self._send_receive_msg(
            assign_schedule_msg_tx_bin)

        if response_msg_bin:
            assign_schedule_msg_rx_dict = Msg.AssignSchedule.Server.unpack(
                response_msg_bin)
            if assign_schedule_msg_rx_dict['result'] == 'success':
                success = True
                logger.info(
                    f'Successfully assigned schedule {self.__config.schedule_name} to channel {self.__config.channel}')
                logger.debug(assign_schedule_msg_rx_dict)
            else:
                logger.error(
                    f'Failed to assign schedule {self.__config.schedule_name}! Issue: {assign_schedule_msg_rx_dict["result"]}')

        return success

    def start_test(self) -> bool:
        """
        Starts channel on method specified in config.  

        Returns
        -------
        success : bool
            True/False based on whether the test was started without issue.
        """
        success = False

        if not self.__config.test_name:
            logger.error("Test name undefined!")
            return success

        # Make sure the schedule is assigned before starting the test to avoid any funny business
        if self.assign_schedule():
            start_test_msg_tx_bin = Msg.StartSchedule.Client.pack(
                {'channel': self.__config.channel, 'test_name': self.__config.test_name})
            response_msg_bin = self._send_receive_msg(start_test_msg_tx_bin)

            if response_msg_bin:
                start_test_msg_rx_dict = Msg.StartSchedule.Server.unpack(
                    response_msg_bin)
                if start_test_msg_rx_dict['result'] == 'success':
                    success = True
                    logger.info(
                        f'Successfully started test {self.__config.test_name} with schedule {self.__config.schedule_name} on channel {self.__config.channel}')
                    logger.debug(start_test_msg_rx_dict)
                else:
                    logger.error(
                        f'Failed to start test {self.__config.test_name} with schedule {self.__config.schedule_name} on channel {self.__config.channel}. Issue: {start_test_msg_rx_dict["result"]}')

        return success

    def stop_test(self) -> bool:
        """
        Stops the test running on the channel specified in the config.

        Returns
        -------
        success : bool
            True/False based on whether the test stopped without issue.
            Also returns True if no test was running on the channel. 
        """
        success = False

        stop_test_msg_tx_bin = Msg.StopSchedule.Client.pack(
            {'channel': self.__config.channel})
        response_msg_bin = self._send_receive_msg(
            stop_test_msg_tx_bin)

        if response_msg_bin:
            stop_test_msg_rx_dict = Msg.StopSchedule.Server.unpack(
                response_msg_bin)
            if stop_test_msg_rx_dict['result'] == 'success':
                success = True
                logger.info(
                    f'Successfully stopped test on channel {self.__config.channel}')
                logger.debug(stop_test_msg_rx_dict)
            else:
                logger.error(
                    f'Failed to stop test on channel {self.__config.channel}! Issue: {stop_test_msg_rx_dict["result"]}')

        return success

    def set_meta_variable(self, mv_num: int, mv_value: float) -> bool:
        """
        Sets the passed meta variable number `mv_num` to the passed value `mv_value`
        on the channel specified in the config. Note the test must be running.

        Parameters
        ----------
        mv_num : int
            The meta variable number to set. Must be between 1 and 16 (inclusive)
        mv_value : float
            The meta variable value to set.
        Returns
        -------
        success : bool
            True/False based on whether the meta variable was set. 
        """
        success = False

        updated_msg_vals = {}
        updated_msg_vals['channel'] = self.__config.channel
        updated_msg_vals['mv_meta_code'] = Msg.SetMetaVariable.Client.mv_channel_codes[mv_num]
        updated_msg_vals['mv_data'] = mv_value

        set_mv_msg_tx_bin = Msg.SetMetaVariable.Client.pack(updated_msg_vals)
        response_msg_bin = self._send_receive_msg(
            set_mv_msg_tx_bin)

        if response_msg_bin:
            set_mv_msg_rx_dict = Msg.SetMetaVariable.Server.unpack(
                response_msg_bin)
            if set_mv_msg_rx_dict['result'] == 'success':
                success = True
                logger.info(
                    f'Successfully set meta variable {mv_num} to a value of {mv_value}')
                logger.debug(set_mv_msg_rx_dict)
            else:
                logger.error(
                    f'Failed to set meta variable {mv_num} to a value of {mv_value}! Issue: {set_mv_msg_rx_dict["result"]}')

        return success

class ChannelInterfaceConfig(BaseModel):
    '''
    Holds channel config information for the CyclerInterface class.

    Parameters
    ----------
        channel : int
            The channel to target with the ChannelInterface class instance.
        test_name : *optional*
            The test name to use if using the ChannelInterface to start a test.
        schedule_name : str
            The name of the schedule file to use if using the ChannelInterface to start a test.
        ip_address : str 
            The IP address of the Arbin host computer.
        port : int 
            The TCP port to communicate through.
        timeout_s : float 
            How long to wait before timing out on TCP communication. Defaults to 3 seconds. 
        msg_buffer_size : int 
             How big of a message buffer to use for sending/receiving messages. 
            A minimum of 1024 bytes is recommended. Defaults to 4096 bytes. 
    '''
    channel: int
    test_name: str = None
    schedule_name: str = None
    ip_address: str
    port: int
    timeout_s: float = 3.0
    msg_buffer_size: int = 4096

    @validator('channel')
    def username_alphanumeric(cls, v):
        if v < 1:
            raise ValueError('Channel must be greater than zero!')
        return v-1

Classes

class ChannelInterface (config: dict, env_path: str = '/Users/zander/Documents/Work/BattGenie/pycti/.env')

Class for interfacing with Arbin battery cycler at a channel level.

Creates a class instance for interfacing with Arbin battery cycler at a channel level.

Parameters

config : dict
A configuration dictionary. Must contain the following keys: channel : int The channel to target with the ChannelInterface class instance. test_name : optional : str The test name to use if using the ChannelInterface to start a test. schedule_name : optional : str The name of the schedule file to use if using the ChannelInterface to start a test. ip_address : str The IP address of the Arbin host computer. port : int The TCP port to communicate through. timeout_s : optional : float How long to wait before timing out on TCP communication. Defaults to 3 seconds. msg_buffer_size : optional : int How big of a message buffer to use for sending/receiving messages. A minimum of 1024 bytes is recommended. Defaults to 4096 bytes.
env_path : *optional* : str
The path to the .env file containing the Arbin CTI username,ARBIN_CTI_USERNAME, and password, ARBIN_CTI_PASSWORD. Defaults to looking in the working directory.
Expand source code
class ChannelInterface(CyclerInterface):
    """
    Class for interfacing with Arbin battery cycler at a channel level.
    """

    def __init__(self, config: dict, env_path: str = os.path.join(os.getcwd(), '.env')):
        """
        Creates a class instance for interfacing with Arbin battery cycler at a channel level.

        Parameters
        ----------
        config : dict
            A configuration dictionary. Must contain the following keys:
                channel : int
                    The channel to target with the ChannelInterface class instance.
                test_name : *optional* : str
                    The test name to use if using the ChannelInterface to start a test.
                schedule_name : *optional* : str
                    The name of the schedule file to use if using the ChannelInterface to start a test.
                ip_address : str 
                    The IP address of the Arbin host computer.
                port : int 
                    The TCP port to communicate through.
                timeout_s : *optional* : float 
                    How long to wait before timing out on TCP communication. Defaults to 3 seconds. 
                msg_buffer_size : *optional* : int 
                    How big of a message buffer to use for sending/receiving messages. 
                    A minimum of 1024 bytes is recommended. Defaults to 4096 bytes. 
        env_path : *optional* : str
            The path to the `.env` file containing the Arbin CTI username,`ARBIN_CTI_USERNAME`, and password, `ARBIN_CTI_PASSWORD`.
            Defaults to looking in the working directory.
        """
        self.__config = ChannelInterfaceConfig(**config)
        super().__init__(self.__config.dict(), env_path)

    def read_channel_status(self) -> dict:
        """
        Method to read the status of the channel defined in the config.

        Returns
        -------
        status : dict
            A dictionary detailing the status of the channel. Returns None if there is an issue.
        """
        # Add to channel value to account for zero indexing subtraction in parent method.
        return super().read_channel_status(channel=(self.__config.channel+1))

    def assign_schedule(self) -> bool:
        """
        Method to assign a schedule to the channel defined in the config.

        Returns
        -------
        success : bool
            True/False based on whether the schedule was assigned without issue.
        """
        success = False

        if not self.__config.schedule_name:
            logger.error("Schedule name undefined!")
            return success

        assign_schedule_msg_tx_bin = Msg.AssignSchedule.Client.pack(
            {'channel': self.__config.channel, 'schedule': self.__config.schedule_name})
        response_msg_bin = self._send_receive_msg(
            assign_schedule_msg_tx_bin)

        if response_msg_bin:
            assign_schedule_msg_rx_dict = Msg.AssignSchedule.Server.unpack(
                response_msg_bin)
            if assign_schedule_msg_rx_dict['result'] == 'success':
                success = True
                logger.info(
                    f'Successfully assigned schedule {self.__config.schedule_name} to channel {self.__config.channel}')
                logger.debug(assign_schedule_msg_rx_dict)
            else:
                logger.error(
                    f'Failed to assign schedule {self.__config.schedule_name}! Issue: {assign_schedule_msg_rx_dict["result"]}')

        return success

    def start_test(self) -> bool:
        """
        Starts channel on method specified in config.  

        Returns
        -------
        success : bool
            True/False based on whether the test was started without issue.
        """
        success = False

        if not self.__config.test_name:
            logger.error("Test name undefined!")
            return success

        # Make sure the schedule is assigned before starting the test to avoid any funny business
        if self.assign_schedule():
            start_test_msg_tx_bin = Msg.StartSchedule.Client.pack(
                {'channel': self.__config.channel, 'test_name': self.__config.test_name})
            response_msg_bin = self._send_receive_msg(start_test_msg_tx_bin)

            if response_msg_bin:
                start_test_msg_rx_dict = Msg.StartSchedule.Server.unpack(
                    response_msg_bin)
                if start_test_msg_rx_dict['result'] == 'success':
                    success = True
                    logger.info(
                        f'Successfully started test {self.__config.test_name} with schedule {self.__config.schedule_name} on channel {self.__config.channel}')
                    logger.debug(start_test_msg_rx_dict)
                else:
                    logger.error(
                        f'Failed to start test {self.__config.test_name} with schedule {self.__config.schedule_name} on channel {self.__config.channel}. Issue: {start_test_msg_rx_dict["result"]}')

        return success

    def stop_test(self) -> bool:
        """
        Stops the test running on the channel specified in the config.

        Returns
        -------
        success : bool
            True/False based on whether the test stopped without issue.
            Also returns True if no test was running on the channel. 
        """
        success = False

        stop_test_msg_tx_bin = Msg.StopSchedule.Client.pack(
            {'channel': self.__config.channel})
        response_msg_bin = self._send_receive_msg(
            stop_test_msg_tx_bin)

        if response_msg_bin:
            stop_test_msg_rx_dict = Msg.StopSchedule.Server.unpack(
                response_msg_bin)
            if stop_test_msg_rx_dict['result'] == 'success':
                success = True
                logger.info(
                    f'Successfully stopped test on channel {self.__config.channel}')
                logger.debug(stop_test_msg_rx_dict)
            else:
                logger.error(
                    f'Failed to stop test on channel {self.__config.channel}! Issue: {stop_test_msg_rx_dict["result"]}')

        return success

    def set_meta_variable(self, mv_num: int, mv_value: float) -> bool:
        """
        Sets the passed meta variable number `mv_num` to the passed value `mv_value`
        on the channel specified in the config. Note the test must be running.

        Parameters
        ----------
        mv_num : int
            The meta variable number to set. Must be between 1 and 16 (inclusive)
        mv_value : float
            The meta variable value to set.
        Returns
        -------
        success : bool
            True/False based on whether the meta variable was set. 
        """
        success = False

        updated_msg_vals = {}
        updated_msg_vals['channel'] = self.__config.channel
        updated_msg_vals['mv_meta_code'] = Msg.SetMetaVariable.Client.mv_channel_codes[mv_num]
        updated_msg_vals['mv_data'] = mv_value

        set_mv_msg_tx_bin = Msg.SetMetaVariable.Client.pack(updated_msg_vals)
        response_msg_bin = self._send_receive_msg(
            set_mv_msg_tx_bin)

        if response_msg_bin:
            set_mv_msg_rx_dict = Msg.SetMetaVariable.Server.unpack(
                response_msg_bin)
            if set_mv_msg_rx_dict['result'] == 'success':
                success = True
                logger.info(
                    f'Successfully set meta variable {mv_num} to a value of {mv_value}')
                logger.debug(set_mv_msg_rx_dict)
            else:
                logger.error(
                    f'Failed to set meta variable {mv_num} to a value of {mv_value}! Issue: {set_mv_msg_rx_dict["result"]}')

        return success

Ancestors

Methods

def assign_schedule(self) ‑> bool

Method to assign a schedule to the channel defined in the config.

Returns

success : bool
True/False based on whether the schedule was assigned without issue.
Expand source code
def assign_schedule(self) -> bool:
    """
    Method to assign a schedule to the channel defined in the config.

    Returns
    -------
    success : bool
        True/False based on whether the schedule was assigned without issue.
    """
    success = False

    if not self.__config.schedule_name:
        logger.error("Schedule name undefined!")
        return success

    assign_schedule_msg_tx_bin = Msg.AssignSchedule.Client.pack(
        {'channel': self.__config.channel, 'schedule': self.__config.schedule_name})
    response_msg_bin = self._send_receive_msg(
        assign_schedule_msg_tx_bin)

    if response_msg_bin:
        assign_schedule_msg_rx_dict = Msg.AssignSchedule.Server.unpack(
            response_msg_bin)
        if assign_schedule_msg_rx_dict['result'] == 'success':
            success = True
            logger.info(
                f'Successfully assigned schedule {self.__config.schedule_name} to channel {self.__config.channel}')
            logger.debug(assign_schedule_msg_rx_dict)
        else:
            logger.error(
                f'Failed to assign schedule {self.__config.schedule_name}! Issue: {assign_schedule_msg_rx_dict["result"]}')

    return success
def read_channel_status(self) ‑> dict

Method to read the status of the channel defined in the config.

Returns

status : dict
A dictionary detailing the status of the channel. Returns None if there is an issue.
Expand source code
def read_channel_status(self) -> dict:
    """
    Method to read the status of the channel defined in the config.

    Returns
    -------
    status : dict
        A dictionary detailing the status of the channel. Returns None if there is an issue.
    """
    # Add to channel value to account for zero indexing subtraction in parent method.
    return super().read_channel_status(channel=(self.__config.channel+1))
def set_meta_variable(self, mv_num: int, mv_value: float) ‑> bool

Sets the passed meta variable number mv_num to the passed value mv_value on the channel specified in the config. Note the test must be running.

Parameters

mv_num : int
The meta variable number to set. Must be between 1 and 16 (inclusive)
mv_value : float
The meta variable value to set.

Returns

success : bool
True/False based on whether the meta variable was set.
Expand source code
def set_meta_variable(self, mv_num: int, mv_value: float) -> bool:
    """
    Sets the passed meta variable number `mv_num` to the passed value `mv_value`
    on the channel specified in the config. Note the test must be running.

    Parameters
    ----------
    mv_num : int
        The meta variable number to set. Must be between 1 and 16 (inclusive)
    mv_value : float
        The meta variable value to set.
    Returns
    -------
    success : bool
        True/False based on whether the meta variable was set. 
    """
    success = False

    updated_msg_vals = {}
    updated_msg_vals['channel'] = self.__config.channel
    updated_msg_vals['mv_meta_code'] = Msg.SetMetaVariable.Client.mv_channel_codes[mv_num]
    updated_msg_vals['mv_data'] = mv_value

    set_mv_msg_tx_bin = Msg.SetMetaVariable.Client.pack(updated_msg_vals)
    response_msg_bin = self._send_receive_msg(
        set_mv_msg_tx_bin)

    if response_msg_bin:
        set_mv_msg_rx_dict = Msg.SetMetaVariable.Server.unpack(
            response_msg_bin)
        if set_mv_msg_rx_dict['result'] == 'success':
            success = True
            logger.info(
                f'Successfully set meta variable {mv_num} to a value of {mv_value}')
            logger.debug(set_mv_msg_rx_dict)
        else:
            logger.error(
                f'Failed to set meta variable {mv_num} to a value of {mv_value}! Issue: {set_mv_msg_rx_dict["result"]}')

    return success
def start_test(self) ‑> bool

Starts channel on method specified in config.

Returns

success : bool
True/False based on whether the test was started without issue.
Expand source code
def start_test(self) -> bool:
    """
    Starts channel on method specified in config.  

    Returns
    -------
    success : bool
        True/False based on whether the test was started without issue.
    """
    success = False

    if not self.__config.test_name:
        logger.error("Test name undefined!")
        return success

    # Make sure the schedule is assigned before starting the test to avoid any funny business
    if self.assign_schedule():
        start_test_msg_tx_bin = Msg.StartSchedule.Client.pack(
            {'channel': self.__config.channel, 'test_name': self.__config.test_name})
        response_msg_bin = self._send_receive_msg(start_test_msg_tx_bin)

        if response_msg_bin:
            start_test_msg_rx_dict = Msg.StartSchedule.Server.unpack(
                response_msg_bin)
            if start_test_msg_rx_dict['result'] == 'success':
                success = True
                logger.info(
                    f'Successfully started test {self.__config.test_name} with schedule {self.__config.schedule_name} on channel {self.__config.channel}')
                logger.debug(start_test_msg_rx_dict)
            else:
                logger.error(
                    f'Failed to start test {self.__config.test_name} with schedule {self.__config.schedule_name} on channel {self.__config.channel}. Issue: {start_test_msg_rx_dict["result"]}')

    return success
def stop_test(self) ‑> bool

Stops the test running on the channel specified in the config.

Returns

success : bool
True/False based on whether the test stopped without issue. Also returns True if no test was running on the channel.
Expand source code
def stop_test(self) -> bool:
    """
    Stops the test running on the channel specified in the config.

    Returns
    -------
    success : bool
        True/False based on whether the test stopped without issue.
        Also returns True if no test was running on the channel. 
    """
    success = False

    stop_test_msg_tx_bin = Msg.StopSchedule.Client.pack(
        {'channel': self.__config.channel})
    response_msg_bin = self._send_receive_msg(
        stop_test_msg_tx_bin)

    if response_msg_bin:
        stop_test_msg_rx_dict = Msg.StopSchedule.Server.unpack(
            response_msg_bin)
        if stop_test_msg_rx_dict['result'] == 'success':
            success = True
            logger.info(
                f'Successfully stopped test on channel {self.__config.channel}')
            logger.debug(stop_test_msg_rx_dict)
        else:
            logger.error(
                f'Failed to stop test on channel {self.__config.channel}! Issue: {stop_test_msg_rx_dict["result"]}')

    return success

Inherited members

class ChannelInterfaceConfig (**data: Any)

Holds channel config information for the CyclerInterface class.

Parameters

channel : int
    The channel to target with the ChannelInterface class instance.
test_name : *optional*
    The test name to use if using the ChannelInterface to start a test.
schedule_name : str
    The name of the schedule file to use if using the ChannelInterface to start a test.
ip_address : str 
    The IP address of the Arbin host computer.
port : int 
    The TCP port to communicate through.
timeout_s : float 
    How long to wait before timing out on TCP communication. Defaults to 3 seconds. 
msg_buffer_size : int 
     How big of a message buffer to use for sending/receiving messages. 
    A minimum of 1024 bytes is recommended. Defaults to 4096 bytes.

Create a new model by parsing and validating input data from keyword arguments.

Raises ValidationError if the input data cannot be parsed to form a valid model.

Expand source code
class ChannelInterfaceConfig(BaseModel):
    '''
    Holds channel config information for the CyclerInterface class.

    Parameters
    ----------
        channel : int
            The channel to target with the ChannelInterface class instance.
        test_name : *optional*
            The test name to use if using the ChannelInterface to start a test.
        schedule_name : str
            The name of the schedule file to use if using the ChannelInterface to start a test.
        ip_address : str 
            The IP address of the Arbin host computer.
        port : int 
            The TCP port to communicate through.
        timeout_s : float 
            How long to wait before timing out on TCP communication. Defaults to 3 seconds. 
        msg_buffer_size : int 
             How big of a message buffer to use for sending/receiving messages. 
            A minimum of 1024 bytes is recommended. Defaults to 4096 bytes. 
    '''
    channel: int
    test_name: str = None
    schedule_name: str = None
    ip_address: str
    port: int
    timeout_s: float = 3.0
    msg_buffer_size: int = 4096

    @validator('channel')
    def username_alphanumeric(cls, v):
        if v < 1:
            raise ValueError('Channel must be greater than zero!')
        return v-1

Ancestors

  • pydantic.main.BaseModel
  • pydantic.utils.Representation

Class variables

var channel : int
var ip_address : str
var msg_buffer_size : int
var port : int
var schedule_name : str
var test_name : str
var timeout_s : float

Static methods

def username_alphanumeric(v)
Expand source code
@validator('channel')
def username_alphanumeric(cls, v):
    if v < 1:
        raise ValueError('Channel must be greater than zero!')
    return v-1