feat(tms2812): добавь общий протокол терминала и привязки Python и Android
This commit is contained in:
37
c/set-protocol/TMS2812.md
Normal file
37
c/set-protocol/TMS2812.md
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
# Legacy TMS2812 / BALZAM protocol
|
||||||
|
|
||||||
|
`src/tms2812.c` is the shared wire implementation for the host terminal,
|
||||||
|
memory access, firmware framing and CRC validation. The wire format is the
|
||||||
|
legacy controller protocol, **not SETProtocol v2**. The shared library name
|
||||||
|
does not select or change the wire format.
|
||||||
|
|
||||||
|
- `tms2812_build_memory_request`: PEEK (56), POKE (57), host operation 0
|
||||||
|
for PING (PEEK of service word address `0x00000100`).
|
||||||
|
- `tms2812_build_test_request`: TEST_ALL (66), two uint16 timings and
|
||||||
|
twelve control bytes.
|
||||||
|
- `tms2812_build_fixed_request`: address, command, payload, CRC16/MODBUS.
|
||||||
|
- `tms2812_validate_response`: checks controller, command, length and CRC;
|
||||||
|
may accept omission of the two reserved bytes after CRC. The transport
|
||||||
|
must wait for a receive gap before sending the next command.
|
||||||
|
- UPLOAD (52) retains its separate validator: four reserved trailing bytes.
|
||||||
|
|
||||||
|
Bindings:
|
||||||
|
|
||||||
|
- Python: `python/protocan/tms_terminal.py` calls the exported `pcan_abi.h`
|
||||||
|
functions through ctypes. `set_devices.tms_terminal` keeps telemetry
|
||||||
|
layouts/parsing; it uses the C framing for CMD_STD.
|
||||||
|
- Kotlin: `ports/android/kotlin/ru/setcorp/setflash/core/Tms2812Protocol.kt`
|
||||||
|
calls the same C functions through `setprotocol_jni.c` and
|
||||||
|
`NativeSetProtocol`. Consumers include the templates Kotlin source tree;
|
||||||
|
they must not keep a second Tms2812Protocol class in their app source tree.
|
||||||
|
|
||||||
|
Consumers own UART I/O, timeout/settle timers, sequential write/readback
|
||||||
|
transactions, GUI field names and JSON display profiles. They do not own
|
||||||
|
copies of request builders or response CRC checks.
|
||||||
|
|
||||||
|
Rebuild the native library after updating templates. There is no fallback
|
||||||
|
to a separate local encoder if the terminal ABI is missing.
|
||||||
|
|
||||||
|
Tests: `tests/test_tms2812.c`, `python/tests/test_tms_terminal_shared.py`,
|
||||||
|
and the Android `TmsTerminalSharedTest`. All use the same Android/captured
|
||||||
|
PING and PEEK vectors and TEST_ALL field layout.
|
||||||
@@ -102,6 +102,14 @@ PCAN_ABI_API const char *pcan_abi_periph28335_command_name(
|
|||||||
size_t project_index, size_t command_index);
|
size_t project_index, size_t command_index);
|
||||||
|
|
||||||
/* PM67/TMS320F2812 legacy memory upload. */
|
/* PM67/TMS320F2812 legacy memory upload. */
|
||||||
|
PCAN_ABI_API size_t pcan_abi_tms2812_build_fixed(uint8_t controller, uint8_t command,
|
||||||
|
const uint8_t *payload, size_t payload_size, uint8_t *output, size_t output_size);
|
||||||
|
PCAN_ABI_API size_t pcan_abi_tms2812_build_memory(uint8_t controller, uint8_t command,
|
||||||
|
uint32_t word_address, uint16_t value, uint8_t *output, size_t output_size);
|
||||||
|
PCAN_ABI_API size_t pcan_abi_tms2812_build_test(uint8_t controller, uint16_t duration,
|
||||||
|
uint16_t pause, const uint8_t *digits, size_t digit_count, uint8_t *output, size_t output_size);
|
||||||
|
PCAN_ABI_API int pcan_abi_tms2812_validate_response(const uint8_t *data, size_t size,
|
||||||
|
uint8_t controller, uint8_t command, size_t full_size, int allow_short_tail);
|
||||||
PCAN_ABI_API uint16_t pcan_abi_tms2812_crc16(
|
PCAN_ABI_API uint16_t pcan_abi_tms2812_crc16(
|
||||||
const uint8_t *data, size_t size);
|
const uint8_t *data, size_t size);
|
||||||
PCAN_ABI_API size_t pcan_abi_tms2812_build_upload(
|
PCAN_ABI_API size_t pcan_abi_tms2812_build_upload(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/**
|
/**
|
||||||
* @file tms2812.h
|
* @file tms2812.h
|
||||||
* @brief Shared PM67/TMS320F2812 legacy upload protocol.
|
* @brief Shared PM67/TMS320F2812 legacy terminal and memory protocol.
|
||||||
*/
|
*/
|
||||||
#ifndef TMS2812_H
|
#ifndef TMS2812_H
|
||||||
#define TMS2812_H
|
#define TMS2812_H
|
||||||
@@ -13,6 +13,10 @@ extern "C" {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define TMS2812_CMD_UPLOAD 52U
|
#define TMS2812_CMD_UPLOAD 52U
|
||||||
|
#define TMS2812_CMD_PEEK 56U
|
||||||
|
#define TMS2812_CMD_POKE 57U
|
||||||
|
#define TMS2812_CMD_TEST_ALL 66U
|
||||||
|
#define TMS2812_PING_ADDRESS 0x00000100UL
|
||||||
#define TMS2812_UPLOAD_REQUEST_SIZE 12U
|
#define TMS2812_UPLOAD_REQUEST_SIZE 12U
|
||||||
#define TMS2812_UPLOAD_RESPONSE_OVERHEAD 8U
|
#define TMS2812_UPLOAD_RESPONSE_OVERHEAD 8U
|
||||||
|
|
||||||
@@ -28,6 +32,20 @@ typedef enum {
|
|||||||
|
|
||||||
uint16_t tms2812_crc16(const uint8_t *data, size_t size);
|
uint16_t tms2812_crc16(const uint8_t *data, size_t size);
|
||||||
|
|
||||||
|
size_t tms2812_build_fixed_request(uint8_t controller, uint8_t command,
|
||||||
|
const uint8_t *payload, size_t payload_size, uint8_t *output, size_t output_size);
|
||||||
|
/* command=0 is the host PING operation; on wire it is PEEK at 0x100. */
|
||||||
|
size_t tms2812_build_memory_request(uint8_t controller, uint8_t command,
|
||||||
|
uint32_t word_address, uint16_t value, uint8_t *output, size_t output_size);
|
||||||
|
size_t tms2812_build_test_request(uint8_t controller, uint16_t duration,
|
||||||
|
uint16_t pause, const uint8_t *digits, size_t digit_count,
|
||||||
|
uint8_t *output, size_t output_size);
|
||||||
|
/* Ordinary BALZAM responses reserve two bytes after CRC. If allow_short_tail
|
||||||
|
* is true only those bytes may be omitted; caller must observe the UART gap.
|
||||||
|
* UPLOAD responses use tms2812_validate_upload_response instead. */
|
||||||
|
int tms2812_validate_response(const uint8_t *data, size_t size,
|
||||||
|
uint8_t controller, uint8_t command, size_t full_size, int allow_short_tail);
|
||||||
|
|
||||||
size_t tms2812_build_upload_request(
|
size_t tms2812_build_upload_request(
|
||||||
uint8_t controller, uint32_t word_address, uint32_t byte_count,
|
uint8_t controller, uint32_t word_address, uint32_t byte_count,
|
||||||
uint8_t *output, size_t output_size);
|
uint8_t *output, size_t output_size);
|
||||||
|
|||||||
@@ -0,0 +1,263 @@
|
|||||||
|
package ru.setcorp.setflash.core
|
||||||
|
|
||||||
|
import ru.setcorp.setprotocol.NativeSetProtocol
|
||||||
|
|
||||||
|
/** Shared templates facade for BALZAM TMS320F2812; framing and CRC use C99. */
|
||||||
|
object Tms2812Protocol {
|
||||||
|
const val CMD_LOAD = 51
|
||||||
|
const val CMD_UPLOAD = 52
|
||||||
|
const val CMD_RUN = 53
|
||||||
|
const val CMD_XFLASH = 54
|
||||||
|
const val CMD_TFLASH = 55
|
||||||
|
const val CMD_PEEK = 56
|
||||||
|
const val CMD_POKE = 57
|
||||||
|
const val CMD_INITLOAD = 58
|
||||||
|
const val CMD_INIT = 59
|
||||||
|
const val CMD_EXTEND = 60
|
||||||
|
const val CMD_VECTOR = 61
|
||||||
|
const val CMD_IMPULSE = 62
|
||||||
|
const val CMD_STD = 65
|
||||||
|
const val CMD_TEST_ALL = 66
|
||||||
|
|
||||||
|
// BALZAM CHIEF bios.c returns 0x0055 at ADR_FOR_SPECIAL without reading
|
||||||
|
// the peripheral bus. Address 0 goes through read_bus_special(), whose
|
||||||
|
// failure suppresses the reply entirely, so it is unsuitable for PING.
|
||||||
|
const val PING_MEMORY_ADDRESS = 0x00000100L
|
||||||
|
|
||||||
|
// extendbios() operation selectors from BALZAM_167/Src/mybalzam_7/bios.c.
|
||||||
|
const val EXTEND_FLASH_REMOTE_EEPROM = 6
|
||||||
|
const val EXTEND_READ_REMOTE_EEPROM = 7
|
||||||
|
const val EXTEND_RESET_TO_INTERNAL_FLASH = 9
|
||||||
|
const val EXTEND_FLASH_SPARTAN6 = 10
|
||||||
|
const val EXTEND_VERIFY_REMOTE_EEPROM = 17
|
||||||
|
|
||||||
|
const val ACK_SIZE = 6
|
||||||
|
const val PEEK_RESPONSE_SIZE = 10
|
||||||
|
const val STD_RESPONSE_SIZE = 112
|
||||||
|
const val UPLOAD_RESPONSE_OVERHEAD = 8
|
||||||
|
const val EXTEND_RESPONSE_SIZE = 18
|
||||||
|
|
||||||
|
data class StandardResponse(
|
||||||
|
val digital: ByteArray,
|
||||||
|
val analog: List<Int>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class ExtendResponse(
|
||||||
|
val processedWords: Long,
|
||||||
|
val errorCode: Long,
|
||||||
|
val repeatErrors: Long,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun crc16(data: ByteArray, initial: Int = 0xFFFF): Int {
|
||||||
|
require(initial == 0xFFFF) {
|
||||||
|
"Произвольное начальное значение CRC не входит в протокол ПМ67"
|
||||||
|
}
|
||||||
|
requireNative()
|
||||||
|
return NativeSetProtocol.nativeTms2812Crc16(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun initLoad(address: Int, ramAddress: Long, length: Int): ByteArray = fixed(
|
||||||
|
address,
|
||||||
|
CMD_INITLOAD,
|
||||||
|
le32(ramAddress) + le32(length.toLong()),
|
||||||
|
12,
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* LOAD is address + command, followed by raw data and CRC. RS485.c consumes
|
||||||
|
* the first two bytes as a normal header, switches RS_FlagBegin off for
|
||||||
|
* CMD_LOAD, and stores the remaining Length + 2 bytes in RAM.
|
||||||
|
*/
|
||||||
|
fun loadData(address: Int, image: ByteArray): ByteArray {
|
||||||
|
return fixed(address, CMD_LOAD, image, image.size + 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun tFlash(address: Int, ramAddress: Long, flashAddress: Long, length: Int): ByteArray = fixed(
|
||||||
|
address,
|
||||||
|
CMD_TFLASH,
|
||||||
|
le32(ramAddress) + le32(flashAddress) + le32(length.toLong()),
|
||||||
|
16,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun upload(address: Int, memoryAddress: Long, length: Int): ByteArray {
|
||||||
|
require(address in 0..255) { "Адрес контроллера должен быть 0…255" }
|
||||||
|
require(memoryAddress in 0..0xFFFF_FFFFL) { "Адрес слова вне диапазона uint32" }
|
||||||
|
require(length > 0) { "Размер блока должен быть положительным" }
|
||||||
|
requireNative()
|
||||||
|
return requireNotNull(
|
||||||
|
NativeSetProtocol.nativeTms2812BuildUpload(
|
||||||
|
address, memoryAddress, length.toLong(),
|
||||||
|
),
|
||||||
|
) { "SETProtocol отклонил CMD_UPLOAD ПМ67" }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* bios.c sends UPLOAD as address, command, packed bytes, CRC16 and four
|
||||||
|
* trailing bytes. Unlike ordinary replies its CRC immediately follows data.
|
||||||
|
*/
|
||||||
|
fun validateUploadResponse(packet: ByteArray, address: Int, length: Int): Boolean {
|
||||||
|
if (length <= 0 || address !in 0..255) return false
|
||||||
|
requireNative()
|
||||||
|
return NativeSetProtocol.nativeTms2812ValidateUpload(packet, address, length)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun uploadData(packet: ByteArray, address: Int, length: Int): ByteArray {
|
||||||
|
requireNative()
|
||||||
|
return requireNotNull(
|
||||||
|
NativeSetProtocol.nativeTms2812DecodeUpload(packet, address, length),
|
||||||
|
) { "Поврежден ответ CMD_UPLOAD" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun peek(address: Int, memoryAddress: Long): ByteArray =
|
||||||
|
memory(address, CMD_PEEK, memoryAddress, 0)
|
||||||
|
|
||||||
|
fun ping(address: Int): ByteArray = memory(address, 0, 0, 0)
|
||||||
|
|
||||||
|
fun poke(address: Int, memoryAddress: Long, value: Int): ByteArray =
|
||||||
|
memory(address, CMD_POKE, memoryAddress, value)
|
||||||
|
|
||||||
|
private fun memory(address: Int, command: Int, memoryAddress: Long, value: Int): ByteArray {
|
||||||
|
require(address in 0..255 && memoryAddress in 0..0xFFFF_FFFFL && value in 0..65535) {
|
||||||
|
"Адрес или значение TMS2812 вне диапазона"
|
||||||
|
}
|
||||||
|
requireNative()
|
||||||
|
return requireNotNull(NativeSetProtocol.nativeTms2812BuildMemory(address, command, memoryAddress, value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun testAll(address: Int, duration: Int, pause: Int, digits: ByteArray): ByteArray {
|
||||||
|
require(address in 0..255 && duration in 0..65535 && pause in 0..65535 && digits.size == 12)
|
||||||
|
requireNative()
|
||||||
|
return requireNotNull(NativeSetProtocol.nativeTms2812BuildTest(address, duration, pause, digits))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun extend(
|
||||||
|
address: Int,
|
||||||
|
firstAddress: Long,
|
||||||
|
secondAddress: Long,
|
||||||
|
length: Long,
|
||||||
|
code1: Int,
|
||||||
|
code2: Int,
|
||||||
|
): ByteArray = fixed(
|
||||||
|
address,
|
||||||
|
CMD_EXTEND,
|
||||||
|
le32(firstAddress) + le32(secondAddress) + le32(length) + byteArrayOf(code1.toByte(), code2.toByte()),
|
||||||
|
18,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun parseExtendResponse(packet: ByteArray, address: Int): ExtendResponse {
|
||||||
|
require(packet.size == EXTEND_RESPONSE_SIZE && validateResponse(packet, address, CMD_EXTEND)) {
|
||||||
|
"Поврежден ответ CMD_EXTEND"
|
||||||
|
}
|
||||||
|
return ExtendResponse(
|
||||||
|
processedWords = le32Value(packet, 2),
|
||||||
|
errorCode = le32Value(packet, 6),
|
||||||
|
repeatErrors = le32Value(packet, 10),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun standard(address: Int, analog: List<Int>, flags: Set<Int>): ByteArray {
|
||||||
|
require(analog.size == 4) { "CMD_STD должен содержать четыре аналоговых значения" }
|
||||||
|
val payload = ByteArray(14)
|
||||||
|
analog.forEachIndexed { index, value ->
|
||||||
|
require(value in Short.MIN_VALUE..Short.MAX_VALUE) { "Аналог ${index + 1} вне диапазона Int16" }
|
||||||
|
payload[index * 2] = value.toByte()
|
||||||
|
payload[index * 2 + 1] = (value ushr 8).toByte()
|
||||||
|
}
|
||||||
|
flags.forEach { oneBased ->
|
||||||
|
require(oneBased in 1..48) { "Номер флага должен быть от 1 до 48" }
|
||||||
|
val bit = oneBased - 1
|
||||||
|
payload[8 + bit / 8] = (payload[8 + bit / 8].toInt() or (1 shl (bit % 8))).toByte()
|
||||||
|
}
|
||||||
|
return fixed(address, CMD_STD, payload, 18)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun validateResponse(packet: ByteArray, address: Int, command: Int): Boolean {
|
||||||
|
requireNative()
|
||||||
|
return NativeSetProtocol.nativeTms2812ValidateResponse(packet, address, command, packet.size, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BALZAM replies can omit final reserved bytes after CRC (two in ordinary
|
||||||
|
* replies, four in UPLOAD data replies).
|
||||||
|
* Call after the receive settle interval so a delayed tail cannot overlap
|
||||||
|
* the next request. Never pad missing data or CRC bytes. UPLOAD uses its
|
||||||
|
* own validator because its CRC precedes four trailing bytes, not two.
|
||||||
|
*/
|
||||||
|
fun normalizeResponseTail(
|
||||||
|
packet: ByteArray,
|
||||||
|
address: Int,
|
||||||
|
command: Int,
|
||||||
|
fullSize: Int,
|
||||||
|
): ByteArray? {
|
||||||
|
val uploadData = command == CMD_UPLOAD && fullSize >= UPLOAD_RESPONSE_OVERHEAD
|
||||||
|
val reservedBytes = if (uploadData) 4 else 2
|
||||||
|
if (fullSize < ACK_SIZE || packet.size !in (fullSize - reservedBytes)..fullSize) return null
|
||||||
|
val normalized = packet.copyOf(fullSize)
|
||||||
|
val valid = if (uploadData) {
|
||||||
|
validateUploadResponse(normalized, address, fullSize - UPLOAD_RESPONSE_OVERHEAD)
|
||||||
|
} else {
|
||||||
|
requireNative()
|
||||||
|
NativeSetProtocol.nativeTms2812ValidateResponse(packet, address, command, fullSize, true)
|
||||||
|
}
|
||||||
|
return normalized.takeIf { valid }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun peekValue(packet: ByteArray, address: Int): Int {
|
||||||
|
require(packet.size == PEEK_RESPONSE_SIZE && validateResponse(packet, address, CMD_PEEK)) {
|
||||||
|
"Поврежден ответ CMD_PEEK"
|
||||||
|
}
|
||||||
|
return (packet[2].toInt() and 0xFF) or ((packet[3].toInt() and 0xFF) shl 8)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun parseStandard(packet: ByteArray, address: Int): StandardResponse {
|
||||||
|
require(packet.size == STD_RESPONSE_SIZE && validateResponse(packet, address, CMD_STD)) {
|
||||||
|
"Поврежден ответ CMD_STD"
|
||||||
|
}
|
||||||
|
val digital = packet.copyOfRange(2, 40)
|
||||||
|
val analog = (0 until 34).map { index ->
|
||||||
|
val offset = 40 + index * 2
|
||||||
|
(((packet[offset].toInt() and 0xFF) or
|
||||||
|
((packet[offset + 1].toInt() and 0xFF) shl 8)).toShort()).toInt()
|
||||||
|
}
|
||||||
|
return StandardResponse(digital, analog)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun commandName(command: Int): String = when (command) {
|
||||||
|
CMD_LOAD -> "CMD_LOAD"
|
||||||
|
CMD_UPLOAD -> "CMD_UPLOAD"
|
||||||
|
CMD_RUN -> "CMD_RUN"
|
||||||
|
CMD_XFLASH -> "CMD_XFLASH"
|
||||||
|
CMD_TFLASH -> "CMD_TFLASH"
|
||||||
|
CMD_PEEK -> "CMD_PEEK"
|
||||||
|
CMD_POKE -> "CMD_POKE"
|
||||||
|
CMD_INITLOAD -> "CMD_INITLOAD"
|
||||||
|
CMD_INIT -> "CMD_INIT"
|
||||||
|
CMD_EXTEND -> "CMD_EXTEND"
|
||||||
|
CMD_VECTOR -> "CMD_VECTOR"
|
||||||
|
CMD_IMPULSE -> "CMD_IMPULSE"
|
||||||
|
CMD_STD -> "CMD_STD"
|
||||||
|
CMD_TEST_ALL -> "CMD_TEST_ALL"
|
||||||
|
else -> "CMD_$command"
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun fixed(address: Int, command: Int, payload: ByteArray, size: Int): ByteArray {
|
||||||
|
require(address in 0..255) { "Адрес контроллера должен быть 0…255" }
|
||||||
|
require(payload.size + 4 == size) { "Неверная длина пакета" }
|
||||||
|
requireNative()
|
||||||
|
return requireNotNull(NativeSetProtocol.nativeTms2812BuildFixed(address, command, payload))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun le32(value: Long): ByteArray = ByteArray(4) { index ->
|
||||||
|
(value ushr (index * 8)).toByte()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun le32Value(data: ByteArray, offset: Int): Long =
|
||||||
|
(data[offset].toLong() and 0xFF) or
|
||||||
|
((data[offset + 1].toLong() and 0xFF) shl 8) or
|
||||||
|
((data[offset + 2].toLong() and 0xFF) shl 16) or
|
||||||
|
((data[offset + 3].toLong() and 0xFF) shl 24)
|
||||||
|
|
||||||
|
private fun requireNative() {
|
||||||
|
check(NativeSetProtocol.available) { "Нативное ядро SETProtocol недоступно" }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,11 @@ object NativeSetProtocol {
|
|||||||
external fun nativePeriph28335ProjectName(projectIndex: Int): String?
|
external fun nativePeriph28335ProjectName(projectIndex: Int): String?
|
||||||
external fun nativePeriph28335CommandName(projectIndex: Int, commandIndex: Int): String?
|
external fun nativePeriph28335CommandName(projectIndex: Int, commandIndex: Int): String?
|
||||||
external fun nativeTms2812Crc16(input: ByteArray): Int
|
external fun nativeTms2812Crc16(input: ByteArray): Int
|
||||||
|
external fun nativeTms2812BuildFixed(controller: Int, command: Int, payload: ByteArray): ByteArray?
|
||||||
|
external fun nativeTms2812BuildMemory(controller: Int, command: Int, address: Long, value: Int): ByteArray?
|
||||||
|
external fun nativeTms2812BuildTest(controller: Int, duration: Int, pause: Int, digits: ByteArray): ByteArray?
|
||||||
|
external fun nativeTms2812ValidateResponse(input: ByteArray, controller: Int, command: Int,
|
||||||
|
fullSize: Int, allowShortTail: Boolean): Boolean
|
||||||
external fun nativeTms2812BuildUpload(controller: Int, wordAddress: Long, byteCount: Long): ByteArray?
|
external fun nativeTms2812BuildUpload(controller: Int, wordAddress: Long, byteCount: Long): ByteArray?
|
||||||
external fun nativeTms2812ExpectedUploadSize(byteCount: Long): Int
|
external fun nativeTms2812ExpectedUploadSize(byteCount: Long): Int
|
||||||
external fun nativeTms2812ValidateUpload(input: ByteArray, controller: Int, byteCount: Int): Boolean
|
external fun nativeTms2812ValidateUpload(input: ByteArray, controller: Int, byteCount: Int): Boolean
|
||||||
|
|||||||
@@ -10,6 +10,79 @@
|
|||||||
#include "periph28335.h"
|
#include "periph28335.h"
|
||||||
#include "tms2812.h"
|
#include "tms2812.h"
|
||||||
|
|
||||||
|
static jbyteArray tms_result(JNIEnv *env, const uint8_t *data, size_t size)
|
||||||
|
{
|
||||||
|
if (size == 0U || size > INT32_MAX) return NULL;
|
||||||
|
jbyteArray result = (*env)->NewByteArray(env, (jsize)size);
|
||||||
|
if (result != NULL) (*env)->SetByteArrayRegion(env, result, 0, (jsize)size, (const jbyte *)data);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jbyteArray JNICALL
|
||||||
|
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812BuildFixed(
|
||||||
|
JNIEnv *env, jobject self, jint controller, jint command, jbyteArray input)
|
||||||
|
{
|
||||||
|
(void)self;
|
||||||
|
if (input == NULL || controller < 0 || controller > 255 || command < 0 || command > 255) return NULL;
|
||||||
|
jsize size = (*env)->GetArrayLength(env, input);
|
||||||
|
if (size > INT32_MAX - 4) return NULL;
|
||||||
|
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
|
||||||
|
if (data == NULL) return NULL;
|
||||||
|
uint8_t *output = (uint8_t *)malloc((size_t)size + 4U);
|
||||||
|
if (output == NULL) { (*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT); return NULL; }
|
||||||
|
size_t written = tms2812_build_fixed_request((uint8_t)controller, (uint8_t)command,
|
||||||
|
(const uint8_t *)data, (size_t)size, output, (size_t)size + 4U);
|
||||||
|
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||||
|
jbyteArray result = tms_result(env, output, written);
|
||||||
|
free(output);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jbyteArray JNICALL
|
||||||
|
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812BuildMemory(
|
||||||
|
JNIEnv *env, jobject self, jint controller, jint command, jlong address, jint value)
|
||||||
|
{
|
||||||
|
(void)self;
|
||||||
|
uint8_t output[12];
|
||||||
|
if (controller < 0 || controller > 255 || command < 0 || command > 255 ||
|
||||||
|
address < 0 || (uint64_t)address > UINT32_MAX || value < 0 || value > 65535) return NULL;
|
||||||
|
size_t written = tms2812_build_memory_request((uint8_t)controller, (uint8_t)command,
|
||||||
|
(uint32_t)address, (uint16_t)value, output, sizeof output);
|
||||||
|
return tms_result(env, output, written);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jbyteArray JNICALL
|
||||||
|
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812BuildTest(
|
||||||
|
JNIEnv *env, jobject self, jint controller, jint duration, jint pause, jbyteArray input)
|
||||||
|
{
|
||||||
|
(void)self;
|
||||||
|
uint8_t output[20];
|
||||||
|
if (input == NULL || controller < 0 || controller > 255 || duration < 0 ||
|
||||||
|
duration > 65535 || pause < 0 || pause > 65535 || (*env)->GetArrayLength(env, input) != 12) return NULL;
|
||||||
|
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
|
||||||
|
if (data == NULL) return NULL;
|
||||||
|
size_t written = tms2812_build_test_request((uint8_t)controller, (uint16_t)duration,
|
||||||
|
(uint16_t)pause, (const uint8_t *)data, 12U, output, sizeof output);
|
||||||
|
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||||
|
return tms_result(env, output, written);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT jboolean JNICALL
|
||||||
|
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812ValidateResponse(
|
||||||
|
JNIEnv *env, jobject self, jbyteArray input, jint controller, jint command,
|
||||||
|
jint full_size, jboolean allow_short_tail)
|
||||||
|
{
|
||||||
|
(void)self;
|
||||||
|
if (input == NULL || controller < 0 || controller > 255 || command < 0 || command > 255 || full_size < 6) return JNI_FALSE;
|
||||||
|
jsize size = (*env)->GetArrayLength(env, input);
|
||||||
|
jbyte *data = (*env)->GetByteArrayElements(env, input, NULL);
|
||||||
|
if (data == NULL) return JNI_FALSE;
|
||||||
|
int status = tms2812_validate_response((const uint8_t *)data, (size_t)size,
|
||||||
|
(uint8_t)controller, (uint8_t)command, (size_t)full_size, allow_short_tail != JNI_FALSE);
|
||||||
|
(*env)->ReleaseByteArrayElements(env, input, data, JNI_ABORT);
|
||||||
|
return status == TMS2812_OK ? JNI_TRUE : JNI_FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
JNIEXPORT jint JNICALL
|
JNIEXPORT jint JNICALL
|
||||||
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812Crc16(
|
Java_ru_setcorp_setprotocol_NativeSetProtocol_nativeTms2812Crc16(
|
||||||
JNIEnv *env, jobject self, jbyteArray input)
|
JNIEnv *env, jobject self, jbyteArray input)
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package ru.setcorp.setflash.core
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class TmsTerminalSharedTest {
|
||||||
|
@Test fun testAllUsesSharedNativeFraming() {
|
||||||
|
val digits = byteArrayOf(0x81.toByte(), 2, 4, 8, 9, 0x20, 0x10, 0x20, 0x40, 0x80.toByte(), 3, 4)
|
||||||
|
val packet = Tms2812Protocol.testAll(7, 25, 18, digits)
|
||||||
|
assertEquals(20, packet.size)
|
||||||
|
assertArrayEquals(byteArrayOf(7, 66, 25, 0, 18, 0) + digits, packet.copyOf(18))
|
||||||
|
assertEquals(0, Tms2812Protocol.crc16(packet))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test fun pokeKeepsWordAddressAndZeroPadding() {
|
||||||
|
val packet = Tms2812Protocol.poke(10, 0x12345678L, 0xABCD)
|
||||||
|
assertArrayEquals(byteArrayOf(10, 57, 0x78, 0x56, 0x34, 0x12,
|
||||||
|
0xCD.toByte(), 0xAB.toByte(), 0, 0), packet.copyOf(10))
|
||||||
|
assertEquals(0, Tms2812Protocol.crc16(packet))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test(expected = IllegalArgumentException::class)
|
||||||
|
fun invalidAddressIsNotTruncated() {
|
||||||
|
Tms2812Protocol.peek(10, 0x1_0000_0000L)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -168,6 +168,30 @@ uint16_t pcan_abi_tms2812_crc16(const uint8_t *data, size_t size)
|
|||||||
return tms2812_crc16(data, size);
|
return tms2812_crc16(data, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t pcan_abi_tms2812_build_fixed(uint8_t controller, uint8_t command,
|
||||||
|
const uint8_t *payload, size_t payload_size, uint8_t *output, size_t output_size)
|
||||||
|
{
|
||||||
|
return tms2812_build_fixed_request(controller, command, payload, payload_size, output, output_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pcan_abi_tms2812_build_memory(uint8_t controller, uint8_t command,
|
||||||
|
uint32_t word_address, uint16_t value, uint8_t *output, size_t output_size)
|
||||||
|
{
|
||||||
|
return tms2812_build_memory_request(controller, command, word_address, value, output, output_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t pcan_abi_tms2812_build_test(uint8_t controller, uint16_t duration,
|
||||||
|
uint16_t pause, const uint8_t *digits, size_t digit_count, uint8_t *output, size_t output_size)
|
||||||
|
{
|
||||||
|
return tms2812_build_test_request(controller, duration, pause, digits, digit_count, output, output_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int pcan_abi_tms2812_validate_response(const uint8_t *data, size_t size,
|
||||||
|
uint8_t controller, uint8_t command, size_t full_size, int allow_short_tail)
|
||||||
|
{
|
||||||
|
return tms2812_validate_response(data, size, controller, command, full_size, allow_short_tail);
|
||||||
|
}
|
||||||
|
|
||||||
size_t pcan_abi_tms2812_build_upload(
|
size_t pcan_abi_tms2812_build_upload(
|
||||||
uint8_t controller, uint32_t word_address, uint32_t byte_count,
|
uint8_t controller, uint32_t word_address, uint32_t byte_count,
|
||||||
uint8_t *output, size_t output_size)
|
uint8_t *output, size_t output_size)
|
||||||
|
|||||||
@@ -18,6 +18,61 @@ uint16_t tms2812_crc16(const uint8_t *data, size_t size)
|
|||||||
return set_crc16_modbus(data, size);
|
return set_crc16_modbus(data, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
size_t tms2812_build_fixed_request(uint8_t controller, uint8_t command,
|
||||||
|
const uint8_t *payload, size_t payload_size, uint8_t *output, size_t output_size)
|
||||||
|
{
|
||||||
|
uint16_t crc;
|
||||||
|
if (output == NULL || (payload == NULL && payload_size != 0U) ||
|
||||||
|
payload_size > SIZE_MAX - 4U || output_size < payload_size + 4U) return 0U;
|
||||||
|
output[0] = controller;
|
||||||
|
output[1] = command;
|
||||||
|
if (payload_size != 0U) memcpy(output + 2U, payload, payload_size);
|
||||||
|
crc = tms2812_crc16(output, payload_size + 2U);
|
||||||
|
output[payload_size + 2U] = (uint8_t)crc;
|
||||||
|
output[payload_size + 3U] = (uint8_t)(crc >> 8);
|
||||||
|
return payload_size + 4U;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t tms2812_build_memory_request(uint8_t controller, uint8_t command,
|
||||||
|
uint32_t word_address, uint16_t value, uint8_t *output, size_t output_size)
|
||||||
|
{
|
||||||
|
uint8_t payload[8];
|
||||||
|
if (command == 0U) { command = TMS2812_CMD_PEEK; word_address = TMS2812_PING_ADDRESS; }
|
||||||
|
if (command != TMS2812_CMD_PEEK && command != TMS2812_CMD_POKE) return 0U;
|
||||||
|
put_le32(payload, word_address);
|
||||||
|
put_le32(payload + 4U, value);
|
||||||
|
return tms2812_build_fixed_request(controller, command, payload,
|
||||||
|
command == TMS2812_CMD_PEEK ? 4U : 8U, output, output_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t tms2812_build_test_request(uint8_t controller, uint16_t duration,
|
||||||
|
uint16_t pause, const uint8_t *digits, size_t digit_count,
|
||||||
|
uint8_t *output, size_t output_size)
|
||||||
|
{
|
||||||
|
uint8_t payload[16];
|
||||||
|
if (digits == NULL || digit_count != 12U) return 0U;
|
||||||
|
payload[0] = (uint8_t)duration; payload[1] = (uint8_t)(duration >> 8);
|
||||||
|
payload[2] = (uint8_t)pause; payload[3] = (uint8_t)(pause >> 8);
|
||||||
|
memcpy(payload + 4U, digits, 12U);
|
||||||
|
return tms2812_build_fixed_request(controller, TMS2812_CMD_TEST_ALL,
|
||||||
|
payload, sizeof payload, output, output_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
int tms2812_validate_response(const uint8_t *data, size_t size,
|
||||||
|
uint8_t controller, uint8_t command, size_t full_size, int allow_short_tail)
|
||||||
|
{
|
||||||
|
size_t offset;
|
||||||
|
uint16_t crc;
|
||||||
|
if (data == NULL) return TMS2812_ERROR_ARGUMENT;
|
||||||
|
if (full_size < 6U) return TMS2812_ERROR_ARGUMENT;
|
||||||
|
if (size > full_size || size < full_size - (allow_short_tail ? 2U : 0U))
|
||||||
|
return TMS2812_ERROR_LENGTH;
|
||||||
|
if (data[0] != controller || data[1] != command) return TMS2812_ERROR_HEADER;
|
||||||
|
offset = full_size - 4U;
|
||||||
|
crc = (uint16_t)(data[offset] | ((uint16_t)data[offset + 1U] << 8));
|
||||||
|
return crc == tms2812_crc16(data, offset) ? TMS2812_OK : TMS2812_ERROR_CRC;
|
||||||
|
}
|
||||||
|
|
||||||
size_t tms2812_build_upload_request(
|
size_t tms2812_build_upload_request(
|
||||||
uint8_t controller, uint32_t word_address, uint32_t byte_count,
|
uint8_t controller, uint32_t word_address, uint32_t byte_count,
|
||||||
uint8_t *output, size_t output_size)
|
uint8_t *output, size_t output_size)
|
||||||
|
|||||||
@@ -3,8 +3,40 @@
|
|||||||
|
|
||||||
#include "tms2812.h"
|
#include "tms2812.h"
|
||||||
|
|
||||||
|
static int test_terminal(void)
|
||||||
|
{
|
||||||
|
uint8_t output[20], digits[12] = {0x81, 2, 4, 8, 9, 0x20, 0x10, 0x20, 0x40, 0x80, 3, 4};
|
||||||
|
const uint8_t ping[] = {0x0a, 0x38, 0, 1, 0, 0, 0xf0, 0xb4};
|
||||||
|
uint8_t reply[] = {0x0a, 0x38, 0x65, 0xa5, 0, 0, 0xaf, 0x9b, 0, 0};
|
||||||
|
const uint8_t test_prefix[] = {7, 66, 25, 0, 18, 0};
|
||||||
|
size_t size;
|
||||||
|
if (tms2812_build_memory_request(10, 0, 0, 0, output, sizeof output) != 8U ||
|
||||||
|
memcmp(output, ping, sizeof ping) != 0) return 10;
|
||||||
|
if (tms2812_build_memory_request(10, TMS2812_CMD_POKE, 0x12345678UL, 0xABCD,
|
||||||
|
output, sizeof output) != 12U || output[2] != 0x78 || output[5] != 0x12 ||
|
||||||
|
output[6] != 0xCD || output[7] != 0xAB || output[8] != 0 || output[9] != 0 ||
|
||||||
|
tms2812_crc16(output, 12U) != 0U) return 11;
|
||||||
|
if (tms2812_build_memory_request(10, 99, 0, 0, output, sizeof output) != 0U ||
|
||||||
|
tms2812_build_memory_request(10, 56, 0, 0, output, 7) != 0U) return 12;
|
||||||
|
if (tms2812_build_test_request(7, 25, 18, digits, 12, output, sizeof output) != 20U ||
|
||||||
|
memcmp(output, test_prefix, sizeof test_prefix) || memcmp(output + 6, digits, 12) ||
|
||||||
|
tms2812_crc16(output, 20) != 0U) return 13;
|
||||||
|
if (tms2812_build_test_request(7, 25, 18, digits, 11, output, sizeof output) != 0U ||
|
||||||
|
tms2812_build_fixed_request(7, 65, NULL, 1, output, sizeof output) != 0U) return 14;
|
||||||
|
for (size = 8; size <= 10; ++size)
|
||||||
|
if (tms2812_validate_response(reply, size, 10, 56, 10, 1) != TMS2812_OK) return 15;
|
||||||
|
if (tms2812_validate_response(reply, 7, 10, 56, 10, 1) != TMS2812_ERROR_LENGTH ||
|
||||||
|
tms2812_validate_response(reply, 8, 10, 56, 10, 0) != TMS2812_ERROR_LENGTH ||
|
||||||
|
tms2812_validate_response(reply, 10, 11, 56, 10, 1) != TMS2812_ERROR_HEADER) return 16;
|
||||||
|
reply[2] ^= 1;
|
||||||
|
if (tms2812_validate_response(reply, 10, 10, 56, 10, 1) != TMS2812_ERROR_CRC) return 17;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int main(void)
|
int main(void)
|
||||||
{
|
{
|
||||||
|
int terminal_status = test_terminal();
|
||||||
|
if (terminal_status != 0) return terminal_status;
|
||||||
uint8_t request[TMS2812_UPLOAD_REQUEST_SIZE];
|
uint8_t request[TMS2812_UPLOAD_REQUEST_SIZE];
|
||||||
static const uint8_t prefix[] = {
|
static const uint8_t prefix[] = {
|
||||||
0x05U, 0x34U, 0x78U, 0x56U, 0x34U, 0x12U,
|
0x05U, 0x34U, 0x78U, 0x56U, 0x34U, 0x12U,
|
||||||
|
|||||||
134
python/protocan/tms_terminal.py
Normal file
134
python/protocan/tms_terminal.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
"""Thin host binding for legacy TMS2812 requests and ordinary responses.
|
||||||
|
|
||||||
|
The wire implementation is c/set-protocol/src/tms2812.c, also used by JNI.
|
||||||
|
This is not the SETProtocol v2 wire format.
|
||||||
|
"""
|
||||||
|
import ctypes as ct
|
||||||
|
|
||||||
|
from .native import get_native_protocol, NativeProtocolUnavailable
|
||||||
|
from .tms2812 import crc16_modbus
|
||||||
|
|
||||||
|
PEEK = 56
|
||||||
|
POKE = 57
|
||||||
|
PING_ADDRESS = 0x100
|
||||||
|
|
||||||
|
|
||||||
|
def _api(name, arguments, result=ct.c_size_t):
|
||||||
|
try:
|
||||||
|
function = getattr(get_native_protocol().lib, "pcan_abi_tms2812_" + name)
|
||||||
|
except AttributeError as error:
|
||||||
|
raise NativeProtocolUnavailable("Пересоберите templates: отсутствует TMS2812 terminal API") from error
|
||||||
|
function.argtypes = arguments
|
||||||
|
function.restype = result
|
||||||
|
return function
|
||||||
|
|
||||||
|
|
||||||
|
def _range(value, maximum, name):
|
||||||
|
if not isinstance(value, int) or not 0 <= value <= maximum:
|
||||||
|
raise ValueError(f"{name} вне диапазона 0…{maximum}")
|
||||||
|
|
||||||
|
|
||||||
|
def build_fixed_request(controller, command, payload):
|
||||||
|
_range(controller, 255, "Адрес контроллера")
|
||||||
|
_range(command, 255, "Команда")
|
||||||
|
payload = bytes(payload)
|
||||||
|
output = ct.create_string_buffer(len(payload) + 4)
|
||||||
|
function = _api("build_fixed", [ct.c_uint8, ct.c_uint8, ct.c_void_p, ct.c_size_t, ct.c_void_p, ct.c_size_t])
|
||||||
|
size = function(controller, command, payload, len(payload), output, len(output))
|
||||||
|
if not size:
|
||||||
|
raise ValueError("Не удалось сформировать команду TMS2812")
|
||||||
|
return output.raw[:size]
|
||||||
|
|
||||||
|
|
||||||
|
def _memory(controller, command, address, value):
|
||||||
|
_range(controller, 255, "Адрес контроллера")
|
||||||
|
_range(address, 0xFFFFFFFF, "Адрес слова")
|
||||||
|
_range(value, 0xFFFF, "Значение")
|
||||||
|
output = ct.create_string_buffer(12)
|
||||||
|
function = _api("build_memory", [ct.c_uint8, ct.c_uint8, ct.c_uint32, ct.c_uint16, ct.c_void_p, ct.c_size_t])
|
||||||
|
size = function(controller, command, address, value, output, len(output))
|
||||||
|
if not size:
|
||||||
|
raise ValueError("Не удалось сформировать команду памяти TMS2812")
|
||||||
|
return output.raw[:size]
|
||||||
|
|
||||||
|
|
||||||
|
def request(controller, address, value=None):
|
||||||
|
return _memory(controller, PEEK if value is None else POKE, address, 0 if value is None else value)
|
||||||
|
|
||||||
|
|
||||||
|
def ping(controller):
|
||||||
|
return _memory(controller, 0, 0, 0)
|
||||||
|
|
||||||
|
|
||||||
|
def build_test_request(address, duration, pause, digits):
|
||||||
|
_range(address, 255, "Адрес контроллера")
|
||||||
|
_range(duration, 65535, "Длительность")
|
||||||
|
_range(pause, 65535, "Пауза")
|
||||||
|
digits = bytes(digits)
|
||||||
|
if len(digits) != 12:
|
||||||
|
raise ValueError("CMD_TEST_ALL: требуется 12 байт управления")
|
||||||
|
output = ct.create_string_buffer(20)
|
||||||
|
function = _api("build_test", [ct.c_uint8, ct.c_uint16, ct.c_uint16, ct.c_void_p, ct.c_size_t, ct.c_void_p, ct.c_size_t])
|
||||||
|
size = function(address, duration, pause, digits, len(digits), output, len(output))
|
||||||
|
if not size:
|
||||||
|
raise ValueError("Не удалось сформировать CMD_TEST_ALL")
|
||||||
|
return output.raw[:size]
|
||||||
|
|
||||||
|
|
||||||
|
def validate_response(raw, controller, command, full_size, allow_short_tail=False):
|
||||||
|
_range(controller, 255, "Адрес контроллера")
|
||||||
|
_range(command, 255, "Команда")
|
||||||
|
if full_size < 6:
|
||||||
|
raise ValueError("Неверная длина ответа TMS2812")
|
||||||
|
function = _api("validate_response", [ct.c_void_p, ct.c_size_t, ct.c_uint8, ct.c_uint8, ct.c_size_t, ct.c_int], ct.c_int)
|
||||||
|
status = function(bytes(raw), len(raw), controller, command, full_size, bool(allow_short_tail))
|
||||||
|
errors = {-1: "Неверный формат ответа TMS2812", -3: "Неверная длина ответа TMS2812",
|
||||||
|
-4: "CRC ответа TMS2812 не совпадает", -5: "Неверный адрес или команда ответа TMS2812"}
|
||||||
|
if status:
|
||||||
|
raise ValueError(errors.get(status, f"Ошибка TMS2812: {status}"))
|
||||||
|
|
||||||
|
|
||||||
|
def decode_reply(raw, controller, command):
|
||||||
|
if command not in (PEEK, POKE):
|
||||||
|
raise ValueError("Ожидается CMD_PEEK или CMD_POKE")
|
||||||
|
validate_response(raw, controller, command, 10 if command == PEEK else 6, True)
|
||||||
|
return int.from_bytes(raw[2:4], "little") if command == PEEK else None
|
||||||
|
|
||||||
|
|
||||||
|
def memory_response(raw, controller, command):
|
||||||
|
"""Find a PEEK/POKE response in a UART buffer, as Android feedLegacy does.
|
||||||
|
|
||||||
|
Returns None until data plus CRC are present. A short valid result still
|
||||||
|
needs the transport's settle timer before the next request. Ignore noise,
|
||||||
|
an optional preliminary ACK, and bytes after the complete response.
|
||||||
|
"""
|
||||||
|
_range(controller, 255, "Адрес контроллера")
|
||||||
|
if command not in (PEEK, POKE):
|
||||||
|
raise ValueError("Ожидается CMD_PEEK или CMD_POKE")
|
||||||
|
header = bytes((controller, command))
|
||||||
|
start = bytes(raw).find(header)
|
||||||
|
if start < 0:
|
||||||
|
return None
|
||||||
|
data = bytes(raw[start:])
|
||||||
|
size = 10 if command == PEEK else 6
|
||||||
|
if size > 6:
|
||||||
|
for ack_size in (6, 4):
|
||||||
|
if data[ack_size:ack_size + 2] != header:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
validate_response(data[:ack_size], controller, command, 6, True)
|
||||||
|
except ValueError:
|
||||||
|
continue
|
||||||
|
data = data[ack_size:]
|
||||||
|
break
|
||||||
|
if len(data) >= size:
|
||||||
|
packet = data[:size]
|
||||||
|
validate_response(packet, controller, command, size)
|
||||||
|
return packet
|
||||||
|
if len(data) >= size - 2:
|
||||||
|
try:
|
||||||
|
validate_response(data, controller, command, size, True)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
return data
|
||||||
|
return None
|
||||||
43
python/tests/test_tms_terminal_shared.py
Normal file
43
python/tests/test_tms_terminal_shared.py
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
"""Wire vectors shared with Android and the native TMS2812 host test."""
|
||||||
|
import pytest
|
||||||
|
from protocan import tms_terminal as tms
|
||||||
|
|
||||||
|
|
||||||
|
def test_ping_and_poke_vectors():
|
||||||
|
assert tms.ping(10).hex() == "0a3800010000f0b4"
|
||||||
|
raw = tms.request(10, 0x12345678, 0xABCD)
|
||||||
|
assert raw[:10].hex() == "0a3978563412cdab0000"
|
||||||
|
assert tms.crc16_modbus(raw) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_test_all_vector():
|
||||||
|
digits = bytes.fromhex("810204080920102040800304")
|
||||||
|
raw = tms.build_test_request(7, 25, 18, digits)
|
||||||
|
assert raw[:18].hex() == "074219001200810204080920102040800304"
|
||||||
|
assert len(raw) == 20 and tms.crc16_modbus(raw) == 0
|
||||||
|
|
||||||
|
|
||||||
|
def test_response_accepts_only_missing_reserved_bytes():
|
||||||
|
raw = bytes.fromhex("0a3865a50000af9b0000")
|
||||||
|
for size in (8, 9, 10):
|
||||||
|
assert tms.decode_reply(raw[:size], 10, 56) == 0xA565
|
||||||
|
for invalid in (raw[:7], raw + b"\0", raw[:2] + b"\0" + raw[3:]):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
tms.decode_reply(invalid, 10, 56)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
tms.decode_reply(raw, 11, 56)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("args", [(256, 0, None), (1, -1, None), (1, 2**32, None), (1, 0, -1), (1, 0, 65536)])
|
||||||
|
def test_rejects_out_of_range_before_native_conversion(args):
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
tms.request(*args)
|
||||||
|
|
||||||
|
|
||||||
|
def test_uart_response_ignores_noise_and_preliminary_ack():
|
||||||
|
response = bytes.fromhex("0a3865a50000af9b0000")
|
||||||
|
ack = tms.build_fixed_request(10, 56, b"")
|
||||||
|
for prefix in (b"", ack, ack + b"\0\0"):
|
||||||
|
assert tms.memory_response(b"noise" + prefix + response, 10, 56) == response
|
||||||
|
assert tms.memory_response(response, 11, 56) is None
|
||||||
|
assert tms.memory_response(response[:7], 10, 56) is None
|
||||||
Reference in New Issue
Block a user