39 lines
5.8 KiB
Python
39 lines
5.8 KiB
Python
import unittest # Стандартный тестовый раннер проверяет wire-контракт без зависимости от GUI.
|
|
from protocan import protocan as pc # Используем публичный построитель и декодер CAN-сообщений.
|
|
from logic_analyzer.decoders.set_can import legacy # Тот же формат должен распознаваться при анализе аппаратного захвата.
|
|
|
|
|
|
class ModbusTests(unittest.TestCase): # Проверяем объединение банков, битовую упаковку и отбраковку неверных окон.
|
|
def test_functions_and_counts(self): # Все шестнадцать сочетаний должны сохранять функцию, количество и значения.
|
|
for function in range(1, 5): # Номера 1…4 соответствуют Coil, Discrete, Holding и Input.
|
|
for count in range(1, 5): # Количество ограничено 1…4 независимо от размера элемента банка.
|
|
body = pc.merge_modbus(0x123, count, function) # Адрес выбран так, чтобы старшие биты отличались от младших служебных полей.
|
|
self.assertEqual(body, 0x1230 | ((function - 1) << 2) | (count - 1)) # Эталон явно задаёт адрес [15:4], функцию [3:2] и количество [1:0].
|
|
self.assertEqual(pc.split_modbus(body), (0x123, count, function)) # Обратное преобразование возвращает физические значения 1…4.
|
|
values = [index % 2 for index in range(count)] if function <= 2 else [0x1234 + index for index in range(count)] # Битовые и регистровые банки содержат различимые последовательности.
|
|
data = bytes([sum(value << index for index, value in enumerate(values))]) if function <= 2 else b''.join(value.to_bytes(2, 'little') for value in values) # Биты начинаются с младшего, слова передаются little-endian.
|
|
identifier = pc.ProtoCanId.build(1, 1, 7, 3, pc.MsgType.MODBUS, body) # Каждый ответ имеет единый MsgType=0x5 и направление от устройства.
|
|
decoded = pc.decode(identifier, data) # Проверяем штатный путь CAN-журнала приложения.
|
|
self.assertFalse(decoded.warnings, decoded.warnings) # Допустимое окно не должно сопровождаться предупреждениями.
|
|
self.assertEqual(decoded.registers, list(zip(range(0x123, 0x123 + count), values))) # Для каждого бита и слова публикуется точный адрес.
|
|
captured = legacy(identifier, data) # Проверяем согласованность анализатора захватов с протокольной библиотекой.
|
|
self.assertEqual((captured['function'], captured['count'], captured['registers']), (function, count, decoded.registers)) # Оба публичных пути возвращают одинаковые физические значения.
|
|
|
|
def test_invalid_inputs_and_payloads(self): # Ошибки должны отклоняться до превращения в другой допустимый wire-код.
|
|
for args in ((0, 0, 1), (0, 5, 1), (0, 1, 0), (0, 1, 5), (-1, 1, 1), (4096, 1, 1), (4095, 2, 4)): # Охватываем обе границы полей и переполнение окна.
|
|
with self.assertRaises(ValueError): pc.merge_modbus(*args) # Неверные значения не маскируются и не оборачиваются.
|
|
self.assertEqual(pc.merge_modbus(4095, 1, 4), 0xFFFC) # Последний адрес остаётся доступным для одного регистра Input.
|
|
for body, data in ((0x1233, b'\x10'), (0x123B, b'\x01\x02'), (0xFFFF, b'\0' * 8), (0x123B, b'\0' * 9)): # Проверяем лишний бит, короткие данные, выход окна за 0xFFF и запрет молчаливой обрезки девятого октета.
|
|
decoded = pc.decode(0x1F350000 | body, data) # Формируем ответ устройства с единым типом 0x5.
|
|
self.assertTrue(decoded.warnings) # Каждая ошибка должна быть видима в журнале.
|
|
self.assertFalse(decoded.registers) # Некорректный payload не становится источником значений для тренда.
|
|
with self.assertRaises(ValueError): legacy(0x1F350000 | body, data) # Анализатор захвата использует те же проверки.
|
|
self.assertFalse(pc.decode(0x1735123F, b'').warnings) # Запрос ПМ с функцией 4 и количеством 4 не содержит данных.
|
|
self.assertTrue(pc.decode(0x1F35123F, b'').warnings) # Пустой ответ прибора является ошибкой протокола.
|
|
for msg_type in (6, 7): # Бывшие отдельные типы Modbus освобождены.
|
|
self.assertTrue(pc.decode(0x1F300000 | (msg_type << 16), b'').warnings) # Декодер не трактует старые MsgType как единый Modbus.
|
|
|
|
|
|
if __name__ == '__main__': # Допускаем прямой запуск теста на хосте.
|
|
unittest.main() # Стандартный код завершения отражает результат проверок.
|