Skip to content

Commit ed6555b

Browse files
LIN: network support (#146)
* LIN: network support * Add LIN LDF communication example
1 parent 9a56f55 commit ed6555b

4 files changed

Lines changed: 381 additions & 1 deletion

File tree

build_libicsneo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import shutil
55
import sys
66

7-
LIBICSNEO_COMMIT = 'bf6a059820e753c7672adbc01b0e8fdc58b2cfea'
7+
LIBICSNEO_COMMIT = '63c81b1c3d13bb96371611d7e2573d6e4adcb6e2'
88
LIBUSB_COMMIT = '4239bc3a50014b8e6a5a2a59df1fff3b7469543b'
99
CPUS = str(multiprocessing.cpu_count())
1010

examples/lin22.ldf

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*******************************************************/
2+
/* This is the example LDF from LIN 2.2A specification */
3+
/*******************************************************/
4+
5+
// Source: https://lin-cia.org/fileadmin/microsites/lin-cia.org/resources/documents/LIN_2.2A.pdf
6+
7+
LIN_description_file;
8+
LIN_protocol_version = "2.2";
9+
LIN_language_version = "2.2";
10+
LIN_speed = 19.2 kbps;
11+
Channel_name = "DB";
12+
13+
Nodes {
14+
Master: CEM, 5 ms, 0.1 ms;
15+
Slaves: LSM, RSM;
16+
}
17+
18+
Signals {
19+
InternalLightsRequest: 2, 0, CEM, LSM, RSM;
20+
RightIntLightsSwitch: 8, 0, RSM, CEM;
21+
LeftIntLightsSwitch: 8, 0, LSM, CEM;
22+
LSMerror: 1, 0, LSM, CEM;
23+
RSMerror: 1, 0, RSM, CEM;
24+
IntTest: 2, 0, LSM, CEM;
25+
}
26+
27+
Frames {
28+
CEM_Frm1: 0x01, CEM, 1 {
29+
InternalLightsRequest, 0;
30+
}
31+
LSM_Frm1: 0x02, LSM, 2 {
32+
LeftIntLightsSwitch, 8;
33+
}
34+
LSM_Frm2: 0x03, LSM, 1 {
35+
LSMerror, 0;
36+
IntTest, 1;
37+
}
38+
RSM_Frm1: 0x04, RSM, 2 {
39+
RightIntLightsSwitch, 8;
40+
}
41+
RSM_Frm2: 0x05, RSM, 1 {
42+
RSMerror, 0;
43+
}
44+
}
45+
46+
Event_triggered_frames {
47+
Node_Status_Event : Collision_resolver, 0x06, RSM_Frm1, LSM_Frm1;
48+
}
49+
50+
Node_attributes {
51+
RSM {
52+
LIN_protocol = "2.0";
53+
configured_NAD = 0x20;
54+
product_id = 0x4E4E, 0x4553, 1;
55+
response_error = RSMerror;
56+
P2_min = 150 ms;
57+
ST_min = 50 ms;
58+
configurable_frames {
59+
Node_Status_Event=0x000; CEM_Frm1 = 0x0001; RSM_Frm1 = 0x0002;
60+
RSM_Frm2 = 0x0003;
61+
}
62+
}
63+
LSM {
64+
LIN_protocol = "2.2";
65+
configured_NAD = 0x21;
66+
initial_NAD = 0x01;
67+
product_id = 0x4A4F, 0x4841;
68+
response_error = LSMerror;
69+
fault_state_signals = IntTest;
70+
P2_min = 150 ms;
71+
ST_min = 50 ms;
72+
configurable_frames {
73+
Node_Status_Event;
74+
CEM_Frm1;
75+
LSM_Frm1;
76+
LSM_Frm2;
77+
}
78+
}
79+
}
80+
81+
Schedule_tables {
82+
Configuration_Schedule {
83+
AssignNAD {LSM} delay 15 ms;
84+
AssignFrameIdRange {LSM, 0} delay 15 ms;
85+
AssignFrameId {RSM, CEM_Frm1} delay 15 ms;
86+
AssignFrameId {RSM, RSM_Frm1} delay 15 ms;
87+
AssignFrameId {RSM, RSM_Frm2} delay 15 ms;
88+
}
89+
Normal_Schedule {
90+
CEM_Frm1 delay 15 ms;
91+
LSM_Frm2 delay 15 ms;
92+
RSM_Frm2 delay 15 ms;
93+
Node_Status_Event delay 10 ms;
94+
}
95+
MRF_schedule {
96+
MasterReq delay 10 ms;
97+
}
98+
SRF_schedule {
99+
SlaveResp delay 10 ms;
100+
}
101+
Collision_resolver { // Keep timing of other frames if collision
102+
CEM_Frm1 delay 15 ms;
103+
LSM_Frm2 delay 15 ms;
104+
RSM_Frm2 delay 15 ms;
105+
RSM_Frm1 delay 10 ms; // Poll the RSM node
106+
CEM_Frm1 delay 15 ms;
107+
LSM_Frm2 delay 15 ms;
108+
RSM_Frm2 delay 15 ms;
109+
LSM_Frm1 delay 10 ms; // Poll the LSM node
110+
}
111+
}
112+
113+
Signal_encoding_types {
114+
Dig2Bit {
115+
logical_value, 0, "off";
116+
logical_value, 1, "on";
117+
logical_value, 2, "error";
118+
logical_value, 3, "void";
119+
}
120+
ErrorEncoding {
121+
logical_value, 0, "OK";
122+
logical_value, 1, "error";
123+
}
124+
FaultStateEncoding {
125+
logical_value, 0, "No test result";
126+
logical_value, 1, "failed";
127+
logical_value, 2, "passed";
128+
logical_value, 3, "not used";
129+
}
130+
LightEncoding {
131+
logical_value, 0, "Off";
132+
physical_value, 1, 254, 1, 100, "lux";
133+
logical_value, 255, "error";
134+
}
135+
}
136+
Signal_representation {
137+
Dig2Bit: InternalLightsRequest;
138+
ErrorEncoding: RSMerror, LSMerror;
139+
LightEncoding: RightIntLightsSwitch, LeftIntLightsSwitch;
140+
}

examples/transmit_lin_example.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
import ics
2+
import random
3+
import time
4+
5+
# Tells our custom open_device() function to do special behavior
6+
# with neoVI Server enabled so we can successfully open devices
7+
enable_use_server = True
8+
9+
def dev_name(device):
10+
# Return a friendly name of the device (ie. neoVI FIRE2 CY1234)
11+
if int("AA0000", 36) <= device.SerialNumber <= int("ZZZZZZ", 36):
12+
return device.Name + " " + ics.base36enc(device.SerialNumber)
13+
else:
14+
return device.Name + " " + str(device.SerialNumber)
15+
16+
def open_device(index=0):
17+
device = None
18+
if enable_use_server:
19+
# ics.open_device() won't open a device if we have handles open already
20+
# so we need to find them and specify which ones to connect to.
21+
devices = ics.find_devices()
22+
print("Opening Device {} (Open Client handles: {})...".format(dev_name(devices[index]), devices[index].NumberOfClients))
23+
ics.open_device(devices[index])
24+
device = devices[index]
25+
else:
26+
print("Opening Device...")
27+
device = ics.open_device()
28+
print("Opened Device %s." % dev_name(device))
29+
return device
30+
31+
def calc_checksum(msg):
32+
checksum = 0
33+
for byte in msg.Header[1:]+msg.Data:
34+
checksum += byte
35+
if (checksum > 255):
36+
checksum -= 255
37+
msg.Data += tuple([~checksum & 0xFF])
38+
39+
def transmit_lin(msg):
40+
calc_checksum(msg)
41+
msg.NumberBytesHeader = len(msg.Header)
42+
msg.NumberBytesData = len(msg.Data)
43+
try:
44+
ics.transmit_messages(device, msg)
45+
except ics.RuntimeError as ex:
46+
print(ex)
47+
raise ex
48+
49+
def prepare_message(device):
50+
msg = ics.SpyMessageJ1850()
51+
msg.StatusBitField = ics.SPY_STATUS_INIT_MESSAGE
52+
msg.Protocol = ics.SPY_PROTOCOL_LIN
53+
msg.NetworkID = ics.NETID_LIN
54+
msg.Header = (0x11, 0x11, 0x22)
55+
msg.Data = (0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x88)
56+
transmit_lin(msg)
57+
58+
def receive_lin(device):
59+
msgs, error_count = ics.get_messages(device)
60+
print("Received {} messages with {} errors.".format(len(msgs), error_count))
61+
netids = { ics.NETID_LIN: "LIN 1", ics.NETID_LIN2: "LIN 2" }
62+
for i, m in enumerate(msgs):
63+
print('\nMessage #{}: '.format(i+1), end='')
64+
netid = netids.get(m.NetworkID)
65+
if netid is not None:
66+
print('{}'.format(netid), end='')
67+
if (m.StatusBitField & ics.SPY_STATUS_INIT_MESSAGE):
68+
print(' | Init', end='')
69+
data_list = []
70+
for x in m.Header[1:]:
71+
data_list.append(x)
72+
for y in m.Data:
73+
data_list.append(y)
74+
print('\nID: {} | '.format(hex(m.Header[0])), end='')
75+
if len(data_list):
76+
checksum = data_list.pop()
77+
print('Data: {} | '.format([hex(x) for x in data_list]), end='')
78+
print('Checksum: {}'.format(hex(checksum)))
79+
80+
if __name__ == "__main__":
81+
import time
82+
# Lets figure out how many are connected to the PC and display it
83+
connected_count = len(ics.find_devices())
84+
print("Found {} connected device(s)...".format(connected_count))
85+
device = open_device(0)
86+
prepare_message(device)
87+
time.sleep(1)
88+
receive_lin(device)
89+
print("Finished.")
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import ics
2+
import random
3+
import time
4+
import os
5+
import ldfparser
6+
7+
def parse_ldf():
8+
path = os.path.join(os.path.dirname(__file__), 'lin22.ldf')
9+
return ldfparser.parse_ldf(path)
10+
11+
def dev_name(device):
12+
# Return a friendly name of the device (ie. neoVI FIRE2 CY1234)
13+
if int("AA0000", 36) <= device.SerialNumber <= int("ZZZZZZ", 36):
14+
return device.Name + " " + ics.base36enc(device.SerialNumber)
15+
else:
16+
return device.Name + " " + str(device.SerialNumber)
17+
18+
def open_device(index=0):
19+
device = None
20+
# ics.open_device() won't open a device if we have handles open already
21+
# so we need to find them and specify which ones to connect to.
22+
devices = ics.find_devices()
23+
print("Opening Device {} (Open Client handles: {})...".format(dev_name(devices[index]), devices[index].NumberOfClients))
24+
ics.open_device(devices[index])
25+
device = devices[index]
26+
print("Opened Device %s." % dev_name(device))
27+
return device
28+
29+
def receive_lin(device):
30+
msgs, error_count = ics.get_messages(device)
31+
print("Received {} messages with {} errors.".format(len(msgs), error_count))
32+
netids = { ics.NETID_LIN: "LIN 1" }#, ics.NETID_LIN2: "LIN 2" }
33+
for i, m in enumerate(msgs):
34+
netid = netids.get(m.NetworkID)
35+
if netid is not None:
36+
#print('\nMessage #{}: '.format(i+1), end='')
37+
print('{}'.format(netid), end='')
38+
if (m.StatusBitField & ics.SPY_STATUS_INIT_MESSAGE):
39+
print(' | Init')
40+
data_list = []
41+
for x in m.Header[1:]:
42+
data_list.append(x)
43+
for y in m.Data:
44+
data_list.append(y)
45+
try:
46+
frame_name = ldf.get_frame(m.Header[0] & 0x3F).name
47+
if frame_name is not None:
48+
print('{} | '.format(frame_name), end='')
49+
except:
50+
pass
51+
print('ID: {} | '.format(hex(m.Header[0])), end='')
52+
if len(data_list):
53+
checksum = data_list.pop()
54+
print('Data: {} | '.format([hex(x) for x in data_list]), end='')
55+
print('Checksum: {}'.format(hex(checksum)))
56+
print()
57+
return True
58+
59+
def send_frame(self, baudrate: int, frame_id: int, data: bytearray, netid: int, is_commander: bool):
60+
# set baudrate with device settings?
61+
msg = ics.SpyMessageJ1850()
62+
if(is_commander):
63+
msg.StatusBitField = ics.SPY_STATUS_INIT_MESSAGE
64+
msg.Protocol = ics.SPY_PROTOCOL_LIN
65+
msg.NetworkID = netid
66+
header_bytes = [frame_id]
67+
frame_bytes = []
68+
if(len(data)):
69+
hdr_len = min(len(data), 2)
70+
data_len = min((len(data) - hdr_len), 6)
71+
checksum = 0
72+
if(hdr_len > 0):
73+
header_bytes += list(data[0:hdr_len])
74+
if(data_len > 0):
75+
frame_bytes += list(data[hdr_len:])
76+
for byte in header_bytes+frame_bytes:
77+
checksum += byte
78+
if (checksum > 255):
79+
checksum -= 255
80+
if(len(header_bytes) == 2):
81+
header_bytes.append(~checksum & 0xFF)
82+
else:
83+
frame_bytes.append(~checksum & 0xFF)
84+
msg.Header = tuple(header_bytes)
85+
msg.Data = tuple(frame_bytes)
86+
msg.NumberBytesHeader = len(msg.Header)
87+
msg.NumberBytesData = len(msg.Data)
88+
try:
89+
ics.transmit_messages(self.device, msg)
90+
except ics.RuntimeError as ex:
91+
print(ex)
92+
return False
93+
return True
94+
95+
class LINCommander:
96+
def __init__(self, device):
97+
self.device = device
98+
self.frames = {}
99+
100+
def send_commander_frame(self, baudrate: int, frame_id: int, data: bytearray):
101+
send_frame(self, baudrate, frame_id, data, ics.NETID_LIN, True)
102+
103+
class LINResponder:
104+
def __init__(self, device):
105+
self.device = device
106+
self.frames = {}
107+
108+
def send_responder_frame(self, baudrate: int, frame_id: int, data: bytearray):
109+
send_frame(self, baudrate, frame_id, data, ics.NETID_LIN2, False)
110+
111+
if __name__ == "__main__":
112+
import time
113+
ldf = parse_ldf()
114+
# Lets figure out how many are connected to the PC and display it
115+
connected_count = len(ics.find_devices())
116+
print("Found {} connected device(s)...".format(connected_count))
117+
device = open_device(0)
118+
responder_nodes = ldf.get_slaves()
119+
commander_nodes = ldf.get_master()
120+
lin_cmdr = LINCommander(device)
121+
lin_resp = LINResponder(device)
122+
for resp in responder_nodes:
123+
for each in resp.publishes_frames:
124+
lin_resp.frames.update({each.name: each})
125+
126+
for cmdr in commander_nodes.publishes_frames:
127+
lin_cmdr.frames.update({cmdr.name: cmdr})
128+
129+
collision_schedule = ldf.get_schedule_table("Collision_resolver")
130+
for entry in collision_schedule.schedule:
131+
if(entry.frame is not None):
132+
if(entry.frame.name in lin_cmdr.frames):
133+
cmdr_data = entry.frame.encode({})
134+
lin_cmdr.send_commander_frame(ldf.baudrate, entry.frame.frame_id, cmdr_data)
135+
elif(entry.frame.name in lin_resp.frames):
136+
resp_data = entry.frame.encode({})
137+
lin_resp.send_responder_frame(ldf.baudrate, entry.frame.frame_id, resp_data)
138+
time.sleep(150/1000)
139+
lin_cmdr.send_commander_frame(ldf.baudrate, entry.frame.frame_id, bytearray())
140+
time.sleep(150/1000)
141+
142+
#request_frame = ldf.get_unconditional_frame('CEM_Frm1')
143+
#request_data = request_frame.encode({"InternalLightsRequest": 'on'}, ldf.converters)
144+
#lin_cmdr.send_commander_frame(ldf.baudrate, request_frame.frame_id, request_data)
145+
146+
#responseFrame = ldf.get_unconditional_frame('RSM_Frm1')
147+
#responseData = responseFrame.encode({"RightIntLightsSwitch": 'off'}, ldf.converters)
148+
#lin_resp.send_responder_frame(ldf.baudrate, responseFrame.frame_id, responseData)
149+
receive_lin(device)
150+
time.sleep(1)
151+
print("Finished.")

0 commit comments

Comments
 (0)