Skip to content
This repository has been archived by the owner on Sep 2, 2024. It is now read-only.

Commit

Permalink
Support input of multiple DBC files
Browse files Browse the repository at this point in the history
  • Loading branch information
erikbosch committed Jul 3, 2023
1 parent 333e0c9 commit 9d67580
Show file tree
Hide file tree
Showing 8 changed files with 88 additions and 76 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/kuksa_dbc_feeder.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,3 +124,10 @@ jobs:
pylint --exit-zero dbcfeeder.py dbcfeederlib test
# Fail on errors and above
pylint -E dbcfeeder.py dbcfeederlib test
- name: Run mypy, fail on errors.
run: |
# Also install some dependencies for analysis
pip3 install mypy grpc-stubs
cd dbc2val
python -m mypy *.py dbcfeederlib test
6 changes: 3 additions & 3 deletions dbc2val/dbcfeederlib/clientwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
#################################################################################

import logging
from typing import Any, List
from typing import Any, List, Optional

from abc import ABC, abstractmethod

Expand All @@ -36,8 +36,8 @@ def __init__(self, ip: str, port: int, token_path: str, tls: bool = True):
self._token_path = token_path
self._tls = tls
self._registered = False
self._root_ca_path = None
self._tls_server_name = None
self._root_ca_path: Optional[str] = None
self._tls_server_name: Optional[str] = None

def set_ip(self, ip: str):
""" Set IP address to use """
Expand Down
4 changes: 2 additions & 2 deletions dbc2val/dbcfeederlib/databrokerclientwrapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@
import grpc.aio
from pathlib import Path

import kuksa_client.grpc
import kuksa_client.grpc # type: ignore[import]
from kuksa_client.grpc import Datapoint
from kuksa_client.grpc import DataEntry
from kuksa_client.grpc import DataType
from kuksa_client.grpc import EntryUpdate
from kuksa_client.grpc import Field
from kuksa_client.grpc import Metadata
from kuksa_client.grpc.aio import VSSClient
from kuksa_client.grpc.aio import VSSClient # type: ignore[import]
from kuksa_client.grpc import SubscribeEntry
from kuksa_client.grpc import View
from dbcfeederlib import clientwrapper
Expand Down
32 changes: 19 additions & 13 deletions dbc2val/dbcfeederlib/dbc2vssmapper.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@
import json
import logging
import sys
from typing import Any, Dict, List, Set
from typing import Any, Dict, List, Set, Optional, KeysView
from dataclasses import dataclass

from py_expression_eval import Parser
from py_expression_eval import Parser # type: ignore[import]

from dbcfeederlib import dbcparser

Expand Down Expand Up @@ -201,12 +201,15 @@ def transform_dbc_value(self, vss_observation: VSSObservation) -> Any:
"""
Find VSS mapping and transform DBC value to VSS value.
"""
# If we have an observation we know that a mapping exists
vss_signal = self.get_dbc2val_mapping(vss_observation.dbc_name, vss_observation.vss_name)
value = vss_signal.transform_value(vss_observation.raw_value)
log.debug(f"Transformed dbc {vss_observation.dbc_name} to VSS "
f"{vss_observation.vss_name}, "
f"from raw value {vss_observation.raw_value} to {value}")
if vss_signal:
value = vss_signal.transform_value(vss_observation.raw_value)
log.debug(f"Transformed dbc {vss_observation.dbc_name} to VSS "
f"{vss_observation.vss_name}, "
f"from raw value {vss_observation.raw_value} to {value}")
else:
log.error("No mapping found, that is not expected!")
value = None
return value

def extract_verify_transform(self, expanded_name: str, node: dict):
Expand Down Expand Up @@ -310,6 +313,9 @@ def analyze_val2dbc(self, expanded_name, node: dict, vss2dbc: dict):

# Also add CAN-id
dbc_can_id = self.dbc_parser.get_canid_for_signal(dbc_name)
if not dbc_can_id:
log.error(f"Could not find {dbc_name}")
return
if dbc_can_id not in self.val2dbc_can_id_mapping:
self.val2dbc_can_id_mapping[dbc_can_id] = []
self.val2dbc_can_id_mapping[dbc_can_id].append(mapping_entry)
Expand Down Expand Up @@ -360,7 +366,7 @@ def traverse_vss_node(self, name, node, prefix=""):
for item in node.items():
self.traverse_vss_node(item[0], item[1], prefix)

def get_dbc2val_mapping(self, dbc_name: str, vss_name: str) -> VSSMapping:
def get_dbc2val_mapping(self, dbc_name: str, vss_name: str) -> Optional[VSSMapping]:
"""
Helper method for test purposes
"""
Expand All @@ -370,11 +376,11 @@ def get_dbc2val_mapping(self, dbc_name: str, vss_name: str) -> VSSMapping:
return mapping
return None

def get_dbc2val_entries(self) -> Set[str]:
def get_dbc2val_entries(self) -> KeysView:
"""Return a set of all dbc names used for reception"""
return self.dbc2val_mapping.keys()

