Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
E
ebpfCAT
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Build
Pipelines
Jobs
Pipeline schedules
Artifacts
Deploy
Releases
Package Registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
karaboDevices
ebpfCAT
Commits
4d5ddc4e
Commit
4d5ddc4e
authored
4 years ago
by
Martin Teichmann
Browse files
Options
Downloads
Patches
Plain Diff
add some documentation
parent
cd579568
No related branches found
No related tags found
No related merge requests found
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
ebpfcat.py
+8
-52
8 additions, 52 deletions
ebpfcat.py
ethercat.py
+97
-9
97 additions, 9 deletions
ethercat.py
with
105 additions
and
61 deletions
ebpfcat.py
+
8
−
52
View file @
4d5ddc4e
"""
The high-level API for EtherCAT loops
"""
from
asyncio
import
ensure_future
,
gather
,
sleep
from
asyncio
import
ensure_future
,
gather
,
sleep
from
struct
import
pack
,
unpack
,
calcsize
,
pack_into
,
unpack_from
from
struct
import
pack
,
unpack
,
calcsize
,
pack_into
,
unpack_from
from
time
import
time
from
time
import
time
...
@@ -89,6 +90,11 @@ class DeviceVar(ArrayGlobalVarDesc):
...
@@ -89,6 +90,11 @@ class DeviceVar(ArrayGlobalVarDesc):
class
Device
(
SubProgram
):
class
Device
(
SubProgram
):
"""
A device is a functional unit in an EtherCAT loop
A device aggregates data coming in and going to terminals
to serve a common goal. A terminal may be used by several
devices.
"""
def
get_terminals
(
self
):
def
get_terminals
(
self
):
ret
=
set
()
ret
=
set
()
for
pv
in
self
.
__dict__
.
values
():
for
pv
in
self
.
__dict__
.
values
():
...
@@ -194,6 +200,8 @@ class FastEtherCat(EtherCat):
...
@@ -194,6 +200,8 @@ class FastEtherCat(EtherCat):
class
SyncGroup
:
class
SyncGroup
:
"""
A group of devices communicating at the same time
"""
packet_index
=
1000
packet_index
=
1000
current_data
=
False
# None is used to indicate FastSyncGroup
current_data
=
False
# None is used to indicate FastSyncGroup
...
@@ -259,55 +267,3 @@ class FastSyncGroup(XDP):
...
@@ -259,55 +267,3 @@ class FastSyncGroup(XDP):
self
.
ec
.
send_packet
(
self
.
packet
.
assemble
(
index
))
self
.
ec
.
send_packet
(
self
.
packet
.
assemble
(
index
))
self
.
monitor
=
ensure_future
(
gather
(
*
[
t
.
to_operational
()
self
.
monitor
=
ensure_future
(
gather
(
*
[
t
.
to_operational
()
for
t
in
self
.
terminals
]))
for
t
in
self
.
terminals
]))
def
script
():
fd
=
create_map
(
MapType
.
HASH
,
4
,
4
,
7
)
update_elem
(
fd
,
b
"
AAAA
"
,
b
"
BBBB
"
,
0
)
e
=
EBPF
(
ProgType
.
XDP
,
"
GPL
"
)
e
.
r1
=
e
.
get_fd
(
fd
)
e
.
r2
=
e
.
r10
e
.
r2
+=
-
8
e
.
m32
[
e
.
r10
-
8
]
=
0x41414141
e
.
call
(
1
)
with
e
.
If
(
e
.
r0
!=
0
):
e
.
r1
=
e
.
get_fd
(
fd
)
e
.
r2
=
e
.
r10
e
.
r2
+=
-
8
e
.
r3
=
e
.
m32
[
e
.
r0
]
e
.
r3
-=
1
e
.
m32
[
e
.
r10
-
16
]
=
e
.
r3
e
.
r3
=
e
.
r10
e
.
r3
+=
-
16
e
.
r4
=
0
e
.
call
(
2
)
e
.
r0
=
2
# XDP_PASS
e
.
exit
()
return
fd
,
e
async
def
logger
(
map_fd
):
lasttime
=
time
()
lastno
=
0x42424242
while
True
:
r
=
lookup_elem
(
map_fd
,
b
"
AAAA
"
,
4
)
no
,
=
unpack
(
"
i
"
,
r
)
t
=
time
()
print
(
f
"
L
{
no
:
7
}
{
lastno
-
no
:
7
}
{
t
-
lasttime
:
7.3
f
}
{
(
lastno
-
no
)
/
(
t
-
lasttime
)
:
7.1
f
}
"
)
lasttime
=
t
lastno
=
no
await
sleep
(
0.1
)
async
def
install_ebpf
(
network
):
map_fd
,
e
=
script
()
fd
,
disas
=
e
.
load
(
log_level
=
1
)
print
(
disas
)
prog_test_run
(
fd
,
512
,
512
,
512
,
512
,
repeat
=
10
)
ensure_future
(
logger
(
map_fd
))
await
set_link_xdp_fd
(
"
eth0
"
,
fd
)
return
map_fd
if
__name__
==
"
__main__
"
:
from
asyncio
import
get_event_loop
loop
=
get_event_loop
()
loop
.
run_until_complete
(
install_ebpf
(
"
eth0
"
))
This diff is collapsed.
Click to expand it.
ethercat.py
+
97
−
9
View file @
4d5ddc4e
"""
Low-level access to EtherCAT
this modules contains the code to actually talk to EtherCAT terminals.
"""
from
asyncio
import
ensure_future
,
Event
,
Future
,
gather
,
get_event_loop
,
Protocol
,
Queue
from
asyncio
import
ensure_future
,
Event
,
Future
,
gather
,
get_event_loop
,
Protocol
,
Queue
from
enum
import
Enum
from
enum
import
Enum
from
random
import
randint
from
random
import
randint
...
@@ -115,6 +119,12 @@ def datasize(args, data):
...
@@ -115,6 +119,12 @@ def datasize(args, data):
class
Packet
:
class
Packet
:
"""
An EtherCAT packet representation
A packet contains one or more datagrams which are sent as EtherNet
packets. We implicitly add a datagram in the front which later serves
as an identifier for the packet.
"""
MAXSIZE
=
1000
# maximum size we use for an EtherCAT packet
MAXSIZE
=
1000
# maximum size we use for an EtherCAT packet
ETHERNET_HEADER
=
14
ETHERNET_HEADER
=
14
DATAGRAM_HEADER
=
10
DATAGRAM_HEADER
=
10
...
@@ -125,10 +135,27 @@ class Packet:
...
@@ -125,10 +135,27 @@ class Packet:
self
.
size
=
14
self
.
size
=
14
def
append
(
self
,
cmd
,
data
,
idx
,
*
address
):
def
append
(
self
,
cmd
,
data
,
idx
,
*
address
):
"""
Append a datagram to the packet
:param cmd: EtherCAT command
:type cmd: ECCmd
:param data: the data in the datagram
:param idx: the datagram index, unchanged by terminals
Depending on the command, one or two more parameters represent the
address, either terminal and offset for position or node addressing,
or one value for logical addressing.
"""
self
.
data
.
append
((
cmd
,
data
,
idx
)
+
address
)
self
.
data
.
append
((
cmd
,
data
,
idx
)
+
address
)
self
.
size
+=
len
(
data
)
+
self
.
DATAGRAM_HEADER
+
self
.
DATAGRAM_TAIL
self
.
size
+=
len
(
data
)
+
self
.
DATAGRAM_HEADER
+
self
.
DATAGRAM_TAIL
def
assemble
(
self
,
index
):
def
assemble
(
self
,
index
):
"""
Assemble the datagrams into a packet
:param index: an identifier for the packet
An implicit empty datagram is added at the beginning of the packet
that may be used as an identifier for the packet.
"""
ret
=
[
pack
(
"
<HBBiHHH
"
,
self
.
size
|
0x1000
,
0
,
0
,
index
,
1
<<
15
,
0
,
0
)]
ret
=
[
pack
(
"
<HBBiHHH
"
,
self
.
size
|
0x1000
,
0
,
0
,
index
,
1
<<
15
,
0
,
0
)]
for
i
,
(
cmd
,
data
,
*
dgram
)
in
enumerate
(
self
.
data
,
start
=
1
):
for
i
,
(
cmd
,
data
,
*
dgram
)
in
enumerate
(
self
.
data
,
start
=
1
):
ret
.
append
(
pack
(
"
<BBhHHH
"
if
len
(
dgram
)
==
3
else
"
<BBiHH
"
,
ret
.
append
(
pack
(
"
<BBhHHH
"
if
len
(
dgram
)
==
3
else
"
<BBiHH
"
,
...
@@ -154,27 +181,37 @@ class Packet:
...
@@ -154,27 +181,37 @@ class Packet:
return
''
.
join
(
f
"
{
i
}
:
{
c
}
{
f
}
{
d
}
\n
"
for
i
,
(
c
,
d
,
f
)
in
enumerate
(
ret
))
return
''
.
join
(
f
"
{
i
}
:
{
c
}
{
f
}
{
d
}
\n
"
for
i
,
(
c
,
d
,
f
)
in
enumerate
(
ret
))
def
full
(
self
):
def
full
(
self
):
return
self
.
size
>
self
.
MAXSIZE
"""
Is the data limit reached?
"""
return
self
.
size
>
self
.
MAXSIZE
or
len
(
self
.
data
)
>
14
class
AsyncBase
:
class
EtherCat
(
Protocol
):
async
def
__new__
(
cls
,
*
args
,
**
kwargs
):
"""
The EtherCAT connection
ret
=
super
().
__new__
(
cls
)
await
ret
.
__init__
(
*
args
,
**
kwargs
)
return
ret
An object of this class represents one connection to an EtherCAT loop.
It keeps the socket, and eventually all data flows through it.
class
EtherCat
(
Protocol
):
This class supports both to send individual datagrams and wait for their
response, but also to send and receive entire packets.
"""
def
__init__
(
self
,
network
):
def
__init__
(
self
,
network
):
"""
:param network: the name of the network adapter, like
"
eth0
"
"""
self
.
addr
=
(
network
,
0x88A4
,
0
,
0
,
b
"
\xff\xff\xff\xff\xff\xff
"
)
self
.
addr
=
(
network
,
0x88A4
,
0
,
0
,
b
"
\xff\xff\xff\xff\xff\xff
"
)
self
.
send_queue
=
Queue
()
self
.
send_queue
=
Queue
()
self
.
wait_futures
=
{}
self
.
wait_futures
=
{}
async
def
connect
(
self
):
async
def
connect
(
self
):
"""
connect to the EtherCAT loop
"""
await
get_event_loop
().
create_datagram_endpoint
(
await
get_event_loop
().
create_datagram_endpoint
(
lambda
:
self
,
family
=
AF_PACKET
,
proto
=
0xA488
)
lambda
:
self
,
family
=
AF_PACKET
,
proto
=
0xA488
)
async
def
sendloop
(
self
):
async
def
sendloop
(
self
):
"""
the eternal datagram sending loop
This method runs while we are connected, takes the datagrams
to be sent from a queue, packs them in a packet and ships them
out.
"""
packet
=
Packet
()
packet
=
Packet
()
dgrams
=
[]
dgrams
=
[]
while
True
:
while
True
:
...
@@ -190,6 +227,10 @@ class EtherCat(Protocol):
...
@@ -190,6 +227,10 @@ class EtherCat(Protocol):
packet
=
Packet
()
packet
=
Packet
()
async
def
roundtrip_packet
(
self
,
packet
):
async
def
roundtrip_packet
(
self
,
packet
):
"""
Send a packet and return the response
Send the `packet` to the loop and wait that it comes back,
and return that to the caller.
"""
index
=
randint
(
2000
,
1000000000
)
index
=
randint
(
2000
,
1000000000
)
while
index
in
self
.
wait_futures
:
while
index
in
self
.
wait_futures
:
index
=
randint
(
2000
,
1000000000
)
index
=
randint
(
2000
,
1000000000
)
...
@@ -197,6 +238,7 @@ class EtherCat(Protocol):
...
@@ -197,6 +238,7 @@ class EtherCat(Protocol):
return
await
self
.
receive_index
(
index
)
return
await
self
.
receive_index
(
index
)
async
def
receive_index
(
self
,
index
):
async
def
receive_index
(
self
,
index
):
"""
Wait for packet identified by `index`
"""
future
=
Future
()
future
=
Future
()
self
.
wait_futures
[
index
]
=
future
self
.
wait_futures
[
index
]
=
future
try
:
try
:
...
@@ -205,9 +247,24 @@ class EtherCat(Protocol):
...
@@ -205,9 +247,24 @@ class EtherCat(Protocol):
del
self
.
wait_futures
[
index
]
del
self
.
wait_futures
[
index
]
def
send_packet
(
self
,
packet
):
def
send_packet
(
self
,
packet
):
"""
simply send the `packet`, fire-and-forget
"""
self
.
transport
.
sendto
(
packet
,
self
.
addr
)
self
.
transport
.
sendto
(
packet
,
self
.
addr
)
async
def
roundtrip
(
self
,
cmd
,
pos
,
offset
,
*
args
,
data
=
None
,
idx
=
0
):
async
def
roundtrip
(
self
,
cmd
,
pos
,
offset
,
*
args
,
data
=
None
,
idx
=
0
):
"""
Send a datagram and wait for its response
:param cmd: the EtherCAT command
:type cmd: ECCmd
:param pos: the positional address of the terminal
:param offset: the offset within the terminal
:param idx: the EtherCAT datagram index
:param data: the data to be sent, or and integer for the number of
zeros to be sent as placeholder
Any additional parameters will be interpreted as follows: every `str` is
interpreted as a format for a `struct.pack`, everything else is the data
for those format. Upon returning, the received data will be unpacked
accoding to the format strings.
"""
future
=
Future
()
future
=
Future
()
fmt
=
"
<
"
+
""
.
join
(
arg
for
arg
in
args
[:
-
1
]
if
isinstance
(
arg
,
str
))
fmt
=
"
<
"
+
""
.
join
(
arg
for
arg
in
args
[:
-
1
]
if
isinstance
(
arg
,
str
))
out
=
pack
(
fmt
,
*
[
arg
for
arg
in
args
if
not
isinstance
(
arg
,
str
)])
out
=
pack
(
fmt
,
*
[
arg
for
arg
in
args
if
not
isinstance
(
arg
,
str
)])
...
@@ -230,17 +287,31 @@ class EtherCat(Protocol):
...
@@ -230,17 +287,31 @@ class EtherCat(Protocol):
return
ret
return
ret
def
connection_made
(
self
,
transport
):
def
connection_made
(
self
,
transport
):
"""
start the send loop once the connection is made
"""
transport
.
get_extra_info
(
"
socket
"
).
bind
(
self
.
addr
)
transport
.
get_extra_info
(
"
socket
"
).
bind
(
self
.
addr
)
self
.
transport
=
transport
self
.
transport
=
transport
ensure_future
(
self
.
sendloop
())
ensure_future
(
self
.
sendloop
())
def
datagram_received
(
self
,
data
,
addr
):
def
datagram_received
(
self
,
data
,
addr
):
"""
distribute received packets to the recipients
"""
index
,
=
unpack
(
"
<I
"
,
data
[
4
:
8
])
index
,
=
unpack
(
"
<I
"
,
data
[
4
:
8
])
self
.
wait_futures
[
index
].
set_result
(
data
)
self
.
wait_futures
[
index
].
set_result
(
data
)
class
Terminal
:
class
Terminal
:
"""
Represent one terminal (
"
slave
"
) in the loop
"""
async
def
initialize
(
self
,
relative
,
absolute
):
async
def
initialize
(
self
,
relative
,
absolute
):
"""
Initialize the terminal
this sets up the connection to the terminal we represent.
:param relative: the position of the terminal in the loop,
a negative number counted down from 0 for the first terminal
:param absolute: the number used to identify the terminal henceforth
This also reads the EEPROM and sets up the sync manager as defined
therein. It still leaves the terminal in the init state.
"""
await
self
.
ec
.
roundtrip
(
ECCmd
.
APWR
,
relative
,
0x10
,
"
H
"
,
absolute
)
await
self
.
ec
.
roundtrip
(
ECCmd
.
APWR
,
relative
,
0x10
,
"
H
"
,
absolute
)
self
.
position
=
absolute
self
.
position
=
absolute
...
@@ -300,16 +371,23 @@ class Terminal:
...
@@ -300,16 +371,23 @@ class Terminal:
parse_pdo
(
self
.
eeprom
[
51
])
parse_pdo
(
self
.
eeprom
[
51
])
async
def
set_state
(
self
,
state
):
async
def
set_state
(
self
,
state
):
"""
try to set the state, and return the new state
"""
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPWR
,
self
.
position
,
0x0120
,
"
H
"
,
state
)
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPWR
,
self
.
position
,
0x0120
,
"
H
"
,
state
)
ret
,
=
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPRD
,
self
.
position
,
0x0130
,
"
H
"
)
ret
,
=
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPRD
,
self
.
position
,
0x0130
,
"
H
"
)
return
ret
return
ret
async
def
get_state
(
self
):
async
def
get_state
(
self
):
"""
get the current state
"""
ret
,
=
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPRD
,
self
.
position
,
0x0130
,
"
H
"
)
ret
,
=
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPRD
,
self
.
position
,
0x0130
,
"
H
"
)
return
ret
return
ret
async
def
to_operational
(
self
):
async
def
to_operational
(
self
):
"""
try to bring the terminal to operational state
"""
"""
try to bring the terminal to operational state
this tries to push the terminal through its state machine to the
operational state. Note that even if it reaches there, the terminal
will quickly return to pre-operational if no packets are sent to keep
it operational.
"""
order
=
[
1
,
2
,
4
,
8
]
order
=
[
1
,
2
,
4
,
8
]
ret
,
error
=
await
self
.
ec
.
roundtrip
(
ret
,
error
=
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPRD
,
self
.
position
,
0x0130
,
"
H2xH
"
)
ECCmd
.
FPRD
,
self
.
position
,
0x0130
,
"
H2xH
"
)
...
@@ -326,23 +404,31 @@ class Terminal:
...
@@ -326,23 +404,31 @@ class Terminal:
while
s
!=
state
:
while
s
!=
state
:
s
,
error
=
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPRD
,
self
.
position
,
s
,
error
=
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPRD
,
self
.
position
,
0x0130
,
"
H2xH
"
)
0x0130
,
"
H2xH
"
)
print
(
'
State
'
,
self
.
position
,
s
,
error
)
if
error
!=
0
:
if
error
!=
0
:
raise
RuntimeError
(
f
"
AL register
{
error
}
"
)
raise
RuntimeError
(
f
"
AL register
{
error
}
"
)
async
def
get_error
(
self
):
async
def
get_error
(
self
):
"""
read the error register
"""
return
(
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPRD
,
self
.
position
,
return
(
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPRD
,
self
.
position
,
0x0134
,
"
H
"
))[
0
]
0x0134
,
"
H
"
))[
0
]
async
def
read
(
self
,
start
,
*
args
,
**
kwargs
):
async
def
read
(
self
,
start
,
*
args
,
**
kwargs
):
"""
read data from the terminal at offset `start`
see `EtherCat.roundtrip` for details on more parameters.
"""
return
(
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPRD
,
self
.
position
,
return
(
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPRD
,
self
.
position
,
start
,
*
args
,
**
kwargs
))
start
,
*
args
,
**
kwargs
))
async
def
write
(
self
,
start
,
*
args
,
**
kwargs
):
async
def
write
(
self
,
start
,
*
args
,
**
kwargs
):
"""
write data from the terminal at offset `start`
see `EtherCat.roundtrip` for details on more parameters
"""
return
(
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPWR
,
self
.
position
,
return
(
await
self
.
ec
.
roundtrip
(
ECCmd
.
FPWR
,
self
.
position
,
start
,
*
args
,
**
kwargs
))
start
,
*
args
,
**
kwargs
))
async
def
eeprom_read_one
(
self
,
start
):
async
def
eeprom_read_one
(
self
,
start
):
"""
read 8 bytes from the eeprom at start
"""
"""
read 8 bytes from the eeprom at
`
start
`
"""
while
(
await
self
.
read
(
0x502
,
"
H
"
))[
0
]
&
0x8000
:
while
(
await
self
.
read
(
0x502
,
"
H
"
))[
0
]
&
0x8000
:
pass
pass
await
self
.
write
(
0x502
,
"
HI
"
,
0x100
,
start
)
await
self
.
write
(
0x502
,
"
HI
"
,
0x100
,
start
)
...
@@ -373,6 +459,7 @@ class Terminal:
...
@@ -373,6 +459,7 @@ class Terminal:
eeprom
[
hd
]
=
await
get_data
(
ws
*
2
)
eeprom
[
hd
]
=
await
get_data
(
ws
*
2
)
async
def
mbx_send
(
self
,
type
,
*
args
,
data
=
None
,
address
=
0
,
priority
=
0
,
channel
=
0
):
async
def
mbx_send
(
self
,
type
,
*
args
,
data
=
None
,
address
=
0
,
priority
=
0
,
channel
=
0
):
"""
send data to the mailbox
"""
status
,
=
await
self
.
read
(
0x805
,
"
B
"
)
# always using mailbox 0, OK?
status
,
=
await
self
.
read
(
0x805
,
"
B
"
)
# always using mailbox 0, OK?
if
status
&
8
:
if
status
&
8
:
raise
RuntimeError
(
"
mailbox full, read first
"
)
raise
RuntimeError
(
"
mailbox full, read first
"
)
...
@@ -385,6 +472,7 @@ class Terminal:
...
@@ -385,6 +472,7 @@ class Terminal:
self
.
mbx_cnt
=
self
.
mbx_cnt
%
7
+
1
# yes, we start at 1 not 0
self
.
mbx_cnt
=
self
.
mbx_cnt
%
7
+
1
# yes, we start at 1 not 0
async
def
mbx_recv
(
self
):
async
def
mbx_recv
(
self
):
"""
receive data from the mailbox
"""
status
=
0
status
=
0
while
status
&
8
==
0
:
while
status
&
8
==
0
:
status
,
=
await
self
.
read
(
0x80D
,
"
B
"
)
# always using mailbox 1, OK?
status
,
=
await
self
.
read
(
0x80D
,
"
B
"
)
# always using mailbox 1, OK?
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment