[etherlab-dev] Python master ioctl module

Dave Page dave.page at gleeble.com
Sun Feb 23 01:31:37 CET 2014


Hi,

     I have started writing a pure python module to provide access to 
the master ioctl interface exposing equivalent capabilities as the 
'ethercat' command line tool. The purpose is to provide scripted PRE-OP 
slave verification and configuration. The implementation is based on the 
tool's MasterDevice class.

     So, for example, to obtain a dump of the master info and attached 
slaves:

from ethercat_master import MasterDevice
with MasterDevice(0) as m:

     master = m.get_master()

     print master


     for i in xrange(master.slave_count):

         print '** Slave',i

         print m.get_slave(i)


     It still needs code review and an integration test module (e.g. 
with a specified slave configuration) to test the API.

     If anybody wants to put this in the etherlab source tree, that is 
fine with me. Or, I will put it on sourceforge somewhere.

     Best regards - Dave Page



-------------- next part --------------
# -*- coding: utf-8 -*-
"""
ethercat_master.py

Provides a pure-python interface to the IgH EtherCAT master.

The intent is to provide all the capability of the ethercat command line 
tool in order to facilitate scripted system configuration and troubleshooting.

The API provided is similar to ethercat/tool/MasterDevice.cpp

>>> from ethercat_master import MasterDevice 
>>> with MasterDevice() as m:
...     m.set_debug(1)
...     master = m.get_master()
...     print master
...     for i in xrange(master.slave_count):
...         print m.get_slave(i)

Created on Mon Feb 17 22:29:30 2014

author: Dave Page, Dynamic Systems Inc.

Copyright (C) 2014 Dynamic Systems Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License version 2 as 
published by the Free Software Foundation.

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 http://www.gnu.org/licenses/gpl-2.0.html
"""

import fcntl
import struct
import re
import ctypes
import itertools

############################################################################
#
# EtherCAT master configuration information
#
############################################################################
# These settings must match the master configuration

EC_IOCTL_VERSION_MAGIC=28

# These defines must reflect the ethercat master configuration
c_defines = {
    'EC_EOE':1, # Comment out for no EoE
    'EC_MAX_NUM_DEVICES':1,
    'EC_SYNC_SIGNAL_COUNT':2,
    'EC_RATE_COUNT':3,
    'EC_MAX_SYNC_MANAGERS':16,
    'EC_MAX_PORTS':4,
    'EC_MAX_STRING_LENGTH':64,
    'EC_IOCTL_STRING_SIZE':64,
    'EC_DATAGRAM_NAME_SIZE':20,
    'EC_SDO_ENTRY_ACCESS_COUNT':3,
    'EC_MAX_SDO_DATA_SIZE':1024,
    'EC_MAX_IDN_DATA_SIZE':1024,
}

def ifdef_ec_eoe(code):
    if 'EC_EOE' in c_defines:
        return code
    else:
        return ''

############################################################################
#
# Implementation of Linux ioctl.h functionality
#
############################################################################
# Targets x86. May require revision for other architectures

class _ioctl(object):
    nrbits = 8
    typebits = 8
    sizebits = 14
    dirbits = 2

    nrshift = 0
    typeshift = nrshift + nrbits
    sizeshift = typeshift + typebits
    dirshift = sizeshift + sizebits

    class dir(object):
        none, write, read, writeread = xrange(4)

    @staticmethod
    def _ioc(dir,type,nr,size):
        if isinstance(size,basestring):
            size = c_struct_map[size].sizeof()

        return (dir<<_ioctl.dirshift |
                type<<_ioctl.typeshift |
                nr<<_ioctl.nrshift |
                size<<_ioctl.sizeshift)

    @staticmethod
    def io(type,nr):
        return _ioctl._ioc(_ioctl.dir.none, type, nr, 0)

    @staticmethod
    def ior(type,nr,size):
        return _ioctl._ioc(_ioctl.dir.read, type, nr, size)

    @staticmethod
    def iow(type,nr,size):
        return _ioctl._ioc(_ioctl.dir.write, type, nr, size)

    @staticmethod
    def iowr(type,nr,size):
        return _ioctl._ioc(_ioctl.dir.writeread, type, nr, size)

    @staticmethod
    def is_r(op):
        return (op>>_ioctl.dirshift & _ioctl.dir.read) > 0

    @staticmethod
    def is_w(op):
        return (op>>_ioctl.dirshift & _ioctl.dir.write) > 0

    @staticmethod
    def is_wr(op):
        return _ioctl.is_r(op) and _ioctl.is_w(op)

############################################################################
#
# EtherCAT IOCTL structure support
#
############################################################################
# Provides convenience classes to wrap data passed to IOCTLs and to define
# the mapping between the ioctl layout and python objects.

class ec_var_def(object):
    """
    Struct type definition of a simple variable corresponding to one py 
    struct format character.
    """
    def __init__(self, struct_format, default=0):
        assert isinstance(struct_format, basestring)
        self._format = struct_format
        self._default = default
        
    def get_item(self, it):
        ret = next(it)
        return self._default if ret is None else ret
        
    def set_item(self, value):
        return [value]
        
    def get_format(self):
        return self._format

