From 2316a5a26a0da11744ee1fc18fd7389c27fd4905 Mon Sep 17 00:00:00 2001 From: Andrey Date: Fri, 4 Sep 2026 20:39:04 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20Android=20API=20=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D0=BA?= =?UTF-8?q?=D0=BE=D0=BB=D0=B0=20=D0=9F=D0=9C35?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- c/set-protocol/README.md | 6 + .../periph28335/Periph28335Protocol.kt | 112 ++++++++++++++++++ .../setcorp/setprotocol/trends/TrendModels.kt | 4 +- .../periph28335/Periph28335ProtocolTest.kt | 35 ++++++ 4 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 c/set-protocol/ports/android/kotlin/ru/setcorp/setprotocol/periph28335/Periph28335Protocol.kt create mode 100644 c/set-protocol/ports/android/tests/ru/setcorp/setprotocol/periph28335/Periph28335ProtocolTest.kt diff --git a/c/set-protocol/README.md b/c/set-protocol/README.md index 6bd2b4d..8842a43 100644 --- a/c/set-protocol/README.md +++ b/c/set-protocol/README.md @@ -21,6 +21,12 @@ TCP, UART/DMA и аппаратный CAN подключаются портам Масштабирование, координаты и измерительные маркеры Android/SETGUI используют общий `set_plot.c`: [границы модулей, ABI и проверки](docs/GUI_PLOT.md). +Прямой терминал ПМ35/TMS320F28335 использует единый wire contract +`Set_Terminal_28335`: функции MODBUS RTU `03/06`, 128 регистров и CRC16. +Эталонные адаптеры находятся в +[`python/protocan/periph28335.py`](../../python/protocan/periph28335.py) и +[`ports/android/kotlin/.../periph28335`](ports/android/kotlin/ru/setcorp/setprotocol/periph28335). + ## Структура | Каталог | Назначение | diff --git a/c/set-protocol/ports/android/kotlin/ru/setcorp/setprotocol/periph28335/Periph28335Protocol.kt b/c/set-protocol/ports/android/kotlin/ru/setcorp/setprotocol/periph28335/Periph28335Protocol.kt new file mode 100644 index 0000000..a08a149 --- /dev/null +++ b/c/set-protocol/ports/android/kotlin/ru/setcorp/setprotocol/periph28335/Periph28335Protocol.kt @@ -0,0 +1,112 @@ +package ru.setcorp.setprotocol.periph28335 + +/** Shared wire protocol migrated from Set_Terminal_28335/DTrans.pas and UNiiefa.pas. */ +object Periph28335Protocol { + const val REGISTER_COUNT = 128 + const val DEFAULT_CONTROLLER = 16 + const val DEFAULT_BAUD_RATE = 115_200 + + val projectCommands: Map> = linkedMapOf( + "По умолчанию" to listOf("Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret", "Light", "Raw", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"), + "23470" to listOf("Test", "Def", "Save", "Load", "Calibr", "Read", "Secret", "-", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"), + "23550" to listOf("Test", "Def", "Save", "Load", "Calibr", "Read", "Secret", "Send", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"), + "23550.2" to listOf("Test", "Def", "Save", "Load", "Calibr", "Calcul", "Secret", "Send", "Raw", "Beep", "", "", "", "", "Log", "Reset", "Nothing at all"), + "ICE 22220.1-3" to listOf("Test", "Zero", "Save", "Def", "Calibr", "Read", "ExtLamp", "ExtLite", "-", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"), + "ICE 22220.4-5" to listOf("Test", "Def", "Save", "Load", "Raw", "Read", "ExtLamp", "ExtLite", "No log", "-", "-", "-", "-", "-", "-", "Reset", "Nothing at all"), + "Бальзам 161" to listOf("Test", "Zero", "Save", "Def", "Calibr", "Clbr 400", "Stop", "Start", "Init", "Secret", "-", "-", "-", "-", "-", "Reset", "Nothing at all"), + "Бальзам 162" to listOf("Test", "Def", "Save", "Load", "Calibr", "Secret", "Stop", "Start", "Init", "Tune", "-", "-", "-", "-", "-", "Reset", "Nothing at all"), + "Бальзам 163" to listOf("Test", "Def", "Save", "Load", "Calibr", "Calcul", "Stop", "Start", "Init", "Tune", "Secret", "-", "-", "-", "-", "Reset", "Nothing at all"), + ) + + fun crc16Modbus(data: ByteArray, initial: Int = 0xFFFF): Int { + var crc = initial and 0xFFFF + data.forEach { value -> + crc = crc xor (value.toInt() and 0xFF) + repeat(8) { + crc = if (crc and 1 != 0) (crc ushr 1) xor 0xA001 else crc ushr 1 + } + } + return crc and 0xFFFF + } + + fun withCrc(payload: ByteArray): ByteArray { + val crc = crc16Modbus(payload) + return payload + byteArrayOf(crc.toByte(), (crc ushr 8).toByte()) + } + + fun buildReadRegisters(controller: Int, start: Int, count: Int): ByteArray { + requireRange("Адрес контроллера", controller, 0xFF) + requireRange("Начальный регистр", start, 0xFFFF) + require(count in 1..REGISTER_COUNT && start + count <= REGISTER_COUNT) { + "Диапазон регистров должен находиться в 0..127" + } + return withCrc(byteArrayOf( + controller.toByte(), 3, + (start ushr 8).toByte(), start.toByte(), + (count ushr 8).toByte(), count.toByte(), + )) + } + + fun buildWriteRegister(controller: Int, address: Int, value: Int): ByteArray { + requireRange("Адрес контроллера", controller, 0xFF) + requireRange("Адрес регистра", address, REGISTER_COUNT - 1) + requireRange("Значение", value, 0xFFFF) + return withCrc(byteArrayOf( + controller.toByte(), 6, + (address ushr 8).toByte(), address.toByte(), + (value ushr 8).toByte(), value.toByte(), + )) + } + + fun buildCommand(controller: Int, commandIndex: Int): ByteArray { + require(commandIndex in 0..16) { "Номер команды должен быть в диапазоне 0..16" } + return buildWriteRegister(controller, 127, if (commandIndex < 16) 1 shl commandIndex else 0) + } + + fun expectedReadResponseSize(count: Int): Int { + require(count in 1..REGISTER_COUNT) + return count * 2 + 5 + } + + fun decodeReadResponse(data: ByteArray, controller: Int, count: Int): List { + val expected = expectedReadResponseSize(count) + require(data.size == expected) { "Ожидалось $expected байт, получено ${data.size}" } + validateCrc(data) + require(data[0].toInt() and 0xFF == controller) { "Ответ другого контроллера" } + require(data[1].toInt() and 0xFF == 3) { "Неверная функция ответа" } + require(data[2].toInt() and 0xFF == count * 2) { "Неверная длина данных ответа" } + return (0 until count).map { index -> + val offset = 3 + index * 2 + ((data[offset].toInt() and 0xFF) shl 8) or (data[offset + 1].toInt() and 0xFF) + } + } + + fun validateWriteResponse(data: ByteArray, request: ByteArray): Boolean = + data.size == 8 && request.size == 8 && data.contentEquals(request) && runCatching { validateCrc(data) }.isSuccess + + fun bitsLsbFirst(value: Int): List { + requireRange("Значение", value, 0xFFFF) + return (0 until 16).map { bit -> value and (1 shl bit) != 0 } + } + + fun wordFromBits(bits: List): Int { + require(bits.size == 16) { "Должно быть ровно 16 бит" } + return bits.foldIndexed(0) { bit, value, checked -> if (checked) value or (1 shl bit) else value } + } + + fun signedWord(value: Int): Int { + requireRange("Значение", value, 0xFFFF) + return if (value < 0x8000) value else value - 0x10000 + } + + private fun validateCrc(data: ByteArray) { + require(data.size >= 2) + val expected = crc16Modbus(data.copyOf(data.size - 2)) + val actual = (data[data.lastIndex - 1].toInt() and 0xFF) or ((data.last().toInt() and 0xFF) shl 8) + require(actual == expected) { "Ошибка CRC ответа" } + } + + private fun requireRange(name: String, value: Int, maximum: Int) { + require(value in 0..maximum) { "$name вне диапазона 0..$maximum" } + } +} diff --git a/c/set-protocol/ports/android/kotlin/ru/setcorp/setprotocol/trends/TrendModels.kt b/c/set-protocol/ports/android/kotlin/ru/setcorp/setprotocol/trends/TrendModels.kt index c254742..54bc95a 100644 --- a/c/set-protocol/ports/android/kotlin/ru/setcorp/setprotocol/trends/TrendModels.kt +++ b/c/set-protocol/ports/android/kotlin/ru/setcorp/setprotocol/trends/TrendModels.kt @@ -6,7 +6,7 @@ enum class TrendSection { SIGNALS, CHART } /** File-format IDs; host applications map their connection profiles to these IDs. */ enum class TrendProfile(val title: String) { - SET_V1("SET GUI v1"), TMS2812("TMS320F2812 / BALZAM"), CAN_BRIDGE("CAN ↔ RS485"), + SET_V1("SET GUI v1"), TMS2812("TMS320F2812 / BALZAM"), TMS28335("TMS320F28335 / ПМ35"), CAN_BRIDGE("CAN ↔ RS485"), GS_USB_CAN("CANgaroo / gs_usb"), SLCAN("SKLab SLCAN"), CANGAROO_SLCAN("CANgaroo / SLCAN"), BALZAM_CAN("Старый CAN BALZAM"), } @@ -29,7 +29,7 @@ enum class TrendValueType(val title: String) { } fun TrendProfile.trendSources(): List = when (this) { - TrendProfile.TMS2812 -> listOf(TrendSource.TMS_MEMORY) + TrendProfile.TMS2812, TrendProfile.TMS28335 -> listOf(TrendSource.TMS_MEMORY) TrendProfile.SET_V1 -> listOf(TrendSource.SET_GAS, TrendSource.SET_SENSOR) TrendProfile.BALZAM_CAN -> listOf(TrendSource.CAN_RAW) else -> listOf(TrendSource.CAN_GAS, TrendSource.CAN_RAW) diff --git a/c/set-protocol/ports/android/tests/ru/setcorp/setprotocol/periph28335/Periph28335ProtocolTest.kt b/c/set-protocol/ports/android/tests/ru/setcorp/setprotocol/periph28335/Periph28335ProtocolTest.kt new file mode 100644 index 0000000..93b7ef0 --- /dev/null +++ b/c/set-protocol/ports/android/tests/ru/setcorp/setprotocol/periph28335/Periph28335ProtocolTest.kt @@ -0,0 +1,35 @@ +package ru.setcorp.setprotocol.periph28335 + +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue +import org.junit.Test + +class Periph28335ProtocolTest { + @Test fun readRequestMatchesDelphiByteOrder() { + val request = Periph28335Protocol.buildReadRegisters(16, 24, 64) + assertTrue(request.copyOf(6).contentEquals(byteArrayOf(0x10, 0x03, 0x00, 0x18, 0x00, 0x40))) + assertEquals( + Periph28335Protocol.crc16Modbus(request.copyOf(6)), + (request[6].toInt() and 0xFF) or ((request[7].toInt() and 0xFF) shl 8), + ) + } + + @Test fun writeAndCommandUseRegister127() { + assertTrue( + Periph28335Protocol.buildWriteRegister(16, 7, 0x1234).copyOf(6) + .contentEquals(byteArrayOf(0x10, 0x06, 0x00, 0x07, 0x12, 0x34)), + ) + assertTrue( + Periph28335Protocol.buildCommand(16, 15).copyOf(6) + .contentEquals(byteArrayOf(0x10, 0x06, 0x00, 0x7F, 0x80.toByte(), 0x00)), + ) + } + + @Test fun responseAndBitsRoundTrip() { + val response = Periph28335Protocol.withCrc(byteArrayOf(0x10, 0x03, 0x04, 0x80.toByte(), 0x05, 0x12, 0x34)) + assertEquals(listOf(0x8005, 0x1234), Periph28335Protocol.decodeReadResponse(response, 16, 2)) + val bits = Periph28335Protocol.bitsLsbFirst(0x8005) + assertTrue(bits[0] && bits[2] && bits[15]) + assertEquals(0x8005, Periph28335Protocol.wordFromBits(bits)) + } +}