[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: 314 bytes
Desc: not available
URL: <http://lists.etherlab.org/pipermail/etherlab-dev/attachments/20140223/c8b736a0/attachment-0003.vcf>
More information about the Etherlab-dev
mailing list