# Map C types to python struct module formats
# The value is ec_*_def, whos given format is supplied to pack/unpack
# Please note the pointer types must group the star to the left next to 
# the type to simplify parsing
c_struct_map = {
    'char':     ec_var_def('b'),
    'int8_t':   ec_var_def('b'),
    'int16_t':  ec_var_def('h'),
    'int32_t':  ec_var_def('l'),
    'int64_t':  ec_var_def('q'),
    'uint8_t':  ec_var_def('B'),
    'uint16_t': ec_var_def('H'),
    'uint32_t': ec_var_def('L'),
    'uint64_t': ec_var_def('Q'),

    'int':      ec_var_def('l'),
    'unsigned': ec_var_def('L'),
    'size_t':   ec_var_def('L'),

    'float':    ec_var_def('f'), 

    'uint8_t*': ec_var_def('L'),
    'uint16_t*':ec_var_def('L'),

    # Standard EtherCAT symbols, provided for convenience
    'SINT':     ec_var_def('b'),
    'INT':      ec_var_def('h'),
    'DINT':     ec_var_def('l'),
    'USINT':    ec_var_def('B'),
    'UINT':     ec_var_def('H'),
    'UDINT':    ec_var_def('L'),
    'REAL':     ec_var_def('f'),
}

class ec_struct(object):
    """
    Working structure data. Live IOCTL data is stored as attributes of
    this object. The ec_type attribute references an ec_struct_def object 
    which describes the structure layout in detail.
    ec_struct instances are nested or used as array elements as per the
    structure definition.
    """
    def __init__(self, ec_type):
        self.ec_type = ec_type

    def __repr__(self):
        return '<%s>' % '\n '.join('%s : %s' % (k, repr(v)) for (k, v) 
            in iter(sorted(self.__dict__.items())) if k!='ec_type')

    def struct_buffer(self):
        "Obtain an empty struct buffer"
        return self.ec_type.struct_buffer()

    def pack(self):
        "Obtain a struct buffer filled out with this struct's properties"
        return self.ec_type.pack(self)

    def unpack(self, data):
        "Return a new object from the given struct buffer"
        return self.ec_type.unpack(data)

class ec_string_def(ec_var_def):
    """
    Supports mapping a fixed-size array of bytes to python string
    The python string is right-padded with zeroes on write
    """
    def __init__(self, size):
        super(ec_string_def, self).__init__('%ds' % size, '\0'*size)
        self._size = size
        
    def set_item(self, value):
        if len(value)>self._size:
            raise RuntimeError("String size too large")
        return [value.ljust(self._size, '\0')]

class ec_cstr_def(ec_string_def):
    "C-String support: Truncates input string at the first zero character"
    def get_item(self, it):
        return super(ec_cstr_def, self).get_item(it).split('\0',1)[0]

class ec_array_def(object):
    """
    Array support. Stores fixed-N copies of the given type. Type is described
    by a reference to another ec_*_def object.
    """
    def __init__(self, ec_def, size):
        self._element_type = ec_def
        self._size = size
        
    def get_item(self, items):
        lst = []
        for i in xrange(self._size):
            lst.append( self._element_type.get_item(items) )
        return lst
        
    def set_item(self, value):
        assert len(value) == self._size
        items = []
        for i in xrange(self._size):
            items.extend(self._element_type.set_item(value[i]))
        return items

    def get_format(self):
        return self._element_type.get_format() * self._size

class ec_struct_def(object):
    """
    Structure support. Defines an ordered set of ec_*_def types which are
    serialed to/from binary data as a contiguous block (as in a C struct)
    """
    def __init__(self, definition, symbol):
        """
        Initialize py structure definition from C-style text. The definition
        is obtained from the C header text found between the brackets of
        the C struct definition. This text is minimally parsed and stored
        as a tree of ec_*_def objects. The methods of the top-level
        ec_struct_def can then be used to serialize/deserialize the 
        struct binary data.
        The objective of this approach is to minimize the maintenance 
        issues as the IOCTL struct definitions change. Often a simple
        copy/paste will suffice.
        Please note pointers must have the star grouped left (uint8_t* foo)
        as the parser is pretty dumb.
        """
        self._name = symbol

        self._symbols = []      # Ordered list of this struct's symbols
        self._types = []        # Ordered list of this struct's types

        # Remove most C/C++ comments
        definition = re.sub('//.*?\n|/\*.*?\*/', '', definition, re.S)

        for stmt in definition.split(';'):
            stmt = stmt.strip()
            if len(stmt)==0:
                continue
            g = re.match('([\w\*]+) +(\w+)(\[\w+\])?', stmt)
            if not g:
                raise RuntimeError('Parse error: '+stmt)

            c_type = g.group(1)
            struct_type = c_struct_map[c_type]
            sym = g.group(2)
            array_size = g.group(3)

            if array_size:
                array_size = array_size[1:-1]
                if array_size in c_defines:
                    cnt = c_defines[array_size]
                else:
                    cnt = int(array_size)

                if c_type=='char':
                    # Char arrays as c-string
                    self._append_def(ec_cstr_def(cnt), sym)
                elif (isinstance(struct_type, ec_var_def) and 
                    struct_type.get_format() in ('b','B')):
                    # Byte arrays as byte string
                    self._append_def(ec_string_def(cnt), sym)
                else:
                    # Other kinds of arrays
                    self._append_def(ec_array_def(struct_type, cnt), sym)
            else:
                self._append_def(struct_type, sym)

        self._struct = struct.Struct(self.get_format())

    def _append_def(self, struct_type, symbol):
        self._types.append(struct_type)
        self._symbols.append(symbol)        

    def get_format(self):
        "Obtain the python struct format of this structure including children"
        fmt =  ''.join(ec_def.get_format() for ec_def in self._types)

        # Pad to 32 bits (empirically required by ioctl size)
        remainder = struct.calcsize(fmt) % 4
        if remainder>0:
            fmt += '%dx' % (4-remainder)
        
        return fmt

    def sizeof(self):
        "Obtain the size in bytes of this structure including children"
        return self._struct.size
    
    def get_item(self, items):
        """
        Read the values from the items iterator (sourced, for example, from 
        struct.unpack) and populate into the attributes of this object. The 
        tree of values, arrays, and structs will be  walked depth-first and 
        populated as described by items.
        """
        obj = ec_struct(self)
        for sym,typ in zip(self._symbols, self._types):
            setattr(obj, sym, typ.get_item(items))
        return obj
        
    def set_item(self, value):
        """
        Return the attributes of this type serialized into a list suitable
        for use by struct.pack. The tree of values, arrays, and structs will 
        be  walked depth-first and returned as a simple list vector.
        """
        items = []
        for sym,typ in zip(self._symbols, self._types):
            items.extend(typ.set_item(getattr(value, sym)))
        return items

    def struct_buffer(self):
        """
        Obtain a new data buffer of the correct size for this type
        """
        return ctypes.create_string_buffer(self.sizeof()) 

    def pack(self, ecs):
        """
        Serialize the given ec_struct into a new data buffer and 
        return the buffer.
        """
        assert ecs.ec_type == self
        buf = self.struct_buffer()
        self._struct.pack_into(buf, 0, *self.set_item(ecs)) 
        return buf

    def unpack(self, data):
        """
        Make a new ec_struct of this type. Attributes will be set to 
        the values ready from the binary data.
        """
        return self.get_item(iter(self._struct.unpack_from(data)))

    def new(self):
        """
        Make a new ec_struct of this type. Attributes will be set to 
        their default value.
        """
        return self.get_item(itertools.repeat(None))

    def name(self):
        return self._name