def get_val2dbc_entries(self) -> Set[str]:
def get_val2dbc_entries(self) -> KeysView:
"""Return a set of all vss names used for reception"""
return self.val2dbc_mapping.keys()

Expand All @@ -384,8 +390,8 @@ def get_vss_names(self) -> Set[str]:
for entry in self.dbc2val_mapping.values():
for vss_mapping in entry:
vss_names.add(vss_mapping.vss_name)
for entry in self.val2dbc_mapping.keys():
vss_names.add(entry)
for key_entry in self.val2dbc_mapping.keys():
vss_names.add(key_entry)
return vss_names

def has_dbc2val_mapping(self) -> bool:
Expand Down Expand Up @@ -415,7 +421,7 @@ def handle_update(self, vss_name, value: Any) -> Set[str]:
dbc_ids.add(dbc_mapping.dbc_name)
return dbc_ids

def get_default_values(self, can_id) -> Dict[int, Any]:
def get_default_values(self, can_id) -> Dict[str, Any]:

res = {}
for signal in self.dbc_parser.get_signals_for_canid(can_id):
Expand Down
18 changes: 12 additions & 6 deletions dbc2val/dbcfeederlib/dbcparser.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
########################################################################

import logging
from typing import Set, Optional, Dict
import sys
from typing import Set, Optional, Dict, cast
import cantools

log = logging.getLogger(__name__)
Expand All @@ -28,7 +29,6 @@
class DBCParser:
def __init__(self, dbcfile: str, use_strict_parsing: bool = True):

self.db = None
first = True
found_names = set()
for name in dbcfile.split(","):
Expand All @@ -39,8 +39,14 @@ def __init__(self, dbcfile: str, use_strict_parsing: bool = True):
found_names.add(filename)
if first:
log.info("Reading DBC file {} as first file".format(filename))
self.db = cantools.database.load_file(filename, strict=use_strict_parsing)
first = False
db = cantools.database.load_file(filename, strict=use_strict_parsing)
# load_file can return multiple types of databases, make sure we have CAN database
if isinstance(db, cantools.database.can.database.Database):
self.db = cast(cantools.database.can.database.Database, db)
first = False
else:
log.error("File is not a CAN database, likely a diagnostics database")
sys.exit(-1)
else:
log.info("Adding definitions from {}".format(filename))
self.db.add_dbc_file(filename)
Expand Down Expand Up @@ -81,5 +87,5 @@ def get_signals_for_canid(self, canid: int) -> Set[str]:
self.canid_to_signals[canid] = names
return names
log.warning(f"CAN id {canid} not found in DBC file")
self.canid_to_signals[canid] = []
return []
self.canid_to_signals[canid] = set()
return set()
93 changes: 43 additions & 50 deletions dbc2val/dbcfeederlib/elm2canbridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

import os
import sys
import serial
import serial # type: ignore[import]
import can
import threading
from multiprocessing import Queue, Process
Expand All @@ -29,8 +29,6 @@
QUEUE_MAX_ELEMENTS = 2048




class elm2canbridge:
def __init__(self, canport, cfg, whitelist=None):
print("Try setting up elm2can bridge")
Expand All @@ -41,7 +39,7 @@ def __init__(self, canport, cfg, whitelist=None):
sys.exit(-1)

self.canport = canport
self.whitelist=whitelist
self.whitelist = whitelist
elm = serial.Serial()
elm.baudrate = cfg['baud']
elm.port = cfg['port']
Expand All @@ -56,18 +54,17 @@ def __init__(self, canport, cfg, whitelist=None):
print("elm2canbridge: Can not open serial port")
sys.exit(-1)

self.initelm(elm,cfg['speed'], cfg['canack'])
self.initelm(elm, cfg['speed'], cfg['canack'])
can = self.initcan(cfg)

serQueue = Queue(QUEUE_MAX_ELEMENTS)

mt = threading.Thread(target=self.serialProcesor, args=(serQueue, can,))
mt.start()

sr = p = Process(target=self.serialReader, args=(elm, serQueue,))
#sr = threading.Thread(target=self.serialReader, args=(elm, serQueue,))
sr = Process(target=self.serialReader, args=(elm, serQueue,))
sr.start()
srpid=sr.pid
srpid = sr.pid
print("Running on pid {}".format(srpid))

def serialReader(self, elm, q):
Expand All @@ -80,7 +77,7 @@ def serialReader(self, elm, q):

os.nice(-10)
print("elm2canbridge: Enter monitoring mode...")
if self.whitelist != None:
if self.whitelist is not None:
print("Applying whitelist")
elm.write(b'STM\r')
elm.read(4) # Consume echo
Expand All @@ -90,53 +87,51 @@ def serialReader(self, elm, q):
elm.read(5) # Consume echo

