DoIP — это аббревиатура диагностической связи по интернет-протоколу. На самом деле это передача данных на основе протокола UDS Ethernet. Это также протокол сам по себе, стандартизированный в стандарте ISO13400. Поскольку DoIP может передавать большой объем данных, имеет высокую скорость отклика и может выполнять удаленную диагностику, прошивку, OTA и другие задачи через Ethernet, DoIP постепенно стал заменой традиционного CAN.
Главный компьютер диагностики может получить доступ к целевому чипу в соответствующем домене через шлюз и doip для выполнения таких задач, как диагностика, перепрошивка и OTA.
2.1
2.2
Заголовок протокола[8 byte] состоит из следующих четырех полей Protocol version [1 byte] Inverse protocol version [1 byte] Payload type [2 byte] Payload length [4 byte]
Адрес источника/цели DoIP представляет собой идентификатор обоих концов этой связи UDS, что несколько похоже на IP в Интернет-связи. Поскольку самый ранний протокол UDS работал на CAN, адрес источника/цели DoIP эквивалентен IP на CAN.
Если взглянуть на предыдущее введение, оно может показаться немного абстрактным. Мы можем запустить проект с открытым исходным кодом, чтобы понять его глубже.
git clone https://gitlab.com/rohfle/doip-simulator.git
Изменить уровень журнала
diff --git a/doipclient.py b/doipclient.py
index 1c77d03..06247ef 100644
--- a/doipclient.py
+++ b/doipclient.py
@@ -36,7 +36,7 @@ STEERING_MULTIPLIER = float(os.environ.get("APP_STEERING_MULTIPLIER", 14)) # 14
STEERING_DEADZONE_CAR = int(os.environ.get("APP_STEERING_DEADZONE_CAR", 0)) # 0
STEERING_DEADZONE_XBOX = int(os.environ.get("APP_STEERING_DEADZONE_XBOX", 10000)) # 10000
-logging.basicConfig(level=logging.WARNING)
+logging.basicConfig(level=logging.INFO)
def debug_parser(func):
def print_args(*args, **kwargs):
diff --git a/doipserver.py b/doipserver.py
index a738a8d..31d3e91 100644
--- a/doipserver.py
+++ b/doipserver.py
@@ -23,7 +23,7 @@ from lib import utils
from lib.simulator import fstep, framp, fsine, IdentifierDataSimulator
import logging
-logging.basicConfig(level=logging.WARNING)
+logging.basicConfig(level=logging.INFO)
def accelerator_format(n):
Откройте два окна и выполните следующие команды соответственно,Конечно, лучше всего выполнить его на двух устройствах отдельно.,Потому что я столкнулся с этим на компьютере Mac,Address already in use!
вопрос。
в моем WSL Ubuntu может работать на одном устройстве, потому что Ubuntu 20.04 поддерживает SO_REUSEADDR, но Mac не поддерживает SO_REUSEADDR.
Используйте параметр SO_REUSEADDR: вы можете установить параметр SO_REUSEADDR при создании объекта сокета, чтобы разрешить привязку порта нескольким процессам или потокам одновременно.
Это эквивалентно шлюзу на автомобиле, показанному на рисунке 2.1.
python3 doipserver.py
Это эквивалентно диагностическому главному компьютеру на автомобиле, показанному на рисунке 2.1.
python3 doipclient.py
Результат на стороне сервера
kobe@41001005-26-0:~/study/doip/doip-simulator$ python3 doipserver.py
INFO:discovery:Starting UDP discovery thread
Serving on ('0.0.0.0', 13400) //Прослушиваем порт 13400, чтобы узнать, есть ли запрос VehicleIdentityRequest
INFO:discovery:Vehicle identity requested by IP 172.31.68.132. Responding with vehicle объявление.//Нашел запрос, ответьте с объявлением
INFO:server:Connection established with ('172.31.68.132', 43292)//Официально установлено соединение
INFO:simulator:Generating value for Dummy Accelerator (0x3200 on target 0x3300)
INFO:simulator:Dummy Accelerator value at time 1683682540.2956088 is bytearray(b'\xcc')
Результат на стороне клиента
kobe@41001005-26-0:~/study/doip/doip-simulator$ python3 doipclient.py
INFO:root:Looking for DOIP gateway... //Находим шлюз автомобиля
INFO:root:Received Vehicle Announcement from 172.31.68.132! //получатьсерверная Трансляция из части
INFO:root:Routing activated successfully. //Активация маршрута успешна
INFO:root:Data read loop time 2.91 milliseconds with length 3
INFO:root:Data read loop time 1.95 milliseconds with length 3
С помощью анализа кода давайте рассмотрим два ключевых процесса: обнаружение службы, установление соединения и отправка данных UDS после установления соединения.
Фактически псевдокод на стороне сервера выглядит следующим образом
while {
Прослушивайте запросы от порта 13400 и возвращайте информацию об объявлении, если есть запрос.
время тайм-аута истекло
Анонс трансляции
}
def run(self, *args, **kwargs):
logger.info('Starting UDP discovery thread')
self.sock.bind(('', 13400))//Прослушиваем '0.0.0.0:13400', чтобы увидеть, есть ли какой-либо запрос клиента с ним на соединение.
self.sock.settimeout(0.5)//Устанавливаем время ожидания прослушивания
self.running = True
self.announcement = self.generate_announcement(self.address, self.config).render()
self.last_broadcast_time = time.time() - self.broadcast_interval
while self.running:
try:
//Прослушиваем, есть ли с ним клиент соединяться
data, addr = self.sock.recvfrom(1024)
message, used = doip.parse(bytearray(data))
logger.debug("Message received from %s:%i : %s", addr[0], addr[1], message)
if type(message) is doip.VehicleIdentityRequest:
logger.info('Vehicle identity requested by IP {}. '
'Responding with vehicle announcement.'.format(addr[0]))
//Если запрос найден, отправляем объявление клиенту
self.sock.sendto(self.announcement, addr)
except SocketTimeout:
pass
except Exception as err:
logger.error('Error:', str(err))
logger.exception('Trace:')
//время тайм-аута истекает трансляции
now_time = time.time()
if now_time - self.last_broadcast_time > self.broadcast_interval:
self.last_broadcast_time = now_time
self.sock.sendto(self.announcement, ('255.255.255.255', 13400))
Самое важное содержание объявления — это IP, vin (идентификатор автомобиля) и Mac автомобиля.
def generate_announcement(self, address, config):
vin = config['vin']
mac = config['mac']
return doip.VehicleAnnouncement(vin=vin, logical_address=address, eid=mac, gid=mac)//ключевой контент
config = {
'vin': 'TESTVIN0000012345',
'mac': int('123456789ABC', 16),
....
}
Транслируйте запрос на порт 13400, потому что в предыдущей версии 5.1 сервер прослушивал собственный порт 13400 и получал весь запрос, а затем возвращал IP сервера, чтобы клиент мог позвонить после получения IP
def discover_doip():
"""Find the IP of the DOIP gateway"""
# errors and their response:
# - TimeoutException - cooldown before resend vehicle identity request
# - network unreachable - caught by parent function
s = socket(AF_INET, SOCK_DGRAM)
s.setsockopt(SOL_SOCKET, SO_BROADCAST, 1)
if NETWORK_INTERFACE is not None:
s.setsockopt(SOL_SOCKET, 25, str(NETWORK_INTERFACE + '\0').encode('utf-8'))
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
s.settimeout(DOIP_TIMEOUT)
s.bind(('', 13400))
logging.info("Looking for DOIP gateway...")
while True:
request = doip.VehicleIdentityRequest()
logging.debug('SEND: %s', request)
s.sendto(request.render(), ('255.255.255.255', 13400)) // Широковещательный запрос VehicleIdentityRequest
try:
start = time.time()
while True:
data, addr = s.recvfrom(1024)
data = bytearray(data)
response, used = doip.parse(data)
logging.debug('RECV: %s', response)
//Получаем информацию об объявлении транспортного средства и возвращаем IP
if type(response) is doip.VehicleAnnouncement:
logging.info('Received Vehicle Announcement from %s!' % (addr[0]))
return addr[0]
if time.time() - start > DOIP_TIMEOUT:
logging.warning('No vehicle announcement received. Requesting identity again immediately...')
break
except timeout:
logging.warning('No vehicle announcement received. Waiting 2 seconds before trying again...')
time.sleep(2)
Как только вы получите IP-адрес сервера, вы сможете установить соединение и с радостью передавать данные doip.
while True:
try:
gateway_addr = discover_doip()//получатьсерверная частьизIP setup_doip(gateway_addr)//Задаем адрес шлюза doip, то есть кому клиент должен отправлять данные.
except OSError as err:
logging.error("Error: %s", str(err))
time.sleep(5)
def setup_doip(gateway_addr):
while True:
try:
s = socket(AF_INET, SOCK_STREAM)
s.settimeout(DOIP_TIMEOUT)
s.connect((gateway_addr, 13400))
Обнаружили ли вы, что основная идея серверной стороны заключается в том, что обе стороны договорились о порте 13400, обе прослушивают этот порт и отправляют запросы на обслуживание на этот порт для обеспечения широковещательной передачи услуг, чтобы клиент мог получить IP-адрес сервера? а затем установите соединение. Зачем использовать этот порт? Это связано с правилами ISO-13400.
Отправной точкой любого межпроцессного и междевайсного взаимодействия на самом деле является некое соглашение. Doip — это соглашение о порте 13400, объявлении и VehicleIdentityRequest. По аналогии с биндером в системе Android фактически договорились, что формат служебной String-Binder предоставляется через ServiceManager, а служебная строка запрашивается. Например, протокол и обнаружение служб someip также реализованы в соответствии с определенным соглашением. Подробно об этом я расскажу позже, когда представлю someip.
5.1.3
def run_doip(s):
while True:
data_payload = {}
start = time.time()
for target_address, identifiers in config['datamap'].items():
for identifier, meta in identifiers.items():
label, key, parser = meta
logging.debug('Getting identifier 0x{:04x} ({})'.format(identifier, label))
uds_request = uds.ReadDataByIdentifier(identifier)
data = uds_request.render() //Создаем пакет данных uds
request = doip.DiagnosticMessage(target_address, DOIP_SOURCE_ADDRESS, data) //Переходим на 5.2.2
logging.debug("SEND %s", str(request))
s.send(request.render())
value = receive_doip(s, identifier, parser)
if value is not None:
data_payload[key] = value
if len(data_payload) > 0:
serial_thread.send(data_payload)
else:
logging.warning('Data read loop result is empty')
time_taken = time.time() - start
logging.info('Data read loop time %.2f milliseconds with length %i', time_taken * 1000, len(data_payload))
config = {
'datamap': {
# target_address (hex) : {
# identifier (hex) : tuple(label (str), key (str), parser (func))
# }
0x3300: {
0x3200: ('Dummy Accelerator', 'accelerator', parse_accelerator),
0x3230: ('Dummy Brake', 'brake', parse_brake_pressure),
},
0x3301: {
0x3250: ('Dummy Steering', 'steering', parse_steering_angle),
}
}
}
Можно обратиться кФормат протокола DoIP
глава,Упаковка пакета данных DoIP также относительно проста.
class DiagnosticMessage(DOIPMessage):
def render(self):
source_address = self.params['source_address']
target_address = self.params['target_address']
userdata = self.params['userdata']
data = bytearray(utils.num_to_bytes(source_address, 2))
data += bytearray(utils.num_to_bytes(target_address, 2))
data += bytearray(userdata)
if len(data) < 5:
raise InvalidMessage('Rendered diagnostic message is less than 5 bytes long')
else:
return super().render(data)
class DOIPMessage(object):
def render(self, data):
if self.payload_type is None:
raise Exception('DOIPMessage subclass has no payload_type value')
header = bytearray([0x2, 0xfd])
header += bytearray(utils.num_to_bytes(self.payload_type, 2))
header += bytearray(utils.num_to_bytes(len(data), 4))
return header + data
Сервер получит DoIP-пакет, а затем распакует его по правилам, и код дальше интерпретироваться не будет.
Это моя первая настоящая техническая статья после перехода с мобильных телефонов на автомобильную сферу. Я поделюсь некоторыми своими ощущениями.
От одного чипа и одной системы до многочиповой мультисистемы, каждый чип имеет соответствующее ядро, промежуточное программное обеспечение и приложения. Чтобы стандартизировать поведение всех чиповых систем в автомобиле, есть такие вещи, как Doip. UDS Существует множество спецификаций протоколов, которые необходимо читать при работе в автомобильной сфере, поэтому необходимо совершенствовать умение читать англоязычные документы.
В последующих статьях я еще проведу некоторые аналогии с Android. Я считаю, что многие концепции дизайна Android и многие концепции дизайна, используемые в автомобильных системах, могут учиться друг у друга, и, возможно, первые учатся даже у вторых.
Поскольку я только начал заниматься автомобилестроением, если в этой статье есть что-то не так, пожалуйста, поправьте меня.