def typedef_struct(definition, symbol):
    """
    Helper function to define structure layout with minimal edits
    from the C source.
    """
    c_struct_map[symbol] = ec_struct_def(definition, symbol)

############################################################################
#
# EtherCAT IOCTL structure definitions 
#
############################################################################
# Lifted from ethercat/master/ioctl.h
# Please note the C definition parser (above) is very simple 

typedef_struct("""
    uint8_t flags;
""", 'ec_sii_coe_details_t')

typedef_struct("""
    uint8_t flags;
""", 'ec_sii_general_flags_t')

typedef_struct("""
    uint32_t enum;
""", 'ec_slave_dc_range_t')

# EtherCAT slave sync signal configuration.
typedef_struct("""
    uint32_t cycle_time; /**< Cycle time [ns]. */
    int32_t shift_time; /**< Shift time [ns]. */
""",'ec_sync_signal_t')

typedef_struct("""
    uint32_t enum;
""", 'ec_slave_port_desc_t')

EC_WC_ZERO=0        # No registered process data were exchanged. */
EC_WC_INCOMPLETE=1  # Some of the registered process data were exchanged. */
EC_WC_COMPLETE=2    # All registered process data were exchanged. */
typedef_struct("""
    uint32_t enum;
""", 'ec_wc_state_t')

# Direction type for PDO assignment functions.
EC_DIR_INVALID=0    # Invalid direction. Do not use this value. */
EC_DIR_OUTPUT=1     # Values written by the master. */
EC_DIR_INPUT=2      # Values read by the master. */
EC_DIR_COUNT=3      # Number of directions. For internal use only. */
typedef_struct("""
    uint32_t enum;
""", 'ec_direction_t')

# Watchdog mode for sync manager configuration.
# Used to specify, if a sync manager's watchdog is to be enabled.
EC_WD_DEFAULT=0     # Use the default setting of the sync manager. */
EC_WD_ENABLE=1      # Enable the watchdog. */
EC_WD_DISABLE=2     # Disable the watchdog. */
typedef_struct("""
    uint32_t enum;
""", 'ec_watchdog_mode_t')

# Application-layer state.
EC_AL_STATE_INIT = 1    # Init. */
EC_AL_STATE_PREOP = 2   # Pre-operational. */
EC_AL_STATE_SAFEOP = 4  # Safe-operational. */
EC_AL_STATE_OP = 8      # Operational. */
typedef_struct("""
    uint32_t enum;
""", 'ec_al_state_t')

typedef_struct("""
    uint32_t ioctl_version_magic;
    uint32_t master_count;
""", 'module_t')

typedef_struct("""
    uint8_t link_up;
    uint8_t loop_closed;
    uint8_t signal_detected;
""", 'ec_slave_port_link_t')

typedef_struct("""
    uint8_t address[6];
    uint8_t attached;
    uint8_t link_state;

    uint64_t tx_count;
    uint64_t rx_count;
    uint64_t tx_bytes;
    uint64_t rx_bytes;
    uint64_t tx_errors;
    int32_t tx_frame_rates[EC_RATE_COUNT];
    int32_t rx_frame_rates[EC_RATE_COUNT];
    int32_t tx_byte_rates[EC_RATE_COUNT];
    int32_t rx_byte_rates[EC_RATE_COUNT];
""", 'master_t_devices')

typedef_struct("""
    uint32_t slave_count;
    uint32_t config_count;
    uint32_t domain_count;
"""+ifdef_ec_eoe("""
    uint32_t eoe_handler_count;
""")+"""
    uint8_t phase;
    uint8_t active;
    uint8_t scan_busy;

    master_t_devices devices[EC_MAX_NUM_DEVICES];

    uint32_t num_devices;
    uint64_t tx_count;
    uint64_t rx_count;
    uint64_t tx_bytes;
    uint64_t rx_bytes;
    int32_t tx_frame_rates[EC_RATE_COUNT];
    int32_t rx_frame_rates[EC_RATE_COUNT];
    int32_t tx_byte_rates[EC_RATE_COUNT];
    int32_t rx_byte_rates[EC_RATE_COUNT];
    int32_t loss_rates[EC_RATE_COUNT];
    uint64_t app_time;
    uint16_t ref_clock;
""", 'master_t')

