Skip to content

Commit

Permalink
canpy: First prototype of can communication handler implemented
Browse files Browse the repository at this point in the history
  • Loading branch information
Stefan Hölzl committed Jun 11, 2016
1 parent e39358a commit aae7b66
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 13 deletions.
48 changes: 48 additions & 0 deletions canpy/can_communication_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
__author__ = "Stefan Hölzl"


class CANCommunicationHandler(object):
"""Takes care of sending and receiving CAN-Messages.
class CANInterfaceProtocol(object):
def __init__(self):
def register_receiving_message(self, can_id):
def register_receive_callback(self, callback):
def initialize(self, speed):
def send_message(self, can_id, data):
"""
def __init__(self, can_network, thread_register_callback):
self._can_network = can_network
self._thread_register_callback = thread_register_callback
self._registered_messages = {}

def initialize(self):
msgs_by_cycle_time = {}
for can_id in self._registered_messages:
msg = self._can_network.get_message(can_id)
if 'cyclic' in msg.attributes['GenMsgSendType'].value and msg.attributes['GenMsgCycleTime'].value > 0:
cycle_time = msg.attributes['GenMsgCycleTime'].value
if cycle_time not in msgs_by_cycle_time.keys():
msgs_by_cycle_time[cycle_time] = []
msgs_by_cycle_time[cycle_time].append(msg)
for cycle_time, msgs in msgs_by_cycle_time.items():
callback = lambda m=msgs: self._send_messages(m)
self._thread_register_callback(cycle_time, callback)

def connect(self, node_names, can_interface):
for node_name in node_names:
node = self._can_network.nodes[node_name]
for msg in node.messages.values():
self._registered_messages[msg.can_id] = can_interface
for recv_msg in self._can_network.get_consumed_messages(node):
can_interface.register_receiving_message(recv_msg.can_id)

def _send_messages(self, msgs):
for msg in msgs:
can_interface = self._registered_messages[msg.can_id]
if 'IfActive' not in msg.attributes['GenMsgSendType'].value:
can_interface.send_message(msg.can_id, int(msg))
elif msg.is_active:
can_interface.send_message(msg.can_id, int(msg))

18 changes: 13 additions & 5 deletions canpy/can_objects/can_attribute.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ def _check_attribute_for_default_value(self, attribute_name, object_to_check=Non
default = CANAttribute(object_to_check.attributes.definitions[attribute_name],
value=object_to_check.attributes.definitions[attribute_name].default)
return default
if object_to_check.parent:
return self._check_attribute_for_default_value(attribute_name, object_to_check.parent)
raise KeyError('No default definition for this attribute')

def add(self, attribute):
Expand All @@ -44,7 +46,7 @@ def __len__(self):
def __getitem__(self, item):
lookup_chain = [lambda: self._attributes[item],
lambda: self._check_attribute_for_default_value(item),
lambda: self._check_attribute_for_default_value(item, self._can_object.parent)]
]
for look_up_item in lookup_chain:
try:
return look_up_item()
Expand Down Expand Up @@ -152,10 +154,16 @@ def check_value(self, value):
return True

def cast(self, value):
value = int(value)
if value < 0:
raise AttributeError('Negative enum index not allowed')
return self.values[value]
try:
value = int(value)
if value < 0:
raise IndexError('Negative enum index not allowed')
return self.values[value]
except ValueError:
value = str(value)
if value in self.values:
return value
raise AttributeError('Value not in enum')


class CANFloatAttributeDefinition(CANAttributeDefinition):
Expand Down
1 change: 1 addition & 0 deletions canpy/can_objects/can_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ def __init__(self, can_id, name, length):
self.can_id = can_id
self.name = name
self.length = length
self.is_active = True

self.sender = None

Expand Down
13 changes: 13 additions & 0 deletions canpy/can_objects/can_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,16 @@ def get_signal(self, can_id, name):
if len(signals) == 0:
return None
return signals[0]

def get_consumed_messages(self, node):
"""Returns a list with all messages which contains a signals which is consumed by the given node
Args:
node: Node for which you want the messages
Returns:
List of consumed messages
"""
return [msg for n in self.nodes.values()
for msg in n.messages.values()
for sig in msg.signals.values()
if node in sig.receiver]
3 changes: 3 additions & 0 deletions canpy/can_objects/can_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ def __init__(self):
pass