elm.timeout = None
CR=13
CR = 13
while True:
buffer[index] = elm.read()[0]
#print("Read: {}=={} ".format(buffer[index],CR))
#print("Buffer {}".format(buffer))
# print("Read: {}=={} ".format(buffer[index],CR))
# print("Buffer {}".format(buffer))
if buffer[index] == CR or index == 63:
#print("Received {}".format(bytes(buffer).hex()[:index]))
q.put(buffer[:index]) #Todo will slice copy deep enough or is this a race?
index=0
# print("Received {}".format(bytes(buffer).hex()[:index]))
q.put(buffer[:index]) # Todo will slice copy deep enough or is this a race?
index = 0
continue
index+=1



index += 1

def serialProcesor(self,q, candevice):
def serialProcesor(self, q, candevice):
print("elm2canbridge: Waiting for incoming...")

while True:
line=q.get().decode('utf-8')
#print("Received {}".format(line))
line = q.get().decode('utf-8')
# print("Received {}".format(line))

isextendedid=False
#print("Received from elm: {}".format(line))
isextendedid = False
# print("Received from elm: {}".format(line))
try:
items = line.split()
if len(items[0]) == 3: # normal id
canid=int(items[0], 16)
#print("Normal ID {}".format(canid))
canid = int(items[0], 16)
# print("Normal ID {}".format(canid))
del items[0]
elif len(items) >= 4: # extended id
isextendedid=True
isextendedid = True
canid = int(items[0] + items[1] + items[2] + items[3], 16)
items = items[4:]
#print("Extended ID {}".format(canid))
# print("Extended ID {}".format(canid))
else:
print(
"Parseline: Invalid line: {}, len first element: {}, total elements: {}".format(line, len(items[0]),
"Parseline: Invalid line: {}, len first element: {}, total elements: {}".format(line,
len(items[0]),
len(items)))
continue

data=''.join(items)
#print("data: {}".format(data))
dataBytes= bytearray.fromhex(data)
except Exception as e:
# print("Error parsing: " + str(e))
# print("Error. ELM line, items **{}**".format(line.split()))
data = ''.join(items)
# print("data: {}".format(data))
dataBytes = bytearray.fromhex(data)
except Exception:
# print("Error parsing: " + str(e))
# print("Error. ELM line, items **{}**".format(line.split()))
continue

if len(dataBytes) > 8:
Expand All @@ -149,12 +144,12 @@ def serialProcesor(self,q, candevice):
try:
candevice.send(canmsg)
except Exception as e:
print("Error formwarding message to Can ID 0x{:02x} (extended: {}) with data 0x{}".format(canid, isextendedid, dataBytes.hex()))
print("Error forwarding message to Can ID 0x{:02x} (extended: {}) with data 0x{}".
format(canid, isextendedid, dataBytes.hex()))
print("Error: {}".format(e))


#Currently only works with obdlink devices
def initelm(self, elm, canspeed, ack):
"""Currently only works with obdlink devices"""
print("Detecting ELM...")
elm.write(b'\r\r')
self.waitforprompt(elm)
Expand All @@ -174,39 +169,38 @@ def initelm(self, elm, canspeed, ack):
print("Disable DLC")
self.executecommand(elm, b'AT D0\r')

if self.whitelist != None:
if self.whitelist:
print("Using Whitelist")
print("Clear all filters")
self.executecommand(elm, b'STFAC\r')
for canid in self.whitelist:
if canid < 2048:
cmd="STFPA {:04x}, 7fff\r".format(canid)
cmd = "STFPA {:04x}, 7fff\r".format(canid)
else:
cmd = "STFPA {:08x}, 1fffffff\r".format(canid)
print("Exec "+str(cmd))
self.executecommand(elm, cmd.encode('utf-8'))


print("Set CAN speed")
self.executecommand(elm, b'STP 32\r')
cmd = "STPBR " + str(canspeed) + "\r"
self.executecommand(elm, cmd.encode('utf-8'))
baud = self.executecommand(elm, b'STPBRR\r', expectok=False)
self.executecommand(elm, b'STPBRR\r', expectok=False)
print("Speed is {}".format(canspeed))
if ack:
self.executecommand(elm, b'STCMM 1\r')
else:
self.executecommand(elm, b'STCMM 0\r')

#open vcan where we mirror the elmcan monitor output
# open vcan where we mirror the elmcan monitor output
def initcan(self, cfg):
return can.interface.Bus(self.canport, bustype='socketcan') # pylint: disable=abstract-class-instantiated

def waitforprompt(self,elm):
def waitforprompt(self, elm):
while elm.read() != b'>':
pass

def writetoelm(self,elm,data):
def writetoelm(self, elm, data):
# print("Write")
length = len(data)
elm.write(data)
Expand All @@ -215,15 +209,14 @@ def writetoelm(self,elm,data):
print("elm2canbridge: Not the same {}/{}".format(data, echo))
# print("Write Done")


def readresponse(self, elm):
response=""
response = ""
while True:
d=elm.read()
d = elm.read()
if d == b'\r':
return response
response=response+d.decode('utf-8')
#print("DEBUG: "+response)
response = response + d.decode('utf-8')
# print("DEBUG: "+response)

def executecommand(self, elm, command, expectok=True):
self.writetoelm(elm, command)
Expand Down
Loading

0 comments on commit 9d67580

Please sign in to comment.