typedef_struct("""
    ec_slave_port_desc_t desc;
    ec_slave_port_link_t link;
    uint32_t receive_time;
    uint16_t next_slave;
    uint32_t delay_to_next_dc;
""", 'slave_t_ports')

typedef_struct("""
    uint16_t position;

    unsigned device_index;
    uint32_t vendor_id;
    uint32_t product_code;
    uint32_t revision_number;
    uint32_t serial_number;
    uint16_t alias;
    uint16_t boot_rx_mailbox_offset;
    uint16_t boot_rx_mailbox_size;
    uint16_t boot_tx_mailbox_offset;
    uint16_t boot_tx_mailbox_size;
    uint16_t std_rx_mailbox_offset;
    uint16_t std_rx_mailbox_size;
    uint16_t std_tx_mailbox_offset;
    uint16_t std_tx_mailbox_size;
    uint16_t mailbox_protocols;
    uint8_t has_general_category;
    uint8_t coe_details;  //# altered to get correct layout
    uint8_t general_flags; //# altered to get correct layout
    int16_t current_on_ebus;

    slave_t_ports ports[EC_MAX_PORTS];

    uint8_t fmmu_bit;
    uint8_t dc_supported;
    ec_slave_dc_range_t dc_range;
    uint8_t has_dc_system_time;
    uint32_t transmission_delay;
    uint8_t al_state;
    uint8_t error_flag;
    uint8_t sync_count;
    uint16_t sdo_count;
    uint32_t sii_nwords;
    char group[EC_IOCTL_STRING_SIZE];
    char image[EC_IOCTL_STRING_SIZE];
    char order[EC_IOCTL_STRING_SIZE];
    char name[EC_IOCTL_STRING_SIZE];
""", 'slave_t')

typedef_struct("""
    // inputs
    uint16_t slave_position;
    uint32_t sync_index;

    // outputs
    uint16_t physical_start_address;
    uint16_t default_size;
    uint8_t control_register;
    uint8_t enable;
    uint8_t pdo_count;
""", 'slave_sync_t')

typedef_struct("""
    // inputs
    uint16_t slave_position;
    uint32_t sync_index;
    uint32_t pdo_pos;

    // outputs
    uint16_t index;
    uint8_t entry_count;
    int8_t name[EC_IOCTL_STRING_SIZE];
""", 'slave_sync_pdo_t')

typedef_struct("""
    // inputs
    uint16_t slave_position;
    uint32_t sync_index;
    uint32_t pdo_pos;
    uint32_t entry_pos;

    // outputs
    uint16_t index;
    uint8_t subindex;
    uint8_t bit_length;
    int8_t name[EC_IOCTL_STRING_SIZE];
""", 'slave_sync_pdo_entry_t')

typedef_struct("""
    uint32_t index;

    // outputs
    uint32_t data_size;
    uint32_t logical_base_address;
    uint16_t working_counter[EC_MAX_NUM_DEVICES];
    uint16_t expected_working_counter;
    uint32_t fmmu_count;
""", 'domain_t')

typedef_struct("""
    // inputs
    uint32_t domain_index;
    uint32_t fmmu_index;

    // outputs
    uint16_t slave_config_alias;
    uint16_t slave_config_position;
    uint8_t sync_index;
    ec_direction_t dir;
    uint32_t logical_address;
    uint32_t data_size;
""", 'domain_fmmu_t')

typedef_struct("""
    // inputs
    uint32_t domain_index;
    uint32_t data_size;
    uint8_t* target;
""", 'domain_data_t')

typedef_struct("""
    uint16_t slave_position;
    uint8_t al_state;
""",'slave_state_t')

typedef_struct("""
    uint16_t slave_position;
    uint16_t sdo_position;

    uint16_t sdo_index;
    uint8_t max_subindex;
    int8_t name[EC_IOCTL_STRING_SIZE];
""",'slave_sdo_t')

typedef_struct("""
    uint16_t slave_position;
    int sdo_spec;
    uint8_t sdo_entry_subindex;

    uint16_t data_type;
    uint16_t bit_length;
    uint8_t read_access[EC_SDO_ENTRY_ACCESS_COUNT];
    uint8_t write_access[EC_SDO_ENTRY_ACCESS_COUNT];
    char description[EC_IOCTL_STRING_SIZE];
""",'slave_sdo_entry_t')

typedef_struct("""
    uint16_t slave_position;
    uint16_t sdo_index;
    uint8_t sdo_entry_subindex;
    size_t target_size;
    uint8_t* target;

    size_t data_size;
    uint32_t abort_code;
""",'slave_sdo_upload_t')

typedef_struct("""
    uint16_t slave_position;
    uint16_t sdo_index;
    uint8_t sdo_entry_subindex;
    uint8_t complete_access;
    size_t data_size;
    uint8_t* data;

    uint32_t abort_code;
""",'slave_sdo_download_t')

typedef_struct("""
    uint16_t slave_position;
    uint16_t offset;
    uint32_t nwords;
    uint16_t* words;
""",'slave_sii_t')