# Protoclol definitions
def __bool__(self):
return False

def __eq__(self, other):
if other is None:
return True
Expand Down
2 changes: 2 additions & 0 deletions canpy/interfaces/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
__author__ = "Stefan Hölzl"

2 changes: 1 addition & 1 deletion docs/DBC_Specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ Supported types:
* none
Definition: `BA_DEF BO_ "GenMsgSendType" ENUM "cyclic","triggered","cyclicIfActive","cyclicAndTriggered","cyclicIfActiveAndTriggered","none"`
Default: none
Definition: `BA_DEF_DEF "GenMsgCycleTime" "none"`
Definition: `BA_DEF_DEF "GenMsgSendType" "none"`

### GenMsgCycleTime
Defines the cycle time of a message in ms.
Expand Down
34 changes: 27 additions & 7 deletions tests/test_can_objects.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,27 @@ def test_get_none_signal(self):
assert cn.get_signal(can_id=1234, name='Signal') == None

def test_add_attribute_definition(self):
cb = CANNetwork()
cn = CANNetwork()
cad = CANAttributeDefinition('Test', CANSignal)
cb.attributes.add_definition(cad)
assert 'Test' in cb.attributes.definitions
cn.attributes.add_definition(cad)
assert 'Test' in cn.attributes.definitions

def test_add_value_dict(self):
cb = CANNetwork()
cn = CANNetwork()
value_dict = {0: 'Val0'}
cb.add_value_dict('ValueDict', value_dict)
assert len(cb.value_dicts) == 1
assert 'ValueDict' in cb.value_dicts
cn.add_value_dict('ValueDict', value_dict)
assert len(cn.value_dicts) == 1
assert 'ValueDict' in cn.value_dicts

def test_get_consumed_messages(self):
cn = CANNetwork()
cn.add_node(CANNode('SendingNode'))
cn.add_node(CANNode('ReceivingNode'))
cn.nodes['SendingNode'].add_message(CANMessage(1, 'Message', 1))
sig = CANSignal('Signal', 0, 8)
sig.add_receiver(cn.nodes['ReceivingNode'])
cn.nodes['SendingNode'].messages[1].add_signal(sig)
assert cn.nodes['SendingNode'].messages[1] in cn.get_consumed_messages(cn.nodes['ReceivingNode'])


class TestCANNode(object):
Expand Down Expand Up @@ -335,10 +345,14 @@ def __str__(self):
'EnumAttribute', 2, True),
(CANEnumAttributeDefinition('EnumAttribute', CANObject, ['Val0', 'Val1', 'Val2', 'Val3']),
'EnumAttribute', 3, True),
(CANEnumAttributeDefinition('EnumAttribute', CANObject, ['Val0', 'Val1', 'Val2', 'Val3']),
'EnumAttribute', 'Val0', True),
(CANEnumAttributeDefinition('EnumAttribute', CANObject, ['Val0', 'Val1', 'Val2', 'Val3']),
'EnumAttribute', -1, False),
(CANEnumAttributeDefinition('EnumAttribute', CANObject, ['Val0', 'Val1', 'Val2', 'Val3']),
'EnumAttribute', 4, False),
(CANEnumAttributeDefinition('EnumAttribute', CANObject, ['Val0', 'Val1', 'Val2', 'Val3']),
'EnumAttribute', 'Val4', False),
])
def test_check(self, attr_definition, expected_name, test_value, expected_result):
assert attr_definition.name == expected_name
Expand Down Expand Up @@ -395,12 +409,18 @@ def test_add_attributes(self):
assert co.attributes['Name'] == ca

def test_lookup_chain(self):
grandparent_co = CANObject()
parent_co = CANObject()
co = CANObject()
grandparent_co.add_child(parent_co)
parent_co.add_child(co)
with pytest.raises(KeyError):
co.attributes['Attribute']

grandparent_co.attributes.add_definition(CANStringAttributeDefinition('Attribute', CANObject,
default='GrandParentDefault'))
assert co.attributes['Attribute'].value == 'GrandParentDefault'

