- Proficient, automatic obd2 chip tuning box for vehicles –
- Архитектура приложения python для взаимодействия с obd-ii через can
- Диагностический адаптер elm327
- Коммутационный бокс детектор автомобильных протоколов / обзоры оборудования / o-b-d.ru
- Краткий разговор о can
- Софт панели приборов на python и kivy (ui framework)
- Тестирование нашего примера приложения с помощью симулятора obd-ii
- Упрощение с помощью расширения torizon с кодом visual studio
- Установка torizoncore 5
Proficient, automatic obd2 chip tuning box for vehicles –
All the. obd2 chip tuning box available on the site are engineered intricately. The tools are very handy, made from sturdy materials such as iron and stainless steel, and can cover multiple systems that are widely used. These. obd2 chip tuning box are professional-grade, and yet, simple enough to be used by amateurs as well. They can also help in fixing all kinds of crucial systems such as transmissions, engines, brakes, safety, emissions and so on. These. obd2 chip tuning box are electrically powered and come with warranty periods.
o-b-d.ru features a wide selection of. obd2 chip tuning box that are available in distinct models, sizes and model-specific features. These. obd2 chip tuning box are equipped with bright LED displays that offer you clear visibility. These. obd2 chip tuning box are also equipped with an innovative DS Tool software that can update and show you all the customer records through PC, Netbook and other devices. It is compliant with all types of Operating Systems and can help you monitor records too.
o-b-d.ru can offer you a plethora of. obd2 chip tuning box that will help you save money on the purchase. These products are ISO, CE, SGS certified that also gives peace of mind in terms of authenticity. You can also place OEM orders along with customized packaging as well.
Архитектура приложения python для взаимодействия с obd-ii через can
Чтобы создать наше простое приложение на Python, мы будем использовать библиотеку Python CAN для управления сетью CAN. Вы также можете использовать API сокетов в Python для связи CAN, поскольку Python поддерживает CAN с версии 3.3, но на данный момент это более низкоуровневый подход.
Чтобы проиллюстрировать запрос OBD-II для PID 0x0C, как определено в стандарте для частоты вращения двигателя (RPM), мы представляем код ниже. Это будет:
- Создайте интерфейс CAN-шины
- Создайте ссылку на сообщение CAN для запроса
- Запрос сообщения CAN — это кадр CAN с DLC размером 8 байтов.
- Сообщение будет построено в следующем формате для стандарта SAE:
- Байт 0 — количество дополнительных байтов: 2
- Байт 1 — 1, чтобы показать текущие данные
- Байт 2 — запрашиваемый PID-код
- Байты с 3 по 7: они не используются, но ISO 15765-2 предлагает установить для них CCh
- Отправьте запрос в главный ЭБУ с идентификатором 0x7DF
- Получите сообщение и сравните его с ожидаемым идентификатором ответа 0x7E8
- Если мы получим сообщение от ожидаемого идентификатора ответа, он напечатает результат в шестнадцатеричном формате.
Чтобы выполнить наше приложение CAN, мы должны сначала настроить и включить сеть CAN в модуле. Интерфейс CAN1, физический, уже включен в его дереве устройств и обозначен как can0 на стороне Linux. Процесс настройки и включения может быть выполнен с помощью вызовов os.system () в Python, в которых мы настраиваем сеть CAN с битрейтом 500k.
# In the extension we shall bring up the interfaces directly within our Python app
os.system(“ip link set can0 type can bitrate 500000”)
time.sleep(1)
os.system(“ip link set can0 up”)
time.sleep(1)
# set up a CAN bus
bus = can.interface.Bus(channel=”can0″, bustype=’socketcan’)
# using defaults functional address
obd2_tx_arb_id = 0x7DF
obd2_rx_arb_id = 0x7E8
# CAN Frame for a PID Request of 0x0C (RPM) with SAE Standard
obd_req_data = [2, 1, 0x0C, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC]
# Send our OBD-II query in CAN 11-bit format
msg = can.Message(arbitration_id=obd2_tx_arb_id, data=obd_req_data, is_extended_id=False)
bus.send(msg)
message = bus.recv(1.0) # Timeout in seconds.
if message is None:
print(‘Timeout occurred, no message.’)
sys.exit(1)
# Check if received the expected response message
if message.arbitration_id == obd2_rx_arb_id:
print(“Message received from OBD-II request!”)
hex_data = “”
for c in message.data:
hex_data = “X ” % c
print(“Response ID: X | Response Data: %s” % (message.arbitration_id, hex_data))
import time import os import sys import can # In the extension we shall bring up the interfaces directly within our Python app os.system(“ip link set can0 type can bitrate 500000”) time.sleep(1) os.system(“ip link set can0 up”) time.sleep(1) # set up a CAN bus bus=can.interface.Bus(channel=“can0”,bustype=‘socketcan’) # using defaults functional address obd2_tx_arb_id=0x7DF obd2_rx_arb_id=0x7E8 # CAN Frame for a PID Request of 0x0C (RPM) with SAE Standard obd_req_data=[2, 1, 0x0C, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC] # Send our OBD-II query in CAN 11-bit format msg=can.Message(arbitration_id=obd2_tx_arb_id,data=obd_req_data,is_extended_id=False) bus.send(msg) message=bus.recv(1.0)# Timeout in seconds. ifmessage isNone: print(‘Timeout occurred, no message.’) sys.exit(1) # Check if received the expected response message ifmessage.arbitration_id==obd2_rx_arb_id: print(“Message received from OBD-II request!”) hex_data=“” forcinmessage.data: hex_data =“X “%c print(“Response ID: X | Response Data: %s”%(message.arbitration_id,hex_data)) |
Вы можете загрузить этот код в свою цель, скопировав и вставив его с помощью редактора nano, который мы установили в наш образ контейнера, как показано в его Dockerfile. Другой способ — привязать этот контейнер к /home/torizon, чтобы упростить отправку кода через scp.
Имея приложение под рукой, давайте попробуем его.
Диагностический адаптер elm327
Для меня некоторое время было вопросом, как получить данные из CAN шины и передать на телефон. Можно было бы разработать собственный шлюз с Wi-Fi или Bluetooth, как это делают производители сигнализаций, например Starline. Но изучив документацию на популярный автомобильный сканер ELM327 понял, что его можно настроить с помощью AT команд на доступ к CAN шине.
Не все ELM327 одинаково полезны
Оригинальный ELM327 от компании elmelectronics стоит порядка 50$, в России я таких не встречал в продаже. У нас продаются только китайские копии/подделки, разного качества и цены 10-30$. Бывают полноценные копии, которые поддерживают все протоколы, а бывают и те которые умеют отвечать только на несколько команд, остальные игнорируют, такие адаптеры не имеют доступ к CAN шине. Я например пользуюсь копией Viecar BLE 4.0, который поддерживает 100% всех функций оригинала.
Для работы с протоколом UDS через ELM327 нужно указать адреса назначения, источника и разрешить длинные 8 байтные сообщения, по умолчанию пропускается максимум 7 байт.
Последовательность ELM327 AT команд для работы с UDS по CAN шине:
ATZ // сброс настроек
AT E0 // отключаем эхо
AT L0 // отключаем перенос строки
AT SP 6 // Задаем протокол ISO 15765-4 CAN (11 bit ID, 500 kbaud)
AT ST 10 // Таймаут 10 * 4 мс, иначе EBU шлет повторные ответы каждые 100 мс, а мы не отвечаем, потому что ожидаем конца, а нам нужен только первый ответ
AT CA F0
AT AL // Allow Long (>7 byte) messages
AT SH 7E0 // задаем ID, к кому обращаемся (двигатель)
AT CRA 7E8 // CAN Receive Address. Можно задать несколько 7Xe
AT FC SH 7E0
AT FC SD 30 00 00
AT FC SM 1 // Режим Flow Control 1 должен быть определен после FC SH и FC SD, иначе в ответ придет "?"
03 22 F4 0С 55 55 55 55 // UDS запрос оборотов двигателя
Для работы с протоколом KWP2000 через ELM327 нужно только указать адреса назначения и источника.
Последовательность ELM327 AT команд для работы с VW TP 2.0 по CAN шине:
Коммутационный бокс детектор автомобильных протоколов / обзоры оборудования / o-b-d.ru
Данное устройство было куплено на AliExpress за 43$ или, приблизительно, 3100 российских рублей. Доставка, на этот раз затянулась и заняла, приблизительно, один месяц.
Упаковано всё было в обычную картонную коробку, в которой обычно присылают китайские автокомы, лексии и др.
Содержимое коробки: сам прибор и перемычка.
Предназначено данное устройство для мониторинга и(или) детектирования протоколов используемых при диагностике автомобилей. Попросту говоря с помощью этого устройства, Вы можете наблюдать какие пины используются при диагностике автомобиля. Принцип работы здесь элементарный: на лицевой панели расположено 16 светодиодов, каждый из них отвечает за наличие напряжения на своем контакте диагностического разъема, в следствии чего появляется наглядность в работе оборудования.
Также лицевая панель устройства оборудована цифровым вольтметром с точностью до одной десятой вольта. В дальнейшем, при тестировании прибора выяснилось, что вольтметр может врать вплоть до 0,5 вольта.
С помощью перемычки, идущей в комплекте, можно коммутировать любые пины, к примеру, соеденить 7 пин (k-line) и 9 пин (назначение устанавливает производитель авто) или любые другие пины.
Все разъемы на устройстве стандартизированные, поэтому сюда можно подключать любые другие щупы, перемычки и др. устройства со стандартизированными разъемами.
Диагностические разъемы довольно таки качественные, только вот разъем который подключается к автомобилю Китайцы как всегда сделали с прорезью для фиксации, в результате чего разъем будет застревать в некоторых автомобилях, лучше сразу выломать эту рамку.
Размерами это устройство примерно с автоком, в принципе ни меньше не больше не нужно, только я бы хотел, чтобы цвет корпуса был черным, сами понимаете руки в масле и отпечатки будут на всем корпусе.
Для того чтобы разобрать устройство потребуется открутить 4 винта на обратной стороне, после чего можно разделить корпус на 2 половины.
На плате можно наблюдать стабилизатор напряжения lm2576-5.0 (datasheet) это преобразователь напряжения с 7 до 40 вольт в 5 вольт, т.е. теоретически устройство будет работать до 40 вольт.
К сожалению, полностью вскрыть его не удалось, т.к. плата припаяна большим количеством олова к разъемам, которые прикручены к корпусу.
Но, каких либо, сложных деталей там и нет, есть только вольтметр, светодиоды же, скорее всего, подключены с помощью резисторов и катушек.
Диагностические разъемы на входе и выходе просто запараллелены и подключены к плате.
Разъемы можно вытащить и использовать вместо удлинителя.
Короче говоря, внутри ничего интересного для меня не нашлось, я, конечно, понимал, что это изобрели китайцы и на что-то интересное рассчитывать не придется, но всё же надеялся, что там стоит какой-нибудь простенький контроллер, который отслеживает питание на пинах и потом зажигает нужные светодиоды. Короче в техническом плане китайцы заморачиваться не стали, жаль.
Как оно работает, наверное, лучше посмотреть видео.
Ну и по справочнику по диагностических разъемов, скачать его можно отсюда
auto_connectors_help.zip (191.06 MB)
, выдран он из KTS. В справочнике расписано назначение пинов на диагностических разъемах автомобилей, примеры ниже:
Цифры в таблицах означают на каких пинах сидит та или иная система автомобиля.
Идеально к этому боксу подойдет программа
CASCADE
, там и пин необходимый подсвечивается и протокол, только используйте аккуратно, т.к. она не стабильна. Работает только с K-Line адаптерами на базе чипа FT232, у меня заработала только с китайским BMW-INPA.
С помощью этого бокса можно будет перекидывать пины, заодно и за работой посмотрите.
Так же можно контролировать работу китайских автокомов, ланчей, укандасов и др. мультимарочников, для проверенных марочных сканеров, вроде клипа, форда и др. я думаю, что оно не пригодится.
Покупал на AliExpress ссылка
Краткий разговор о can
Прежде чем мы углубимся в технические детали приложения, вы должны знать, что оно будет использовать CAN, что означает сеть контроллеров. Это один из наиболее часто используемых протоколов связи для транспортных средств, грузовиков и даже тракторов. Если у вас есть автомобиль, произведенный после 2004 года, он наверняка имеет сеть CAN, соединяющую десятки ЭБУ.
Для тех, кто не знаком с этим термином, ЭБУ(ECU) — это аббревиатура от электронный блок управления (Electronic Control Unit). Он соответствует каждому электронному устройству в сети CAN, которое может принимать и передавать данные, отвечая за управление одной или несколькими функциями в транспортном средстве, такими как двигатель, трансмиссия и даже мультимедийная система.
Как правило, любой данный ЭБУ, действующий как узел CAN, способный взаимодействовать с шиной CAN транспортного средства, должен иметь два основных компонента: контроллер CAN, который реализует уровень канала передачи данных ISO 11898-1 для CAN, и приемопередатчик CAN, который, в свою очередь, заботится о физическом уровне в соответствии со стандартами ISO 11898-2 / 3, как показано на рисунке 1.
Первоначально шина CAN была предназначена для использования на транспортных средствах, но она оказалась настолько надежной, что ее начали использовать другие области, добавляя транспортные протоколы, чтобы она могла поддерживать больше приложений, таких как стандарт CAN J1939, созданный для грузовиков, и ISO-11783 (также известный как ISOBUS) создан для тракторов. OBD-II поверх CAN, о котором мы будем говорить, построен на ISOTP, или, другими словами, ISO-15765-2.
В том же направлении сеть CAN в большинстве транспортных средств также должна обмениваться данными по стандарту бортовой диагностики (OBD-II), в котором она должна отвечать на серию запросов, чтобы предоставить информацию о скорости, оборотах в минуту, уровне батареи, уровне топлива, среди множества других данных, связанных с информацией о двигателе.
Возможность взаимодействия с данным транспортным средством со стандартом OBD-II позволяет любому приложению запрашивать информацию из главного ЭБУ данного транспортного средства, без необходимости знать собственные сообщения CAN, используемые каждым производителем для передачи интересующего сообщения.
Этот разъем обычно используется компаниями для телематических устройств для мониторинга, помимо других доступных переменных транспортных средств, скорости транспортного средства, уровня топлива, уровня заряда батареи, сгруппированных вместе с данными геолокации, полученными через приемник GPS / GNSS.
OBD-II — это подход «запрос-ответ». Другими словами, вам не придется читать не интересные вам сообщения по мере их появления. Вы будете отправлять сообщения главному ЭБУ транспортного средства, чтобы он реагировал на данную информацию, например, на скорость транспортного средства.
Главный ЭБУ автомобиля ответит на этот запрос, и вы обработаете сообщение в соответствии со стандартом OBD-II. Главное преимущество этого подхода заключается в том, чтобы не спамить шину CAN и периодически запрашивать интересующие сообщения, например, один раз в минуту.
Torizon и Verdin
Если вы еще не слышали о Torizon, предлагаем вам взглянуть. Torizon — это простая в использовании промышленная встраиваемая Linux-платформа Toradex, которая использует приложения в контейнерах, управляемых Docker, с тем, чтобы облегчить разработку встроенных системных решений. Он также поставляется с клиентом OTA с безопасностью автомобильного уровня. Это открытый исходный код.
Вместе с Torizon Toradex уже предоставляет новое семейство компьютеров-на-модулях под названием Verdin, основанное на разъеме DDR4 SODIMM. Verdin имеет оптимизированный интерфейс, а также упрощенные требования к источнику питания и управлению питанием всей системы.
Он разработан для суровых условий, и его прямой выход позволяет добавлять реальные порты ввода-вывода без необходимости пересекать трассы или слои. Первые модули Verdin основаны на процессорах приложений i.MX 8M Mini, подобных показанному на рисунке 6, который использовался в этом примере.
NXP i.MX8 M Mini SoC не поставляется с собственными контроллерами CAN. Чтобы компенсировать это, Toradex добавила в модуль контроллер MCP2518 SPI CAN, как показано на рисунке 7. Контроллер CAN MCP2518 совместим с CAN-FD и является хорошим выбором для приложений CAN высокого класса.
В настоящее время есть две несущие платы, которые вы можете использовать для оценки компьютера-на-модуле Verdin: Dahlia и плата разработки Verdin. Dahlia — это компактная несущая плата, обеспечивающая легкий доступ к наиболее распространенным функциям семейства Verdin, а плата разработчика Verdin — это несущая плата, цель которой — раскрыть все функции модуля.
В этой демонстрации мы будем использовать плата разработки Verdin, но вы можете легко использовать Dahlia. Плата разработки Verdin использует изолированный CAN-трансивер ISO1042BDWR от Texas Instruments, который предоставляет все необходимые сигналы для CAN, такие как:
Мы предоставляем подробные инструкции по правильному использованию Verdin iMX8MM и платы разработки Verdin в Кратком руководстве от Toradex.
Поскольку TorizonCore является встроенным дистрибутивом Linux, он поддерживает SocketCAN, предоставляемый ядром Linux, что позволяет приложению взаимодействовать с сетью CAN как соединение сокета с Linux Socket API.
Теперь, когда все настроено, давайте сделаем шаг за шагом, чтобы вы могли установить TorizonCore 5 в свой Verdin iMX8MM и наше приложение-контейнер для связи CAN со стандартом OBD-II.
Софт панели приборов на python и kivy (ui framework)
Параллельно со сборкой самой панели приборов я вел разработку приложения для отображения информации с датчиков. В самом начале я не планировал какой либо дизайн.
Первая версия панели приборов
По мере разработки решил визуализировать данные более наглядно. Хотел гоночный дизайн, а получилось, что-то в стиле 80-х.
Вторая версия панели приборов
Продолжив поиски более современного дизайна я обратил внимание какие цифровые приборки делают автопроизводители и постарался сделать что-то похожее.
Третья версия панели приборов
Ранее, я никогда не разрабатывал графические приложения под Linux поэтому не знал с чего начать. Вариант на вебе простой в разработке, но слишком много лишних компонентов: иксы, браузер, nodejs, хотелось быстрой загрузки. Попробовав Qt PySide2 я понял, что это займет у меня много времени, т.к. мало опыта.
Kivy позволяет запускать приложение без Иксов, прямо из консоли, в качестве рендера используется OpenGL. Благодаря этому полная загрузка системы может происходить за 10 секунд.
import can
import os
import sys
from threading import Thread
import time
os.environ['KIVY_GL_BACKEND'] = 'gl'
os.environ['KIVY_WINDOW'] = 'egl_rpi'
from kivy.app import App
from kivy.properties import NumericProperty
from kivy.properties import BoundedNumericProperty
from kivy.properties import StringProperty
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.uix.scatter import Scatter
from kivy.animation import Animation
messageCommands = {
'GET_DOORS_COMMAND': 0x220D,
'GET_OIL_TEMPERATURE' : 0x202F,
'GET_OUTDOOR_TEMPERATURE' : 0x220C,
'GET_INDOOR_TEMPERATURE' : 0x2613,
'GET_COOLANT_TEMPERATURE' : 0xF405,
'GET_SPEED' : 0xF40D,
'GET_RPM' : 0xF40C,
'GET_KM_LEFT': 0x2294,
'GET_FUEL_LEFT': 0x2206,
'GET_TIME': 0x2216
}
bus = can.interface.Bus(channel='can0', bustype='socketcan')
# -*- coding: utf-8 -*-
import can
import os
import sys
from threading import Thread
import time
os.environ['KIVY_GL_BACKEND'] = 'gl'
os.environ['KIVY_WINDOW'] = 'egl_rpi'
from kivy.app import App
from kivy.properties import NumericProperty
from kivy.properties import BoundedNumericProperty
from kivy.properties import StringProperty
from kivy.uix.label import Label
from kivy.uix.image import Image
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget
from kivy.uix.scatter import Scatter
from kivy.animation import Animation
messageCommands = {
'GET_DOORS_COMMAND': 0x220D,
'GET_OIL_TEMPERATURE' : 0x202F,
'GET_OUTDOOR_TEMPERATURE' : 0x220C,
'GET_INDOOR_TEMPERATURE' : 0x2613,
'GET_COOLANT_TEMPERATURE' : 0xF405,
'GET_SPEED' : 0xF40D,
'GET_RPM' : 0xF40C,
'GET_KM_LEFT': 0x2294,
'GET_FUEL_LEFT': 0x2206,
'GET_TIME': 0x2216
}
bus = can.interface.Bus(channel='can0', bustype='socketcan')
class PropertyState:
def __init__(self, last, current):
self.last = last
self.current = current
def lastIsNotNow(self):
return self.last is not self.current
class CanListener(can.Listener):
def __init__(self, dashboard):
self.dashboard = dashboard
self.speedStates = PropertyState(None,None)
self.rpmStates = PropertyState(None,None)
self.kmLeftStates = PropertyState(None,None)
self.coolantTemperatureStates = PropertyState(None,None)
self.oilTempratureStates = PropertyState(None,None)
self.timeStates = PropertyState(None,None)
self.outDoorTemperatureStates = PropertyState(None,None)
self.doorsStates = PropertyState(None,None)
self.carMinimized = True
def on_message_received(self, message):
messageCommand = message.data[3] | message.data[2] << 8
if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_SPEED']:
self.speedStates.current = message.data[4]
if self.speedStates.lastIsNotNow():
self.dashboard.speedometer.text = str(self.speedStates.current)
self.speedStates.last = self.speedStates.current
if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_RPM']:
self.rpmStates.current = message.data[5] | message.data[4] << 8
if self.rpmStates.lastIsNotNow():
self.dashboard.rpm.value = self.rpmStates.current/4
self.rpmStates.last = self.rpmStates.current
if message.arbitration_id == 0x35B:
self.rpmStates.current = message.data[2] | message.data[1] << 8
if self.rpmStates.lastIsNotNow():
self.dashboard.rpm.value = self.rpmStates.current/4
self.rpmStates.last = self.rpmStates.current
if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_KM_LEFT']:
self.kmLeftStates.current = message.data[5] | message.data[4] << 8
if self.kmLeftStates.lastIsNotNow():
self.dashboard.kmLeftLabel.text = str(self.kmLeftStates.current)
self.kmLeftStates.last = self.kmLeftStates.current
if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_COOLANT_TEMPERATURE']:
self.coolantTemperatureStates.current = message.data[4]
if self.coolantTemperatureStates.lastIsNotNow():
self.dashboard.coolantLabel.text = str(self.coolantTemperatureStates.current-81)
self.coolantTemperatureStates.last = self.coolantTemperatureStates.current
if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_OIL_TEMPERATURE']:
self.oilTempratureStates.current = message.data[4]
if self.oilTempratureStates.lastIsNotNow():
self.dashboard.oilLabel.text = str(self.oilTempratureStates.current-58)
self.oilTempratureStates.last = self.oilTempratureStates.current
if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_TIME']:
self.timeStates.current = message.data[5] | message.data[4] << 8
if self.timeStates.lastIsNotNow():
self.dashboard.clock.text = str(message.data[4]) ":" str(message.data[5])
self.timeStates.last = self.timeStates.current
if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_OUTDOOR_TEMPERATURE']:
self.outDoorTemperatureStates.current = float(message.data[4])
if self.outDoorTemperatureStates.lastIsNotNow():
self.dashboard.outDoorTemperatureLabel.text = str((self.outDoorTemperatureStates.current - 100)/2)
self.outDoorTemperatureStates.last = self.outDoorTemperatureStates.current
if message.arbitration_id == 0x77E and messageCommand == messageCommands['GET_DOORS_COMMAND']:
self.doorsStates.current = message.data[4]
if self.doorsStates.lastIsNotNow():
self.doorsStates.last = self.doorsStates.current
self.dashboard.car.doorsStates=message.data[4]
# all doors closed -> minimize car
if self.doorsStates.current == 0x55:
self.dashboard.minimizeCar()
self.carMinimized = True
else:
if self.carMinimized:
self.dashboard.maximizeCar()
self.carMinimized = False
class Dashboard(FloatLayout):
def __init__(self,**kwargs):
super(Dashboard,self).__init__(**kwargs)
# Background
self.backgroundImage = Image(source='bg.png')
self.add_widget(self.backgroundImage)
# RPM
self.rpm = Gauge(file_gauge = "gauge512.png", unit = 0.023, value=0, size_gauge=512, pos=(0,0))
self.add_widget(self.rpm)
self.rpm.value = -200
# Speedometer
self.speedometer = Label(text='0', font_size=80, font_name='hemi_head_bd_it.ttf', pos=(0,-15))
self.add_widget(self.speedometer)
# KM LEFT
self.kmLeftLabel = Label(text='000', font_name='Avenir.ttc', halign="right", text_size=self.size, font_size=25, pos=(278,233))
self.add_widget(self.kmLeftLabel)
# COOLANT TEMPEARATURE
self.coolantLabel = Label(text='00', font_name='hemi_head_bd_it.ttf', halign="right", text_size=self.size, font_size=27, pos=(295,-168))
self.add_widget(self.coolantLabel)
# OIL TEMPERATURE
self.oilLabel = Label(text='00', font_name='hemi_head_bd_it.ttf', halign="right", text_size=self.size, font_size=27, pos=(-385,-168))
self.add_widget(self.oilLabel)
# CLOCK
self.clock = Label(text='00:00', font_name='Avenir.ttc', font_size=27, pos=(-116,-202))
self.add_widget(self.clock)
# OUTDOOR TEMPERATURE
self.outDoorTemperatureLabel = Label(text='00.0', font_name='Avenir.ttc', halign="right", text_size=self.size, font_size=27, pos=(76,-169))
self.add_widget(self.outDoorTemperatureLabel)
# CAR DOORS
self.car = Car(pos=(257,84))
self.add_widget(self.car)
def minimizeCar(self, *args):
print("min")
anim = Animation(scale=0.5, opacity = 0, x = 400, y = 240, t='linear', duration=0.5)
anim.start(self.car)
animRpm = Animation(scale=1, opacity = 1, x = 80, y = -5, t='linear', duration=0.5)
animRpm.start(self.rpm)
def maximizeCar(self, *args):
print("max")
anim = Animation(scale=1, opacity = 1, x=257, y=84, t='linear', duration=0.5)
anim.start(self.car)
animRpm = Animation(scale=0.5, opacity = 0, x = 80, y = -5, t='linear', duration=0.5)
animRpm.start(self.rpm)
class Car(Scatter):
carImage = StringProperty("car362/car.png")
driverDoorClosedImage = StringProperty("car362/driverClosedDoor.png")
driverDoorOpenedImage = StringProperty("car362/driverOpenedDoor.png")
passangerDoorClosedImage = StringProperty("car362/passangerClosedDoor.png")
passangerDoorOpenedImage = StringProperty("car362/passangerOpenedDoor.png")
leftDoorClosedImage = StringProperty("car362/leftClosedDoor.png")
leftDoorOpenedImage = StringProperty("car362/leftOpenedDoor.png")
rightDoorClosedImage = StringProperty("car362/rightClosedDoor.png")
rightDoorOpenedImage = StringProperty("car362/rightOpenedDoor.png")
doorsStates = NumericProperty(0)
size = (286, 362)
def __init__(self, **kwargs):
super(Car, self).__init__(**kwargs)
_car = Image(source=self.carImage, size=self.size)
self.driverDoorOpened = Image(source=self.driverDoorOpenedImage, size=self.size)
self.passangerDoorOpened = Image(source=self.passangerDoorOpenedImage, size=self.size)
self.leftDoorOpened = Image(source=self.leftDoorOpenedImage, size=self.size)
self.rightDoorOpened = Image(source=self.rightDoorOpenedImage, size=self.size)
self.driverDoorClosed = Image(source=self.driverDoorClosedImage, size=self.size)
self.passangerDoorClosed = Image(source=self.passangerDoorClosedImage, size=self.size)
self.leftDoorClosed = Image(source=self.leftDoorClosedImage, size=self.size)
self.rightDoorClosed = Image(source=self.rightDoorClosedImage, size=self.size)
self.add_widget(_car)
self.add_widget(self.driverDoorOpened)
self.add_widget(self.passangerDoorOpened)
self.add_widget(self.leftDoorOpened)
self.add_widget(self.rightDoorOpened)
self.bind(doorsStates=self._update)
def _update(self, *args):
driverDoorStates = self.doorsStates&1
passangerDoorStates = self.doorsStates&4
leftDoorStates = self.doorsStates&16
rightDoorStates = self.doorsStates&64
if driverDoorStates != 0:
try:
self.remove_widget(self.driverDoorOpened)
self.add_widget(self.driverDoorClosed)
except:
pass
else:
try:
self.remove_widget(self.driverDoorClosed)
self.add_widget(self.driverDoorOpened)
except:
pass
if passangerDoorStates != 0:
try:
self.remove_widget(self.passangerDoorOpened)
self.add_widget(self.passangerDoorClosed)
except:
pass
else:
try:
self.remove_widget(self.passangerDoorClosed)
self.add_widget(self.passangerDoorOpened)
except:
pass
if leftDoorStates != 0:
try:
self.remove_widget(self.leftDoorOpened)
self.add_widget(self.leftDoorClosed)
except:
pass
else:
try:
self.remove_widget(self.leftDoorClosed)
self.add_widget(self.leftDoorOpened)
except:
pass
if rightDoorStates != 0:
try:
self.remove_widget(self.rightDoorOpened)
self.add_widget(self.rightDoorClosed)
except:
pass
else:
try:
self.remove_widget(self.rightDoorClosed)
self.add_widget(self.rightDoorOpened)
except:
pass
class Gauge(Scatter):
unit = NumericProperty(1.125)
zero = NumericProperty(116)
value = NumericProperty(10) #BoundedNumericProperty(0, min=0, max=360, errorvalue=0)
size_gauge = BoundedNumericProperty(512, min=128, max=512, errorvalue=128)
size_text = NumericProperty(10)
file_gauge = StringProperty("")
def __init__(self, **kwargs):
super(Gauge, self).__init__(**kwargs)
self._gauge = Scatter(
size=(self.size_gauge, self.size_gauge),
do_rotation=False,
do_scale=False,
do_translation=False
)
_img_gauge = Image(source=self.file_gauge, size=(self.size_gauge, self.size_gauge))
self._needle = Scatter(
size=(self.size_gauge, self.size_gauge),
do_rotation=False,
do_scale=False,
do_translation=False
)
_img_needle = Image(source="arrow512.png", size=(self.size_gauge, self.size_gauge))
self._gauge.add_widget(_img_gauge)
self._needle.add_widget(_img_needle)
self.add_widget(self._gauge)
self.add_widget(self._needle)
self.bind(pos=self._update)
self.bind(size=self._update)
self.bind(value=self._turn)
def _update(self, *args):
self._gauge.pos = self.pos
self._needle.pos = (self.x, self.y)
self._needle.center = self._gauge.center
def _turn(self, *args):
self._needle.center_x = self._gauge.center_x
self._needle.center_y = self._gauge.center_y
a = Animation(rotation=-self.value*self.unit self.zero, t='in_out_quad',duration=0.05)
a.start(self._needle)
class requestsLoop(Thread):
def __init__(self):
Thread.__init__(self)
self.daemon = True
self.start()
canCommands = [
can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_DOORS_COMMAND'] >> 8, messageCommands['GET_DOORS_COMMAND'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_SPEED'] >> 8, messageCommands['GET_SPEED'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_KM_LEFT'] >> 8, messageCommands['GET_KM_LEFT'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_RPM'] >> 8, messageCommands['GET_RPM'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_OIL_TEMPERATURE'] >> 8, messageCommands['GET_OIL_TEMPERATURE'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_FUEL_LEFT'] >> 8, messageCommands['GET_FUEL_LEFT'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_OUTDOOR_TEMPERATURE'] >> 8, messageCommands['GET_OUTDOOR_TEMPERATURE'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
can.Message(arbitration_id=0x746, data=[0x03, 0x22, messageCommands['GET_INDOOR_TEMPERATURE'] >> 8, messageCommands['GET_INDOOR_TEMPERATURE'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_COOLANT_TEMPERATURE'] >> 8, messageCommands['GET_COOLANT_TEMPERATURE'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False),
can.Message(arbitration_id=0x714, data=[0x03, 0x22, messageCommands['GET_TIME'] >> 8, messageCommands['GET_TIME'] & 0xff, 0x55, 0x55, 0x55, 0x55], extended_id=False)
]
def run(self):
while True:
for command in self.canCommands:
bus.send(command)
time.sleep(0.005)
class BoxApp(App):
def build(self):
dashboard = Dashboard();
listener = CanListener(dashboard)
can.Notifier(bus, [listener])
return dashboard
if __name__ == "__main__":
# Send requests
requestsLoop()
_old_excepthook = sys.excepthook
def myexcepthook(exctype, value, traceback):
if exctype == KeyboardInterrupt:
print "Handler code goes here"
else:
_old_excepthook(exctype, value, traceback)
sys.excepthook = myexcepthook
# Show dashboard
BoxApp().run()
Алгоритм работы следующий, используется 3 потока:
- В главном потоке работаем с графическими элементы (спидометр, тахометр, часы, температуры и др) на экране
- Во втором потоке каждые 5 мс делаем опрос следующего датчика
- В третьем потоке слушаем CAN шину, получив ответ парсим его и обновляем соответствующий графический элемент
Работает стабильно, самый долгий процесс в разработке был связан с рисованием дизайна. На данный момент обкатываю решение и потихоньку пишу мобильное приложение для iOS, чтобы любой мог попробовать цифровую панель приборов.
Проект цифровой панель приборов открытый. Рад буду предложениям и комментариям!
Тестирование нашего примера приложения с помощью симулятора obd-ii
У нас есть два способа проверить приложение CAN OBD-II:
- Подключаем наше устройство к разъему OBD-II на транспортном средстве и начинаем общаться с реальным транспортным средством.
- Использование другого устройства в качестве «ЭБУ» и ответа на запросы OBD-II по CAN.
Вариант 2 жизнеспособен, в противном случае потребовалось бы хорошее расширение мощности, чтобы мы могли попробовать его в машине автора этого обзора.
Сохраняя тему «Python», существует также проект Python виртуального ЭБУ для ответа на запросы OBD-II, называемый OBDSimulator. Мы использовали его на Colibri iMX6 с платой-носителем Viola, поэтому он будет вести себя как ЭБУ, отвечающий на наш Verdin iMX8MM по сети CAN между ними.
Использование несущей платы Viola было более сложной задачей при сборке установки с внешним трансивером CAN. Более простой способ — использовать оценочную плату Colibri для семейства Colibri или даже плату Ixora Carrier для семейства Apalis, поскольку эти несущие платы уже поставляются со встроенными трансиверами CAN, что делает их идеальными в качестве реализации эталонного дизайна. .
Схема, использованная для этого теста, показана на Рисунке 12. Для несущей платы Viola мы использовали приемопередатчик CAN SN65HVD230, так как iMX6 уже имеет контроллеры CAN. Он также использует резисторы 120 Ом на каждом конце «простой» сети CAN между ними.
С сервером OBDSimulator, работающим на Colibri iMX6 (как подробно описано в репозитории GitHub), мы выполнили следующие запросы на Verdin iMX8MM:
- Запросите текущие данные (режим 1) скорости двигателя (также известные как RPM, PID 0x0C):
Мы используем CAN в 11-битном формате, и после заданного запроса OBD-II ответ будет в следующем формате:
- Байт 0 — количество дополнительных байтов
- Байт 1 — 41h = отображение текущих данных
- Байт 2 — PID-код
- Байт 3 и выше — содержимое ответа на запрос.
В таблице OBD-II PID в Википедии информация о частоте вращения двигателя получается из содержимого запроса по следующей формуле:
Переменная A является третьим байтом в ответе, а переменная B — четвертым байтом (см. Ответ «Hex:» нашей команды выше). В OBDSimulator частота вращения двигателя составляет 514 об/мин. Давайте проверим, правда ли это?
((256 * 8) 8)/4 = 514
Это также показывает еще один ценный ресурс: обратите внимание на подробное описание каждого PID OBD-II, чтобы декодировать запрошенную информацию!
Мы можем изменить код, чтобы запросить другие PID OBD-II. Измените значение obd_req_data, чтобы теперь он запрашивал PID 0x0D (скорость автомобиля в км/ч) с текущими данными (режим 1):
Выполнение кода теперь даст нам вывод для запроса OBD-II PID 0x0D:
Если после повторного выполнения кода вы получаете сообщение «RTNETLINK отвечает: устройство или ресурс занят», это означает, что сетевой интерфейс уже настроен и работает.
В таблице OBD-II PID в Википедии информация о скорости транспортного средства получается как прямой результат третьего байта ответа, который является ответом на наш запрос. В OBDSimulator установлена скорость автомобиля 26 км / ч. Давайте проверим, правда ли это?
1A в шестнадцатеричном формате — 26 в десятичном. Так что, это!
Упрощение с помощью расширения torizon с кодом visual studio
Некоторые из вас могут быть не слишком знакомы с Docker и контейнерами. Это не проблема для работы с Torizon, знаете почему? Toradex также предоставляет вам расширение Torizon, доступное как для Visual Studio, так и для Visual Studio Code. С помощью расширения Torizon вы сможете быстро разрабатывать и загружать приложения в модуль с TorizonCore.
У использования нашего расширения Torizon для Visual Studio Code много преимуществ, не говоря уже о поддержке разработки приложений на следующих языках программирования:
Расширение Torizon позволяет развертывать и отлаживать ваше приложение в целевом объекте по сети всего за несколько щелчков мышью, имея также возможность управлять и отслеживать ваши устройства и контейнерные приложения с ними.
В этом конкретном примере мы покажем вам, как вы можете легко настроить приложение Python с помощью расширения Torizon для кода Visual Studio и запустить его на Verdin iMX8MM с установленным TorizonCore 5.
Давайте выполним следующие простые шаги для настройки:
- Загрузите и установите Visual Studio Code и Torizon Extension в соответствии с инструкциями.
- Имейте в виду, что вы должны настроить среду сборки для контейнеров Torizon, как мы объяснили выше.
- Создайте новый проект Torizon / Python в коде Visual Studio
После выполнения этой первоначальной настройки ваша среда готова для разработки приложения Python для Torizon. Следующие шаги обычно включают определение необходимых параметров, дополнительных пакетов и самого кода.
Вы можете видеть, что в коде Visual Studio в левой части экрана есть панель, содержащая значки каждого ресурса редактора. Одна из них — иконка Torizon. Щелкните по нему и настройте следующие параметры, наблюдая, что в каждом элементе появится значок типа « » или символ карандаша справа, который вы должны щелкнуть, чтобы добавить или отредактировать этот конкретный элемент:
См. Обзор этой части на рисунке 14.
По сути, это те изменения, которые мы внесли в Dockerfile вместе с командами «docker run», которые мы выполнили вручную выше. Но теперь Torizon Extension позаботится о всех формальностях за нас.
Теперь перейдите в меню «Проводник» в коде Visual Studio, затем откройте файл «main.py» вашего проекта. Скопируйте и вставьте тот же код, который мы использовали в примере командной строки выше.
Чтобы загрузить этот код на свою плату, где расширение Torizon уже запросило свои учетные данные (например, имя хоста / IP, пользователь и пароль), вы можете просто нажать F5 на клавиатуре. Затем утилита начнет создавать образ контейнера, загрузит его в устройство и начнет выполнение в режиме отладки, процесс, который вы можете наблюдать в разделе «Вывод» кода Visual Studio.
С помощью расширения Torizon и кода Visual Studio вы также можете добавлять точки останова в свое приложение для отслеживания частей процесса выполнения программы. На рис. 15 показан пример выполнения приведенной выше программы с точками останова.
Установка torizoncore 5
На момент написания этого обзора TorizonCore 5 все еще находится в стадии разработки. Мы решили использовать его в нашем обзоре, потому что это будет наша основная ориентированная версия TorizonCore. Посетите веб-страницу Torizon, чтобы увидеть дорожную карту для получения дополнительной информации.
Чтобы установить его в Verdin iMX8MM, сначала вы должны использовать нашу последнюю версию Toradex Easy Installer — 2.0b6, доступную через наши ночные сборки в Toradex Artifactory. Вы можете найти подробные инструкции о том, как его получить, в разделе «Ночной выпуск» на странице «Простой установщик Toradex».
Если в Verdin iMX8MM загружен Toradex Easy Installer 2.0b6, вам необходимо использовать наши каналы CI для загрузки TorizonCore 5, что можно сделать, щелкнув пункт меню «Feeds» в меню Toradex Easy Installer и отметив значок Вариант подачи CI, как показано на рисунке 9.
Этот процесс займет некоторое время, так как он загрузит множество ссылок на изображения из Toradex Artifactory.
После завершения загрузки выберите один образ TorizonCore 5, например «5.1.0-devel-20202119 build.98», выбранный ниже (одна из наших последних ночных сборок), и установите его. Если вы не можете найти эту, выберите самую близкую к вашей дате ночную сборку.
Обратите внимание, что ночные сборки могут быть нестабильными. Если вы обнаружите проблему, не стесняйтесь опубликовать ее в нашем сообществе Toradex, и мы проанализируем ее как можно быстрее. После установки вы сможете получить доступ к TorizonCore 5 через адаптер Verdin Development Board USB-to-Serial X66.
Инструкции о том, как получить доступ к консоли модуля через последовательный порт, представлены в нашей статье «Настройка консоли отладки последовательного порта (Linux / U-Boot)». При первом входе в систему и пользователь, и пароль — torizon. Вам будет предложено изменить пароль, как показано на рисунке 11.
Теперь у нас есть Verdin iMX8MM с TorizonCore 5, и пришло время для практического использования контейнера.