typedef_struct("""
    uint16_t slave_position;
    uint8_t emergency;
    uint16_t address;
    size_t size;
    uint8_t* data;
""",'slave_reg_t')

typedef_struct("""
    uint16_t slave_position;
    uint16_t offset;
    size_t buffer_size;
    uint8_t* buffer;

    size_t data_size;
    uint32_t result;
    uint32_t error_code;
    char file_name[32];
""", 'slave_foe_t')

typedef_struct("""
    // inputs
    uint16_t slave_position;
    uint8_t drive_no;
    uint16_t idn;
    size_t mem_size;
    uint8_t* data;

    // outputs
    size_t data_size;
    uint16_t error_code;
""", 'slave_soe_read_t')

typedef_struct("""
    // inputs
    uint16_t slave_position;
    uint8_t drive_no;
    uint16_t idn;
    size_t data_size;
    uint8_t* data;

    // outputs
    uint16_t error_code;
""", 'slave_soe_write_t')

typedef_struct("""
    ec_direction_t dir;
    ec_watchdog_mode_t watchdog_mode;
    uint32_t pdo_count;
    uint8_t config_this;
""", 'config_t_syncs')

typedef_struct("""
    // inputs
    uint32_t config_index;

    // outputs
    uint16_t alias;
    uint16_t position;
    uint32_t vendor_id;
    uint32_t product_code;

    config_t_syncs syncs[EC_MAX_SYNC_MANAGERS];

    uint16_t watchdog_divider;
    uint16_t watchdog_intervals;
    uint32_t sdo_count;
    uint32_t idn_count;
    int32_t slave_position;
    uint16_t dc_assign_activate;
    ec_sync_signal_t dc_sync[EC_SYNC_SIGNAL_COUNT];
""", 'config_t')

typedef_struct("""
    // inputs
    uint32_t config_index;
    uint8_t sync_index;
    uint16_t pdo_pos;

    // outputs
    uint16_t index;
    uint8_t entry_count;
    int8_t name[EC_IOCTL_STRING_SIZE];
""", 'config_pdo_t')

typedef_struct("""
    // inputs
    uint32_t config_index;
    uint8_t sync_index;
    uint16_t pdo_pos;
    uint8_t entry_pos;

    // outputs
    uint16_t index;
    uint8_t subindex;
    uint8_t bit_length;
    int8_t name[EC_IOCTL_STRING_SIZE];
""", 'config_pdo_entry_t')

typedef_struct("""
    // inputs
    uint32_t config_index;
    uint32_t sdo_pos;

    // outputs
    uint16_t index;
    uint8_t subindex;
    size_t size;
    uint8_t data[EC_MAX_SDO_DATA_SIZE];
    uint8_t complete_access;
""", 'config_sdo_t')

typedef_struct("""
    // inputs
    uint32_t config_index;
    uint32_t idn_pos;

    // outputs
    uint8_t drive_no;
    uint16_t idn;
    ec_al_state_t state;
    size_t size;
    uint8_t data[EC_MAX_IDN_DATA_SIZE];
""", 'config_idn_t')

typedef_struct("""
    // input
    uint16_t eoe_index;

    // outputs
    char name[EC_DATAGRAM_NAME_SIZE];
    uint16_t slave_position;
    uint8_t open;
    uint32_t rx_bytes;
    uint32_t rx_rate;
    uint32_t tx_bytes;
    uint32_t tx_rate;
    uint32_t tx_queued_frames;
    uint32_t tx_queue_size;
""", 'eoe_handler_t')

############################################################################
#
# EtherCAT IOCTL Op Constants from ethercat/master/ioctl.h
#
############################################################################
# Similar to the C header, these constant IOCTL op definitions obtain
# the ioctl Op size from the structure definitions above

EC_IOCTL_TYPE=0xa4

def EC_IO(nr):
    return _ioctl.io(EC_IOCTL_TYPE,nr)

def EC_IOR(nr,size):
    return _ioctl.ior(EC_IOCTL_TYPE,nr,size)

def EC_IOW(nr,size):
    return _ioctl.iow(EC_IOCTL_TYPE,nr,size)

def EC_IOWR(nr,size):
    return _ioctl.iowr(EC_IOCTL_TYPE,nr,size)