parent_co.attributes.add_definition(CANStringAttributeDefinition('Attribute', CANObject,
default='DefaultParent'))
assert co.attributes['Attribute'].value == 'DefaultParent'
Expand Down
30 changes: 30 additions & 0 deletions tests/test_communication_handler.dbc
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
VERSION "1.0"

BU_: Node0 Node1

BS_: 500

BO_ 0 CANMessage0: 8 Node0
SG_ Signal0 : 0|32@1- (1,0) [0|0] "" Node1
SG_ Signal1 : 32|32@1+ (100,0) [0|100] "%" Node1

BO_ 1 CANMessage1: 8 Node0
SG_ Signal0 : 0|32@1- (1,0) [0|0] "" Node1
SG_ Signal1 : 32|32@1+ (100,0) [0|100] "%" Node1

BO_ 2 CANMessage2: 8 Node0
SG_ Signal0 : 0|32@1- (1,0) [0|0] "" Node1
SG_ Signal1 : 32|32@1+ (100,0) [0|100] "%" Node1

BO_ 3 CANMessage3: 8 Node0
SG_ Signal0 : 0|32@1- (1,0) [0|0] "" Node1
SG_ Signal1 : 32|32@1+ (100,0) [0|100] "%" Node1

BA_ "GenMsgSendType" BO_ 0 CANMessage0 0;
BA_ "GenMsgCycleTime" BO_ 0 CANMessage0 10;

BA_ "GenMsgSendType" BO_ 1 CANMessage1 4;
BA_ "GenMsgCycleTime" BO_ 1 CANMessage1 100;

BA_ "GenMsgSendType" BO_ 3 CANMessage3 4;
BA_ "GenMsgCycleTime" BO_ 3 CANMessage3 100;
68 changes: 68 additions & 0 deletions tests/test_communication_handler.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
__author__ = 'Stefan Hölzl'

from canpy.can_communication_handler import CANCommunicationHandler
from canpy.parser.dbc_parser import DBCParser


class CANInterfaceStub(object):
def __init__(self):
self.registered_receiving_message_ids = []
self.sent_messages = []

def register_receiving_message(self, can_id):
self.registered_receiving_message_ids.append(can_id)

def initialize(self, speed):
pass

def send_message(self, can_id, data):
self.sent_messages.append((can_id, data))


class TestCANCommunicationHandler(object):
def test_connect_nodes_to_interface_sending_messages(self):
ci = CANInterfaceStub()
cn = DBCParser().parse_file('tests/test_communication_handler.dbc')
cch = CANCommunicationHandler(cn, lambda c, t: None)
cch.connect(['Node0'], ci)
assert 0 in cch._registered_messages.keys()
assert cch._registered_messages[0] == ci

def test_connect_nodes_to_interface_receiving_messages(self):
ci = CANInterfaceStub()
cn = DBCParser().parse_file('tests/test_communication_handler.dbc')
cch = CANCommunicationHandler(cn, lambda c, t: None)
cch.connect(['Node1'], ci)
assert 0 in ci.registered_receiving_message_ids

def test_initialize_cyclic_messages(self):
ci = CANInterfaceStub()
cn = DBCParser().parse_file('tests/test_communication_handler.dbc')
callback_cylce_times = []

def callback(c, t):
nonlocal callback_cylce_times
callback_cylce_times.append(c)

cch = CANCommunicationHandler(cn, callback)
cch.connect(['Node0'], ci)
cch.initialize()
assert 10 in callback_cylce_times
assert 100 in callback_cylce_times

def test_send_messages(self):
ci = CANInterfaceStub()
cn = DBCParser().parse_file('tests/test_communication_handler.dbc')
callbacks = []

def callback(c, t):
nonlocal callbacks
callbacks.append(t)

cch = CANCommunicationHandler(cn, callback)
cch.connect(['Node0'], ci)
cch.initialize()
cn.get_message(1).is_active = False
list(map(lambda cb: cb(), callbacks))
assert len(ci.sent_messages) == 2

0 comments on commit aae7b66

Please sign in to comment.