class EC_IOCTL(object):
    # Command-line tool
    MODULE=EC_IOR(0x00, 'module_t')
    MASTER=EC_IOR(0x01, 'master_t')
    SLAVE=EC_IOWR(0x02, 'slave_t')
    SLAVE_SYNC=EC_IOWR(0x03, 'slave_sync_t')
    SLAVE_SYNC_PDO=EC_IOWR(0x04, 'slave_sync_pdo_t')
    SLAVE_SYNC_PDO_ENTRY=EC_IOWR(0x05, 'slave_sync_pdo_entry_t')
    DOMAIN=EC_IOWR(0x06, 'domain_t')
    DOMAIN_FMMU=EC_IOWR(0x07, 'domain_fmmu_t')
    DOMAIN_DATA=EC_IOWR(0x08, 'domain_data_t')
    MASTER_DEBUG=EC_IO(0x09)
    MASTER_RESCAN=EC_IO(0x0a)
    SLAVE_STATE=EC_IOW(0x0b, 'slave_state_t')
    SLAVE_SDO=EC_IOWR(0x0c, 'slave_sdo_t')
    SLAVE_SDO_ENTRY=EC_IOWR(0x0d, 'slave_sdo_entry_t')
    SLAVE_SDO_UPLOAD=EC_IOWR(0x0e, 'slave_sdo_upload_t')
    SLAVE_SDO_DOWNLOAD=EC_IOWR(0x0f, 'slave_sdo_download_t')
    SLAVE_SII_READ=EC_IOWR(0x10, 'slave_sii_t')
    SLAVE_SII_WRITE=EC_IOW(0x11, 'slave_sii_t')
    SLAVE_REG_READ=EC_IOWR(0x12, 'slave_reg_t')
    SLAVE_REG_WRITE=EC_IOW(0x13, 'slave_reg_t')
    SLAVE_FOE_READ=EC_IOWR(0x14, 'slave_foe_t')
    SLAVE_FOE_WRITE=EC_IOW(0x15, 'slave_foe_t')
    SLAVE_SOE_READ=EC_IOWR(0x16, 'slave_soe_read_t')
    SLAVE_SOE_WRITE=EC_IOWR(0x17, 'slave_soe_write_t')
    CONFIG=EC_IOWR(0x18, 'config_t')
    CONFIG_PDO=EC_IOWR(0x19, 'config_pdo_t')
    CONFIG_PDO_ENTRY=EC_IOWR(0x1a, 'config_pdo_entry_t')
    CONFIG_SDO=EC_IOWR(0x1b, 'config_sdo_t')
    CONFIG_IDN=EC_IOWR(0x1c, 'config_idn_t')
    EOE_HANDLER=EC_IOWR(0x1d, 'eoe_handler_t')

############################################################################
#
# EtherCAT Master API Implmentation
#
############################################################################
# The API design is similar to ethercat/tool/MasterDevice.cpp

class MasterDeviceError(RuntimeError):
    pass

class MasterDeviceSdoAbortError(RuntimeError):
    pass

class MasterDeviceSoeError(RuntimeError):
    pass

class MasterDevice(object):
    def __init__(self, index=0):
        "Instantiate an interface to master by index default 0."
        self._index = index
        self._master_count = 0
        self._fd = None

    class Permissions:
        "Master read only or read/write permission"
        Read = 'rb'
        ReadWrite = 'r+b'

    def open(self, permission):
        """
        Open master with spoecified permission level. Will verify correct
        IOCTL version and throw if invalid.
        """
        device_name = '/dev/EtherCAT%d' % self._index
        if not self._fd: #/ not already open
            self._fd = open(device_name, permission, 0)

        module_data = self.get_module()
        if module_data.ioctl_version_magic != EC_IOCTL_VERSION_MAGIC:
            raise MasterDeviceError(
                "ioctl() version magic is differing: %s: %d expected %d"
                % (device_name, module_data.ioctl_version_magic,
                   EC_IOCTL_VERSION_MAGIC))

        self._master_count = module_data.master_count

    def close(self):
        "Close master device file"
        if self._fd:
            self._fd.close()

    def __enter__(self):
        self.open(self.Permissions.ReadWrite)
        return self

    def __exit__(self, type, value, traceback):
        self.close()

    @staticmethod
    def new_struct(name):
        "Obtain an empty ec_struct by symbol name string"
        return c_struct_map[name].new()

    def _ioctl(self, op, obj):
        if isinstance(obj, basestring):
            obj = self.new_struct(obj)
        elif isinstance(obj, int):
            return fcntl.ioctl(self._fd, op, obj)

        if _ioctl.is_w(op):
            buf = obj.pack()
        else:
            buf = obj.struct_buffer()

        try:
            #print 'ioctl args',self._fd, op, obj.struct, _ioctl.is_r(op)
            fcntl.ioctl(self._fd, op, buf, _ioctl.is_r(op))
            #print 'ioctl result',repr(res), repr(obj.struct)
        except Exception as e:
            if _ioctl.is_r(op):
                raise MasterDeviceError("IOCTL failed:"+e.message, 
                                        obj.unpack(buf))
            else:
                raise MasterDeviceError("IOCTL failed:"+e.message)

        if _ioctl.is_r(op):
            return obj.unpack(buf)
        return None

    def get_module(self):
        return self._ioctl(EC_IOCTL.MODULE,'module_t')

    def get_master(self):
        return self._ioctl(EC_IOCTL.MASTER,'master_t')

    def get_config(self, index):
        config = self.new_struct('config_t')
        config.config_index = index

        return self._ioctl(EC_IOCTL.CONFIG, config)

    def get_config_pdo(self, index, sync_index, pdo_pos):
        config = self.new_struct('config_pdo_t')
        config.config_index = index
        config.sync_index = sync_index
        config.pdo_pos = pdo_pos

        return self._ioctl(EC_IOCTL.CONFIG_PDO, config)

    def get_config_pdo_entry(self, index, sync_index, pdo_pos, entry_pos):
        config = self.new_struct('config_pdo_entry_t')
        config.config_index = index
        config.sync_index = sync_index
        config.pdo_pos = pdo_pos
        config.entry_pos = entry_pos

        return self._ioctl(EC_IOCTL.CONFIG_PDO_ENTRY, config)

    def get_config_sdo(self, index, sdo_pos):
        config = self.new_struct('config_sdo_t')
        config.config_index = index
        config.sdo_pos = sdo_pos

        self._ioctl(EC_IOCTL.CONFIG_SDO, config)

    def get_config_idn(self, index, pos):
        config = self.new_struct('config_idn_t')
        config.config_index = index
        config.idn_pos = pos

        return self._ioctl(EC_IOCTL.CONFIG_IDN, config)

    def get_domain(self, index):
        domain = self.new_struct('domain_t')
        domain.config_index = index

        return self._ioctl(EC_IOCTL.CONFIG_IDN, domain)

    def get_data(self, domainIndex, dataSize):
        buf = ctypes.create_string_buffer(dataSize)
        
        domain = self.new_struct('domain_data_t')
        domain.domain_index = domainIndex
        domain.data_size = dataSize
        domain.target = ctypes.addressof(buf)
        
        self._ioctl(EC_IOCTL.DOMAIN_DATA, domain)
            
        return bytearray(buf)

    def get_slave(self, index):
        slave = self.new_struct('slave_t')
        slave.position = index
        return self._ioctl(EC_IOCTL.SLAVE, slave)

    def get_fmmu(self, domainIndex, fmmuIndex):
        fmmu = self.new_struct('domain_fmmu_t')
        fmmu.domain_index = domainIndex
        fmmu.fmmu_index = fmmuIndex

        return self._ioctl(EC_IOCTL.DOMAIN_FMMU, fmmu)

    def get_sync(self, slaveIndex, syncIndex):
        sync = self.new_struct('slave_sync_t')
        sync.slave_position = slaveIndex
        sync.sync_index = syncIndex
        
        return self._ioctl(EC_IOCTL.SLAVE_SYNC, sync)
        
    def get_pdo(self, slaveIndex, syncIndex, pdoPos):
        pdo = self.new_struct('slave_sync_pdo_t')
        pdo.slave_position = slaveIndex
        pdo.sync_index = syncIndex
        pdo.pdo_pos = pdoPos
        
        return self._ioctl(EC_IOCTL.SLAVE_SYNC_PDO, pdo)

    def get_pdo_entry(self, slaveIndex, syncIndex, pdoPos, entryPos):
        entry = self.new_struct('slave_sync_pdo_entry_t')
        entry.slave_position = slaveIndex
        entry.sync_index = syncIndex
        entry.pdo_pos = pdoPos
        entry.entry_pos = entryPos
        
        return self._ioctl(EC_IOCTL.SLAVE_SYNC_PDO_ENTRY, entry)
    
    def get_sdo(self, slaveIndex, sdoPosition):
        sdo = self.new_struct('slave_sdo_t')
        sdo.slave_position = slaveIndex
        sdo.sdo_position = sdoPosition
        
        return self._ioctl(EC_IOCTL.SLAVE_SDO, sdo)

    def get_sdo_entry(self, slaveIndex, sdo_index, sdo_entry_subindex):
        entry = self.new_struct('slave_sdo_entry_t')
        entry.slave_position = slaveIndex
        entry.sdo_spec = sdo_index
        entry.sdo_entry_subindex = sdo_entry_subindex
        
        return self._ioctl(EC_IOCTL.SLAVE_SDO_ENTRY, entry)

    def read_sii(self, slaveIndex):
        slave = self.get_slave(slaveIndex)
        if slave.sii_nwords < 1:
            return None

        buf = ctypes.create_string_buffer(slave.sii_nwords * 2)
        
        data = self.new_struct('slave_sii_t')
        data.slave_position = slave.position
        data.offset = 0
        data.nwords = slave.sii_nwords
        data.words = ctypes.addressof(buf)
        
        self._ioctl(EC_IOCTL.SLAVE_SII_READ, data)

        return bytearray(buf)

    def write_sii(self, slaveIndex, sii_data):
        slave = self.get_slave(slaveIndex)
        if slave.sii_nwords < 1:
            return None

        buf = ctypes.create_string_buffer( str(sii_data) )
        
        data = self.new_struct('slave_sii_t')
        data.slave_position = slave.position
        data.offset = 0
        data.nwords = slave.sii_nwords
        data.words = ctypes.addressof(buf)
        
        self._ioctl(EC_IOCTL.SLAVE_SII_WRITE, data)

    def read_reg(self, position, reg_type, address):
        if len(reg_type)>1:
            reg_type = c_struct_map[reg_type].get_format()

        reg_len = struct.calcsize(reg_type)
        buf = ctypes.create_string_buffer(reg_len)

        slave_reg = self.new_struct('slave_reg_t')
        slave_reg.slave_position = position
        slave_reg.emergency = 0
        slave_reg.address = address
        slave_reg.size = reg_len
        slave_reg.data = ctypes.addressof(buf)

        self._ioctl(EC_IOCTL.SLAVE_REG_READ, slave_reg)
        
        return struct.unpack_from(reg_type,buf)[0]

    def write_reg(self, position, reg_type, address, data):
        if len(reg_type)>1:
            reg_type = c_struct_map[reg_type].get_format()

        reg_len = struct.calcsize(reg_type)
        buf = ctypes.create_string_buffer(reg_len)

        struct.pack_into(reg_type, buf, 0, data)

        slave_reg = self.new_struct('slave_reg_t')
        slave_reg.slave_position = position
        slave_reg.emergency = 0
        slave_reg.address = address
        slave_reg.size = reg_len
        slave_reg.data = ctypes.addressof(buf)

        self._ioctl(EC_IOCTL.SLAVE_REG_WRITE, slave_reg)

    FOE_BUSY               = 0
    FOE_READY              = 1
    FOE_IDLE               = 2
    FOE_WC_ERROR           = 3
    FOE_RECEIVE_ERROR      = 4
    FOE_PROT_ERROR         = 5
    FOE_NODATA_ERROR       = 6
    FOE_PACKETNO_ERROR     = 7
    FOE_OPCODE_ERROR       = 8
    FOE_TIMEOUT_ERROR      = 9
    FOE_SEND_RX_DATA_ERROR = 10
    FOE_RX_DATA_ACK_ERROR  = 11
    FOE_ACK_ERROR          = 12
    FOE_MBOX_FETCH_ERROR   = 13
    FOE_READ_NODATA_ERROR  = 14
    FOE_MBOX_PROT_ERROR    = 15

    def read_foe(self, position, slave_path, offset=0):
        """
        Read an FoE file at the specified absolute ring position, path
        and offset. Retuns the file contents as a bytearray. Will throw on
        error.
        """
        buf = ctypes.create_string_buffer(32768)

        slave_foe = self.new_struct('slave_foe_t')
        slave_foe.slave_position = position
        slave_foe.offset = offset
        slave_foe.buffer_size = len(buf)
        slave_foe.buffer = ctypes.addressof(buf)
        slave_foe.file_name = slave_path

        try:
            res = self._ioctl(EC_IOCTL.SLAVE_FOE_READ, slave_foe)
            return bytearray(buf[:res.data_size])
        except MasterDeviceError as ex:
            if ex[1].result == self.FOE_OPCODE_ERROR:
                raise MasterDeviceError("FoE read aborted with error code " + 
                    hex(ex[1].error_code))
            else:
                raise MasterDeviceError("FoE Read Failed with %d" % 
                    ex[1].result)

    def write_foe(self, position, slave_path, data, offset=0):
        """
        Write an FoE file at the specified absolute ring position, path
        and offset. Data must be convertable to a string of the correct size.
        """
        buf = ctypes.create_string_buffer(str(data))

        slave_foe = self.new_struct('slave_foe_t')
        slave_foe.slave_position = position
        slave_foe.offset = offset
        slave_foe.buffer_size = len(buf)
        slave_foe.buffer = ctypes.addressof(buf)
        slave_foe.file_name = slave_path

        try:
            self._ioctl(EC_IOCTL.SLAVE_FOE_WRITE, slave_foe)
        except MasterDeviceError as ex:
            if ex[1].result == self.FOE_OPCODE_ERROR:
                raise MasterDeviceError("FoE write aborted with error code " + 
                    hex(ex[1].error_code))
            else:
                raise MasterDeviceError("FoE write Failed with %d" % 
                    ex[1].result)

    def set_debug(self, level):
        self._ioctl(EC_IOCTL.MASTER_DEBUG, level)

    def rescan(self):
        self._ioctl(EC_IOCTL.MASTER_RESCAN, 0)

    def sdo_download(self, position, sdo_type, sdo_index, sdo_subindex, data):
        """ Send SDO data to slave """
        if len(sdo_type)>1:
            sdo_type = c_struct_map[sdo_type].get_format()

        sdo_len = struct.calcsize(sdo_type)
        buf = ctypes.create_string_buffer(sdo_len)

        struct.pack_into(sdo_type, buf, 0, data)

        data = self.new_struct('slave_sdo_download_t')
        data.slave_position = position
        data.sdo_index = sdo_index
        data.sdo_entry_subindex = sdo_subindex
        data.complete_access = 0  # not supported
        data.data_size = sdo_len
        data.data = ctypes.addressof(buf)

        self._ioctl(EC_IOCTL.SLAVE_SDO_DOWNLOAD, data)

    def sdo_upload(self, position, sdo_type, sdo_index, sdo_subindex):
        """ get SDO data from slave """
        if len(sdo_type)>1:
            sdo_type = c_struct_map[sdo_type].get_format()

        sdo_len = struct.calcsize(sdo_type)
        buf = ctypes.create_string_buffer(sdo_len)

        data = self.new_struct('slave_sdo_download_t')
        data.slave_position = position
        data.sdo_index = sdo_index
        data.sdo_entry_subindex = sdo_subindex
        data.complete_access = 0  # not supported
        data.data_size = sdo_len
        data.data = ctypes.addressof(buf)

        self._ioctl(EC_IOCTL.SLAVE_SDO_UPLOAD, data)

        return struct.unpack_from(sdo_type,buf)[0]

    def request_state(self, position, al_state):
        slave_state = self.new_struct('slave_state_t')
        slave_state.slave_position = position
        slave_state.al_state = al_state

        self._ioctl(EC_IOCTL.SLAVE_STATE, slave_state)

if __name__ == "__main__":
    with MasterDevice(0) as m:
        #m.set_debug(1)

        master = m.get_master()
        print master

        for i in xrange(master.slave_count):
            print '** Slave',i
            print m.get_slave(i)

        #print 'read foe',' '.join('%02X'%x for x in m.read_foe(1,'/chan1/mem/0'))

        #m.write_foe(1,'/chan1/mem/0', "Hello, world!".ljust(256,'\0'))

        print hex(m.read_reg(1, 'uint32_t', 0))

        #m.request_state(0,0)

        #print m.get_config(0)
        #print m.get_config_pdo(1, 0, 0)
        #print m.get_config_pdo_entry(1, 0, 0, 0)
        #print m.get_config_sdo(1, 0)

        #print m.get_sdo(1, 0)
        #print m.get_sdo_entry(1, 0x9200, 1)

        #print ' '.join('%02X'%x for x in m.read_sii(1))
        
        print hex(m.sdo_upload(1, 'uint32_t', 0x1a06, 1))
-------------- next part --------------
A non-text attachment was scrubbed...
Name: dave_page.vcf
Type: text/x-vcard
Size: 313 bytes
Desc: not available
URL: <http://lists.etherlab.org/pipermail/etherlab-dev/attachments/20140223/c8b736a0/attachment-0001.vcf>


More information about the etherlab-dev mailing list